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* * @return enumerator over subjectAltNames as identification_t*
*/ */
enumerator_t* (*create_subjectAltName_enumerator)(pkcs10_t *this); 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_ @}*/ #endif /** PKCS10_H_ @}*/

View File

@ -270,6 +270,125 @@ METHOD(pkcs10_t, create_subjectAltName_enumerator, enumerator_t*,
return this->subjectAltNames->create_enumerator(this->subjectAltNames); 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 * 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); DBG2(DBG_ASN, "L%d - challengePassword:", level);
DBG4(DBG_ASN, " '%.*s'", (int)blob.len, blob.ptr); DBG4(DBG_ASN, " '%.*s'", (int)blob.len, blob.ptr);
this->challengePassword = blob;
return TRUE; return TRUE;
} }
@ -558,6 +679,7 @@ static private_x509_pkcs10_t* create_empty(void)
.get_challengePassword = _get_challengePassword, .get_challengePassword = _get_challengePassword,
.get_flags = _get_flags, .get_flags = _get_flags,
.create_subjectAltName_enumerator = _create_subjectAltName_enumerator, .create_subjectAltName_enumerator = _create_subjectAltName_enumerator,
.replace_key = _replace_key,
}, },
}, },
.subjectAltNames = linked_list_create(), .subjectAltNames = linked_list_create(),
@ -567,100 +689,6 @@ static private_x509_pkcs10_t* create_empty(void)
return this; 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. * 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_x509_pkcs10_t *cert;
private_key_t *sign_key = NULL; private_key_t *sign_key = NULL;
hash_algorithm_t digest_alg = HASH_SHA1; hash_algorithm_t digest_alg = HASH_SHA256;
cert = create_empty(); cert = create_empty();
while (TRUE) while (TRUE)

View File

