]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
pki: pki --req can use old certreq as template
authorAndreas Steffen <andreas.steffen@strongswan.org>
Mon, 29 Aug 2022 08:34:58 +0000 (10:34 +0200)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Wed, 31 Aug 2022 13:10:34 +0000 (15:10 +0200)
When an X.509 certificate has to be renewed it is helpful to use
the old PKCS#10 certificate request as a template, so that the
distinguishedName (DN), the subjectAlternativeName (SAN) and
a certificate profile name don't have to be typed-in again.

The old public key in the existing certreq is replaced with the
new key and the signature is re-generated using the new private key.

src/libstrongswan/credentials/certificates/pkcs10.h
src/libstrongswan/plugins/x509/x509_pkcs10.c
src/pki/commands/req.c
src/pki/man/pki---req.1.in

index ab5e3cdaad85f228134d29110dceab4ea4fdc25e..2151fbd759e7f1a40648ce9c7649b6152573b3d5 100644 (file)
@@ -62,6 +62,16 @@ struct pkcs10_t {
         * @return                      enumerator over subjectAltNames as identification_t*
         */
        enumerator_t* (*create_subjectAltName_enumerator)(pkcs10_t *this);
+
+    /**
+     * Replace the public key and private key signature
+     *
+     * @param private   new private key to be used
+     * @param scheme    signature scheme
+     * @param password  optionally set new password
+     */
+    certificate_t* (*replace_key)(pkcs10_t *this, private_key_t *private,
+                    signature_params_t *scheme, chunk_t password);
 };
 
 #endif /** PKCS10_H_ @}*/
index cee518b51a30bf547957acc963dca9a0ea8b5069..549f74dc4adc2c805f67676ca46e88af842d4eec 100644 (file)
@@ -270,6 +270,125 @@ METHOD(pkcs10_t, create_subjectAltName_enumerator, enumerator_t*,
        return this->subjectAltNames->create_enumerator(this->subjectAltNames);
 }
 
