From 685127394468151308299327e62f7806e7c46025 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Fri, 29 Jul 2022 06:48:41 +0200 Subject: [PATCH 01/24] pki: Get CA certs via SCEP --- src/pki/Makefile.am | 4 +- src/pki/command.h | 2 +- src/pki/commands/scepca.c | 439 ++++++++++++++++++++++++++++++++++++ src/pki/scep/scep.c | 461 ++++++++++++++++++++++++++++++++++++++ src/pki/scep/scep.h | 104 +++++++++ 5 files changed, 1008 insertions(+), 2 deletions(-) create mode 100644 src/pki/commands/scepca.c create mode 100644 src/pki/scep/scep.c create mode 100644 src/pki/scep/scep.h diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 1153794cd..fcced120e 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -13,9 +13,11 @@ pki_SOURCES = pki.c pki.h command.c command.h \ commands/print.c \ commands/pub.c \ commands/req.c \ + commands/scepca.c \ commands/self.c \ commands/signcrl.c \ - commands/verify.c + commands/verify.c \ + scep/scep.h scep/scep.c pki_LDADD = \ $(top_builddir)/src/libstrongswan/libstrongswan.la \ diff --git a/src/pki/command.h b/src/pki/command.h index f9be176ea..bdb402a86 100644 --- a/src/pki/command.h +++ b/src/pki/command.h @@ -25,7 +25,7 @@ /** * Maximum number of commands (+1). */ -#define MAX_COMMANDS 14 +#define MAX_COMMANDS 15 /** * Maximum number of options in a command (+3) diff --git a/src/pki/commands/scepca.c b/src/pki/commands/scepca.c new file mode 100644 index 000000000..a443155f3 --- /dev/null +++ b/src/pki/commands/scepca.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2012 Tobias Brunner + * 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 . + * + * 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 +#include +#include +#include +#include + +#include "pki.h" +#include "scep/scep.h" + +#include +#include +#include + + +typedef enum { + CERT_TYPE_ROOT_CA, + CERT_TYPE_SUB_CA, + CERT_TYPE_RA +} cert_type_t; + +static char *cert_type_label[] = { "Root CA", "Sub CA", "RA" }; + +/** + * Determine certificate type based on X.509 certificate flags + */ +static cert_type_t get_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, 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, encoding = chunk_empty; + bool success = FALSE; + + DBG1(DBG_APP, "%s cert \"%Y\"", cert_type_label[cert_type], + cert->get_subject(cert)); + + 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; +} + +static bool build_pathname(char **path, 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; + } +} + +/** + * Writo CA/RA certificate to file in DER or PEM format + */ +static bool write_cert(certificate_t *cert, cert_type_t cert_type, bool trusted, + char *path, cred_encoding_type_t form, bool force) +{ + chunk_t encoding = chunk_empty; + time_t until; + bool written, valid; + + 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; + } + } + 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"); + + return TRUE; +} + +/** + * Get CA certificate[s] from a SCEP server (RFC 8894) + */ +static int scepca() +{ + cred_encoding_type_t form = CERT_ASN1_DER; + chunk_t scep_response = chunk_empty; + mem_cred_t *creds = NULL; + certificate_t *cert; + cert_type_t cert_type; + pkcs7_t *pkcs7 = NULL; + bool force = FALSE, written = FALSE; + char *arg, *url = NULL, *caout = NULL, *raout = NULL, *path = NULL; + int status = 1; + + int cert_type_count[] = { 0, 0, 0 }; + + scep_http_params_t http_params = { + .get_request = TRUE, .timeout = 30, .bind = NULL + }; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'u': + url = arg; + continue; + case 'c': + caout = arg; + continue; + case 'r': + raout = arg; + continue; + case 'f': + if (!get_form(arg, &form, CRED_CERTIFICATE)) + { + return command_usage("invalid certificate output format"); + } + continue; + case 'F': + force = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --scepca option"); + } + break; + } + + if (!url) + { + return command_usage("--url is required"); + } + + if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CERT, &http_params, + &scep_response)) + { + DBG1(DBG_APP, "did not receive a valid scep response"); + return 1; + } + + creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &creds->set); + + pkcs7 = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, + BUILD_BLOB_ASN1_DER, scep_response, BUILD_END); + if (!pkcs7) + { /* no PKCS#7 encoded CA+RA certificates, assume single root CA cert */ + + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB, scep_response, BUILD_END); + if (!cert) + { + DBG1(DBG_APP, "could not parse single CA certificate"); + goto end; + } + cert_type = get_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 + { + enumerator_t *enumerator; + + enumerator = pkcs7->create_cert_enumerator(pkcs7); + while (enumerator->enumerate(enumerator, &cert)) + { + cert_type = get_cert_type(cert); + if (cert_type == CERT_TYPE_ROOT_CA) + { + /* trust in root CA has to be established manuallly */ + creds->add_cert(creds, TRUE, cert->get_ref(cert)); + + cert_type_count[cert_type]++; + + if (!print_cert_info(cert, cert_type)) + { + goto end; + } + if (build_pathname(&path, cert_type, cert_type_count, + caout, raout, form)) + { + written = write_cert(cert, cert_type, FALSE, 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; + + cert_type = get_cert_type(cert); + if (cert_type != CERT_TYPE_ROOT_CA) + { + enumerator_t *certs; + bool trusted; + + if (!print_cert_info(cert, cert_type)) + { + break; + } + + /* establish trust relativ to root CA */ + certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, + KEY_RSA, cert->get_subject(cert), FALSE); + trusted = certs->enumerate(certs, &cert, NULL); + 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); + } + status = written ? 0 : 1; + +end: + /* cleanup */ + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + free(scep_response.ptr); + if (pkcs7) + { + container_t *container = &pkcs7->container; + + container->destroy(container); + } + + return status; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + scepca, 'C', "scepca", + "get CA [and RA] certificate[s] from a SCEP server", + {"--url url [--caout file] [--raout file] [--outform der|pem] [--force]"}, + { + {"help", 'h', 0, "show usage information"}, + {"url", 'u', 1, "URL of the SCEP server"}, + {"caout", 'c', 1, "CA certificate [template]"}, + {"raout", 'r', 1, "RA certificate [template]"}, + {"outform", 'f', 1, "encoding of stored certificates, default: der"}, + {"force", 'F', 0, "force overwrite of existing files"}, + } + }); +} diff --git a/src/pki/scep/scep.c b/src/pki/scep/scep.c new file mode 100644 index 000000000..24fa93b20 --- /dev/null +++ b/src/pki/scep/scep.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2005 Jan Hutter, Martin Willi + * 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 . + * + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "scep.h" + +static const char *pkiStatus_values[] = { "0", "2", "3" }; + +static const char *pkiStatus_names[] = { + "SUCCESS", + "FAILURE", + "PENDING", + "UNKNOWN" +}; + +static const char *msgType_values[] = { "3", "17", "19", "20", "21", "22" }; + +static const char *msgType_names[] = { + "CertRep", + "RenewalReq", + "PKCSReq", + "CertPoll", + "GetCert", + "GetCRL", + "Unknown" +}; + +static const char *failInfo_reasons[] = { + "badAlg - unrecognized or unsupported algorithm identifier", + "badMessageCheck - integrity check failed", + "badRequest - transaction not permitted or supported", + "badTime - Message time field was not sufficiently close to the system time", + "badCertId - No certificate could be identified matching the provided criteria" +}; + +const scep_attributes_t empty_scep_attributes = { + SCEP_Unknown_MSG , /* msgType */ + SCEP_UNKNOWN , /* pkiStatus */ + SCEP_unknown_REASON, /* failInfo */ + { NULL, 0 } , /* transID */ + { NULL, 0 } , /* senderNonce */ + { NULL, 0 } , /* recipientNonce */ +}; + +/** + * Extract X.501 attributes + */ +void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator, + scep_attributes_t *attrs) +{ + chunk_t attr; + + if (pkcs7->get_attribute(pkcs7, OID_PKI_MESSAGE_TYPE, enumerator, &attr)) + { + scep_msg_t m; + + for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++) + { + if (strncmp(msgType_values[m], attr.ptr, attr.len) == 0) + { + attrs->msgType = m; + } + } + DBG2(DBG_APP, "messageType: %s", msgType_names[attrs->msgType]); + free(attr.ptr); + } + if (pkcs7->get_attribute(pkcs7, OID_PKI_STATUS, enumerator, &attr)) + { + pkiStatus_t s; + + for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++) + { + if (strncmp(pkiStatus_values[s], attr.ptr, attr.len) == 0) + { + attrs->pkiStatus = s; + } + } + DBG2(DBG_APP, "pkiStatus: %s", pkiStatus_names[attrs->pkiStatus]); + free(attr.ptr); + } + if (pkcs7->get_attribute(pkcs7, OID_PKI_FAIL_INFO, enumerator, &attr)) + { + if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4') + { + attrs->failInfo = (failInfo_t)(*attr.ptr - '0'); + } + if (attrs->failInfo != SCEP_unknown_REASON) + { + DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]); + } + free(attr.ptr); + } + + pkcs7->get_attribute(pkcs7, OID_PKI_SENDER_NONCE, enumerator, + &attrs->senderNonce); + pkcs7->get_attribute(pkcs7, OID_PKI_RECIPIENT_NONCE, enumerator, + &attrs->recipientNonce); + pkcs7->get_attribute(pkcs7, OID_PKI_TRANS_ID, enumerator, + &attrs->transID); +} + +/** + * Generates a unique fingerprint of the pkcs10 request + * by computing an MD5 hash over it + */ +chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10) +{ + chunk_t digest = chunk_alloca(HASH_SIZE_MD5); + hasher_t *hasher; + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); + if (!hasher || !hasher->get_hash(hasher, pkcs10, digest.ptr)) + { + DESTROY_IF(hasher); + return chunk_empty; + } + hasher->destroy(hasher); + + return chunk_to_hex(digest, NULL, FALSE); +} + +/** + * Generate a transaction id as the MD5 hash of an public key + * the transaction id is also used as a unique serial number + */ +void scep_generate_transaction_id(public_key_t *key, chunk_t *transID, + chunk_t *serialNumber) +{ + chunk_t digest = chunk_alloca(HASH_SIZE_MD5); + chunk_t keyEncoding = chunk_empty, keyInfo; + hasher_t *hasher; + int zeros = 0, msb_set = 0; + + key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding); + + keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_algorithmIdentifier(OID_RSA_ENCRYPTION), + asn1_bitstring("m", keyEncoding)); + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); + if (!hasher || !hasher->get_hash(hasher, keyInfo, digest.ptr)) + { + memset(digest.ptr, 0, digest.len); + } + DESTROY_IF(hasher); + free(keyInfo.ptr); + + /* the serialNumber should be valid ASN1 integer content: + * remove leading zeros, add one if MSB is set (two's complement) */ + while (zeros < digest.len) + { + if (digest.ptr[zeros]) + { + if (digest.ptr[zeros] & 0x80) + { + msb_set = 1; + } + break; + } + zeros++; + } + *serialNumber = chunk_alloc(digest.len - zeros + msb_set); + if (msb_set) + { + serialNumber->ptr[0] = 0x00; + } + memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros, + digest.len - zeros); + + /* the transaction id is the serial number in hex format */ + *transID = chunk_to_hex(digest, NULL, TRUE); +} + +/** + * Builds a pkcs7 enveloped and signed scep request + */ +chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, + certificate_t *enc_cert, encryption_algorithm_t enc_alg, + size_t key_size, certificate_t *signer_cert, + hash_algorithm_t digest_alg, private_key_t *private_key) +{ + chunk_t request; + container_t *container; + char nonce[16]; + rng_t *rng; + chunk_t senderNonce, msgType; + + /* generate senderNonce */ + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng || !rng->get_bytes(rng, sizeof(nonce), nonce)) + { + DESTROY_IF(rng); + return chunk_empty; + } + rng->destroy(rng); + + /* encrypt data in enveloped-data PKCS#7 */ + container = lib->creds->create(lib->creds, + CRED_CONTAINER, CONTAINER_PKCS7_ENVELOPED_DATA, + BUILD_BLOB, data, + BUILD_CERT, enc_cert, + BUILD_ENCRYPTION_ALG, enc_alg, + BUILD_KEY_SIZE, (int)key_size, + BUILD_END); + if (!container) + { + return chunk_empty; + } + if (!container->get_encoding(container, &request)) + { + container->destroy(container); + return chunk_empty; + } + container->destroy(container); + + /* sign enveloped-data in a signed-data PKCS#7 */ + senderNonce = asn1_wrap(ASN1_OCTET_STRING, "c", chunk_from_thing(nonce)); + transID = asn1_wrap(ASN1_PRINTABLESTRING, "c", transID); + msgType = asn1_wrap(ASN1_PRINTABLESTRING, "c", + chunk_create((char*)msgType_values[msg], + strlen(msgType_values[msg]))); + + container = lib->creds->create(lib->creds, + CRED_CONTAINER, CONTAINER_PKCS7_SIGNED_DATA, + BUILD_BLOB, request, + BUILD_SIGNING_CERT, signer_cert, + BUILD_SIGNING_KEY, private_key, + BUILD_DIGEST_ALG, digest_alg, + BUILD_PKCS7_ATTRIBUTE, OID_PKI_SENDER_NONCE, senderNonce, + BUILD_PKCS7_ATTRIBUTE, OID_PKI_TRANS_ID, transID, + BUILD_PKCS7_ATTRIBUTE, OID_PKI_MESSAGE_TYPE, msgType, + BUILD_END); + + free(request.ptr); + free(senderNonce.ptr); + free(transID.ptr); + free(msgType.ptr); + + if (!container) + { + return chunk_empty; + } + if (!container->get_encoding(container, &request)) + { + container->destroy(container); + return chunk_empty; + } + container->destroy(container); + + return request; +} + +/** + * Converts a binary request to base64 with 64 characters per line + * newline and '+' characters are escaped by %0A and %2B, respectively + */ +static char* escape_http_request(chunk_t req) +{ + char *escaped_req = NULL; + char *p1, *p2; + int lines = 0; + int plus = 0; + int n = 0; + + /* compute and allocate the size of the base64-encoded request */ + int len = 1 + 4 * ((req.len + 2) / 3); + char *encoded_req = malloc(len); + + /* do the base64 conversion */ + chunk_t base64 = chunk_to_base64(req, encoded_req); + len = base64.len + 1; + + /* compute newline characters to be inserted every 64 characters */ + lines = (len - 2) / 64; + + /* count number of + characters to be escaped */ + p1 = encoded_req; + while (*p1 != '\0') + { + if (*p1++ == '+') + { + plus++; + } + } + + escaped_req = malloc(len + 3 * (lines + plus)); + + /* escape special characters in the request */ + p1 = encoded_req; + p2 = escaped_req; + while (*p1 != '\0') + { + if (n == 64) + { + memcpy(p2, "%0A", 3); + p2 += 3; + n = 0; + } + if (*p1 == '+') + { + memcpy(p2, "%2B", 3); + p2 += 3; + } + else + { + *p2++ = *p1; + } + p1++; + n++; + } + *p2 = '\0'; + free(encoded_req); + return escaped_req; +} + +/** + * Send a SCEP request via HTTP and wait for a response + */ +bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, + scep_http_params_t *http_params, chunk_t *response) +{ + int len; + status_t status; + char *complete_url = NULL; + host_t *srcip = NULL; + + /* initialize response */ + *response = chunk_empty; + + if (http_params->bind) + { + srcip = host_create_from_string(http_params->bind, 0); + } + + DBG2(DBG_APP, "sending scep request to '%s'", url); + + if (op == SCEP_PKI_OPERATION) + { + const char operation[] = "PKIOperation"; + + if (http_params->get_request) + { + char *escaped_req = escape_http_request(msg); + + /* form complete url */ + len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s&message=%s" + , url, operation, escaped_req); + free(escaped_req); + + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + FETCH_TIMEOUT, http_params->timeout, + FETCH_REQUEST_HEADER, "Pragma:", + FETCH_REQUEST_HEADER, "Host:", + FETCH_REQUEST_HEADER, "Accept:", + FETCH_SOURCEIP, srcip, + FETCH_END); + } + else /* HTTP_POST */ + { + /* form complete url */ + len = strlen(url) + 11 + strlen(operation) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s", url, operation); + + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + FETCH_TIMEOUT, http_params->timeout, + FETCH_REQUEST_DATA, msg, + FETCH_REQUEST_TYPE, "", + FETCH_REQUEST_HEADER, "Expect:", + FETCH_SOURCEIP, srcip, + FETCH_END); + } + } + else /* SCEP_GET_CA_CERT */ + { + const char operation[] = "GetCACert"; + + /* form complete url */ + len = strlen(url) + 11 + strlen(operation) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s", url, operation); + + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + FETCH_TIMEOUT, http_params->timeout, + FETCH_SOURCEIP, srcip, + FETCH_END); + } + + DESTROY_IF(srcip); + free(complete_url); + return (status == SUCCESS); +} + +err_t scep_parse_response(chunk_t response, chunk_t transID, + container_t **out, scep_attributes_t *attrs) +{ + enumerator_t *enumerator; + bool verified = FALSE; + container_t *container; + auth_cfg_t *auth; + + container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, + BUILD_BLOB_ASN1_DER, response, BUILD_END); + if (!container) + { + return "error parsing the scep response"; + } + if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA) + { + container->destroy(container); + return "scep response is not PKCS#7 signed-data"; + } + + enumerator = container->create_signature_enumerator(container); + while (enumerator->enumerate(enumerator, &auth)) + { + verified = TRUE; + extract_attributes((pkcs7_t*)container, enumerator, attrs); + if (!chunk_equals(transID, attrs->transID)) + { + enumerator->destroy(enumerator); + container->destroy(container); + return "transaction ID of scep response does not match"; + } + } + enumerator->destroy(enumerator); + if (!verified) + { + container->destroy(container); + return "unable to verify PKCS#7 container"; + } + *out = container; + return NULL; +} diff --git a/src/pki/scep/scep.h b/src/pki/scep/scep.h new file mode 100644 index 000000000..c9e97b1a0 --- /dev/null +++ b/src/pki/scep/scep.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2005 Jan Hutter, Martin Willi + * 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 . + * + * 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. + */ + +#ifndef _SCEP_H +#define _SCEP_H + +#include +#include + +/* supported SCEP operation types */ +typedef enum { + SCEP_PKI_OPERATION, + SCEP_GET_CA_CERT +} scep_op_t; + +/* SCEP pkiStatus values */ +typedef enum { + SCEP_SUCCESS, + SCEP_FAILURE, + SCEP_PENDING, + SCEP_UNKNOWN +} pkiStatus_t; + +/* SCEP messageType values */ +typedef enum { + SCEP_CertRep_MSG, + SCEP_RenewalReq_MSG, + SCEP_PKCSReq_MSG, + SCEP_CertPoll_MSG, + SCEP_GetCert_MSG, + SCEP_GetCRL_MSG, + SCEP_Unknown_MSG +} scep_msg_t; + +/* SCEP failure reasons */ +typedef enum { + SCEP_badAlg_REASON = 0, + SCEP_badMessageCheck_REASON = 1, + SCEP_badRequest_REASON = 2, + SCEP_badTime_REASON = 3, + SCEP_badCertId_REASON = 4, + SCEP_unknown_REASON = 5 +} failInfo_t; + +/* SCEP attributes */ +typedef struct { + scep_msg_t msgType; + pkiStatus_t pkiStatus; + failInfo_t failInfo; + chunk_t transID; + chunk_t senderNonce; + chunk_t recipientNonce; +} scep_attributes_t; + +/* SCEP http parameters */ +typedef struct { + bool get_request; + u_int timeout; + char *bind; +} scep_http_params_t; + +extern const scep_attributes_t empty_scep_attributes; + +bool parse_attributes(chunk_t blob, scep_attributes_t *attrs); + +void scep_generate_transaction_id(public_key_t *key, + chunk_t *transID, + chunk_t *serialNumber); + +chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10); + +chunk_t scep_transId_attribute(chunk_t transaction_id); + +chunk_t scep_messageType_attribute(scep_msg_t m); + +chunk_t scep_senderNonce_attribute(void); + +chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, + certificate_t *enc_cert, encryption_algorithm_t enc_alg, + size_t key_size, certificate_t *signer_cert, + hash_algorithm_t digest_alg, private_key_t *private_key); + +bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, + scep_http_params_t *http_params, chunk_t *response); + +err_t scep_parse_response(chunk_t response, chunk_t transID, + container_t **out, scep_attributes_t *attrs); + +#endif /* _SCEP_H */ From a9d70bd4851816faa0822e438e7ea88243c6409c Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sat, 30 Jul 2022 14:21:50 +0200 Subject: [PATCH 02/24] pki: Created pki --scepca man page --- configure.ac | 1 + src/pki/man/Makefile.am | 1 + src/pki/man/pki---scepca.1.in | 149 ++++++++++++++++++++++++++++++++++ src/pki/man/pki.1.in | 30 ++++++- 4 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/pki/man/pki---scepca.1.in diff --git a/configure.ac b/configure.ac index dd9d128c1..7434d5861 100644 --- a/configure.ac +++ b/configure.ac @@ -2175,6 +2175,7 @@ AC_CONFIG_FILES([ src/pki/man/pki---print.1 src/pki/man/pki---pub.1 src/pki/man/pki---req.1 + src/pki/man/pki---scepca.1 src/pki/man/pki---self.1 src/pki/man/pki---signcrl.1 src/pki/man/pki---verify.1 diff --git a/src/pki/man/Makefile.am b/src/pki/man/Makefile.am index fc9440031..489c91367 100644 --- a/src/pki/man/Makefile.am +++ b/src/pki/man/Makefile.am @@ -9,6 +9,7 @@ man1_MANS = \ pki---print.1 \ pki---pub.1 \ pki---req.1 \ + pki---scepca.1 \ pki---self.1 \ pki---signcrl.1 \ pki---verify.1 diff --git a/src/pki/man/pki---scepca.1.in b/src/pki/man/pki---scepca.1.in new file mode 100644 index 000000000..ac37055c7 --- /dev/null +++ b/src/pki/man/pki---scepca.1.in @@ -0,0 +1,149 @@ +.TH "PKI \-\-SCEPCA" 1 "2022-08-22" "@PACKAGE_VERSION@" "strongSwan" +. +.SH "NAME" +. +pki \-\-scepca \- Get CA [and RA] certificate[s] from a SCEP server +. +.SH "SYNOPSIS" +. +.SY pki\ \-\-scepca +.BI\-\-\-url\~ url +.OP \-\-caout file +.OP \-\-raout file +.OP \-\-outform encoding +.OP \-\-force +.OP \-\-debug level +.YS +. +.SY pki\ \-\-scepca +.BI \-\-options\~ file +.YS +. +.SY "pki \-\-scepca" +.B \-h +| +.B \-\-help +.YS +. +.SH "DESCRIPTION" +. +This sub-command of +.BR pki (1) +gets CA and RA certificates via http from a SCEP server using the \fIGetCACert\fR +command of the Simple Certificate Enrollment Protocol (RFC 8894). +. +.SH "OPTIONS" +. +.TP +.B "\-h, \-\-help" +Print usage information with a summary of the available options. +.TP +.BI "\-v, \-\-debug " level +Set debug level, default: 1. +.TP +.BI "\-+, \-\-options " file +Read command line options from \fIfile\fR. +.TP +.BI "\-u, \-\-url " url +URL of the SCEP server. +.TP +.BI "\-c, \-\-caout " file +If present, path where the fetched root CA certificate file is stored to. +If several CA certificates are downloaded, then the value of +.B \-\-caout +is used as a template to derive unique filenames (*-1, *-2, etc.) for the +intermediate or sub CA certificates. +If a file suffix is missing, then depending on the value of +.B \-\-outform +either .\fIder\fR (the default) or .\fIpem\fR is automatically appended. +.TP +.BI "\-r, \-\-raout " file +If present, path where the fetched RA certificate file is stored to. +If multiple RA certificates are available, then the value of +.B \-\-raout +is used as a template to derive unique filenames (*-2, etc.). If the +.B \-\-raout +option is missing, then the value of +.B \-\-caout +is used as a template to derive unique filenames (*-ra, *-ra-2, etc.) for the RA +certificates. If a file suffix is missing, then depending on the value of +.B \-\-outform +either .\fIder\fR (the default) or .\fIpem\fR is automatically appended. +.TP +.BI "\-f, \-\-outform " encoding +Encoding of the created certificate file. Either \fIder\fR (ASN.1 DER) or +\fIpem\fR (Base64 PEM), defaults to \fIder\fR. +.TP +.B "\-F, \-\-force" +Force overwrite of existing files. +. +.SH "EXAMPLES" +. +A SCEP server sends a root CA and an intermediate CA certificate as well as an +RA certificate: +.PP +.EX +pki \-\-scepca \-\-url http://pki.strongswan.org:8080/scep \-\-caout myca.crt \-\-raout myra.crt + +Root CA cert "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + serial: 65:31:00:ca:79:da:16:6b:aa:ac:89:e2:a8:f9:49:c3:10:ab:64:54 + SHA256: 96:70:50:51:cd:b9:e7:94:6b:04:f6:15:45:80:fc:90:85:01:71:2a:f6:4f:d1:1b:2d:a1:7e:eb:bf:dd:be:86 + SHA1 : 8e:f3:78:b0:34:a6:c1:6a:7b:c6:f5:91:eb:e5:46:9b:0d:0a:a7:ba (jvN4sDSmwWp7xvWR6+VGmw0Kp7o) +Root CA cert is untrusted, valid until Aug 12 15:51:34 2032, 'myca.crt' +Sub CA cert "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + serial: 74:f9:7e:72:7d:b8:fd:f2:c6:e5:1b:fa:37:f9:cb:87:bf:9c:ea:e2 + SHA256: a3:5b:4b:12:d5:8f:68:7b:05:11:08:27:f5:42:62:b8:b5:01:1b:19:37:9c:28:78:5d:37:08:69:6a:8c:07:bf + SHA1 : 8c:e6:67:67:c2:23:89:7b:d0:bc:b1:50:d2:1c:bc:8d:8d:69:15:11 (jOZnZ8IjiXvQvLFQ0hy8jY1pFRE) + using certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 0 +Sub CA cert is trusted, valid until Aug 12 15:51:34 2027, 'mycacert-1.crt' +RA cert "C=CH, O=strongSwan Project, CN=SCEP RA" + serial: 74:f9:7e:72:7d:b8:fd:f2:c6:e5:1b:fa:37:f9:cb:87:bf:9c:ea:e3 + SHA256: 57:22:f3:13:69:2f:24:82:12:59:8e:05:63:0b:f5:a8:fb:4e:78:87:8d:68:d1:4c:c1:c4:b5:85:db:bb:64:df + SHA1 : bc:d1:46:76:55:7f:8c:d1:c5:22:31:b9:d7:b1:49:b5:95:a4:f3:ea (vNFGdlV/jNHFIjG517FJtZWk8+o) + using certificate "C=CH, O=strongSwan Project, CN=SCEP RA" + using untrusted intermediate certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +RA cert is trusted, valid until Aug 10 15:51:34 2023, 'myra.crt' +.EE +.PP +The trusthworthiness of the root CA certificate has to be established manually by +verifying the SHA256 or SHA1 fingerprint of the DER-encoded certificate that is +e.g. listed on the official PKI website or by some other means. +.P +The stored certificate files in DER format can be overwritten by PEM-encoded +versions with: +.PP +.EX +pki \-\-scepca \-\-url http://pki.strongswan.org:8080/scep \-\-caout myca.crt \-\-raout myra.crt \\ + \-\-outform pem \-\-force +.EE +.PP +If the +.B \-\-raout +option is omitted and the +.B \-\-caout +template doesn't have a file suffix, then with +.B \-\-outform +\fIpem\fR the following filenames are derived: +.PP +.EX +pki \-\-scepca \-\-url http://pki.strongswan.org:8080/scep \-\-caout scep/myca \-\-outform pem + +Root CA cert "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + ... +Root CA cert is untrusted, valid until Aug 12 15:51:34 2032, written to 'scep/myca.pem' +Sub CA cert "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + ... +Sub CA cert is trusted, valid until Aug 12 15:51:34 2027, 'mycacert-1.crt' +RA cert "C=CH, O=strongSwan Project, CN=SCEP RA" + ... +RA cert is trusted, valid until Aug 10 15:51:34 2023, 'myca-ra.crt' +.EE +.PP +. +.SH "SEE ALSO" +. +.BR pki (1) diff --git a/src/pki/man/pki.1.in b/src/pki/man/pki.1.in index f1a2ae2c0..6f1efa7a0 100644 --- a/src/pki/man/pki.1.in +++ b/src/pki/man/pki.1.in @@ -1,4 +1,4 @@ -.TH PKI 1 "2015-08-06" "@PACKAGE_VERSION@" "strongSwan" +.TH PKI 1 "2022-08-22" "@PACKAGE_VERSION@" "strongSwan" . .SH "NAME" . @@ -30,6 +30,16 @@ private key of a CA and containing subjectAltNames, CRL distribution points and URIs of OCSP servers. You can also extract raw public keys from private keys, certificate requests and certificates and compute two kinds of SHA-1-based key IDs. +.P +The +.B pki +command now supports certificate enrollment via the +.B Simple Certificate Enrollment Protocol +(SCEP) as defined by RFC 8894, replacing the obsoleted +.B ipsec scepclient +tool. Additionally the +.B Enrollment over Secure Transport +(EST) protocol (RFC 7030) is supported, too. . .SH "COMMANDS" . @@ -72,6 +82,18 @@ Extract a public key from a private key or certificate. .TP .B "\-v, \-\-verify" Verify a certificate using a CA certificate. +.TP +.B "\-S, \-\-scep" +Enroll an X.509 certificate with a SCEP server. +.TP +.B "\-C, \-\-scepca" +Get CA [and RA] certificate[s] from a SCEP server. +.TP +.B "\-E, \-\-est" +Enroll an X.509 certificate with an EST server. +.TP +.B "\-e, \-\-estca" +Get CA certificate[s] from an EST server. . .SH "EXAMPLES" . @@ -161,4 +183,8 @@ certificates with the \-\-crl option. .BR pki\ \-\-print (1), .BR pki\ \-\-dn (1), .BR pki\ \-\-pub (1), -.BR pki\ \-\-verify (1) +.BR pki\ \-\-verify (1), +.BR pki\ \-\-scep (1) +.BR pki\ \-\-scepca (1) +.BR pki\ \-\-est (1) +.BR pki\ \-\-estca (1) From 7c7a5a0260ca1f8897ea493ff1b6b111cfd4f5e2 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Mon, 1 Aug 2022 11:57:41 +0200 Subject: [PATCH 03/24] pki: Enroll an X.509 certificate with a SCEP server --- conf/options/pki.opt | 6 +- src/pki/Makefile.am | 1 + src/pki/command.h | 2 +- src/pki/commands/scep.c | 714 ++++++++++++++++++++++++++++++++++++++++ src/pki/scep/scep.c | 230 +++++++------ src/pki/scep/scep.h | 73 ++-- 6 files changed, 893 insertions(+), 133 deletions(-) create mode 100644 src/pki/commands/scep.c diff --git a/conf/options/pki.opt b/conf/options/pki.opt index c57dcc8c5..d6d160fa0 100644 --- a/conf/options/pki.opt +++ b/conf/options/pki.opt @@ -1,2 +1,6 @@ pki.load = - Plugins to load in ipsec pki tool. + Plugins to load in the pki tool. + +pki.scep.renewal_via_pkcs_req = no + Some SCEP servers (e.g. openxpki) are incorrectly doing certificate renewal + via messageType PKCSReq (19) instead of RenewalReq (17). diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index fcced120e..172cfcdc3 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -13,6 +13,7 @@ pki_SOURCES = pki.c pki.h command.c command.h \ commands/print.c \ commands/pub.c \ commands/req.c \ + commands/scep.c \ commands/scepca.c \ commands/self.c \ commands/signcrl.c \ diff --git a/src/pki/command.h b/src/pki/command.h index bdb402a86..876a64b99 100644 --- a/src/pki/command.h +++ b/src/pki/command.h @@ -25,7 +25,7 @@ /** * Maximum number of commands (+1). */ -#define MAX_COMMANDS 15 +#define MAX_COMMANDS 16 /** * Maximum number of options in a command (+3) diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c new file mode 100644 index 000000000..5815cf23a --- /dev/null +++ b/src/pki/commands/scep.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2012 Tobias Brunner + * 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 . + * + * 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 +#include +#include +#include +#include +#include + +#include "pki.h" +#include "scep/scep.h" + +#include +#include +#include +#include + +/* default polling time interval in SCEP manual mode */ +#define DEFAULT_POLL_INTERVAL 60 /* seconds */ + +/** + * Enroll an X.509 certificate with a SCEP server (RFC 8894) + */ +static int scep() +{ + char *arg, *url = NULL, *file = NULL, *dn = NULL, *error = NULL; + char *ca_enc_file = NULL, *ca_sig_file = NULL; + char *old_cert_file = NULL, *old_key_file = NULL; + cred_encoding_type_t form = CERT_ASN1_DER; + chunk_t scep_response = chunk_empty; + chunk_t challenge_password = chunk_empty; + chunk_t serialNumber = chunk_empty; + chunk_t transID = chunk_empty; + chunk_t pkcs10_encoding = chunk_empty; + chunk_t cert_encoding = chunk_empty; + chunk_t pkcs7_req = chunk_empty; + chunk_t certPoll = chunk_empty; + chunk_t issuerAndSubject = chunk_empty; + chunk_t data = chunk_empty; + hash_algorithm_t digest_alg = HASH_SHA256; + encryption_algorithm_t cipher = ENCR_AES_CBC; + uint16_t key_size = 128; + signature_params_t *scheme = NULL; + private_key_t *private = NULL, *priv_signer = NULL; + public_key_t *public = NULL; + certificate_t *pkcs10 = NULL, *x509_signer = NULL, *cert = NULL; + certificate_t *x509_ca_sig = NULL, *x509_ca_enc = NULL; + identification_t *subject = NULL, *issuer = NULL; + container_t *container = NULL; + pkcs7_t *pkcs7; + mem_cred_t *creds = NULL; + scep_msg_t scep_msg_type; + scep_attributes_t attrs = empty_scep_attributes; + uint32_t caps_flags; + u_int poll_interval = DEFAULT_POLL_INTERVAL; + u_int max_poll_time = 0; + u_int poll_start = 0; + time_t notBefore, notAfter; + linked_list_t *san; + enumerator_t *enumerator; + int status = 1; + bool ok, stored = FALSE; + + scep_http_params_t http_params = { + .get_request = FALSE, .timeout = 30, .bind = NULL + }; + + bool pss = lib->settings->get_bool(lib->settings, + "%s.rsa_pss", FALSE, lib->ns); + + bool renewal_via_pkcs_req = lib->settings->get_bool(lib->settings, + "%s.scep.renewal_via_pkcs_req", FALSE, lib->ns); + + + /* initialize certificate validity */ + notBefore = time(NULL); + notAfter = notBefore + 365 * 24 * 60 * 60; + + /* initialize list of subjectAltNames */ + san = linked_list_create(); + + /* initialize CA certificate storage */ + creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &creds->set); + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + goto usage; + case 'u': + url = arg; + continue; + case 'i': + file = arg; + continue; + case 'd': + dn = arg; + continue; + case 'a': + san->insert_last(san, identification_create_from_string(arg)); + continue; + case 'p': + challenge_password = chunk_create(arg, strlen(arg)); + continue; + case 'e': + ca_enc_file = arg; + continue; + case 's': + ca_sig_file = arg; + continue; + case 'c': + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_FROM_FILE, arg, BUILD_END); + if (!cert) + { + DBG1(DBG_APP, "could not load cacert file '%s'", arg); + goto end; + } + creds->add_cert(creds, TRUE, cert); + continue; + case 'o': + old_cert_file = arg; + continue; + case 'k': + old_key_file = arg; + continue; + case 'C': + if (strcaseeq(arg, "des3")) + { + cipher = ENCR_3DES; + key_size = 0; + } + else if (strcaseeq(arg, "aes")) + { + cipher = ENCR_AES_CBC; + key_size = 128; + } + else + { + error = "invalid --cipher type"; + goto usage; + } + continue; + case 'g': + if (!enum_from_name(hash_algorithm_short_names, arg, &digest_alg)) + { + error = "invalid --digest type"; + goto usage; + } + continue; + case 'R': + if (streq(arg, "pss")) + { + pss = TRUE; + } + if (streq(arg, "pkcs1")) + { + pss = FALSE; + } + else { + error = "invalid RSA padding"; + goto usage; + } + continue; + case 't': /* --pollinterval */ + poll_interval = atoi(optarg); + if (poll_interval <= 0) + { + error = "invalid interval specified"; + goto usage; + } + continue; + case 'm': /* --maxpolltime */ + max_poll_time = atoi(optarg); + continue; + case 'f': + if (!get_form(arg, &form, CRED_CERTIFICATE)) + { + error = "invalid certificate output format"; + goto usage; + } + continue; + case EOF: + break; + default: + error = "invalid --scep option"; + goto usage; + } + break; + } + + if (!url) + { + error = "--url is required"; + goto usage; + } + + if (!ca_enc_file) + { + error = "--cacert-enc is required"; + goto usage; + } + + if (!ca_sig_file) + { + error = "--cacert-sig is required"; + goto usage; + } + + if (old_cert_file && !old_key_file) + { + error = "--oldkey is required if --oldcert is set"; + goto usage; + } + + if (!dn) + { + error = "--dn is required"; + goto usage; + } + + subject = identification_create_from_string(dn); + if (subject->get_type(subject) != ID_DER_ASN1_DN) + { + DBG1(DBG_APP, "supplied --dn is not a distinguished name"); + goto end; + } + + /* load RSA private key from file or stdin */ + if (file) + { + private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, + BUILD_FROM_FILE, file, BUILD_END); + } + else + { + chunk_t chunk; + + set_file_mode(stdin, CERT_ASN1_DER); + if (!chunk_from_fd(0, &chunk)) + { + DBG1(DBG_APP, "reading private key failed: %s\n", strerror(errno)); + goto end; + } + private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, + BUILD_BLOB, chunk, BUILD_END); + free(chunk.ptr); + } + if (!private) + { + DBG1(DBG_APP, "parsing private key failed"); + goto end; + } + public = private->get_public_key(private); + + /* Request capabilities from SCEP server */ + if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CAPS, &http_params, + &scep_response)) + { + DBG1(DBG_APP, "did not receive a valid scep response"); + goto end; + } + caps_flags = scep_parse_caps(scep_response); + chunk_free(&scep_response); + + /* check support of selected digest algorithm */ + switch (digest_alg) + { + case HASH_SHA256: + ok = (caps_flags & SCEP_CAPS_SHA256) || + (caps_flags & SCEP_CAPS_SCEPSTANDARD); + break; + case HASH_SHA384: + ok = (caps_flags & SCEP_CAPS_SHA384); + break; + case HASH_SHA512: + ok = (caps_flags & SCEP_CAPS_SHA512); + break; + case HASH_SHA224: + ok = (caps_flags & SCEP_CAPS_SHA224); + break; + case HASH_SHA1: + ok = (caps_flags & SCEP_CAPS_SHA1); + break; + default: + ok = FALSE; + } + if (!ok) + { + DBG1(DBG_APP, "%N digest algorithm not supported by CA", + hash_algorithm_short_names, digest_alg); + goto end; + } + + /* check support of selected encryption algorithm */ + switch (cipher) + { + case ENCR_AES_CBC: + ok = (caps_flags & SCEP_CAPS_AES) || + (caps_flags & SCEP_CAPS_SCEPSTANDARD); + break; + case ENCR_3DES: + ok = (caps_flags & SCEP_CAPS_DES3); + break; + default: + ok = FALSE; + } + if (!ok) + { + DBG1(DBG_APP, "%N encryption algorithm not supported by CA", + encryption_algorithm_names, cipher); + goto end; + } + DBG2(DBG_APP, "%N digest and %N encryption algorithm supported by CA", + hash_algorithm_short_names, digest_alg, + encryption_algorithm_names, cipher); + + /* check support of HTTP POST operation */ + if ((caps_flags & SCEP_CAPS_POSTPKIOPERATION) || + (caps_flags & SCEP_CAPS_SCEPSTANDARD)) + { + http_params.get_request = FALSE; + } + DBG2(DBG_APP, "HTTP POST %ssupported", + http_params.get_request ? "not " : ""); + + scheme = get_signature_scheme(private, digest_alg, pss); + if (!scheme) + { + DBG1(DBG_APP, "no signature scheme found"); + goto end; + } + + /* generate PKCS#10 certificate request */ + pkcs10 = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PKCS10_REQUEST, + BUILD_SIGNING_KEY, private, + BUILD_SUBJECT, subject, + BUILD_SUBJECT_ALTNAMES, san, + BUILD_CHALLENGE_PWD, challenge_password, + BUILD_SIGNATURE_SCHEME, scheme, + BUILD_END); + if (!pkcs10) + { + DBG1(DBG_APP, "generating certificate request failed"); + goto end; + } + + /* generate PKCS#10 encoding */ + if (!pkcs10->get_encoding(pkcs10, CERT_ASN1_DER, &pkcs10_encoding)) + { + DBG1(DBG_APP, "encoding certificate request failed"); + goto end; + } + + if (!scep_generate_transaction_id(public, &transID, &serialNumber)) + { + DBG1(DBG_APP, "generating transaction ID failed"); + goto end; + } + DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr); + + if (old_cert_file) + { + /* load old client certificate */ + x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_FROM_FILE, old_cert_file, BUILD_END); + if (!x509_signer) + { + DBG1(DBG_APP, "could not load old cert file '%s'", old_cert_file); + goto end; + } + + /* load old RSA private key */ + priv_signer = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, + BUILD_FROM_FILE, old_key_file, BUILD_END); + if (!priv_signer) + { + DBG1(DBG_APP, "parsing old private key failed"); + goto end; + } + + /* check support of Renewal Operation */ + if (!(caps_flags & SCEP_CAPS_RENEWAL)) + { + DBG1(DBG_APP, "Renewal operation not supported by SCEP server"); + goto end; + } + DBG2(DBG_APP, "SCEP Renewal operation supported"); + + /* set message type for SCEP renewal request */ + scep_msg_type = renewal_via_pkcs_req ? SCEP_PKCSReq_MSG : + SCEP_RenewalReq_MSG; + } + else + { + /* create self-signed X.509 certificate */ + x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_SIGNING_KEY, private, + BUILD_PUBLIC_KEY, public, + BUILD_SUBJECT, subject, + BUILD_NOT_BEFORE_TIME, notBefore, + BUILD_NOT_AFTER_TIME, notAfter, + BUILD_SERIAL, serialNumber, + BUILD_SUBJECT_ALTNAMES, san, + BUILD_SIGNATURE_SCHEME, scheme, + BUILD_END); + if (!x509_signer) + { + DBG1(DBG_APP, "generating self-sigend certificate failed"); + goto end; + } + + /* the signing key is identical to the client key */ + priv_signer = private->get_ref(private); + + /* set message type for SCEP request */ + scep_msg_type = SCEP_PKCSReq_MSG; + } + creds->add_cert(creds, FALSE, x509_signer->get_ref(x509_signer)); + creds->add_key(creds, priv_signer->get_ref(priv_signer)); + + /* load CA or RA certificate used for encryption */ + x509_ca_enc = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_FROM_FILE, ca_enc_file, BUILD_END); + if (!x509_ca_enc) + { + DBG1(DBG_APP, "could not load encryption cacert file '%s'", ca_enc_file); + goto end; + } + + /* load CA certificate used for signature verification */ + x509_ca_sig = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_FROM_FILE, ca_sig_file, BUILD_END); + if (!x509_ca_sig) + { + DBG1(DBG_APP, "could not load signature cacert file '%s'", ca_sig_file); + goto end; + } + creds->add_cert(creds, TRUE, x509_ca_sig->get_ref(x509_ca_sig)); + + /* build pkcs7 request */ + pkcs7_req = scep_build_request(pkcs10_encoding, transID, scep_msg_type, + x509_ca_enc, cipher, key_size, x509_signer, + digest_alg, priv_signer); + if (!pkcs7_req.ptr) + { + DBG1(DBG_APP, "failed to build SCEP request"); + goto end; + } + + if (!scep_http_request(url, pkcs7_req, SCEP_PKI_OPERATION, &http_params, + &scep_response)) + { + DBG1(DBG_APP, "did not receive a valid SCEP response"); + goto end; + } + + if (!scep_parse_response(scep_response, transID, &container, &attrs)) + { + goto end; + } + + /* in case of manual mode, we are going into a polling loop */ + if (attrs.pkiStatus == SCEP_PENDING) + { + issuer = x509_ca_sig->get_subject(x509_ca_sig); + issuerAndSubject = asn1_wrap(ASN1_SEQUENCE, "cc", + issuer->get_encoding(issuer), + subject->get_encoding(subject)); + if (max_poll_time > 0) + { + DBG1(DBG_APP, " SCEP request pending, polling every %d seconds" + " up to %d seconds", poll_interval, max_poll_time); + } + else + { + DBG1(DBG_APP, " SCEP request pending, polling indefinitely" + " every %d seconds", poll_interval); + } + poll_start = time_monotonic(NULL); + } + + while (attrs.pkiStatus == SCEP_PENDING) + { + if (max_poll_time > 0 && + (time_monotonic(NULL) - poll_start) >= max_poll_time) + { + DBG1(DBG_APP, "maximum poll time reached: %d seconds", max_poll_time); + goto end; + } + DBG1(DBG_APP, " going to sleep for %d seconds", poll_interval); + sleep(poll_interval); + chunk_free(&certPoll); + chunk_free(&scep_response); + chunk_free(&attrs.transID); + chunk_free(&attrs.recipientNonce); + container->destroy(container); + container = NULL; + + DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr); + + certPoll = scep_build_request(issuerAndSubject, transID, SCEP_CertPoll_MSG, + x509_ca_enc, cipher, key_size, x509_signer, + digest_alg, priv_signer); + if (!certPoll.ptr) + { + DBG1(DBG_APP, "failed to build SCEP certPoll request"); + goto end; + } + if (!scep_http_request(url, certPoll, SCEP_PKI_OPERATION, + &http_params, &scep_response)) + { + DBG1(DBG_APP, "did not receive a valid SCEP response"); + goto end; + } + if (!scep_parse_response(scep_response, transID, &container, &attrs)) + { + goto end; + } + } + + if (attrs.pkiStatus != SCEP_SUCCESS) + { + DBG1(DBG_APP, "reply status is not 'SUCCESS'"); + goto end; + } + + if (!container->get_data(container, &data)) + { + DBG1(DBG_APP, "extracting enveloped-data failed"); + goto end; + } + container->destroy(container); + + /* decrypt enveloped-data container */ + container = lib->creds->create(lib->creds, + CRED_CONTAINER, CONTAINER_PKCS7, + BUILD_BLOB_ASN1_DER, data, + BUILD_END); + chunk_free(&data); + + if (!container) + { + DBG1(DBG_APP, "could not decrypt envelopedData"); + goto end; + } + + if (!container->get_data(container, &data)) + { + DBG1(DBG_APP, "extracting encrypted-data failed"); + goto end; + } + container->destroy(container); + + /* parse signed-data container */ + container = lib->creds->create(lib->creds, + CRED_CONTAINER, CONTAINER_PKCS7, + BUILD_BLOB_ASN1_DER, data, + BUILD_END); + chunk_free(&data); + + if (!container) + { + DBG1(DBG_APP, "could not parse signed-data"); + goto end; + } + /* no need to verify the signed-data container, the signature does NOT + * cover the contained certificates */ + + /* 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; + enumerator_t *certs; + time_t from, until; + bool trusted, valid; + + if (!(x509->get_flags(x509) & X509_CA)) + { + DBG1(DBG_APP, "certificate \"%Y\"", cert->get_subject(cert)); + + if (stored) + { + DBG1(DBG_APP, "multiple certs received, only first stored"); + continue; + } + + /* establish trust relativ to root CA */ + creds->add_cert(creds, FALSE, cert->get_ref(cert)); + certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, + KEY_RSA, cert->get_subject(cert), FALSE); + trusted = certs->enumerate(certs, &cert, NULL); + valid = cert->get_validity(cert, NULL, &from, &until); + + DBG1(DBG_APP, "certificate is %strusted, valid from %T until %T " + "(currently %svalid)", + trusted ? "" : "not ", &from, FALSE, &until, FALSE, + valid ? "" : "not "); + + certs->destroy(certs); + + if (!cert->get_encoding(cert, form, &cert_encoding)) + { + DBG1(DBG_APP, "encoding certificate failed"); + break; + } + + set_file_mode(stdout, form); + if (fwrite(cert_encoding.ptr, cert_encoding.len, 1, stdout) != 1) + { + DBG1(DBG_APP, "writing certificate failed"); + break; + } + else + { + stored = TRUE; + status = 0; + } + } + } + enumerator->destroy(enumerator); + + +end: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + san->destroy_offset(san, offsetof(identification_t, destroy)); + signature_params_destroy(scheme); + DESTROY_IF(subject); + DESTROY_IF(private); + DESTROY_IF(public); + DESTROY_IF(priv_signer); + DESTROY_IF(x509_signer); + DESTROY_IF(pkcs10); + DESTROY_IF(x509_ca_enc); + DESTROY_IF(x509_ca_sig); + DESTROY_IF(container); + chunk_free(&scep_response); + chunk_free(&serialNumber); + chunk_free(&transID); + chunk_free(&pkcs10_encoding); + chunk_free(&cert_encoding); + chunk_free(&pkcs7_req); + chunk_free(&certPoll); + chunk_free(&issuerAndSubject); + chunk_free(&attrs.transID); + chunk_free(&attrs.recipientNonce); + + return status; + +usage: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + san->destroy_offset(san, offsetof(identification_t, destroy)); + + return command_usage(error); +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + scep, 'S', "scep", + "Enroll an X.509 certificate with a SCEP server", + {"--url url [--in file] --dn distinguished-name [--san subjectAltName]+", + "[--password password] --cacert-enc file --cacert-sig file [--cacert file]+", + "[--oldcert file --oldkey file] [--cipher aes|des3]", + "[--digest sha256|sha384|sha512|sha224|sha1] [--rsa-padding pkcs1|pss]", + "[--interval time] [--maxpolltime time] [--outform der|pem]"}, + { + {"help", 'h', 0, "show usage information"}, + {"url", 'u', 1, "URL of the SCEP server"}, + {"in", 'i', 1, "RSA private key input file, default: stdin"}, + {"dn", 'd', 1, "subject distinguished name"}, + {"san", 'a', 1, "subjectAltName to include in cert request"}, + {"password", 'p', 1, "challengePassword to include in cert request"}, + {"cacert-enc", 'e', 1, "CA certificate for encryption"}, + {"cacert-sig", 's', 1, "CA certificate for signature verification"}, + {"cacert", 'c', 1, "Additional CA certificates"}, + {"oldcert", 'o', 1, "Old certificate about to be renewed"}, + {"oldkey", 'k', 1, "Old RSA private key about to be replaced"}, + {"cipher", 'C', 1, "encryption cipher, default: aes"}, + {"digest", 'g', 1, "digest for signature creation, default: sha256"}, + {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"}, + {"interval", 't', 1, "poll interval, default: 60s"}, + {"maxpolltime", 'm', 1, "maximum poll time, default: 0 (no limit)"}, + {"outform", 'f', 1, "encoding of stored certificates, default: der"}, + } + }); +} diff --git a/src/pki/scep/scep.c b/src/pki/scep/scep.c index 24fa93b20..7d6fafa10 100644 --- a/src/pki/scep/scep.c +++ b/src/pki/scep/scep.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2022 Andreas Steffen, strongSec GmbH * * Copyright (C) secunet Security Networks AG @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,12 @@ #include "scep.h" +static const char *operations[] = { + "PKIOperation", + "GetCACert", + "GetCACaps" +}; + static const char *pkiStatus_values[] = { "0", "2", "3" }; static const char *pkiStatus_names[] = { @@ -58,6 +65,20 @@ static const char *failInfo_reasons[] = { "badCertId - No certificate could be identified matching the provided criteria" }; +static const char *caps_names[] = { + "AES", + "DES3", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA-224", + "SHA-1", + "POSTPKIOperation", + "SCEPStandard", + "GetNextCACert", + "Renewal" +}; + const scep_attributes_t empty_scep_attributes = { SCEP_Unknown_MSG , /* msgType */ SCEP_UNKNOWN , /* pkiStatus */ @@ -125,75 +146,46 @@ void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator, } /** - * Generates a unique fingerprint of the pkcs10 request - * by computing an MD5 hash over it + * Generate a transaction ID as the SHA-1 hash of the publicKeyInfo + * the transaction ID is also used as a unique serial number */ -chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10) +bool scep_generate_transaction_id(public_key_t *public, + chunk_t *transId, chunk_t *serialNumber) { - chunk_t digest = chunk_alloca(HASH_SIZE_MD5); - hasher_t *hasher; - - hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); - if (!hasher || !hasher->get_hash(hasher, pkcs10, digest.ptr)) - { - DESTROY_IF(hasher); - return chunk_empty; - } - hasher->destroy(hasher); - - return chunk_to_hex(digest, NULL, FALSE); -} - -/** - * Generate a transaction id as the MD5 hash of an public key - * the transaction id is also used as a unique serial number - */ -void scep_generate_transaction_id(public_key_t *key, chunk_t *transID, - chunk_t *serialNumber) -{ - chunk_t digest = chunk_alloca(HASH_SIZE_MD5); - chunk_t keyEncoding = chunk_empty, keyInfo; - hasher_t *hasher; + chunk_t digest; int zeros = 0, msb_set = 0; - key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding); - - keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm", - asn1_algorithmIdentifier(OID_RSA_ENCRYPTION), - asn1_bitstring("m", keyEncoding)); - - hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); - if (!hasher || !hasher->get_hash(hasher, keyInfo, digest.ptr)) + if (public->get_fingerprint(public, KEYID_PUBKEY_INFO_SHA1, &digest)) { - memset(digest.ptr, 0, digest.len); - } - DESTROY_IF(hasher); - free(keyInfo.ptr); + /* the transaction ID is the fingerprint in hex format */ + *transId = chunk_to_hex(digest, NULL, TRUE); - /* the serialNumber should be valid ASN1 integer content: - * remove leading zeros, add one if MSB is set (two's complement) */ - while (zeros < digest.len) - { - if (digest.ptr[zeros]) + /** + * the serial number must be a valid positive ASN.1 integer + * remove leading zeros, add one if MSB is set (two's complement) + */ + while (zeros < digest.len) { - if (digest.ptr[zeros] & 0x80) + if (digest.ptr[zeros]) { - msb_set = 1; + if (digest.ptr[zeros] & 0x80) + { + msb_set = 1; + } + break; } - break; + zeros++; } - zeros++; + *serialNumber = chunk_alloc(digest.len - zeros + msb_set); + if (msb_set) + { + serialNumber->ptr[0] = 0x00; + } + memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros, + digest.len - zeros); + return TRUE; } - *serialNumber = chunk_alloc(digest.len - zeros + msb_set); - if (msb_set) - { - serialNumber->ptr[0] = 0x00; - } - memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros, - digest.len - zeros); - - /* the transaction id is the serial number in hex format */ - *transID = chunk_to_hex(digest, NULL, TRUE); + return FALSE; } /** @@ -347,6 +339,7 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, int len; status_t status; char *complete_url = NULL; + const char *operation; host_t *srcip = NULL; /* initialize response */ @@ -356,69 +349,70 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, { srcip = host_create_from_string(http_params->bind, 0); } - DBG2(DBG_APP, "sending scep request to '%s'", url); - if (op == SCEP_PKI_OPERATION) + operation = operations[op]; + switch (op) { - const char operation[] = "PKIOperation"; + case SCEP_PKI_OPERATION: + default: + if (http_params->get_request) + { + char *escaped_req = escape_http_request(msg); - if (http_params->get_request) - { - char *escaped_req = escape_http_request(msg); + /* form complete url */ + len = strlen(url) + 20 + strlen(operation) + + strlen(escaped_req) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s&message=%s" + , url, operation, escaped_req); + free(escaped_req); - /* form complete url */ - len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s&message=%s" - , url, operation, escaped_req); - free(escaped_req); - - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, FETCH_TIMEOUT, http_params->timeout, FETCH_REQUEST_HEADER, "Pragma:", FETCH_REQUEST_HEADER, "Host:", FETCH_REQUEST_HEADER, "Accept:", FETCH_SOURCEIP, srcip, FETCH_END); - } - else /* HTTP_POST */ - { - /* form complete url */ - len = strlen(url) + 11 + strlen(operation) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s", url, operation); + } + else /* HTTP_POST */ + { + /* form complete url */ + len = strlen(url) + 11 + strlen(operation) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s", url, operation); - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, FETCH_TIMEOUT, http_params->timeout, FETCH_REQUEST_DATA, msg, FETCH_REQUEST_TYPE, "", FETCH_REQUEST_HEADER, "Expect:", FETCH_SOURCEIP, srcip, FETCH_END); - } - } - else /* SCEP_GET_CA_CERT */ - { - const char operation[] = "GetCACert"; + } + break; + case SCEP_GET_CA_CERT: + case SCEP_GET_CA_CAPS: + { + /* form complete url */ + len = strlen(url) + 11 + strlen(operation) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s", url, operation); - /* form complete url */ - len = strlen(url) + 11 + strlen(operation) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s", url, operation); - - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, FETCH_TIMEOUT, http_params->timeout, FETCH_SOURCEIP, srcip, FETCH_END); + } } - DESTROY_IF(srcip); free(complete_url); + return (status == SUCCESS); } -err_t scep_parse_response(chunk_t response, chunk_t transID, +bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out, scep_attributes_t *attrs) { enumerator_t *enumerator; @@ -426,16 +420,19 @@ err_t scep_parse_response(chunk_t response, chunk_t transID, container_t *container; auth_cfg_t *auth; + *out = NULL; + container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, BUILD_BLOB_ASN1_DER, response, BUILD_END); if (!container) { - return "error parsing the scep response"; + DBG1(DBG_APP, "error parsing the scep response"); + return FALSE; } if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA) { - container->destroy(container); - return "scep response is not PKCS#7 signed-data"; + DBG1(DBG_APP, "scep response is not PKCS#7 signed-data"); + goto error; } enumerator = container->create_signature_enumerator(container); @@ -446,16 +443,45 @@ err_t scep_parse_response(chunk_t response, chunk_t transID, if (!chunk_equals(transID, attrs->transID)) { enumerator->destroy(enumerator); - container->destroy(container); - return "transaction ID of scep response does not match"; + DBG1(DBG_APP, "transaction ID of scep response does not match"); + goto error; } } enumerator->destroy(enumerator); + if (!verified) { - container->destroy(container); - return "unable to verify PKCS#7 container"; + DBG1(DBG_APP, "unable to verify PKCS#7 container"); + goto error; } *out = container; - return NULL; + + return TRUE; + +error: + container->destroy(container); + return FALSE; } + +uint32_t scep_parse_caps(chunk_t response) +{ + uint32_t caps_flags = 0; + chunk_t line; + + DBG2(DBG_APP, "CA Capabilities:"); + + while (fetchline(&response, &line)) + { + int i; + + for (i = 0; i < countof(caps_names); i++) + { + if (strncaseeq(caps_names[i], line.ptr, line.len)) + { + DBG2(DBG_APP, " %s", caps_names[i]); + caps_flags |= (1 << i); + } + } + } + return caps_flags; +} \ No newline at end of file diff --git a/src/pki/scep/scep.h b/src/pki/scep/scep.h index c9e97b1a0..bfb49a4d1 100644 --- a/src/pki/scep/scep.h +++ b/src/pki/scep/scep.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2005 Jan Hutter, Martin Willi + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2022 Andreas Steffen, strongSec GmbH * * Copyright (C) secunet Security Networks AG @@ -25,36 +25,37 @@ /* supported SCEP operation types */ typedef enum { SCEP_PKI_OPERATION, - SCEP_GET_CA_CERT + SCEP_GET_CA_CERT, + SCEP_GET_CA_CAPS } scep_op_t; /* SCEP pkiStatus values */ typedef enum { - SCEP_SUCCESS, - SCEP_FAILURE, - SCEP_PENDING, - SCEP_UNKNOWN + SCEP_SUCCESS, + SCEP_FAILURE, + SCEP_PENDING, + SCEP_UNKNOWN } pkiStatus_t; /* SCEP messageType values */ typedef enum { - SCEP_CertRep_MSG, - SCEP_RenewalReq_MSG, - SCEP_PKCSReq_MSG, - SCEP_CertPoll_MSG, - SCEP_GetCert_MSG, - SCEP_GetCRL_MSG, - SCEP_Unknown_MSG + SCEP_CertRep_MSG, + SCEP_RenewalReq_MSG, + SCEP_PKCSReq_MSG, + SCEP_CertPoll_MSG, + SCEP_GetCert_MSG, + SCEP_GetCRL_MSG, + SCEP_Unknown_MSG } scep_msg_t; /* SCEP failure reasons */ typedef enum { - SCEP_badAlg_REASON = 0, - SCEP_badMessageCheck_REASON = 1, - SCEP_badRequest_REASON = 2, - SCEP_badTime_REASON = 3, - SCEP_badCertId_REASON = 4, - SCEP_unknown_REASON = 5 + SCEP_badAlg_REASON = 0, + SCEP_badMessageCheck_REASON = 1, + SCEP_badRequest_REASON = 2, + SCEP_badTime_REASON = 3, + SCEP_badCertId_REASON = 4, + SCEP_unknown_REASON = 5 } failInfo_t; /* SCEP attributes */ @@ -69,20 +70,32 @@ typedef struct { /* SCEP http parameters */ typedef struct { - bool get_request; - u_int timeout; - char *bind; + bool get_request; + u_int timeout; + char *bind; } scep_http_params_t; +/* SCEP CA Capabilities */ +typedef enum { + SCEP_CAPS_AES = 0, + SCEP_CAPS_DES3 = 1, + SCEP_CAPS_SHA256 = 2, + SCEP_CAPS_SHA384 = 3, + SCEP_CAPS_SHA512 = 4, + SCEP_CAPS_SHA224 = 5, + SCEP_CAPS_SHA1 = 6, + SCEP_CAPS_POSTPKIOPERATION = 7, + SCEP_CAPS_SCEPSTANDARD = 8, + SCEP_CAPS_GETNEXTCACERT = 9, + SCEP_CAPS_RENEWAL = 10 +} scep_caps_t; + extern const scep_attributes_t empty_scep_attributes; bool parse_attributes(chunk_t blob, scep_attributes_t *attrs); -void scep_generate_transaction_id(public_key_t *key, - chunk_t *transID, - chunk_t *serialNumber); - -chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10); +bool scep_generate_transaction_id(public_key_t *key, + chunk_t *transId, chunk_t *serialNumber); chunk_t scep_transId_attribute(chunk_t transaction_id); @@ -98,7 +111,9 @@ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, scep_http_params_t *http_params, chunk_t *response); -err_t scep_parse_response(chunk_t response, chunk_t transID, - container_t **out, scep_attributes_t *attrs); +bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out, + scep_attributes_t *attrs); + +uint32_t scep_parse_caps(chunk_t response); #endif /* _SCEP_H */ From 93f2901d1a6b0dcf2d9a235e1ebe78120589da57 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sat, 6 Aug 2022 12:23:09 +0200 Subject: [PATCH 04/24] pki: Created pki --scep man page --- configure.ac | 1 + src/pki/man/Makefile.am | 1 + src/pki/man/pki---scep.1.in | 176 ++++++++++++++++++++++++++++++++++ src/pki/man/pki---scepca.1.in | 12 +++ 4 files changed, 190 insertions(+) create mode 100644 src/pki/man/pki---scep.1.in diff --git a/configure.ac b/configure.ac index 7434d5861..fd885ccb1 100644 --- a/configure.ac +++ b/configure.ac @@ -2175,6 +2175,7 @@ AC_CONFIG_FILES([ src/pki/man/pki---print.1 src/pki/man/pki---pub.1 src/pki/man/pki---req.1 + src/pki/man/pki---scep.1 src/pki/man/pki---scepca.1 src/pki/man/pki---self.1 src/pki/man/pki---signcrl.1 diff --git a/src/pki/man/Makefile.am b/src/pki/man/Makefile.am index 489c91367..9df76d9c3 100644 --- a/src/pki/man/Makefile.am +++ b/src/pki/man/Makefile.am @@ -9,6 +9,7 @@ man1_MANS = \ pki---print.1 \ pki---pub.1 \ pki---req.1 \ + pki---scep.1 \ pki---scepca.1 \ pki---self.1 \ pki---signcrl.1 \ diff --git a/src/pki/man/pki---scep.1.in b/src/pki/man/pki---scep.1.in new file mode 100644 index 000000000..2422b54ca --- /dev/null +++ b/src/pki/man/pki---scep.1.in @@ -0,0 +1,176 @@ +.TH "PKI \-\-SCEP" 1 "2022-08-22" "@PACKAGE_VERSION@" "strongSwan" +. +.SH "NAME" +. +pki \-\-scep \- Enroll an X.509 certificate with a SCEP server +. +.SH "SYNOPSIS" +. +.SY pki\ \-\-scep +.BI\-\-\-url\~ url +.OP \-\-in file +.BI \-\-dn\~ distinguished-name +.OP \-\-san subjectAltName +.OP \-\-password password +.BI \-\-ca-cert-enc\~ file +.BI \-\-ca-cert-sig\~ file +.OP \-\-cacert file +.BI [\-\-cert\~ file +.BI \-\-key\~ file ] +.OP \-\-cipher cipher +.OP \-\-digest digest +.OP \-\-rsa-padding padding +.OP \-\-interval time +.OP \-\-maxpolltime time +.OP \-\-outform encoding +.OP \-\-debug level +.YS +. +.SY pki\ \-\-scep +.BI \-\-options\~ file +.YS +. +.SY "pki \-\-scep" +.B \-h +| +.B \-\-help +.YS +. +.SH "DESCRIPTION" +. +This sub-command of +.BR pki (1) +sends a PKCS#10 certificate request in an encrypted and signed PKCS#7 container +via HTTP to a SCEP server using the Simple Certificate Enrollment Protocol +(RFC 8894). After successful authorization which with manual authentication +requires periodic polling by the enrollment client, the SCEP server returns an +X.509 certificate signed by the CA. + +Before the expiry of the current certificate, a new client certificate based on +a fresh RSA private key can be requested, using the old certificate and the old +key for automatic authentication with the SCEP server. +. +.SH "OPTIONS" +. +.TP +.B "\-h, \-\-help" +Print usage information with a summary of the available options. +.TP +.BI "\-v, \-\-debug " level +Set debug level, default: 1. +.TP +.BI "\-+, \-\-options " file +Read command line options from \fIfile\fR. +.TP +.BI "\-u, \-\-url " url +URL of the SCEP server. +.TP +.BI "\-i, \-\-in " file +RSA private key. If not given the key is read from \fISTDIN\fR. +.TP +.BI "\-d, \-\-dn " distinguished-name +Subject distinguished name (DN). Required. +.TP +.BI "\-a, \-\-san " subjectAltName +subjectAltName extension to include in request. Can be used multiple times. +.TP +.BI "\-p, \-\-password " password +The challengePassword to include in the certificate request. +.TP +.BI "\-e, \-\-cacert-enc " file +CA or RA certificate for encryption +.TP +.BI "\-s, \-\-cacert-sig " file +CA certificate for signature verification +.TP +.BI "\-C, \-\-cacert " file +Additional CA certificate in the trust chain used for signature verification. +Can be used multiple times. +.TP +.BI "\-c, \-\-cert " file +Client certificate to be renewed. +.TP +.BI "\-k, \-\-key " file +Client RSA private key to be replaced. +.TP +.BI "\-E, \-\-cipher " cipher +Cipher used for symmetric encryption. Either \fIaes\fR (the default) or \fIdes3\fR. +.TP +.BI "\-g, \-\-digest " digest +Digest to use for signature creation. One of \fIsha256\fR (the default), +\fIsha384\fR, \fIsha512\fR, or \fIsha1\fR. +.TP +.BI "\-R, \-\-rsa\-padding " padding +Padding to use for RSA signatures. Either \fIpkcs1\fR (the default) or \fIpss\fR. +.TP +.BI "\-t, \-\-interval " time +Poll interval in seconds, defaults to \fI60s\fR. +.TP +.BI "\-m, \-\-maxpolltime " time +Maximum poll time in seconds, defaults to \fI0\fR which means unlimited polling. +.TP +.BI "\-f, \-\-outform " encoding +Encoding of the created certificate file. Either \fIder\fR (ASN.1 DER) or +\fIpem\fR (Base64 PEM), defaults to \fIder\fR. +. +.SH "EXAMPLES" +. +To save some typing work the following command line options are stored in a +\fIscep.opt\fR file: +.PP +.EX +\-\-url http://pki.strongswan.org:8080/scep +\-\-cacert-enc myra.crt +\-\-cacert-sig myca-1.crt +\-\-cacert myca.crt +.EE +.PP +With the following command, an X.509 certificate signed by the intermediate CA is +requested from a SCEP server: +.PP +.EX +pki \-\-options scep.opt \-\-in moonKey.der \-\-san "moon.strongswan.org" \\ + \-\-dn "C=CH, O=strongSec GmbH, CN=moon.strongswan.org" > moonCert.der + +transaction ID: 4DFCF31CB18A9B5333CCEC6F99CF230E4524E334 + using certificate "C=CH, O=strongSwan Project, CN=SCEP RA" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 + SCEP request pending, polling indefinitely every 60 seconds + going to sleep for 60 seconds +transaction ID: 4DFCF31CB18A9B5333CCEC6F99CF230E4524E334 + ... + going to sleep for 60 seconds +Issued certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + serial: 1e:ff:22:7b:6e:d7:4c:c1:8a:06 + using certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +Issued certificate is trusted, valid from Aug 22 18:56:23 2022 until Aug 22 18:56:23 2023 (currently valid) +.EE +.PP +A certificate about to expire can be renewed with the command: +.PP +.EX +pki \-\-options scep.opt \-\-in moonNewKey.der \-\-san "moon.strongswan.org" \\ + \-\-dn "C=CH, O=strongSec GmbH, CN=moon.strongswan.org" \\ + \-\-cert moonCert.der \-\-key moonKey.der > moonNewCert.der + +transaction ID: A9A63D028CC439F68452D125C4DBA025E67DBA95 + using certificate "C=CH, O=strongSwan Project, CN=SCEP RA" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +Issued certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + serial: 1f:ff:b2:78:43:a2:9d:85:00:38 + using certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +Issued certificate is trusted, valid from Jul 20 15:05:33 2023 until Jul 20 15:05:33 2024 (currently valid) +. +.SH "SEE ALSO" +. +.BR pki (1) diff --git a/src/pki/man/pki---scepca.1.in b/src/pki/man/pki---scepca.1.in index ac37055c7..337e2f1a6 100644 --- a/src/pki/man/pki---scepca.1.in +++ b/src/pki/man/pki---scepca.1.in @@ -56,6 +56,12 @@ intermediate or sub CA certificates. If a file suffix is missing, then depending on the value of .B \-\-outform either .\fIder\fR (the default) or .\fIpem\fR is automatically appended. +If the +.B \-\-caout +option is missing and +.B \-\-outform +is set to \fIpem\fR then a PEM-encoded CA certificate bundle is written to +\fIstdout\fR. .TP .BI "\-r, \-\-raout " file If present, path where the fetched RA certificate file is stored to. @@ -143,6 +149,12 @@ RA cert "C=CH, O=strongSwan Project, CN=SCEP RA" RA cert is trusted, valid until Aug 10 15:51:34 2023, 'myca-ra.crt' .EE .PP +A CA certificate bundle in PEM format is written to \fIstdout\fR: +.PP +.EX +pki \-\-scepca \-\-url http://pki.strongswan.org:8080/scep --raout myra.crt \-\-outform pem > cacerts.pem +.EE +.PP . .SH "SEE ALSO" . From 122796df27799b4d29e60be9ae8eb885ecc587ff Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Tue, 9 Aug 2022 07:38:06 +0200 Subject: [PATCH 05/24] pki: Additional pki.scep options for strongswan.conf --- conf/options/pki.opt | 6 +++++ src/pki/commands/scep.c | 19 ++++++-------- src/pki/commands/scepca.c | 6 +---- src/pki/scep/scep.c | 52 ++++++++++++++++++++++----------------- src/pki/scep/scep.h | 11 ++------- 5 files changed, 45 insertions(+), 49 deletions(-) diff --git a/conf/options/pki.opt b/conf/options/pki.opt index d6d160fa0..2cbea779c 100644 --- a/conf/options/pki.opt +++ b/conf/options/pki.opt @@ -1,6 +1,12 @@ pki.load = Plugins to load in the pki tool. +pki.scep.http_bind + Source IP address to bind for HTTP operations. + +pki.scep.http_timeout = 30s + Timeout for HTTP operations. + pki.scep.renewal_via_pkcs_req = no Some SCEP servers (e.g. openxpki) are incorrectly doing certificate renewal via messageType PKCSReq (19) instead of RenewalReq (17). diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c index 5815cf23a..37f5a9482 100644 --- a/src/pki/commands/scep.c +++ b/src/pki/commands/scep.c @@ -76,11 +76,7 @@ static int scep() linked_list_t *san; enumerator_t *enumerator; int status = 1; - bool ok, stored = FALSE; - - scep_http_params_t http_params = { - .get_request = FALSE, .timeout = 30, .bind = NULL - }; + bool ok, http_post = FALSE, stored = FALSE; bool pss = lib->settings->get_bool(lib->settings, "%s.rsa_pss", FALSE, lib->ns); @@ -273,7 +269,7 @@ static int scep() public = private->get_public_key(private); /* Request capabilities from SCEP server */ - if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CAPS, &http_params, + if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CAPS, FALSE, &scep_response)) { DBG1(DBG_APP, "did not receive a valid scep response"); @@ -338,10 +334,9 @@ static int scep() if ((caps_flags & SCEP_CAPS_POSTPKIOPERATION) || (caps_flags & SCEP_CAPS_SCEPSTANDARD)) { - http_params.get_request = FALSE; + http_post = TRUE; } - DBG2(DBG_APP, "HTTP POST %ssupported", - http_params.get_request ? "not " : ""); + DBG2(DBG_APP, "HTTP POST %ssupported", http_post ? "" : "not "); scheme = get_signature_scheme(private, digest_alg, pss); if (!scheme) @@ -467,7 +462,7 @@ static int scep() goto end; } - if (!scep_http_request(url, pkcs7_req, SCEP_PKI_OPERATION, &http_params, + if (!scep_http_request(url, pkcs7_req, SCEP_PKI_OPERATION, http_post, &scep_response)) { DBG1(DBG_APP, "did not receive a valid SCEP response"); @@ -526,8 +521,8 @@ static int scep() DBG1(DBG_APP, "failed to build SCEP certPoll request"); goto end; } - if (!scep_http_request(url, certPoll, SCEP_PKI_OPERATION, - &http_params, &scep_response)) + if (!scep_http_request(url, certPoll, SCEP_PKI_OPERATION, http_post, + &scep_response)) { DBG1(DBG_APP, "did not receive a valid SCEP response"); goto end; diff --git a/src/pki/commands/scepca.c b/src/pki/commands/scepca.c index a443155f3..24271f78b 100644 --- a/src/pki/commands/scepca.c +++ b/src/pki/commands/scepca.c @@ -248,10 +248,6 @@ static int scepca() int cert_type_count[] = { 0, 0, 0 }; - scep_http_params_t http_params = { - .get_request = TRUE, .timeout = 30, .bind = NULL - }; - while (TRUE) { switch (command_getopt(&arg)) @@ -289,7 +285,7 @@ static int scepca() return command_usage("--url is required"); } - if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CERT, &http_params, + if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CERT, FALSE, &scep_response)) { DBG1(DBG_APP, "did not receive a valid scep response"); diff --git a/src/pki/scep/scep.c b/src/pki/scep/scep.c index 7d6fafa10..eaa5b5323 100644 --- a/src/pki/scep/scep.c +++ b/src/pki/scep/scep.c @@ -334,7 +334,7 @@ static char* escape_http_request(chunk_t req) * Send a SCEP request via HTTP and wait for a response */ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, - scep_http_params_t *http_params, chunk_t *response) + bool http_post, chunk_t *response) { int len; status_t status; @@ -342,21 +342,42 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, const char *operation; host_t *srcip = NULL; - /* initialize response */ - *response = chunk_empty; + uint32_t http_timeout = lib->settings->get_time(lib->settings, + "%s.scep.http_timeout", 30, lib->ns); - if (http_params->bind) + char *http_bind = lib->settings->get_str(lib->settings, + "%s.scep.http_bind", NULL, lib->ns); + + if (http_bind) { - srcip = host_create_from_string(http_params->bind, 0); + srcip = host_create_from_string(http_bind, 0); } DBG2(DBG_APP, "sending scep request to '%s'", url); + /* initialize response */ + *response = chunk_empty; + operation = operations[op]; switch (op) { case SCEP_PKI_OPERATION: default: - if (http_params->get_request) + if (http_post) + { + /* form complete url */ + len = strlen(url) + 11 + strlen(operation) + 1; + complete_url = malloc(len); + snprintf(complete_url, len, "%s?operation=%s", url, operation); + + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + FETCH_TIMEOUT, http_timeout, + FETCH_REQUEST_DATA, msg, + FETCH_REQUEST_TYPE, "", + FETCH_REQUEST_HEADER, "Expect:", + FETCH_SOURCEIP, srcip, + FETCH_END); + } + else /* HTTP_GET */ { char *escaped_req = escape_http_request(msg); @@ -369,28 +390,13 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, free(escaped_req); status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_params->timeout, + FETCH_TIMEOUT, http_timeout, FETCH_REQUEST_HEADER, "Pragma:", FETCH_REQUEST_HEADER, "Host:", FETCH_REQUEST_HEADER, "Accept:", FETCH_SOURCEIP, srcip, FETCH_END); } - else /* HTTP_POST */ - { - /* form complete url */ - len = strlen(url) + 11 + strlen(operation) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s", url, operation); - - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_params->timeout, - FETCH_REQUEST_DATA, msg, - FETCH_REQUEST_TYPE, "", - FETCH_REQUEST_HEADER, "Expect:", - FETCH_SOURCEIP, srcip, - FETCH_END); - } break; case SCEP_GET_CA_CERT: case SCEP_GET_CA_CAPS: @@ -401,7 +407,7 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, snprintf(complete_url, len, "%s?operation=%s", url, operation); status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_params->timeout, + FETCH_TIMEOUT, http_timeout, FETCH_SOURCEIP, srcip, FETCH_END); } diff --git a/src/pki/scep/scep.h b/src/pki/scep/scep.h index bfb49a4d1..ead203505 100644 --- a/src/pki/scep/scep.h +++ b/src/pki/scep/scep.h @@ -68,13 +68,6 @@ typedef struct { chunk_t recipientNonce; } scep_attributes_t; -/* SCEP http parameters */ -typedef struct { - bool get_request; - u_int timeout; - char *bind; -} scep_http_params_t; - /* SCEP CA Capabilities */ typedef enum { SCEP_CAPS_AES = 0, @@ -108,8 +101,8 @@ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, size_t key_size, certificate_t *signer_cert, hash_algorithm_t digest_alg, private_key_t *private_key); -bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, - scep_http_params_t *http_params, chunk_t *response); +bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, bool use_post, + chunk_t *response); bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out, scep_attributes_t *attrs); From 8716f7c03c6193b1cb53837243177f36280ff4f7 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Tue, 9 Aug 2022 10:15:36 +0200 Subject: [PATCH 06/24] scepclient: Removal and replacement by pki subcommands The "ipsec scepclient" tool has been removed and replaced by the pki subcommands "pki --scep" and "pki --scepca" which implement the new SCEP RFC 8894 standard that was released in September 2020 and which supports trusted "certificate renewal" based on the existing client certificate. --- configure.ac | 67 +- src/Makefile.am | 4 - src/checksum/Makefile.am | 4 - src/scepclient/.gitignore | 1 - src/scepclient/Android.mk | 28 - src/scepclient/Makefile.am | 16 - src/scepclient/README.md | 18 + src/scepclient/scep.c | 474 ----------- src/scepclient/scep.h | 88 --- src/scepclient/scepclient.8 | 293 ------- src/scepclient/scepclient.c | 1491 ----------------------------------- 11 files changed, 49 insertions(+), 2435 deletions(-) delete mode 100644 src/scepclient/.gitignore delete mode 100644 src/scepclient/Android.mk delete mode 100644 src/scepclient/Makefile.am create mode 100644 src/scepclient/README.md delete mode 100644 src/scepclient/scep.c delete mode 100644 src/scepclient/scep.h delete mode 100644 src/scepclient/scepclient.8 delete mode 100644 src/scepclient/scepclient.c diff --git a/configure.ac b/configure.ac index fd885ccb1..40252e79d 100644 --- a/configure.ac +++ b/configure.ac @@ -302,7 +302,6 @@ ARG_ENABL_SET([medcli], [enable mediation client configuration database ARG_ENABL_SET([medsrv], [enable mediation server web frontend and daemon plugin.]) ARG_ENABL_SET([nm], [enable NetworkManager backend.]) ARG_DISBL_SET([pki], [disable pki certificate utility.]) -ARG_DISBL_SET([scepclient], [disable SCEP client tool.]) ARG_DISBL_SET([scripts], [disable additional utilities (found in directory scripts).]) ARG_ENABL_SET([svc], [enable charon Windows service.]) ARG_ENABL_SET([systemd], [enable systemd specific IKE daemon charon-systemd.]) @@ -1483,7 +1482,6 @@ charon_plugins= starter_plugins= pool_plugins= attest_plugins= -scepclient_plugins= pki_plugins= scripts_plugins= fuzz_plugins= @@ -1500,48 +1498,48 @@ s_plugins= t_plugins= p_plugins= -ADD_PLUGIN([test-vectors], [s charon scepclient pki]) +ADD_PLUGIN([test-vectors], [s charon pki]) ADD_PLUGIN([unbound], [s charon scripts]) -ADD_PLUGIN([ldap], [s charon scepclient scripts nm cmd]) +ADD_PLUGIN([ldap], [s charon scripts nm cmd]) ADD_PLUGIN([pkcs11], [s charon pki nm cmd]) ADD_PLUGIN([tpm], [p charon pki nm cmd]) -ADD_PLUGIN([aesni], [s charon scepclient pki scripts medsrv attest nm cmd aikgen]) -ADD_PLUGIN([aes], [s charon scepclient pki scripts nm cmd]) -ADD_PLUGIN([des], [s charon scepclient pki scripts nm cmd]) -ADD_PLUGIN([blowfish], [s charon scepclient pki scripts nm cmd]) -ADD_PLUGIN([rc2], [s charon scepclient pki scripts nm cmd]) -ADD_PLUGIN([sha2], [s charon scepclient pki scripts medsrv attest nm cmd aikgen fuzz]) -ADD_PLUGIN([sha3], [s charon scepclient pki scripts medsrv attest nm cmd aikgen fuzz]) -ADD_PLUGIN([sha1], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen fuzz]) -ADD_PLUGIN([md4], [s charon scepclient pki nm cmd]) -ADD_PLUGIN([md5], [s charon scepclient pki scripts attest nm cmd aikgen]) -ADD_PLUGIN([mgf1], [s charon scepclient pki scripts medsrv attest nm cmd aikgen]) -ADD_PLUGIN([rdrand], [s charon scepclient pki scripts medsrv attest nm cmd aikgen]) -ADD_PLUGIN([random], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen]) +ADD_PLUGIN([aesni], [s charon pki scripts medsrv attest nm cmd aikgen]) +ADD_PLUGIN([aes], [s charon pki scripts nm cmd]) +ADD_PLUGIN([des], [s charon pki scripts nm cmd]) +ADD_PLUGIN([blowfish], [s charon pki scripts nm cmd]) +ADD_PLUGIN([rc2], [s charon pki scripts nm cmd]) +ADD_PLUGIN([sha2], [s charon pki scripts medsrv attest nm cmd aikgen fuzz]) +ADD_PLUGIN([sha3], [s charon pki scripts medsrv attest nm cmd aikgen fuzz]) +ADD_PLUGIN([sha1], [s charon pki scripts manager medsrv attest nm cmd aikgen fuzz]) +ADD_PLUGIN([md4], [s charon pki nm cmd]) +ADD_PLUGIN([md5], [s charon pki scripts attest nm cmd aikgen]) +ADD_PLUGIN([mgf1], [s charon pki scripts medsrv attest nm cmd aikgen]) +ADD_PLUGIN([rdrand], [s charon pki scripts medsrv attest nm cmd aikgen]) +ADD_PLUGIN([random], [s charon pki scripts manager medsrv attest nm cmd aikgen]) ADD_PLUGIN([nonce], [s charon nm cmd aikgen]) -ADD_PLUGIN([x509], [s charon scepclient pki scripts attest nm cmd aikgen fuzz]) +ADD_PLUGIN([x509], [s charon pki scripts attest nm cmd aikgen fuzz]) ADD_PLUGIN([revocation], [s charon pki nm cmd]) ADD_PLUGIN([constraints], [s charon nm cmd]) ADD_PLUGIN([acert], [s charon]) ADD_PLUGIN([pubkey], [s charon pki cmd aikgen]) -ADD_PLUGIN([pkcs1], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen fuzz]) -ADD_PLUGIN([pkcs7], [s charon scepclient pki scripts nm cmd]) -ADD_PLUGIN([pkcs12], [s charon scepclient pki scripts cmd]) +ADD_PLUGIN([pkcs1], [s charon pki scripts manager medsrv attest nm cmd aikgen fuzz]) +ADD_PLUGIN([pkcs7], [s charon pki scripts nm cmd]) +ADD_PLUGIN([pkcs12], [s charon pki scripts cmd]) ADD_PLUGIN([pgp], [s charon]) ADD_PLUGIN([dnskey], [s charon pki]) ADD_PLUGIN([sshkey], [s charon pki nm cmd]) ADD_PLUGIN([dnscert], [c charon]) ADD_PLUGIN([ipseckey], [c charon]) -ADD_PLUGIN([pem], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen fuzz]) +ADD_PLUGIN([pem], [s charon pki scripts manager medsrv attest nm cmd aikgen fuzz]) ADD_PLUGIN([padlock], [s charon]) -ADD_PLUGIN([openssl], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen]) -ADD_PLUGIN([wolfssl], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen]) -ADD_PLUGIN([gcrypt], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen]) -ADD_PLUGIN([botan], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen]) -ADD_PLUGIN([pkcs8], [s charon scepclient pki scripts manager medsrv attest nm cmd]) -ADD_PLUGIN([af-alg], [s charon scepclient pki scripts medsrv attest nm cmd aikgen]) +ADD_PLUGIN([openssl], [s charon pki scripts manager medsrv attest nm cmd aikgen]) +ADD_PLUGIN([wolfssl], [s charon pki scripts manager medsrv attest nm cmd aikgen]) +ADD_PLUGIN([gcrypt], [s charon pki scripts manager medsrv attest nm cmd aikgen]) +ADD_PLUGIN([botan], [s charon pki scripts manager medsrv attest nm cmd aikgen]) +ADD_PLUGIN([pkcs8], [s charon pki scripts manager medsrv attest nm cmd]) +ADD_PLUGIN([af-alg], [s charon pki scripts medsrv attest nm cmd aikgen]) ADD_PLUGIN([fips-prf], [s charon nm cmd]) -ADD_PLUGIN([gmp], [s charon scepclient pki scripts manager medsrv attest nm cmd aikgen fuzz]) +ADD_PLUGIN([gmp], [s charon pki scripts manager medsrv attest nm cmd aikgen fuzz]) ADD_PLUGIN([curve25519], [s charon pki scripts nm cmd]) ADD_PLUGIN([agent], [s charon nm cmd]) ADD_PLUGIN([keychain], [s charon cmd]) @@ -1557,8 +1555,8 @@ ADD_PLUGIN([ntru], [s charon scripts nm cmd]) ADD_PLUGIN([drbg], [s charon pki scripts nm cmd]) ADD_PLUGIN([newhope], [s charon scripts nm cmd]) ADD_PLUGIN([bliss], [s charon pki scripts nm cmd]) -ADD_PLUGIN([curl], [s charon scepclient pki scripts nm cmd]) -ADD_PLUGIN([files], [s charon scepclient pki scripts nm cmd]) +ADD_PLUGIN([curl], [s charon pki scripts nm cmd]) +ADD_PLUGIN([files], [s charon pki scripts nm cmd]) ADD_PLUGIN([winhttp], [s charon pki scripts]) ADD_PLUGIN([soup], [s charon pki scripts nm cmd]) ADD_PLUGIN([mysql], [s charon pool manager medsrv attest]) @@ -1838,11 +1836,10 @@ AM_CONDITIONAL(USE_ADNS, test x$adns = xtrue) AM_CONDITIONAL(USE_CHARON, test x$charon = xtrue) AM_CONDITIONAL(USE_NM, test x$nm = xtrue) AM_CONDITIONAL(USE_PKI, test x$pki = xtrue) -AM_CONDITIONAL(USE_SCEPCLIENT, test x$scepclient = xtrue) AM_CONDITIONAL(USE_SCRIPTS, test x$scripts = xtrue) AM_CONDITIONAL(USE_FUZZING, test x$fuzzing = xtrue) AM_CONDITIONAL(USE_CONFTEST, test x$conftest = xtrue) -AM_CONDITIONAL(USE_LIBSTRONGSWAN, test x$charon = xtrue -o x$pki = xtrue -o x$scepclient = xtrue -o x$conftest = xtrue -o x$fast = xtrue -o x$imcv = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$tls = xtrue -o x$tnc_tnccs = xtrue -o x$aikgen = xtrue -o x$svc = xtrue -o x$systemd = xtrue) +AM_CONDITIONAL(USE_LIBSTRONGSWAN, test x$charon = xtrue -o x$pki = xtrue -o x$conftest = xtrue -o x$fast = xtrue -o x$imcv = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$tls = xtrue -o x$tnc_tnccs = xtrue -o x$aikgen = xtrue -o x$svc = xtrue -o x$systemd = xtrue) AM_CONDITIONAL(USE_LIBCHARON, test x$charon = xtrue -o x$conftest = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$svc = xtrue -o x$systemd = xtrue) AM_CONDITIONAL(USE_LIBIPSEC, test x$libipsec = xtrue) AM_CONDITIONAL(USE_LIBNTTFFT, test x$bliss = xtrue -o x$newhope = xtrue) @@ -1851,7 +1848,7 @@ AM_CONDITIONAL(USE_LIBTNCCS, test x$tnc_tnccs = xtrue) AM_CONDITIONAL(USE_LIBPTTLS, test x$tnc_tnccs = xtrue) AM_CONDITIONAL(USE_LIBTPMTSS, test x$tss_trousers = xtrue -o x$tss_tss2 = xtrue -o x$tpm = xtrue -o x$aikgen = xtrue -o x$imcv = xtrue) AM_CONDITIONAL(USE_FILE_CONFIG, test x$stroke = xtrue) -AM_CONDITIONAL(USE_IPSEC_SCRIPT, test x$stroke = xtrue -o x$scepclient = xtrue -o x$conftest = xtrue) +AM_CONDITIONAL(USE_IPSEC_SCRIPT, test x$stroke = xtrue -o x$conftest = xtrue) AM_CONDITIONAL(USE_LIBCAP, test x$capabilities = xlibcap) AM_CONDITIONAL(USE_VSTR, test x$printf_hooks = xvstr) AM_CONDITIONAL(USE_BUILTIN_PRINTF, test x$printf_hooks = xbuiltin) @@ -1927,7 +1924,6 @@ AM_COND_IF([USE_IMV_SWIMA], [strongswan_options=${strongswan_options}" sec-updat AM_COND_IF([USE_LIBTNCCS], [strongswan_options=${strongswan_options}" tnc"]) AM_COND_IF([USE_MANAGER], [strongswan_options=${strongswan_options}" manager"]) AM_COND_IF([USE_MEDSRV], [strongswan_options=${strongswan_options}" medsrv"]) -AM_COND_IF([USE_SCEPCLIENT], [strongswan_options=${strongswan_options}" scepclient"]) AM_COND_IF([USE_PKI], [strongswan_options=${strongswan_options}" pki"]) AM_COND_IF([USE_SWANCTL], [strongswan_options=${strongswan_options}" swanctl"]) AM_COND_IF([USE_SYSTEMD], [strongswan_options=${strongswan_options}" charon-systemd"]) @@ -2134,7 +2130,6 @@ AC_CONFIG_FILES([ src/starter/Makefile src/starter/tests/Makefile src/_updown/Makefile - src/scepclient/Makefile src/aikgen/Makefile src/tpm_extendpcr/Makefile src/pki/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 16699f11c..2e3af366d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -75,10 +75,6 @@ if USE_UPDOWN SUBDIRS += _updown endif -if USE_SCEPCLIENT - SUBDIRS += scepclient -endif - if USE_PKI SUBDIRS += pki endif diff --git a/src/checksum/Makefile.am b/src/checksum/Makefile.am index 107b26c31..6c1224dac 100644 --- a/src/checksum/Makefile.am +++ b/src/checksum/Makefile.am @@ -102,10 +102,6 @@ if USE_SYSTEMD exes += $(DESTDIR)$(sbindir)/charon-systemd endif -if USE_SCEPCLIENT - exes += $(DESTDIR)$(ipsecdir)/scepclient -endif - if USE_PKI exes += $(DESTDIR)$(bindir)/pki endif diff --git a/src/scepclient/.gitignore b/src/scepclient/.gitignore deleted file mode 100644 index 0cc9ec626..000000000 --- a/src/scepclient/.gitignore +++ /dev/null @@ -1 +0,0 @@ -scepclient diff --git a/src/scepclient/Android.mk b/src/scepclient/Android.mk deleted file mode 100644 index bec3d77ff..000000000 --- a/src/scepclient/Android.mk +++ /dev/null @@ -1,28 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -# copy-n-paste from Makefile.am -scepclient_SOURCES := \ -scepclient.c scep.c scep.h - -LOCAL_SRC_FILES := $(filter %.c,$(scepclient_SOURCES)) - -# build scepclient ------------------------------------------------------------- - -LOCAL_C_INCLUDES += \ - $(strongswan_PATH)/src/libstrongswan - -LOCAL_CFLAGS := $(strongswan_CFLAGS) \ - -DPLUGINS='"$(strongswan_SCEPCLIENT_PLUGINS)"' - -LOCAL_MODULE := scepclient - -LOCAL_MODULE_TAGS := optional - -LOCAL_ARM_MODE := arm - -LOCAL_PRELINK_MODULE := false - -LOCAL_SHARED_LIBRARIES += libstrongswan - -include $(BUILD_EXECUTABLE) \ No newline at end of file diff --git a/src/scepclient/Makefile.am b/src/scepclient/Makefile.am deleted file mode 100644 index 13116723b..000000000 --- a/src/scepclient/Makefile.am +++ /dev/null @@ -1,16 +0,0 @@ -ipsec_PROGRAMS = scepclient -scepclient_SOURCES = \ -scepclient.c scep.c scep.h - -scepclient.o : $(top_builddir)/config.status - -AM_CPPFLAGS = \ - -I$(top_srcdir)/src/libstrongswan \ - -DIPSEC_CONFDIR=\"${sysconfdir}\" \ - -DPLUGINS=\""${scepclient_plugins}\"" - -scepclient_LDADD = \ -$(top_builddir)/src/libstrongswan/libstrongswan.la - -dist_man_MANS = scepclient.8 -EXTRA_DIST = Android.mk diff --git a/src/scepclient/README.md b/src/scepclient/README.md new file mode 100644 index 000000000..18526cf11 --- /dev/null +++ b/src/scepclient/README.md @@ -0,0 +1,18 @@ +# ipsec scepclient # + +## Description ## + +The `ipsec scepclient` tool was an early client implementation of the +_Simple Certificate Enrollment Protocol_ (SCEP). + +The tool was written in 2005 and only got marginal updates since then. Hence it +implemented an old version of the SCEP Internet Draft (version 10/11 of +`draft-nourse-scep` and used the broken `MD5` hash and single `DES` encryption +algorithms as defaults. + +## Obsolescence ## + +With strongSwan version 5.9.8 `*ipsec scepclient*` has been removed and replaced +by the `pki` subcommands `pki --scep` and `pki --scepca` which implement the new +SCEP RFC 8894 standard that was released in September 2020 and which supports +trusted **certificate renewal** based on the existing client certificate. diff --git a/src/scepclient/scep.c b/src/scepclient/scep.c deleted file mode 100644 index 01ae450aa..000000000 --- a/src/scepclient/scep.c +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2005 Jan Hutter, Martin Willi - * - * 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 . - * - * 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. - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "scep.h" - -static const char *pkiStatus_values[] = { "0", "2", "3" }; - -static const char *pkiStatus_names[] = { - "SUCCESS", - "FAILURE", - "PENDING", - "UNKNOWN" -}; - -static const char *msgType_values[] = { "3", "19", "20", "21", "22" }; - -static const char *msgType_names[] = { - "CertRep", - "PKCSReq", - "GetCertInitial", - "GetCert", - "GetCRL", - "Unknown" -}; - -static const char *failInfo_reasons[] = { - "badAlg - unrecognized or unsupported algorithm identifier", - "badMessageCheck - integrity check failed", - "badRequest - transaction not permitted or supported", - "badTime - Message time field was not sufficiently close to the system time", - "badCertId - No certificate could be identified matching the provided criteria" -}; - -const scep_attributes_t empty_scep_attributes = { - SCEP_Unknown_MSG , /* msgType */ - SCEP_UNKNOWN , /* pkiStatus */ - SCEP_unknown_REASON, /* failInfo */ - { NULL, 0 } , /* transID */ - { NULL, 0 } , /* senderNonce */ - { NULL, 0 } , /* recipientNonce */ -}; - -/** - * Extract X.501 attributes - */ -void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator, - scep_attributes_t *attrs) -{ - chunk_t attr; - - if (pkcs7->get_attribute(pkcs7, OID_PKI_MESSAGE_TYPE, enumerator, &attr)) - { - scep_msg_t m; - - for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++) - { - if (strncmp(msgType_values[m], attr.ptr, attr.len) == 0) - { - attrs->msgType = m; - } - } - DBG2(DBG_APP, "messageType: %s", msgType_names[attrs->msgType]); - free(attr.ptr); - } - if (pkcs7->get_attribute(pkcs7, OID_PKI_STATUS, enumerator, &attr)) - { - pkiStatus_t s; - - for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++) - { - if (strncmp(pkiStatus_values[s], attr.ptr, attr.len) == 0) - { - attrs->pkiStatus = s; - } - } - DBG2(DBG_APP, "pkiStatus: %s", pkiStatus_names[attrs->pkiStatus]); - free(attr.ptr); - } - if (pkcs7->get_attribute(pkcs7, OID_PKI_FAIL_INFO, enumerator, &attr)) - { - if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4') - { - attrs->failInfo = (failInfo_t)(*attr.ptr - '0'); - } - if (attrs->failInfo != SCEP_unknown_REASON) - { - DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]); - } - free(attr.ptr); - } - - pkcs7->get_attribute(pkcs7, OID_PKI_SENDER_NONCE, enumerator, - &attrs->senderNonce); - pkcs7->get_attribute(pkcs7, OID_PKI_RECIPIENT_NONCE, enumerator, - &attrs->recipientNonce); - pkcs7->get_attribute(pkcs7, OID_PKI_TRANS_ID, enumerator, - &attrs->transID); -} - -/** - * Generates a unique fingerprint of the pkcs10 request - * by computing an MD5 hash over it - */ -chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10) -{ - chunk_t digest = chunk_alloca(HASH_SIZE_MD5); - hasher_t *hasher; - - hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); - if (!hasher || !hasher->get_hash(hasher, pkcs10, digest.ptr)) - { - DESTROY_IF(hasher); - return chunk_empty; - } - hasher->destroy(hasher); - - return chunk_to_hex(digest, NULL, FALSE); -} - -/** - * Generate a transaction id as the MD5 hash of an public key - * the transaction id is also used as a unique serial number - */ -void scep_generate_transaction_id(public_key_t *key, chunk_t *transID, - chunk_t *serialNumber) -{ - chunk_t digest = chunk_alloca(HASH_SIZE_MD5); - chunk_t keyEncoding = chunk_empty, keyInfo; - hasher_t *hasher; - int zeros = 0, msb_set = 0; - - key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding); - - keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm", - asn1_algorithmIdentifier(OID_RSA_ENCRYPTION), - asn1_bitstring("m", keyEncoding)); - - hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); - if (!hasher || !hasher->get_hash(hasher, keyInfo, digest.ptr)) - { - memset(digest.ptr, 0, digest.len); - } - DESTROY_IF(hasher); - free(keyInfo.ptr); - - /* the serialNumber should be valid ASN1 integer content: - * remove leading zeros, add one if MSB is set (two's complement) */ - while (zeros < digest.len) - { - if (digest.ptr[zeros]) - { - if (digest.ptr[zeros] & 0x80) - { - msb_set = 1; - } - break; - } - zeros++; - } - *serialNumber = chunk_alloc(digest.len - zeros + msb_set); - if (msb_set) - { - serialNumber->ptr[0] = 0x00; - } - memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros, - digest.len - zeros); - - /* the transaction id is the serial number in hex format */ - *transID = chunk_to_hex(digest, NULL, TRUE); -} - -/** - * Builds a pkcs7 enveloped and signed scep request - */ -chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, - certificate_t *enc_cert, encryption_algorithm_t enc_alg, - size_t key_size, certificate_t *signer_cert, - hash_algorithm_t digest_alg, private_key_t *private_key) -{ - chunk_t request; - container_t *container; - char nonce[16]; - rng_t *rng; - chunk_t senderNonce, msgType; - - /* generate senderNonce */ - rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); - if (!rng || !rng->get_bytes(rng, sizeof(nonce), nonce)) - { - DESTROY_IF(rng); - return chunk_empty; - } - rng->destroy(rng); - - /* encrypt data in enveloped-data PKCS#7 */ - container = lib->creds->create(lib->creds, - CRED_CONTAINER, CONTAINER_PKCS7_ENVELOPED_DATA, - BUILD_BLOB, data, - BUILD_CERT, enc_cert, - BUILD_ENCRYPTION_ALG, enc_alg, - BUILD_KEY_SIZE, (int)key_size, - BUILD_END); - if (!container) - { - return chunk_empty; - } - if (!container->get_encoding(container, &request)) - { - container->destroy(container); - return chunk_empty; - } - container->destroy(container); - - /* sign enveloped-data in a signed-data PKCS#7 */ - senderNonce = asn1_wrap(ASN1_OCTET_STRING, "c", chunk_from_thing(nonce)); - transID = asn1_wrap(ASN1_PRINTABLESTRING, "c", transID); - msgType = asn1_wrap(ASN1_PRINTABLESTRING, "c", - chunk_create((char*)msgType_values[msg], - strlen(msgType_values[msg]))); - - container = lib->creds->create(lib->creds, - CRED_CONTAINER, CONTAINER_PKCS7_SIGNED_DATA, - BUILD_BLOB, request, - BUILD_SIGNING_CERT, signer_cert, - BUILD_SIGNING_KEY, private_key, - BUILD_DIGEST_ALG, digest_alg, - BUILD_PKCS7_ATTRIBUTE, OID_PKI_SENDER_NONCE, senderNonce, - BUILD_PKCS7_ATTRIBUTE, OID_PKI_TRANS_ID, transID, - BUILD_PKCS7_ATTRIBUTE, OID_PKI_MESSAGE_TYPE, msgType, - BUILD_END); - - free(request.ptr); - free(senderNonce.ptr); - free(transID.ptr); - free(msgType.ptr); - - if (!container) - { - return chunk_empty; - } - if (!container->get_encoding(container, &request)) - { - container->destroy(container); - return chunk_empty; - } - container->destroy(container); - - return request; -} - -/** - * Converts a binary request to base64 with 64 characters per line - * newline and '+' characters are escaped by %0A and %2B, respectively - */ -static char* escape_http_request(chunk_t req) -{ - char *escaped_req = NULL; - char *p1, *p2; - int lines = 0; - int plus = 0; - int n = 0; - - /* compute and allocate the size of the base64-encoded request */ - int len = 1 + 4 * ((req.len + 2) / 3); - char *encoded_req = malloc(len); - - /* do the base64 conversion */ - chunk_t base64 = chunk_to_base64(req, encoded_req); - len = base64.len + 1; - - /* compute newline characters to be inserted every 64 characters */ - lines = (len - 2) / 64; - - /* count number of + characters to be escaped */ - p1 = encoded_req; - while (*p1 != '\0') - { - if (*p1++ == '+') - { - plus++; - } - } - - escaped_req = malloc(len + 3 * (lines + plus)); - - /* escape special characters in the request */ - p1 = encoded_req; - p2 = escaped_req; - while (*p1 != '\0') - { - if (n == 64) - { - memcpy(p2, "%0A", 3); - p2 += 3; - n = 0; - } - if (*p1 == '+') - { - memcpy(p2, "%2B", 3); - p2 += 3; - } - else - { - *p2++ = *p1; - } - p1++; - n++; - } - *p2 = '\0'; - free(encoded_req); - return escaped_req; -} - -/** - * Send a SCEP request via HTTP and wait for a response - */ -bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, - bool http_get_request, u_int timeout, char *src, - chunk_t *response) -{ - int len; - status_t status; - char *complete_url = NULL; - host_t *srcip = NULL; - - /* initialize response */ - *response = chunk_empty; - - if (src) - { - srcip = host_create_from_string(src, 0); - } - - DBG2(DBG_APP, "sending scep request to '%s'", url); - - if (op == SCEP_PKI_OPERATION) - { - const char operation[] = "PKIOperation"; - - if (http_get_request) - { - char *escaped_req = escape_http_request(msg); - - /* form complete url */ - len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s&message=%s" - , url, operation, escaped_req); - free(escaped_req); - - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_HTTP_VERSION_1_0, - FETCH_TIMEOUT, timeout, - FETCH_REQUEST_HEADER, "Pragma:", - FETCH_REQUEST_HEADER, "Host:", - FETCH_REQUEST_HEADER, "Accept:", - FETCH_SOURCEIP, srcip, - FETCH_END); - } - else /* HTTP_POST */ - { - /* form complete url */ - len = strlen(url) + 11 + strlen(operation) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s", url, operation); - - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_HTTP_VERSION_1_0, - FETCH_TIMEOUT, timeout, - FETCH_REQUEST_DATA, msg, - FETCH_REQUEST_TYPE, "", - FETCH_REQUEST_HEADER, "Expect:", - FETCH_SOURCEIP, srcip, - FETCH_END); - } - } - else /* SCEP_GET_CA_CERT */ - { - const char operation[] = "GetCACert"; - int i; - - /* escape spaces, TODO: complete URL escape */ - for (i = 0; i < msg.len; i++) - { - if (msg.ptr[i] == ' ') - { - msg.ptr[i] = '+'; - } - } - - /* form complete url */ - len = strlen(url) + 32 + strlen(operation) + msg.len + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s&message=%.*s", - url, operation, (int)msg.len, msg.ptr); - - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_HTTP_VERSION_1_0, - FETCH_TIMEOUT, timeout, - FETCH_SOURCEIP, srcip, - FETCH_END); - } - - DESTROY_IF(srcip); - free(complete_url); - return (status == SUCCESS); -} - -err_t scep_parse_response(chunk_t response, chunk_t transID, - container_t **out, scep_attributes_t *attrs) -{ - enumerator_t *enumerator; - bool verified = FALSE; - container_t *container; - auth_cfg_t *auth; - - container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, - BUILD_BLOB_ASN1_DER, response, BUILD_END); - if (!container) - { - return "error parsing the scep response"; - } - if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA) - { - container->destroy(container); - return "scep response is not PKCS#7 signed-data"; - } - - enumerator = container->create_signature_enumerator(container); - while (enumerator->enumerate(enumerator, &auth)) - { - verified = TRUE; - extract_attributes((pkcs7_t*)container, enumerator, attrs); - if (!chunk_equals(transID, attrs->transID)) - { - enumerator->destroy(enumerator); - container->destroy(container); - return "transaction ID of scep response does not match"; - } - } - enumerator->destroy(enumerator); - if (!verified) - { - container->destroy(container); - return "unable to verify PKCS#7 container"; - } - *out = container; - return NULL; -} diff --git a/src/scepclient/scep.h b/src/scepclient/scep.h deleted file mode 100644 index d5ad2a3bd..000000000 --- a/src/scepclient/scep.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2005 Jan Hutter, Martin Willi - * - * 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 . - * - * 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. - */ - -#ifndef _SCEP_H -#define _SCEP_H - -#include -#include - -/* supported SCEP operation types */ -typedef enum { - SCEP_PKI_OPERATION, - SCEP_GET_CA_CERT -} scep_op_t; - -/* SCEP pkiStatus values */ -typedef enum { - SCEP_SUCCESS, - SCEP_FAILURE, - SCEP_PENDING, - SCEP_UNKNOWN -} pkiStatus_t; - -/* SCEP messageType values */ -typedef enum { - SCEP_CertRep_MSG, - SCEP_PKCSReq_MSG, - SCEP_GetCertInitial_MSG, - SCEP_GetCert_MSG, - SCEP_GetCRL_MSG, - SCEP_Unknown_MSG -} scep_msg_t; - -/* SCEP failure reasons */ -typedef enum { - SCEP_badAlg_REASON = 0, - SCEP_badMessageCheck_REASON = 1, - SCEP_badRequest_REASON = 2, - SCEP_badTime_REASON = 3, - SCEP_badCertId_REASON = 4, - SCEP_unknown_REASON = 5 -} failInfo_t; - -/* SCEP attributes */ -typedef struct { - scep_msg_t msgType; - pkiStatus_t pkiStatus; - failInfo_t failInfo; - chunk_t transID; - chunk_t senderNonce; - chunk_t recipientNonce; -} scep_attributes_t; - -extern const scep_attributes_t empty_scep_attributes; - -bool parse_attributes(chunk_t blob, scep_attributes_t *attrs); -void scep_generate_transaction_id(public_key_t *key, - chunk_t *transID, - chunk_t *serialNumber); -chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10); -chunk_t scep_transId_attribute(chunk_t transaction_id); -chunk_t scep_messageType_attribute(scep_msg_t m); -chunk_t scep_senderNonce_attribute(void); -chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, - certificate_t *enc_cert, encryption_algorithm_t enc_alg, - size_t key_size, certificate_t *signer_cert, - hash_algorithm_t digest_alg, private_key_t *private_key); -bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, - bool http_get_request, u_int timeout, char *src, - chunk_t *response); -err_t scep_parse_response(chunk_t response, chunk_t transID, - container_t **out, scep_attributes_t *attrs); - -#endif /* _SCEP_H */ diff --git a/src/scepclient/scepclient.8 b/src/scepclient/scepclient.8 deleted file mode 100644 index a9d3bd993..000000000 --- a/src/scepclient/scepclient.8 +++ /dev/null @@ -1,293 +0,0 @@ -.\" -.TH "IPSEC_SCEPCLIENT" "8" "2012-05-11" "strongSwan" "" -.SH "NAME" -ipsec scepclient \- Client for the SCEP protocol -.SH "SYNOPSIS" -.B ipsec scepclient [argument ...] -.sp -.B ipsec scepclient -.B \-\-help -.br -.B ipsec scepclient -.B \-\-version -.SH "DESCRIPTION" -.BR scepclient -is a client implementation of Cisco System's Simple Certificate Enrollment Protocol (SCEP) written for Linux strongSwan . -.BR scepclient -is designed to be used for certificate enrollment on machines using the OpenSource IPsec solution -.I strongSwan. -.SH "FEATURES" -.BR scepclient -implements the following features of SCEP: -.br -.IP "\-" 4 -Automatic enrollment of client certificate using a preshared secret -.IP "\-" 4 -Manual enrollment of client certificate. Offline fingerprint check required! -.IP "\-" 4 -Acquisition of CA certificate(s) -.SH "OPTIONS" -.SS Basic Startup Options -.B \-v, \-\-version -.RS 4 -Display the version of ipsec scepclient. -.PP -.RE -.B \-h, \-\-help -.RS 4 -Display usage of ipsec scepclient. -.RE - -.SS General Options -.B \-u, \-\-url \fIurl\fP -.RS 4 -Full HTTP URL of the SCEP server to be used for certificate enrollment and CA certificate acquisition. -.RE -.PP -.B \-+, \-\-optionsfrom \fIfilename\fP -.RS 4 -Reads additional options from \fIfilename\fP. -.RE -.PP -.B \-f, \-\-force -.RS 4 -Overwrite existing output file[s]. -.RE -.PP -.B \-q, \-\-quiet -.RS 4 -Do not write log output to stderr. -.RE - -.SS Options for CA Certificate Acquisition -.B \-o, \-\-out cacert[=\fIfilename\fP] -.RS 4 -Output file of acquired CA certificate. If more then one CA certificate is -available, \fIfilename\fP is used as prefix for the resulting files (refer to -EXAMPLES below for details). -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/cacerts/caCert.der. -.RE - -.SS Options For Certificate Enrollment -.B \-i, \-\-in \fItype\fP[=\fIfilename\fP] -.RS 4 -Input file for certificate enrollment. This option can be specified multiple times to specify input files for every \fItype\fP. -Input files can be either DER or PEM encoded. -.PP -Supported values for \fItype\fP: -.IP "\fBpkcs1\fP" 12 -RSA private key in PKCS#1 file format. If no input of this type is specified, a RSA key gets generated. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/private/myKey.der. -.IP "\fBpkcs10\fP" 12 -PKCS#10 certificate request to be used in the SCEP request. If no input of this type is specified, a request is generated. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/req/myReq.der. -.IP "\fBcacert\-enc\fP" 12 -CA certificate to encrypt the SCEP request. Has to be specified for certificate enrollment. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/cacerts/caCert.der. -.IP "\fBcacert\-sig\fP" 12 -CA certificate to check signature of SCEP reply. Has to be specified for certificate enrollment. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/cacerts/caCert.der. -.IP "\fBcert-self\fP" 12 -Certificate to be used in the SCEP request. If it is not specified a -self-signed certificate is generated automatically. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/certs/selfCert.der. -.RE -.PP -.B \-k, \-\-keylength \fIbits\fP -.RS 4 -sets the key length for RSA key generation. The default length for a generated rsa key is set to 2048 bit. -.RE -.PP -.B \-D, \-\-days \fIdays\fP -.RS 4 -Validity of the self-signed X.509 certificate in days. The default is 1825 days (5 years). -.RE -.PP -.B \-S, \-\-startdate \fIYYMMDDHHMMSS\fPZ -.RS 4 -defines the \fBnotBefore\fP date when the X.509 certificate becomes valid. -The date has the format \fIYYMMDDHHMMSS\fP and must be specified in UTC (Zulu time). -If the \fB--startdate\fP option is not specified then the current date is taken as a default. -.RE -.PP -.B \-E, \-\-enddate \fIYYMMDDHHMMSS\fPZ -.RS 4 -defines the \fBnotAfter\fP date when the X.509 certificate will expire. -The date has the format \fIYYMMDDHHMMSS\fP and must be specified in UTC (Zulu time). -If the \fB--enddate\fP option is not specified then the default \fBnotAfter\fP value is computed by -adding the validity interval specified by the \fB--days\fP option to the \fBnotBefore\fP date. -.RE -.PP -.B \-d, \-\-dn \fIdn\fP -.RS 4 -Distinguished name as comma separated list of relative distinguished names. Use quotation marks for a distinguished name containing spaces. If the \fB\-\-dn\fP parameter is missing then the default "C=CH, O=Linux strongSwan, CN=\fIhostname\fP" -is used with \fIhostname\fP being the return value of the \fIgethostname\fP() function. -.RE -.PP -.B \-s, \-\-subjectAltName \fItype\fP=\fIvalue\fP -.RS 4 -Include subjectAltName in certificate request. This option can be specified multiple times to specify a subjectAltName -for every \fItype\fP. -.PP -Supported values for \fItype\fP: -.IP "\fBemail\fP" 12 -subjectAltName is a email address. -.IP "\fBdns\fP" 12 -subjectAltName is a hostname. -.IP "\fBip\fP" 12 -subjectAltName is a IP address. -.RE -.PP -.B \-p, \-\-password \fIpw\fP -.RS 4 -Password to be included as a \fIchallenge password\fP in SCEP request. -If \fIpw\fP is \fB%prompt\fP', the password gets prompted for on the command line. -.IP -\- In automatic mode, this password corresponds to the preshared secret for the given enrollment. -.IP -\- In manual mode, this password can be used to later revoke the corresponding certificate. -.RE -.PP -.B \-a, \-\-algorithm [\fItype\fP=]\fIalgo\fP -.RS 4 -Change the algorithms to be used when generating and transporting (PKCS#7) -certificate requests (PKCS#10). -.PP -Supported values for \fItype\fP: -.IP "\fBenc\fP" 12 -symmetric encryption algorithm in PKCS#7 -.IP "\fBdgst\fP" 12 -hash algorithm for message digest in PKCS#7 -.IP "\fBsig\fP" 12 -hash algorithm for the signature in PKCS#10 -.PP -If \fItype\fP is not specified \fBenc\fP is assumed. -.PP -Supported values for \fIalgo\fP (\fBenc\fP): -.IP "\fBdes\fP" 12 -DES-CBC encryption (key size = 56 bit). Default. -.IP "\fB3des\fP" 12 -Triple DES-EDE-CBC encryption (key size = 168 bit). -.IP "\fBaes128\fP" 12 -AES-CBC encryption (key size = 128 bit). -.IP "\fBaes192\fP" 12 -AES-CBC encryption (key size = 192 bit). -.IP "\fBaes256\fP" 12 -AES-CBC encryption (key size = 256 bit). -.IP "\fBcamellia128\fP" 12 -Camellia-CBC encryption (key size = 128 bit). -.IP "\fBcamellia192\fP" 12 -Camellia-CBC encryption (key size = 192 bit). -.IP "\fBcamellia256\fP" 12 -Camellia-CBC encryption (key size = 256 bit). -.PP -Supported values for \fIalgo\fP (\fBdgst\fP or \fBsig\fP): -.PP -\fBmd5\fP (default), \fBsha1\fP, \fBsha256\fP, \fBsha384\fP, \fBsha512\fP -.RE -.PP -.B \-o, \-\-out \fItype\fP[=\fIfilename\fP] -.RS 4 -Output file for certificate enrollment. This option can be specified multiple times to specify output files for every \fItype\fP. -.PP -Supported values for \fItype\fP: -.IP "\fBpkcs1\fP" 12 -RSA private key in PKCS#1 file format. If specified, the RSA key used for enrollment is stored in file \fIfilename\fP. -If none of the \fItypes\fP listed below are specified, \fBscepclient\fP will stop after outputting this file. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/private/myKey.der. -.IP "\fBpkcs10\fP" 12 -PKCS#10 certificate request. If specified, the PKCS#10 request used or certificate enrollment is stored in file \fIfilename\fP. -If none of the \fItypes\fP listed below are specified, \fBscepclient\fP will stop after outputting this file. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/req/myReq.der. -.IP "\fBpkcs7\fP" 12 -PKCS#7 SCEP request as it is sent using HTTP to the SCEP server. If specified, this SCEP request is stored in file \fIfilename\fP. -If none of \fItypes\fP listed below is not specified, \fBscepclient\fP will stop after outputting this file. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/req/pkcs7.der. -.IP "\fBcert-self\fP" 12 -Self-signed certificate. If specified the self-signed certificate is stored in file \fIfilename\fP. -.br -The default \fIfilename\fP is $CONFDIR/ipsec.d/certs/selfCert.der. -.IP "\fBcert\fP" 12 -Enrolled certificate. This \fItype\fP must be specified for certificate enrollment. -The enrolled certificate is stored in file \fIfilename\fP. -.br -The default \fIfilename\fP is set to $CONFDIR/ipsec.d/certs/myCert.der. -.RE -.PP -.B \-m, \-\-method \fImethod\fP -.RS 4 -Change HTTP request method for certificate enrollment. Default is \fBget\fP. -.PP -Supported values for \fImethod\fP: -.IP "\fBpost\fP" 12 -Certificate enrollment using HTTP POST. Must be supported by the given SCEP server. -.IP "\fBget\fP" 12 -Certificate enrollment using HTTP GET. -.RE -.PP -.B \-t, \-\-interval \fIseconds\fP -.RS 4 -Set interval time in seconds when polling in manual mode. -The default interval is set to 5 seconds. -.RE -.PP -.B \-x, \-\-maxpolltime \fIseconds\fP -.RS 4 -Set max time in seconds to poll in manual mode. -The default max time is set to unlimited. -.RE - -.SS Debugging Output Options: -.B \-l, \-\-debug \fIlevel\fP -.RS 4 -Changes the log level (-1..4, default: 1) -.RE -.SH "EXAMPLES" -.B ipsec scepclient \-\-out caCert \-\-url http://scepserver/cgi\-bin/pkiclient.exe \-f -.RS 4 -Acquire CA certificate from SCEP server and store it in the default file $CONFDIR/ipsec.d/cacerts/caCert.der. -If more then one CA certificate is returned, store them in files named -\'caCert\-1.der\', \'caCert\-2.der\', etc. -If an RA certificate is returned, store it in a file named \'caCert\-ra.der\'. -If more than one RA certificate is returned, store them in files named -\'caCert\-ra\-1.der\', \'caCert\-ra\-2.der\', etc. -.RE -.PP -.B ipsec scepclient \-\-out pkcs1=joeKey.der \-k 1024 -.RS 4 -Generate RSA private key with key length of 1024 bit and store it in file joeKey.der. -.RE -.PP -.B ipsec scepclient \-\-in pkcs1=joeKey.der \-\-out pkcs10=joeReq.der \e -.br -.B \-\-dn \*(rqC=AT, CN=John Doe\*(rq \-s email=john@doe.com \-p mypassword -.RS 4 -Generate a PKCS#10 request and store it in file joeReq.der. Use the RSA private key joeKey.der -created earlier to sign the PKCS#10\-Request. In addition to the distinguished name include a -email\-subjectAltName and a challenge password in the request. -.RE -.PP -.B ipsec scepclient \-\-out pkcs1=joeKey.der \-\-out cert==joeCert.der \e -.br -.B \-\-dn \*(rqC=CH, CN=John Doe\*(rq \-k 512 \-p 5xH2pnT7wq \e -.br -.B \-\-url http://scep.hsr.ch/cgi\-bin/pkiclient.exe \e -.br -.B \-\-in cacert\-enc=caCert.der \-\-in cacert\-sig=caCert.der -.RS 4 -Generate a new RSA key for the request and store it in joeKey.der. Then enroll a certificate and store as joeCert.der. -The challenge password is '5xH2pnT7wq'. The encryption and signature check has to be made with the same CA certificate -caCert.der. -.RE - -.SH "BUGS" -\fB\-\-optionsfrom\fP seems to have parsing problems reading option files containing strings in quotation marks. diff --git a/src/scepclient/scepclient.c b/src/scepclient/scepclient.c deleted file mode 100644 index 1c5e24237..000000000 --- a/src/scepclient/scepclient.c +++ /dev/null @@ -1,1491 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2005 Jan Hutter, Martin Willi - * - * 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 . - * - * 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "scep.h" - -/* - * definition of some defaults - */ - -/* some paths */ -#define REQ_PATH IPSEC_CONFDIR "/ipsec.d/reqs" -#define HOST_CERT_PATH IPSEC_CONFDIR "/ipsec.d/certs" -#define CA_CERT_PATH IPSEC_CONFDIR "/ipsec.d/cacerts" -#define PRIVATE_KEY_PATH IPSEC_CONFDIR "/ipsec.d/private" - -/* default name of DER-encoded PKCS#1 private key file */ -#define DEFAULT_FILENAME_PKCS1 "myKey.der" - -/* default name of DER-encoded PKCS#10 certificate request file */ -#define DEFAULT_FILENAME_PKCS10 "myReq.der" - -/* default name of DER-encoded PKCS#7 file */ -#define DEFAULT_FILENAME_PKCS7 "pkcs7.der" - -/* default name of DER-encoded self-signed X.509 certificate file */ -#define DEFAULT_FILENAME_CERT_SELF "selfCert.der" - -/* default name of DER-encoded X.509 certificate file */ -#define DEFAULT_FILENAME_CERT "myCert.der" - -/* default name of DER-encoded CA cert file used for key encipherment */ -#define DEFAULT_FILENAME_CACERT_ENC "caCert.der" - -/* default name of the der encoded CA cert file used for signature verification */ -#define DEFAULT_FILENAME_CACERT_SIG "caCert.der" - -/* default prefix of the der encoded CA certificates received from the SCEP server */ -#define DEFAULT_FILENAME_PREFIX_CACERT "caCert.der" - -/* default certificate validity */ -#define DEFAULT_CERT_VALIDITY 5 * 3600 * 24 * 365 /* seconds */ - -/* default polling time interval in SCEP manual mode */ -#define DEFAULT_POLL_INTERVAL 20 /* seconds */ - -/* default key length for self-generated RSA keys */ -#define DEFAULT_RSA_KEY_LENGTH 2048 /* bits */ - -/* default distinguished name */ -#define DEFAULT_DN "C=CH, O=Linux strongSwan, CN=" - -/* minimum RSA key size */ -#define RSA_MIN_OCTETS (512 / BITS_PER_BYTE) - -/* challenge password buffer size */ -#define MAX_PASSWORD_LENGTH 256 - -/* Max length of filename for tempfile */ -#define MAX_TEMP_FILENAME_LENGTH 256 - - -/* current scepclient version */ -static const char *scepclient_version = "1.0"; - -/* by default the CRL policy is lenient */ -bool strict_crl_policy = FALSE; - -/* by default pluto does not check crls dynamically */ -long crl_check_interval = 0; - -/* by default pluto logs out after every smartcard use */ -bool pkcs11_keep_state = FALSE; - -/* by default HTTP fetch timeout is 30s */ -static u_int http_timeout = 30; - -/* address to bind for HTTP fetches */ -static char* http_bind = NULL; - -/* options read by optionsfrom */ -options_t *options; - -/* - * Global variables - */ -chunk_t pkcs1; -chunk_t pkcs7; -chunk_t challengePassword; -chunk_t serialNumber; -chunk_t transID; -chunk_t fingerprint; -chunk_t encoding; -chunk_t pkcs10_encoding; -chunk_t issuerAndSubject; -chunk_t getCertInitial; -chunk_t scep_response; - -linked_list_t *subjectAltNames; - -identification_t *subject = NULL; -private_key_t *private_key = NULL; -public_key_t *public_key = NULL; -certificate_t *x509_signer = NULL; -certificate_t *x509_ca_enc = NULL; -certificate_t *x509_ca_sig = NULL; -certificate_t *pkcs10_req = NULL; - -mem_cred_t *creds = NULL; - -/* logging */ -static bool log_to_stderr = TRUE; -static bool log_to_syslog = TRUE; -static level_t default_loglevel = 1; - -/** - * logging function for scepclient - */ -static void scepclient_dbg(debug_t group, level_t level, char *fmt, ...) -{ - char buffer[8192]; - char *current = buffer, *next; - va_list args; - - if (level <= default_loglevel) - { - if (log_to_stderr) - { - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - fprintf(stderr, "\n"); - } - if (log_to_syslog) - { - /* write in memory buffer first */ - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - - /* do a syslog with every line */ - while (current) - { - next = strchr(current, '\n'); - if (next) - { - *(next++) = '\0'; - } - syslog(LOG_INFO, "%s\n", current); - current = next; - } - } - } -} - -/** - * Initialize logging to stderr/syslog - */ -static void init_log(const char *program) -{ - dbg = scepclient_dbg; - - if (log_to_stderr) - { - setbuf(stderr, NULL); - } - if (log_to_syslog) - { - openlog(program, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_AUTHPRIV); - } -} - -/** - * join two paths if filename is not absolute - */ -static void join_paths(char *target, size_t target_size, char *parent, - char *filename) -{ - if (*filename == '/' || *filename == '.') - { - snprintf(target, target_size, "%s", filename); - } - else - { - snprintf(target, target_size, "%s/%s", parent, filename); - } -} - -/** - * add a suffix to a given filename, properly handling extensions like '.der' - */ -static void add_path_suffix(char *target, size_t target_size, char *filename, - char *suffix_fmt, ...) -{ - char suffix[PATH_MAX], *start, *dot; - va_list args; - - va_start(args, suffix_fmt); - vsnprintf(suffix, sizeof(suffix), suffix_fmt, args); - va_end(args); - - start = strrchr(filename, '/'); - start = start ?: filename; - dot = strrchr(start, '.'); - - if (!dot || dot == start || dot[1] == '\0') - { /* no extension add suffix at the end */ - snprintf(target, target_size, "%s%s", filename, suffix); - } - else - { /* add the suffix between the filename and the extension */ - snprintf(target, target_size, "%.*s%s%s", (int)(dot - filename), - filename, suffix, dot); - } -} - -/** - * @brief exit scepclient - * - * @param status 0 = OK, 1 = general discomfort - */ -static void exit_scepclient(err_t message, ...) -{ - int status = 0; - - if (creds) - { - lib->credmgr->remove_set(lib->credmgr, &creds->set); - creds->destroy(creds); - } - - DESTROY_IF(subject); - DESTROY_IF(private_key); - DESTROY_IF(public_key); - DESTROY_IF(x509_signer); - DESTROY_IF(x509_ca_enc); - DESTROY_IF(x509_ca_sig); - DESTROY_IF(pkcs10_req); - subjectAltNames->destroy_offset(subjectAltNames, - offsetof(identification_t, destroy)); - free(pkcs1.ptr); - free(pkcs7.ptr); - free(serialNumber.ptr); - free(transID.ptr); - free(fingerprint.ptr); - free(encoding.ptr); - free(pkcs10_encoding.ptr); - free(issuerAndSubject.ptr); - free(getCertInitial.ptr); - free(scep_response.ptr); - options->destroy(options); - - /* print any error message to stderr */ - if (message != NULL && *message != '\0') - { - va_list args; - char m[8192]; - - va_start(args, message); - vsnprintf(m, sizeof(m), message, args); - va_end(args); - - fprintf(stderr, "error: %s\n", m); - status = -1; - } - library_deinit(); - exit(status); -} - -/** - * @brief prints the program version and exits - * - */ -static void version(void) -{ - printf("scepclient %s\n", scepclient_version); - exit_scepclient(NULL); -} - -/** - * @brief prints the usage of the program to the stderr output - * - * If message is set, program is exited with 1 (error) - * @param message message in case of an error - */ -static void usage(const char *message) -{ - fprintf(stderr, - "Usage: scepclient\n" - " --help (-h) show usage and exit\n" - " --version (-v) show version and exit\n" - " --quiet (-q) do not write log output to stderr\n" - " --in (-i) [=] use of for input\n" - " = pkcs1 | pkcs10 | cert-self\n" - " cacert-enc | cacert-sig\n" - " - if no pkcs1 input is defined, an RSA\n" - " key will be generated\n" - " - if no pkcs10 input is defined, a\n" - " PKCS#10 request will be generated\n" - " - if no cert-self input is defined, a\n" - " self-signed certificate will be generated\n" - " - if no filename is given, default is used\n" - " --out (-o) [=] write output of to \n" - " multiple outputs are allowed\n" - " = pkcs1 | pkcs10 | pkcs7 | cert-self |\n" - " cert | cacert\n" - " - type cacert defines filename prefix of\n" - " received CA certificate(s)\n" - " - if no filename is given, default is used\n" - " --optionsfrom (-+) reads additional options from given file\n" - " --force (-f) force existing file(s)\n" - " --httptimeout (-T) timeout for HTTP operations (default: 30s)\n" - " --bind (-b) source address to bind for HTTP operations\n" - "\n" - "Options for key generation (pkcs1):\n" - " --keylength (-k) key length for RSA key generation\n" - " (default: 2048 bits)\n" - "\n" - "Options for validity:\n" - " --days (-D) validity in days\n" - " --startdate (-S) Z not valid before date\n" - " --enddate (-E) Z not valid after date\n" - "\n" - "Options for request generation (pkcs10):\n" - " --dn (-d) comma separated list of distinguished names\n" - " --subjectAltName (-s) = include subjectAltName in certificate request\n" - " = email | dns | ip \n" - " --password (-p) challenge password\n" - " - use '%%prompt' as pw for a password prompt\n" - " --algorithm (-a) [=] algorithm to be used for PKCS#7 encryption,\n" - " PKCS#7 digest or PKCS#10 signature\n" - " = enc | dgst | sig\n" - " - if no type is given enc is assumed\n" - " = des (default) | 3des | aes128 |\n" - " aes192 | aes256 | camellia128 |\n" - " camellia192 | camellia256\n" - " = md5 (default) | sha1 | sha256 |\n" - " sha384 | sha512\n" - "\n" - "Options for CA certificate acquisition:\n" - " --caname (-c) name of CA to fetch CA certificate(s)\n" - " (default: CAIdentifier)\n" - "Options for enrollment (cert):\n" - " --url (-u) url of the SCEP server\n" - " --method (-m) post | get http request type\n" - " --interval (-t) poll interval in seconds (default 20s)\n" - " --maxpolltime (-x) max poll time in seconds when in manual mode\n" - " (default: unlimited)\n" - "\n" - "Debugging output:\n" - " --debug (-l) changes the log level (-1..4, default: 1)\n" - ); - exit_scepclient(message); -} - -/** - * @brief main of scepclient - * - * @param argc number of arguments - * @param argv pointer to the argument values - */ -int main(int argc, char **argv) -{ - /* external values */ - extern char * optarg; - extern int optind; - - /* type of input and output files */ - typedef enum { - PKCS1 = 0x01, - PKCS10 = 0x02, - PKCS7 = 0x04, - CERT_SELF = 0x08, - CERT = 0x10, - CACERT_ENC = 0x20, - CACERT_SIG = 0x40, - } scep_filetype_t; - - /* filetype to read from, defaults to "generate a key" */ - scep_filetype_t filetype_in = 0; - - /* filetype to write to, no default here */ - scep_filetype_t filetype_out = 0; - - /* input files */ - char *file_in_pkcs1 = DEFAULT_FILENAME_PKCS1; - char *file_in_pkcs10 = DEFAULT_FILENAME_PKCS10; - char *file_in_cert_self = DEFAULT_FILENAME_CERT_SELF; - char *file_in_cacert_enc = DEFAULT_FILENAME_CACERT_ENC; - char *file_in_cacert_sig = DEFAULT_FILENAME_CACERT_SIG; - - /* output files */ - char *file_out_pkcs1 = DEFAULT_FILENAME_PKCS1; - char *file_out_pkcs10 = DEFAULT_FILENAME_PKCS10; - char *file_out_pkcs7 = DEFAULT_FILENAME_PKCS7; - char *file_out_cert_self = DEFAULT_FILENAME_CERT_SELF; - char *file_out_cert = DEFAULT_FILENAME_CERT; - char *file_out_ca_cert = DEFAULT_FILENAME_CACERT_ENC; - - /* by default user certificate is requested */ - bool request_ca_certificate = FALSE; - - /* by default existing files are not overwritten */ - bool force = FALSE; - - /* length of RSA key in bits */ - u_int rsa_keylength = DEFAULT_RSA_KEY_LENGTH; - - /* validity of self-signed certificate */ - time_t validity = DEFAULT_CERT_VALIDITY; - time_t notBefore = 0; - time_t notAfter = 0; - - /* distinguished name for requested certificate, ASCII format */ - char *distinguishedName = NULL; - char default_distinguished_name[BUF_LEN]; - - /* challenge password */ - char challenge_password_buffer[MAX_PASSWORD_LENGTH]; - - /* symmetric encryption algorithm used by pkcs7, default is DES */ - encryption_algorithm_t pkcs7_symmetric_cipher = ENCR_DES; - size_t pkcs7_key_size = 0; - - /* digest algorithm used by pkcs7, default is MD5 */ - hash_algorithm_t pkcs7_digest_alg = HASH_MD5; - - /* signature algorithm used by pkcs10, default is MD5 */ - hash_algorithm_t pkcs10_signature_alg = HASH_MD5; - - /* URL of the SCEP-Server */ - char *scep_url = NULL; - - /* Name of CA to fetch CA certs for */ - char *ca_name = "CAIdentifier"; - - /* http request method, default is GET */ - bool http_get_request = TRUE; - - /* poll interval time in manual mode in seconds */ - u_int poll_interval = DEFAULT_POLL_INTERVAL; - - /* maximum poll time */ - u_int max_poll_time = 0; - - err_t ugh = NULL; - - /* initialize library */ - if (!library_init(NULL, "scepclient")) - { - library_deinit(); - exit(SS_RC_LIBSTRONGSWAN_INTEGRITY); - } - if (lib->integrity && - !lib->integrity->check_file(lib->integrity, "scepclient", argv[0])) - { - fprintf(stderr, "integrity check of scepclient failed\n"); - library_deinit(); - exit(SS_RC_DAEMON_INTEGRITY); - } - - /* initialize global variables */ - pkcs1 = chunk_empty; - pkcs7 = chunk_empty; - serialNumber = chunk_empty; - transID = chunk_empty; - fingerprint = chunk_empty; - encoding = chunk_empty; - pkcs10_encoding = chunk_empty; - issuerAndSubject = chunk_empty; - challengePassword = chunk_empty; - getCertInitial = chunk_empty; - scep_response = chunk_empty; - subjectAltNames = linked_list_create(); - options = options_create(); - - for (;;) - { - static const struct option long_opts[] = { - /* name, has_arg, flag, val */ - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - { "optionsfrom", required_argument, NULL, '+' }, - { "quiet", no_argument, NULL, 'q' }, - { "debug", required_argument, NULL, 'l' }, - { "in", required_argument, NULL, 'i' }, - { "out", required_argument, NULL, 'o' }, - { "force", no_argument, NULL, 'f' }, - { "httptimeout", required_argument, NULL, 'T' }, - { "bind", required_argument, NULL, 'b' }, - { "keylength", required_argument, NULL, 'k' }, - { "dn", required_argument, NULL, 'd' }, - { "days", required_argument, NULL, 'D' }, - { "startdate", required_argument, NULL, 'S' }, - { "enddate", required_argument, NULL, 'E' }, - { "subjectAltName", required_argument, NULL, 's' }, - { "password", required_argument, NULL, 'p' }, - { "algorithm", required_argument, NULL, 'a' }, - { "url", required_argument, NULL, 'u' }, - { "caname", required_argument, NULL, 'c'}, - { "method", required_argument, NULL, 'm' }, - { "interval", required_argument, NULL, 't' }, - { "maxpolltime", required_argument, NULL, 'x' }, - { 0,0,0,0 } - }; - - /* parse next option */ - int c = getopt_long(argc, argv, "hv+:ql:i:o:fT:k:d:s:p:a:u:c:m:t:x:APRCMS", long_opts, NULL); - - switch (c) - { - case EOF: /* end of flags */ - break; - - case 'h': /* --help */ - usage(NULL); - - case 'v': /* --version */ - version(); - - case 'q': /* --quiet */ - log_to_stderr = FALSE; - continue; - - case 'l': /* --debug */ - default_loglevel = atoi(optarg); - continue; - - case 'i': /* --in [= ] */ - { - char *filename = strstr(optarg, "="); - - if (filename) - { - /* replace '=' by '\0' */ - *filename = '\0'; - /* set pointer to start of filename */ - filename++; - } - if (strcaseeq("pkcs1", optarg)) - { - filetype_in |= PKCS1; - if (filename) - file_in_pkcs1 = filename; - } - else if (strcaseeq("pkcs10", optarg)) - { - filetype_in |= PKCS10; - if (filename) - file_in_pkcs10 = filename; - } - else if (strcaseeq("cacert-enc", optarg)) - { - filetype_in |= CACERT_ENC; - if (filename) - file_in_cacert_enc = filename; - } - else if (strcaseeq("cacert-sig", optarg)) - { - filetype_in |= CACERT_SIG; - if (filename) - file_in_cacert_sig = filename; - } - else if (strcaseeq("cert-self", optarg)) - { - filetype_in |= CERT_SELF; - if (filename) - file_in_cert_self = filename; - } - else - { - usage("invalid --in file type"); - } - continue; - } - - case 'o': /* --out [= ] */ - { - char *filename = strstr(optarg, "="); - - if (filename) - { - /* replace '=' by '\0' */ - *filename = '\0'; - /* set pointer to start of filename */ - filename++; - } - if (strcaseeq("pkcs1", optarg)) - { - filetype_out |= PKCS1; - if (filename) - file_out_pkcs1 = filename; - } - else if (strcaseeq("pkcs10", optarg)) - { - filetype_out |= PKCS10; - if (filename) - file_out_pkcs10 = filename; - } - else if (strcaseeq("pkcs7", optarg)) - { - filetype_out |= PKCS7; - if (filename) - file_out_pkcs7 = filename; - } - else if (strcaseeq("cert-self", optarg)) - { - filetype_out |= CERT_SELF; - if (filename) - file_out_cert_self = filename; - } - else if (strcaseeq("cert", optarg)) - { - filetype_out |= CERT; - if (filename) - file_out_cert = filename; - } - else if (strcaseeq("cacert", optarg)) - { - request_ca_certificate = TRUE; - if (filename) - file_out_ca_cert = filename; - } - else - { - usage("invalid --out file type"); - } - continue; - } - - case 'f': /* --force */ - force = TRUE; - continue; - - case 'T': /* --httptimeout */ - http_timeout = atoi(optarg); - if (http_timeout <= 0) - { - usage("invalid httptimeout specified"); - } - continue; - - case 'b': /* --bind */ - http_bind = optarg; - continue; - - case '+': /* --optionsfrom */ - if (!options->from(options, optarg, &argc, &argv, optind)) - { - exit_scepclient("optionsfrom failed"); - } - continue; - - case 'k': /* --keylength */ - { - div_t q; - - rsa_keylength = atoi(optarg); - if (rsa_keylength == 0) - usage("invalid keylength"); - - /* check if key length is a multiple of 8 bits */ - q = div(rsa_keylength, 2*BITS_PER_BYTE); - if (q.rem != 0) - { - exit_scepclient("keylength is not a multiple of %d bits!" - , 2*BITS_PER_BYTE); - } - continue; - } - - case 'D': /* --days */ - if (optarg == NULL || !isdigit(optarg[0])) - { - usage("missing number of days"); - } - else - { - char *endptr; - long days = strtol(optarg, &endptr, 0); - - if (*endptr != '\0' || endptr == optarg - || days <= 0) - usage(" must be a positive number"); - validity = 24*3600*days; - } - continue; - - case 'S': /* --startdate */ - if (optarg == NULL || strlen(optarg) != 13 || optarg[12] != 'Z') - { - usage("date format must be YYMMDDHHMMSSZ"); - } - else - { - chunk_t date = { optarg, 13 }; - notBefore = asn1_to_time(&date, ASN1_UTCTIME); - } - continue; - - case 'E': /* --enddate */ - if (optarg == NULL || strlen(optarg) != 13 || optarg[12] != 'Z') - { - usage("date format must be YYMMDDHHMMSSZ"); - } - else - { - chunk_t date = { optarg, 13 }; - notAfter = asn1_to_time(&date, ASN1_UTCTIME); - } - continue; - - case 'd': /* --dn */ - if (distinguishedName) - { - usage("only one distinguished name allowed"); - } - distinguishedName = optarg; - continue; - - case 's': /* --subjectAltName */ - { - char *value = strstr(optarg, "="); - - if (value) - { - /* replace '=' by '\0' */ - *value = '\0'; - /* set pointer to start of value */ - value++; - } - - if (strcaseeq("email", optarg) || - strcaseeq("dns", optarg) || - strcaseeq("ip", optarg)) - { - subjectAltNames->insert_last(subjectAltNames, - identification_create_from_string(value)); - continue; - } - else - { - usage("invalid --subjectAltName type"); - continue; - } - } - - case 'p': /* --password */ - if (challengePassword.len > 0) - { - usage("only one challenge password allowed"); - } - if (strcaseeq("%prompt", optarg)) - { - printf("Challenge password: "); - if (fgets(challenge_password_buffer, - sizeof(challenge_password_buffer) - 1, stdin)) - { - challengePassword.ptr = challenge_password_buffer; - /* discard the terminating '\n' from the input */ - challengePassword.len = strlen(challenge_password_buffer) - 1; - } - else - { - usage("challenge password could not be read"); - } - } - else - { - challengePassword.ptr = optarg; - challengePassword.len = strlen(optarg); - } - continue; - - case 'u': /* -- url */ - if (scep_url) - { - usage("only one URL argument allowed"); - } - scep_url = optarg; - continue; - - case 'c': /* -- caname */ - ca_name = optarg; - continue; - - case 'm': /* --method */ - if (strcaseeq("get", optarg)) - { - http_get_request = TRUE; - } - else if (strcaseeq("post", optarg)) - { - http_get_request = FALSE; - } - else - { - usage("invalid http request method specified"); - } - continue; - - case 't': /* --interval */ - poll_interval = atoi(optarg); - if (poll_interval <= 0) - { - usage("invalid interval specified"); - } - continue; - - case 'x': /* --maxpolltime */ - max_poll_time = atoi(optarg); - continue; - - case 'a': /*--algorithm [=]algo */ - { - const proposal_token_t *token; - char *type = optarg; - char *algo = strstr(optarg, "="); - - if (algo) - { - *algo = '\0'; - algo++; - } - else - { - type = "enc"; - algo = optarg; - } - - if (strcaseeq("enc", type)) - { - token = lib->proposal->get_token(lib->proposal, algo); - if (token == NULL || token->type != ENCRYPTION_ALGORITHM) - { - usage("invalid algorithm specified"); - } - pkcs7_symmetric_cipher = token->algorithm; - pkcs7_key_size = token->keysize; - if (encryption_algorithm_to_oid(token->algorithm, - token->keysize) == OID_UNKNOWN) - { - usage("unsupported encryption algorithm specified"); - } - } - else if (strcaseeq("dgst", type) || - strcaseeq("sig", type)) - { - hash_algorithm_t hash; - - token = lib->proposal->get_token(lib->proposal, algo); - if (token == NULL || token->type != INTEGRITY_ALGORITHM) - { - usage("invalid algorithm specified"); - } - hash = hasher_algorithm_from_integrity(token->algorithm, - NULL); - if (hash == (hash_algorithm_t)OID_UNKNOWN) - { - usage("invalid algorithm specified"); - } - if (strcaseeq("dgst", type)) - { - pkcs7_digest_alg = hash; - } - else - { - pkcs10_signature_alg = hash; - } - } - else - { - usage("invalid --algorithm type"); - } - continue; - } - default: - usage("unknown option"); - } - /* break from loop */ - break; - } - - init_log("scepclient"); - - /* load plugins, further infrastructure may need it */ - if (!lib->plugins->load(lib->plugins, - lib->settings->get_str(lib->settings, "scepclient.load", PLUGINS))) - { - exit_scepclient("plugin loading failed"); - } - lib->plugins->status(lib->plugins, LEVEL_DIAG); - - if ((filetype_out == 0) && (!request_ca_certificate)) - { - usage("--out filetype required"); - } - if (request_ca_certificate && (filetype_out > 0 || filetype_in > 0)) - { - usage("in CA certificate request, no other --in or --out option allowed"); - } - - /* check if url is given, if cert output defined */ - if (((filetype_out & CERT) || request_ca_certificate) && !scep_url) - { - usage("URL of SCEP server required"); - } - - /* check for sanity of --in/--out */ - if (!filetype_in && (filetype_in > filetype_out)) - { - usage("cannot generate --out of given --in!"); - } - - /* get CA cert */ - if (request_ca_certificate) - { - char ca_path[PATH_MAX]; - container_t *container; - pkcs7_t *p7; - - if (!scep_http_request(scep_url, chunk_create(ca_name, strlen(ca_name)), - SCEP_GET_CA_CERT, http_get_request, - http_timeout, http_bind, &scep_response)) - { - exit_scepclient("did not receive a valid scep response"); - } - - join_paths(ca_path, sizeof(ca_path), CA_CERT_PATH, file_out_ca_cert); - - p7 = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, - BUILD_BLOB_ASN1_DER, scep_response, BUILD_END); - - if (!p7) - { /* no PKCS#7 encoded CA+RA certificates, assume simple CA cert */ - - DBG1(DBG_APP, "unable to parse PKCS#7, assuming plain CA cert"); - if (!chunk_write(scep_response, ca_path, 0022, force)) - { - exit_scepclient("could not write ca cert file '%s': %s", - ca_path, strerror(errno)); - } - } - else - { - enumerator_t *enumerator; - certificate_t *cert; - int ra_certs = 0, ca_certs = 0; - int ra_index = 1, ca_index = 1; - - enumerator = p7->create_cert_enumerator(p7); - while (enumerator->enumerate(enumerator, &cert)) - { - x509_t *x509 = (x509_t*)cert; - if (x509->get_flags(x509) & X509_CA) - { - ca_certs++; - } - else - { - ra_certs++; - } - } - enumerator->destroy(enumerator); - - enumerator = p7->create_cert_enumerator(p7); - while (enumerator->enumerate(enumerator, &cert)) - { - x509_t *x509 = (x509_t*)cert; - bool ca_cert = x509->get_flags(x509) & X509_CA; - char cert_path[PATH_MAX], *path = ca_path; - - if (ca_cert && ca_certs > 1) - { - add_path_suffix(cert_path, sizeof(cert_path), ca_path, - "-%.1d", ca_index++); - path = cert_path; - } - else if (!ca_cert) - { /* use CA name as base for RA certs */ - if (ra_certs > 1) - { - add_path_suffix(cert_path, sizeof(cert_path), ca_path, - "-ra-%.1d", ra_index++); - } - else - { - add_path_suffix(cert_path, sizeof(cert_path), ca_path, - "-ra"); - } - path = cert_path; - } - - if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding) || - !chunk_write(encoding, path, 0022, force)) - { - exit_scepclient("could not write cert file '%s': %s", - path, strerror(errno)); - } - chunk_free(&encoding); - } - enumerator->destroy(enumerator); - container = &p7->container; - container->destroy(container); - } - exit_scepclient(NULL); /* no further output required */ - } - - creds = mem_cred_create(); - lib->credmgr->add_set(lib->credmgr, &creds->set); - - /* - * input of PKCS#1 file - */ - if (filetype_in & PKCS1) /* load an RSA key pair from file */ - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), PRIVATE_KEY_PATH, file_in_pkcs1); - - private_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, - BUILD_FROM_FILE, path, BUILD_END); - } - else /* generate an RSA key pair */ - { - private_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, - BUILD_KEY_SIZE, rsa_keylength, - BUILD_END); - } - if (private_key == NULL) - { - exit_scepclient("no RSA private key available"); - } - creds->add_key(creds, private_key->get_ref(private_key)); - public_key = private_key->get_public_key(private_key); - - /* check for minimum key length */ - if (private_key->get_keysize(private_key) < RSA_MIN_OCTETS / BITS_PER_BYTE) - { - exit_scepclient("length of RSA key has to be at least %d bits", - RSA_MIN_OCTETS * BITS_PER_BYTE); - } - - /* - * input of PKCS#10 file - */ - if (filetype_in & PKCS10) - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), REQ_PATH, file_in_pkcs10); - - pkcs10_req = lib->creds->create(lib->creds, CRED_CERTIFICATE, - CERT_PKCS10_REQUEST, BUILD_FROM_FILE, - path, BUILD_END); - if (!pkcs10_req) - { - exit_scepclient("could not read certificate request '%s'", path); - } - subject = pkcs10_req->get_subject(pkcs10_req); - subject = subject->clone(subject); - } - else - { - if (distinguishedName == NULL) - { - int n = sprintf(default_distinguished_name, DEFAULT_DN); - - /* set the common name to the hostname */ - if (gethostname(default_distinguished_name + n, BUF_LEN - n) || - strlen(default_distinguished_name) == n) - { - exit_scepclient("no hostname defined, use " - "--dn option"); - } - distinguishedName = default_distinguished_name; - } - - DBG2(DBG_APP, "dn: '%s'", distinguishedName); - subject = identification_create_from_string(distinguishedName); - if (subject->get_type(subject) != ID_DER_ASN1_DN) - { - exit_scepclient("parsing of distinguished name failed"); - } - - DBG2(DBG_APP, "building pkcs10 object:"); - pkcs10_req = lib->creds->create(lib->creds, CRED_CERTIFICATE, - CERT_PKCS10_REQUEST, - BUILD_SIGNING_KEY, private_key, - BUILD_SUBJECT, subject, - BUILD_SUBJECT_ALTNAMES, subjectAltNames, - BUILD_CHALLENGE_PWD, challengePassword, - BUILD_DIGEST_ALG, pkcs10_signature_alg, - BUILD_END); - if (!pkcs10_req) - { - exit_scepclient("generating pkcs10 request failed"); - } - } - pkcs10_req->get_encoding(pkcs10_req, CERT_ASN1_DER, &pkcs10_encoding); - fingerprint = scep_generate_pkcs10_fingerprint(pkcs10_encoding); - DBG1(DBG_APP, " fingerprint: %s", fingerprint.ptr); - - /* - * output of PKCS#10 file - */ - if (filetype_out & PKCS10) - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), REQ_PATH, file_out_pkcs10); - - if (!chunk_write(pkcs10_encoding, path, 0022, force)) - { - exit_scepclient("could not write pkcs10 file '%s': %s", - path, strerror(errno)); - } - filetype_out &= ~PKCS10; /* delete PKCS10 flag */ - } - - if (!filetype_out) - { - exit_scepclient(NULL); /* no further output required */ - } - - /* - * output of PKCS#1 file - */ - if (filetype_out & PKCS1) - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), PRIVATE_KEY_PATH, file_out_pkcs1); - - DBG2(DBG_APP, "building pkcs1 object:"); - if (!private_key->get_encoding(private_key, PRIVKEY_ASN1_DER, &pkcs1) || - !chunk_write(pkcs1, path, 0066, force)) - { - exit_scepclient("could not write pkcs1 file '%s': %s", - path, strerror(errno)); - } - filetype_out &= ~PKCS1; /* delete PKCS1 flag */ - } - - if (!filetype_out) - { - exit_scepclient(NULL); /* no further output required */ - } - - scep_generate_transaction_id(public_key, &transID, &serialNumber); - DBG1(DBG_APP, " transaction ID: %.*s", (int)transID.len, transID.ptr); - - /* - * read or generate self-signed X.509 certificate - */ - if (filetype_in & CERT_SELF) - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), HOST_CERT_PATH, file_in_cert_self); - - x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_FROM_FILE, path, BUILD_END); - if (!x509_signer) - { - exit_scepclient("could not read certificate file '%s'", path); - } - } - else - { - notBefore = notBefore ? notBefore : time(NULL); - notAfter = notAfter ? notAfter : (notBefore + validity); - x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_SIGNING_KEY, private_key, - BUILD_PUBLIC_KEY, public_key, - BUILD_SUBJECT, subject, - BUILD_NOT_BEFORE_TIME, notBefore, - BUILD_NOT_AFTER_TIME, notAfter, - BUILD_SERIAL, serialNumber, - BUILD_SUBJECT_ALTNAMES, subjectAltNames, - BUILD_END); - if (!x509_signer) - { - exit_scepclient("generating certificate failed"); - } - } - creds->add_cert(creds, TRUE, x509_signer->get_ref(x509_signer)); - - /* - * output of self-signed X.509 certificate file - */ - if (filetype_out & CERT_SELF) - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), HOST_CERT_PATH, file_out_cert_self); - - if (!x509_signer->get_encoding(x509_signer, CERT_ASN1_DER, &encoding)) - { - exit_scepclient("encoding certificate failed"); - } - if (!chunk_write(encoding, path, 0022, force)) - { - exit_scepclient("could not write self-signed cert file '%s': %s", - path, strerror(errno)); - } - chunk_free(&encoding); - filetype_out &= ~CERT_SELF; /* delete CERT_SELF flag */ - } - - if (!filetype_out) - { - exit_scepclient(NULL); /* no further output required */ - } - - /* - * load ca encryption certificate - */ - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), CA_CERT_PATH, file_in_cacert_enc); - - x509_ca_enc = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_FROM_FILE, path, BUILD_END); - if (!x509_ca_enc) - { - exit_scepclient("could not load encryption cacert file '%s'", path); - } - } - - /* - * input of PKCS#7 file - */ - if (filetype_in & PKCS7) - { - /* user wants to load a pkcs7 encrypted request - * operation is not yet supported! - * would require additional parsing of transaction-id - - pkcs7 = pkcs7_read_from_file(file_in_pkcs7); - - */ - } - else - { - DBG2(DBG_APP, "building pkcs7 request"); - pkcs7 = scep_build_request(pkcs10_encoding, - transID, SCEP_PKCSReq_MSG, x509_ca_enc, - pkcs7_symmetric_cipher, pkcs7_key_size, - x509_signer, pkcs7_digest_alg, private_key); - if (!pkcs7.ptr) - { - exit_scepclient("failed to build pkcs7 request"); - } - } - - /* - * output pkcs7 encrypted and signed certificate request - */ - if (filetype_out & PKCS7) - { - char path[PATH_MAX]; - - join_paths(path, sizeof(path), REQ_PATH, file_out_pkcs7); - - if (!chunk_write(pkcs7, path, 0022, force)) - { - exit_scepclient("could not write pkcs7 file '%s': %s", - path, strerror(errno)); - } - filetype_out &= ~PKCS7; /* delete PKCS7 flag */ - } - - if (!filetype_out) - { - exit_scepclient(NULL); /* no further output required */ - } - - /* - * output certificate fetch from SCEP server - */ - if (filetype_out & CERT) - { - bool stored = FALSE; - certificate_t *cert; - enumerator_t *enumerator; - char path[PATH_MAX]; - time_t poll_start = 0; - pkcs7_t *p7; - container_t *container = NULL; - chunk_t chunk; - scep_attributes_t attrs = empty_scep_attributes; - - join_paths(path, sizeof(path), CA_CERT_PATH, file_in_cacert_sig); - - x509_ca_sig = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_FROM_FILE, path, BUILD_END); - if (!x509_ca_sig) - { - exit_scepclient("could not load signature cacert file '%s'", path); - } - - creds->add_cert(creds, TRUE, x509_ca_sig->get_ref(x509_ca_sig)); - - if (!scep_http_request(scep_url, pkcs7, SCEP_PKI_OPERATION, - http_get_request, http_timeout, http_bind, &scep_response)) - { - exit_scepclient("did not receive a valid scep response"); - } - ugh = scep_parse_response(scep_response, transID, &container, &attrs); - if (ugh != NULL) - { - exit_scepclient(ugh); - } - - /* in case of manual mode, we are going into a polling loop */ - if (attrs.pkiStatus == SCEP_PENDING) - { - identification_t *issuer = x509_ca_sig->get_subject(x509_ca_sig); - - DBG1(DBG_APP, " scep request pending, polling every %d seconds", - poll_interval); - poll_start = time_monotonic(NULL); - issuerAndSubject = asn1_wrap(ASN1_SEQUENCE, "cc", - issuer->get_encoding(issuer), - subject->get_encoding(subject)); - } - while (attrs.pkiStatus == SCEP_PENDING) - { - if (max_poll_time > 0 && - (time_monotonic(NULL) - poll_start >= max_poll_time)) - { - exit_scepclient("maximum poll time reached: %d seconds" - , max_poll_time); - } - DBG2(DBG_APP, "going to sleep for %d seconds", poll_interval); - sleep(poll_interval); - free(scep_response.ptr); - container->destroy(container); - - DBG2(DBG_APP, "fingerprint: %.*s", - (int)fingerprint.len, fingerprint.ptr); - DBG2(DBG_APP, "transaction ID: %.*s", - (int)transID.len, transID.ptr); - - chunk_free(&getCertInitial); - getCertInitial = scep_build_request(issuerAndSubject, - transID, SCEP_GetCertInitial_MSG, x509_ca_enc, - pkcs7_symmetric_cipher, pkcs7_key_size, - x509_signer, pkcs7_digest_alg, private_key); - if (!getCertInitial.ptr) - { - exit_scepclient("failed to build scep request"); - } - if (!scep_http_request(scep_url, getCertInitial, SCEP_PKI_OPERATION, - http_get_request, http_timeout, http_bind, &scep_response)) - { - exit_scepclient("did not receive a valid scep response"); - } - ugh = scep_parse_response(scep_response, transID, &container, &attrs); - if (ugh != NULL) - { - exit_scepclient(ugh); - } - } - - if (attrs.pkiStatus != SCEP_SUCCESS) - { - container->destroy(container); - exit_scepclient("reply status is not 'SUCCESS'"); - } - - if (!container->get_data(container, &chunk)) - { - container->destroy(container); - exit_scepclient("extracting signed-data failed"); - } - container->destroy(container); - - /* decrypt enveloped-data container */ - container = lib->creds->create(lib->creds, - CRED_CONTAINER, CONTAINER_PKCS7, - BUILD_BLOB_ASN1_DER, chunk, - BUILD_END); - free(chunk.ptr); - if (!container) - { - exit_scepclient("could not decrypt envelopedData"); - } - - if (!container->get_data(container, &chunk)) - { - container->destroy(container); - exit_scepclient("extracting encrypted-data failed"); - } - container->destroy(container); - - /* parse signed-data container */ - container = lib->creds->create(lib->creds, - CRED_CONTAINER, CONTAINER_PKCS7, - BUILD_BLOB_ASN1_DER, chunk, - BUILD_END); - free(chunk.ptr); - if (!container) - { - exit_scepclient("could not parse singed-data"); - } - /* no need to verify the signed-data container, the signature does NOT - * cover the contained certificates */ - - /* store the end entity certificate */ - join_paths(path, sizeof(path), HOST_CERT_PATH, file_out_cert); - - p7 = (pkcs7_t*)container; - enumerator = p7->create_cert_enumerator(p7); - while (enumerator->enumerate(enumerator, &cert)) - { - x509_t *x509 = (x509_t*)cert; - - if (!(x509->get_flags(x509) & X509_CA)) - { - if (stored) - { - exit_scepclient("multiple certs received, only first stored"); - } - if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding) || - !chunk_write(encoding, path, 0022, force)) - { - exit_scepclient("could not write cert file '%s': %s", - path, strerror(errno)); - } - chunk_free(&encoding); - stored = TRUE; - } - } - enumerator->destroy(enumerator); - container->destroy(container); - chunk_free(&attrs.transID); - chunk_free(&attrs.senderNonce); - chunk_free(&attrs.recipientNonce); - - filetype_out &= ~CERT; /* delete CERT flag */ - } - - exit_scepclient(NULL); - return -1; /* should never be reached */ -} From 1ef8b922119abc4e22457f731afb0656a8c4ce69 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Thu, 11 Aug 2022 00:21:28 +0200 Subject: [PATCH 07/24] pkcs10: Support of Microsoft CertTypeExtension The msCertificateTypeExtension OID (1.3.6.1.4.1.311.20.2) can be used in a PKCS#10 certificate request to define a certificate profile. It consists of an UTF8 string. pki: profile option --- src/libstrongswan/asn1/oid.txt | 2 +- src/libstrongswan/credentials/builder.c | 3 +- src/libstrongswan/credentials/builder.h | 4 +- .../credentials/certificates/pkcs10.h | 13 ++- src/libstrongswan/plugins/x509/x509_pkcs10.c | 102 ++++++++++++++---- src/pki/commands/issue.c | 7 +- src/pki/commands/req.c | 12 ++- src/pki/commands/scep.c | 11 +- src/pki/man/pki---req.1.in | 19 +++- src/pki/man/pki---scep.1.in | 9 ++ 10 files changed, 143 insertions(+), 39 deletions(-) diff --git a/src/libstrongswan/asn1/oid.txt b/src/libstrongswan/asn1/oid.txt index b09f9eafa..c91c1262a 100644 --- a/src/libstrongswan/asn1/oid.txt +++ b/src/libstrongswan/asn1/oid.txt @@ -212,7 +212,7 @@ 0x03 "msSGC" 0x04 "msEncryptingFileSystem" 0x14 "msEnrollmentInfrastructure" - 0x02 "msCertificateTypeExtension" + 0x02 "msCertTypeExtension" OID_MS_CERT_TYPE_EXT 0x02 "msSmartcardLogon" OID_MS_SMARTCARD_LOGON 0x03 "msUPN" OID_USER_PRINCIPAL_NAME 0x15 "msCertSrvInfrastructure" diff --git a/src/libstrongswan/credentials/builder.c b/src/libstrongswan/credentials/builder.c index 196118f82..bb50e097f 100644 --- a/src/libstrongswan/credentials/builder.c +++ b/src/libstrongswan/credentials/builder.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2008 Martin Willi - * Copyright (C) 2016-2019 Andreas Steffen + * Copyright (C) 2016-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -59,6 +59,7 @@ ENUM(builder_part_names, BUILD_FROM_FILE, BUILD_END, "BUILD_REVOKED_ENUMERATOR", "BUILD_BASE_CRL", "BUILD_CHALLENGE_PWD", + "BUILD_CERT_TYPE_EXT", "BUILD_PKCS7_ATTRIBUTE", "BUILD_PKCS11_MODULE", "BUILD_PKCS11_SLOT", diff --git a/src/libstrongswan/credentials/builder.h b/src/libstrongswan/credentials/builder.h index f09c01146..6d143dd4f 100644 --- a/src/libstrongswan/credentials/builder.h +++ b/src/libstrongswan/credentials/builder.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2008 Martin Willi - * Copyright (C) 2016-2019 Andreas Steffen + * Copyright (C) 2016-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -127,6 +127,8 @@ enum builder_part_t { BUILD_BASE_CRL, /** PKCS#10 challenge password */ BUILD_CHALLENGE_PWD, + /** PKCS#10 certificate type extension */ + BUILD_CERT_TYPE_EXT, /** PKCS#7 attribute, int oid, chunk_t with ASN1 type encoded value */ BUILD_PKCS7_ATTRIBUTE, /** friendly name of a PKCS#11 module, null terminated char* */ diff --git a/src/libstrongswan/credentials/certificates/pkcs10.h b/src/libstrongswan/credentials/certificates/pkcs10.h index a6727bc3a..ab5e3cdaa 100644 --- a/src/libstrongswan/credentials/certificates/pkcs10.h +++ b/src/libstrongswan/credentials/certificates/pkcs10.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 Andreas Steffen + * Copyright (C) 2009-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -22,6 +22,8 @@ #ifndef PKCS10_H_ #define PKCS10_H_ +#include "x509.h" + #include #include @@ -47,8 +49,15 @@ struct pkcs10_t { */ chunk_t (*get_challengePassword)(pkcs10_t *this); + /** + * Get Extended Key Usage (EKU) flags + * + * @return EKU flags + */ + x509_flag_t (*get_flags)(pkcs10_t *this); + /** - * Get. + * Get subjectAltNames * * @return enumerator over subjectAltNames as identification_t* */ diff --git a/src/libstrongswan/plugins/x509/x509_pkcs10.c b/src/libstrongswan/plugins/x509/x509_pkcs10.c index f1d90abd0..cee518b51 100644 --- a/src/libstrongswan/plugins/x509/x509_pkcs10.c +++ b/src/libstrongswan/plugins/x509/x509_pkcs10.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2005 Jan Hutter, Martin Willi - * Copyright (C) 2009-2017 Andreas Steffen + * Copyright (C) 2009-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -72,6 +72,11 @@ struct private_x509_pkcs10_t { */ chunk_t challengePassword; + /** + * certificate type extension + */ + chunk_t certTypeExt; + /** * Signature scheme */ @@ -230,6 +235,35 @@ METHOD(pkcs10_t, get_challengePassword, chunk_t, return this->challengePassword; } +METHOD(pkcs10_t, get_flags, x509_flag_t, + private_x509_pkcs10_t *this) +{ + x509_flag_t flags = X509_NONE; + char *profile; + + profile = strndup(this->certTypeExt.ptr, this->certTypeExt.len); + + if (strcaseeq(profile, "server")) + { + flags |= X509_SERVER_AUTH; + } + else if (strcaseeq(profile, "client")) + { + flags |= X509_CLIENT_AUTH; + } + else if (strcaseeq(profile, "dual")) + { + flags |= (X509_SERVER_AUTH | X509_CLIENT_AUTH); + } + else if (strcaseeq(profile, "ocsp")) + { + flags |= X509_OCSP_SIGNER; + } + free(profile); + + return flags; +} + METHOD(pkcs10_t, create_subjectAltName_enumerator, enumerator_t*, private_x509_pkcs10_t *this) { @@ -240,12 +274,12 @@ METHOD(pkcs10_t, create_subjectAltName_enumerator, enumerator_t*, * ASN.1 definition of a PKCS#10 extension request */ static const asn1Object_t extensionRequestObjects[] = { - { 0, "extensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ + { 0, "extensions", ASN1_SEQUENCE, ASN1_LOOP }, /* 0 */ { 1, "extension", ASN1_SEQUENCE, ASN1_NONE }, /* 1 */ - { 2, "extnID", ASN1_OID, ASN1_BODY }, /* 2 */ + { 2, "extnID", ASN1_OID, ASN1_BODY }, /* 2 */ { 2, "critical", ASN1_BOOLEAN, ASN1_DEF|ASN1_BODY }, /* 3 */ { 2, "extnValue", ASN1_OCTET_STRING, ASN1_BODY }, /* 4 */ - { 1, "end loop", ASN1_EOC, ASN1_END }, /* 5 */ + { 0, "end loop", ASN1_EOC, ASN1_END }, /* 5 */ { 0, "exit", ASN1_EOC, ASN1_EXIT } }; #define PKCS10_EXTN_ID 2 @@ -291,6 +325,14 @@ static bool parse_extension_request(private_x509_pkcs10_t *this, chunk_t blob, i goto end; } break; + case OID_MS_CERT_TYPE_EXT: + if (!asn1_parse_simple_object(&object, ASN1_UTF8STRING, + level, "certTypeExt")) + { + goto end; + } + this->certTypeExt = object; + break; default: break; } @@ -482,6 +524,7 @@ METHOD(certificate_t, destroy, void, { /* only parsed certificate requests point these fields to "encoded" */ chunk_free(&this->certificationRequestInfo); chunk_free(&this->challengePassword); + chunk_free(&this->certTypeExt); chunk_free(&this->signature); } free(this); @@ -513,6 +556,7 @@ static private_x509_pkcs10_t* create_empty(void) .destroy = _destroy, }, .get_challengePassword = _get_challengePassword, + .get_flags = _get_flags, .create_subjectAltName_enumerator = _create_subjectAltName_enumerator, }, }, @@ -530,7 +574,7 @@ static bool generate(private_x509_pkcs10_t *cert, private_key_t *sign_key, int digest_alg) { chunk_t key_info, subjectAltNames, attributes; - chunk_t extensionRequest = chunk_empty; + chunk_t extensionRequest = chunk_empty, certTypeExt = chunk_empty; chunk_t challengePassword = chunk_empty, sig_scheme = chunk_empty; identification_t *subject; @@ -565,35 +609,44 @@ static bool generate(private_x509_pkcs10_t *cert, private_key_t *sign_key, /* encode subjectAltNames */ subjectAltNames = x509_build_subjectAltNames(cert->subjectAltNames); - if (subjectAltNames.ptr) + /* encode certTypeExt */ + if (cert->certTypeExt.len > 0) + { + certTypeExt = asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_build_known_oid(OID_MS_CERT_TYPE_EXT), + asn1_wrap(ASN1_OCTET_STRING, "m", + asn1_simple_object(ASN1_UTF8STRING, cert->certTypeExt) + )); + } + + /* encode extensionRequest attribute */ + if (subjectAltNames.ptr || certTypeExt.ptr) { extensionRequest = asn1_wrap(ASN1_SEQUENCE, "mm", - asn1_build_known_oid(OID_EXTENSION_REQUEST), - asn1_wrap(ASN1_SET, "m", - asn1_wrap(ASN1_SEQUENCE, "m", subjectAltNames) - )); + asn1_build_known_oid(OID_EXTENSION_REQUEST), + asn1_wrap(ASN1_SET, "m", + asn1_wrap(ASN1_SEQUENCE, "mm", subjectAltNames, certTypeExt) + )); } + + /* encode challengePassword attribute */ if (cert->challengePassword.len > 0) { - asn1_t type = asn1_is_printablestring(cert->challengePassword) ? - ASN1_PRINTABLESTRING : ASN1_T61STRING; - challengePassword = asn1_wrap(ASN1_SEQUENCE, "mm", - asn1_build_known_oid(OID_CHALLENGE_PASSWORD), - asn1_wrap(ASN1_SET, "m", - asn1_simple_object(type, cert->challengePassword) - ) - ); + asn1_build_known_oid(OID_CHALLENGE_PASSWORD), + asn1_wrap(ASN1_SET, "m", + asn1_simple_object(ASN1_UTF8STRING, cert->challengePassword) + )); } + attributes = asn1_wrap(ASN1_CONTEXT_C_0, "mm", extensionRequest, challengePassword); cert->certificationRequestInfo = asn1_wrap(ASN1_SEQUENCE, "ccmm", - ASN1_INTEGER_0, - subject->get_encoding(subject), - key_info, - attributes); - + ASN1_INTEGER_0, + subject->get_encoding(subject), + key_info, + attributes); if (!sign_key->sign(sign_key, cert->scheme->scheme, cert->scheme->params, cert->certificationRequestInfo, &cert->signature)) { @@ -685,6 +738,9 @@ x509_pkcs10_t *x509_pkcs10_gen(certificate_type_t type, va_list args) case BUILD_CHALLENGE_PWD: cert->challengePassword = chunk_clone(va_arg(args, chunk_t)); continue; + case BUILD_CERT_TYPE_EXT: + cert->certTypeExt = chunk_clone(va_arg(args, chunk_t)); + continue; case BUILD_SIGNATURE_SCHEME: cert->scheme = va_arg(args, signature_params_t*); cert->scheme = signature_params_clone(cert->scheme); diff --git a/src/pki/commands/issue.c b/src/pki/commands/issue.c index 1b66548d4..023f0536a 100644 --- a/src/pki/commands/issue.c +++ b/src/pki/commands/issue.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2009 Martin Willi - * Copyright (C) 2015-2019 Andreas Steffen + * Copyright (C) 2015-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -480,9 +480,12 @@ static int issue() id = cert_req->get_subject(cert_req); id = id->clone(id); } + req = (pkcs10_t*)cert_req; + + /* Add Extended Key Usage (EKU) flags */ + flags |= req->get_flags(req); /* Add subjectAltNames from PKCS#10 certificate request */ - req = (pkcs10_t*)cert_req; enumerator = req->create_subjectAltName_enumerator(req); while (enumerator->enumerate(enumerator, &subjectAltName)) { diff --git a/src/pki/commands/req.c b/src/pki/commands/req.c index 44208771c..b2f3545e6 100644 --- a/src/pki/commands/req.c +++ b/src/pki/commands/req.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2009 Martin Willi - * Copyright (C) 2009-2017 Andreas Steffen + * Copyright (C) 2009-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -39,6 +39,7 @@ static int req() linked_list_t *san; chunk_t encoding = chunk_empty; chunk_t challenge_password = chunk_empty; + chunk_t cert_type_ext = chunk_empty; char *arg; bool pss = lib->settings->get_bool(lib->settings, "%s.rsa_pss", FALSE, lib->ns); @@ -101,6 +102,9 @@ static int req() case 'a': san->insert_last(san, identification_create_from_string(arg)); continue; + case 'P': + cert_type_ext = chunk_create(arg, strlen(arg)); + continue; case 'p': challenge_password = chunk_create(arg, strlen(arg)); continue; @@ -180,6 +184,7 @@ static int req() BUILD_SUBJECT, id, BUILD_SUBJECT_ALTNAMES, san, BUILD_CHALLENGE_PWD, challenge_password, + BUILD_CERT_TYPE_EXT, cert_type_ext, BUILD_SIGNATURE_SCHEME, scheme, BUILD_END); if (!cert) @@ -228,9 +233,9 @@ static void __attribute__ ((constructor))reg() req, 'r', "req", "create a PKCS#10 certificate request", {"[--in file|--keyid hex] [--type rsa|ecdsa|bliss|priv] --dn distinguished-name", - "[--san subjectAltName]+ [--password challengePassword]", + "[--san subjectAltName]+ [--profile server|client|dual|ocsp]", + "[--password challengePassword] [--rsa-padding pkcs1|pss]", "[--digest md5|sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]", - "[--rsa-padding pkcs1|pss]", "[--outform der|pem]"}, { {"help", 'h', 0, "show usage information"}, @@ -239,6 +244,7 @@ static void __attribute__ ((constructor))reg() {"type", 't', 1, "type of input key, default: priv"}, {"dn", 'd', 1, "subject distinguished name"}, {"san", 'a', 1, "subjectAltName to include in cert request"}, + {"profile", 'P', 1, "certificate profile name to include in cert request"}, {"password", 'p', 1, "challengePassword to include in cert request"}, {"digest", 'g', 1, "digest for signature creation, default: key-specific"}, {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"}, diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c index 37f5a9482..03703e76a 100644 --- a/src/pki/commands/scep.c +++ b/src/pki/commands/scep.c @@ -46,6 +46,7 @@ static int scep() cred_encoding_type_t form = CERT_ASN1_DER; chunk_t scep_response = chunk_empty; chunk_t challenge_password = chunk_empty; + chunk_t cert_type = chunk_empty; chunk_t serialNumber = chunk_empty; chunk_t transID = chunk_empty; chunk_t pkcs10_encoding = chunk_empty; @@ -114,6 +115,9 @@ static int scep() case 'a': san->insert_last(san, identification_create_from_string(arg)); continue; + case 'P': + cert_type = chunk_create(arg, strlen(arg)); + continue; case 'p': challenge_password = chunk_create(arg, strlen(arg)); continue; @@ -351,6 +355,7 @@ static int scep() BUILD_SUBJECT, subject, BUILD_SUBJECT_ALTNAMES, san, BUILD_CHALLENGE_PWD, challenge_password, + BUILD_CERT_TYPE_EXT, cert_type, BUILD_SIGNATURE_SCHEME, scheme, BUILD_END); if (!pkcs10) @@ -682,8 +687,9 @@ static void __attribute__ ((constructor))reg() scep, 'S', "scep", "Enroll an X.509 certificate with a SCEP server", {"--url url [--in file] --dn distinguished-name [--san subjectAltName]+", - "[--password password] --cacert-enc file --cacert-sig file [--cacert file]+", - "[--oldcert file --oldkey file] [--cipher aes|des3]", + "[--profile profile] [--password password]", + " --cacert-enc file --cacert-sig file [--cacert file]+", + " --oldcert file --oldkey file] [--cipher aes|des3]", "[--digest sha256|sha384|sha512|sha224|sha1] [--rsa-padding pkcs1|pss]", "[--interval time] [--maxpolltime time] [--outform der|pem]"}, { @@ -692,6 +698,7 @@ static void __attribute__ ((constructor))reg() {"in", 'i', 1, "RSA private key input file, default: stdin"}, {"dn", 'd', 1, "subject distinguished name"}, {"san", 'a', 1, "subjectAltName to include in cert request"}, + {"profile", 'P', 1, "certificate profile name to include in cert request"}, {"password", 'p', 1, "challengePassword to include in cert request"}, {"cacert-enc", 'e', 1, "CA certificate for encryption"}, {"cacert-sig", 's', 1, "CA certificate for signature verification"}, diff --git a/src/pki/man/pki---req.1.in b/src/pki/man/pki---req.1.in index 8f7de248c..516088f3d 100644 --- a/src/pki/man/pki---req.1.in +++ b/src/pki/man/pki---req.1.in @@ -1,4 +1,4 @@ -.TH "PKI \-\-REQ" 1 "2013-07-31" "@PACKAGE_VERSION@" "strongSwan" +.TH "PKI \-\-REQ" 1 "2022-08-11" "@PACKAGE_VERSION@" "strongSwan" . .SH "NAME" . @@ -13,6 +13,7 @@ pki \-\-req \- Create a PKCS#10 certificate request .OP \-\-type type .BI \-\-dn\~ distinguished-name .OP \-\-san subjectAltName +.OP \-\-profile profile .OP \-\-password password .OP \-\-digest digest .OP \-\-rsa\-padding padding @@ -29,7 +30,7 @@ pki \-\-req \- Create a PKCS#10 certificate request | .B \-\-help .YS -. +.q .SH "DESCRIPTION" . This sub-command of @@ -65,6 +66,15 @@ Subject distinguished name (DN). Required. .BI "\-a, \-\-san " subjectAltName subjectAltName extension to include in request. Can be used multiple times. .TP +.BI "\-P, \-\-profile " profile +Certificate profile name to be included in the certificate request. Can be any +UTF8 string. Supported e.g. by +.B openxpki +with profiles (\fIpc-client\fR, \fItls-server\fR, etc.) or +.B pki \-\-issue +with (\fIserver\fR, \fIclient\fR, \fIdual\fR, or \fIocsp\fR) that are translated into +corresponding Extended Key Usage (EKU) flags in the generated X.509 certificate. +.TP .BI "\-p, \-\-password " password The challengePassword to include in the certificate request. .TP @@ -83,11 +93,12 @@ Encoding of the created certificate file. Either \fIder\fR (ASN.1 DER) or . .SH "EXAMPLES" . -Generate a certificate request for an RSA key, with a subjectAltName extension: +Generate a certificate request for an RSA key, with a subjectAltName extension +and a TLS-server profile: .PP .EX pki \-\-req \-\-in key.der \-\-dn "C=CH, O=strongSwan, CN=moon" \\ - \-\-san moon@strongswan.org > req.der + \-\-san moon@strongswan.org \-\-profile server > req.der .EE .PP Generate a certificate request for an ECDSA key and a different digest: diff --git a/src/pki/man/pki---scep.1.in b/src/pki/man/pki---scep.1.in index 2422b54ca..8817cffc1 100644 --- a/src/pki/man/pki---scep.1.in +++ b/src/pki/man/pki---scep.1.in @@ -11,6 +11,7 @@ pki \-\-scep \- Enroll an X.509 certificate with a SCEP server .OP \-\-in file .BI \-\-dn\~ distinguished-name .OP \-\-san subjectAltName +.OP \-\-profile profile .OP \-\-password password .BI \-\-ca-cert-enc\~ file .BI \-\-ca-cert-sig\~ file @@ -74,6 +75,14 @@ Subject distinguished name (DN). Required. .BI "\-a, \-\-san " subjectAltName subjectAltName extension to include in request. Can be used multiple times. .TP +.BI "\-P, \-\-profile " profile +Certificate profile name to be included in the certificate request. Can be any +UTF8 string. Supported e.g. by the +.B openxpki +SCEP server with profiles (\fIpc-client\fR, \fItls-server\fR, etc.) that are +translated into corresponding Extended Key Usage (EKU) flags in the generated +X.509 certificate. +.TP .BI "\-p, \-\-password " password The challengePassword to include in the certificate request. .TP From ba76a9f5ffb38cf474cea55acc6d716531429655 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sat, 13 Aug 2022 12:31:44 +0200 Subject: [PATCH 08/24] pki: Get CA certs via EST (RFC 7030) --- src/pki/Makefile.am | 4 +- src/pki/command.h | 2 +- src/pki/commands/estca.c | 100 ++++++++ src/pki/commands/scep.c | 108 ++------- src/pki/commands/scepca.c | 352 +--------------------------- src/pki/est/est.c | 112 +++++++++ src/pki/est/est.h | 40 ++++ src/pki/pki_cert.c | 472 ++++++++++++++++++++++++++++++++++++++ src/pki/pki_cert.h | 43 ++++ src/pki/scep/scep.c | 19 +- src/pki/scep/scep.h | 7 +- 11 files changed, 815 insertions(+), 444 deletions(-) create mode 100644 src/pki/commands/estca.c create mode 100644 src/pki/est/est.c create mode 100644 src/pki/est/est.h create mode 100644 src/pki/pki_cert.c create mode 100644 src/pki/pki_cert.h diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 172cfcdc3..3c40a4f04 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -2,9 +2,10 @@ SUBDIRS = man bin_PROGRAMS = pki -pki_SOURCES = pki.c pki.h command.c command.h \ +pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/acert.c \ commands/dn.c \ + commands/estca.c \ commands/gen.c \ commands/issue.c \ commands/keyid.c \ @@ -18,6 +19,7 @@ pki_SOURCES = pki.c pki.h command.c command.h \ commands/self.c \ commands/signcrl.c \ commands/verify.c \ + est/est.h est/est.c \ scep/scep.h scep/scep.c pki_LDADD = \ diff --git a/src/pki/command.h b/src/pki/command.h index 876a64b99..af6587fe9 100644 --- a/src/pki/command.h +++ b/src/pki/command.h @@ -25,7 +25,7 @@ /** * Maximum number of commands (+1). */ -#define MAX_COMMANDS 16 +#define MAX_COMMANDS 17 /** * Maximum number of options in a command (+3) diff --git a/src/pki/commands/estca.c b/src/pki/commands/estca.c new file mode 100644 index 000000000..02161ddd0 --- /dev/null +++ b/src/pki/commands/estca.c @@ -0,0 +1,100 @@ +/* + * 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 . + * + * 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. + */ + +#include "pki.h" +#include "pki_cert.h" +#include "est/est.h" + +#include +#include +#include + +/** + * Get CA certificate[s] from an EST server (RFC 7030) + */ +static int estca() +{ + cred_encoding_type_t form = CERT_ASN1_DER; + chunk_t est_response = chunk_empty; + char *arg, *url = NULL, *caout = NULL; + bool force = FALSE, success; + u_int http_code = 0; + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + return command_usage(NULL); + case 'u': + url = arg; + continue; + case 'c': + caout = arg; + continue; + case 'f': + if (!get_form(arg, &form, CRED_CERTIFICATE)) + { + return command_usage("invalid certificate output format"); + } + continue; + case 'F': + force = TRUE; + continue; + case EOF: + break; + default: + return command_usage("invalid --estca option"); + } + break; + } + + if (!url) + { + return command_usage("--url is required"); + } + + if (!est_https_request(url, EST_CACERTS, FALSE, chunk_empty, &est_response, + &http_code)) + { + DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", http_code); + return 1; + } + success = pki_cert_extract_cacerts(est_response, caout, NULL, TRUE, form, + force); + chunk_free(&est_response); + + return success ? 0 : 1; +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + estca, 'e', "estca", + "get CA certificate[s] from a EST server", + {"--url url [--caout file] [--outform der|pem] [--force]"}, + { + {"help", 'h', 0, "show usage information"}, + {"url", 'u', 1, "URL of the SCEP server"}, + {"caout", 'c', 1, "CA certificate [template]"}, + {"outform", 'f', 1, "encoding of stored certificates, default: der"}, + {"force", 'F', 0, "force overwrite of existing files"}, + } + }); +} diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c index 03703e76a..97a6fc285 100644 --- a/src/pki/commands/scep.c +++ b/src/pki/commands/scep.c @@ -16,15 +16,12 @@ * for more details. */ -#define _GNU_SOURCE -#include -#include #include -#include #include #include #include "pki.h" +#include "pki_cert.h" #include "scep/scep.h" #include @@ -50,7 +47,6 @@ static int scep() chunk_t serialNumber = chunk_empty; chunk_t transID = chunk_empty; chunk_t pkcs10_encoding = chunk_empty; - chunk_t cert_encoding = chunk_empty; chunk_t pkcs7_req = chunk_empty; chunk_t certPoll = chunk_empty; chunk_t issuerAndSubject = chunk_empty; @@ -65,19 +61,17 @@ static int scep() certificate_t *x509_ca_sig = NULL, *x509_ca_enc = NULL; identification_t *subject = NULL, *issuer = NULL; container_t *container = NULL; - pkcs7_t *pkcs7; mem_cred_t *creds = NULL; scep_msg_t scep_msg_type; scep_attributes_t attrs = empty_scep_attributes; uint32_t caps_flags; u_int poll_interval = DEFAULT_POLL_INTERVAL; - u_int max_poll_time = 0; - u_int poll_start = 0; + u_int max_poll_time = 0, poll_start = 0; + u_int http_code = 0; time_t notBefore, notAfter; linked_list_t *san; - enumerator_t *enumerator; int status = 1; - bool ok, http_post = FALSE, stored = FALSE; + bool ok, http_post = FALSE; bool pss = lib->settings->get_bool(lib->settings, "%s.rsa_pss", FALSE, lib->ns); @@ -258,7 +252,7 @@ static int scep() set_file_mode(stdin, CERT_ASN1_DER); if (!chunk_from_fd(0, &chunk)) { - DBG1(DBG_APP, "reading private key failed: %s\n", strerror(errno)); + DBG1(DBG_APP, "reading private key failed: %s", strerror(errno)); goto end; } private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, @@ -273,10 +267,10 @@ static int scep() public = private->get_public_key(private); /* Request capabilities from SCEP server */ - if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CAPS, FALSE, - &scep_response)) + if (!scep_http_request(url, SCEP_GET_CA_CAPS, FALSE, chunk_empty, + &scep_response, &http_code)) { - DBG1(DBG_APP, "did not receive a valid scep response"); + DBG1(DBG_APP, "did not receive a valid scep response: HTTP %u", http_code); goto end; } caps_flags = scep_parse_caps(scep_response); @@ -467,10 +461,10 @@ static int scep() goto end; } - if (!scep_http_request(url, pkcs7_req, SCEP_PKI_OPERATION, http_post, - &scep_response)) + if (!scep_http_request(url, SCEP_PKI_OPERATION, http_post, pkcs7_req, + &scep_response, &http_code)) { - DBG1(DBG_APP, "did not receive a valid SCEP response"); + DBG1(DBG_APP, "did not receive a valid SCEP response: HTTP %u", http_code); goto end; } @@ -526,10 +520,11 @@ static int scep() DBG1(DBG_APP, "failed to build SCEP certPoll request"); goto end; } - if (!scep_http_request(url, certPoll, SCEP_PKI_OPERATION, http_post, - &scep_response)) + if (!scep_http_request(url, SCEP_PKI_OPERATION, http_post, certPoll, + &scep_response, &http_code)) { - DBG1(DBG_APP, "did not receive a valid SCEP response"); + DBG1(DBG_APP, "did not receive a valid SCEP response: HTTP %u", + http_code); goto end; } if (!scep_parse_response(scep_response, transID, &container, &attrs)) @@ -570,79 +565,11 @@ static int scep() goto end; } container->destroy(container); + container = NULL; - /* parse signed-data container */ - container = lib->creds->create(lib->creds, - CRED_CONTAINER, CONTAINER_PKCS7, - BUILD_BLOB_ASN1_DER, data, - BUILD_END); + status = pki_cert_extract_cert(data, form, creds) ? 0 : 1; chunk_free(&data); - if (!container) - { - DBG1(DBG_APP, "could not parse signed-data"); - goto end; - } - /* no need to verify the signed-data container, the signature does NOT - * cover the contained certificates */ - - /* 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; - enumerator_t *certs; - time_t from, until; - bool trusted, valid; - - if (!(x509->get_flags(x509) & X509_CA)) - { - DBG1(DBG_APP, "certificate \"%Y\"", cert->get_subject(cert)); - - if (stored) - { - DBG1(DBG_APP, "multiple certs received, only first stored"); - continue; - } - - /* establish trust relativ to root CA */ - creds->add_cert(creds, FALSE, cert->get_ref(cert)); - certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, - KEY_RSA, cert->get_subject(cert), FALSE); - trusted = certs->enumerate(certs, &cert, NULL); - valid = cert->get_validity(cert, NULL, &from, &until); - - DBG1(DBG_APP, "certificate is %strusted, valid from %T until %T " - "(currently %svalid)", - trusted ? "" : "not ", &from, FALSE, &until, FALSE, - valid ? "" : "not "); - - certs->destroy(certs); - - if (!cert->get_encoding(cert, form, &cert_encoding)) - { - DBG1(DBG_APP, "encoding certificate failed"); - break; - } - - set_file_mode(stdout, form); - if (fwrite(cert_encoding.ptr, cert_encoding.len, 1, stdout) != 1) - { - DBG1(DBG_APP, "writing certificate failed"); - break; - } - else - { - stored = TRUE; - status = 0; - } - } - } - enumerator->destroy(enumerator); - - end: lib->credmgr->remove_set(lib->credmgr, &creds->set); creds->destroy(creds); @@ -661,7 +588,6 @@ end: chunk_free(&serialNumber); chunk_free(&transID); chunk_free(&pkcs10_encoding); - chunk_free(&cert_encoding); chunk_free(&pkcs7_req); chunk_free(&certPoll); chunk_free(&issuerAndSubject); diff --git a/src/pki/commands/scepca.c b/src/pki/commands/scepca.c index 24271f78b..32df55de7 100644 --- a/src/pki/commands/scepca.c +++ b/src/pki/commands/scepca.c @@ -16,220 +16,11 @@ * for more details. */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include - #include "pki.h" +#include "pki_cert.h" #include "scep/scep.h" #include -#include -#include - - -typedef enum { - CERT_TYPE_ROOT_CA, - CERT_TYPE_SUB_CA, - CERT_TYPE_RA -} cert_type_t; - -static char *cert_type_label[] = { "Root CA", "Sub CA", "RA" }; - -/** - * Determine certificate type based on X.509 certificate flags - */ -static cert_type_t get_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, 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, encoding = chunk_empty; - bool success = FALSE; - - DBG1(DBG_APP, "%s cert \"%Y\"", cert_type_label[cert_type], - cert->get_subject(cert)); - - 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; -} - -static bool build_pathname(char **path, 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; - } -} - -/** - * Writo CA/RA certificate to file in DER or PEM format - */ -static bool write_cert(certificate_t *cert, cert_type_t cert_type, bool trusted, - char *path, cred_encoding_type_t form, bool force) -{ - chunk_t encoding = chunk_empty; - time_t until; - bool written, valid; - - 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; - } - } - 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"); - - return TRUE; -} /** * Get CA certificate[s] from a SCEP server (RFC 8894) @@ -238,15 +29,9 @@ static int scepca() { cred_encoding_type_t form = CERT_ASN1_DER; chunk_t scep_response = chunk_empty; - mem_cred_t *creds = NULL; - certificate_t *cert; - cert_type_t cert_type; - pkcs7_t *pkcs7 = NULL; - bool force = FALSE, written = FALSE; - char *arg, *url = NULL, *caout = NULL, *raout = NULL, *path = NULL; - int status = 1; - - int cert_type_count[] = { 0, 0, 0 }; + char *arg, *url = NULL, *caout = NULL, *raout = NULL; + bool force = FALSE, success; + u_int http_code = 0; while (TRUE) { @@ -285,133 +70,18 @@ static int scepca() return command_usage("--url is required"); } - if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CERT, FALSE, - &scep_response)) + if (!scep_http_request(url, SCEP_GET_CA_CERT, FALSE, chunk_empty, + &scep_response, &http_code)) { - DBG1(DBG_APP, "did not receive a valid scep response"); + DBG1(DBG_APP, "did not receive a valid SCEP response: HTTP %u", http_code); return 1; } - creds = mem_cred_create(); - lib->credmgr->add_set(lib->credmgr, &creds->set); + success = pki_cert_extract_cacerts(scep_response, caout, raout, TRUE, form, + force); + chunk_free(&scep_response); - pkcs7 = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, - BUILD_BLOB_ASN1_DER, scep_response, BUILD_END); - if (!pkcs7) - { /* no PKCS#7 encoded CA+RA certificates, assume single root CA cert */ - - cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_BLOB, scep_response, BUILD_END); - if (!cert) - { - DBG1(DBG_APP, "could not parse single CA certificate"); - goto end; - } - cert_type = get_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 - { - enumerator_t *enumerator; - - enumerator = pkcs7->create_cert_enumerator(pkcs7); - while (enumerator->enumerate(enumerator, &cert)) - { - cert_type = get_cert_type(cert); - if (cert_type == CERT_TYPE_ROOT_CA) - { - /* trust in root CA has to be established manuallly */ - creds->add_cert(creds, TRUE, cert->get_ref(cert)); - - cert_type_count[cert_type]++; - - if (!print_cert_info(cert, cert_type)) - { - goto end; - } - if (build_pathname(&path, cert_type, cert_type_count, - caout, raout, form)) - { - written = write_cert(cert, cert_type, FALSE, 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; - - cert_type = get_cert_type(cert); - if (cert_type != CERT_TYPE_ROOT_CA) - { - enumerator_t *certs; - bool trusted; - - if (!print_cert_info(cert, cert_type)) - { - break; - } - - /* establish trust relativ to root CA */ - certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, - KEY_RSA, cert->get_subject(cert), FALSE); - trusted = certs->enumerate(certs, &cert, NULL); - 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); - } - status = written ? 0 : 1; - -end: - /* cleanup */ - lib->credmgr->remove_set(lib->credmgr, &creds->set); - creds->destroy(creds); - free(scep_response.ptr); - if (pkcs7) - { - container_t *container = &pkcs7->container; - - container->destroy(container); - } - - return status; + return success ? 0 : 1; } /** diff --git a/src/pki/est/est.c b/src/pki/est/est.c new file mode 100644 index 000000000..c24bf5c3e --- /dev/null +++ b/src/pki/est/est.c @@ -0,0 +1,112 @@ +/* + * 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 . + * + * 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 + +#include "est.h" + +#define HTTP_CODE_OK 200 + +static const char *operations[] = { + "cacerts", + "simpleenroll", + "simplereenroll", + "fullcmc", + "serverkeygen", + "csrattrs" +}; + +static const char *request_types[] = { + "", + "application/pkcs10", + "application/pkcs10", + "application/pkcs7-mime", + "application/pkcs10", + "" +}; + +/** + * Send an EST request via HTTPS and wait for a response + */ +bool est_https_request(const char *url, est_op_t op, bool http_post, + chunk_t data, chunk_t *response, u_int *http_code) +{ + host_t *srcip = NULL; + char *complete_url = NULL; + status_t status; + + uint32_t http_timeout = lib->settings->get_time(lib->settings, + "%s.est.http_timeout", 30, lib->ns); + + char *http_bind = lib->settings->get_str(lib->settings, + "%s.est.http_bind", NULL, lib->ns); + + /* initialize response */ + *response = chunk_empty; + *http_code = 0; + + /* construct complete EST URL */ + if (asprintf(&complete_url, "%s/.well-known/est/%s", url, operations[op]) == -1) + { + DBG1(DBG_APP, "could not allocate complete_url string"); + return FALSE; + } + DBG2(DBG_APP, "sending EST request to '%s'", url); + + if (http_bind) + { + srcip = host_create_from_string(http_bind, 0); + } + + if (http_post) + { + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + FETCH_TIMEOUT, http_timeout, + FETCH_REQUEST_DATA, data, + FETCH_REQUEST_TYPE, request_types[op], + FETCH_REQUEST_HEADER, "Expect:", + FETCH_SOURCEIP, srcip, + FETCH_RESPONSE_CODE, http_code, + FETCH_END); + } + else /* HTTP_GET */ + { + status = lib->fetcher->fetch(lib->fetcher, complete_url, response, + FETCH_TIMEOUT, http_timeout, + FETCH_SOURCEIP, srcip, + FETCH_RESPONSE_CODE, http_code, + FETCH_END); + } + DESTROY_IF(srcip); + free(complete_url); + + if (status != SUCCESS) + { + return FALSE; + } + + if (*http_code == HTTP_CODE_OK) + { + chunk_t base64_response = *response; + + *response = chunk_from_base64(base64_response, NULL); + chunk_free(&base64_response); + } + + return TRUE; +} + diff --git a/src/pki/est/est.h b/src/pki/est/est.h new file mode 100644 index 000000000..3d9bdd3cf --- /dev/null +++ b/src/pki/est/est.h @@ -0,0 +1,40 @@ +/* + * 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 . + * + * 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. + */ + +#ifndef _EST_H +#define _EST_H + +#include + +/** + * EST (RFC 7030) Operations + */ +typedef enum { + EST_CACERTS, + EST_SIMPLE_ENROLL, + EST_SIMPLE_REENROLL, + EST_FULL_CMC, + EST_SERVER_KEYGEN, + EST_CSR_ATTRS +} est_op_t; + +/** + * Send an EST request via HTTPS and wait for a response + */ +bool est_https_request(const char *url, est_op_t op, bool http_post, + chunk_t data, chunk_t *response, u_int *http_code); + +#endif /* _EST_H */ diff --git a/src/pki/pki_cert.c b/src/pki/pki_cert.c new file mode 100644 index 000000000..d3c49f2c8 --- /dev/null +++ b/src/pki/pki_cert.c @@ -0,0 +1,472 @@ +/* + * 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 . + * + * 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 +#include + +#include "pki.h" +#include "pki_cert.h" + +#include +#include +#include + +/* + * Certificate types + */ +typedef enum { + CERT_TYPE_ROOT_CA, + CERT_TYPE_SUB_CA, + CERT_TYPE_RA +} pki_cert_type_t; + +static char *cert_type_label[] = { "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, encoding = chunk_empty; + bool success = FALSE; + + DBG1(DBG_APP, "%s cert \"%Y\"", cert_type_label[cert_type], + cert->get_subject(cert)); + + 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; + time_t until; + bool written, valid; + + 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", encoding.len, encoding.ptr); + chunk_free(&encoding); + path = "stdout"; + } + + 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"); + + 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; + + enumerator = pkcs7->create_cert_enumerator(pkcs7); + while (enumerator->enumerate(enumerator, &cert)) + { + cert_type = get_pki_cert_type(cert); + if (cert_type == CERT_TYPE_ROOT_CA) + { + /* trust in root CA has to be established manuallly */ + creds->add_cert(creds, TRUE, cert->get_ref(cert)); + + cert_type_count[cert_type]++; + + if (!print_cert_info(cert, cert_type)) + { + goto end; + } + if (build_pathname(&path, cert_type, cert_type_count, caout, + raout, form)) + { + written = write_cert(cert, cert_type, FALSE, 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; + + cert_type = get_pki_cert_type(cert); + if (cert_type != CERT_TYPE_ROOT_CA) + { + enumerator_t *certs; + bool trusted; + + if (!print_cert_info(cert, cert_type)) + { + break; + } + + /* establish trust relativ to root CA */ + certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, + KEY_ANY, cert->get_subject(cert), FALSE); + trusted = certs->enumerate(certs, &cert, NULL); + 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, + mem_cred_t *creds) +{ + pkcs7_t *pkcs7; + container_t *container; + certificate_t *cert; + 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; + } + + /* 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; + enumerator_t *certs; + time_t from, until; + bool trusted, valid; + + if (!(x509->get_flags(x509) & X509_CA)) + { + DBG1(DBG_APP, "certificate \"%Y\"", cert->get_subject(cert)); + + if (stored) + { + DBG1(DBG_APP, "multiple certs received, only first stored"); + continue; + } + + /* establish trust relativ to root CA */ + creds->add_cert(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, NULL); + valid = cert->get_validity(cert, NULL, &from, &until); + + DBG1(DBG_APP, "certificate is %strusted, valid from %T until %T " + "(currently %svalid)", + trusted ? "" : "not ", &from, FALSE, &until, FALSE, + valid ? "" : "not "); + + certs->destroy(certs); + + 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); + + return stored; +} diff --git a/src/pki/pki_cert.h b/src/pki/pki_cert.h new file mode 100644 index 000000000..853436c9e --- /dev/null +++ b/src/pki/pki_cert.h @@ -0,0 +1,43 @@ +/* + * 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 . + * + * 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. + */ + +/** + * @defgroup pki_cert pki_cert + * @{ @ingroup pki + */ + +#ifndef _PKI_CERT +#define _PKI_CERT + +#include +#include + +/** + * 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); + +/** + * 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, + mem_cred_t *creds); + +#endif /** PKI_CERT_H_ @}*/ diff --git a/src/pki/scep/scep.c b/src/pki/scep/scep.c index eaa5b5323..fbc6e1cfa 100644 --- a/src/pki/scep/scep.c +++ b/src/pki/scep/scep.c @@ -333,8 +333,8 @@ static char* escape_http_request(chunk_t req) /** * Send a SCEP request via HTTP and wait for a response */ -bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, - bool http_post, chunk_t *response) +bool scep_http_request(const char *url, scep_op_t op, bool http_post, + chunk_t data, chunk_t *response, u_int *http_code) { int len; status_t status; @@ -356,6 +356,7 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, /* initialize response */ *response = chunk_empty; + *http_code = 0; operation = operations[op]; switch (op) @@ -371,23 +372,23 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, status = lib->fetcher->fetch(lib->fetcher, complete_url, response, FETCH_TIMEOUT, http_timeout, - FETCH_REQUEST_DATA, msg, + FETCH_REQUEST_DATA, data, FETCH_REQUEST_TYPE, "", FETCH_REQUEST_HEADER, "Expect:", FETCH_SOURCEIP, srcip, + FETCH_RESPONSE_CODE, http_code, FETCH_END); } else /* HTTP_GET */ { - char *escaped_req = escape_http_request(msg); + char *msg = escape_http_request(data); /* form complete url */ - len = strlen(url) + 20 + strlen(operation) + - strlen(escaped_req) + 1; + len = strlen(url) + 20 + strlen(operation) + strlen(msg) + 1; complete_url = malloc(len); snprintf(complete_url, len, "%s?operation=%s&message=%s" - , url, operation, escaped_req); - free(escaped_req); + , url, operation, msg); + free(msg); status = lib->fetcher->fetch(lib->fetcher, complete_url, response, FETCH_TIMEOUT, http_timeout, @@ -395,6 +396,7 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, FETCH_REQUEST_HEADER, "Host:", FETCH_REQUEST_HEADER, "Accept:", FETCH_SOURCEIP, srcip, + FETCH_RESPONSE_CODE, http_code, FETCH_END); } break; @@ -409,6 +411,7 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, status = lib->fetcher->fetch(lib->fetcher, complete_url, response, FETCH_TIMEOUT, http_timeout, FETCH_SOURCEIP, srcip, + FETCH_RESPONSE_CODE, http_code, FETCH_END); } } diff --git a/src/pki/scep/scep.h b/src/pki/scep/scep.h index ead203505..b24cb622a 100644 --- a/src/pki/scep/scep.h +++ b/src/pki/scep/scep.h @@ -101,8 +101,11 @@ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, size_t key_size, certificate_t *signer_cert, hash_algorithm_t digest_alg, private_key_t *private_key); -bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, bool use_post, - chunk_t *response); +/** + * Send a SCEP request via HTTP and wait for a response + */ +bool scep_http_request(const char *url, scep_op_t op, bool http_post, + chunk_t data, chunk_t *response, u_int *http_code); bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out, scep_attributes_t *attrs); From b16c0e928e02abadee8bbc6e298b60921bdbee45 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sun, 14 Aug 2022 04:29:44 +0200 Subject: [PATCH 09/24] pki: Clean up SCEP functions --- src/pki/scep/scep.c | 144 +++++++++++++++++++++++--------------------- src/pki/scep/scep.h | 23 ++++--- 2 files changed, 89 insertions(+), 78 deletions(-) diff --git a/src/pki/scep/scep.c b/src/pki/scep/scep.c index fbc6e1cfa..0b3772dca 100644 --- a/src/pki/scep/scep.c +++ b/src/pki/scep/scep.c @@ -89,65 +89,34 @@ const scep_attributes_t empty_scep_attributes = { }; /** - * Extract X.501 attributes + * Parse CA Capabilities of SCEP server */ -void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator, - scep_attributes_t *attrs) +uint32_t scep_parse_caps(chunk_t response) { - chunk_t attr; + uint32_t caps_flags = 0; + chunk_t line; - if (pkcs7->get_attribute(pkcs7, OID_PKI_MESSAGE_TYPE, enumerator, &attr)) + DBG2(DBG_APP, "CA Capabilities:"); + + while (fetchline(&response, &line)) { - scep_msg_t m; + int i; - for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++) + for (i = 0; i < countof(caps_names); i++) { - if (strncmp(msgType_values[m], attr.ptr, attr.len) == 0) + if (strncaseeq(caps_names[i], line.ptr, line.len)) { - attrs->msgType = m; + DBG2(DBG_APP, " %s", caps_names[i]); + caps_flags |= (1 << i); } } - DBG2(DBG_APP, "messageType: %s", msgType_names[attrs->msgType]); - free(attr.ptr); } - if (pkcs7->get_attribute(pkcs7, OID_PKI_STATUS, enumerator, &attr)) - { - pkiStatus_t s; - - for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++) - { - if (strncmp(pkiStatus_values[s], attr.ptr, attr.len) == 0) - { - attrs->pkiStatus = s; - } - } - DBG2(DBG_APP, "pkiStatus: %s", pkiStatus_names[attrs->pkiStatus]); - free(attr.ptr); - } - if (pkcs7->get_attribute(pkcs7, OID_PKI_FAIL_INFO, enumerator, &attr)) - { - if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4') - { - attrs->failInfo = (failInfo_t)(*attr.ptr - '0'); - } - if (attrs->failInfo != SCEP_unknown_REASON) - { - DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]); - } - free(attr.ptr); - } - - pkcs7->get_attribute(pkcs7, OID_PKI_SENDER_NONCE, enumerator, - &attrs->senderNonce); - pkcs7->get_attribute(pkcs7, OID_PKI_RECIPIENT_NONCE, enumerator, - &attrs->recipientNonce); - pkcs7->get_attribute(pkcs7, OID_PKI_TRANS_ID, enumerator, - &attrs->transID); + return caps_flags; } /** * Generate a transaction ID as the SHA-1 hash of the publicKeyInfo - * the transaction ID is also used as a unique serial number + * The transaction ID is also used as a unique serial number */ bool scep_generate_transaction_id(public_key_t *public, chunk_t *transId, chunk_t *serialNumber) @@ -189,7 +158,7 @@ bool scep_generate_transaction_id(public_key_t *public, } /** - * Builds a pkcs7 enveloped and signed scep request + * Builds a PKCS#7 enveloped and signed SCEP request */ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, certificate_t *enc_cert, encryption_algorithm_t enc_alg, @@ -421,6 +390,66 @@ bool scep_http_request(const char *url, scep_op_t op, bool http_post, return (status == SUCCESS); } +/** + * Extract X.501 attributes + */ +void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator, + scep_attributes_t *attrs) +{ + chunk_t attr; + + if (pkcs7->get_attribute(pkcs7, OID_PKI_MESSAGE_TYPE, enumerator, &attr)) + { + scep_msg_t m; + + for (m = SCEP_CertRep_MSG; m < SCEP_Unknown_MSG; m++) + { + if (strncmp(msgType_values[m], attr.ptr, attr.len) == 0) + { + attrs->msgType = m; + } + } + DBG2(DBG_APP, "messageType: %s", msgType_names[attrs->msgType]); + free(attr.ptr); + } + if (pkcs7->get_attribute(pkcs7, OID_PKI_STATUS, enumerator, &attr)) + { + pkiStatus_t s; + + for (s = SCEP_SUCCESS; s < SCEP_UNKNOWN; s++) + { + if (strncmp(pkiStatus_values[s], attr.ptr, attr.len) == 0) + { + attrs->pkiStatus = s; + } + } + DBG2(DBG_APP, "pkiStatus: %s", pkiStatus_names[attrs->pkiStatus]); + free(attr.ptr); + } + if (pkcs7->get_attribute(pkcs7, OID_PKI_FAIL_INFO, enumerator, &attr)) + { + if (attr.len == 1 && *attr.ptr >= '0' && *attr.ptr <= '4') + { + attrs->failInfo = (failInfo_t)(*attr.ptr - '0'); + } + if (attrs->failInfo != SCEP_unknown_REASON) + { + DBG1(DBG_APP, "failInfo: %s", failInfo_reasons[attrs->failInfo]); + } + free(attr.ptr); + } + + pkcs7->get_attribute(pkcs7, OID_PKI_SENDER_NONCE, enumerator, + &attrs->senderNonce); + pkcs7->get_attribute(pkcs7, OID_PKI_RECIPIENT_NONCE, enumerator, + &attrs->recipientNonce); + pkcs7->get_attribute(pkcs7, OID_PKI_TRANS_ID, enumerator, + &attrs->transID); +} + +/** + * Parse PKCS#7 encoded SCEP response + */ bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out, scep_attributes_t *attrs) { @@ -471,26 +500,3 @@ error: container->destroy(container); return FALSE; } - -uint32_t scep_parse_caps(chunk_t response) -{ - uint32_t caps_flags = 0; - chunk_t line; - - DBG2(DBG_APP, "CA Capabilities:"); - - while (fetchline(&response, &line)) - { - int i; - - for (i = 0; i < countof(caps_names); i++) - { - if (strncaseeq(caps_names[i], line.ptr, line.len)) - { - DBG2(DBG_APP, " %s", caps_names[i]); - caps_flags |= (1 << i); - } - } - } - return caps_flags; -} \ No newline at end of file diff --git a/src/pki/scep/scep.h b/src/pki/scep/scep.h index b24cb622a..922747f1f 100644 --- a/src/pki/scep/scep.h +++ b/src/pki/scep/scep.h @@ -85,17 +85,21 @@ typedef enum { extern const scep_attributes_t empty_scep_attributes; -bool parse_attributes(chunk_t blob, scep_attributes_t *attrs); +/** + * Parse SCEP CA Capabilities + */ +uint32_t scep_parse_caps(chunk_t response); +/** + * Generate a transaction ID as the SHA-1 hash of the publicKeyInfo + * The transaction ID is also used as a unique serial number + */ bool scep_generate_transaction_id(public_key_t *key, chunk_t *transId, chunk_t *serialNumber); -chunk_t scep_transId_attribute(chunk_t transaction_id); - -chunk_t scep_messageType_attribute(scep_msg_t m); - -chunk_t scep_senderNonce_attribute(void); - +/** + * Builds a PKCS#7 enveloped and signed SCEP request + */ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, certificate_t *enc_cert, encryption_algorithm_t enc_alg, size_t key_size, certificate_t *signer_cert, @@ -107,9 +111,10 @@ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg, bool scep_http_request(const char *url, scep_op_t op, bool http_post, chunk_t data, chunk_t *response, u_int *http_code); +/** + * Parse PKCS#7 encoded SCEP response + */ bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out, scep_attributes_t *attrs); -uint32_t scep_parse_caps(chunk_t response); - #endif /* _SCEP_H */ From 7e5daec56e389bd948a331933d118ee7a7c079fa Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sun, 14 Aug 2022 04:51:23 +0200 Subject: [PATCH 10/24] pki: Created pki --estca man page --- configure.ac | 1 + src/pki/man/Makefile.am | 1 + src/pki/man/pki---estca.1.in | 139 +++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 src/pki/man/pki---estca.1.in diff --git a/configure.ac b/configure.ac index 40252e79d..e0a78cc7f 100644 --- a/configure.ac +++ b/configure.ac @@ -2162,6 +2162,7 @@ AC_CONFIG_FILES([ src/pki/man/pki.1 src/pki/man/pki---acert.1 src/pki/man/pki---dn.1 + src/pki/man/pki---estca.1 src/pki/man/pki---gen.1 src/pki/man/pki---issue.1 src/pki/man/pki---keyid.1 diff --git a/src/pki/man/Makefile.am b/src/pki/man/Makefile.am index 9df76d9c3..c3f3982d9 100644 --- a/src/pki/man/Makefile.am +++ b/src/pki/man/Makefile.am @@ -2,6 +2,7 @@ man1_MANS = \ pki.1 \ pki---acert.1 \ pki---dn.1 \ + pki---estca.1 \ pki---gen.1 \ pki---issue.1 \ pki---keyid.1 \ diff --git a/src/pki/man/pki---estca.1.in b/src/pki/man/pki---estca.1.in new file mode 100644 index 000000000..85ccd0929 --- /dev/null +++ b/src/pki/man/pki---estca.1.in @@ -0,0 +1,139 @@ +.TH "PKI \-\-ESTCA" 1 "2022-08-22" "@PACKAGE_VERSION@" "strongSwan" +. +.SH "NAME" +. +pki \-\-estca \- Get CA certificate[s] from an EST server +. +.SH "SYNOPSIS" +. +.SY pki\ \-\-estca +.BI\-\-\-url\~ url +.BI\-\-\-cacert\~ file +.OP \-\-caout file +.OP \-\-outform encoding +.OP \-\-force +.OP \-\-debug level +.YS +. +.SY pki\ \-\-estca +.BI \-\-options\~ file +.YS +. +.SY "pki \-\-estca" +.B \-h +| +.B \-\-help +.YS +. +.SH "DESCRIPTION" +. +This sub-command of +.BR pki (1) +gets CA certificates via https from an EST server using the \fI/cacerts\fR +operation of the Enrollment over Secure Transport protocol (RFC 7030). +. +.SH "OPTIONS" +. +.TP +.B "\-h, \-\-help" +Print usage information with a summary of the available options. +.TP +.BI "\-v, \-\-debug " level +Set debug level, default: 1. +.TP +.BI "\-+, \-\-options " file +Read command line options from \fIfile\fR. +.TP +.BI "\-u, \-\-url " url +URL of the SCEP server. +.TP +.BI "\-C, \-\-cacert " file +CA certificate in the trust chain used for EST TLS server signature verification. +Can be used multiple times. +.TP +.BI "\-c, \-\-caout " file +If present, path where the fetched root CA certificate file is stored to. +If several CA certificates are downloaded, then the value of +.B \-\-caout +is used as a template to derive unique filenames (*-1, *-2, etc.) for the +intermediate or sub CA certificates. +If a file suffix is missing, then depending on the value of +.B \-\-outform +either .\fIder\fR (the default) or .\fIpem\fR is automatically appended. +If the +.B \-\-caout +option is missing and +.B \-\-outform +is set to \fIpem\fR then a PEM-encoded CA certificate bundle is written to +\fIstdout\fR. +.TP +.BI "\-f, \-\-outform " encoding +Encoding of the created certificate file. Either \fIder\fR (ASN.1 DER) or +\fIpem\fR (Base64 PEM), defaults to \fIder\fR. +.TP +.B "\-F, \-\-force" +Force overwrite of existing files. +. +.SH "EXAMPLES" +. +To save some typing work the following command line options are stored in a +\fIest.opt\fR file: +.PP +.EX +\-\-url https://pki.strongswan.org:8443 +\-\-cacert tlsca.crt +\-\-cacert tlsca-1.crt +.EE +.PP +.B NOTE: +For a successful HTTPS connection, trust must be established into the EST server +certificate. The TLS trust chain including the root CA certificate and optionally +intermediate CA certificates must be given using [multiple] +.B --cacert +options. +.P +An EST server sends a root CA and an intermediate CA certificate: +.PP +.EX +pki \-\-estca \-\-options est.opt \-\-caout myca.crt + +Root CA cert "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + serial: 65:31:00:ca:79:da:16:6b:aa:ac:89:e2:a8:f9:49:c3:10:ab:64:54 + SHA256: 96:70:50:51:cd:b9:e7:94:6b:04:f6:15:45:80:fc:90:85:01:71:2a:f6:4f:d1:1b:2d:a1:7e:eb:bf:dd:be:86 + SHA1 : 8e:f3:78:b0:34:a6:c1:6a:7b:c6:f5:91:eb:e5:46:9b:0d:0a:a7:ba (jvN4sDSmwWp7xvWR6+VGmw0Kp7o) +Root CA equals trusted TLS Root CA +Root CA cert is untrusted, valid until Aug 12 15:51:34 2032, 'myca.crt' +Sub CA cert "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + serial: 74:f9:7e:72:7d:b8:fd:f2:c6:e5:1b:fa:37:f9:cb:87:bf:9c:ea:e2 + SHA256: a3:5b:4b:12:d5:8f:68:7b:05:11:08:27:f5:42:62:b8:b5:01:1b:19:37:9c:28:78:5d:37:08:69:6a:8c:07:bf + SHA1 : 8c:e6:67:67:c2:23:89:7b:d0:bc:b1:50:d2:1c:bc:8d:8d:69:15:11 (jOZnZ8IjiXvQvLFQ0hy8jY1pFRE) + using certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 0 +Sub CA cert is trusted, valid until Aug 12 15:51:34 2027, 'mycacert-1.crt' +.EE +.PP +.B NOTE: +The trusthworthiness of the root CA certificate is either verified automatically +if the Root CA certificate of the TLS trust chain is the same as that of the +Issuing CA. Otherwise trust has to be established manually by verifying the SHA256 +or SHA1 fingerprint of the DER-encoded certificate that is e.g. listed on the +official PKI website or by some other means. +.P +The stored certificate files in DER format can be overwritten by PEM-encoded +versions with: +.PP +.EX +pki \-\-estca \-\-options est.opt \-\-caout myca.crt \-\-outform pem \-\-force +.EE +.PP +A CA certificate bundle in PEM format is written to \fIstdout\fR: +.PP +.EX +pki \-\-estca \-\-options est.opt \-\-outform pem > cacerts.pem +.EE +.PP +. +.SH "SEE ALSO" +. +.BR pki (1) From ba1d8aba322228c99d1c0a22f9fa973e9f700c98 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Mon, 15 Aug 2022 21:16:11 +0200 Subject: [PATCH 11/24] pki: Enroll an X.509 certificate with an EST server --- src/pki/Makefile.am | 1 + src/pki/command.h | 2 +- src/pki/commands/est.c | 268 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/pki/commands/est.c diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 3c40a4f04..382cd42c8 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -5,6 +5,7 @@ bin_PROGRAMS = pki pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/acert.c \ commands/dn.c \ + commands/est.c \ commands/estca.c \ commands/gen.c \ commands/issue.c \ diff --git a/src/pki/command.h b/src/pki/command.h index af6587fe9..d628a956f 100644 --- a/src/pki/command.h +++ b/src/pki/command.h @@ -25,7 +25,7 @@ /** * Maximum number of commands (+1). */ -#define MAX_COMMANDS 17 +#define MAX_COMMANDS 18 /** * Maximum number of options in a command (+3) diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c new file mode 100644 index 000000000..55ba4dc09 --- /dev/null +++ b/src/pki/commands/est.c @@ -0,0 +1,268 @@ +/* + * 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 . + * + * 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. + */ + +#include +#include + +#include "pki.h" +#include "pki_cert.h" +#include "est/est.h" + +#include +#include + +#define HTTP_CODE_OK 200 +#define HTTP_CODE_ACCEPTED 202 + +/* default polling time interval in EST manual mode */ +#define DEFAULT_POLL_INTERVAL 60 /* seconds */ + +/** + * Enroll an X.509 certificate with an EST server (RFC 7030) + */ +static int est() +{ + char *arg, *url = NULL, *file = NULL, *error = NULL; + char *client_cert_file = NULL, *client_key_file = NULL; + cred_encoding_type_t form = CERT_ASN1_DER; + chunk_t pkcs10_encoding = chunk_empty, est_response = chunk_empty; + certificate_t *pkcs10 = NULL, *client_cert = NULL, *cacert = NULL; + mem_cred_t *creds = NULL; + private_key_t *client_key = NULL; + est_op_t est_op = EST_SIMPLE_ENROLL; + u_int poll_interval = DEFAULT_POLL_INTERVAL; + u_int max_poll_time = 0, poll_start = 0; + u_int http_code = 0; + int status = 1; + + /* initialize CA certificate storage */ + creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &creds->set); + + while (TRUE) + { + switch (command_getopt(&arg)) + { + case 'h': + goto usage; + case 'u': + url = arg; + continue; + case 'i': + file = arg; + continue; + case 'c': + cacert = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); + if (!cacert) + { + DBG1(DBG_APP, "could not load cacert file '%s'", arg); + goto end; + } + creds->add_cert(creds, TRUE, cacert); + continue; + case 'o': + client_cert_file = arg; + continue; + case 'k': + client_key_file = arg; + continue; + case 't': /* --pollinterval */ + poll_interval = atoi(arg); + if (poll_interval <= 0) + { + error = "invalid interval specified"; + goto usage; + } + continue; + case 'm': /* --maxpolltime */ + max_poll_time = atoi(arg); + continue; + case 'f': + if (!get_form(arg, &form, CRED_CERTIFICATE)) + { + error = "invalid certificate output format"; + goto usage; + } + continue; + case EOF: + break; + default: + error = "invalid --est option"; + goto usage; + } + break; + } + + if (!url) + { + error = "--url is required"; + goto usage; + } + + if (client_cert_file && !client_key_file) + { + error = "--key is required if --cert is set"; + goto usage; + } + + /* load PKCS#10 certificate request from file or stdin */ + if (file) + { + pkcs10 = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_PKCS10_REQUEST, + BUILD_FROM_FILE, file, BUILD_END); + } + else + { + chunk_t chunk; + + set_file_mode(stdin, CERT_ASN1_DER); + if (!chunk_from_fd(0, &chunk)) + { + DBG1(DBG_APP, "reading PKCS#10 certificate request failed: %s\n", + strerror(errno)); + goto end; + } + pkcs10 = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_PKCS10_REQUEST, + BUILD_BLOB, chunk, BUILD_END); + free(chunk.ptr); + } + if (!pkcs10) + { + DBG1(DBG_APP, "parsing certificate request failed"); + goto end; + } + + /* generate PKCS#10 encoding */ + if (!pkcs10->get_encoding(pkcs10, CERT_ASN1_DER, &pkcs10_encoding)) + { + DBG1(DBG_APP, "encoding certificate request failed"); + goto end; + } + + if (client_cert_file) + { + /* load old client certificate */ + client_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_FROM_FILE, client_cert_file, BUILD_END); + if (!client_cert) + { + DBG1(DBG_APP, "could not load client cert file '%s'", client_cert_file); + goto end; + } + + /* load old client private key */ + client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, + BUILD_FROM_FILE, client_key_file, BUILD_END); + if (!client_key) + { + DBG1(DBG_APP, "parsing client private key failed"); + goto end; + } + est_op = EST_SIMPLE_REENROLL; + } + + if (!est_https_request(url, est_op, TRUE, pkcs10_encoding, &est_response, + &http_code)) + { + DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", http_code); + goto end; + } + + /* in case of manual mode, we are going into a polling loop */ + if (http_code == HTTP_CODE_ACCEPTED) + { + if (max_poll_time > 0) + { + DBG1(DBG_APP, " EST request pending, polling every %d seconds" + " up to %d seconds", poll_interval, max_poll_time); + } + else + { + DBG1(DBG_APP, " EST request pending, polling indefinitely" + " every %d seconds", poll_interval); + } + poll_start = time_monotonic(NULL); + } + + while (http_code == HTTP_CODE_ACCEPTED) + { + if (max_poll_time > 0 && + (time_monotonic(NULL) - poll_start) >= max_poll_time) + { + DBG1(DBG_APP, "maximum poll time reached: %d seconds", max_poll_time); + goto end; + } + DBG1(DBG_APP, " going to sleep for %d seconds", poll_interval); + sleep(poll_interval); + chunk_free(&est_response); + if (!est_https_request(url, est_op, TRUE, pkcs10_encoding, &est_response, + &http_code)) + { + DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", + http_code); + goto end; + } + } + + if (http_code == HTTP_CODE_OK) + { + status = pki_cert_extract_cert(est_response, form, creds) ? 0 : 1; + } + +end: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + DESTROY_IF(client_cert); + DESTROY_IF(client_key); + DESTROY_IF(pkcs10); + chunk_free(&pkcs10_encoding); + chunk_free(&est_response); + + return status; + +usage: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + + return command_usage(error); +} + +/** + * Register the command. + */ +static void __attribute__ ((constructor))reg() +{ + command_register((command_t) { + est, 'E', "est", + "Enroll an X.509 certificate with an EST server", + {"--url url [--in file] [--cacert file]+ [--cert file --key file]", + "[--interval time] [--maxpolltime time] [--outform der|pem]"}, + { + {"help", 'h', 0, "show usage information"}, + {"url", 'u', 1, "URL of the EST server"}, + {"in", 'i', 1, "PKCS#10 input file, default: stdin"}, + {"cacert", 'c', 1, "CA certificate"}, + {"cert", 'o', 1, "Old certificate about to be renewed"}, + {"key", 'k', 1, "Old RSA private key about to be replaced"}, + {"interval", 't', 1, "poll interval, default: 60s"}, + {"maxpolltime", 'm', 1, "maximum poll time, default: 0 (no limit)"}, + {"outform", 'f', 1, "encoding of stored certificates, default: der"}, + } + }); +} From c2dc5f69cac078bae2f4f8efe99d7e7244dc7336 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Tue, 16 Aug 2022 15:24:02 +0200 Subject: [PATCH 12/24] pki: Created pki --est man page --- configure.ac | 3 +- src/libstrongswan/utils/lexparser.c | 8 ++ src/libstrongswan/utils/lexparser.h | 8 +- src/pki/man/Makefile.am | 1 + src/pki/man/pki---est.1.in | 183 ++++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 src/pki/man/pki---est.1.in diff --git a/configure.ac b/configure.ac index e0a78cc7f..7cbc96b6e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # # Copyright (C) 2007-2017 Tobias Brunner -# Copyright (C) 2006-2019 Andreas Steffen +# Copyright (C) 2006-2022 Andreas Steffen # Copyright (C) 2006-2014 Martin Willi # # Copyright (C) secunet Security Networks AG @@ -2162,6 +2162,7 @@ AC_CONFIG_FILES([ src/pki/man/pki.1 src/pki/man/pki---acert.1 src/pki/man/pki---dn.1 + src/pki/man/pki---est.1 src/pki/man/pki---estca.1 src/pki/man/pki---gen.1 src/pki/man/pki---issue.1 diff --git a/src/libstrongswan/utils/lexparser.c b/src/libstrongswan/utils/lexparser.c index 654a5b4b4..fe94882f2 100644 --- a/src/libstrongswan/utils/lexparser.c +++ b/src/libstrongswan/utils/lexparser.c @@ -36,6 +36,14 @@ bool match(const char *pattern, const chunk_t *ch) return ch->len == strlen(pattern) && strncmp(pattern, ch->ptr, ch->len) == 0; } +/** + * compare string with chunk ignoring the case of the characters + */ +bool matchcase(const char *pattern, const chunk_t *ch) +{ + return ch->len == strlen(pattern) && strncasecmp(pattern, ch->ptr, ch->len) == 0; +} + /** * extracts a token ending with the first occurrence of a given termination symbol */ diff --git a/src/libstrongswan/utils/lexparser.h b/src/libstrongswan/utils/lexparser.h index 96d8d8cb5..20379fb59 100644 --- a/src/libstrongswan/utils/lexparser.h +++ b/src/libstrongswan/utils/lexparser.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2001-2008 Andreas Steffen - * + * Copyright (C) 2001-2022 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -35,6 +34,11 @@ bool eat_whitespace(chunk_t *src); */ bool match(const char *pattern, const chunk_t *ch); +/** + * Compare null-terminated pattern with chunk ignoring the case of the characters + */ +bool matchcase(const char *pattern, const chunk_t *ch); + /** * Extracts a token ending with the first occurrence of a given termination symbol */ diff --git a/src/pki/man/Makefile.am b/src/pki/man/Makefile.am index c3f3982d9..f220f39f4 100644 --- a/src/pki/man/Makefile.am +++ b/src/pki/man/Makefile.am @@ -2,6 +2,7 @@ man1_MANS = \ pki.1 \ pki---acert.1 \ pki---dn.1 \ + pki---est.1 \ pki---estca.1 \ pki---gen.1 \ pki---issue.1 \ diff --git a/src/pki/man/pki---est.1.in b/src/pki/man/pki---est.1.in new file mode 100644 index 000000000..5c4f16fc7 --- /dev/null +++ b/src/pki/man/pki---est.1.in @@ -0,0 +1,183 @@ +.TH "PKI \-\-EST" 1 "2022-08-22" "@PACKAGE_VERSION@" "strongSwan" +. +.SH "NAME" +. +pki \-\-est \- Enroll an X.509 certificate with an EST server +. +.SH "SYNOPSIS" +. +.SY pki\ \-\-est +.BI\-\-\-url\~ url +.OP \-\-in file +.BI \-\-cacert\~ file +.RB [ \-\-cert +.IR file | \fB\-\-certid\fR +.IR hex ] +.RB [ \-\-key +.IR file | \fB\-\-keyid\fR +.IR hex ] +.OP \-\-userpass username:password +.OP \-\-interval time +.OP \-\-maxpolltime time +.OP \-\-outform encoding +.OP \-\-debug level +.YS +. +.SY pki\ \-\-est +.BI \-\-options\~ file +.YS +. +.SY "pki \-\-est" +.B \-h +| +.B \-\-help +.YS +. +.SH "DESCRIPTION" +. +This sub-command of +.BR pki (1) +sends a PKCS#10 certificate request via HTTPS to a server using the Enrollment +over Secure Transport (EST) Protocol (RFC 7030). After successful authorization +which with manual authentication requires periodic polling by the enrollment +client, the EST server returns an X.509 certificate signed by the CA. + +Before the expiry of the current certificate, a new client certificate based on +a fresh private key can be requested, using the old certificate and the old +key for automatic TLS client authentication with the EST server. +. +.SH "OPTIONS" +. +.TP +.B "\-h, \-\-help" +Print usage information with a summary of the available options. +.TP +.BI "\-v, \-\-debug " level +Set debug level, default: 1. +.TP +.BI "\-+, \-\-options " file +Read command line options from \fIfile\fR. +.TP +.BI "\-u, \-\-url " url +URL of the EST server. +.TP +.BI "\-i, \-\-in " file +PKCS#10 certificate request. If not given, the certificate request is read from +\fISTDIN\fR. +.TP +.BI "\-C, \-\-cacert " file +CA certificate in the trust chain used for EST TLS server signature verification +or in the trust chain to verify the client certificate issued by the CA. +Can be used multiple times. +.TP +.BI "\-c, \-\-cert " file +Client certificate to be renewed. +.TP +.BI "\-X, \-\-certid " hex +Smartcard or TPM 2.0 client certficate object handle. +.TP +.BI "\-k, \-\-key " file +Client private key to be replaced. +.TP +.BI "\-x, \-\-keyid " hex +Smartcard or TPM 2.0 client private key object handle. +.TP +.BI "\-p, \-\-userpass " username:password +Optional username:password that may be used for HTTP basic authentication. +.TP +.BI "\-t, \-\-interval " time +Poll interval in seconds, defaults to \fI60s\fR. This value might get overridden +by the +.B retry-after +header in the HTTP 202 reply from the EST server. +.TP +.BI "\-m, \-\-maxpolltime " time +Maximum poll time in seconds, defaults to \fI0\fR which means unlimited polling. +.TP +.BI "\-f, \-\-outform " encoding +Encoding of the created certificate file. Either \fIder\fR (ASN.1 DER) or +\fIpem\fR (Base64 PEM), defaults to \fIder\fR. +. +.SH "EXAMPLES" +. +To save some typing work the following command line options are stored in a +\fIest.opt\fR file: +.PP +.EX +\-\-url https://pki.strongswan.org:8443 +\-\-cacert tlsca.crt +\-\-cacert tlsca-1.crt +\-\-cacert myca.crt +\-\-cacert myca-1.crt +.EE +.PP +.B NOTE: +For a successful HTTPS connection, trust must be established into the EST server +certificate. The TLS trust chain including the root CA certificate and +optionally intermediate CA certificates must be given using [multiple] +.B --cacert* +options. +.P +The +.B --cacert +option must also be used to be able to verify the received client certificate +issued by the CA. This second trust chain might be identical to the TLS trust +chain (if the EST server is using a TLS server certificate issued by its own CA) +or might be totally different, e.g. if a Let's Encrypt EST server certificate is +used. +.P +With the following command, an X.509 certificate signed by the intermediate CA is +requested from an EST server based on a PKCS#10 certificate request: +.PP +.EX +pki \-\-options est.opt --in moonReq.der > moonCert.der + +negotiated TLS 1.3 using suite TLS_AES_256_GCM_SHA384 +received TLS server certificate 'C=CH, O=strongSwan Project, CN=pki.strongswan.org' + using certificate "C=CH, O=strongSwan Project, CN=pki.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 + EST request pending, polling indefinitely every 300 seconds + going to sleep for 300 seconds + ... +Issued certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + serial: 1a:ff:de:66:d9:38:ea:d5:b6:da + using certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +Issued certificate is trusted, valid from Aug 22 15:19:43 2022 until Aug 22 15:19:43 2023 (currently valid) +.EE +.PP +This certificate can be renewed some time before it expires with the command: +.PP +.EX +pki \-\-options est.opt --in moonReqNew.der --cert moonCert.der --key moonKey.der > moonCertNew.der + +negotiated TLS 1.3 using suite TLS_AES_256_GCM_SHA384 +received TLS server certificate 'C=CH, O=strongSwan Project, CN=pki.strongswan.org' + using certificate "C=CH, O=strongSwan Project, CN=pki.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +sending TLS client certificate 'C=CH, O=strongSwan Project, CN=moon.strongswan.org' +sending TLS intermediate certificate 'C=CH, O=strongSwan Project, CN=strongSwan Issuing CA' +Issued certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + serial: 1b:ff:ad:dc:2f:50:c4:cb:a1:44 + using certificate "C=CH, O=strongSwan Project, CN=moon.strongswan.org" + using trusted intermediate ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Issuing CA" + using trusted ca certificate "C=CH, O=strongSwan Project, CN=strongSwan Root CA" + reached self-signed root ca with a path length of 1 +Issued certificate is trusted, valid from Jul 20 12:21:00 2023 until Jul 20 12:21:00 2024 (currently valid) +.EE +.PP +If the private key and the certificate of the client is stored in a TPM 2.0, the +renewal can be done wtih the following options: +.PP +.EX +pki \-\-options est.opt --in moonReqNew.der --certid 0x01800004 --keyid 0x81010004 > moonCertNew.der + +.SH "SEE ALSO" +. +.BR pki (1) From 60a764bad9e74080d8e45fcf3ea631a466343a61 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Fri, 19 Aug 2022 02:04:58 +0200 Subject: [PATCH 13/24] pki: use libtls for pki --est --- configure.ac | 2 +- src/pki/Makefile.am | 4 +- src/pki/commands/est.c | 47 +++-- src/pki/est/est_tls.c | 426 +++++++++++++++++++++++++++++++++++++++++ src/pki/est/est_tls.h | 69 +++++++ 5 files changed, 532 insertions(+), 16 deletions(-) create mode 100644 src/pki/est/est_tls.c create mode 100644 src/pki/est/est_tls.h diff --git a/configure.ac b/configure.ac index 7cbc96b6e..4c44bd0d0 100644 --- a/configure.ac +++ b/configure.ac @@ -452,7 +452,7 @@ if test x$tnc_imc = xtrue -o x$tnc_imv = xtrue -o x$tnccs_11 = xtrue -o x$tnccs_ tnc_tnccs=true; fi -if test x$eap_tls = xtrue -o x$eap_ttls = xtrue -o x$eap_peap = xtrue -o x$tnc_tnccs = xtrue; then +if test x$eap_tls = xtrue -o x$eap_ttls = xtrue -o x$eap_peap = xtrue -o x$tnc_tnccs = xtrue -o x$pki = xtrue; then tls=true; fi diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 382cd42c8..22fb11fe8 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -20,15 +20,17 @@ pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/self.c \ commands/signcrl.c \ commands/verify.c \ - est/est.h est/est.c \ + est/est.h est/est.c est/est_tls.h est/est_tls.c \ scep/scep.h scep/scep.c pki_LDADD = \ $(top_builddir)/src/libstrongswan/libstrongswan.la \ + $(top_builddir)/src/libtls/libtls.la \ $(PTHREADLIB) $(ATOMICLIB) $(DLLIB) pki.o : $(top_builddir)/config.status AM_CPPFLAGS = \ -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libtls \ -DPLUGINS=\""${pki_plugins}\"" diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c index 55ba4dc09..ceee5b810 100644 --- a/src/pki/commands/est.c +++ b/src/pki/commands/est.c @@ -20,13 +20,11 @@ #include "pki.h" #include "pki_cert.h" #include "est/est.h" +#include "est/est_tls.h" #include #include -#define HTTP_CODE_OK 200 -#define HTTP_CODE_ACCEPTED 202 - /* default polling time interval in EST manual mode */ #define DEFAULT_POLL_INTERVAL 60 /* seconds */ @@ -43,9 +41,10 @@ static int est() mem_cred_t *creds = NULL; private_key_t *client_key = NULL; est_op_t est_op = EST_SIMPLE_ENROLL; + est_tls_t *est_tls; u_int poll_interval = DEFAULT_POLL_INTERVAL; u_int max_poll_time = 0, poll_start = 0; - u_int http_code = 0; + u_int http_code = 0, retry_after = 0; int status = 1; /* initialize CA certificate storage */ @@ -165,6 +164,7 @@ static int est() DBG1(DBG_APP, "could not load client cert file '%s'", client_cert_file); goto end; } + creds->add_cert(creds, FALSE, client_cert->get_ref(client_cert)); /* load old client private key */ client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, @@ -174,19 +174,30 @@ static int est() DBG1(DBG_APP, "parsing client private key failed"); goto end; } + creds->add_key(creds, client_key->get_ref(client_key)); est_op = EST_SIMPLE_REENROLL; } - if (!est_https_request(url, est_op, TRUE, pkcs10_encoding, &est_response, - &http_code)) + est_tls = est_tls_create(url, client_cert, NULL); + if (!est_tls) { - DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", http_code); + DBG1(DBG_APP, "TLS connection to EST server was not established"); + goto end; + } + if (!est_tls->request(est_tls, est_op, pkcs10_encoding, &est_response, + &http_code, &retry_after)) + { + DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); goto end; } /* in case of manual mode, we are going into a polling loop */ - if (http_code == HTTP_CODE_ACCEPTED) + if (http_code == EST_HTTP_CODE_ACCEPTED) { + if (retry_after > 0 && poll_interval < retry_after) + { + poll_interval = retry_after; + } if (max_poll_time > 0) { DBG1(DBG_APP, " EST request pending, polling every %d seconds" @@ -200,7 +211,7 @@ static int est() poll_start = time_monotonic(NULL); } - while (http_code == HTTP_CODE_ACCEPTED) + while (http_code == EST_HTTP_CODE_ACCEPTED) { if (max_poll_time > 0 && (time_monotonic(NULL) - poll_start) >= max_poll_time) @@ -211,16 +222,23 @@ static int est() DBG1(DBG_APP, " going to sleep for %d seconds", poll_interval); sleep(poll_interval); chunk_free(&est_response); - if (!est_https_request(url, est_op, TRUE, pkcs10_encoding, &est_response, - &http_code)) + + est_tls->destroy(est_tls); + est_tls = est_tls_create(url, client_cert, NULL); + if (!est_tls) { - DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", - http_code); + DBG1(DBG_APP, "TLS connection to EST server was not established"); + goto end; + } + if (!est_tls->request(est_tls, est_op, pkcs10_encoding, &est_response, + &http_code, &retry_after)) + { + DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); goto end; } } - if (http_code == HTTP_CODE_OK) + if (http_code == EST_HTTP_CODE_OK) { status = pki_cert_extract_cert(est_response, form, creds) ? 0 : 1; } @@ -228,6 +246,7 @@ static int est() end: lib->credmgr->remove_set(lib->credmgr, &creds->set); creds->destroy(creds); + DESTROY_IF(est_tls); DESTROY_IF(client_cert); DESTROY_IF(client_key); DESTROY_IF(pkcs10); diff --git a/src/pki/est/est_tls.c b/src/pki/est/est_tls.c new file mode 100644 index 000000000..2546bb026 --- /dev/null +++ b/src/pki/est/est_tls.c @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2013-2022 Andreas Steffen + * + * 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 . + * + * 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 /* for asprintf() */ +#include +#include +#include +#include +#include + +#include "est_tls.h" + +#include +#include +#include + +static const char *operations[] = { + "cacerts", + "simpleenroll", + "simplereenroll", + "fullcmc", + "serverkeygen", + "csrattrs" +}; + +static const char *request_types[] = { + "", + "application/pkcs10", + "application/pkcs10", + "application/pkcs7-mime", + "application/pkcs10", + "" +}; + +typedef struct private_est_tls_t private_est_tls_t; + +/** + * Private data of an est_tls_t object. + */ +struct private_est_tls_t { + + /** + * Public est_tls_t interface. + */ + est_tls_t public; + + /** + * EST Server (IP address and port) + */ + host_t *host; + + /** + * File descriptor for secure TCP socket + */ + int fd; + + /** + * TLS socket + */ + tls_socket_t *tls; + + /** + * Host string of the form used for http requests + */ + char *http_host; + + /** + * Path string used for http requests + */ + char *http_path; + + /** + * Optional for http basic authentication + */ + char *user_pass; +}; + +static chunk_t build_http_request(private_est_tls_t *this, est_op_t op, chunk_t in) +{ + char *http_header, http_auth[256]; + chunk_t request = chunk_empty, data; + int len; + + /* Use Basic Authentication? */ + if (this->user_pass) + { + snprintf(http_auth, sizeof(http_auth), "Authorization: Basic %s\r\n", + this->user_pass); + } + else + { + *http_auth = '\0'; + } + + if (strlen(request_types[op]) > 0) /* create HTTP POST request */ + { + data = chunk_to_base64(in, NULL); + + len = asprintf(&http_header, + "POST %s/.well-known/est/%s HTTP/1.1\r\n" + "Host: %s\r\n" + "%s" + "Content-Type: %s\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Length: %d\r\n" + "\r\n", + this->http_path, operations[op], this->http_host, http_auth, + request_types[op], (int)data.len); + if (len > 0) + { + request = chunk_cat("mm", chunk_create(http_header, len), data); + } + else + { + chunk_free(&data); + } + } + else /* create HTTP GET request */ + { + len = asprintf(&http_header, + "GET %s/.well-known/est/%s HTTP/1.1\r\n" + "Host: %s\r\n" + "%s", + this->http_path, operations[op], this->http_host, http_auth); + if (len > 0) + { + request = chunk_create(http_header, len); + } + } + return request; +} + +static bool parse_http_header(chunk_t *in, u_int *http_code, u_int *content_len, + bool *base64, u_int *retry_after) +{ + chunk_t line, version, parameter; + u_int len; + + /*initialize output parameters */ + *http_code = 0; + *content_len = 0; + *base64 = FALSE; + + /* Process HTTP protocol version and HTTP status code */ + if (!fetchline(in, &line) || !extract_token(&version, ' ', &line) || + !match("HTTP/1.1", &version) || sscanf(line.ptr, "%d", http_code) != 1) + { + DBG1(DBG_APP, "malformed http response header"); + return FALSE; + } + + /* Process HTTP header line by line until the HTTP body is reached */ + while (fetchline(in, &line)) + { + if (line.len == 0) + { + break; + } + if (extract_token(¶meter, ':', &line) && eat_whitespace(&line)) + { + if (matchcase("Content-Length", ¶meter)) + { + if (sscanf(line.ptr, "%u", &len) == 1) + { + *content_len = len; + } + } + else if (matchcase("Content-Transfer-Encoding", ¶meter) && + matchcase("Base64", &line)) + { + *base64 = TRUE; + } + else if (matchcase("Retry-After", ¶meter)) + { + if (sscanf(line.ptr, "%u", &len) == 1 && retry_after) + { + *retry_after = len; + } + } + } + } + + return (*http_code < 300); +} + + +METHOD(est_tls_t, request, bool, + private_est_tls_t *this, est_op_t op, chunk_t in, chunk_t *out, + u_int *http_code, u_int *retry_after) +{ + chunk_t http = chunk_empty, data = chunk_empty, response; + u_int content_len; + char buf[1024]; + bool base64; + int len; + + /* initialize output variables */ + *out = chunk_empty; + *http_code = 0; + *retry_after = 0; + + http = build_http_request(this, op, in); + + if (http.len == 0) + { + return FALSE; + } + DBG1(DBG_APP, "http: %B", &http); + + /* send https request */ + if (this->tls->write(this->tls, http.ptr, http.len) != http.len) + { + DBG1(DBG_APP, "TLS socket write failed"); + chunk_free(&http); + return FALSE; + } + chunk_free(&http); + + /* receive first part of https response */ + len = this->tls->read(this->tls, buf, sizeof(buf), TRUE); + if (len <= 0) + { + DBG1(DBG_APP, "TLS socket first read failed"); + return FALSE; + } + response = chunk_create(buf, len); + DBG1(DBG_APP, "response: %B", &response); + + if (!parse_http_header(&response, http_code, &content_len, &base64, + retry_after)) + { + return FALSE; + } + if (*http_code == EST_HTTP_CODE_OK) + { + if (content_len == 0) + { + DBG1(DBG_APP, "no content-length defined in http header"); + return FALSE; + } + if (response.len > content_len) + { + DBG1(DBG_APP, "http body is larger than content-length"); + return FALSE; + } + + data = chunk_alloc(content_len); + memcpy(data.ptr, response.ptr, response.len); + + if (data.len > response.len) + { + /* read remaining part of https response */ + len = this->tls->read(this->tls, data.ptr + response.len, + data.len - response.len, TRUE); + if (len < data.len - response.len) + { + DBG1(DBG_APP, "TLS socket second read failed"); + chunk_free(&data); + return FALSE; + } + } + + if (base64) + { + *out = chunk_from_base64(data, NULL); + chunk_free(&data); + } + else + { + *out = data; + } + } + return TRUE; +} + +METHOD(est_tls_t, destroy, void, + private_est_tls_t *this) +{ + DESTROY_IF(this->tls); + DESTROY_IF(this->host); + if (this->fd != -1) + { + close(this->fd); + } + free(this->http_host); + free(this->http_path); + free(this->user_pass); + free(this); +} + +static bool est_tls_init(private_est_tls_t *this, char *uri, + certificate_t *client_cert) +{ + identification_t *client_id = NULL, *server_id = NULL; + char *host_str, *port_str, *path_str; + int port = 443; + bool success = FALSE; + + /* check for "https://" prefix and remove it */ + if (strlen(uri) < 8 || !strncaseeq(uri, "https://", 8)) + { + DBG1(DBG_APP, "'%s' is not an https URI", uri); + return FALSE; + } + uri += 8; + + /* any trailing path or command? */ + path_str = strchr(uri, '/'); + + this->http_path = + strdup( (path_str == NULL || path_str[1] == '\0') ? "" : path_str ); + + if (path_str) + { + /* NUL-terminate host_str */ + *path_str = '\0'; + } + + /* duplicate string since we are going to manipulate it */ + host_str = strdup(uri); + + /* another duplicate for http requests */ + this->http_host = strdup(host_str); + + /* extract hostname and port from URI */ + port_str = strchr(host_str, ':'); + + if (port_str) + { + /* NUL-terminate hostname */ + *port_str++ = '\0'; + + /* extract port */ + if (sscanf(port_str, "%d", &port) != 1) + { + DBG1(DBG_APP, "parsing server port %s failed", port_str); + goto end; + } + } + + /* open TCP socket and connect to EST server */ + this->host = host_create_from_dns(host_str, 0, port); + if (!this->host) + { + DBG1(DBG_APP, "resolving hostname %s failed", host_str); + goto end; + } + + this->fd = socket(this->host->get_family(this->host), SOCK_STREAM, 0); + if (this->fd == -1) + { + DBG1(DBG_APP, "opening socket failed: %s", strerror(errno)); + goto end; + } + + if (connect(this->fd, this->host->get_sockaddr(this->host), + *this->host->get_sockaddr_len(this->host)) == -1) + { + DBG1(DBG_APP, "connecting to %#H failed: %s", + this->host, strerror(errno)); + goto end; + } + + if (client_cert) + { + client_id = client_cert->get_subject(client_cert); + } + server_id = identification_create_from_string(host_str); + + /* open TLS socket */ + this->tls = tls_socket_create(FALSE, server_id, client_id, this->fd, + NULL, TLS_UNSPEC, TLS_UNSPEC, 0); + server_id->destroy(server_id); + if (!this->tls) + { + DBG1(DBG_APP, "creating TLS socket failed"); + goto end; + } + success = TRUE; + +end: + free(host_str); + + return success; +} + +/** + * See header + */ +est_tls_t *est_tls_create(char *uri, certificate_t *client_cert, char *user_pass) +{ + private_est_tls_t *this; + + INIT(this, + .public = { + .request = _request, + .destroy = _destroy, + }, + ); + + if (user_pass) + { + this->user_pass = strdup(user_pass); + } + + if (!est_tls_init(this, uri, client_cert)) + { + destroy(this); + return NULL; + } + + return &this->public; +} diff --git a/src/pki/est/est_tls.h b/src/pki/est/est_tls.h new file mode 100644 index 000000000..d05831c71 --- /dev/null +++ b/src/pki/est/est_tls.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 Andreas Steffen + * + * 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 . + * + * 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. + */ + +/** + * @defgroup est_tls est_tls + * @{ @ingroup pki + */ + +#ifndef EST_TLS_H_ +#define EST_TLS_H_ + +#include "est.h" + +#include +#include + +#define EST_HTTP_CODE_OK 200 +#define EST_HTTP_CODE_ACCEPTED 202 + +typedef struct est_tls_t est_tls_t; + +/** + * TLS Interface for sending and receiving HTTPS messages + */ +struct est_tls_t { + + /** + * Send a https request and get a response back + * + * @param option EST operation + * @param in HTTP POST input data + * @param out HTTP response + * @param http_code HTTP status code + * @param retry_after Retry time in seconds + * @result TRUE if successful + */ + bool (*request)(est_tls_t *this, est_op_t op, chunk_t in, chunk_t *out, + u_int *http_code, u_int *retry_after); + + /** + * Destroy an est_tls_t object. + */ + void (*destroy)(est_tls_t *this); +}; + +/** + * Create a est_tls instance. + * + * @param uri URI (https://...) + * @param client_cert Optional client certificate + * @param user_pass Optional username:password for HTTP Basic Authentication + */ +est_tls_t *est_tls_create(char *uri, certificate_t *client_cert, + char *user_pass); + +#endif /** EST_TLS_H_ @}*/ From a3914d7db5db0a76098f63d3e0e2a6452548088c Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Fri, 19 Aug 2022 17:09:02 +0200 Subject: [PATCH 14/24] libtls: Send empty cert payload upon cert request Currently when a TLS client doesn't have a certificate, it doesn't send a certficiate payload upon receiving a certificate request from the TLS server. According to the TLS 1.2 and 1.3 RFCs an empty certificate payload must be sent. --- src/libtls/tls_peer.c | 56 +++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/libtls/tls_peer.c b/src/libtls/tls_peer.c index 04f4bcb37..91f7efba8 100644 --- a/src/libtls/tls_peer.c +++ b/src/libtls/tls_peer.c @@ -90,6 +90,11 @@ struct private_tls_peer_t { */ peer_state_t state; + /** + * Received a certificate request from server + */ + bool certreq_received; + /** * TLS version we offered in hello */ @@ -982,6 +987,7 @@ static status_t process_certreq(private_tls_peer_t *this, bio_reader_t *reader) } extensions->destroy(extensions); } + this->certreq_received = TRUE; this->state = STATE_CERTREQ_RECEIVED; return NEED_MORE; } @@ -1478,35 +1484,39 @@ static status_t send_certificate(private_tls_peer_t *this, version_min = this->tls->get_version_min(this->tls); version_max = this->tls->get_version_max(this->tls); - if (!this->hashsig.len) + + if (this->peer) { - convert_cert_types(this); - } - enumerator = tls_create_private_key_enumerator(version_min, version_max, - this->hashsig, this->peer); - if (!enumerator || !enumerator->enumerate(enumerator, &key, &auth)) - { - if (!enumerator) + if (!this->hashsig.len) { - DBG1(DBG_TLS, "no common signature algorithms found"); + convert_cert_types(this); + } + enumerator = tls_create_private_key_enumerator(version_min, version_max, + this->hashsig, this->peer); + if (!enumerator || !enumerator->enumerate(enumerator, &key, &auth)) + { + if (!enumerator) + { + DBG1(DBG_TLS, "no common signature algorithms found"); + } + else + { + DBG1(DBG_TLS, "no usable TLS client certificate found for '%Y'", + this->peer); + } + this->peer->destroy(this->peer); + this->peer = NULL; } else { - DBG1(DBG_TLS, "no usable TLS client certificate found for '%Y'", - this->peer); + this->private = key->get_ref(key); + this->peer_auth->merge(this->peer_auth, auth, FALSE); } - this->peer->destroy(this->peer); - this->peer = NULL; + DESTROY_IF(enumerator); } - else - { - this->private = key->get_ref(key); - this->peer_auth->merge(this->peer_auth, auth, FALSE); - } - DESTROY_IF(enumerator); /* certificate request context as described in RFC 8446, section 4.4.2 */ - if (this->tls->get_version_max(this->tls) > TLS_1_2) + if (version_max > TLS_1_2) { writer->write_uint8(writer, 0); } @@ -1524,7 +1534,7 @@ static status_t send_certificate(private_tls_peer_t *this, free(data.ptr); } /* extensions see RFC 8446, section 4.4.2 */ - if (this->tls->get_version_max(this->tls) > TLS_1_2) + if (version_max > TLS_1_2) { certs->write_uint16(certs, 0); } @@ -1767,7 +1777,7 @@ METHOD(tls_handshake_t, build, status_t, case STATE_INIT: return send_client_hello(this, type, writer); case STATE_HELLO_DONE: - if (this->peer) + if (this->peer || this->certreq_received) { return send_certificate(this, type, writer); } @@ -1804,7 +1814,7 @@ METHOD(tls_handshake_t, build, status_t, return NEED_MORE; } this->crypto->change_cipher(this->crypto, TRUE); - if (this->peer) + if (this->peer || this->certreq_received) { return send_certificate(this, type, writer); } From b392fbd68c1599dc10118e32b439086b2fbb9cba Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Fri, 19 Aug 2022 17:18:52 +0200 Subject: [PATCH 15/24] libtls: unit tests run with default plugins The gcm plugin has been added to the default plugins and all certificate types are loaded to allow the libtls socket unit tests to run with the strongSwan default plugins. --- configure.ac | 2 +- src/libtls/tests/suites/test_socket.c | 30 +++++++++++---------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/configure.ac b/configure.ac index 4c44bd0d0..140cbe53f 100644 --- a/configure.ac +++ b/configure.ac @@ -144,7 +144,7 @@ ARG_ENABL_SET([ctr], [enables the Counter Mode wrapper crypto plugin. ARG_DISBL_SET([des], [disable DES/3DES software implementation plugin.]) ARG_DISBL_SET([drbg], [disable the NIST Deterministic Random Bit Generator plugin.]) ARG_DISBL_SET([fips-prf], [disable FIPS PRF software implementation plugin.]) -ARG_ENABL_SET([gcm], [enables the GCM AEAD wrapper crypto plugin.]) +ARG_DISBL_SET([gcm], [disable the GCM AEAD wrapper crypto plugin.]) ARG_ENABL_SET([gcrypt], [enables the libgcrypt plugin.]) ARG_DISBL_SET([gmp], [disable GNU MP (libgmp) based crypto implementation plugin.]) ARG_DISBL_SET([curve25519], [disable Curve25519 Diffie-Hellman plugin.]) diff --git a/src/libtls/tests/suites/test_socket.c b/src/libtls/tests/suites/test_socket.c index 2b1b5c89a..e410ffd28 100644 --- a/src/libtls/tests/suites/test_socket.c +++ b/src/libtls/tests/suites/test_socket.c @@ -339,12 +339,6 @@ static void setup_credentials(chunk_t key_data, chunk_t cert_data) } } -START_SETUP(setup_creds) -{ - setup_credentials(chunk_from_thing(ecdsa), chunk_from_thing(ecdsa_crt)); -} -END_SETUP - START_SETUP(setup_ed25519_creds) { setup_credentials(chunk_from_thing(ed25519), chunk_from_thing(ed25519_crt)); @@ -778,22 +772,22 @@ Suite *socket_suite_create() s = suite_create("socket"); tc = tcase_create("TLS [1.0..1.3] client to TLS 1.3 server"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_versions_test(test_tls_13_server, TLS_1_0, TLS_1_3); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.3 client to TLS [1.0..1.3] server"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_versions_test(test_tls_13_client, TLS_1_0, TLS_1_3); suite_add_tcase(s, tc); tc = tcase_create("TLS [1.0..1.3] client to TLS 1.2 server"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_versions_test(test_tls_12_server, TLS_1_0, TLS_1_3); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.3/key exchange groups"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); tcase_add_loop_test(tc, test_tls13_ke_groups, 0, tls_crypto_get_supported_groups(NULL)); suite_add_tcase(s, tc); @@ -823,42 +817,42 @@ Suite *socket_suite_create() suite_add_tcase(s, tc); tc = tcase_create("TLS 1.3/anon"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls13, TLS_1_3); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.3/mutl"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls13_mutual, TLS_1_3); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.2/anon"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls12, TLS_1_2); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.2/mutl"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls12_mutual, TLS_1_2); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.1/anon"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls11, TLS_1_1); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.1/mutl"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls11_mutual, TLS_1_1); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.0/anon"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls10, TLS_1_0); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.0/mutl"); - tcase_add_checked_fixture(tc, setup_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); add_tls_test(test_tls10_mutual, TLS_1_0); suite_add_tcase(s, tc); From 2b53b1055daafb2253a084a2ec4aa39e204fb89a Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sun, 21 Aug 2022 11:13:53 +0200 Subject: [PATCH 16/24] pki: Optimize certificate download for --scep and --est --- src/pki/commands/est.c | 70 ++++++++++++++++++++------------- src/pki/commands/scep.c | 86 ++++++++++++++++++++++++----------------- src/pki/est/est_tls.c | 4 +- src/pki/pki_cert.c | 36 +++++++++++------ src/pki/pki_cert.h | 3 +- 5 files changed, 121 insertions(+), 78 deletions(-) diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c index ceee5b810..73e3ebc45 100644 --- a/src/pki/commands/est.c +++ b/src/pki/commands/est.c @@ -35,10 +35,11 @@ static int est() { char *arg, *url = NULL, *file = NULL, *error = NULL; char *client_cert_file = NULL, *client_key_file = NULL; + char *user_pass = NULL; cred_encoding_type_t form = CERT_ASN1_DER; chunk_t pkcs10_encoding = chunk_empty, est_response = chunk_empty; certificate_t *pkcs10 = NULL, *client_cert = NULL, *cacert = NULL; - mem_cred_t *creds = NULL; + mem_cred_t *creds = NULL, *client_creds = NULL; private_key_t *client_key = NULL; est_op_t est_op = EST_SIMPLE_ENROLL; est_tls_t *est_tls; @@ -55,30 +56,33 @@ static int est() { switch (command_getopt(&arg)) { - case 'h': + case 'h': /* --help */ goto usage; - case 'u': + case 'u': /* --url */ url = arg; continue; - case 'i': + case 'i': /* --in */ file = arg; continue; - case 'c': + case 'c': /* --cacert */ cacert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); if (!cacert) { DBG1(DBG_APP, "could not load cacert file '%s'", arg); - goto end; + goto err; } creds->add_cert(creds, TRUE, cacert); continue; - case 'o': + case 'o': /* --cert */ client_cert_file = arg; continue; - case 'k': + case 'k': /* --key */ client_key_file = arg; continue; + case 'p': /* --userpass */ + user_pass = arg; + continue; case 't': /* --pollinterval */ poll_interval = atoi(arg); if (poll_interval <= 0) @@ -90,7 +94,7 @@ static int est() case 'm': /* --maxpolltime */ max_poll_time = atoi(arg); continue; - case 'f': + case 'f': /* --force */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { error = "invalid certificate output format"; @@ -134,7 +138,7 @@ static int est() { DBG1(DBG_APP, "reading PKCS#10 certificate request failed: %s\n", strerror(errno)); - goto end; + goto err; } pkcs10 = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PKCS10_REQUEST, @@ -144,15 +148,21 @@ static int est() if (!pkcs10) { DBG1(DBG_APP, "parsing certificate request failed"); - goto end; + goto err; } /* generate PKCS#10 encoding */ if (!pkcs10->get_encoding(pkcs10, CERT_ASN1_DER, &pkcs10_encoding)) { DBG1(DBG_APP, "encoding certificate request failed"); - goto end; + pkcs10->destroy(pkcs10); + goto err; } + pkcs10->destroy(pkcs10); + + /* create a separate set for the old client credentials */ + client_creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &client_creds->set); if (client_cert_file) { @@ -164,7 +174,7 @@ static int est() DBG1(DBG_APP, "could not load client cert file '%s'", client_cert_file); goto end; } - creds->add_cert(creds, FALSE, client_cert->get_ref(client_cert)); + client_creds->add_cert(client_creds, FALSE, client_cert); /* load old client private key */ client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, @@ -174,11 +184,11 @@ static int est() DBG1(DBG_APP, "parsing client private key failed"); goto end; } - creds->add_key(creds, client_key->get_ref(client_key)); + client_creds->add_key(client_creds, client_key); est_op = EST_SIMPLE_REENROLL; } - est_tls = est_tls_create(url, client_cert, NULL); + est_tls = est_tls_create(url, client_cert, user_pass); if (!est_tls) { DBG1(DBG_APP, "TLS connection to EST server was not established"); @@ -187,6 +197,7 @@ static int est() if (!est_tls->request(est_tls, est_op, pkcs10_encoding, &est_response, &http_code, &retry_after)) { + est_tls->destroy(est_tls); DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); goto end; } @@ -213,6 +224,9 @@ static int est() while (http_code == EST_HTTP_CODE_ACCEPTED) { + chunk_free(&est_response); + est_tls->destroy(est_tls); + if (max_poll_time > 0 && (time_monotonic(NULL) - poll_start) >= max_poll_time) { @@ -221,10 +235,8 @@ static int est() } DBG1(DBG_APP, " going to sleep for %d seconds", poll_interval); sleep(poll_interval); - chunk_free(&est_response); - est_tls->destroy(est_tls); - est_tls = est_tls_create(url, client_cert, NULL); + est_tls = est_tls_create(url, client_cert, user_pass); if (!est_tls) { DBG1(DBG_APP, "TLS connection to EST server was not established"); @@ -234,22 +246,26 @@ static int est() &http_code, &retry_after)) { DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); + est_tls->destroy(est_tls); goto end; } } + est_tls->destroy(est_tls); + +end: + /* remove the old client certificate before extracting the new one */ + lib->credmgr->remove_set(lib->credmgr, &client_creds->set); + client_creds->destroy(client_creds); if (http_code == EST_HTTP_CODE_OK) { - status = pki_cert_extract_cert(est_response, form, creds) ? 0 : 1; + status = pki_cert_extract_cert(est_response, form) ? 0 : 1; } -end: +err: + /* cleanup */ lib->credmgr->remove_set(lib->credmgr, &creds->set); creds->destroy(creds); - DESTROY_IF(est_tls); - DESTROY_IF(client_cert); - DESTROY_IF(client_key); - DESTROY_IF(pkcs10); chunk_free(&pkcs10_encoding); chunk_free(&est_response); @@ -271,14 +287,16 @@ static void __attribute__ ((constructor))reg() est, 'E', "est", "Enroll an X.509 certificate with an EST server", {"--url url [--in file] [--cacert file]+ [--cert file --key file]", - "[--interval time] [--maxpolltime time] [--outform der|pem]"}, + "[-userpass username:password] [--interval time] [--maxpolltime time]", + "[--outform der|pem]"}, { {"help", 'h', 0, "show usage information"}, {"url", 'u', 1, "URL of the EST server"}, {"in", 'i', 1, "PKCS#10 input file, default: stdin"}, {"cacert", 'c', 1, "CA certificate"}, {"cert", 'o', 1, "Old certificate about to be renewed"}, - {"key", 'k', 1, "Old RSA private key about to be replaced"}, + {"key", 'k', 1, "Old private key about to be replaced"}, + {"userpass", 'p', 1, "username:password for http basic auth"}, {"interval", 't', 1, "poll interval, default: 60s"}, {"maxpolltime", 'm', 1, "maximum poll time, default: 0 (no limit)"}, {"outform", 'f', 1, "encoding of stored certificates, default: der"}, diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c index 97a6fc285..500735d17 100644 --- a/src/pki/commands/scep.c +++ b/src/pki/commands/scep.c @@ -61,7 +61,7 @@ static int scep() certificate_t *x509_ca_sig = NULL, *x509_ca_enc = NULL; identification_t *subject = NULL, *issuer = NULL; container_t *container = NULL; - mem_cred_t *creds = NULL; + mem_cred_t *creds = NULL, *client_creds = NULL; scep_msg_t scep_msg_type; scep_attributes_t attrs = empty_scep_attributes; uint32_t caps_flags; @@ -127,7 +127,7 @@ static int scep() if (!cert) { DBG1(DBG_APP, "could not load cacert file '%s'", arg); - goto end; + goto err; } creds->add_cert(creds, TRUE, cert); continue; @@ -236,7 +236,7 @@ static int scep() if (subject->get_type(subject) != ID_DER_ASN1_DN) { DBG1(DBG_APP, "supplied --dn is not a distinguished name"); - goto end; + goto err; } /* load RSA private key from file or stdin */ @@ -253,7 +253,7 @@ static int scep() if (!chunk_from_fd(0, &chunk)) { DBG1(DBG_APP, "reading private key failed: %s", strerror(errno)); - goto end; + goto err; } private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, BUILD_BLOB, chunk, BUILD_END); @@ -262,7 +262,7 @@ static int scep() if (!private) { DBG1(DBG_APP, "parsing private key failed"); - goto end; + goto err; } public = private->get_public_key(private); @@ -271,7 +271,7 @@ static int scep() &scep_response, &http_code)) { DBG1(DBG_APP, "did not receive a valid scep response: HTTP %u", http_code); - goto end; + goto err; } caps_flags = scep_parse_caps(scep_response); chunk_free(&scep_response); @@ -302,7 +302,7 @@ static int scep() { DBG1(DBG_APP, "%N digest algorithm not supported by CA", hash_algorithm_short_names, digest_alg); - goto end; + goto err; } /* check support of selected encryption algorithm */ @@ -322,7 +322,7 @@ static int scep() { DBG1(DBG_APP, "%N encryption algorithm not supported by CA", encryption_algorithm_names, cipher); - goto end; + goto err; } DBG2(DBG_APP, "%N digest and %N encryption algorithm supported by CA", hash_algorithm_short_names, digest_alg, @@ -340,7 +340,7 @@ static int scep() if (!scheme) { DBG1(DBG_APP, "no signature scheme found"); - goto end; + goto err; } /* generate PKCS#10 certificate request */ @@ -355,32 +355,46 @@ static int scep() if (!pkcs10) { DBG1(DBG_APP, "generating certificate request failed"); - goto end; + goto err; } /* generate PKCS#10 encoding */ if (!pkcs10->get_encoding(pkcs10, CERT_ASN1_DER, &pkcs10_encoding)) { DBG1(DBG_APP, "encoding certificate request failed"); - goto end; + pkcs10->destroy(pkcs10); + goto err; } + pkcs10->destroy(pkcs10); if (!scep_generate_transaction_id(public, &transID, &serialNumber)) { DBG1(DBG_APP, "generating transaction ID failed"); - goto end; + goto err; } DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr); if (old_cert_file) { + /* check support of Renewal Operation */ + if (!(caps_flags & SCEP_CAPS_RENEWAL)) + { + DBG1(DBG_APP, "Renewal operation not supported by SCEP server"); + goto err; + } + DBG2(DBG_APP, "SCEP Renewal operation supported"); + + /* set message type for SCEP renewal request */ + scep_msg_type = renewal_via_pkcs_req ? SCEP_PKCSReq_MSG : + SCEP_RenewalReq_MSG; + /* load old client certificate */ x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, old_cert_file, BUILD_END); if (!x509_signer) { DBG1(DBG_APP, "could not load old cert file '%s'", old_cert_file); - goto end; + goto err; } /* load old RSA private key */ @@ -389,20 +403,8 @@ static int scep() if (!priv_signer) { DBG1(DBG_APP, "parsing old private key failed"); - goto end; + goto err; } - - /* check support of Renewal Operation */ - if (!(caps_flags & SCEP_CAPS_RENEWAL)) - { - DBG1(DBG_APP, "Renewal operation not supported by SCEP server"); - goto end; - } - DBG2(DBG_APP, "SCEP Renewal operation supported"); - - /* set message type for SCEP renewal request */ - scep_msg_type = renewal_via_pkcs_req ? SCEP_PKCSReq_MSG : - SCEP_RenewalReq_MSG; } else { @@ -419,8 +421,8 @@ static int scep() BUILD_END); if (!x509_signer) { - DBG1(DBG_APP, "generating self-sigend certificate failed"); - goto end; + DBG1(DBG_APP, "generating self-signed certificate failed"); + goto err; } /* the signing key is identical to the client key */ @@ -429,8 +431,13 @@ static int scep() /* set message type for SCEP request */ scep_msg_type = SCEP_PKCSReq_MSG; } - creds->add_cert(creds, FALSE, x509_signer->get_ref(x509_signer)); - creds->add_key(creds, priv_signer->get_ref(priv_signer)); + + /* create a separate set for the self-signed or old client credentials */ + client_creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &client_creds->set); + + client_creds->add_cert(client_creds, FALSE, x509_signer); + client_creds->add_key(client_creds, priv_signer); /* load CA or RA certificate used for encryption */ x509_ca_enc = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, @@ -566,11 +573,21 @@ static int scep() } container->destroy(container); container = NULL; - - status = pki_cert_extract_cert(data, form, creds) ? 0 : 1; - chunk_free(&data); + status = 0; end: + /* remove the old client certificate before extracting the new one */ + lib->credmgr->remove_set(lib->credmgr, &client_creds->set); + client_creds->destroy(client_creds); + + if (status == 0) + { + status = pki_cert_extract_cert(data, form) ? 0 : 1; + chunk_free(&data); + } + +err: + /* cleanup */ lib->credmgr->remove_set(lib->credmgr, &creds->set); creds->destroy(creds); san->destroy_offset(san, offsetof(identification_t, destroy)); @@ -578,9 +595,6 @@ end: DESTROY_IF(subject); DESTROY_IF(private); DESTROY_IF(public); - DESTROY_IF(priv_signer); - DESTROY_IF(x509_signer); - DESTROY_IF(pkcs10); DESTROY_IF(x509_ca_enc); DESTROY_IF(x509_ca_sig); DESTROY_IF(container); diff --git a/src/pki/est/est_tls.c b/src/pki/est/est_tls.c index 2546bb026..a10ed1416 100644 --- a/src/pki/est/est_tls.c +++ b/src/pki/est/est_tls.c @@ -218,7 +218,7 @@ METHOD(est_tls_t, request, bool, { return FALSE; } - DBG1(DBG_APP, "http: %B", &http); + DBG2(DBG_APP, "http request: %B", &http); /* send https request */ if (this->tls->write(this->tls, http.ptr, http.len) != http.len) @@ -237,7 +237,7 @@ METHOD(est_tls_t, request, bool, return FALSE; } response = chunk_create(buf, len); - DBG1(DBG_APP, "response: %B", &response); + DBG2(DBG_APP, "http response: %B", &response); if (!parse_http_header(&response, http_code, &content_len, &base64, retry_after)) diff --git a/src/pki/pki_cert.c b/src/pki/pki_cert.c index d3c49f2c8..202284e43 100644 --- a/src/pki/pki_cert.c +++ b/src/pki/pki_cert.c @@ -348,6 +348,7 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, cert_type = get_pki_cert_type(cert); if (cert_type != CERT_TYPE_ROOT_CA) { + certificate_t *cert_found = NULL; enumerator_t *certs; bool trusted; @@ -359,7 +360,8 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, /* establish trust relativ to root CA */ certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, KEY_ANY, cert->get_subject(cert), FALSE); - trusted = certs->enumerate(certs, &cert, NULL); + trusted = certs->enumerate(certs, &cert_found, NULL) && + (cert_found == cert); certs->destroy(certs); cert_type_count[cert_type]++; @@ -394,12 +396,12 @@ end: * 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, - mem_cred_t *creds) +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; @@ -413,6 +415,10 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form, 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); @@ -420,13 +426,17 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form, while (enumerator->enumerate(enumerator, &cert)) { x509_t *x509 = (x509_t*)cert; + certificate_t *cert_found = NULL; enumerator_t *certs; + chunk_t serial; time_t from, until; bool trusted, valid; if (!(x509->get_flags(x509) & X509_CA)) { - DBG1(DBG_APP, "certificate \"%Y\"", cert->get_subject(cert)); + DBG1(DBG_APP, "issued certificate \"%Y\"", cert->get_subject(cert)); + serial = x509->get_serial(x509); + DBG1(DBG_APP, " serial: %#B", &serial); if (stored) { @@ -434,20 +444,20 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form, continue; } - /* establish trust relativ to root CA */ - creds->add_cert(creds, FALSE, cert->get_ref(cert)); + /* 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, NULL); - valid = cert->get_validity(cert, NULL, &from, &until); + trusted = certs->enumerate(certs, &cert_found, NULL) && + (cert_found == cert); + certs->destroy(certs); - DBG1(DBG_APP, "certificate is %strusted, valid from %T until %T " - "(currently %svalid)", + 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 "); - certs->destroy(certs); - if (!cert->get_encoding(cert, form, &cert_encoding)) { DBG1(DBG_APP, "encoding certificate failed"); @@ -467,6 +477,8 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form, } enumerator->destroy(enumerator); container->destroy(container); + lib->credmgr->remove_set(lib->credmgr, &client_creds->set); + client_creds->destroy(client_creds); return stored; } diff --git a/src/pki/pki_cert.h b/src/pki/pki_cert.h index 853436c9e..69746e6b2 100644 --- a/src/pki/pki_cert.h +++ b/src/pki/pki_cert.h @@ -37,7 +37,6 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, * 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, - mem_cred_t *creds); +bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form); #endif /** PKI_CERT_H_ @}*/ From 976c74b772bbe41768a5d6effa26b6a9a54df8e3 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Sun, 21 Aug 2022 15:21:22 +0200 Subject: [PATCH 17/24] pki: --est adds --keyid and --certid options With the --keyid option private keys stored on a smartcard or in a TPM 2.0 can be used for public key based client authentication. With the --certid option the corresponding client certificate can reside on a smartcard or a TPM 2.0. --- src/pki/commands/est.c | 113 +++++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 21 deletions(-) diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c index 73e3ebc45..8f39b0f52 100644 --- a/src/pki/commands/est.c +++ b/src/pki/commands/est.c @@ -35,7 +35,7 @@ static int est() { char *arg, *url = NULL, *file = NULL, *error = NULL; char *client_cert_file = NULL, *client_key_file = NULL; - char *user_pass = NULL; + char *keyid = NULL, *certid = NULL, *user_pass = NULL; cred_encoding_type_t form = CERT_ASN1_DER; chunk_t pkcs10_encoding = chunk_empty, est_response = chunk_empty; certificate_t *pkcs10 = NULL, *client_cert = NULL, *cacert = NULL; @@ -77,9 +77,15 @@ static int est() case 'o': /* --cert */ client_cert_file = arg; continue; + case 'X': /* --certid */ + certid = arg; + continue; case 'k': /* --key */ client_key_file = arg; continue; + case 'x': /* --keyid */ + keyid = arg; + continue; case 'p': /* --userpass */ user_pass = arg; continue; @@ -116,9 +122,27 @@ static int est() goto usage; } - if (client_cert_file && !client_key_file) + if ((client_cert_file || certid) && !(client_key_file || keyid)) { - error = "--key is required if --cert is set"; + error = "--key or --keyid is required if --cert or --certid is set"; + goto usage; + } + + if (!(client_cert_file || certid) && (client_key_file || keyid)) + { + error = "--cert or --certid is required if --key or --keyid is set"; + goto usage; + } + + if (client_key_file && keyid) + { + error = "only one of --key or --keyid can be set"; + goto usage; + } + + if (client_cert_file && certid) + { + error = "only one of --cert or --certid can be set"; goto usage; } @@ -164,27 +188,72 @@ static int est() client_creds = mem_cred_create(); lib->credmgr->add_set(lib->credmgr, &client_creds->set); - if (client_cert_file) + /* re-enrollment with existing client certificate */ + if (client_cert_file || certid) { - /* load old client certificate */ - client_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_FROM_FILE, client_cert_file, BUILD_END); - if (!client_cert) + chunk_t handle; + + if (client_cert_file) /* loadold certificate file */ { - DBG1(DBG_APP, "could not load client cert file '%s'", client_cert_file); - goto end; + client_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_X509, + BUILD_FROM_FILE, client_cert_file, + BUILD_END); + if (!client_cert) + { + DBG1(DBG_APP, "loading client cert '%s' failed", + client_cert_file); + goto end; + } + } + else /* attach old certificate object */ + { + handle = chunk_from_hex(chunk_create(certid, strlen(certid)), NULL); + client_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_X509, + BUILD_PKCS11_KEYID, handle, + BUILD_END); + chunk_free(&handle); + if (!client_cert) + { + DBG1(DBG_APP, "attaching to certificate handle %s failed", + certid); + goto end; + } } client_creds->add_cert(client_creds, FALSE, client_cert); - /* load old client private key */ - client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, - BUILD_FROM_FILE, client_key_file, BUILD_END); - if (!client_key) + if (client_key_file) /* load old client private key file */ { - DBG1(DBG_APP, "parsing client private key failed"); - goto end; + client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, + KEY_ANY, + BUILD_FROM_FILE, client_key_file, + BUILD_END); + if (!client_key) + { + DBG1(DBG_APP, "loading client private key '%s' failed", + client_key_file); + goto end; + } + } + else /* attach old client private key object */ + { + + handle = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL); + client_key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, + KEY_ANY, + BUILD_PKCS11_KEYID, handle, + BUILD_END); + chunk_free(&handle); + if (!client_key) + { + DBG1(DBG_APP, "attaching to private key handle %s failed", + keyid); + goto end; + } } client_creds->add_key(client_creds, client_key); + est_op = EST_SIMPLE_REENROLL; } @@ -286,16 +355,18 @@ static void __attribute__ ((constructor))reg() command_register((command_t) { est, 'E', "est", "Enroll an X.509 certificate with an EST server", - {"--url url [--in file] [--cacert file]+ [--cert file --key file]", - "[-userpass username:password] [--interval time] [--maxpolltime time]", - "[--outform der|pem]"}, + {"--url url [--in file] [--cacert file]+ [-userpass username:password]", + "[--cert file|--certid hex --key file|--keyid hex] [--interval time]", + "[--maxpolltime time] [--outform der|pem]"}, { {"help", 'h', 0, "show usage information"}, {"url", 'u', 1, "URL of the EST server"}, {"in", 'i', 1, "PKCS#10 input file, default: stdin"}, {"cacert", 'c', 1, "CA certificate"}, - {"cert", 'o', 1, "Old certificate about to be renewed"}, - {"key", 'k', 1, "Old private key about to be replaced"}, + {"cert", 'o', 1, "old certificate about to be renewed"}, + {"certid", 'X', 1, "smartcard or TPM certificate object handle" }, + {"key", 'k', 1, "old private key about to be replaced"}, + {"keyid", 'x', 1, "smartcard or TPM private key object handle"}, {"userpass", 'p', 1, "username:password for http basic auth"}, {"interval", 't', 1, "poll interval, default: 60s"}, {"maxpolltime", 'm', 1, "maximum poll time, default: 0 (no limit)"}, From 784606a82778c3f41702d4f7b0a443e469e49cca Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Mon, 22 Aug 2022 12:42:09 +0200 Subject: [PATCH 18/24] pki: use libtls for pki --est|--estca --- src/pki/Makefile.am | 2 +- src/pki/commands/est.c | 13 ++--- src/pki/commands/estca.c | 78 ++++++++++++++++++++------ src/pki/commands/scep.c | 62 +++++++++++---------- src/pki/commands/scepca.c | 12 ++-- src/pki/est/est.c | 112 -------------------------------------- src/pki/est/est.h | 40 -------------- src/pki/est/est_tls.c | 16 ++++-- src/pki/est/est_tls.h | 14 ++++- src/pki/pki_cert.c | 70 ++++++++++++++++++------ 10 files changed, 183 insertions(+), 236 deletions(-) delete mode 100644 src/pki/est/est.c delete mode 100644 src/pki/est/est.h diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 22fb11fe8..b4c05318a 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -20,7 +20,7 @@ pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/self.c \ commands/signcrl.c \ commands/verify.c \ - est/est.h est/est.c est/est_tls.h est/est_tls.c \ + est/est_tls.h est/est_tls.c \ scep/scep.h scep/scep.c pki_LDADD = \ diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c index 8f39b0f52..67e011f96 100644 --- a/src/pki/commands/est.c +++ b/src/pki/commands/est.c @@ -19,7 +19,6 @@ #include "pki.h" #include "pki_cert.h" -#include "est/est.h" #include "est/est_tls.h" #include @@ -64,7 +63,7 @@ static int est() case 'i': /* --in */ file = arg; continue; - case 'c': /* --cacert */ + case 'C': /* --cacert */ cacert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); if (!cacert) @@ -74,7 +73,7 @@ static int est() } creds->add_cert(creds, TRUE, cacert); continue; - case 'o': /* --cert */ + case 'c': /* --cert */ client_cert_file = arg; continue; case 'X': /* --certid */ @@ -193,7 +192,7 @@ static int est() { chunk_t handle; - if (client_cert_file) /* loadold certificate file */ + if (client_cert_file) /* load old certificate file */ { client_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, @@ -362,9 +361,9 @@ static void __attribute__ ((constructor))reg() {"help", 'h', 0, "show usage information"}, {"url", 'u', 1, "URL of the EST server"}, {"in", 'i', 1, "PKCS#10 input file, default: stdin"}, - {"cacert", 'c', 1, "CA certificate"}, - {"cert", 'o', 1, "old certificate about to be renewed"}, - {"certid", 'X', 1, "smartcard or TPM certificate object handle" }, + {"cacert", 'C', 1, "CA certificate"}, + {"cert", 'c', 1, "old certificate about to be renewed"}, + {"certid", 'X', 1, "smartcard or TPM certificate object handle" }, {"key", 'k', 1, "old private key about to be replaced"}, {"keyid", 'x', 1, "smartcard or TPM private key object handle"}, {"userpass", 'p', 1, "username:password for http basic auth"}, diff --git a/src/pki/commands/estca.c b/src/pki/commands/estca.c index 02161ddd0..f6a584bb7 100644 --- a/src/pki/commands/estca.c +++ b/src/pki/commands/estca.c @@ -16,7 +16,7 @@ #include "pki.h" #include "pki_cert.h" -#include "est/est.h" +#include "est/est_tls.h" #include #include @@ -29,35 +29,55 @@ static int estca() { cred_encoding_type_t form = CERT_ASN1_DER; chunk_t est_response = chunk_empty; - char *arg, *url = NULL, *caout = NULL; + certificate_t *cacert; + mem_cred_t *creds = NULL; + est_tls_t *est_tls; + char *arg, *error = NULL, *url = NULL, *caout = NULL; bool force = FALSE, success; u_int http_code = 0; + status_t status = 1; + + /* initialize CA certificate storage */ + creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &creds->set); while (TRUE) { switch (command_getopt(&arg)) { - case 'h': - return command_usage(NULL); - case 'u': + case 'h': /* --help */ + goto usage; + case 'u': /* --url */ url = arg; continue; - case 'c': + case 'C': /* --cacert */ + cacert = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); + if (!cacert) + { + DBG1(DBG_APP, "could not load cacert file '%s'", arg); + goto err; + } + creds->add_cert(creds, TRUE, cacert); + continue; + case 'c': /* --caout */ caout = arg; continue; - case 'f': + case 'f': /* --outform */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { - return command_usage("invalid certificate output format"); + error ="invalid certificate output format"; + goto usage; } continue; - case 'F': + case 'F': /* --force */ force = TRUE; continue; case EOF: break; default: - return command_usage("invalid --estca option"); + error ="invalid --estca option"; + goto usage; } break; } @@ -67,17 +87,38 @@ static int estca() return command_usage("--url is required"); } - if (!est_https_request(url, EST_CACERTS, FALSE, chunk_empty, &est_response, - &http_code)) + est_tls = est_tls_create(url, NULL, NULL); + if (!est_tls) { - DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", http_code); - return 1; + DBG1(DBG_APP, "TLS connection to EST server was not established"); + goto err; } - success = pki_cert_extract_cacerts(est_response, caout, NULL, TRUE, form, - force); + success = est_tls->request(est_tls, EST_CACERTS, chunk_empty, &est_response, + &http_code, NULL); + est_tls->destroy(est_tls); + + if (!success) + { + DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); + goto err; + } + if (pki_cert_extract_cacerts(est_response, caout, NULL, TRUE, form, force)) + { + status = 0; + } + +err: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); chunk_free(&est_response); - return success ? 0 : 1; + return status; + +usage: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + + return command_usage(error); } /** @@ -88,10 +129,11 @@ static void __attribute__ ((constructor))reg() command_register((command_t) { estca, 'e', "estca", "get CA certificate[s] from a EST server", - {"--url url [--caout file] [--outform der|pem] [--force]"}, + {"--url url [--cacert file]+ [--caout file] [--outform der|pem] [--force]"}, { {"help", 'h', 0, "show usage information"}, {"url", 'u', 1, "URL of the SCEP server"}, + {"cacert", 'C', 1, "TLS CA certificate"}, {"caout", 'c', 1, "CA certificate [template]"}, {"outform", 'f', 1, "encoding of stored certificates, default: der"}, {"force", 'F', 0, "force overwrite of existing files"}, diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c index 500735d17..f7db744d5 100644 --- a/src/pki/commands/scep.c +++ b/src/pki/commands/scep.c @@ -39,7 +39,7 @@ static int scep() { char *arg, *url = NULL, *file = NULL, *dn = NULL, *error = NULL; char *ca_enc_file = NULL, *ca_sig_file = NULL; - char *old_cert_file = NULL, *old_key_file = NULL; + char *client_cert_file = NULL, *client_key_file = NULL; cred_encoding_type_t form = CERT_ASN1_DER; chunk_t scep_response = chunk_empty; chunk_t challenge_password = chunk_empty; @@ -95,33 +95,33 @@ static int scep() { switch (command_getopt(&arg)) { - case 'h': + case 'h': /* --help */ goto usage; - case 'u': + case 'u': /* --url */ url = arg; continue; - case 'i': + case 'i': /* --in */ file = arg; continue; - case 'd': + case 'd': /* --dn */ dn = arg; continue; - case 'a': + case 'a': /* --san */ san->insert_last(san, identification_create_from_string(arg)); continue; - case 'P': + case 'P': /* --profile */ cert_type = chunk_create(arg, strlen(arg)); continue; - case 'p': + case 'p': /* --password */ challenge_password = chunk_create(arg, strlen(arg)); continue; - case 'e': + case 'e': /* --cacert-enc */ ca_enc_file = arg; continue; - case 's': + case 's': /* --cacert-sig */ ca_sig_file = arg; continue; - case 'c': + case 'C': /* --cacert */ cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); if (!cert) @@ -131,13 +131,13 @@ static int scep() } creds->add_cert(creds, TRUE, cert); continue; - case 'o': - old_cert_file = arg; + case 'c': /* --cert */ + client_cert_file = arg; continue; - case 'k': - old_key_file = arg; + case 'k': /* --key */ + client_key_file = arg; continue; - case 'C': + case 'E': /* --cipher */ if (strcaseeq(arg, "des3")) { cipher = ENCR_3DES; @@ -154,14 +154,14 @@ static int scep() goto usage; } continue; - case 'g': + case 'g': /* --digest */ if (!enum_from_name(hash_algorithm_short_names, arg, &digest_alg)) { error = "invalid --digest type"; goto usage; } continue; - case 'R': + case 'R': /* --rsa-padding */ if (streq(arg, "pss")) { pss = TRUE; @@ -186,7 +186,7 @@ static int scep() case 'm': /* --maxpolltime */ max_poll_time = atoi(optarg); continue; - case 'f': + case 'f': /* --form */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { error = "invalid certificate output format"; @@ -220,7 +220,7 @@ static int scep() goto usage; } - if (old_cert_file && !old_key_file) + if (client_cert_file && !client_key_file) { error = "--oldkey is required if --oldcert is set"; goto usage; @@ -374,7 +374,7 @@ static int scep() } DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr); - if (old_cert_file) + if (client_cert_file) { /* check support of Renewal Operation */ if (!(caps_flags & SCEP_CAPS_RENEWAL)) @@ -390,19 +390,21 @@ static int scep() /* load old client certificate */ x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_FROM_FILE, old_cert_file, BUILD_END); + BUILD_FROM_FILE, client_cert_file, BUILD_END); if (!x509_signer) { - DBG1(DBG_APP, "could not load old cert file '%s'", old_cert_file); + DBG1(DBG_APP, "loading client cert file '%s' failed", + client_cert_file); goto err; } /* load old RSA private key */ priv_signer = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, - BUILD_FROM_FILE, old_key_file, BUILD_END); + BUILD_FROM_FILE, client_key_file, BUILD_END); if (!priv_signer) { - DBG1(DBG_APP, "parsing old private key failed"); + DBG1(DBG_APP, "loading client private key file '%s' failed", + client_key_file); goto err; } } @@ -456,7 +458,7 @@ static int scep() DBG1(DBG_APP, "could not load signature cacert file '%s'", ca_sig_file); goto end; } - creds->add_cert(creds, TRUE, x509_ca_sig->get_ref(x509_ca_sig)); + x509_ca_sig = creds->add_cert_ref(creds, TRUE, x509_ca_sig); /* build pkcs7 request */ pkcs7_req = scep_build_request(pkcs10_encoding, transID, scep_msg_type, @@ -642,10 +644,10 @@ static void __attribute__ ((constructor))reg() {"password", 'p', 1, "challengePassword to include in cert request"}, {"cacert-enc", 'e', 1, "CA certificate for encryption"}, {"cacert-sig", 's', 1, "CA certificate for signature verification"}, - {"cacert", 'c', 1, "Additional CA certificates"}, - {"oldcert", 'o', 1, "Old certificate about to be renewed"}, - {"oldkey", 'k', 1, "Old RSA private key about to be replaced"}, - {"cipher", 'C', 1, "encryption cipher, default: aes"}, + {"cacert", 'C', 1, "Additional CA certificates"}, + {"cert", 'c', 1, "Old certificate about to be renewed"}, + {"key", 'k', 1, "Old RSA private key about to be replaced"}, + {"cipher", 'E', 1, "encryption cipher, default: aes"}, {"digest", 'g', 1, "digest for signature creation, default: sha256"}, {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"}, {"interval", 't', 1, "poll interval, default: 60s"}, diff --git a/src/pki/commands/scepca.c b/src/pki/commands/scepca.c index 32df55de7..9ba72d41c 100644 --- a/src/pki/commands/scepca.c +++ b/src/pki/commands/scepca.c @@ -37,24 +37,24 @@ static int scepca() { switch (command_getopt(&arg)) { - case 'h': + case 'h': /* --help */ return command_usage(NULL); - case 'u': + case 'u': /* --url */ url = arg; continue; - case 'c': + case 'c': /* --caout */ caout = arg; continue; - case 'r': + case 'r': /* --raout */ raout = arg; continue; - case 'f': + case 'f': /* --form */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { return command_usage("invalid certificate output format"); } continue; - case 'F': + case 'F': /* --force */ force = TRUE; continue; case EOF: diff --git a/src/pki/est/est.c b/src/pki/est/est.c deleted file mode 100644 index c24bf5c3e..000000000 --- a/src/pki/est/est.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 . - * - * 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 - -#include "est.h" - -#define HTTP_CODE_OK 200 - -static const char *operations[] = { - "cacerts", - "simpleenroll", - "simplereenroll", - "fullcmc", - "serverkeygen", - "csrattrs" -}; - -static const char *request_types[] = { - "", - "application/pkcs10", - "application/pkcs10", - "application/pkcs7-mime", - "application/pkcs10", - "" -}; - -/** - * Send an EST request via HTTPS and wait for a response - */ -bool est_https_request(const char *url, est_op_t op, bool http_post, - chunk_t data, chunk_t *response, u_int *http_code) -{ - host_t *srcip = NULL; - char *complete_url = NULL; - status_t status; - - uint32_t http_timeout = lib->settings->get_time(lib->settings, - "%s.est.http_timeout", 30, lib->ns); - - char *http_bind = lib->settings->get_str(lib->settings, - "%s.est.http_bind", NULL, lib->ns); - - /* initialize response */ - *response = chunk_empty; - *http_code = 0; - - /* construct complete EST URL */ - if (asprintf(&complete_url, "%s/.well-known/est/%s", url, operations[op]) == -1) - { - DBG1(DBG_APP, "could not allocate complete_url string"); - return FALSE; - } - DBG2(DBG_APP, "sending EST request to '%s'", url); - - if (http_bind) - { - srcip = host_create_from_string(http_bind, 0); - } - - if (http_post) - { - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_timeout, - FETCH_REQUEST_DATA, data, - FETCH_REQUEST_TYPE, request_types[op], - FETCH_REQUEST_HEADER, "Expect:", - FETCH_SOURCEIP, srcip, - FETCH_RESPONSE_CODE, http_code, - FETCH_END); - } - else /* HTTP_GET */ - { - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_timeout, - FETCH_SOURCEIP, srcip, - FETCH_RESPONSE_CODE, http_code, - FETCH_END); - } - DESTROY_IF(srcip); - free(complete_url); - - if (status != SUCCESS) - { - return FALSE; - } - - if (*http_code == HTTP_CODE_OK) - { - chunk_t base64_response = *response; - - *response = chunk_from_base64(base64_response, NULL); - chunk_free(&base64_response); - } - - return TRUE; -} - diff --git a/src/pki/est/est.h b/src/pki/est/est.h deleted file mode 100644 index 3d9bdd3cf..000000000 --- a/src/pki/est/est.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 . - * - * 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. - */ - -#ifndef _EST_H -#define _EST_H - -#include - -/** - * EST (RFC 7030) Operations - */ -typedef enum { - EST_CACERTS, - EST_SIMPLE_ENROLL, - EST_SIMPLE_REENROLL, - EST_FULL_CMC, - EST_SERVER_KEYGEN, - EST_CSR_ATTRS -} est_op_t; - -/** - * Send an EST request via HTTPS and wait for a response - */ -bool est_https_request(const char *url, est_op_t op, bool http_post, - chunk_t data, chunk_t *response, u_int *http_code); - -#endif /* _EST_H */ diff --git a/src/pki/est/est_tls.c b/src/pki/est/est_tls.c index a10ed1416..ccc03280d 100644 --- a/src/pki/est/est_tls.c +++ b/src/pki/est/est_tls.c @@ -16,8 +16,6 @@ #define _GNU_SOURCE /* for asprintf() */ #include -#include -#include #include #include @@ -133,7 +131,8 @@ static chunk_t build_http_request(private_est_tls_t *this, est_op_t op, chunk_t len = asprintf(&http_header, "GET %s/.well-known/est/%s HTTP/1.1\r\n" "Host: %s\r\n" - "%s", + "%s" + "\r\n", this->http_path, operations[op], this->http_host, http_auth); if (len > 0) { @@ -154,6 +153,11 @@ static bool parse_http_header(chunk_t *in, u_int *http_code, u_int *content_len *content_len = 0; *base64 = FALSE; + if (retry_after) + { + *retry_after = 0; + } + /* Process HTTP protocol version and HTTP status code */ if (!fetchline(in, &line) || !extract_token(&version, ' ', &line) || !match("HTTP/1.1", &version) || sscanf(line.ptr, "%d", http_code) != 1) @@ -210,7 +214,11 @@ METHOD(est_tls_t, request, bool, /* initialize output variables */ *out = chunk_empty; *http_code = 0; - *retry_after = 0; + + if (retry_after) + { + *retry_after = 0; + } http = build_http_request(this, op, in); diff --git a/src/pki/est/est_tls.h b/src/pki/est/est_tls.h index d05831c71..ded2aeb19 100644 --- a/src/pki/est/est_tls.h +++ b/src/pki/est/est_tls.h @@ -22,8 +22,6 @@ #ifndef EST_TLS_H_ #define EST_TLS_H_ -#include "est.h" - #include #include @@ -32,6 +30,18 @@ typedef struct est_tls_t est_tls_t; +/** + * EST (RFC 7030) Operations + */ +typedef enum { + EST_CACERTS, + EST_SIMPLE_ENROLL, + EST_SIMPLE_REENROLL, + EST_FULL_CMC, + EST_SERVER_KEYGEN, + EST_CSR_ATTRS +} est_op_t; + /** * TLS Interface for sending and receiving HTTPS messages */ diff --git a/src/pki/pki_cert.c b/src/pki/pki_cert.c index 202284e43..34f8d4691 100644 --- a/src/pki/pki_cert.c +++ b/src/pki/pki_cert.c @@ -73,11 +73,15 @@ static bool print_cert_info(certificate_t *cert, pki_cert_type_t cert_type) 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, encoding = chunk_empty; + chunk_t cert_id, serial, 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)) { @@ -299,26 +303,53 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, { 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) { - /* trust in root CA has to be established manuallly */ - creds->add_cert(creds, TRUE, cert->get_ref(cert)); - - cert_type_count[cert_type]++; - 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 manuallly */ + 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, FALSE, path, form, + written = write_cert(cert, cert_type, trusted, path, form, force); free(path); } @@ -344,24 +375,31 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, while (enumerator->enumerate(enumerator, &cert)) { written = FALSE; + trusted = FALSE; cert_type = get_pki_cert_type(cert); if (cert_type != CERT_TYPE_ROOT_CA) { - certificate_t *cert_found = NULL; - enumerator_t *certs; - bool trusted; - if (!print_cert_info(cert, cert_type)) { break; } - /* establish trust relativ to root CA */ + /* establish trust relative to root CA */ 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); + 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]++; @@ -434,7 +472,7 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form) if (!(x509->get_flags(x509) & X509_CA)) { - DBG1(DBG_APP, "issued certificate \"%Y\"", cert->get_subject(cert)); + DBG1(DBG_APP, "Issued certificate \"%Y\"", cert->get_subject(cert)); serial = x509->get_serial(x509); DBG1(DBG_APP, " serial: %#B", &serial); @@ -453,7 +491,7 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form) certs->destroy(certs); valid = cert->get_validity(cert, NULL, &from, &until); - DBG1(DBG_APP, "issued certificate is %strusted, " + DBG1(DBG_APP, "Issued certificate is %strusted, " "valid from %T until %T (currently %svalid)", trusted ? "" : "not ", &from, FALSE, &until, FALSE, valid ? "" : "not "); From 9664ef4ba60fc303dd35d319222dda1dd1d2c8b9 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Mon, 22 Aug 2022 14:27:48 +0200 Subject: [PATCH 19/24] libtls: Fixed encoding of TLS 1.3 certificate extension --- src/libtls/tls_peer.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libtls/tls_peer.c b/src/libtls/tls_peer.c index 91f7efba8..edddf3262 100644 --- a/src/libtls/tls_peer.c +++ b/src/libtls/tls_peer.c @@ -938,7 +938,6 @@ static status_t process_certreq(private_tls_peer_t *this, bio_reader_t *reader) { /* certificate request context as described in RFC 8446, section 4.3.2 */ reader->read_data8(reader, &context); - reader->read_data16(reader, &ext); extensions = bio_reader_create(ext); while (extensions->remaining(extensions)) @@ -1532,11 +1531,12 @@ static status_t send_certificate(private_tls_peer_t *this, cert->get_subject(cert)); certs->write_data24(certs, data); free(data.ptr); - } - /* extensions see RFC 8446, section 4.4.2 */ - if (version_max > TLS_1_2) - { - certs->write_uint16(certs, 0); + + /* extensions see RFC 8446, section 4.4.2 */ + if (version_max > TLS_1_2) + { + certs->write_uint16(certs, 0); + } } } enumerator = this->peer_auth->create_enumerator(this->peer_auth); @@ -1550,6 +1550,12 @@ static status_t send_certificate(private_tls_peer_t *this, cert->get_subject(cert)); certs->write_data24(certs, data); free(data.ptr); + + /* extensions see RFC 8446, section 4.4.2 */ + if (version_max > TLS_1_2) + { + certs->write_uint16(certs, 0); + } } } } From 77a15f55beb2d41f9a0459d3c36ccb0b542cc215 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Mon, 22 Aug 2022 14:33:00 +0200 Subject: [PATCH 20/24] libtls: unit tests with crypto libs need additional plugins In order for libtls to run with the gcrypt libraryi, additionally the random, pem, gcm, hmac, kdf, x509, constraints, and the curve2519 plugins are needed. The botan library additionally need the hmac (for HMAC_MD5), x509 and constraints plugins. The wolfssl library additionally need the pkcs1, pkcs8, x509 and constraints plugins. --- scripts/test.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 33661a22c..36583be87 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -160,8 +160,8 @@ openssl*) fi ;; gcrypt) - CONFIG="--disable-defaults --enable-pki --enable-gcrypt --enable-pkcs1 --enable-pkcs8" - export TESTS_PLUGINS="test-vectors pkcs1 pkcs8 gcrypt!" + CONFIG="--disable-defaults --enable-pki --enable-gcrypt --enable-random --enable-pem --enable-pkcs1 --enable-pkcs8 --enable-gcm --enable-hmac --enable-kdf -enable-curve25519 --enable-x509 --enable-constraints" + export TESTS_PLUGINS="test-vectors random pem pkcs1 pkcs8 gcm hmac kdf curve25519 x509 constraints gcrypt!" if [ "$ID" = "ubuntu" -a "$VERSION_ID" = "20.04" ]; then DEPS="libgcrypt20-dev" else @@ -169,16 +169,16 @@ gcrypt) fi ;; botan) - CONFIG="--disable-defaults --enable-pki --enable-botan --enable-pem" - export TESTS_PLUGINS="test-vectors pem botan!" + CONFIG="--disable-defaults --enable-pki --enable-botan --enable-pem --enable-hmac --enable-x509 --enable-constraints" + export TESTS_PLUGINS="test-vectors pem hmac x509 constraints botan!" DEPS="" if test "$1" = "build-deps"; then build_botan fi ;; wolfssl) - CONFIG="--disable-defaults --enable-pki --enable-wolfssl --enable-pem" - export TESTS_PLUGINS="test-vectors pem wolfssl!" + CONFIG="--disable-defaults --enable-pki --enable-wolfssl --enable-pem --enable-pkcs1 --enable-pkcs8 --enable-x509 --enable-constraints" + export TESTS_PLUGINS="test-vectors pem pkcs1 pkcs8 x509 constraints wolfssl!" # build with custom options to enable all the features the plugin supports DEPS="" if test "$1" = "build-deps"; then From a41770330104d664d30d81930ccb9ba955472058 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Tue, 23 Aug 2022 23:52:39 +0200 Subject: [PATCH 21/24] libtls: enforce correct signature scheme for ECDSA keys --- src/libtls/tls_crypto.c | 65 +++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/src/libtls/tls_crypto.c b/src/libtls/tls_crypto.c index cb0e003c9..1b787ca4b 100644 --- a/src/libtls/tls_crypto.c +++ b/src/libtls/tls_crypto.c @@ -1789,11 +1789,12 @@ METHOD(tls_crypto_t, sign, bool, const chunk_t hashsig_def = chunk_from_chars(0x02, 0x01, 0x02, 0x03); signature_params_t *params; key_type_t type; - uint16_t scheme; + uint16_t scheme = 0, hashsig_scheme; bio_reader_t *reader; chunk_t sig; bool done = FALSE; + if (this->tls->get_version_max(this->tls) >= TLS_1_3) { chunk_t transcript_hash; @@ -1817,19 +1818,65 @@ METHOD(tls_crypto_t, sign, bool, { /* fallback if none given */ hashsig = hashsig_def; } + + /* Determine TLS signature scheme if unique */ type = key->get_type(key); + switch (type) + { + case KEY_ED448: + scheme = TLS_SIG_ED448; + break; + case KEY_ED25519: + scheme = TLS_SIG_ED25519; + break; + case KEY_ECDSA: + switch (key->get_keysize(key)) + { + case 256: + scheme = TLS_SIG_ECDSA_SHA256; + break; + case 384: + scheme = TLS_SIG_ECDSA_SHA384; + break; + case 521: + scheme = TLS_SIG_ECDSA_SHA512; + break; + default: + DBG1(DBG_TLS, "%d bit ECDSA private key size not supported", + key->get_keysize(key)); + return FALSE; + } + break; + case KEY_RSA: + /* Several TLS signature schemes possible, select later on */ + break; + default: + DBG1(DBG_TLS, "%N private key type not supported", + key_type_names, type); + return FALSE; + } + reader = bio_reader_create(hashsig); while (reader->remaining(reader) >= 2) { - if (reader->read_uint16(reader, &scheme)) + if (reader->read_uint16(reader, &hashsig_scheme)) { - params = params_for_scheme(scheme, TRUE); - if (params && - type == key_type_from_signature_scheme(params->scheme) && - key->sign(key, params->scheme, params->params, data, &sig)) + params = params_for_scheme(hashsig_scheme, TRUE); + + /** + * All key types except RSA have a single fixed signature scheme + * RSA signature schemes are tried until sign() is successful + */ + if (params && (scheme == hashsig_scheme || + (!scheme && + type == key_type_from_signature_scheme(params->scheme)))) { - done = TRUE; - break; + if (key->sign(key, params->scheme, params->params, data, &sig)) + { + done = TRUE; + scheme = hashsig_scheme; + break; + } } } } @@ -1839,7 +1886,7 @@ METHOD(tls_crypto_t, sign, bool, DBG1(DBG_TLS, "none of the proposed hash/sig algorithms supported"); return FALSE; } - DBG2(DBG_TLS, "created signature with %N", tls_signature_scheme_names, + DBG1(DBG_TLS, "created signature with %N", tls_signature_scheme_names, scheme); writer->write_uint16(writer, scheme); writer->write_data16(writer, sig); From 63fd718915b5d246dcc5560382db0c30de309040 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Wed, 24 Aug 2022 12:01:51 +0200 Subject: [PATCH 22/24] libtls: call create_public_enumerator() with key_type --- src/libtls/tls_server.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libtls/tls_server.c b/src/libtls/tls_server.c index 97c4c40da..f1119884c 100644 --- a/src/libtls/tls_server.c +++ b/src/libtls/tls_server.c @@ -176,14 +176,21 @@ public_key_t *tls_find_public_key(auth_cfg_t *peer_auth, identification_t *id) { public_key_t *public = NULL, *current; certificate_t *cert, *found; + key_type_t key_type = KEY_ANY; enumerator_t *enumerator; auth_cfg_t *auth; cert = peer_auth->get(peer_auth, AUTH_HELPER_SUBJECT_CERT); if (cert) { + public = cert->get_public_key(cert); + if (public) + { + key_type = public->get_type(public); + public->destroy(public); + } enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, - KEY_ANY, id, peer_auth, TRUE); + key_type, id, peer_auth, TRUE); while (enumerator->enumerate(enumerator, ¤t, &auth)) { found = auth->get(auth, AUTH_RULE_SUBJECT_CERT); From 52a3c3662d9bdcc945798e093f3ee64ffd32ffb5 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Wed, 24 Aug 2022 15:06:12 +0200 Subject: [PATCH 23/24] libtls: the signature unit tests use scheme-specific credentials --- src/libtls/tests/suites/test_socket.c | 188 +++++++++++++++++++++++--- 1 file changed, 166 insertions(+), 22 deletions(-) diff --git a/src/libtls/tests/suites/test_socket.c b/src/libtls/tests/suites/test_socket.c index e410ffd28..a9f44c07c 100644 --- a/src/libtls/tests/suites/test_socket.c +++ b/src/libtls/tests/suites/test_socket.c @@ -112,9 +112,25 @@ static char rsa[] = { }; /** - * ECDSA private key + * ECDSA256 private key + * pki --gen --type ecdsa --size 256 */ -static char ecdsa[] = { +static char ecdsa256[] = { + 0x30,0x77,0x02,0x01,0x01,0x04,0x20,0x2d,0x01,0x7e,0x5b,0x4a,0x7d,0x78,0xe9,0x23, + 0xeb,0xb2,0xac,0x4c,0xf1,0x28,0x3b,0xfa,0x1d,0xa9,0x08,0x5c,0xd0,0x60,0x2a,0xa6, + 0x54,0xd3,0x94,0xd4,0x05,0xa1,0x04,0xa0,0x0a,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d, + 0x03,0x01,0x07,0xa1,0x44,0x03,0x42,0x00,0x04,0x15,0x9c,0xbe,0xdb,0x54,0xa6,0xe7, + 0x7f,0x76,0x05,0xa6,0x9d,0xf3,0x41,0x38,0x43,0x98,0xe9,0x0b,0x2b,0x8b,0x02,0xb4, + 0x04,0x9b,0x61,0x84,0x65,0x63,0x3b,0x08,0xb2,0x4b,0x1e,0xd0,0x32,0x20,0xe9,0xfc, + 0x62,0xa7,0xd0,0x71,0x9e,0xe9,0xf9,0x2d,0x91,0xb8,0xf2,0xa3,0x4d,0x8a,0x78,0xb2, + 0x0b,0xfb,0x59,0x7c,0x40,0xbd,0xaf,0xa2,0x07 +}; + +/** + * ECDSA384 private key + * pki --gen --type ecdsa --size 384 + */ +static char ecdsa384[] = { 0x30,0x81,0xa4,0x02,0x01,0x01,0x04,0x30,0xc0,0x1f,0xfd,0x65,0xc6,0xc4,0x4c,0xb8, 0xff,0x56,0x08,0xb5,0xbd,0xb8,0xf5,0x93,0xf7,0x51,0x0e,0x92,0x1f,0x06,0xbf,0xa6, 0xd9,0x1d,0xae,0xa3,0x16,0x0d,0x0f,0xc9,0xd5,0x97,0x90,0x46,0xf1,0x98,0xa8,0x18, @@ -128,6 +144,27 @@ static char ecdsa[] = { 0xb1,0x47,0xc8,0xf6,0x18,0xbb,0x97, }; +/** + * ECDSA521 private key + * pki --gen --type ecdsa --size 521 + */ +static char ecdsa521[] = { + 0X30,0x81,0xdc,0x02,0x01,0x01,0x04,0x42,0x01,0x88,0x0f,0x17,0x00,0x2c,0x62,0x5c, + 0x3e,0xed,0xe6,0xc8,0x6a,0x12,0x8e,0x09,0x8e,0x4b,0x41,0x8f,0x1a,0xbc,0xf3,0xa4, + 0xa6,0xcb,0xd4,0xa5,0x45,0x40,0xc8,0x29,0xc8,0x72,0x49,0x0a,0x04,0x9d,0xb2,0x02, + 0xc7,0x6a,0x98,0x3c,0xc9,0x4d,0x87,0x30,0x8b,0x17,0xd8,0x94,0x3d,0x8b,0x88,0xc9, + 0xe5,0x17,0x22,0x73,0x41,0x90,0x6d,0x52,0xee,0x11,0xa0,0x07,0x06,0x05,0x2b,0x81, + 0X04,0x00,0x23,0xa1,0x81,0x89,0x03,0x81,0x86,0x00,0x04,0x01,0x9a,0x71,0x4e,0x04, + 0X42,0xa7,0xdd,0x7c,0xe6,0xdb,0x0d,0x9d,0xe9,0xde,0x21,0x42,0x0b,0x56,0x90,0x7b, + 0X5b,0xbc,0x33,0xdf,0x79,0x9a,0xb8,0xf0,0x79,0xad,0x78,0xe2,0x77,0xee,0x62,0x4b, + 0Xc5,0x18,0xb8,0x7d,0x86,0x0a,0xb9,0xb4,0x24,0x3f,0x80,0xcf,0x34,0xfd,0x68,0xd0, + 0X90,0xd0,0x66,0xe7,0x79,0x30,0x13,0xc7,0x55,0xb3,0x74,0xf7,0xd3,0x01,0x03,0x0c, + 0X46,0x89,0xbf,0x7b,0xd6,0x26,0xe9,0xf6,0x50,0x35,0x7c,0x81,0x6f,0xb7,0xa5,0x62, + 0Xa9,0xc9,0xba,0x45,0xd7,0xc2,0x09,0xfd,0xc5,0x0b,0x76,0x75,0xe7,0x47,0xa6,0x70, + 0X09,0x16,0x14,0xc0,0x7e,0x09,0x3d,0xde,0xd4,0x79,0xa3,0xb6,0x95,0x2a,0xaa,0x5b, + 0Xdc,0xd5,0xab,0xdc,0x8a,0xd9,0xf3,0x37,0x96,0xaa,0x84,0xfc,0xae,0x94,0xea +}; + /** * Ed25519 private key * pki --gen --type ed25519 @@ -207,10 +244,41 @@ static char rsa_crt[] = { }; /** - * TLS certificate for ECDSA key - * pki --self --in ecdsa.key --dn "C=CH, O=strongSwan, CN=tls-ecdsa" --san 127.0.0.1 + * TLS certificate for ECDSA256 key + * pki --self --in ecdsa256.key --dn "C=CH, O=strongSwan, CN=tls-ecdsa" --san 127.0.0.1 */ -static char ecdsa_crt[] = { +static char ecdsa256_crt[] = { + 0x30,0x82,0x01,0x74,0x30,0x82,0x01,0x1b,0xa0,0x03,0x02,0x01,0x02,0x02,0x08,0x1e, + 0x80,0xe3,0xbb,0xf4,0x6f,0xc5,0xab,0x30,0x0a,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d, + 0x04,0x03,0x02,0x30,0x36,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, + 0x43,0x48,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x13,0x0a,0x73,0x74,0x72, + 0x6f,0x6e,0x67,0x53,0x77,0x61,0x6e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x03, + 0x13,0x09,0x74,0x6c,0x73,0x2d,0x65,0x63,0x64,0x73,0x61,0x30,0x1e,0x17,0x0d,0x32, + 0x32,0x30,0x38,0x32,0x33,0x30,0x39,0x31,0x33,0x35,0x34,0x5a,0x17,0x0d,0x32,0x35, + 0x30,0x38,0x32,0x32,0x30,0x39,0x31,0x33,0x35,0x34,0x5a,0x30,0x36,0x31,0x0b,0x30, + 0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x43,0x48,0x31,0x13,0x30,0x11,0x06,0x03, + 0x55,0x04,0x0a,0x13,0x0a,0x73,0x74,0x72,0x6f,0x6e,0x67,0x53,0x77,0x61,0x6e,0x31, + 0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x03,0x13,0x09,0x74,0x6c,0x73,0x2d,0x65,0x63, + 0x64,0x73,0x61,0x30,0x59,0x30,0x13,0x06,0x07,0x2a,0x86,0x48,0xce,0x3d,0x02,0x01, + 0x06,0x08,0x2a,0x86,0x48,0xce,0x3d,0x03,0x01,0x07,0x03,0x42,0x00,0x04,0x15,0x9c, + 0xbe,0xdb,0x54,0xa6,0xe7,0x7f,0x76,0x05,0xa6,0x9d,0xf3,0x41,0x38,0x43,0x98,0xe9, + 0x0b,0x2b,0x8b,0x02,0xb4,0x04,0x9b,0x61,0x84,0x65,0x63,0x3b,0x08,0xb2,0x4b,0x1e, + 0xd0,0x32,0x20,0xe9,0xfc,0x62,0xa7,0xd0,0x71,0x9e,0xe9,0xf9,0x2d,0x91,0xb8,0xf2, + 0xa3,0x4d,0x8a,0x78,0xb2,0x0b,0xfb,0x59,0x7c,0x40,0xbd,0xaf,0xa2,0x07,0xa3,0x13, + 0x30,0x11,0x30,0x0f,0x06,0x03,0x55,0x1d,0x11,0x04,0x08,0x30,0x06,0x87,0x04,0x7f, + 0x00,0x00,0x01,0x30,0x0a,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d,0x04,0x03,0x02,0x03, + 0x47,0x00,0x30,0x44,0x02,0x20,0x3d,0xa0,0x7e,0xff,0xfe,0x38,0xa4,0xfc,0x28,0x7b, + 0x6a,0x63,0xea,0xb9,0x04,0x11,0x63,0x98,0x25,0x1f,0x7f,0xc6,0xbc,0xe7,0x2e,0x53, + 0xbf,0x4a,0x7c,0x73,0xe9,0xe1,0x02,0x20,0x28,0xec,0x8b,0x84,0xa5,0xa3,0xd1,0xac, + 0x92,0x0b,0x9d,0xdc,0xa5,0x59,0xe8,0x64,0xb9,0xd1,0x66,0xe9,0x23,0xca,0x3b,0xee, + 0xc8,0x0e,0x08,0x4e,0x8f,0xc7,0xed,0x11 +}; + +/** + * TLS certificate for ECDSA384 key + * pki --self --in ecdsa384.key --dn "C=CH, O=strongSwan, CN=tls-ecdsa" --san 127.0.0.1 + */ +static char ecdsa384_crt[] = { 0x30,0x82,0x01,0xb1,0x30,0x82,0x01,0x38,0xa0,0x03,0x02,0x01,0x02,0x02,0x08,0x77, 0x8f,0x61,0x26,0xa2,0xae,0xe8,0x6c,0x30,0x0a,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d, 0x04,0x03,0x03,0x30,0x36,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, @@ -241,6 +309,46 @@ static char ecdsa_crt[] = { 0xac,0x36,0x08,0x14,0x29, }; +/** + * TLS certificate for ECDSA521 key + * pki --self --in ecdsa521.key --dn "C=CH, O=strongSwan, CN=tls-ecdsa" --san 127.0.0.1 + */ +static char ecdsa521_crt[] = { + 0x30,0x82,0x01,0xfd,0x30,0x82,0x01,0x5e,0xa0,0x03,0x02,0x01,0x02,0x02,0x08,0x6c, + 0x72,0xcb,0x98,0xc7,0x4c,0x46,0xf7,0x30,0x0a,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d, + 0x04,0x03,0x04,0x30,0x36,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02, + 0x43,0x48,0x31,0x13,0x30,0x11,0x06,0x03,0x55,0x04,0x0a,0x13,0x0a,0x73,0x74,0x72, + 0x6f,0x6e,0x67,0x53,0x77,0x61,0x6e,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x03, + 0x13,0x09,0x74,0x6c,0x73,0x2d,0x65,0x63,0x64,0x73,0x61,0x30,0x1e,0x17,0x0d,0x32, + 0x32,0x30,0x38,0x32,0x34,0x31,0x32,0x35,0x33,0x31,0x36,0x5a,0x17,0x0d,0x32,0x35, + 0x30,0x38,0x32,0x33,0x31,0x32,0x35,0x33,0x31,0x36,0x5a,0x30,0x36,0x31,0x0b,0x30, + 0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x43,0x48,0x31,0x13,0x30,0x11,0x06,0x03, + 0x55,0x04,0x0a,0x13,0x0a,0x73,0x74,0x72,0x6f,0x6e,0x67,0x53,0x77,0x61,0x6e,0x31, + 0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x03,0x13,0x09,0x74,0x6c,0x73,0x2d,0x65,0x63, + 0x64,0x73,0x61,0x30,0x81,0x9b,0x30,0x10,0x06,0x07,0x2a,0x86,0x48,0xce,0x3d,0x02, + 0x01,0x06,0x05,0x2b,0x81,0x04,0x00,0x23,0x03,0x81,0x86,0x00,0x04,0x01,0x9a,0x71, + 0x4e,0x04,0x42,0xa7,0xdd,0x7c,0xe6,0xdb,0x0d,0x9d,0xe9,0xde,0x21,0x42,0x0b,0x56, + 0x90,0x7b,0x5b,0xbc,0x33,0xdf,0x79,0x9a,0xb8,0xf0,0x79,0xad,0x78,0xe2,0x77,0xee, + 0x62,0x4b,0xc5,0x18,0xb8,0x7d,0x86,0x0a,0xb9,0xb4,0x24,0x3f,0x80,0xcf,0x34,0xfd, + 0x68,0xd0,0x90,0xd0,0x66,0xe7,0x79,0x30,0x13,0xc7,0x55,0xb3,0x74,0xf7,0xd3,0x01, + 0x03,0x0c,0x46,0x89,0xbf,0x7b,0xd6,0x26,0xe9,0xf6,0x50,0x35,0x7c,0x81,0x6f,0xb7, + 0xa5,0x62,0xa9,0xc9,0xba,0x45,0xd7,0xc2,0x09,0xfd,0xc5,0x0b,0x76,0x75,0xe7,0x47, + 0xa6,0x70,0x09,0x16,0x14,0xc0,0x7e,0x09,0x3d,0xde,0xd4,0x79,0xa3,0xb6,0x95,0x2a, + 0xaa,0x5b,0xdc,0xd5,0xab,0xdc,0x8a,0xd9,0xf3,0x37,0x96,0xaa,0x84,0xfc,0xae,0x94, + 0xea,0xa3,0x13,0x30,0x11,0x30,0x0f,0x06,0x03,0x55,0x1d,0x11,0x04,0x08,0x30,0x06, + 0x87,0x04,0x7f,0x00,0x00,0x01,0x30,0x0a,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d,0x04, + 0x03,0x04,0x03,0x81,0x8c,0x00,0x30,0x81,0x88,0x02,0x42,0x01,0x1f,0x37,0x05,0xa6, + 0x91,0x84,0x36,0x0f,0x63,0xf1,0x42,0x84,0xc2,0xfc,0xd2,0x4d,0x1e,0x7a,0xfe,0xe9, + 0x22,0xc7,0xcf,0x12,0x37,0xdd,0xe7,0xc1,0xce,0xb7,0x92,0x5b,0x15,0xea,0xe5,0x81, + 0x25,0x48,0x29,0x22,0xe2,0xe3,0x3f,0xbb,0xa7,0x3d,0xac,0xa7,0x29,0x0e,0xa6,0xcb, + 0xf9,0x6a,0xa8,0x3a,0x33,0x2b,0xbd,0xaa,0x7b,0x81,0x7d,0x87,0x29,0x02,0x42,0x00, + 0xcc,0x80,0xb7,0x7c,0xf3,0x04,0x1f,0x0c,0x6f,0xef,0xb3,0x4c,0x7b,0x2d,0x54,0x1f, + 0x3d,0xb4,0xdd,0x6f,0x7c,0x2a,0xdb,0xfa,0x3e,0x47,0xa9,0x3a,0xe1,0x68,0x96,0xff, + 0xc3,0x42,0xa1,0xd1,0xc3,0xe4,0x03,0xa7,0x33,0x82,0xb2,0x76,0x12,0xeb,0xaa,0xed, + 0x00,0x3f,0x1f,0x4a,0xd5,0x1c,0x63,0x50,0xd0,0xae,0xa5,0x58,0xc2,0x16,0x56,0xcd, + 0x9b +}; + /** * TLS certificate for Ed25519 key * pki --self --in ed25519.key --dn "C=CH, O=strongSwan, CN=tls-ed25519" \ @@ -313,24 +421,13 @@ static void setup_credentials(chunk_t key_data, chunk_t cert_data) lib->credmgr->add_set(lib->credmgr, &creds->set); } - key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, - BUILD_BLOB, chunk_from_thing(rsa), BUILD_END); - if (key) - { - creds->add_key(creds, key); - } key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_ANY, BUILD_BLOB, key_data, BUILD_END); if (key) { creds->add_key(creds, key); } - cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_BLOB, chunk_from_thing(rsa_crt), BUILD_END); - if (cert) - { - creds->add_cert(creds, TRUE, cert); - } + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_BLOB, cert_data, BUILD_END); if (cert) @@ -339,21 +436,31 @@ static void setup_credentials(chunk_t key_data, chunk_t cert_data) } } +START_SETUP(setup_rsa_creds) +{ + setup_credentials(chunk_from_thing(rsa), chunk_from_thing(rsa_crt)); +} +END_SETUP + START_SETUP(setup_ed25519_creds) { + setup_credentials(chunk_from_thing(rsa), chunk_from_thing(rsa_crt)); setup_credentials(chunk_from_thing(ed25519), chunk_from_thing(ed25519_crt)); } END_SETUP START_SETUP(setup_ed448_creds) { + + setup_credentials(chunk_from_thing(rsa), chunk_from_thing(rsa_crt)); setup_credentials(chunk_from_thing(ed448), chunk_from_thing(ed448_crt)); } END_SETUP START_SETUP(setup_all_creds) { - setup_credentials(chunk_from_thing(ecdsa), chunk_from_thing(ecdsa_crt)); + setup_credentials(chunk_from_thing(rsa), chunk_from_thing(rsa_crt)); + setup_credentials(chunk_from_thing(ecdsa256), chunk_from_thing(ecdsa256_crt)); setup_credentials(chunk_from_thing(ed25519), chunk_from_thing(ed25519_crt)); setup_credentials(chunk_from_thing(ed448), chunk_from_thing(ed448_crt)); } @@ -600,20 +707,57 @@ static void test_tls_ke_groups(tls_version_t version, uint16_t port, bool cauth, static void test_tls_signature_schemes(tls_version_t version, uint16_t port, bool cauth, u_int i) { + chunk_t key_data = chunk_empty, cert_data = chunk_empty; tls_signature_scheme_t *schemes; char signature[128]; int count; + /* config used for both TLS server and client */ server_config = create_config(version, port, cauth); + /* start TLS server */ start_echo_server(server_config); + /* configure signature scheme */ count = tls_crypto_get_supported_signatures(version, &schemes); ck_assert(i < count); snprintf(signature, sizeof(signature), "%N", tls_signature_scheme_names, schemes[i]); lib->settings->set_str(lib->settings, "%s.tls.signature", signature, lib->ns); + /* depending on the signature scheme load a second set of credentials */ + switch (schemes[i]) + { + case TLS_SIG_ECDSA_SHA256: + case TLS_SIG_ECDSA_SHA1: + key_data = chunk_from_thing(ecdsa256); + cert_data = chunk_from_thing(ecdsa256_crt); + break; + case TLS_SIG_ECDSA_SHA384: + key_data = chunk_from_thing(ecdsa384); + cert_data = chunk_from_thing(ecdsa384_crt); + break; + case TLS_SIG_ECDSA_SHA512: + key_data = chunk_from_thing(ecdsa521); + cert_data = chunk_from_thing(ecdsa521_crt); + break; + case TLS_SIG_ED25519: + key_data = chunk_from_thing(ed25519); + cert_data = chunk_from_thing(ed25519_crt); + break; + case TLS_SIG_ED448: + key_data = chunk_from_thing(ed448); + cert_data = chunk_from_thing(ed448_crt); + break; + default: + break; + } + if (key_data.len > 0 || cert_data.len > 0) + { + setup_credentials(key_data, cert_data); + } + + /* run TLS client */ run_echo_client(server_config); free(schemes); @@ -793,25 +937,25 @@ Suite *socket_suite_create() suite_add_tcase(s, tc); tc = tcase_create("TLS 1.3/signature schemes"); - tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_rsa_creds, teardown_creds); tcase_add_loop_test(tc, test_tls13_signature_schemes, 0, tls_crypto_get_supported_signatures(TLS_1_3, NULL)); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.2/signature schemes"); - tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_rsa_creds, teardown_creds); tcase_add_loop_test(tc, test_tls12_signature_schemes, 0, tls_crypto_get_supported_signatures(TLS_1_2, NULL)); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.1/signature schemes"); - tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_rsa_creds, teardown_creds); tcase_add_loop_test(tc, test_tls11_signature_schemes, 0, tls_crypto_get_supported_signatures(TLS_1_1, NULL)); suite_add_tcase(s, tc); tc = tcase_create("TLS 1.0/signature schemes"); - tcase_add_checked_fixture(tc, setup_all_creds, teardown_creds); + tcase_add_checked_fixture(tc, setup_rsa_creds, teardown_creds); tcase_add_loop_test(tc, test_tls10_signature_schemes, 0, tls_crypto_get_supported_signatures(TLS_1_0, NULL)); suite_add_tcase(s, tc); From 6e860fb07c8f2e8e29bca8e54396766ca9892a85 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Thu, 25 Aug 2022 10:48:55 +0200 Subject: [PATCH 24/24] leak_detective: Whitelist botan_privkey_load_rsa_pkcs1() --- src/libstrongswan/utils/leak_detective.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index 96b4ec279..20e280728 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -661,6 +661,7 @@ static char *whitelist[] = { "botan_privkey_create", "botan_privkey_load_ecdh", "botan_privkey_load", + "botan_privkey_load_rsa_pkcs1", "botan_kdf", };