@ -22,6 +22,7 @@
#include <collections/linked_list.h> #include <collections/linked_list.h>
#include <credentials/certificates/certificate.h> #include <credentials/certificates/certificate.h>
#include <credentials/certificates/pkcs10.h>
/** /**
* Create a self-signed PKCS#10 certificate request. * Create a self-signed PKCS#10 certificate request.
@ -32,9 +33,11 @@ static int req()
key_type_t type = KEY_ANY; key_type_t type = KEY_ANY;
hash_algorithm_t digest = HASH_UNKNOWN; hash_algorithm_t digest = HASH_UNKNOWN;
signature_params_t *scheme = NULL; signature_params_t *scheme = NULL;
certificate_t *cert = NULL; certificate_t *cert = NULL, *oldreq = NULL;
pkcs10_t *pkcs10;
private_key_t *private = NULL; private_key_t *private = NULL;
char *file = NULL, *keyid = NULL, *dn = NULL, *error = NULL; char *file = NULL, *keyid = NULL, *dn = NULL, *error = NULL;
char *oldreq_file = NULL;
identification_t *id = NULL; identification_t *id = NULL;
linked_list_t *san; linked_list_t *san;
chunk_t encoding = chunk_empty; chunk_t encoding = chunk_empty;
@ -50,9 +53,9 @@ static int req()
{ {
switch (command_getopt(&arg)) switch (command_getopt(&arg))
{ {
case 'h': case 'h': /* --help */
goto usage; goto usage;
case 't': case 't': /* --type */
if (streq(arg, "rsa")) if (streq(arg, "rsa"))
{ {
type = KEY_RSA; type = KEY_RSA;
@ -75,16 +78,17 @@ static int req()
goto usage; goto usage;
} }
continue; continue;
case 'g': case 'g': /* --digest */
if (!enum_from_name(hash_algorithm_short_names, arg, &digest)) if (!enum_from_name(hash_algorithm_short_names, arg, &digest))
{ {
error = "invalid --digest type"; error = "invalid --digest type";
goto usage; goto usage;
} }
continue; continue;
case 'R': case 'R': /* --rsa-padding */
if (streq(arg, "pss")) if (streq(arg, "pss"))
{ {
pss = TRUE; pss = TRUE;
} }
else if (!streq(arg, "pkcs1")) else if (!streq(arg, "pkcs1"))
@ -93,31 +97,34 @@ static int req()
goto usage; goto usage;
} }
continue; continue;
case 'i': case 'i': /* --in */
file = arg; file = arg;
continue; continue;
case 'd': case 'd': /* --dn */
dn = arg; dn = arg;
continue; continue;
case 'a': case 'a': /* --san */
san->insert_last(san, identification_create_from_string(arg)); san->insert_last(san, identification_create_from_string(arg));
continue; continue;
case 'P': case 'P': /* --profile */
cert_type_ext = chunk_create(arg, strlen(arg)); cert_type_ext = chunk_create(arg, strlen(arg));
continue; continue;
case 'p': case 'p': /* --password */
challenge_password = chunk_create(arg, strlen(arg)); challenge_password = chunk_create(arg, strlen(arg));
continue; continue;
case 'f': case 'f': /* --outform */
if (!get_form(arg, &form, CRED_CERTIFICATE)) if (!get_form(arg, &form, CRED_CERTIFICATE))
{ {
error = "invalid output format"; error = "invalid output format";
goto usage; goto usage;
} }
continue; continue;
case 'x': case 'x': /* --keyid */
keyid = arg; keyid = arg;
continue; continue;
case 'o': /* --oldreq */
oldreq_file = arg;
continue;
case EOF: case EOF:
break; break;
default: default:
@ -127,17 +134,12 @@ static int req()
break; break;
} }
if (!dn) if (!dn && !oldreq_file)
{ {
error = "--dn is required"; error = "--dn or --oldreq is required";
goto usage; 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) if (file)
{ {
private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, type, private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, type,
@ -179,6 +181,33 @@ static int req()
goto end; goto end;
} }
if (oldreq_file)
{
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, cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PKCS10_REQUEST,
BUILD_SIGNING_KEY, private, BUILD_SIGNING_KEY, private,
BUILD_SUBJECT, id, BUILD_SUBJECT, id,
@ -192,6 +221,7 @@ static int req()
error = "generating certificate request failed"; error = "generating certificate request failed";
goto end; goto end;
} }
}
if (!cert->get_encoding(cert, form, &encoding)) if (!cert->get_encoding(cert, form, &encoding))
{ {
error = "encoding certificate request failed"; error = "encoding certificate request failed";
@ -232,16 +262,17 @@ static void __attribute__ ((constructor))reg()
command_register((command_t) { command_register((command_t) {
req, 'r', "req", req, 'r', "req",
"create a PKCS#10 certificate request", "create a PKCS#10 certificate request",
{"[--in file|--keyid hex] [--type rsa|ecdsa|bliss|priv] --dn distinguished-name", {"[--in file|--keyid hex] [--type rsa|ecdsa|bliss|priv]",
"[--san subjectAltName]+ [--profile server|client|dual|ocsp]", " --oldreq file|--dn distinguished-name [--san subjectAltName]+",
"[--password challengePassword] [--rsa-padding pkcs1|pss]", "[--profile server|client|dual|ocsp] [--password challengePassword]",
"[--digest md5|sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]", "[--digest sha1|sha224|sha256|sha384|sha512|sha3_224|sha3_256|sha3_384|sha3_512]",
"[--outform der|pem]"}, "[--rsa-padding pkcs1|pss] [--outform der|pem]"},
{ {
{"help", 'h', 0, "show usage information"}, {"help", 'h', 0, "show usage information"},
{"in", 'i', 1, "private key input file, default: stdin"}, {"in", 'i', 1, "private key input file, default: stdin"},
{"keyid", 'x', 1, "smartcard or TPM private key object handle"}, {"keyid", 'x', 1, "smartcard or TPM private key object handle"},
{"type", 't', 1, "type of input key, default: priv"}, {"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"}, {"dn", 'd', 1, "subject distinguished name"},
{"san", 'a', 1, "subjectAltName to include in cert request"}, {"san", 'a', 1, "subjectAltName to include in cert request"},
{"profile", 'P', 1, "certificate profile name to include in cert request"}, {"profile", 'P', 1, "certificate profile name to include in cert request"},

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" .SH "NAME"
. .
@ -22,6 +22,19 @@ pki \-\-req \- Create a PKCS#10 certificate request
.YS .YS
. .
.SY pki\ \-\-req .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 .BI \-\-options\~ file
.YS .YS
. .
@ -61,7 +74,9 @@ Type of the input key. Either \fIpriv\fR, \fIrsa\fR, \fIecdsa\fR or \fIbliss\fR,
defaults to \fIpriv\fR. defaults to \fIpriv\fR.
.TP .TP
.BI "\-d, \-\-dn " distinguished-name .BI "\-d, \-\-dn " distinguished-name
Subject distinguished name (DN). Required. Subject distinguished name (DN). Required if the
.B \-\-dn
option is not set.
.TP .TP
.BI "\-a, \-\-san " subjectAltName .BI "\-a, \-\-san " subjectAltName
subjectAltName extension to include in request. Can be used multiple times. 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 Certificate profile name to be included in the certificate request. Can be any
UTF8 string. Supported e.g. by UTF8 string. Supported e.g. by
.B openxpki .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 .B pki \-\-issue
with (\fIserver\fR, \fIclient\fR, \fIdual\fR, or \fIocsp\fR) that are translated into (with profiles \fIserver\fR, \fIclient\fR, \fIdual\fR, or \fIocsp\fR) that are
corresponding Extended Key Usage (EKU) flags in the generated X.509 certificate. translated into corresponding Extended Key Usage (EKU) flags in the generated
X.509 certificate.
.TP .TP
.BI "\-p, \-\-password " password .BI "\-p, \-\-password " password
The challengePassword to include in the certificate request. The challengePassword to include in the certificate request.
.TP .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 .BI "\-g, \-\-digest " digest
Digest to use for signature creation. One of \fImd5\fR, \fIsha1\fR, Digest to use for signature creation. One of \fIsha1\fR, \fIsha224\fR,
\fIsha224\fR, \fIsha256\fR, \fIsha384\fR, or \fIsha512\fR. The default is \fIsha256\fR, \fIsha384\fR, \fIsha512\fR, \fIsha3_224\fR, \fIsha3_256\fR,
determined based on the type and size of the signature key. \fIsha3_384\fR, or \fIsha3_512\fR. The default is determined based on
the type and size of the signature key.
.TP .TP
.BI "\-R, \-\-rsa\-padding " padding .BI "\-R, \-\-rsa\-padding " padding
Padding to use for RSA signatures. Either \fIpkcs1\fR or \fIpss\fR, defaults Padding to use for RSA signatures. Either \fIpkcs1\fR or \fIpss\fR, defaults
@ -101,6 +127,12 @@ and a TLS-server profile:
\-\-san moon@strongswan.org \-\-profile server > req.der \-\-san moon@strongswan.org \-\-profile server > req.der
.EE .EE
.PP .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: Generate a certificate request for an ECDSA key and a different digest:
.PP .PP
.EX .EX