+/**
+ * Generate and sign a new certificate request
+ */
+static bool generate(private_x509_pkcs10_t *cert, private_key_t *sign_key,
+                                        int digest_alg)
+{
+       chunk_t key_info, subjectAltNames, attributes;
+       chunk_t extensionRequest  = chunk_empty, certTypeExt = chunk_empty;
+       chunk_t challengePassword = chunk_empty, sig_scheme = chunk_empty;
+       identification_t *subject;
+
+       subject = cert->subject;
+       cert->public_key = sign_key->get_public_key(sign_key);
+
+       /* select signature scheme, if not already specified */
+       if (!cert->scheme)
+       {
+               INIT(cert->scheme,
+                       .scheme = signature_scheme_from_oid(
+                                                               hasher_signature_algorithm_to_oid(digest_alg,
+                                                                                               sign_key->get_type(sign_key))),
+               );
+       }
+       if (cert->scheme->scheme == SIGN_UNKNOWN)
+       {
+               return FALSE;
+       }
+       if (!signature_params_build(cert->scheme, &sig_scheme))
+       {
+               return FALSE;
+       }
+
+       if (!cert->public_key->get_encoding(cert->public_key,
+                                                                               PUBKEY_SPKI_ASN1_DER, &key_info))
+       {
+               chunk_free(&sig_scheme);
+               return FALSE;
+       }
+
+       /* encode subjectAltNames */
+       subjectAltNames = x509_build_subjectAltNames(cert->subjectAltNames);
+
+       /* encode certTypeExt */
+       if (cert->certTypeExt.len > 0)
+       {
+               certTypeExt = asn1_wrap(ASN1_SEQUENCE, "mm",
+                               asn1_build_known_oid(OID_MS_CERT_TYPE_EXT),
+                               asn1_wrap(ASN1_OCTET_STRING, "m",
+                                       asn1_simple_object(ASN1_UTF8STRING, cert->certTypeExt)
+                               ));
+       }
+
+       /* encode extensionRequest attribute */
+       if (subjectAltNames.ptr || certTypeExt.ptr)
+       {
+               extensionRequest = asn1_wrap(ASN1_SEQUENCE, "mm",
+                               asn1_build_known_oid(OID_EXTENSION_REQUEST),
+                               asn1_wrap(ASN1_SET, "m",
+                                       asn1_wrap(ASN1_SEQUENCE, "mm", subjectAltNames, certTypeExt)
+                               ));
+       }
+
+       /* encode challengePassword attribute */
+       if (cert->challengePassword.len > 0)
+       {
+               challengePassword = asn1_wrap(ASN1_SEQUENCE, "mm",
+                               asn1_build_known_oid(OID_CHALLENGE_PASSWORD),
+                               asn1_wrap(ASN1_SET, "m",
+                                       asn1_simple_object(ASN1_UTF8STRING, cert->challengePassword)
+                               ));
+       }
+
+       attributes = asn1_wrap(ASN1_CONTEXT_C_0, "mm", extensionRequest,
+                                                                                                  challengePassword);
+
+       cert->certificationRequestInfo = asn1_wrap(ASN1_SEQUENCE, "ccmm",
+                                                                               ASN1_INTEGER_0,
+                                                                               subject->get_encoding(subject),
+                                                                               key_info,
+                                                                               attributes);
+       if (!sign_key->sign(sign_key, cert->scheme->scheme, cert->scheme->params,
+                                               cert->certificationRequestInfo, &cert->signature))
+       {
+               chunk_free(&sig_scheme);
+               return FALSE;
+       }
+
+       cert->encoding = asn1_wrap(ASN1_SEQUENCE, "cmm",
+                                                          cert->certificationRequestInfo,
+                                                          sig_scheme,
+                                                          asn1_bitstring("c", cert->signature));
+       return TRUE;
+}
+
+METHOD(pkcs10_t, replace_key, certificate_t*,
+       private_x509_pkcs10_t *this, private_key_t *private,
+       signature_params_t *scheme, chunk_t password)
+{
+       /* remove old public key and signature */
+       this->public_key->destroy(this->public_key);
+       this->signature = chunk_empty;
+
+       /* copy existing attributes from old certreq encoding */
+       this->certificationRequestInfo = chunk_empty;
+       this->certTypeExt =     chunk_clone(this->certTypeExt);
+       this->challengePassword = chunk_clone((password.len > 0) ?
+                                                                                  password : this->challengePassword);
+       chunk_free(&this->encoding);
+       signature_params_destroy(this->scheme);
+       this->scheme = signature_params_clone(scheme);
+       this->parsed = FALSE;
+
+       if (generate(this, private, HASH_SHA256))
+       {
+               return &this->public.interface.interface;
+       }
+       return NULL;
+}
+
 /**
  * ASN.1 definition of a PKCS#10 extension request
  */
@@ -378,6 +497,8 @@ static bool parse_challengePassword(private_x509_pkcs10_t *this, chunk_t blob, i
        }
        DBG2(DBG_ASN, "L%d - challengePassword:", level);
        DBG4(DBG_ASN, "  '%.*s'", (int)blob.len, blob.ptr);
+       this->challengePassword = blob;
+
        return TRUE;
 }
 
@@ -558,6 +679,7 @@ static private_x509_pkcs10_t* create_empty(void)
                                .get_challengePassword = _get_challengePassword,
                                .get_flags = _get_flags,
                                .create_subjectAltName_enumerator = _create_subjectAltName_enumerator,
+                               .replace_key = _replace_key,
                        },
                },
                .subjectAltNames = linked_list_create(),
@@ -567,100 +689,6 @@ static private_x509_pkcs10_t* create_empty(void)
        return this;
 }
 
