From: Andreas Steffen Date: Mon, 1 Aug 2022 09:57:41 +0000 (+0200) Subject: pki: Enroll an X.509 certificate with a SCEP server X-Git-Tag: 5.9.8dr1~2^2~21 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7c7a5a0260ca1f8897ea493ff1b6b111cfd4f5e2;p=thirdparty%2Fstrongswan.git pki: Enroll an X.509 certificate with a SCEP server --- diff --git a/conf/options/pki.opt b/conf/options/pki.opt index c57dcc8c5d..d6d160fa06 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 fcced120e7..172cfcdc3c 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 bdb402a86d..876a64b99c 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 0000000000..5815cf23a5 --- /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 24fa93b20a..7d6fafa10d 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 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 transaction ID is the fingerprint in hex format */ + *transId = chunk_to_hex(digest, NULL, TRUE); + + /** + * 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; + *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; } - 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"; - - if (http_params->get_request) - { - char *escaped_req = escape_http_request(msg); + case SCEP_PKI_OPERATION: + default: + 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"; - - /* form complete url */ - len = strlen(url) + 11 + strlen(operation) + 1; - complete_url = malloc(len); - snprintf(complete_url, len, "%s?operation=%s", url, operation); + } + 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); - 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 c9e97b1a0e..bfb49a4d16 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 */