From: Andreas Steffen Date: Mon, 29 Aug 2022 08:34:58 +0000 (+0200) Subject: pki: pki --req can use old certreq as template X-Git-Tag: 5.9.8dr2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3fa3d2666a547580b40822f3eb7f2dfd6a5861a3;p=thirdparty%2Fstrongswan.git pki: pki --req can use old certreq as template 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. --- diff --git a/src/libstrongswan/credentials/certificates/pkcs10.h b/src/libstrongswan/credentials/certificates/pkcs10.h index ab5e3cdaad..2151fbd759 100644 --- a/src/libstrongswan/credentials/certificates/pkcs10.h +++ b/src/libstrongswan/credentials/certificates/pkcs10.h @@ -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_ @}*/ diff --git a/src/libstrongswan/plugins/x509/x509_pkcs10.c b/src/libstrongswan/plugins/x509/x509_pkcs10.c index cee518b51a..549f74dc4a 100644 --- a/src/libstrongswan/plugins/x509/x509_pkcs10.c +++ b/src/libstrongswan/plugins/x509/x509_pkcs10.c @@ -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) diff --git a/src/pki/commands/req.c b/src/pki/commands/req.c index b2f3545e64..43eec90011 100644 --- a/src/pki/commands/req.c +++ b/src/pki/commands/req.c @@ -22,6 +22,7 @@ #include #include +#include /** * 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"}, } }); } diff --git a/src/pki/man/pki---req.1.in b/src/pki/man/pki---req.1.in index 516088f3d2..a988527c65 100644 --- a/src/pki/man/pki---req.1.in +++ b/src/pki/man/pki---req.1.in @@ -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: