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.
This commit is contained in:
Andreas Steffen 2022-08-29 10:34:58 +02:00
parent 359b5739f4
commit 3fa3d2666a
4 changed files with 251 additions and 150 deletions

View 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_ @}*/

View 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)

View 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"},
}
});
}

View 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: