--- /dev/null
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+
+#include "pki.h"
+#include "scep/scep.h"
+
+#include <credentials/certificates/certificate.h>
+#include <credentials/certificates/x509.h>
+#include <credentials/sets/mem_cred.h>
+#include <asn1/asn1.h>
+
+/* 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"},
+ }
+ });
+}
/*
- * 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
#include <library.h>
#include <utils/debug.h>
+#include <utils/lexparser.h>
#include <asn1/asn1.h>
#include <asn1/asn1_parser.h>
#include <asn1/oid.h>
#include "scep.h"
+static const char *operations[] = {
+ "PKIOperation",
+ "GetCACert",
+ "GetCACaps"
+};
+
static const char *pkiStatus_values[] = { "0", "2", "3" };
static const char *pkiStatus_names[] = {
"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 */
}
/**
- * 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;
}
/**
int len;
status_t status;
char *complete_url = NULL;
+ const char *operation;
host_t *srcip = NULL;
/* initialize response */
{
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;
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);
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