-/**
- * Generate and sign a new certificate request
- */
-static bool generate(private_x509_pkcs10_t *cert, private_key_t *sign_key,
-                                        int digest_alg)
-{
-       chunk_t key_info, subjectAltNames, attributes;
-       chunk_t extensionRequest  = chunk_empty, certTypeExt = chunk_empty;
-       chunk_t challengePassword = chunk_empty, sig_scheme = chunk_empty;
-       identification_t *subject;
-
-       subject = cert->subject;
-       cert->public_key = sign_key->get_public_key(sign_key);
-
-       /* select signature scheme, if not already specified */
-       if (!cert->scheme)
-       {
-               INIT(cert->scheme,
-                       .scheme = signature_scheme_from_oid(
-                                                               hasher_signature_algorithm_to_oid(digest_alg,
-                                                                                               sign_key->get_type(sign_key))),
-               );
-       }
-       if (cert->scheme->scheme == SIGN_UNKNOWN)
-       {
-               return FALSE;
-       }
-       if (!signature_params_build(cert->scheme, &sig_scheme))
-       {
-               return FALSE;
-       }
-
-       if (!cert->public_key->get_encoding(cert->public_key,
-                                                                               PUBKEY_SPKI_ASN1_DER, &key_info))
-       {
-               chunk_free(&sig_scheme);
-               return FALSE;
-       }
-
-       /* encode subjectAltNames */
-       subjectAltNames = x509_build_subjectAltNames(cert->subjectAltNames);
-
-       /* encode certTypeExt */
-       if (cert->certTypeExt.len > 0)
-       {
-               certTypeExt = asn1_wrap(ASN1_SEQUENCE, "mm",
-                               asn1_build_known_oid(OID_MS_CERT_TYPE_EXT),
-                               asn1_wrap(ASN1_OCTET_STRING, "m",
-                                       asn1_simple_object(ASN1_UTF8STRING, cert->certTypeExt)
-                               ));
-       }
-
-       /* encode extensionRequest attribute */
-       if (subjectAltNames.ptr || certTypeExt.ptr)
-       {
-               extensionRequest = asn1_wrap(ASN1_SEQUENCE, "mm",
-                               asn1_build_known_oid(OID_EXTENSION_REQUEST),
-                               asn1_wrap(ASN1_SET, "m",
-                                       asn1_wrap(ASN1_SEQUENCE, "mm", subjectAltNames, certTypeExt)
-                               ));
-       }
-
-       /* encode challengePassword attribute */
-       if (cert->challengePassword.len > 0)
-       {
-               challengePassword = asn1_wrap(ASN1_SEQUENCE, "mm",
-                               asn1_build_known_oid(OID_CHALLENGE_PASSWORD),
-                               asn1_wrap(ASN1_SET, "m",
-                                       asn1_simple_object(ASN1_UTF8STRING, cert->challengePassword)
-                               ));
-       }
-
-       attributes = asn1_wrap(ASN1_CONTEXT_C_0, "mm", extensionRequest,
-                                                                                                  challengePassword);
-
-       cert->certificationRequestInfo = asn1_wrap(ASN1_SEQUENCE, "ccmm",
-                                                                               ASN1_INTEGER_0,
-                                                                               subject->get_encoding(subject),
-                                                                               key_info,
-                                                                               attributes);
-       if (!sign_key->sign(sign_key, cert->scheme->scheme, cert->scheme->params,
-                                               cert->certificationRequestInfo, &cert->signature))
-       {
-               chunk_free(&sig_scheme);
-               return FALSE;
-       }
-
-       cert->encoding = asn1_wrap(ASN1_SEQUENCE, "cmm",
-                                                          cert->certificationRequestInfo,
-                                                          sig_scheme,
-                                                          asn1_bitstring("c", cert->signature));
-       return TRUE;
-}
-
 /**
  * See header.
  */
