]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
pki: Enroll an X.509 certificate with a SCEP server
authorAndreas Steffen <andreas.steffen@strongswan.org>
Mon, 1 Aug 2022 09:57:41 +0000 (11:57 +0200)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Wed, 24 Aug 2022 18:46:44 +0000 (20:46 +0200)
conf/options/pki.opt
src/pki/Makefile.am
src/pki/command.h
src/pki/commands/scep.c [new file with mode: 0644]
src/pki/scep/scep.c
src/pki/scep/scep.h

index c57dcc8c5d52cf9de8d3de85b4a1792ca3beaf33..d6d160fa0600ad1aa3e775235d8f0b9a33626ac2 100644 (file)
@@ -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).
index fcced120e7eac8fb8a1553c6fcb6199de6d98e50..172cfcdc3c60f2f188bc4141cb98f9e8a50f4b61 100644 (file)
@@ -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 \
index bdb402a86d6b4fcc1a9566dd52853d453a64cb05..876a64b99c217fa4732afb53dd94e5920cb83e25 100644 (file)
@@ -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 (file)
index 0000000..5815cf2
--- /dev/null
@@ -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 <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"},
+               }
+       });
+}
index 24fa93b20a9025da4ef24d9f99ecf085b654e62e..7d6fafa10d2184a9b461e0c8db56ddfb820e0702 100644 (file)
@@ -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 <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[] = {
@@ -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
index c9e97b1a0e4246bb694076d767e63753bb09daa1..bfb49a4d16666a66d56c9ac4f3b8e6c063473968 100644 (file)
@@ -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
 /* 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 */