blob: 1e88a9a0ed09784b4e93f171b4148508a10e82bf [file]
/*
* Copyright (C) 2022 Andreas Steffen, strongSec GmbH
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include "pki.h"
#include "pki_cert.h"
#include <credentials/certificates/certificate.h>
#include <credentials/certificates/x509.h>
#include <credentials/containers/pkcs7.h>
/*
* Certificate types
*/
typedef enum {
CERT_TYPE_ROOT_CA,
CERT_TYPE_SUB_CA,
CERT_TYPE_RA
} pki_cert_type_t;
static char *cert_type_label[] DBG_UNUSED = { "Root CA", "Sub CA", "RA" };
/**
* Determine certificate type based on X.509 certificate flags
*/
static pki_cert_type_t get_pki_cert_type(certificate_t *cert)
{
x509_t *x509;
x509_flag_t flags;
x509 = (x509_t*)cert;
flags = x509->get_flags(x509);
if (flags & X509_CA)
{
if (flags & X509_SELF_SIGNED)
{
return CERT_TYPE_ROOT_CA;
}
else
{
return CERT_TYPE_SUB_CA;
}
}
else
{
return CERT_TYPE_RA;
}
}
/**
* Output cert type, subject as well as SHA256 and SHA1 fingerprints
*/
static bool print_cert_info(certificate_t *cert, pki_cert_type_t cert_type)
{
hasher_t *hasher = NULL;
char digest_buf[HASH_SIZE_SHA256];
char base64_buf[HASH_SIZE_SHA256];
chunk_t cert_digest = {digest_buf, HASH_SIZE_SHA256};
chunk_t cert_id DBG_UNUSED, serial DBG_UNUSED, encoding = chunk_empty;
x509_t *x509;
bool success = FALSE;
DBG1(DBG_APP, "%s cert \"%Y\"", cert_type_label[cert_type],
cert->get_subject(cert));
x509 = (x509_t*)cert;
serial = x509->get_serial(x509);
DBG1(DBG_APP, " serial: %#B", &serial);
if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding))
{
DBG1(DBG_APP, "could not get certificate encoding");
return FALSE;
}
/* SHA256 certificate digest */
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA256);
if (!hasher)
{
DBG1(DBG_APP, "could not create SHA256 hasher");
goto end;
}
if (!hasher->get_hash(hasher, encoding, digest_buf))
{
DBG1(DBG_APP, "could not compute SHA256 hash");
goto end;
}
hasher->destroy(hasher);
DBG1(DBG_APP, " SHA256: %#B", &cert_digest);
/* SHA1 certificate digest */
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
if (!hasher)
{
DBG1(DBG_APP, "could not create SHA1 hasher");
goto end;
}
if (!hasher->get_hash(hasher, encoding, digest_buf))
{
DBG1(DBG_APP, "could not compute SHA1 hash");
goto end;
}
cert_digest.len = HASH_SIZE_SHA1;
cert_id = chunk_to_base64(cert_digest, base64_buf);
DBG1(DBG_APP, " SHA1 : %#B (%.*s)", &cert_digest,
cert_id.len-1, cert_id.ptr);
success = TRUE;
end:
DESTROY_IF(hasher);
chunk_free(&encoding);
return success;
}
/**
* Build a CA or RA pathname
*/
static bool build_pathname(char **path, pki_cert_type_t cert_type,
int *cert_type_count, char *caout, char *raout,
cred_encoding_type_t form)
{
char *basename, *extension, *dot, *suffix;
int count, len;
bool number;
basename = caout;
extension = "";
suffix = (form == CERT_ASN1_DER) ? "der" : "pem";
count = cert_type_count[cert_type];
number = count > 1;
switch (cert_type)
{
default:
case CERT_TYPE_ROOT_CA:
if (count > 1)
{
extension = "-root";
}
break;
case CERT_TYPE_SUB_CA:
number = TRUE;
break;
case CERT_TYPE_RA:
if (raout)
{
basename = raout;
}
else
{
extension = "-ra";
}
break;
}
/* skip if no path is defined */
if (!basename)
{
*path = NULL;
return TRUE;
}
/* check for a file suffix */
dot = strrchr(basename, '.');
len = dot ? (dot - basename) : strlen(basename);
if (dot && (dot[1] != '\0'))
{
suffix = dot + 1;
}
if (number)
{
return asprintf(path, "%.*s%s-%d.%s", len, basename, extension,
count, suffix) > 0;
}
else
{
return asprintf(path, "%.*s%s.%s", len, basename, extension, suffix) > 0;
}
}
/**
* Write CA/RA certificate to file in DER or PEM format
*/
static bool write_cert(certificate_t *cert, pki_cert_type_t cert_type,
bool trusted, char *path, cred_encoding_type_t form,
bool force)
{
chunk_t encoding = chunk_empty;
bool written;
if (path)
{
if (!cert->get_encoding(cert, form, &encoding))
{
DBG1(DBG_APP, "could not get certificate encoding");
return FALSE;
}
written = chunk_write(encoding, path, 0022, force);
chunk_free(&encoding);
if (!written)
{
DBG1(DBG_APP, "could not write cert file '%s': %s",
path, strerror(errno));
return FALSE;
}
}
else if (form == CERT_PEM)
{
if (!cert->get_encoding(cert, form, &encoding))
{
DBG1(DBG_APP, "could not get certificate encoding");
return FALSE;
}
printf("%.*s", (int)encoding.len, encoding.ptr);
chunk_free(&encoding);
path = "stdout";
}
#if DEBUG_LEVEL >= 1
time_t until;
bool valid = cert->get_validity(cert, NULL, NULL, &until);
DBG1(DBG_APP, "%s cert is %strusted, %s %T, %s'%s'",
cert_type_label[cert_type], trusted ? "" : "un",
valid ? "valid until" : "invalid since", &until, FALSE,
path ? "written to " : "", path ? path : "not written");
#endif
return TRUE;
}
/**
* Extract X.509 CA [and SCEP RA] certificates from PKCS#7 container,
* check trust as well as validity and write to files
*/
bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout,
bool is_scep, cred_encoding_type_t form,
bool force)
{
container_t *container;
mem_cred_t *creds = NULL;
certificate_t *cert;
pki_cert_type_t cert_type;
bool written = FALSE, success = FALSE;
char *path;
int cert_type_count[] = { 0, 0, 0 };
creds = mem_cred_create();
lib->credmgr->add_set(lib->credmgr, &creds->set);
container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, data, BUILD_END);
if (!container)
{
if (is_scep)
{
/* no PKCS#7 encoded certificates, assume single root CA cert */
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_BLOB, data, BUILD_END);
if (!cert)
{
DBG1(DBG_APP, "could not parse single CA certificate");
goto end;
}
cert_type = get_pki_cert_type(cert);
cert_type_count[cert_type]++;
if (print_cert_info(cert, cert_type) &&
build_pathname(&path, cert_type, cert_type_count, caout, raout,
form))
{
written = write_cert(cert, cert_type, FALSE, path, form, force);
}
}
else
{
DBG1(DBG_APP, "did not receive a valid pkcs7 container");
goto end;
}
}
else
{
enumerator_t *enumerator;
pkcs7_t *pkcs7 = (pkcs7_t*)container;
certificate_t *cert_found;
enumerator_t *certs;
bool trusted;
enumerator = pkcs7->create_cert_enumerator(pkcs7);
while (enumerator->enumerate(enumerator, &cert))
{
trusted = FALSE;
cert_type = get_pki_cert_type(cert);
if (cert_type == CERT_TYPE_ROOT_CA)
{
if (!print_cert_info(cert, cert_type))
{
goto end;
}
/* same root CA as trusted TLS root CA already in cred set? */
certs = lib->credmgr->create_trusted_enumerator(lib->credmgr,
KEY_ANY, cert->get_subject(cert), FALSE);
while (certs->enumerate(certs, &cert_found, NULL))
{
if (cert->equals(cert, cert_found))
{
DBG1(DBG_APP, "Root CA equals trusted TLS Root CA");
trusted = TRUE;
break;
}
else
{
DBG1(DBG_APP, "non-matching TLS Root CA of same name");
}
}
certs->destroy(certs);
/* otherwise trust in root CA has to be established manually */
if (!trusted)
{
creds->add_cert(creds, TRUE, cert->get_ref(cert));
trusted = FALSE;
}
cert_type_count[cert_type]++;
if (build_pathname(&path, cert_type, cert_type_count, caout,
raout, form))
{
written = write_cert(cert, cert_type, trusted, path, form,
force);
free(path);
}
if (!written)
{
break;
}
}
else
{
/* trust relative to root CA will be established in round 2 */
creds->add_cert(creds, FALSE, cert->get_ref(cert));
}
}
enumerator->destroy(enumerator);
if (!written)
{
goto end;
}
enumerator = pkcs7->create_cert_enumerator(pkcs7);
while (enumerator->enumerate(enumerator, &cert))
{
written = FALSE;
trusted = FALSE;
cert_type = get_pki_cert_type(cert);
if (cert_type != CERT_TYPE_ROOT_CA)
{
if (!print_cert_info(cert, cert_type))
{
break;
}
/* establish trust relative to root CA */
certs = lib->credmgr->create_trusted_enumerator(lib->credmgr,
KEY_ANY, cert->get_subject(cert), FALSE);
while (certs->enumerate(certs, &cert_found, NULL))
{
if (cert->equals(cert, cert_found))
{
trusted = TRUE;
break;
}
else
{
DBG1(DBG_APP, "non-matching TLS Sub CA of same name");
}
}
certs->destroy(certs);
cert_type_count[cert_type]++;
if (build_pathname(&path, cert_type, cert_type_count, caout,
raout, form))
{
written = write_cert(cert, cert_type, trusted, path, form,
force);
free(path);
}
if (!written)
{
break;
}
}
}
enumerator->destroy(enumerator);
}
success = TRUE;
end:
/* cleanup */
lib->credmgr->remove_set(lib->credmgr, &creds->set);
creds->destroy(creds);
DESTROY_IF(container);
return success;
}
/**
* Extract an X.509 client certificates from PKCS#7 container
* check trust as well as validity and write to stdout
*/
bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form)
{
pkcs7_t *pkcs7;
container_t *container;
certificate_t *cert;
mem_cred_t *client_creds;
chunk_t cert_encoding = chunk_empty;
enumerator_t *enumerator;
bool stored = FALSE;
/* parse pkcs7 signed-data container */
container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, data, BUILD_END);
if (!container)
{
DBG1(DBG_APP, "could not parse pkcs7 signed-data container");
return FALSE;
}
lib->credmgr->flush_cache(lib->credmgr, CERT_X509);
client_creds = mem_cred_create();
lib->credmgr->add_set(lib->credmgr, &client_creds->set);
/* store the end entity certificate */
pkcs7 = (pkcs7_t*)container;
enumerator = pkcs7->create_cert_enumerator(pkcs7);
while (enumerator->enumerate(enumerator, &cert))
{
x509_t *x509 = (x509_t*)cert;
certificate_t *cert_found = NULL;
enumerator_t *certs;
bool trusted DBG_UNUSED;
if (!(x509->get_flags(x509) & X509_CA))
{
DBG1(DBG_APP, "Issued certificate \"%Y\"", cert->get_subject(cert));
#if DEBUG_LEVEL >= 1
chunk_t serial = x509->get_serial(x509);
DBG1(DBG_APP, " serial: %#B", &serial);
#endif
if (stored)
{
DBG1(DBG_APP, "multiple certs received, only first stored");
continue;
}
/* establish trust relative to root CA */
client_creds->add_cert(client_creds, FALSE, cert->get_ref(cert));
certs = lib->credmgr->create_trusted_enumerator(lib->credmgr,
KEY_ANY, cert->get_subject(cert), FALSE);
trusted = certs->enumerate(certs, &cert_found, NULL) &&
(cert_found == cert);
certs->destroy(certs);
#if DEBUG_LEVEL >= 1
time_t from, until;
bool valid = cert->get_validity(cert, NULL, &from, &until);
DBG1(DBG_APP, "Issued certificate is %strusted, "
"valid from %T until %T (currently %svalid)",
trusted ? "" : "not ", &from, FALSE, &until, FALSE,
valid ? "" : "not ");
#endif
if (!cert->get_encoding(cert, form, &cert_encoding))
{
DBG1(DBG_APP, "encoding certificate failed");
break;
}
set_file_mode(stdout, form);
stored = fwrite(cert_encoding.ptr, cert_encoding.len, 1, stdout) == 1;
chunk_free(&cert_encoding);
if (!stored)
{
DBG1(DBG_APP, "writing certificate failed");
break;
}
}
}
enumerator->destroy(enumerator);
container->destroy(container);
lib->credmgr->remove_set(lib->credmgr, &client_creds->set);
client_creds->destroy(client_creds);
return stored;
}