@@ -705,7 +733,7 @@ x509_pkcs10_t *x509_pkcs10_gen(certificate_type_t type, va_list args)
 {
        private_x509_pkcs10_t *cert;
        private_key_t *sign_key = NULL;
-       hash_algorithm_t digest_alg = HASH_SHA1;
+       hash_algorithm_t digest_alg = HASH_SHA256;
 
        cert = create_empty();
        while (TRUE)
index b2f3545e64f29ab49f7c6c46e2bc41186969f287..43eec90011f8af1d22b2d1f543c732e0c1f86580 100644 (file)
@@ -22,6 +22,7 @@
 
 #include <collections/linked_list.h>
 #include <credentials/certificates/certificate.h>
+#include <credentials/certificates/pkcs10.h>
 
 /**
  * Create a self-signed PKCS#10 certificate request.
@@ -32,9 +33,11 @@ static int req()
        key_type_t type = KEY_ANY;
        hash_algorithm_t digest = HASH_UNKNOWN;
        signature_params_t *scheme = NULL;
-       certificate_t *cert = NULL;
+       certificate_t *cert = NULL, *oldreq = NULL;
+       pkcs10_t *pkcs10;
        private_key_t *private = NULL;
        char *file = NULL, *keyid = NULL, *dn = NULL, *error = NULL;
+       char *oldreq_file = NULL;
        identification_t *id = NULL;
        linked_list_t *san;
        chunk_t encoding = chunk_empty;
@@ -50,9 +53,9 @@ static int req()
        {
                switch (command_getopt(&arg))
                {
-                       case 'h':
+                       case 'h':       /* --help */
                                goto usage;
-                       case 't':
+                       case 't':       /* --type */
                                if (streq(arg, "rsa"))
                                {
                                        type = KEY_RSA;
@@ -75,16 +78,17 @@ static int req()
                                        goto usage;
                                }
                                continue;
-                       case 'g':
+                       case 'g':       /* --digest */
                                if (!enum_from_name(hash_algorithm_short_names, arg, &digest))
                                {
                                        error = "invalid --digest type";
                                        goto usage;
                                }
                                continue;
-                       case 'R':
+                       case 'R':       /* --rsa-padding */
                                if (streq(arg, "pss"))
                                {
+
                                        pss = TRUE;
                                }
                                else if (!streq(arg, "pkcs1"))
@@ -93,31 +97,34 @@ static int req()
                                        goto usage;
                                }
                                continue;
-                       case 'i':
+                       case 'i':       /* --in */
                                file = arg;
                                continue;
-                       case 'd':
+                       case 'd':       /* --dn */
                                dn = arg;
                                continue;
-                       case 'a':
+                       case 'a':       /* --san */
                                san->insert_last(san, identification_create_from_string(arg));
                                continue;
-                       case 'P':
+                       case 'P':       /* --profile */
                                cert_type_ext = chunk_create(arg, strlen(arg));
                                continue;
-                       case 'p':
+                       case 'p':       /* --password */
                                challenge_password = chunk_create(arg, strlen(arg));
                                continue;
-                       case 'f':
+                       case 'f':       /* --outform */
                                if (!get_form(arg, &form, CRED_CERTIFICATE))
                                {
                                        error = "invalid output format";
                                        goto usage;
                                }
                                continue;
-                       case 'x':
+                       case 'x':       /* --keyid */
                                keyid = arg;
                                continue;
+                       case 'o':       /* --oldreq */
+                               oldreq_file = arg;
+                               continue;
                        case EOF:
                                break;
                        default:
@@ -127,17 +134,12 @@ static int req()
                break;
        }
 
-       if (!dn)
+       if (!dn && !oldreq_file)
        {
-               error = "--dn is required";
+               error = "--dn or --oldreq is required";
                goto usage;
        }
-       id = identification_create_from_string(dn);
-       if (id->get_type(id) != ID_DER_ASN1_DN)
-       {
-               error = "supplied --dn is not a distinguished name";
-               goto end;
-       }
+
        if (file)
        {
                private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, type,
@@ -179,18 +181,46 @@ static int req()
                goto end;
        }
 
