From 685127394468151308299327e62f7806e7c46025 Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Fri, 29 Jul 2022 06:48:41 +0200 Subject: [PATCH] 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 1153794cdd..fcced120e7 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 f9be176ea1..bdb402a86d 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 0000000000..a443155f37 --- /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 0000000000..24fa93b20a --- /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 0000000000..c9e97b1a0e --- /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 */ -- 2.47.2