-       cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PKCS10_REQUEST,
-                                                         BUILD_SIGNING_KEY, private,
-                                                         BUILD_SUBJECT, id,
-                                                         BUILD_SUBJECT_ALTNAMES, san,
-                                                         BUILD_CHALLENGE_PWD, challenge_password,
-                                                         BUILD_CERT_TYPE_EXT, cert_type_ext,
-                                                         BUILD_SIGNATURE_SCHEME, scheme,
-                                                         BUILD_END);
-       if (!cert)
+       if (oldreq_file)
        {
-               error = "generating certificate request failed";
-               goto end;
+               oldreq = lib->creds->create(lib->creds, CRED_CERTIFICATE,
+                                                                       CERT_PKCS10_REQUEST,
+                                                                       BUILD_FROM_FILE, oldreq_file, BUILD_END);
+               if (!oldreq)
+               {
+                       error = "parsing certificate request failed";
+                       goto end;
+               }
+               pkcs10 = (pkcs10_t*)oldreq;
+               cert = pkcs10->replace_key(pkcs10, private, scheme, challenge_password);
+               if (!cert)
+               {
+                       error = "key replacement in certificate request failed";
+                       oldreq->destroy(oldreq);
+                       goto end;
+               }
+       }
+       else
+       {
+               id = identification_create_from_string(dn);
+               if (id->get_type(id) != ID_DER_ASN1_DN)
+               {
+                       error = "supplied --dn is not a distinguished name";
+                       goto end;
+               }
+               cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PKCS10_REQUEST,
+                                                                 BUILD_SIGNING_KEY, private,
+                                                                 BUILD_SUBJECT, id,
+                                                                 BUILD_SUBJECT_ALTNAMES, san,
+                                                                 BUILD_CHALLENGE_PWD, challenge_password,
+                                                                 BUILD_CERT_TYPE_EXT, cert_type_ext,
+                                                                 BUILD_SIGNATURE_SCHEME, scheme,
+                                                                 BUILD_END);
+               if (!cert)
+               {
+                       error = "generating certificate request failed";
+                       goto end;
+               }
        }
        if (!cert->get_encoding(cert, form, &encoding))
        {
@@ -232,23 +262,24 @@ static void __attribute__ ((constructor))reg()
        command_register((command_t) {
                req, 'r', "req",
                "create a PKCS#10 certificate request",
-               {"[--in file|--keyid hex] [--type rsa|ecdsa|bliss|priv] --dn distinguished-name",
-                "[--san subjectAltName]+ [--profile server|client|dual|ocsp]",
-                "[--password challengePassword] [--rsa-padding pkcs1|pss]",
-                "[--digest md5|sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]",
-                "[--outform der|pem]"},
+               {"[--in file|--keyid hex] [--type rsa|ecdsa|bliss|priv]",
+                " --oldreq file|--dn distinguished-name [--san subjectAltName]+",
+                "[--profile server|client|dual|ocsp] [--password challengePassword]",
+                "[--digest sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]",
+                "[--rsa-padding pkcs1|pss] [--outform der|pem]"},
                {
-                       {"help",                'h', 0, "show usage information"},
-                       {"in",                  'i', 1, "private key input file, default: stdin"},
-                       {"keyid",               'x', 1, "smartcard or TPM private key object handle"},
-                       {"type",                't', 1, "type of input key, default: priv"},
-                       {"dn",                  'd', 1, "subject distinguished name"},
-                       {"san",                 'a', 1, "subjectAltName to include in cert request"},
+                       {"help",        'h', 0, "show usage information"},
+                       {"in",          'i', 1, "private key input file, default: stdin"},
+                       {"keyid",       'x', 1, "smartcard or TPM private key object handle"},
+                       {"type",        't', 1, "type of input key, default: priv"},
+                       {"oldreq",      'o', 1, "old certificate request to be used as a template"},
+                       {"dn",          'd', 1, "subject distinguished name"},
+                       {"san",         'a', 1, "subjectAltName to include in cert request"},
                        {"profile",     'P', 1, "certificate profile name to include in cert request"},
-                       {"password",    'p', 1, "challengePassword to include in cert request"},
-                       {"digest",              'g', 1, "digest for signature creation, default: key-specific"},
-                       {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"},
-                       {"outform",             'f', 1, "encoding of generated request, default: der"},
+                       {"password",    'p', 1, "challengePassword to include in cert request"},
+                       {"digest",      'g', 1, "digest for signature creation, default: key-specific"},
+                       {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"},
+                       {"outform",     'f', 1, "encoding of generated request, default: der"},
                }
        });
 }
index 516088f3d21a02c6ef80a91ee564ea9b2703e3fc..a988527c65e75cae311243fd94398e78da2a6a61 100644 (file)
@@ -1,4 +1,4 @@
-.TH "PKI \-\-REQ" 1 "2022-08-11" "@PACKAGE_VERSION@" "strongSwan"
+.TH "PKI \-\-REQ" 1 "2022-08-30" "@PACKAGE_VERSION@" "strongSwan"
 .
 .SH "NAME"
 .
@@ -22,6 +22,19 @@ pki \-\-req \- Create a PKCS#10 certificate request
 .YS
 .
 .SY pki\ \-\-req
+.RB [ \-\-in
+.IR file | \fB\-\-keyid\fR
+.IR hex ]
+.OP \-\-type type
+.BI \-\-oldreq\~ file
+.OP \-\-password password
+.OP \-\-digest digest
+.OP \-\-rsa\-padding padding
+.OP \-\-outform encoding
+.OP \-\-debug level
+.YS
+.
+.SY pki\ \-\-req
 .BI \-\-options\~ file
 .YS
 .
@@ -61,7 +74,9 @@ Type of the input key. Either \fIpriv\fR, \fIrsa\fR, \fIecdsa\fR or \fIbliss\fR,
 defaults to \fIpriv\fR.
 .TP
 .BI "\-d, \-\-dn " distinguished-name
-Subject distinguished name (DN). Required.
+Subject distinguished name (DN). Required if the
+.B \-\-dn
+option is not set.
 .TP
 .BI "\-a, \-\-san " subjectAltName
 subjectAltName extension to include in request. Can be used multiple times.
@@ -70,18 +85,29 @@ subjectAltName extension to include in request. Can be used multiple times.
 Certificate profile name to be included in the certificate request. Can be any
 UTF8 string. Supported e.g. by
 .B openxpki
-with profiles (\fIpc-client\fR, \fItls-server\fR, etc.) or
+(with profiles \fIpc-client\fR, \fItls-server\fR, etc.) or
 .B pki \-\-issue
-with (\fIserver\fR, \fIclient\fR, \fIdual\fR, or \fIocsp\fR) that are translated into
-corresponding Extended Key Usage (EKU) flags in the generated X.509 certificate.
+(with profiles \fIserver\fR, \fIclient\fR, \fIdual\fR, or \fIocsp\fR) that are
+translated into corresponding Extended Key Usage (EKU) flags in the generated
+X.509 certificate.
 .TP
 .BI "\-p, \-\-password " password
 The challengePassword to include in the certificate request.
 .TP
+.BI "\-o, \-\-oldreq " file
+Old certificate request to be used as a template. Required if the
+.B --dn
+option is not set. The public key in the old certificate request is replaced and
+a fresh signature is generated using the new private key. Optionally a new
+challengePassword may be set using the
+.B --password
+option.
+.TP
 .BI "\-g, \-\-digest " digest
-Digest to use for signature creation. One of \fImd5\fR, \fIsha1\fR,
-\fIsha224\fR, \fIsha256\fR, \fIsha384\fR, or \fIsha512\fR.  The default is
-determined based on the type and size of the signature key.
+Digest to use for signature creation. One of \fIsha1\fR, \fIsha224\fR,
+\fIsha256\fR, \fIsha384\fR, \fIsha512\fR, \fIsha3_224\fR, \fIsha3_256\fR,
+\fIsha3_384\fR, or \fIsha3_512\fR. The default is determined based on
+the type and size of the signature key.
 .TP
 .BI "\-R, \-\-rsa\-padding " padding
 Padding to use for RSA signatures. Either \fIpkcs1\fR or \fIpss\fR, defaults
@@ -98,7 +124,13 @@ and a TLS-server profile:
 .PP
 .EX
   pki \-\-req \-\-in key.der \-\-dn "C=CH, O=strongSwan, CN=moon" \\
-       \-\-san moon@strongswan.org \-\-profile server > req.der
+      \-\-san moon@strongswan.org \-\-profile server > req.der
+.EE
+.PP
+Generate a certificate request for a renewed key based on an existing template
+.PP
+.EX
+  pki \-\-req \-\-in myNewKey.der \-\-oldreq myReq.der > myNewReq.der
 .EE
 .PP
 Generate a certificate request for an ECDSA key and a different digest: