diff --git a/SOURCES/0024-Remove-PKINIT-RSA-support.patch b/SOURCES/0024-Remove-PKINIT-RSA-support.patch new file mode 100644 index 0000000..8f19c05 --- /dev/null +++ b/SOURCES/0024-Remove-PKINIT-RSA-support.patch @@ -0,0 +1,1295 @@ +From c52dea4944820750bf5881891bd92d6f5c7a73c4 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Sun, 26 Nov 2023 17:42:34 -0500 +Subject: [PATCH] Remove PKINIT RSA support + +RSA mode is no longer needed for interoperability. Reduce the attack +surface of clients and KDCs by removing support for it. + +ticket: 9108 (new) +(cherry picked from commit 401f584526e501b68e7516c17d8e467883f8f210) +--- + doc/user/user_commands/kinit.rst | 4 - + src/plugins/preauth/pkinit/pkinit.h | 2 - + src/plugins/preauth/pkinit/pkinit_clnt.c | 235 +++----- + src/plugins/preauth/pkinit/pkinit_crypto.h | 39 -- + .../preauth/pkinit/pkinit_crypto_openssl.c | 502 ------------------ + src/plugins/preauth/pkinit/pkinit_lib.c | 2 - + src/plugins/preauth/pkinit/pkinit_srv.c | 208 +++----- + src/plugins/preauth/pkinit/pkinit_trace.h | 9 - + src/tests/t_pkinit.py | 7 - + src/windows/leash/htmlhelp/html/KINIT.htm | 3 - + 10 files changed, 131 insertions(+), 880 deletions(-) + +diff --git a/doc/user/user_commands/kinit.rst b/doc/user/user_commands/kinit.rst +index 5b105e35a5..d947e83cc6 100644 +--- a/doc/user/user_commands/kinit.rst ++++ b/doc/user/user_commands/kinit.rst +@@ -193,10 +193,6 @@ OPTIONS + **X509_anchors**\ =\ *value* + specify where to find trusted X509 anchor information + +- **flag_RSA_PROTOCOL**\ [**=yes**] +- specify use of RSA, rather than the default Diffie-Hellman +- protocol +- + **disable_freshness**\ [**=yes**] + disable sending freshness tokens (for testing purposes only) + +diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h +index 66f92d8f03..5ab0f4bc28 100644 +--- a/src/plugins/preauth/pkinit/pkinit.h ++++ b/src/plugins/preauth/pkinit/pkinit.h +@@ -146,7 +146,6 @@ typedef struct _pkinit_plg_opts { + int require_eku; /* require EKU checking (default is true) */ + int accept_secondary_eku;/* accept secondary EKU (default is false) */ + int allow_upn; /* allow UPN-SAN instead of pkinit-SAN */ +- int dh_or_rsa; /* selects DH or RSA based pkinit */ + int require_crl_checking; /* require CRL for a CA (default is false) */ + int require_freshness; /* require freshness token (default is false) */ + int disable_freshness; /* disable freshness token on client for testing */ +@@ -160,7 +159,6 @@ typedef struct _pkinit_req_opts { + int require_eku; + int accept_secondary_eku; + int allow_upn; +- int dh_or_rsa; + int require_crl_checking; + int dh_size; /* initial request DH modulus size (default=1024) */ + int require_hostname_match; +diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c +index ea9ba454df..54e7537600 100644 +--- a/src/plugins/preauth/pkinit/pkinit_clnt.c ++++ b/src/plugins/preauth/pkinit/pkinit_clnt.c +@@ -191,7 +191,6 @@ pkinit_as_req_create(krb5_context context, + krb5_auth_pack auth_pack; + krb5_pa_pk_as_req *req = NULL; + krb5_algorithm_identifier **cmstypes = NULL; +- int protocol = reqctx->opts->dh_or_rsa; + + pkiDebug("pkinit_as_req_create pa_type = %d\n", reqctx->pa_type); + +@@ -214,29 +213,14 @@ pkinit_as_req_create(krb5_context context, + if (retval) + goto cleanup; + +- switch(protocol) { +- case DH_PROTOCOL: +- TRACE_PKINIT_CLIENT_REQ_DH(context); +- pkiDebug("as_req: DH key transport algorithm\n"); ++ TRACE_PKINIT_CLIENT_REQ_DH(context); + +- /* create client-side DH keys */ +- retval = client_create_dh(context, plgctx->cryptoctx, +- reqctx->cryptoctx, reqctx->idctx, +- reqctx->opts->dh_size, &spki); +- auth_pack.clientPublicValue = spki; +- if (retval != 0) { +- pkiDebug("failed to create dh parameters\n"); +- goto cleanup; +- } +- break; +- case RSA_PROTOCOL: +- TRACE_PKINIT_CLIENT_REQ_RSA(context); +- pkiDebug("as_req: RSA key transport algorithm\n"); +- break; +- default: +- pkiDebug("as_req: unknown key transport protocol %d\n", +- protocol); +- retval = -1; ++ /* create client-side DH keys */ ++ retval = client_create_dh(context, plgctx->cryptoctx, reqctx->cryptoctx, ++ reqctx->idctx, reqctx->opts->dh_size, &spki); ++ auth_pack.clientPublicValue = spki; ++ if (retval != 0) { ++ pkiDebug("failed to create dh parameters\n"); + goto cleanup; + } + +@@ -553,49 +537,34 @@ pkinit_as_rep_parse(krb5_context context, + return retval; + } + +- switch(kdc_reply->choice) { +- case choice_pa_pk_as_rep_dhInfo: +- pkiDebug("as_rep: DH key transport algorithm\n"); ++ if (kdc_reply->choice != choice_pa_pk_as_rep_dhInfo) { ++ pkiDebug("unknown as_rep type %d\n", kdc_reply->choice); ++ retval = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; ++ } ++ + #ifdef DEBUG_ASN1 +- print_buffer_bin(kdc_reply->u.dh_Info.dhSignedData.data, +- kdc_reply->u.dh_Info.dhSignedData.length, "/tmp/client_kdc_signeddata"); ++ print_buffer_bin(kdc_reply->u.dh_Info.dhSignedData.data, ++ kdc_reply->u.dh_Info.dhSignedData.length, ++ "/tmp/client_kdc_signeddata"); + #endif +- if ((retval = cms_signeddata_verify(context, plgctx->cryptoctx, +- reqctx->cryptoctx, reqctx->idctx, CMS_SIGN_SERVER, +- reqctx->opts->require_crl_checking, +- (unsigned char *) +- kdc_reply->u.dh_Info.dhSignedData.data, +- kdc_reply->u.dh_Info.dhSignedData.length, +- (unsigned char **)&dh_data.data, +- &dh_data.length, +- NULL, NULL, NULL)) != 0) { +- pkiDebug("failed to verify pkcs7 signed data\n"); +- TRACE_PKINIT_CLIENT_REP_DH_FAIL(context); +- goto cleanup; +- } +- TRACE_PKINIT_CLIENT_REP_DH(context); +- break; +- case choice_pa_pk_as_rep_encKeyPack: +- pkiDebug("as_rep: RSA key transport algorithm\n"); +- if ((retval = cms_envelopeddata_verify(context, plgctx->cryptoctx, +- reqctx->cryptoctx, reqctx->idctx, pa_type, +- reqctx->opts->require_crl_checking, +- (unsigned char *) +- kdc_reply->u.encKeyPack.data, +- kdc_reply->u.encKeyPack.length, +- (unsigned char **)&dh_data.data, +- &dh_data.length)) != 0) { +- pkiDebug("failed to verify pkcs7 enveloped data\n"); +- TRACE_PKINIT_CLIENT_REP_RSA_FAIL(context); +- goto cleanup; +- } +- TRACE_PKINIT_CLIENT_REP_RSA(context); +- break; +- default: +- pkiDebug("unknown as_rep type %d\n", kdc_reply->choice); +- retval = -1; ++ retval = cms_signeddata_verify(context, plgctx->cryptoctx, ++ reqctx->cryptoctx, reqctx->idctx, ++ CMS_SIGN_SERVER, ++ reqctx->opts->require_crl_checking, ++ (unsigned char *) ++ kdc_reply->u.dh_Info.dhSignedData.data, ++ kdc_reply->u.dh_Info.dhSignedData.length, ++ (unsigned char **)&dh_data.data, ++ &dh_data.length, ++ NULL, NULL, NULL); ++ if (retval) { ++ pkiDebug("failed to verify pkcs7 signed data\n"); ++ TRACE_PKINIT_CLIENT_REP_DH_FAIL(context); + goto cleanup; + } ++ TRACE_PKINIT_CLIENT_REP_DH(context); ++ + retval = krb5_build_principal_ext(context, &kdc_princ, + request->server->realm.length, + request->server->realm.data, +@@ -632,116 +601,54 @@ pkinit_as_rep_parse(krb5_context context, + + OCTETDATA_TO_KRB5DATA(&dh_data, &k5data); + +- switch(kdc_reply->choice) { +- case choice_pa_pk_as_rep_dhInfo: + #ifdef DEBUG_ASN1 +- print_buffer_bin(dh_data.data, dh_data.length, +- "/tmp/client_dh_key"); ++ print_buffer_bin(dh_data.data, dh_data.length, "/tmp/client_dh_key"); + #endif +- if ((retval = k5int_decode_krb5_kdc_dh_key_info(&k5data, +- &kdc_dh)) != 0) { +- pkiDebug("failed to decode kdc_dh_key_info\n"); +- goto cleanup; +- } +- +- /* client after KDC reply */ +- if ((retval = client_process_dh(context, plgctx->cryptoctx, +- reqctx->cryptoctx, reqctx->idctx, +- (unsigned char *) +- kdc_dh->subjectPublicKey.data, +- kdc_dh->subjectPublicKey.length, +- &client_key, &client_key_len)) != 0) { +- pkiDebug("failed to process dh params\n"); +- goto cleanup; +- } +- +- /* If we have a KDF algorithm ID, call the algorithm agility KDF... */ +- if (kdc_reply->u.dh_Info.kdfID) { +- secret.length = client_key_len; +- secret.data = (char *)client_key; +- +- retval = pkinit_alg_agility_kdf(context, &secret, +- kdc_reply->u.dh_Info.kdfID, +- request->client, request->server, +- etype, encoded_request, +- (krb5_data *)as_rep, key_block); +- +- if (retval) { +- pkiDebug("failed to create key pkinit_alg_agility_kdf %s\n", +- error_message(retval)); +- goto cleanup; +- } +- TRACE_PKINIT_CLIENT_KDF_ALG(context, kdc_reply->u.dh_Info.kdfID, +- key_block); ++ retval = k5int_decode_krb5_kdc_dh_key_info(&k5data, &kdc_dh); ++ if (retval) { ++ pkiDebug("failed to decode kdc_dh_key_info\n"); ++ goto cleanup; ++ } + +- /* ...otherwise, use the older octetstring2key function. */ +- } else { ++ /* client after KDC reply */ ++ retval = client_process_dh(context, plgctx->cryptoctx, reqctx->cryptoctx, ++ reqctx->idctx, ++ (unsigned char *)kdc_dh->subjectPublicKey.data, ++ kdc_dh->subjectPublicKey.length, &client_key, ++ &client_key_len); ++ if (retval) { ++ pkiDebug("failed to process dh params\n"); ++ goto cleanup; ++ } + +- retval = pkinit_octetstring2key(context, etype, client_key, +- client_key_len, key_block); +- if (retval) { +- pkiDebug("failed to create key pkinit_octetstring2key %s\n", +- error_message(retval)); +- goto cleanup; +- } +- TRACE_PKINIT_CLIENT_KDF_OS2K(context, key_block); +- } ++ /* If we have a KDF algorithm ID, call the algorithm agility KDF. */ ++ if (kdc_reply->u.dh_Info.kdfID) { ++ secret.length = client_key_len; ++ secret.data = (char *)client_key; + +- break; +- case choice_pa_pk_as_rep_encKeyPack: +-#ifdef DEBUG_ASN1 +- print_buffer_bin(dh_data.data, dh_data.length, +- "/tmp/client_key_pack"); +-#endif +- retval = k5int_decode_krb5_reply_key_pack(&k5data, &key_pack); ++ retval = pkinit_alg_agility_kdf(context, &secret, ++ kdc_reply->u.dh_Info.kdfID, ++ request->client, request->server, ++ etype, encoded_request, ++ (krb5_data *)as_rep, key_block); + if (retval) { +- pkiDebug("failed to decode reply_key_pack\n"); ++ pkiDebug("failed to create key pkinit_alg_agility_kdf %s\n", ++ error_message(retval)); + goto cleanup; + } +- retval = krb5_c_make_checksum(context, +- key_pack->asChecksum.checksum_type, +- &key_pack->replyKey, +- KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, +- encoded_request, &cksum); ++ TRACE_PKINIT_CLIENT_KDF_ALG(context, kdc_reply->u.dh_Info.kdfID, ++ key_block); ++ ++ } else { ++ /* Otherwise, use the older octetstring2key function. */ ++ retval = pkinit_octetstring2key(context, etype, client_key, ++ client_key_len, key_block); + if (retval) { +- pkiDebug("failed to make a checksum\n"); ++ pkiDebug("failed to create key pkinit_octetstring2key %s\n", ++ error_message(retval)); + goto cleanup; + } +- +- if ((cksum.length != key_pack->asChecksum.length) || +- k5_bcmp(cksum.contents, key_pack->asChecksum.contents, +- cksum.length) != 0) { +- TRACE_PKINIT_CLIENT_REP_CHECKSUM_FAIL(context, &cksum, +- &key_pack->asChecksum); +- pkiDebug("failed to match the checksums\n"); +-#ifdef DEBUG_CKSUM +- pkiDebug("calculating checksum on buf size (%d)\n", +- encoded_request->length); +- print_buffer(encoded_request->data, encoded_request->length); +- pkiDebug("encrypting key (%d)\n", key_pack->replyKey.length); +- print_buffer(key_pack->replyKey.contents, +- key_pack->replyKey.length); +- pkiDebug("received checksum type=%d size=%d ", +- key_pack->asChecksum.checksum_type, +- key_pack->asChecksum.length); +- print_buffer(key_pack->asChecksum.contents, +- key_pack->asChecksum.length); +- pkiDebug("expected checksum type=%d size=%d ", +- cksum.checksum_type, cksum.length); +- print_buffer(cksum.contents, cksum.length); +-#endif +- goto cleanup; +- } else +- pkiDebug("checksums match\n"); +- +- krb5_copy_keyblock_contents(context, &key_pack->replyKey, +- key_block); +- TRACE_PKINIT_CLIENT_REP_RSA_KEY(context, key_block, &cksum); +- +- break; +- default: +- pkiDebug("unknown as_rep type %d\n", kdc_reply->choice); +- goto cleanup; ++ TRACE_PKINIT_CLIENT_KDF_OS2K(context, key_block); + } + + retval = 0; +@@ -1286,7 +1193,6 @@ pkinit_client_req_init(krb5_context context, + + reqctx->opts->require_eku = plgctx->opts->require_eku; + reqctx->opts->accept_secondary_eku = plgctx->opts->accept_secondary_eku; +- reqctx->opts->dh_or_rsa = plgctx->opts->dh_or_rsa; + reqctx->opts->allow_upn = plgctx->opts->allow_upn; + reqctx->opts->require_crl_checking = plgctx->opts->require_crl_checking; + reqctx->opts->disable_freshness = plgctx->opts->disable_freshness; +@@ -1457,11 +1363,6 @@ handle_gic_opt(krb5_context context, + retval = add_string_to_array(context, &plgctx->idopts->anchors, value); + if (retval) + return retval; +- } else if (strcmp(attr, "flag_RSA_PROTOCOL") == 0) { +- if (strcmp(value, "yes") == 0) { +- pkiDebug("Setting flag to use RSA_PROTOCOL\n"); +- plgctx->opts->dh_or_rsa = RSA_PROTOCOL; +- } + } else if (strcmp(attr, "disable_freshness") == 0) { + if (strcmp(value, "yes") == 0) + plgctx->opts->disable_freshness = 1; +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h +index 8bdbea8e95..04199b45a4 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto.h ++++ b/src/plugins/preauth/pkinit/pkinit_crypto.h +@@ -181,45 +181,6 @@ krb5_error_code cms_signeddata_verify + int *is_signed); /* OUT + receives whether message is signed */ + +-/* +- * this function creates a CMS message where eContentType is EnvelopedData +- */ +-krb5_error_code cms_envelopeddata_create +- (krb5_context context, /* IN */ +- pkinit_plg_crypto_context plg_cryptoctx, /* IN */ +- pkinit_req_crypto_context req_cryptoctx, /* IN */ +- pkinit_identity_crypto_context id_cryptoctx, /* IN */ +- krb5_preauthtype pa_type, /* IN */ +- unsigned char *key_pack, /* IN +- contains DER encoded ReplyKeyPack */ +- unsigned int key_pack_len, /* IN +- contains length of key_pack */ +- unsigned char **envel_data, /* OUT +- receives DER encoded encKeyPack */ +- unsigned int *envel_data_len); /* OUT +- receives length of envel_data */ +- +-/* +- * this function creates a CMS message where eContentType is EnvelopedData +- */ +-krb5_error_code cms_envelopeddata_verify +- (krb5_context context, /* IN */ +- pkinit_plg_crypto_context plg_cryptoctx, /* IN */ +- pkinit_req_crypto_context req_cryptoctx, /* IN */ +- pkinit_identity_crypto_context id_cryptoctx, /* IN */ +- krb5_preauthtype pa_type, /* IN */ +- int require_crl_checking, /* IN +- specifies whether CRL checking should be +- strictly enforced */ +- unsigned char *envel_data, /* IN +- contains DER encoded encKeyPack */ +- unsigned int envel_data_len, /* IN +- contains length of envel_data */ +- unsigned char **signed_data, /* OUT +- receives ReplyKeyPack */ +- unsigned int *signed_data_len); /* OUT +- receives length of signed_data */ +- + /* + * This function retrieves the signer's identity, in a form that could + * be passed back in to a future invocation of this module as a candidate +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +index af3fa9ee8b..980a89edc1 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +@@ -66,26 +66,14 @@ static krb5_error_code create_signature + (unsigned char **, unsigned int *, unsigned char *, unsigned int, + EVP_PKEY *pkey); + +-static krb5_error_code pkinit_decode_data +-(krb5_context context, pkinit_identity_crypto_context cryptoctx, +- const uint8_t *data, unsigned int data_len, uint8_t **decoded, +- unsigned int *decoded_len); +- + #ifdef DEBUG_DH + static void print_dh(DH *, char *); + static void print_pubkey(BIGNUM *, char *); + #endif + +-static int prepare_enc_data +-(const uint8_t *indata, int indata_len, uint8_t **outdata, int *outdata_len); +- + static int openssl_callback (int, X509_STORE_CTX *); + static int openssl_callback_ignore_crls (int, X509_STORE_CTX *); + +-static int pkcs7_decrypt +-(krb5_context context, pkinit_identity_crypto_context id_cryptoctx, PKCS7 *p7, +- unsigned char **data_out, unsigned int *len_out); +- + static ASN1_OBJECT * pkinit_pkcs7type2oid + (pkinit_plg_crypto_context plg_cryptoctx, int pkcs7_type); + +@@ -115,20 +103,12 @@ static krb5_error_code pkinit_sign_data_pkcs11 + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **sig, unsigned int *sig_len); +-static krb5_error_code pkinit_decode_data_pkcs11 +-(krb5_context context, pkinit_identity_crypto_context id_cryptoctx, +- const uint8_t *data, unsigned int data_len, uint8_t **decoded_data, +- unsigned int *decoded_data_len); + #endif /* WITHOUT_PKCS11 */ + + static krb5_error_code pkinit_sign_data_fs + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **sig, unsigned int *sig_len); +-static krb5_error_code pkinit_decode_data_fs +-(krb5_context context, pkinit_identity_crypto_context id_cryptoctx, +- const uint8_t *data, unsigned int data_len, uint8_t **decoded_data, +- unsigned int *decoded_data_len); + + static krb5_error_code + create_krb5_invalidCertificates(krb5_context context, +@@ -140,10 +120,6 @@ create_krb5_invalidCertificates(krb5_context context, + static krb5_error_code + create_identifiers_from_stack(STACK_OF(X509) *sk, + krb5_external_principal_identifier *** ids); +-static int +-wrap_signeddata(unsigned char *data, unsigned int data_len, +- unsigned char **out, unsigned int *out_len); +- + static const char * + pkcs11err(int err); + +@@ -2177,175 +2153,6 @@ cleanup: + return retval; + } + +-krb5_error_code +-cms_envelopeddata_create(krb5_context context, +- pkinit_plg_crypto_context plgctx, +- pkinit_req_crypto_context reqctx, +- pkinit_identity_crypto_context idctx, +- krb5_preauthtype pa_type, +- unsigned char *key_pack, +- unsigned int key_pack_len, +- unsigned char **out, +- unsigned int *out_len) +-{ +- +- krb5_error_code retval = ENOMEM; +- PKCS7 *p7 = NULL; +- BIO *in = NULL; +- unsigned char *p = NULL, *signed_data = NULL, *enc_data = NULL; +- int signed_data_len = 0, enc_data_len = 0, flags = PKCS7_BINARY; +- STACK_OF(X509) *encerts = NULL; +- const EVP_CIPHER *cipher = NULL; +- +- retval = cms_signeddata_create(context, plgctx, reqctx, idctx, +- CMS_ENVEL_SERVER, key_pack, key_pack_len, +- &signed_data, +- (unsigned int *)&signed_data_len); +- if (retval) { +- pkiDebug("failed to create pkcs7 signed data\n"); +- goto cleanup; +- } +- +- /* check we have client's certificate */ +- if (reqctx->received_cert == NULL) { +- retval = KRB5KDC_ERR_PREAUTH_FAILED; +- goto cleanup; +- } +- encerts = sk_X509_new_null(); +- sk_X509_push(encerts, reqctx->received_cert); +- +- cipher = EVP_des_ede3_cbc(); +- in = BIO_new(BIO_s_mem()); +- prepare_enc_data(signed_data, signed_data_len, &enc_data, +- &enc_data_len); +- retval = BIO_write(in, enc_data, enc_data_len); +- if (retval != enc_data_len) { +- pkiDebug("BIO_write only wrote %d\n", retval); +- goto cleanup; +- } +- +- p7 = PKCS7_encrypt(encerts, in, cipher, flags); +- if (p7 == NULL) { +- retval = oerr(context, 0, _("Failed to encrypt PKCS7 object")); +- goto cleanup; +- } +- p7->d.enveloped->enc_data->content_type = OBJ_nid2obj(NID_pkcs7_signed); +- +- *out_len = i2d_PKCS7(p7, NULL); +- if (!*out_len || (p = *out = malloc(*out_len)) == NULL) { +- retval = ENOMEM; +- goto cleanup; +- } +- retval = i2d_PKCS7(p7, &p); +- if (!retval) { +- retval = oerr(context, 0, _("Failed to DER encode PKCS7")); +- goto cleanup; +- } +- retval = 0; +- +-#ifdef DEBUG_ASN1 +- print_buffer_bin(*out, *out_len, "/tmp/kdc_enveloped_data"); +-#endif +- +-cleanup: +- if (p7 != NULL) +- PKCS7_free(p7); +- if (in != NULL) +- BIO_free(in); +- free(signed_data); +- free(enc_data); +- if (encerts != NULL) +- sk_X509_free(encerts); +- +- return retval; +-} +- +-krb5_error_code +-cms_envelopeddata_verify(krb5_context context, +- pkinit_plg_crypto_context plg_cryptoctx, +- pkinit_req_crypto_context req_cryptoctx, +- pkinit_identity_crypto_context id_cryptoctx, +- krb5_preauthtype pa_type, +- int require_crl_checking, +- unsigned char *enveloped_data, +- unsigned int enveloped_data_len, +- unsigned char **data, +- unsigned int *data_len) +-{ +- krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; +- PKCS7 *p7 = NULL; +- const unsigned char *p = enveloped_data; +- unsigned int tmp_buf_len = 0, tmp_buf2_len = 0, vfy_buf_len = 0; +- unsigned char *tmp_buf = NULL, *tmp_buf2 = NULL, *vfy_buf = NULL; +- +-#ifdef DEBUG_ASN1 +- print_buffer_bin(enveloped_data, enveloped_data_len, +- "/tmp/client_envelopeddata"); +-#endif +- /* decode received PKCS7 message */ +- if ((p7 = d2i_PKCS7(NULL, &p, (int)enveloped_data_len)) == NULL) { +- retval = oerr(context, 0, _("Failed to decode PKCS7")); +- goto cleanup; +- } +- +- /* verify that the received message is PKCS7 EnvelopedData message */ +- if (OBJ_obj2nid(p7->type) != NID_pkcs7_enveloped) { +- pkiDebug("Expected id-enveloped PKCS7 msg (received type = %d)\n", +- OBJ_obj2nid(p7->type)); +- krb5_set_error_message(context, retval, "wrong oid\n"); +- goto cleanup; +- } +- +- /* decrypt received PKCS7 message */ +- if (pkcs7_decrypt(context, id_cryptoctx, p7, &tmp_buf, &tmp_buf_len)) { +- pkiDebug("PKCS7 decryption successful\n"); +- } else { +- retval = oerr(context, 0, _("Failed to decrypt PKCS7 message")); +- goto cleanup; +- } +- +-#ifdef DEBUG_ASN1 +- print_buffer_bin(tmp_buf, tmp_buf_len, "/tmp/client_enc_keypack"); +-#endif +- /* verify PKCS7 SignedData message */ +- /* Wrap the signed data to make decoding easier in the verify routine. */ +- retval = wrap_signeddata(tmp_buf, tmp_buf_len, &tmp_buf2, &tmp_buf2_len); +- if (retval) { +- pkiDebug("failed to encode signeddata\n"); +- goto cleanup; +- } +- vfy_buf = tmp_buf2; +- vfy_buf_len = tmp_buf2_len; +- +-#ifdef DEBUG_ASN1 +- print_buffer_bin(vfy_buf, vfy_buf_len, "/tmp/client_enc_keypack2"); +-#endif +- +- retval = cms_signeddata_verify(context, plg_cryptoctx, req_cryptoctx, +- id_cryptoctx, CMS_ENVEL_SERVER, +- require_crl_checking, +- vfy_buf, vfy_buf_len, +- data, data_len, NULL, NULL, NULL); +- +- if (!retval) +- pkiDebug("PKCS7 Verification Success\n"); +- else { +- pkiDebug("PKCS7 Verification Failure\n"); +- goto cleanup; +- } +- +- retval = 0; +- +-cleanup: +- +- if (p7 != NULL) +- PKCS7_free(p7); +- free(tmp_buf); +- free(tmp_buf2); +- +- return retval; +-} +- + static krb5_error_code + crypto_retrieve_X509_sans(krb5_context context, + pkinit_plg_crypto_context plgctx, +@@ -3396,70 +3203,6 @@ pkinit_pkcs7type2oid(pkinit_plg_crypto_context cryptoctx, int pkcs7_type) + + } + +-static int +-wrap_signeddata(unsigned char *data, unsigned int data_len, +- unsigned char **out, unsigned int *out_len) +-{ +- +- unsigned int orig_len = 0, oid_len = 0, tot_len = 0; +- ASN1_OBJECT *oid = NULL; +- unsigned char *p = NULL; +- +- /* Get length to wrap the original data with SEQUENCE tag */ +- tot_len = orig_len = ASN1_object_size(1, (int)data_len, V_ASN1_SEQUENCE); +- +- /* Add the signedData OID and adjust lengths */ +- oid = OBJ_nid2obj(NID_pkcs7_signed); +- oid_len = i2d_ASN1_OBJECT(oid, NULL); +- +- tot_len = ASN1_object_size(1, (int)(orig_len+oid_len), V_ASN1_SEQUENCE); +- +- p = *out = malloc(tot_len); +- if (p == NULL) return -1; +- +- ASN1_put_object(&p, 1, (int)(orig_len+oid_len), +- V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); +- +- i2d_ASN1_OBJECT(oid, &p); +- +- ASN1_put_object(&p, 1, (int)data_len, 0, V_ASN1_CONTEXT_SPECIFIC); +- memcpy(p, data, data_len); +- +- *out_len = tot_len; +- +- return 0; +-} +- +-static int +-prepare_enc_data(const uint8_t *indata, int indata_len, uint8_t **outdata, +- int *outdata_len) +-{ +- int tag, class; +- long tlen, slen; +- const uint8_t *p = indata, *oldp; +- +- if (ASN1_get_object(&p, &slen, &tag, &class, indata_len) & 0x80) +- return EINVAL; +- if (tag != V_ASN1_SEQUENCE) +- return EINVAL; +- +- oldp = p; +- if (ASN1_get_object(&p, &tlen, &tag, &class, slen) & 0x80) +- return EINVAL; +- p += tlen; +- slen -= (p - oldp); +- +- if (ASN1_get_object(&p, &tlen, &tag, &class, slen) & 0x80) +- return EINVAL; +- +- *outdata = malloc(tlen); +- if (*outdata == NULL) +- return ENOMEM; +- memcpy(*outdata, p, tlen); +- *outdata_len = tlen; +- return 0; +-} +- + #ifndef WITHOUT_PKCS11 + static struct plugin_file_handle * + load_pkcs11_module(krb5_context context, const char *modname, +@@ -3778,169 +3521,6 @@ pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, + } + #endif + +-static krb5_error_code +-pkinit_decode_data_fs(krb5_context context, +- pkinit_identity_crypto_context id_cryptoctx, +- const uint8_t *data, unsigned int data_len, +- uint8_t **decoded_data, unsigned int *decoded_data_len) +-{ +- X509 *cert = sk_X509_value(id_cryptoctx->my_certs, +- id_cryptoctx->cert_index); +- EVP_PKEY *pkey = id_cryptoctx->my_key; +- EVP_PKEY_CTX *ctx = NULL; +- uint8_t *buf = NULL; +- size_t buf_len = 0; +- int ok; +- +- *decoded_data = NULL; +- *decoded_data_len = 0; +- +- if (cert != NULL && !X509_check_private_key(cert, pkey)) { +- pkiDebug("private key does not match certificate\n"); +- return KRB5KDC_ERR_PREAUTH_FAILED; +- } +- +- ctx = EVP_PKEY_CTX_new(pkey, NULL); +- if (ctx == NULL) +- return KRB5KDC_ERR_PREAUTH_FAILED; +- +- ok = EVP_PKEY_decrypt_init(ctx); +- if (!ok) +- goto cleanup; +- +- /* Get the length of the eventual output. */ +- ok = EVP_PKEY_decrypt(ctx, NULL, &buf_len, data, data_len); +- if (!ok) { +- pkiDebug("unable to decrypt received data\n"); +- goto cleanup; +- } +- +- buf = malloc(buf_len); +- if (buf == NULL) { +- ok = 0; +- goto cleanup; +- } +- +- ok = EVP_PKEY_decrypt(ctx, buf, &buf_len, data, data_len); +- if (!ok) { +- pkiDebug("unable to decrypt received data\n"); +- goto cleanup; +- } +- +- *decoded_data = buf; +- *decoded_data_len = buf_len; +- buf = NULL; +-cleanup: +- zapfree(buf, buf_len); +- EVP_PKEY_CTX_free(ctx); +- return ok ? 0 : KRB5KDC_ERR_PREAUTH_FAILED; +-} +- +-#ifndef WITHOUT_PKCS11 +-/* +- * When using the ActivCard Linux pkcs11 library (v2.0.1), the decrypt function +- * fails. By inserting an extra function call, which serves nothing but to +- * change the stack, we were able to work around the issue. If the ActivCard +- * library is fixed in the future, this function can be inlined back into the +- * caller. +- */ +-static CK_RV +-pkinit_C_Decrypt(pkinit_identity_crypto_context id_cryptoctx, +- CK_BYTE_PTR pEncryptedData, +- CK_ULONG ulEncryptedDataLen, +- CK_BYTE_PTR pData, +- CK_ULONG_PTR pulDataLen) +-{ +- CK_RV rv = CKR_OK; +- +- rv = id_cryptoctx->p11->C_Decrypt(id_cryptoctx->session, pEncryptedData, +- ulEncryptedDataLen, pData, pulDataLen); +- if (rv == CKR_OK) { +- pkiDebug("pData %p *pulDataLen %d\n", (void *) pData, +- (int) *pulDataLen); +- } +- return rv; +-} +- +-static krb5_error_code +-pkinit_decode_data_pkcs11(krb5_context context, +- pkinit_identity_crypto_context id_cryptoctx, +- const uint8_t *data, unsigned int data_len, +- uint8_t **decoded_data, +- unsigned int *decoded_data_len) +-{ +- CK_OBJECT_HANDLE obj; +- CK_ULONG len; +- CK_MECHANISM mech; +- uint8_t *cp; +- int r; +- +- *decoded_data = NULL; +- *decoded_data_len = 0; +- +- if (pkinit_open_session(context, id_cryptoctx)) { +- pkiDebug("can't open pkcs11 session\n"); +- return KRB5KDC_ERR_PREAUTH_FAILED; +- } +- +- pkinit_find_private_key(id_cryptoctx, CKA_DECRYPT, &obj); +- +- mech.mechanism = CKM_RSA_PKCS; +- mech.pParameter = NULL; +- mech.ulParameterLen = 0; +- +- if ((r = id_cryptoctx->p11->C_DecryptInit(id_cryptoctx->session, &mech, +- obj)) != CKR_OK) { +- pkiDebug("C_DecryptInit: 0x%x\n", (int) r); +- return KRB5KDC_ERR_PREAUTH_FAILED; +- } +- pkiDebug("data_len = %d\n", data_len); +- cp = malloc((size_t) data_len); +- if (cp == NULL) +- return ENOMEM; +- len = data_len; +- pkiDebug("session %p edata %p edata_len %d data %p datalen @%p %d\n", +- (void *) id_cryptoctx->session, (void *) data, (int) data_len, +- (void *) cp, (void *) &len, (int) len); +- r = pkinit_C_Decrypt(id_cryptoctx, (CK_BYTE_PTR) data, (CK_ULONG) data_len, +- cp, &len); +- if (r != CKR_OK) { +- pkiDebug("C_Decrypt: %s\n", pkcs11err(r)); +- if (r == CKR_BUFFER_TOO_SMALL) +- pkiDebug("decrypt %d needs %d\n", (int) data_len, (int) len); +- return KRB5KDC_ERR_PREAUTH_FAILED; +- } +- pkiDebug("decrypt %d -> %d\n", (int) data_len, (int) len); +- *decoded_data_len = len; +- *decoded_data = cp; +- +- return 0; +-} +-#endif +- +-krb5_error_code +-pkinit_decode_data(krb5_context context, +- pkinit_identity_crypto_context id_cryptoctx, +- const uint8_t *data, unsigned int data_len, +- uint8_t **decoded_data, unsigned int *decoded_data_len) +-{ +- krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; +- +- *decoded_data = NULL; +- *decoded_data_len = 0; +- +- if (id_cryptoctx->pkcs11_method != 1) +- retval = pkinit_decode_data_fs(context, id_cryptoctx, data, data_len, +- decoded_data, decoded_data_len); +-#ifndef WITHOUT_PKCS11 +- else +- retval = pkinit_decode_data_pkcs11(context, id_cryptoctx, data, +- data_len, decoded_data, decoded_data_len); +-#endif +- +- return retval; +-} +- + static krb5_error_code + pkinit_sign_data_fs(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, +@@ -5615,88 +5195,6 @@ cleanup: + return retval; + } + +-/* Originally based on OpenSSL's PKCS7_dataDecode(), now modified to remove the +- * use of BIO objects and to fit the PKINIT internal interfaces. */ +-static int +-pkcs7_decrypt(krb5_context context, +- pkinit_identity_crypto_context id_cryptoctx, PKCS7 *p7, +- unsigned char **data_out, unsigned int *len_out) +-{ +- krb5_error_code ret; +- int ok = 0, plaintext_len = 0, final_len; +- unsigned int keylen = 0, eklen = 0, blocksize; +- unsigned char *ek = NULL, *tkey = NULL, *plaintext = NULL, *use_key; +- ASN1_OCTET_STRING *data_body = p7->d.enveloped->enc_data->enc_data; +- const EVP_CIPHER *evp_cipher; +- EVP_CIPHER_CTX *evp_ctx = NULL; +- X509_ALGOR *enc_alg = p7->d.enveloped->enc_data->algorithm; +- STACK_OF(PKCS7_RECIP_INFO) *rsk = p7->d.enveloped->recipientinfo; +- PKCS7_RECIP_INFO *ri = NULL; +- +- *data_out = NULL; +- *len_out = 0; +- +- p7->state = PKCS7_S_HEADER; +- +- /* RFC 4556 section 3.2.3.2 requires that there be exactly one +- * recipientInfo. */ +- if (sk_PKCS7_RECIP_INFO_num(rsk) != 1) { +- pkiDebug("invalid number of EnvelopedData RecipientInfos\n"); +- return 0; +- } +- ri = sk_PKCS7_RECIP_INFO_value(rsk, 0); +- +- evp_cipher = EVP_get_cipherbyobj(enc_alg->algorithm); +- if (evp_cipher == NULL) +- goto cleanup; +- keylen = EVP_CIPHER_key_length(evp_cipher); +- blocksize = EVP_CIPHER_block_size(evp_cipher); +- +- evp_ctx = EVP_CIPHER_CTX_new(); +- if (evp_ctx == NULL) +- goto cleanup; +- if (!EVP_DecryptInit(evp_ctx, evp_cipher, NULL, NULL) || +- EVP_CIPHER_asn1_to_param(evp_ctx, enc_alg->parameter) <= 0) +- goto cleanup; +- +- /* Generate a random symmetric key to avoid exposing timing data if RSA +- * decryption fails the padding check. */ +- tkey = malloc(keylen); +- if (tkey == NULL || !EVP_CIPHER_CTX_rand_key(evp_ctx, tkey)) +- goto cleanup; +- +- /* Decrypt the secret key with the private key. */ +- ret = pkinit_decode_data(context, id_cryptoctx, +- ASN1_STRING_get0_data(ri->enc_key), +- ASN1_STRING_length(ri->enc_key), &ek, &eklen); +- use_key = (ret || eklen != keylen) ? tkey : ek; +- +- /* Allocate a plaintext buffer and decrypt data_body into it. */ +- plaintext = malloc(data_body->length + blocksize); +- if (plaintext == NULL) +- goto cleanup; +- if (!EVP_DecryptInit(evp_ctx, NULL, use_key, NULL)) +- goto cleanup; +- if (!EVP_DecryptUpdate(evp_ctx, plaintext, &plaintext_len, +- data_body->data, data_body->length)) +- goto cleanup; +- if (!EVP_DecryptFinal(evp_ctx, plaintext + plaintext_len, &final_len)) +- goto cleanup; +- plaintext_len += final_len; +- +- *len_out = plaintext_len; +- *data_out = plaintext; +- plaintext = NULL; +- ok = 1; +- +-cleanup: +- EVP_CIPHER_CTX_free(evp_ctx); +- zapfree(plaintext, plaintext_len); +- zapfree(ek, eklen); +- zapfree(tkey, keylen); +- return ok; +-} +- + #ifdef DEBUG_DH + static void + print_dh(DH * dh, char *msg) +diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c +index 4c3d46bf5a..19db695a4d 100644 +--- a/src/plugins/preauth/pkinit/pkinit_lib.c ++++ b/src/plugins/preauth/pkinit/pkinit_lib.c +@@ -50,7 +50,6 @@ pkinit_init_req_opts(pkinit_req_opts **reqopts) + opts->require_eku = 1; + opts->accept_secondary_eku = 0; + opts->allow_upn = 0; +- opts->dh_or_rsa = DH_PROTOCOL; + opts->require_crl_checking = 0; + opts->dh_size = PKINIT_DEFAULT_DH_MIN_BITS; + +@@ -79,7 +78,6 @@ pkinit_init_plg_opts(pkinit_plg_opts **plgopts) + + opts->require_eku = 1; + opts->accept_secondary_eku = 0; +- opts->dh_or_rsa = DH_PROTOCOL; + opts->allow_upn = 0; + opts->require_crl_checking = 0; + opts->require_freshness = 0; +diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c +index 768a4e559f..aab21f951c 100644 +--- a/src/plugins/preauth/pkinit/pkinit_srv.c ++++ b/src/plugins/preauth/pkinit/pkinit_srv.c +@@ -821,132 +821,55 @@ pkinit_server_return_padata(krb5_context context, + retval = ENOMEM; + goto cleanup; + } +- /* let's assume it's RSA. we'll reset it to DH if needed */ +- rep->choice = choice_pa_pk_as_rep_encKeyPack; + +- if (reqctx->rcv_auth_pack != NULL && +- reqctx->rcv_auth_pack->clientPublicValue.length > 0) { +- rep->choice = choice_pa_pk_as_rep_dhInfo; +- +- pkiDebug("received DH key delivery AS REQ\n"); +- retval = server_process_dh(context, plgctx->cryptoctx, +- reqctx->cryptoctx, plgctx->idctx, +- &dh_pubkey, &dh_pubkey_len, +- &server_key, &server_key_len); +- if (retval) { +- pkiDebug("failed to process/create dh parameters\n"); +- goto cleanup; +- } +- +- /* +- * This is DH, so don't generate the key until after we +- * encode the reply, because the encoded reply is needed +- * to generate the key in some cases. +- */ +- +- dhkey_info.subjectPublicKey.length = dh_pubkey_len; +- dhkey_info.subjectPublicKey.data = (char *)dh_pubkey; +- dhkey_info.nonce = request->nonce; +- dhkey_info.dhKeyExpiration = 0; +- +- retval = k5int_encode_krb5_kdc_dh_key_info(&dhkey_info, +- &encoded_dhkey_info); +- if (retval) { +- pkiDebug("encode_krb5_kdc_dh_key_info failed\n"); +- goto cleanup; +- } +-#ifdef DEBUG_ASN1 +- print_buffer_bin((unsigned char *)encoded_dhkey_info->data, +- encoded_dhkey_info->length, +- "/tmp/kdc_dh_key_info"); +-#endif +- +- retval = cms_signeddata_create(context, plgctx->cryptoctx, +- reqctx->cryptoctx, plgctx->idctx, +- CMS_SIGN_SERVER, +- (unsigned char *) +- encoded_dhkey_info->data, +- encoded_dhkey_info->length, +- (unsigned char **) +- &rep->u.dh_Info.dhSignedData.data, +- &rep->u.dh_Info.dhSignedData.length); +- if (retval) { +- pkiDebug("failed to create pkcs7 signed data\n"); +- goto cleanup; +- } +- +- } else { +- pkiDebug("received RSA key delivery AS REQ\n"); +- +- init_krb5_reply_key_pack(&key_pack); +- if (key_pack == NULL) { +- retval = ENOMEM; +- goto cleanup; +- } ++ if (reqctx->rcv_auth_pack == NULL || ++ reqctx->rcv_auth_pack->clientPublicValue.length == 0) { ++ retval = KRB5KDC_ERR_PREAUTH_FAILED; ++ k5_setmsg(context, retval, _("Unsupported PKINIT RSA request")); ++ goto cleanup; ++ } + +- retval = krb5_c_make_random_key(context, enctype, &key_pack->replyKey); +- if (retval) { +- pkiDebug("unable to make a session key\n"); +- goto cleanup; +- } ++ rep->choice = choice_pa_pk_as_rep_dhInfo; + +- retval = krb5_c_make_checksum(context, 0, &key_pack->replyKey, +- KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, +- req_pkt, &key_pack->asChecksum); +- if (retval) { +- pkiDebug("unable to calculate AS REQ checksum\n"); +- goto cleanup; +- } +-#ifdef DEBUG_CKSUM +- pkiDebug("calculating checksum on buf size = %d\n", req_pkt->length); +- print_buffer(req_pkt->data, req_pkt->length); +- pkiDebug("checksum size = %d\n", key_pack->asChecksum.length); +- print_buffer(key_pack->asChecksum.contents, +- key_pack->asChecksum.length); +- pkiDebug("encrypting key (%d)\n", key_pack->replyKey.length); +- print_buffer(key_pack->replyKey.contents, key_pack->replyKey.length); +-#endif ++ retval = server_process_dh(context, plgctx->cryptoctx, reqctx->cryptoctx, ++ plgctx->idctx, &dh_pubkey, &dh_pubkey_len, ++ &server_key, &server_key_len); ++ if (retval) { ++ pkiDebug("failed to process/create dh parameters\n"); ++ goto cleanup; ++ } + +- retval = k5int_encode_krb5_reply_key_pack(key_pack, +- &encoded_key_pack); +- if (retval) { +- pkiDebug("failed to encode reply_key_pack\n"); +- goto cleanup; +- } ++ dhkey_info.subjectPublicKey.length = dh_pubkey_len; ++ dhkey_info.subjectPublicKey.data = (char *)dh_pubkey; ++ dhkey_info.nonce = request->nonce; ++ dhkey_info.dhKeyExpiration = 0; + +- rep->choice = choice_pa_pk_as_rep_encKeyPack; +- retval = cms_envelopeddata_create(context, plgctx->cryptoctx, +- reqctx->cryptoctx, plgctx->idctx, +- padata->pa_type, +- (unsigned char *) +- encoded_key_pack->data, +- encoded_key_pack->length, +- (unsigned char **) +- &rep->u.encKeyPack.data, +- &rep->u.encKeyPack.length); +- if (retval) { +- pkiDebug("failed to create pkcs7 enveloped data: %s\n", +- error_message(retval)); +- goto cleanup; +- } ++ retval = k5int_encode_krb5_kdc_dh_key_info(&dhkey_info, ++ &encoded_dhkey_info); ++ if (retval) { ++ pkiDebug("encode_krb5_kdc_dh_key_info failed\n"); ++ goto cleanup; ++ } + #ifdef DEBUG_ASN1 +- print_buffer_bin((unsigned char *)encoded_key_pack->data, +- encoded_key_pack->length, +- "/tmp/kdc_key_pack"); +- print_buffer_bin(rep->u.encKeyPack.data, rep->u.encKeyPack.length, +- "/tmp/kdc_enc_key_pack"); ++ print_buffer_bin((unsigned char *)encoded_dhkey_info->data, ++ encoded_dhkey_info->length, "/tmp/kdc_dh_key_info"); + #endif + +- retval = cb->replace_reply_key(context, rock, &key_pack->replyKey, +- FALSE); +- if (retval) +- goto cleanup; ++ retval = cms_signeddata_create(context, plgctx->cryptoctx, ++ reqctx->cryptoctx, plgctx->idctx, ++ CMS_SIGN_SERVER, ++ (unsigned char *)encoded_dhkey_info->data, ++ encoded_dhkey_info->length, ++ (unsigned char **) ++ &rep->u.dh_Info.dhSignedData.data, ++ &rep->u.dh_Info.dhSignedData.length); ++ if (retval) { ++ pkiDebug("failed to create pkcs7 signed data\n"); ++ goto cleanup; + } + +- if (rep->choice == choice_pa_pk_as_rep_dhInfo && +- ((reqctx->rcv_auth_pack != NULL && +- reqctx->rcv_auth_pack->supportedKDFs != NULL))) { +- ++ if (reqctx->rcv_auth_pack != NULL && ++ reqctx->rcv_auth_pack->supportedKDFs != NULL) { + /* If using the alg-agility KDF, put the algorithm in the reply + * before encoding it. + */ +@@ -973,41 +896,36 @@ pkinit_server_return_padata(krb5_context context, + "/tmp/kdc_as_rep"); + #endif + +- /* If this is DH, we haven't computed the key yet, so do it now. */ +- if (rep->choice == choice_pa_pk_as_rep_dhInfo) { +- +- /* If mutually supported KDFs were found, use the algorithm agility +- * KDF. */ +- if (rep->u.dh_Info.kdfID) { +- secret.data = (char *)server_key; +- secret.length = server_key_len; ++ /* If mutually supported KDFs were found, use the algorithm agility KDF. */ ++ if (rep->u.dh_Info.kdfID) { ++ secret.data = (char *)server_key; ++ secret.length = server_key_len; + +- retval = pkinit_alg_agility_kdf(context, &secret, +- rep->u.dh_Info.kdfID, +- request->client, request->server, +- enctype, req_pkt, out_data, +- &reply_key); +- if (retval) { +- pkiDebug("pkinit_alg_agility_kdf failed: %s\n", +- error_message(retval)); +- goto cleanup; +- } ++ retval = pkinit_alg_agility_kdf(context, &secret, rep->u.dh_Info.kdfID, ++ request->client, request->server, ++ enctype, req_pkt, out_data, ++ &reply_key); ++ if (retval) { ++ pkiDebug("pkinit_alg_agility_kdf failed: %s\n", ++ error_message(retval)); ++ goto cleanup; ++ } + +- /* Otherwise, use the older octetstring2key() function */ +- } else { +- retval = pkinit_octetstring2key(context, enctype, server_key, ++ /* Otherwise, use the older octetstring2key() function */ ++ } else { ++ retval = pkinit_octetstring2key(context, enctype, server_key, + server_key_len, &reply_key); +- if (retval) { +- pkiDebug("pkinit_octetstring2key failed: %s\n", +- error_message(retval)); +- goto cleanup; +- } +- } +- retval = cb->replace_reply_key(context, rock, &reply_key, FALSE); +- if (retval) ++ if (retval) { ++ pkiDebug("pkinit_octetstring2key failed: %s\n", ++ error_message(retval)); + goto cleanup; ++ } + } + ++ retval = cb->replace_reply_key(context, rock, &reply_key, FALSE); ++ if (retval) ++ goto cleanup; ++ + *send_pa = malloc(sizeof(krb5_pa_data)); + if (*send_pa == NULL) { + retval = ENOMEM; +diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h +index 5ee39c085c..d385759145 100644 +--- a/src/plugins/preauth/pkinit/pkinit_trace.h ++++ b/src/plugins/preauth/pkinit/pkinit_trace.h +@@ -58,19 +58,10 @@ + TRACE(c, "PKINIT client verified DH reply") + #define TRACE_PKINIT_CLIENT_REP_DH_FAIL(c) \ + TRACE(c, "PKINIT client could not verify DH reply") +-#define TRACE_PKINIT_CLIENT_REP_RSA(c) \ +- TRACE(c, "PKINIT client verified RSA reply") +-#define TRACE_PKINIT_CLIENT_REP_RSA_KEY(c, keyblock, cksum) \ +- TRACE(c, "PKINIT client retrieved reply key {keyblock} from RSA " \ +- "reply (checksum {cksum})", keyblock, cksum) +-#define TRACE_PKINIT_CLIENT_REP_RSA_FAIL(c) \ +- TRACE(c, "PKINIT client could not verify RSA reply") + #define TRACE_PKINIT_CLIENT_REQ_CHECKSUM(c, cksum) \ + TRACE(c, "PKINIT client computed kdc-req-body checksum {cksum}", cksum) + #define TRACE_PKINIT_CLIENT_REQ_DH(c) \ + TRACE(c, "PKINIT client making DH request") +-#define TRACE_PKINIT_CLIENT_REQ_RSA(c) \ +- TRACE(c, "PKINIT client making RSA request") + #define TRACE_PKINIT_CLIENT_SAN_CONFIG_DNSNAME(c, host) \ + TRACE(c, "PKINIT client config accepts KDC dNSName SAN {str}", host) + #define TRACE_PKINIT_CLIENT_SAN_MATCH_DNSNAME(c, host) \ +diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py +index ec2356ea22..62e6c426d3 100755 +--- a/src/tests/t_pkinit.py ++++ b/src/tests/t_pkinit.py +@@ -179,13 +179,6 @@ id_conf = {'realms': {'$realm': {'pkinit_identities': [file_identity + 'X', + id_env = realm.special_env('idconf', False, krb5_conf=id_conf) + realm.kinit(realm.user_princ, expected_trace=msgs, env=id_env) + +-# Try again using RSA instead of DH. +-mark('FILE identity, no password, RSA') +-realm.pkinit(realm.user_princ, flags=['-X', 'flag_RSA_PROTOCOL=yes'], +- expected_trace=('PKINIT client making RSA request', +- 'PKINIT client verified RSA reply')) +-realm.klist(realm.user_princ) +- + # Test a DH parameter renegotiation by temporarily setting a 4096-bit + # minimum on the KDC. (Preauth type 16 is PKINIT PA_PK_AS_REQ; + # 109 is PKINIT TD_DH_PARAMETERS; 133 is FAST PA-FX-COOKIE.) +diff --git a/src/windows/leash/htmlhelp/html/KINIT.htm b/src/windows/leash/htmlhelp/html/KINIT.htm +index eeee211a6e..46cb4a3ad8 100644 +--- a/src/windows/leash/htmlhelp/html/KINIT.htm ++++ b/src/windows/leash/htmlhelp/html/KINIT.htm +@@ -146,9 +146,6 @@ default credentials cache may vary between systems. If the KRB5CCNAME en + -S service_name + specify an alternate service name to use when getting initial + tickets. +- +- flag_RSA_PROTOCOL[=yes] +- specify use of RSA, rather than the default Diffie-Hellman protocol. + + +

ENVIRONMENT

+-- +2.46.0 + diff --git a/SOURCES/0025-Fix-various-issues-detected-by-static-analysis.patch b/SOURCES/0025-Fix-various-issues-detected-by-static-analysis.patch new file mode 100644 index 0000000..fb60c0f --- /dev/null +++ b/SOURCES/0025-Fix-various-issues-detected-by-static-analysis.patch @@ -0,0 +1,175 @@ +From 5464ad5b64f7ce7c3d78082352189af7c8feb95f Mon Sep 17 00:00:00 2001 +From: Julien Rische +Date: Fri, 6 Sep 2024 17:18:11 +0200 +Subject: [PATCH] Fix various issues detected by static analysis + +(cherry picked from commit 53d352949941ee236461658d01f03c37abafc6f6) +--- + src/clients/klist/klist.c | 13 +++++++------ + src/kadmin/dbutil/dump.c | 5 +++++ + src/kdc/ndr.c | 2 +- + src/lib/kdb/decrypt_key.c | 2 +- + src/lib/rpc/svc_auth_gss.c | 5 ++++- + src/lib/rpc/svc_udp.c | 13 +++++++------ + src/util/support/threads.c | 2 -- + 7 files changed, 25 insertions(+), 17 deletions(-) + +diff --git a/src/clients/klist/klist.c b/src/clients/klist/klist.c +index 27cf0ee11b..9db66f6072 100644 +--- a/src/clients/klist/klist.c ++++ b/src/clients/klist/klist.c +@@ -666,7 +666,7 @@ show_credential(krb5_creds *cred) + krb5_error_code ret; + krb5_ticket *tkt = NULL; + char *name = NULL, *sname = NULL, *tktsname, *flags; +- int extra_field = 0, ccol = 0, i; ++ int extra_field = 0, ccol = 0, i, r; + krb5_boolean is_config = krb5_is_config_principal(context, cred->server); + + ret = krb5_unparse_name(context, cred->client, &name); +@@ -696,11 +696,12 @@ show_credential(krb5_creds *cred) + fputs("config: ", stdout); + ccol = 8; + for (i = 1; i < cred->server->length; i++) { +- ccol += printf("%s%.*s%s", +- i > 1 ? "(" : "", +- (int)cred->server->data[i].length, +- cred->server->data[i].data, +- i > 1 ? ")" : ""); ++ r = printf("%s%.*s%s", i > 1 ? "(" : "", ++ (int)cred->server->data[i].length, ++ cred->server->data[i].data, ++ i > 1 ? ")" : ""); ++ if (r >= 0) ++ ccol += r; + } + fputs(" = ", stdout); + ccol += 3; +diff --git a/src/kadmin/dbutil/dump.c b/src/kadmin/dbutil/dump.c +index 4d6cc0bdf9..feb053d834 100644 +--- a/src/kadmin/dbutil/dump.c ++++ b/src/kadmin/dbutil/dump.c +@@ -704,6 +704,11 @@ process_k5beta7_princ(krb5_context context, const char *fname, FILE *filep, + + dbentry->len = u1; + dbentry->n_key_data = u4; ++ ++ if (u5 > UINT16_MAX) { ++ load_err(fname, *linenop, _("invalid principal extra data size")); ++ goto fail; ++ } + dbentry->e_length = u5; + + if (kp != NULL) { +diff --git a/src/kdc/ndr.c b/src/kdc/ndr.c +index d438408ee2..38be9fe42a 100644 +--- a/src/kdc/ndr.c ++++ b/src/kdc/ndr.c +@@ -242,7 +242,7 @@ ndr_enc_delegation_info(struct pac_s4u_delegation_info *in, krb5_data *out) + { + krb5_error_code ret; + size_t i; +- struct k5buf b; ++ struct k5buf b = EMPTY_K5BUF; + struct encoded_wchars pt_encoded = { 0 }, *tss_encoded = NULL; + uint32_t pointer = 0; + +diff --git a/src/lib/kdb/decrypt_key.c b/src/lib/kdb/decrypt_key.c +index 82bbed6312..c971793c9d 100644 +--- a/src/lib/kdb/decrypt_key.c ++++ b/src/lib/kdb/decrypt_key.c +@@ -60,7 +60,7 @@ krb5_dbe_def_decrypt_key_data(krb5_context context, const krb5_keyblock *mkey, + krb5_keyblock *dbkey_out, + krb5_keysalt *keysalt_out) + { +- krb5_error_code ret; ++ krb5_error_code ret = KRB5_CRYPTO_INTERNAL; + int16_t keylen; + krb5_enc_data cipher; + krb5_data plain = empty_data(); +diff --git a/src/lib/rpc/svc_auth_gss.c b/src/lib/rpc/svc_auth_gss.c +index 98d601c8ab..461e5de542 100644 +--- a/src/lib/rpc/svc_auth_gss.c ++++ b/src/lib/rpc/svc_auth_gss.c +@@ -297,7 +297,7 @@ svcauth_gss_validate(struct svc_req *rqst, struct svc_rpc_gss_data *gd, struct r + struct opaque_auth *oa; + gss_buffer_desc rpcbuf, checksum; + OM_uint32 maj_stat, min_stat, qop_state; +- u_char rpchdr[128]; ++ u_char rpchdr[32 + MAX_AUTH_BYTES]; + int32_t *buf; + + log_debug("in svcauth_gss_validate()"); +@@ -315,6 +315,8 @@ svcauth_gss_validate(struct svc_req *rqst, struct svc_rpc_gss_data *gd, struct r + return (FALSE); + + buf = (int32_t *)(void *)rpchdr; ++ ++ /* Write the 32 first bytes of the header. */ + IXDR_PUT_LONG(buf, msg->rm_xid); + IXDR_PUT_ENUM(buf, msg->rm_direction); + IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers); +@@ -323,6 +325,7 @@ svcauth_gss_validate(struct svc_req *rqst, struct svc_rpc_gss_data *gd, struct r + IXDR_PUT_LONG(buf, msg->rm_call.cb_proc); + IXDR_PUT_ENUM(buf, oa->oa_flavor); + IXDR_PUT_LONG(buf, oa->oa_length); ++ + if (oa->oa_length) { + memcpy((caddr_t)buf, oa->oa_base, oa->oa_length); + buf += RNDUP(oa->oa_length) / sizeof(int32_t); +diff --git a/src/lib/rpc/svc_udp.c b/src/lib/rpc/svc_udp.c +index 8ecbdf2b33..3aff277eb7 100644 +--- a/src/lib/rpc/svc_udp.c ++++ b/src/lib/rpc/svc_udp.c +@@ -248,8 +248,9 @@ static bool_t svcudp_reply( + { + struct svcudp_data *su = su_data(xprt); + XDR *xdrs = &su->su_xdrs; +- int slen; ++ u_int slen; + bool_t stat = FALSE; ++ ssize_t r; + + xdrproc_t xdr_results = NULL; + caddr_t xdr_location = 0; +@@ -272,12 +273,12 @@ static bool_t svcudp_reply( + if (xdr_replymsg(xdrs, msg) && + (!has_args || + (SVCAUTH_WRAP(xprt->xp_auth, xdrs, xdr_results, xdr_location)))) { +- slen = (int)XDR_GETPOS(xdrs); +- if (sendto(xprt->xp_sock, rpc_buffer(xprt), slen, 0, +- (struct sockaddr *)&(xprt->xp_raddr), xprt->xp_addrlen) +- == slen) { ++ slen = XDR_GETPOS(xdrs); ++ r = sendto(xprt->xp_sock, rpc_buffer(xprt), slen, 0, ++ (struct sockaddr *)&(xprt->xp_raddr), xprt->xp_addrlen); ++ if (r >= 0 && (u_int)r == slen) { + stat = TRUE; +- if (su->su_cache && slen >= 0) { ++ if (su->su_cache) { + cache_set(xprt, (uint32_t) slen); + } + } +diff --git a/src/util/support/threads.c b/src/util/support/threads.c +index be7e4c2e3f..4ded805b79 100644 +--- a/src/util/support/threads.c ++++ b/src/util/support/threads.c +@@ -118,7 +118,6 @@ struct tsd_block { + # pragma weak pthread_mutex_destroy + # pragma weak pthread_mutex_init + # pragma weak pthread_self +-# pragma weak pthread_equal + # pragma weak pthread_getspecific + # pragma weak pthread_setspecific + # pragma weak pthread_key_create +@@ -151,7 +150,6 @@ int krb5int_pthread_loaded (void) + || &pthread_mutex_destroy == 0 + || &pthread_mutex_init == 0 + || &pthread_self == 0 +- || &pthread_equal == 0 + /* Any program that's really multithreaded will have to be + able to create threads. */ + || &pthread_create == 0 +-- +2.46.0 + diff --git a/SOURCES/0026-Generate-and-verify-message-MACs-in-libkrad.patch b/SOURCES/0026-Generate-and-verify-message-MACs-in-libkrad.patch new file mode 100644 index 0000000..23e93d4 --- /dev/null +++ b/SOURCES/0026-Generate-and-verify-message-MACs-in-libkrad.patch @@ -0,0 +1,629 @@ +From 023dcf87d34e29649dd76d33ce7d896c2b6f61d2 Mon Sep 17 00:00:00 2001 +From: Julien Rische +Date: Thu, 22 Aug 2024 17:15:50 +0200 +Subject: [PATCH] Generate and verify message MACs in libkrad + +Implement some of the measures specified in +draft-ietf-radext-deprecating-radius-03 for mitigating the BlastRADIUS +attack (CVE-2024-3596): + +* Include a Message-Authenticator MAC as the first attribute when + generating a packet of type Access-Request, Access-Reject, + Access-Accept, or Access-Challenge (sections 5.2.1 and 5.2.4), if + the secret is non-empty. (An empty secret indicates the use of Unix + domain socket transport.) + +* Validate the Message-Authenticator MAC in received packets, if + present. + +FreeRADIUS enforces Message-Authenticator as of versions 3.2.5 and +3.0.27. libkrad must generate Message-Authenticator attributes in +order to remain compatible with these implementations. + +[ghudson@mit.edu: adjusted style and naming; simplified some +functions; edited commit message] + +ticket: 9142 (new) +tags: pullup +target_version: 1.21-next + +(cherry picked from commit 871125fea8ce0370a972bf65f7d1de63f619b06c) +--- + src/include/k5-int.h | 5 + + src/lib/crypto/krb/checksum_hmac_md5.c | 28 ++++ + src/lib/crypto/libk5crypto.exports | 1 + + src/lib/krad/attr.c | 17 ++ + src/lib/krad/attrset.c | 59 +++++-- + src/lib/krad/internal.h | 7 +- + src/lib/krad/packet.c | 206 +++++++++++++++++++++++-- + src/lib/krad/t_attrset.c | 2 +- + src/lib/krad/t_daemon.py | 3 +- + src/lib/krad/t_packet.c | 11 ++ + src/tests/t_otp.py | 3 + + 11 files changed, 311 insertions(+), 31 deletions(-) + +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index 69d6a6f569..b7789a2dd8 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -2403,4 +2403,9 @@ krb5_boolean + k5_sname_compare(krb5_context context, krb5_const_principal sname, + krb5_const_principal princ); + ++/* Generate an HMAC-MD5 keyed checksum as specified by RFC 2104. */ ++krb5_error_code ++k5_hmac_md5(const krb5_data *key, const krb5_crypto_iov *data, size_t num_data, ++ krb5_data *output); ++ + #endif /* _KRB5_INT_H */ +diff --git a/src/lib/crypto/krb/checksum_hmac_md5.c b/src/lib/crypto/krb/checksum_hmac_md5.c +index ec024f3966..a809388549 100644 +--- a/src/lib/crypto/krb/checksum_hmac_md5.c ++++ b/src/lib/crypto/krb/checksum_hmac_md5.c +@@ -92,3 +92,31 @@ cleanup: + free(hash_iov); + return ret; + } ++ ++krb5_error_code ++k5_hmac_md5(const krb5_data *key, const krb5_crypto_iov *data, size_t num_data, ++ krb5_data *output) ++{ ++ krb5_error_code ret; ++ const struct krb5_hash_provider *hash = &krb5int_hash_md5; ++ krb5_keyblock keyblock = { 0 }; ++ krb5_data hashed_key; ++ uint8_t hkeybuf[16]; ++ krb5_crypto_iov iov; ++ ++ /* Hash the key if it is longer than the block size. */ ++ if (key->length > hash->blocksize) { ++ hashed_key = make_data(hkeybuf, sizeof(hkeybuf)); ++ iov.flags = KRB5_CRYPTO_TYPE_DATA; ++ iov.data = *key; ++ ret = hash->hash(&iov, 1, &hashed_key); ++ if (ret) ++ return ret; ++ key = &hashed_key; ++ } ++ ++ keyblock.magic = KV5M_KEYBLOCK; ++ keyblock.length = key->length; ++ keyblock.contents = (uint8_t *)key->data; ++ return krb5int_hmac_keyblock(hash, &keyblock, data, num_data, output); ++} +diff --git a/src/lib/crypto/libk5crypto.exports b/src/lib/crypto/libk5crypto.exports +index d8ffa63304..00e0ce1812 100644 +--- a/src/lib/crypto/libk5crypto.exports ++++ b/src/lib/crypto/libk5crypto.exports +@@ -102,3 +102,4 @@ krb5_c_prfplus + krb5_c_derive_prfplus + k5_enctype_to_ssf + krb5int_c_deprecated_enctype ++k5_hmac_md5 +diff --git a/src/lib/krad/attr.c b/src/lib/krad/attr.c +index 42d354a3b5..65ed1d35e7 100644 +--- a/src/lib/krad/attr.c ++++ b/src/lib/krad/attr.c +@@ -125,6 +125,23 @@ static const attribute_record attributes[UCHAR_MAX] = { + {"NAS-Port-Type", 4, 4, NULL, NULL}, + {"Port-Limit", 4, 4, NULL, NULL}, + {"Login-LAT-Port", 1, MAX_ATTRSIZE, NULL, NULL}, ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for tunnelling */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for tunnelling */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for tunnelling */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for tunnelling */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for tunnelling */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for tunnelling */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for Apple Remote Access Protocol */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for Apple Remote Access Protocol */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for Apple Remote Access Protocol */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for Apple Remote Access Protocol */ ++ {NULL, 0, 0, NULL, NULL}, /* Reserved for Apple Remote Access Protocol */ ++ {NULL, 0, 0, NULL, NULL}, /* Password-Retry */ ++ {NULL, 0, 0, NULL, NULL}, /* Prompt */ ++ {NULL, 0, 0, NULL, NULL}, /* Connect-Info */ ++ {NULL, 0, 0, NULL, NULL}, /* Configuration-Token */ ++ {NULL, 0, 0, NULL, NULL}, /* EAP-Message */ ++ {"Message-Authenticator", MD5_DIGEST_SIZE, MD5_DIGEST_SIZE, NULL, NULL}, + }; + + /* Encode User-Password attribute. */ +diff --git a/src/lib/krad/attrset.c b/src/lib/krad/attrset.c +index 6ec031e320..e5457ebfd7 100644 +--- a/src/lib/krad/attrset.c ++++ b/src/lib/krad/attrset.c +@@ -164,15 +164,44 @@ krad_attrset_copy(const krad_attrset *set, krad_attrset **copy) + return 0; + } + ++/* Place an encoded attributes into outbuf at position *i. Increment *i by the ++ * length of the encoding. */ ++static krb5_error_code ++append_attr(krb5_context ctx, const char *secret, ++ const uint8_t *auth, krad_attr type, const krb5_data *data, ++ uint8_t outbuf[MAX_ATTRSETSIZE], size_t *i, krb5_boolean *is_fips) ++{ ++ uint8_t buffer[MAX_ATTRSIZE]; ++ size_t attrlen; ++ krb5_error_code retval; ++ ++ retval = kr_attr_encode(ctx, secret, auth, type, data, buffer, &attrlen, ++ is_fips); ++ if (retval) ++ return retval; ++ ++ if (attrlen > MAX_ATTRSETSIZE - *i - 2) ++ return EMSGSIZE; ++ ++ outbuf[(*i)++] = type; ++ outbuf[(*i)++] = attrlen + 2; ++ memcpy(outbuf + *i, buffer, attrlen); ++ *i += attrlen; ++ ++ return 0; ++} ++ + krb5_error_code + kr_attrset_encode(const krad_attrset *set, const char *secret, +- const unsigned char *auth, ++ const uint8_t *auth, krb5_boolean add_msgauth, + unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen, + krb5_boolean *is_fips) + { +- unsigned char buffer[MAX_ATTRSIZE]; + krb5_error_code retval; +- size_t i = 0, attrlen; ++ krad_attr msgauth_type = krad_attr_name2num("Message-Authenticator"); ++ const uint8_t zeroes[MD5_DIGEST_SIZE] = { 0 }; ++ krb5_data zerodata; ++ size_t i = 0; + attr *a; + + if (set == NULL) { +@@ -180,19 +209,21 @@ kr_attrset_encode(const krad_attrset *set, const char *secret, + return 0; + } + +- K5_TAILQ_FOREACH(a, &set->list, list) { +- retval = kr_attr_encode(set->ctx, secret, auth, a->type, &a->attr, +- buffer, &attrlen, is_fips); +- if (retval != 0) ++ if (add_msgauth) { ++ /* Encode Message-Authenticator as the first attribute, per ++ * draft-ietf-radext-deprecating-radius-03 section 5.2. */ ++ zerodata = make_data((uint8_t *)zeroes, MD5_DIGEST_SIZE); ++ retval = append_attr(set->ctx, secret, auth, msgauth_type, &zerodata, ++ outbuf, &i, is_fips); ++ if (retval) + return retval; ++ } + +- if (i + attrlen + 2 > MAX_ATTRSETSIZE) +- return EMSGSIZE; +- +- outbuf[i++] = a->type; +- outbuf[i++] = attrlen + 2; +- memcpy(&outbuf[i], buffer, attrlen); +- i += attrlen; ++ K5_TAILQ_FOREACH(a, &set->list, list) { ++ retval = append_attr(set->ctx, secret, auth, a->type, &a->attr, ++ outbuf, &i, is_fips); ++ if (retval) ++ return retval; + } + + *outlen = i; +diff --git a/src/lib/krad/internal.h b/src/lib/krad/internal.h +index a17b6f39b1..ca66f3ec68 100644 +--- a/src/lib/krad/internal.h ++++ b/src/lib/krad/internal.h +@@ -49,6 +49,8 @@ + #define UCHAR_MAX 255 + #endif + ++#define MD5_DIGEST_SIZE 16 ++ + /* RFC 2865 */ + #define MAX_ATTRSIZE (UCHAR_MAX - 2) + #define MAX_ATTRSETSIZE (KRAD_PACKET_SIZE_MAX - 20) +@@ -79,10 +81,11 @@ kr_attr_decode(krb5_context ctx, const char *secret, const unsigned char *auth, + krad_attr type, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen); + +-/* Encode the attributes into the buffer. */ ++/* Encode set into outbuf. If add_msgauth is true, include a zeroed ++ * Message-Authenticator as the first attribute. */ + krb5_error_code + kr_attrset_encode(const krad_attrset *set, const char *secret, +- const unsigned char *auth, ++ const uint8_t *auth, krb5_boolean add_msgauth, + unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen, + krb5_boolean *is_fips); + +diff --git a/src/lib/krad/packet.c b/src/lib/krad/packet.c +index c5446b890c..3c1a4d507e 100644 +--- a/src/lib/krad/packet.c ++++ b/src/lib/krad/packet.c +@@ -36,6 +36,7 @@ + typedef unsigned char uchar; + + /* RFC 2865 */ ++#define MSGAUTH_SIZE (2 + MD5_DIGEST_SIZE) + #define OFFSET_CODE 0 + #define OFFSET_ID 1 + #define OFFSET_LENGTH 2 +@@ -222,6 +223,106 @@ packet_set_attrset(krb5_context ctx, const char *secret, krad_packet *pkt) + return kr_attrset_decode(ctx, &tmp, secret, pkt_auth(pkt), &pkt->attrset); + } + ++/* Determine if a packet requires a Message-Authenticator attribute. */ ++static inline krb5_boolean ++requires_msgauth(const char *secret, krad_code code) ++{ ++ /* If no secret is provided, assume that the transport is a UNIX socket. ++ * Message-Authenticator is required only on UDP and TCP connections. */ ++ if (*secret == '\0') ++ return FALSE; ++ ++ /* ++ * Per draft-ietf-radext-deprecating-radius-03 sections 5.2.1 and 5.2.4, ++ * Message-Authenticator is required in Access-Request packets and all ++ * potential responses when UDP or TCP transport is used. ++ */ ++ return code == krad_code_name2num("Access-Request") || ++ code == krad_code_name2num("Access-Reject") || ++ code == krad_code_name2num("Access-Accept") || ++ code == krad_code_name2num("Access-Challenge"); ++} ++ ++/* Check if the packet has a Message-Authenticator attribute. */ ++static inline krb5_boolean ++has_pkt_msgauth(const krad_packet *pkt) ++{ ++ krad_attr msgauth_type = krad_attr_name2num("Message-Authenticator"); ++ ++ return krad_attrset_get(pkt->attrset, msgauth_type, 0) != NULL; ++} ++ ++/* Return the beginning of the Message-Authenticator attribute in pkt, or NULL ++ * if no such attribute is present. */ ++static const uint8_t * ++lookup_msgauth_addr(const krad_packet *pkt) ++{ ++ krad_attr msgauth_type = krad_attr_name2num("Message-Authenticator"); ++ size_t i; ++ uint8_t *p; ++ ++ i = OFFSET_ATTR; ++ while (i + 2 < pkt->pkt.length) { ++ p = (uint8_t *)offset(&pkt->pkt, i); ++ if (msgauth_type == *p) ++ return p; ++ i += p[1]; ++ } ++ ++ return NULL; ++} ++ ++/* ++ * Calculate the message authenticator MAC for pkt as specified in RFC 2869 ++ * section 5.14, placing the result in mac_out. Use the provided authenticator ++ * auth, which may be from pkt or from a corresponding request. ++ */ ++static krb5_error_code ++calculate_mac(const char *secret, const krad_packet *pkt, ++ const uint8_t auth[AUTH_FIELD_SIZE], ++ uint8_t mac_out[MD5_DIGEST_SIZE]) ++{ ++ uint8_t zeroed_msgauth[MSGAUTH_SIZE]; ++ krad_attr msgauth_type = krad_attr_name2num("Message-Authenticator"); ++ const uint8_t *msgauth_attr, *msgauth_end, *pkt_end; ++ krb5_crypto_iov input[5]; ++ krb5_data ksecr, mac; ++ ++ msgauth_attr = lookup_msgauth_addr(pkt); ++ if (msgauth_attr == NULL) ++ return EINVAL; ++ msgauth_end = msgauth_attr + MSGAUTH_SIZE; ++ pkt_end = (const uint8_t *)pkt->pkt.data + pkt->pkt.length; ++ ++ /* Read code, id, and length from the packet. */ ++ input[0].flags = KRB5_CRYPTO_TYPE_DATA; ++ input[0].data = make_data(pkt->pkt.data, OFFSET_AUTH); ++ ++ /* Read the provided authenticator. */ ++ input[1].flags = KRB5_CRYPTO_TYPE_DATA; ++ input[1].data = make_data((uint8_t *)auth, AUTH_FIELD_SIZE); ++ ++ /* Read any attributes before Message-Authenticator. */ ++ input[2].flags = KRB5_CRYPTO_TYPE_DATA; ++ input[2].data = make_data(pkt_attr(pkt), msgauth_attr - pkt_attr(pkt)); ++ ++ /* Read Message-Authenticator with the data bytes all set to zero, per RFC ++ * 2869 section 5.14. */ ++ zeroed_msgauth[0] = msgauth_type; ++ zeroed_msgauth[1] = MSGAUTH_SIZE; ++ memset(zeroed_msgauth + 2, 0, MD5_DIGEST_SIZE); ++ input[3].flags = KRB5_CRYPTO_TYPE_DATA; ++ input[3].data = make_data(zeroed_msgauth, MSGAUTH_SIZE); ++ ++ /* Read any attributes after Message-Authenticator. */ ++ input[4].flags = KRB5_CRYPTO_TYPE_DATA; ++ input[4].data = make_data((uint8_t *)msgauth_end, pkt_end - msgauth_end); ++ ++ mac = make_data(mac_out, MD5_DIGEST_SIZE); ++ ksecr = string2data((char *)secret); ++ return k5_hmac_md5(&ksecr, input, 5, &mac); ++} ++ + ssize_t + krad_packet_bytes_needed(const krb5_data *buffer) + { +@@ -255,6 +356,7 @@ krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, + krad_packet *pkt; + uchar id; + size_t attrset_len; ++ krb5_boolean msgauth_required; + + pkt = packet_new(); + if (pkt == NULL) { +@@ -274,9 +376,13 @@ krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, + if (retval != 0) + goto error; + ++ /* Determine if Message-Authenticator is required. */ ++ msgauth_required = (*secret != '\0' && ++ code == krad_code_name2num("Access-Request")); ++ + /* Encode the attributes. */ +- retval = kr_attrset_encode(set, secret, pkt_auth(pkt), pkt_attr(pkt), +- &attrset_len, &pkt->is_fips); ++ retval = kr_attrset_encode(set, secret, pkt_auth(pkt), msgauth_required, ++ pkt_attr(pkt), &attrset_len, &pkt->is_fips); + if (retval != 0) + goto error; + +@@ -285,6 +391,13 @@ krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, + pkt_code_set(pkt, code); + pkt_len_set(pkt, pkt->pkt.length); + ++ if (msgauth_required) { ++ /* Calculate and set the Message-Authenticator MAC. */ ++ retval = calculate_mac(secret, pkt, pkt_auth(pkt), pkt_attr(pkt) + 2); ++ if (retval != 0) ++ goto error; ++ } ++ + /* Copy the attrset for future use. */ + retval = packet_set_attrset(ctx, secret, pkt); + if (retval != 0) +@@ -307,14 +420,19 @@ krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code, + krb5_error_code retval; + krad_packet *pkt; + size_t attrset_len; ++ krb5_boolean msgauth_required; + + pkt = packet_new(); + if (pkt == NULL) + return ENOMEM; + ++ /* Determine if Message-Authenticator is required. */ ++ msgauth_required = requires_msgauth(secret, code); ++ + /* Encode the attributes. */ +- retval = kr_attrset_encode(set, secret, pkt_auth(request), pkt_attr(pkt), +- &attrset_len, &pkt->is_fips); ++ retval = kr_attrset_encode(set, secret, pkt_auth(request), ++ msgauth_required, pkt_attr(pkt), &attrset_len, ++ &pkt->is_fips); + if (retval != 0) + goto error; + +@@ -330,6 +448,18 @@ krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code, + if (retval != 0) + goto error; + ++ if (msgauth_required) { ++ /* ++ * Calculate and replace the Message-Authenticator MAC. Per RFC 2869 ++ * section 5.14, use the authenticator from the request, not from the ++ * response. ++ */ ++ retval = calculate_mac(secret, pkt, pkt_auth(request), ++ pkt_attr(pkt) + 2); ++ if (retval != 0) ++ goto error; ++ } ++ + /* Copy the attrset for future use. */ + retval = packet_set_attrset(ctx, secret, pkt); + if (retval != 0) +@@ -343,6 +473,34 @@ error: + return retval; + } + ++/* Verify the Message-Authenticator value in pkt, using the provided ++ * authenticator (which may be from pkt or from a corresponding request). */ ++static krb5_error_code ++verify_msgauth(const char *secret, const krad_packet *pkt, ++ const uint8_t auth[AUTH_FIELD_SIZE]) ++{ ++ uint8_t mac[MD5_DIGEST_SIZE]; ++ krad_attr msgauth_type = krad_attr_name2num("Message-Authenticator"); ++ const krb5_data *msgauth; ++ krb5_error_code retval; ++ ++ msgauth = krad_packet_get_attr(pkt, msgauth_type, 0); ++ if (msgauth == NULL) ++ return ENODATA; ++ ++ retval = calculate_mac(secret, pkt, auth, mac); ++ if (retval) ++ return retval; ++ ++ if (msgauth->length != MD5_DIGEST_SIZE) ++ return EMSGSIZE; ++ ++ if (k5_bcmp(mac, msgauth->data, MD5_DIGEST_SIZE) != 0) ++ return EBADMSG; ++ ++ return 0; ++} ++ + /* Decode a packet. */ + static krb5_error_code + decode_packet(krb5_context ctx, const char *secret, const krb5_data *buffer, +@@ -394,21 +552,35 @@ krad_packet_decode_request(krb5_context ctx, const char *secret, + krad_packet **reqpkt) + { + const krad_packet *tmp = NULL; ++ krad_packet *req; + krb5_error_code retval; + +- retval = decode_packet(ctx, secret, buffer, reqpkt); +- if (cb != NULL && retval == 0) { ++ retval = decode_packet(ctx, secret, buffer, &req); ++ if (retval) ++ return retval; ++ ++ /* Verify Message-Authenticator if present. */ ++ if (has_pkt_msgauth(req)) { ++ retval = verify_msgauth(secret, req, pkt_auth(req)); ++ if (retval) { ++ krad_packet_free(req); ++ return retval; ++ } ++ } ++ ++ if (cb != NULL) { + for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { + if (pkt_id_get(*reqpkt) == pkt_id_get(tmp)) + break; + } +- } + +- if (cb != NULL && (retval != 0 || tmp != NULL)) +- (*cb)(data, TRUE); ++ if (tmp != NULL) ++ (*cb)(data, TRUE); ++ } + ++ *reqpkt = req; + *duppkt = tmp; +- return retval; ++ return 0; + } + + krb5_error_code +@@ -435,9 +607,17 @@ krad_packet_decode_response(krb5_context ctx, const char *secret, + break; + } + +- /* If the authenticator matches, then the response is valid. */ +- if (memcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) == 0) +- break; ++ /* Verify the response authenticator. */ ++ if (k5_bcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) != 0) ++ continue; ++ ++ /* Verify Message-Authenticator if present. */ ++ if (has_pkt_msgauth(*rsppkt)) { ++ if (verify_msgauth(secret, *rsppkt, pkt_auth(tmp)) != 0) ++ continue; ++ } ++ ++ break; + } + } + +diff --git a/src/lib/krad/t_attrset.c b/src/lib/krad/t_attrset.c +index 4cdb8b7d8e..f9c66509bd 100644 +--- a/src/lib/krad/t_attrset.c ++++ b/src/lib/krad/t_attrset.c +@@ -63,7 +63,7 @@ main(void) + noerror(krad_attrset_add(set, krad_attr_name2num("User-Password"), &tmp)); + + /* Encode attrset. */ +- noerror(kr_attrset_encode(set, "foo", auth, buffer, &encode_len, ++ noerror(kr_attrset_encode(set, "foo", auth, FALSE, buffer, &encode_len, + &is_fips)); + krad_attrset_free(set); + +diff --git a/src/lib/krad/t_daemon.py b/src/lib/krad/t_daemon.py +index 4a3de079c7..647d4894eb 100755 +--- a/src/lib/krad/t_daemon.py ++++ b/src/lib/krad/t_daemon.py +@@ -40,6 +40,7 @@ DICTIONARY = """ + ATTRIBUTE\tUser-Name\t1\tstring + ATTRIBUTE\tUser-Password\t2\toctets + ATTRIBUTE\tNAS-Identifier\t32\tstring ++ATTRIBUTE\tMessage-Authenticator\t80\toctets + """ + + class TestServer(server.Server): +@@ -52,7 +53,7 @@ class TestServer(server.Server): + if key == "User-Password": + passwd = [pkt.PwDecrypt(x) for x in pkt[key]] + +- reply = self.CreateReplyPacket(pkt) ++ reply = self.CreateReplyPacket(pkt, message_authenticator=True) + if passwd == ['accept']: + reply.code = packet.AccessAccept + else: +diff --git a/src/lib/krad/t_packet.c b/src/lib/krad/t_packet.c +index c22489144f..104b6507a2 100644 +--- a/src/lib/krad/t_packet.c ++++ b/src/lib/krad/t_packet.c +@@ -172,6 +172,9 @@ main(int argc, const char **argv) + krb5_data username, password; + krb5_boolean auth = FALSE; + krb5_context ctx; ++ const krad_packet *dupreq; ++ const krb5_data *encpkt; ++ krad_packet *decreq; + + username = string2data("testUser"); + +@@ -184,9 +187,17 @@ main(int argc, const char **argv) + + password = string2data("accept"); + noerror(make_packet(ctx, &username, &password, &packets[ACCEPT_PACKET])); ++ encpkt = krad_packet_encode(packets[ACCEPT_PACKET]); ++ noerror(krad_packet_decode_request(ctx, "foo", encpkt, NULL, NULL, ++ &dupreq, &decreq)); ++ krad_packet_free(decreq); + + password = string2data("reject"); + noerror(make_packet(ctx, &username, &password, &packets[REJECT_PACKET])); ++ encpkt = krad_packet_encode(packets[REJECT_PACKET]); ++ noerror(krad_packet_decode_request(ctx, "foo", encpkt, NULL, NULL, ++ &dupreq, &decreq)); ++ krad_packet_free(decreq); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; +diff --git a/src/tests/t_otp.py b/src/tests/t_otp.py +index c3b820a411..dd5cdc5c26 100755 +--- a/src/tests/t_otp.py ++++ b/src/tests/t_otp.py +@@ -49,6 +49,7 @@ ATTRIBUTE User-Name 1 string + ATTRIBUTE User-Password 2 octets + ATTRIBUTE Service-Type 6 integer + ATTRIBUTE NAS-Identifier 32 string ++ATTRIBUTE Message-Authenticator 80 octets + ''' + + class RadiusDaemon(Process): +@@ -97,6 +98,8 @@ class RadiusDaemon(Process): + reply.code = packet.AccessReject + replyq['reply'] = False + ++ reply.add_message_authenticator() ++ + outq.put(replyq) + if addr is None: + sock.send(reply.ReplyPacket()) +-- +2.46.0 + diff --git a/SOURCES/0027-PKINIT-ECDH-support.patch b/SOURCES/0027-PKINIT-ECDH-support.patch new file mode 100644 index 0000000..76ad325 --- /dev/null +++ b/SOURCES/0027-PKINIT-ECDH-support.patch @@ -0,0 +1,1027 @@ +From 91ce58cea855b1b1b916ed7dbc8aeb59b32e6563 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Fri, 12 May 2023 15:38:46 -0400 +Subject: [PATCH] PKINIT ECDH support + +Add support for elliptic curve key exchange to PKINIT (RFC 5349 +section 4). Extend pkinit_dh_min_bits to allow the string values +"P-256", "P-384", and "P-521", using rough finite-field strength +equivalents to rank them relative to the Oakley Diffie-Hellman groups. + +When processing TD-DH-PARAMETERS on the client, only accept the three +Oakley groups or the three supported elliptic curve groups. +Previously we accepted any Diffie-Hellman parameters that passed +EVP_PKEY_param_check()/DH_check() and had equal or better bit strength +to the original proposal. + +ticket: 9095 (new) +(cherry picked from commit 0f870b1bcad960fd5319a3f97aafd7f4a289e2fb) +--- + doc/admin/conf_files/kdc_conf.rst | 7 +- + doc/admin/conf_files/krb5_conf.rst | 7 +- + src/plugins/preauth/pkinit/pkinit.h | 6 +- + src/plugins/preauth/pkinit/pkinit_clnt.c | 17 +- + src/plugins/preauth/pkinit/pkinit_constants.c | 27 + + src/plugins/preauth/pkinit/pkinit_crypto.h | 7 + + .../preauth/pkinit/pkinit_crypto_openssl.c | 470 ++++++++++++------ + .../preauth/pkinit/pkinit_crypto_openssl.h | 4 +- + src/plugins/preauth/pkinit/pkinit_lib.c | 3 - + src/plugins/preauth/pkinit/pkinit_srv.c | 17 +- + src/plugins/preauth/pkinit/pkinit_trace.h | 11 + + src/tests/t_pkinit.py | 12 + + 12 files changed, 405 insertions(+), 183 deletions(-) + +diff --git a/doc/admin/conf_files/kdc_conf.rst b/doc/admin/conf_files/kdc_conf.rst +index 846c58ed82..fb0593f281 100644 +--- a/doc/admin/conf_files/kdc_conf.rst ++++ b/doc/admin/conf_files/kdc_conf.rst +@@ -768,8 +768,11 @@ For information about the syntax of some of these options, see + be specified multiple times. + + **pkinit_dh_min_bits** +- Specifies the minimum number of bits the KDC is willing to accept +- for a client's Diffie-Hellman key. The default is 2048. ++ Specifies the minimum strength of Diffie-Hellman group the KDC is ++ willing to accept for key exchange. Valid values in order of ++ increasing strength are 1024, 2048, P-256, 4096, P-384, and P-521. ++ The default is 2048. (P-256, P-384, and P-521 are new in release ++ 1.22.) + + **pkinit_allow_upn** + Specifies that the KDC is willing to accept client certificates +diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst +index b7284c47df..dca52e1426 100644 +--- a/doc/admin/conf_files/krb5_conf.rst ++++ b/doc/admin/conf_files/krb5_conf.rst +@@ -1131,9 +1131,10 @@ PKINIT krb5.conf options + option is not recommended. + + **pkinit_dh_min_bits** +- Specifies the size of the Diffie-Hellman key the client will +- attempt to use. The acceptable values are 1024, 2048, and 4096. +- The default is 2048. ++ Specifies the group of the Diffie-Hellman key the client will ++ attempt to use. The acceptable values are 1024, 2048, P-256, ++ 4096, P-384, and P-521. The default is 2048. (P-256, P-384, and ++ P-521 are new in release 1.22.) + + **pkinit_identities** + Specifies the location(s) to be used to find the user's X.509 +diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h +index 5ab0f4bc28..7ba7155bb4 100644 +--- a/src/plugins/preauth/pkinit/pkinit.h ++++ b/src/plugins/preauth/pkinit/pkinit.h +@@ -59,6 +59,10 @@ + + #define PKINIT_DEFAULT_DH_MIN_BITS 2048 + #define PKINIT_DH_MIN_CONFIG_BITS 1024 ++/* Rough finite-field bit strength equivalents for the elliptic curve groups */ ++#define PKINIT_DH_P256_BITS 3072 ++#define PKINIT_DH_P384_BITS 7680 ++#define PKINIT_DH_P521_BITS 15360 + + #define KRB5_CONF_KDCDEFAULTS "kdcdefaults" + #define KRB5_CONF_LIBDEFAULTS "libdefaults" +@@ -101,8 +105,6 @@ static inline void pkiDebug (const char *fmt, ...) { } + #define OCTETDATA_TO_KRB5DATA(octd, k5d) \ + (k5d)->length = (octd)->length; (k5d)->data = (char *)(octd)->data; + +-extern const krb5_data dh_oid; +- + /* + * notes about crypto contexts: + * +diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c +index 54e7537600..b08022a214 100644 +--- a/src/plugins/preauth/pkinit/pkinit_clnt.c ++++ b/src/plugins/preauth/pkinit/pkinit_clnt.c +@@ -681,7 +681,7 @@ pkinit_client_profile(krb5_context context, + const krb5_data *realm) + { + const char *configured_identity; +- char *eku_string = NULL; ++ char *eku_string = NULL, *minbits = NULL; + + pkiDebug("pkinit_client_profile %p %p %p %p\n", + context, plgctx, reqctx, realm); +@@ -690,17 +690,10 @@ pkinit_client_profile(krb5_context context, + KRB5_CONF_PKINIT_REQUIRE_CRL_CHECKING, + reqctx->opts->require_crl_checking, + &reqctx->opts->require_crl_checking); +- pkinit_libdefault_integer(context, realm, +- KRB5_CONF_PKINIT_DH_MIN_BITS, +- reqctx->opts->dh_size, +- &reqctx->opts->dh_size); +- if (reqctx->opts->dh_size != 1024 && reqctx->opts->dh_size != 2048 +- && reqctx->opts->dh_size != 4096) { +- pkiDebug("%s: invalid value (%d) for pkinit_dh_min_bits, " +- "using default value (%d) instead\n", __FUNCTION__, +- reqctx->opts->dh_size, PKINIT_DEFAULT_DH_MIN_BITS); +- reqctx->opts->dh_size = PKINIT_DEFAULT_DH_MIN_BITS; +- } ++ pkinit_libdefault_string(context, realm, KRB5_CONF_PKINIT_DH_MIN_BITS, ++ &minbits); ++ reqctx->opts->dh_size = parse_dh_min_bits(context, minbits); ++ free(minbits); + pkinit_libdefault_string(context, realm, + KRB5_CONF_PKINIT_EKU_CHECKING, + &eku_string); +diff --git a/src/plugins/preauth/pkinit/pkinit_constants.c b/src/plugins/preauth/pkinit/pkinit_constants.c +index 1da482e0b4..10f8688ec2 100644 +--- a/src/plugins/preauth/pkinit/pkinit_constants.c ++++ b/src/plugins/preauth/pkinit/pkinit_constants.c +@@ -320,6 +320,33 @@ static const uint8_t o4096[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + ++/* Named curve prime256v1 (1.2.840.10045.3.1.7) as parameters for RFC 3279 ++ * section 2.3.5 id-ecPublicKey */ ++static const uint8_t p256[] = { ++ 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 ++}; ++ ++/* Named curve secp384r1 (1.3.132.0.34, from RFC 5480 section 2.1.1.1) as ++ * parameters for RFC 3279 section 2.3.5 id-ecPublicKey */ ++static const uint8_t p384[] = { ++ 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22 ++}; ++ ++/* Named curve secp521r1 (1.3.132.0.35, from RFC 5480 section 2.1.1.1) as ++ * parameters for RFC 3279 section 2.3.5 id-ecPublicKey */ ++static const uint8_t p521[] = { ++ 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23 ++}; ++ + const krb5_data oakley_1024 = { KV5M_DATA, sizeof(o1024), (char *)o1024 }; + const krb5_data oakley_2048 = { KV5M_DATA, sizeof(o2048), (char *)o2048 }; + const krb5_data oakley_4096 = { KV5M_DATA, sizeof(o4096), (char *)o4096 }; ++const krb5_data ec_p256 = { KV5M_DATA, sizeof(p256), (char *)p256 }; ++const krb5_data ec_p384 = { KV5M_DATA, sizeof(p384), (char *)p384 }; ++const krb5_data ec_p521 = { KV5M_DATA, sizeof(p521), (char *)p521 }; ++ ++/* RFC 3279 section 2.3.3 dhpublicnumber (1.2.840.10046.2.1) */ ++const krb5_data dh_oid = { 0, 7, "\x2A\x86\x48\xce\x3e\x02\x01" }; ++ ++/* RFC 3279 section 2.3.5 id-ecPublicKey (1.2.840.10045.2.1) */ ++const krb5_data ec_oid = { 0, 7, "\x2A\x86\x48\xCE\x3D\x02\x01" }; +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h +index 04199b45a4..fd876e4850 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto.h ++++ b/src/plugins/preauth/pkinit/pkinit_crypto.h +@@ -568,6 +568,11 @@ extern const krb5_data sha512_id; + extern const krb5_data oakley_1024; + extern const krb5_data oakley_2048; + extern const krb5_data oakley_4096; ++extern const krb5_data ec_p256; ++extern const krb5_data ec_p384; ++extern const krb5_data ec_p521; ++extern const krb5_data dh_oid; ++extern const krb5_data ec_oid; + + /** + * An ordered set of OIDs, stored as krb5_data, of KDF algorithms +@@ -590,4 +595,6 @@ crypto_req_cert_matching_data(krb5_context context, + pkinit_req_crypto_context reqctx, + pkinit_cert_matching_data **md_out); + ++int parse_dh_min_bits(krb5_context context, const char *str); ++ + #endif /* _PKINIT_CRYPTO_H */ +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +index 980a89edc1..75036f8655 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +@@ -181,6 +181,15 @@ compat_get0_DH(const EVP_PKEY *pkey) + + } + ++#define EVP_PKEY_get0_EC_KEY compat_get0_EC ++static EC_KEY * ++compat_get0_EC(const EVP_PKEY *pkey) ++{ ++ if (pkey->type != EVP_PKEY_EC) ++ return NULL; ++ return pkey->pkey.ec; ++} ++ + /* Return true if the cert c includes a key usage which doesn't include u. + * Define using direct member access for pre-1.1. */ + #define ku_reject(c, u) \ +@@ -260,37 +269,11 @@ decode_bn_der(const uint8_t *der, size_t len) + return bn; + } + +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L +-static int +-params_valid(EVP_PKEY *params) +-{ +- EVP_PKEY_CTX *ctx; +- int result; +- +- ctx = EVP_PKEY_CTX_new(params, NULL); +- if (ctx == NULL) +- return 0; +- result = EVP_PKEY_param_check(ctx); +- EVP_PKEY_CTX_free(ctx); +- return result == 1; +-} +-#else +-static int +-params_valid(EVP_PKEY *params) +-{ +- DH *dh; +- int codes; +- +- dh = EVP_PKEY_get0_DH(params); +- return (dh == NULL) ? 0 : (DH_check(dh, &codes) && codes == 0); +-} +-#endif +- + #if OPENSSL_VERSION_NUMBER >= 0x10100000L + + #if OPENSSL_VERSION_NUMBER >= 0x30000000L + static EVP_PKEY * +-decode_dh_params(const krb5_data *params_der) ++decode_params(const krb5_data *params_der, const char *type) + { + EVP_PKEY *pkey = NULL; + const uint8_t *inptr = (uint8_t *)params_der->data; +@@ -298,7 +281,7 @@ decode_dh_params(const krb5_data *params_der) + OSSL_DECODER_CTX *dctx; + int ok; + +- dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", "type-specific", "DHX", ++ dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", "type-specific", type, + EVP_PKEY_KEY_PARAMETERS, NULL, NULL); + if (dctx == NULL) + return NULL; +@@ -307,7 +290,15 @@ decode_dh_params(const krb5_data *params_der) + OSSL_DECODER_CTX_free(dctx); + return ok ? pkey : NULL; + } ++ ++static EVP_PKEY * ++decode_dh_params(const krb5_data *params_der) ++{ ++ return decode_params(params_der, "DHX"); ++} ++ + #else ++ + static EVP_PKEY * + decode_dh_params(const krb5_data *params_der) + { +@@ -320,6 +311,7 @@ decode_dh_params(const krb5_data *params_der) + DH_free(dh); + return pkey; + } ++ + #endif + + static krb5_error_code +@@ -520,6 +512,39 @@ cleanup: + + #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ ++static EVP_PKEY * ++decode_ec_params(const krb5_data *params_der) ++{ ++ return decode_params(params_der, "EC"); ++} ++ ++#else /* OPENSSL_VERSION_NUMBER < 0x30000000L */ ++ ++static EVP_PKEY * ++decode_ec_params(const krb5_data *params_der) ++{ ++ const uint8_t *p = (uint8_t *)params_der->data; ++ EC_KEY *eckey; ++ EVP_PKEY *pkey; ++ ++ eckey = d2i_ECParameters(NULL, &p, params_der->length); ++ if (eckey == NULL) ++ return NULL; ++ pkey = EVP_PKEY_new(); ++ if (pkey != NULL) { ++ if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { ++ EVP_PKEY_free(pkey); ++ pkey = NULL; ++ } ++ } ++ EC_KEY_free(eckey); ++ return pkey; ++} ++ ++#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ ++ + /* Attempt to specify padded Diffie-Hellman result derivation. Don't error out + * if this fails since we also detect short results and adjust them. */ + #if OPENSSL_VERSION_NUMBER >= 0x30000000L +@@ -551,7 +576,8 @@ dh_result(EVP_PKEY *pkey, EVP_PKEY *peer, + EVP_PKEY_CTX *derive_ctx = NULL; + int ok = 0; + uint8_t *buf = NULL; +- size_t len, dh_size = EVP_PKEY_get_size(pkey); ++ size_t len, result_size; ++ krb5_boolean ecc = (EVP_PKEY_id(pkey) == EVP_PKEY_EC); + + *result_out = NULL; + *len_out = 0; +@@ -561,24 +587,39 @@ dh_result(EVP_PKEY *pkey, EVP_PKEY *peer, + goto cleanup; + if (EVP_PKEY_derive_init(derive_ctx) <= 0) + goto cleanup; +- set_padded_derivation(derive_ctx); ++ if (!ecc) ++ set_padded_derivation(derive_ctx); + if (EVP_PKEY_derive_set_peer(derive_ctx, peer) <= 0) + goto cleanup; + +- buf = malloc(dh_size); ++ if (ecc) { ++ if (EVP_PKEY_derive(derive_ctx, NULL, &result_size) <= 0) ++ goto cleanup; ++ } else { ++ /* ++ * For finite-field Diffie-Hellman we must ensure that the result ++ * matches the key size (normally through padded derivation, but that ++ * isn't supported by OpenSSL 1.0 so we must check). ++ */ ++ result_size = EVP_PKEY_get_size(pkey); ++ } ++ buf = malloc(result_size); + if (buf == NULL) + goto cleanup; +- len = dh_size; ++ len = result_size; + if (EVP_PKEY_derive(derive_ctx, buf, &len) <= 0) + goto cleanup; +- if (len < dh_size) { /* only possible without padded derivation */ +- memmove(buf + (dh_size - len), buf, len); +- memset(buf, 0, dh_size - len); ++ ++ /* If we couldn't specify padded derivation for finite-field DH we may need ++ * to fix up the result by right-shifting it within the buffer. */ ++ if (len < result_size) { ++ memmove(buf + (result_size - len), buf, len); ++ memset(buf, 0, result_size - len); + } + + ok = 1; + *result_out = buf; +- *len_out = dh_size; ++ *len_out = result_size; + buf = NULL; + + cleanup: +@@ -592,13 +633,21 @@ static int + dh_pubkey_der(EVP_PKEY *pkey, uint8_t **pubkey_out, unsigned int *len_out) + { + BIGNUM *pubkey_bn = NULL; +- int len, ok; +- uint8_t *buf; +- +- if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pubkey_bn)) +- return 0; +- ok = encode_bn_der(pubkey_bn, &buf, &len); +- BN_free(pubkey_bn); ++ int len, ok = 0; ++ uint8_t *buf, *outptr; ++ ++ if (EVP_PKEY_id(pkey) == EVP_PKEY_EC) { ++ len = i2d_PublicKey(pkey, NULL); ++ if (len > 0 && (outptr = buf = malloc(len)) != NULL) { ++ (void)i2d_PublicKey(pkey, &outptr); ++ ok = 1; ++ } ++ } else { ++ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pubkey_bn)) ++ return 0; ++ ok = encode_bn_der(pubkey_bn, &buf, &len); ++ BN_free(pubkey_bn); ++ } + if (ok) { + *pubkey_out = buf; + *len_out = len; +@@ -610,19 +659,33 @@ static int + dh_pubkey_der(EVP_PKEY *pkey, uint8_t **pubkey_out, unsigned int *len_out) + { + const DH *dh; ++ EC_KEY *eckey; /* can be const when OpenSSL 1.0 dropped */ + const BIGNUM *pubkey_bn; +- uint8_t *buf; ++ uint8_t *buf, *outptr; + int len; + + dh = EVP_PKEY_get0_DH(pkey); +- if (dh == NULL) +- return 0; +- DH_get0_key(dh, &pubkey_bn, NULL); +- if (!encode_bn_der(pubkey_bn, &buf, &len)) +- return 0; +- *pubkey_out = buf; +- *len_out = len; +- return 1; ++ if (dh != NULL) { ++ DH_get0_key(dh, &pubkey_bn, NULL); ++ if (!encode_bn_der(pubkey_bn, &buf, &len)) ++ return 0; ++ *pubkey_out = buf; ++ *len_out = len; ++ return 1; ++ } ++ ++ eckey = EVP_PKEY_get0_EC_KEY(pkey); ++ if (eckey != NULL) { ++ len = i2o_ECPublicKey(eckey, NULL); ++ if (len > 0 && (outptr = buf = malloc(len)) != NULL) { ++ (void)i2o_ECPublicKey(eckey, &outptr); ++ *pubkey_out = buf; ++ *len_out = len; ++ return 1; ++ } ++ } ++ ++ return 0; + } + #endif + +@@ -686,17 +749,23 @@ compose_dh_pkey(EVP_PKEY *params, const uint8_t *pubkey_der, size_t der_len) + if (pkey == NULL) + goto cleanup; + +- pubkey_bn = decode_bn_der(pubkey_der, der_len); +- if (pubkey_bn == NULL) +- goto cleanup; +- binlen = EVP_PKEY_get_size(pkey); +- pubkey_bin = malloc(binlen); +- if (pubkey_bin == NULL) +- goto cleanup; +- if (BN_bn2binpad(pubkey_bn, pubkey_bin, binlen) != binlen) +- goto cleanup; +- if (EVP_PKEY_set1_encoded_public_key(pkey, pubkey_bin, binlen) != 1) +- goto cleanup; ++ if (EVP_PKEY_id(params) == EVP_PKEY_EC) { ++ if (d2i_PublicKey(EVP_PKEY_id(params), &pkey, &pubkey_der, ++ der_len) == NULL) ++ goto cleanup; ++ } else { ++ pubkey_bn = decode_bn_der(pubkey_der, der_len); ++ if (pubkey_bn == NULL) ++ goto cleanup; ++ binlen = EVP_PKEY_get_size(pkey); ++ pubkey_bin = malloc(binlen); ++ if (pubkey_bin == NULL) ++ goto cleanup; ++ if (BN_bn2binpad(pubkey_bn, pubkey_bin, binlen) != binlen) ++ goto cleanup; ++ if (EVP_PKEY_set1_encoded_public_key(pkey, pubkey_bin, binlen) != 1) ++ goto cleanup; ++ } + + pkey_ret = pkey; + pkey = NULL; +@@ -741,29 +810,60 @@ static EVP_PKEY * + compose_dh_pkey(EVP_PKEY *params, const uint8_t *pubkey_der, size_t der_len) + { + DH *dhparams, *dh = NULL; +- EVP_PKEY *pkey = NULL; ++ EVP_PKEY *pkey = NULL, *pkey_ret = NULL; + BIGNUM *pubkey_bn = NULL; ++ EC_KEY *params_eckey, *eckey = NULL; ++ const EC_GROUP *group; ++ ++ if (EVP_PKEY_id(params) == EVP_PKEY_EC) { ++ /* We would like to use EVP_PKEY_copy_parameters() and d2i_PublicKey(), ++ * but the latter is broken in OpenSSL 1.1.0-1.1.1a for EC keys. */ ++ params_eckey = EVP_PKEY_get0_EC_KEY(params); ++ if (params_eckey == NULL) ++ goto cleanup; ++ group = EC_KEY_get0_group(params_eckey); ++ eckey = EC_KEY_new(); ++ if (eckey == NULL) ++ goto cleanup; ++ if (!EC_KEY_set_group(eckey, group)) ++ goto cleanup; ++ if (o2i_ECPublicKey(&eckey, &pubkey_der, der_len) == NULL) ++ goto cleanup; ++ pkey = EVP_PKEY_new(); ++ if (pkey == NULL) ++ return NULL; ++ if (!EVP_PKEY_assign(pkey, EVP_PKEY_EC, eckey)) { ++ EVP_PKEY_free(pkey); ++ return NULL; ++ } ++ eckey = NULL; ++ } else { ++ pubkey_bn = decode_bn_der(pubkey_der, der_len); ++ if (pubkey_bn == NULL) ++ goto cleanup; + +- pubkey_bn = decode_bn_der(pubkey_der, der_len); +- if (pubkey_bn == NULL) +- goto cleanup; ++ dhparams = EVP_PKEY_get0_DH(params); ++ if (dhparams == NULL) ++ goto cleanup; ++ dh = dup_dh_params(dhparams); ++ if (dh == NULL) ++ goto cleanup; ++ if (!DH_set0_key(dh, pubkey_bn, NULL)) ++ goto cleanup; ++ pubkey_bn = NULL; + +- dhparams = EVP_PKEY_get0_DH(params); +- if (dhparams == NULL) +- goto cleanup; +- dh = dup_dh_params(dhparams); +- if (dh == NULL) +- goto cleanup; +- if (!DH_set0_key(dh, pubkey_bn, NULL)) +- goto cleanup; +- pubkey_bn = NULL; ++ pkey = dh_to_pkey(&dh); ++ } + +- pkey = dh_to_pkey(&dh); ++ pkey_ret = pkey; ++ pkey = NULL; + + cleanup: + BN_free(pubkey_bn); + DH_free(dh); +- return pkey; ++ EC_KEY_free(eckey); ++ EVP_PKEY_free(pkey); ++ return pkey_ret; + } + + #endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ +@@ -1032,7 +1132,6 @@ pkinit_init_req_crypto(pkinit_req_crypto_context *cryptoctx) + memset(ctx, 0, sizeof(*ctx)); + + ctx->client_pkey = NULL; +- ctx->received_params = NULL; + ctx->received_cert = NULL; + + *cryptoctx = ctx; +@@ -1054,7 +1153,6 @@ pkinit_fini_req_crypto(pkinit_req_crypto_context req_cryptoctx) + + pkiDebug("%s: freeing ctx at %p\n", __FUNCTION__, req_cryptoctx); + EVP_PKEY_free(req_cryptoctx->client_pkey); +- EVP_PKEY_free(req_cryptoctx->received_params); + X509_free(req_cryptoctx->received_cert); + + free(req_cryptoctx); +@@ -1258,9 +1356,9 @@ pkinit_fini_pkinit_oids(pkinit_plg_crypto_context ctx) + + static int + try_import_group(krb5_context context, const krb5_data *params, +- const char *name, EVP_PKEY **pkey_out) ++ const char *name, krb5_boolean ec, EVP_PKEY **pkey_out) + { +- *pkey_out = decode_dh_params(params); ++ *pkey_out = ec ? decode_ec_params(params) : decode_dh_params(params); + if (*pkey_out == NULL) + TRACE_PKINIT_DH_GROUP_UNAVAILABLE(context, name); + return (*pkey_out != NULL) ? 1 : 0; +@@ -1271,12 +1369,15 @@ pkinit_init_dh_params(krb5_context context, pkinit_plg_crypto_context plgctx) + { + int n = 0; + +- n += try_import_group(context, &oakley_1024, "MODP 2 (1024-bit)", ++ n += try_import_group(context, &oakley_1024, "MODP 2 (1024-bit)", FALSE, + &plgctx->dh_1024); +- n += try_import_group(context, &oakley_2048, "MODP 14 (2048-bit)", ++ n += try_import_group(context, &oakley_2048, "MODP 14 (2048-bit)", FALSE, + &plgctx->dh_2048); +- n += try_import_group(context, &oakley_4096, "MODP 16 (4096-bit)", ++ n += try_import_group(context, &oakley_4096, "MODP 16 (4096-bit)", FALSE, + &plgctx->dh_4096); ++ n += try_import_group(context, &ec_p256, "P-256", TRUE, &plgctx->ec_p256); ++ n += try_import_group(context, &ec_p384, "P-384", TRUE, &plgctx->ec_p384); ++ n += try_import_group(context, &ec_p521, "P-521", TRUE, &plgctx->ec_p521); + + if (n == 0) { + pkinit_fini_dh_params(plgctx); +@@ -1294,7 +1395,11 @@ pkinit_fini_dh_params(pkinit_plg_crypto_context plgctx) + EVP_PKEY_free(plgctx->dh_1024); + EVP_PKEY_free(plgctx->dh_2048); + EVP_PKEY_free(plgctx->dh_4096); ++ EVP_PKEY_free(plgctx->ec_p256); ++ EVP_PKEY_free(plgctx->ec_p384); ++ EVP_PKEY_free(plgctx->ec_p521); + plgctx->dh_1024 = plgctx->dh_2048 = plgctx->dh_4096 = NULL; ++ plgctx->ec_p256 = plgctx->ec_p384 = plgctx->ec_p521 = NULL; + } + + static krb5_error_code +@@ -2711,6 +2816,62 @@ cleanup: + return ret; + } + ++/* Return the equivalent finite-field bit strength of pkey if it matches a ++ * well-known group, or -1 if it doesn't. */ ++static int ++check_dh_wellknown(pkinit_plg_crypto_context cryptoctx, EVP_PKEY *pkey) ++{ ++ int nbits = EVP_PKEY_get_bits(pkey); ++ ++ if (nbits == 1024 && EVP_PKEY_parameters_eq(cryptoctx->dh_1024, pkey) == 1) ++ return nbits; ++ if (nbits == 2048 && EVP_PKEY_parameters_eq(cryptoctx->dh_2048, pkey) == 1) ++ return nbits; ++ if (nbits == 4096 && EVP_PKEY_parameters_eq(cryptoctx->dh_4096, pkey) == 1) ++ return nbits; ++ if (nbits == 256 && EVP_PKEY_parameters_eq(cryptoctx->ec_p256, pkey) == 1) ++ return PKINIT_DH_P256_BITS; ++ if (nbits == 384 && EVP_PKEY_parameters_eq(cryptoctx->ec_p384, pkey) == 1) ++ return PKINIT_DH_P384_BITS; ++ if (nbits == 521 && EVP_PKEY_parameters_eq(cryptoctx->ec_p521, pkey) == 1) ++ return PKINIT_DH_P521_BITS; ++ return -1; ++} ++ ++/* Return a short description of the Diffie-Hellman group with the given ++ * finite-field group size equivalent. */ ++static const char * ++group_desc(int dh_bits) ++{ ++ switch (dh_bits) { ++ case PKINIT_DH_P256_BITS: return "P-256"; ++ case PKINIT_DH_P384_BITS: return "P-384"; ++ case PKINIT_DH_P521_BITS: return "P-521"; ++ case 1024: return "1024-bit DH"; ++ case 2048: return "2048-bit DH"; ++ case 4096: return "4096-bit DH"; ++ } ++ return "(unknown)"; ++} ++ ++static EVP_PKEY * ++choose_dh_group(pkinit_plg_crypto_context plg_cryptoctx, int dh_size) ++{ ++ if (dh_size == 1024) ++ return plg_cryptoctx->dh_1024; ++ if (dh_size == 2048) ++ return plg_cryptoctx->dh_2048; ++ if (dh_size == 4096) ++ return plg_cryptoctx->dh_4096; ++ if (dh_size == PKINIT_DH_P256_BITS) ++ return plg_cryptoctx->ec_p256; ++ if (dh_size == PKINIT_DH_P384_BITS) ++ return plg_cryptoctx->ec_p384; ++ if (dh_size == PKINIT_DH_P521_BITS) ++ return plg_cryptoctx->ec_p521; ++ return NULL; ++} ++ + krb5_error_code + client_create_dh(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, +@@ -2723,16 +2884,10 @@ client_create_dh(krb5_context context, + + *spki_out = empty_data(); + +- if (cryptoctx->received_params != NULL) +- params = cryptoctx->received_params; +- else if (plg_cryptoctx->dh_1024 != NULL && dh_size == 1024) +- params = plg_cryptoctx->dh_1024; +- else if (plg_cryptoctx->dh_2048 != NULL && dh_size == 2048) +- params = plg_cryptoctx->dh_2048; +- else if (plg_cryptoctx->dh_4096 != NULL && dh_size == 4096) +- params = plg_cryptoctx->dh_4096; +- else ++ params = choose_dh_group(plg_cryptoctx, dh_size); ++ if (params == NULL) + goto cleanup; ++ TRACE_PKINIT_DH_PROPOSING_GROUP(context, group_desc(dh_size)); + + pkey = generate_dh_pkey(params); + if (pkey == NULL) +@@ -2772,8 +2927,11 @@ client_process_dh(krb5_context context, + server_pkey = compose_dh_pkey(cryptoctx->client_pkey, + subjectPublicKey_data, + subjectPublicKey_length); +- if (server_pkey == NULL) ++ if (server_pkey == NULL) { ++ retval = KRB5_PREAUTH_FAILED; ++ k5_setmsg(context, retval, _("Cannot compose PKINIT KDC public key")); + goto cleanup; ++ } + + if (!dh_result(cryptoctx->client_pkey, server_pkey, + &client_key, &client_key_len)) +@@ -2797,20 +2955,6 @@ cleanup: + return retval; + } + +-/* Return 1 if dh is a permitted well-known group, otherwise return 0. */ +-static int +-check_dh_wellknown(pkinit_plg_crypto_context cryptoctx, EVP_PKEY *pkey, +- int nbits) +-{ +- if (nbits == 1024) +- return EVP_PKEY_parameters_eq(cryptoctx->dh_1024, pkey) == 1; +- else if (nbits == 2048) +- return EVP_PKEY_parameters_eq(cryptoctx->dh_2048, pkey) == 1; +- else if (nbits == 4096) +- return EVP_PKEY_parameters_eq(cryptoctx->dh_4096, pkey) == 1; +- return 0; +-} +- + krb5_error_code + server_check_dh(krb5_context context, + pkinit_plg_crypto_context cryptoctx, +@@ -2820,7 +2964,7 @@ server_check_dh(krb5_context context, + int minbits) + { + EVP_PKEY *client_pkey = NULL; +- int dh_prime_bits; ++ int dh_bits; + krb5_error_code retval = KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED; + + client_pkey = decode_spki(client_spki); +@@ -2829,16 +2973,15 @@ server_check_dh(krb5_context context, + goto cleanup; + } + +- /* KDC SHOULD check to see if the key parameters satisfy its policy */ +- dh_prime_bits = EVP_PKEY_get_bits(client_pkey); +- if (minbits && dh_prime_bits < minbits) { +- pkiDebug("client sent dh params with %d bits, we require %d\n", +- dh_prime_bits, minbits); ++ dh_bits = check_dh_wellknown(cryptoctx, client_pkey); ++ if (dh_bits == -1 || dh_bits < minbits) { ++ TRACE_PKINIT_DH_REJECTING_GROUP(context, group_desc(dh_bits), ++ group_desc(minbits)); + goto cleanup; + } ++ TRACE_PKINIT_DH_RECEIVED_GROUP(context, group_desc(dh_bits)); + +- if (check_dh_wellknown(cryptoctx, client_pkey, dh_prime_bits)) +- retval = 0; ++ retval = 0; + + cleanup: + if (retval == 0) +@@ -3023,9 +3166,20 @@ pkinit_create_td_dh_parameters(krb5_context context, + krb5_algorithm_identifier alg_1024 = { dh_oid, oakley_1024 }; + krb5_algorithm_identifier alg_2048 = { dh_oid, oakley_2048 }; + krb5_algorithm_identifier alg_4096 = { dh_oid, oakley_4096 }; +- krb5_algorithm_identifier *alglist[4]; ++ krb5_algorithm_identifier alg_p256 = { ec_oid, ec_p256 }; ++ krb5_algorithm_identifier alg_p384 = { ec_oid, ec_p384 }; ++ krb5_algorithm_identifier alg_p521 = { ec_oid, ec_p521 }; ++ krb5_algorithm_identifier *alglist[7]; + + i = 0; ++ if (plg_cryptoctx->ec_p256 != NULL && ++ opts->dh_min_bits <= PKINIT_DH_P256_BITS) ++ alglist[i++] = &alg_p256; ++ if (plg_cryptoctx->ec_p384 != NULL && ++ opts->dh_min_bits <= PKINIT_DH_P384_BITS) ++ alglist[i++] = &alg_p384; ++ if (plg_cryptoctx->ec_p521 != NULL) ++ alglist[i++] = &alg_p521; + if (plg_cryptoctx->dh_2048 != NULL && opts->dh_min_bits <= 2048) + alglist[i++] = &alg_2048; + if (plg_cryptoctx->dh_4096 != NULL && opts->dh_min_bits <= 4096) +@@ -3110,13 +3264,10 @@ pkinit_process_td_dh_params(krb5_context context, + { + krb5_error_code retval = KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED; + EVP_PKEY *params = NULL; +- int i, dh_prime_bits, old_dh_size; ++ int i, dh_bits, old_dh_size; + + pkiDebug("dh parameters\n"); + +- EVP_PKEY_free(req_cryptoctx->received_params); +- req_cryptoctx->received_params = NULL; +- + old_dh_size = *new_dh_size; + + for (i = 0; algId[i] != NULL; i++) { +@@ -3124,36 +3275,22 @@ pkinit_process_td_dh_params(krb5_context context, + EVP_PKEY_free(params); + params = NULL; + +- /* Skip any parameters for algorithms other than DH. */ +- if (algId[i]->algorithm.length != dh_oid.length || +- memcmp(algId[i]->algorithm.data, dh_oid.data, dh_oid.length)) +- continue; +- +- params = decode_dh_params(&algId[i]->parameters); ++ if (data_eq(algId[i]->algorithm, dh_oid)) ++ params = decode_dh_params(&algId[i]->parameters); ++ else if (data_eq(algId[i]->algorithm, ec_oid)) ++ params = decode_ec_params(&algId[i]->parameters); + if (params == NULL) + continue; +- dh_prime_bits = EVP_PKEY_get_bits(params); +- /* Skip any parameters shorter than the previous size. */ +- if (dh_prime_bits < old_dh_size) +- continue; +- pkiDebug("client sent %d DH bits server prefers %d DH bits\n", +- *new_dh_size, dh_prime_bits); + +- /* If this is one of our well-known groups, just save the new size; we +- * will use our own copy of the parameters. */ +- if (check_dh_wellknown(cryptoctx, params, dh_prime_bits)) { +- *new_dh_size = dh_prime_bits; +- retval = 0; +- goto cleanup; +- } ++ dh_bits = check_dh_wellknown(cryptoctx, params); ++ /* Skip any parameters shorter than the previous size or unknown. */ ++ if (dh_bits == -1 || dh_bits < old_dh_size) ++ continue; ++ TRACE_PKINIT_DH_NEGOTIATED_GROUP(context, group_desc(dh_bits)); + +- /* If the parameters aren't well-known but check out, save them. */ +- if (params_valid(params)) { +- req_cryptoctx->received_params = params; +- params = NULL; +- retval = 0; +- goto cleanup; +- } ++ *new_dh_size = dh_bits; ++ retval = 0; ++ goto cleanup; + } + + cleanup: +@@ -5329,3 +5466,40 @@ crypto_req_cert_matching_data(krb5_context context, + return get_matching_data(context, plgctx, reqctx, reqctx->received_cert, + md_out); + } ++ ++/* ++ * Historically, the strength of PKINIT key exchange has been determined by the ++ * pkinit_dh_min_bits variable, which gives a finite field size. With the ++ * addition of ECDH support, we allow the string values P-256, P-384, and P-521 ++ * for this config variable, represented with the rough equivalent bit ++ * strengths for finite fields. ++ */ ++int ++parse_dh_min_bits(krb5_context context, const char *str) ++{ ++ char *endptr; ++ long n; ++ ++ if (str == NULL) ++ return PKINIT_DEFAULT_DH_MIN_BITS; ++ ++ n = strtol(str, &endptr, 0); ++ if (endptr == str) { ++ if (strcasecmp(str, "P-256") == 0) ++ return PKINIT_DH_P256_BITS; ++ else if (strcasecmp(str, "P-384") == 0) ++ return PKINIT_DH_P384_BITS; ++ else if (strcasecmp(str, "P-521") == 0) ++ return PKINIT_DH_P521_BITS; ++ } else { ++ if (n == 1024) ++ return 1024; ++ else if (n > 1024 && n <= 2048) ++ return 2048; ++ else if (n > 2048 && n <= 4096) ++ return 4096; ++ } ++ ++ TRACE_PKINIT_DH_INVALID_MIN_BITS(context, str); ++ return PKINIT_DEFAULT_DH_MIN_BITS; ++} +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h +index c807f044ac..b7a3358800 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h +@@ -99,6 +99,9 @@ struct _pkinit_plg_crypto_context { + EVP_PKEY *dh_1024; + EVP_PKEY *dh_2048; + EVP_PKEY *dh_4096; ++ EVP_PKEY *ec_p256; ++ EVP_PKEY *ec_p384; ++ EVP_PKEY *ec_p521; + ASN1_OBJECT *id_pkinit_authData; + ASN1_OBJECT *id_pkinit_DHKeyData; + ASN1_OBJECT *id_pkinit_rkeyData; +@@ -113,7 +116,6 @@ struct _pkinit_plg_crypto_context { + struct _pkinit_req_crypto_context { + X509 *received_cert; + EVP_PKEY *client_pkey; +- EVP_PKEY *received_params; + }; + + #endif /* _PKINIT_CRYPTO_OPENSSL_H */ +diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c +index 19db695a4d..25965eb5d2 100644 +--- a/src/plugins/preauth/pkinit/pkinit_lib.c ++++ b/src/plugins/preauth/pkinit/pkinit_lib.c +@@ -33,9 +33,6 @@ + + #define FAKECERT + +-const krb5_data dh_oid = { 0, 7, "\x2A\x86\x48\xce\x3e\x02\x01" }; +- +- + krb5_error_code + pkinit_init_req_opts(pkinit_req_opts **reqopts) + { +diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c +index aab21f951c..e22bcb195b 100644 +--- a/src/plugins/preauth/pkinit/pkinit_srv.c ++++ b/src/plugins/preauth/pkinit/pkinit_srv.c +@@ -988,7 +988,7 @@ static krb5_error_code + pkinit_init_kdc_profile(krb5_context context, pkinit_kdc_context plgctx) + { + krb5_error_code retval; +- char *eku_string = NULL, *ocsp_check = NULL; ++ char *eku_string = NULL, *ocsp_check = NULL, *minbits = NULL; + + pkiDebug("%s: entered for realm %s\n", __FUNCTION__, plgctx->realmname); + retval = pkinit_kdcdefault_string(context, plgctx->realmname, +@@ -1033,17 +1033,10 @@ pkinit_init_kdc_profile(krb5_context context, pkinit_kdc_context plgctx) + goto errout; + } + +- pkinit_kdcdefault_integer(context, plgctx->realmname, +- KRB5_CONF_PKINIT_DH_MIN_BITS, +- PKINIT_DEFAULT_DH_MIN_BITS, +- &plgctx->opts->dh_min_bits); +- if (plgctx->opts->dh_min_bits < PKINIT_DH_MIN_CONFIG_BITS) { +- pkiDebug("%s: invalid value (%d < %d) for pkinit_dh_min_bits, " +- "using default value (%d) instead\n", __FUNCTION__, +- plgctx->opts->dh_min_bits, PKINIT_DH_MIN_CONFIG_BITS, +- PKINIT_DEFAULT_DH_MIN_BITS); +- plgctx->opts->dh_min_bits = PKINIT_DEFAULT_DH_MIN_BITS; +- } ++ pkinit_kdcdefault_string(context, plgctx->realmname, ++ KRB5_CONF_PKINIT_DH_MIN_BITS, &minbits); ++ plgctx->opts->dh_min_bits = parse_dh_min_bits(context, minbits); ++ free(minbits); + + pkinit_kdcdefault_boolean(context, plgctx->realmname, + KRB5_CONF_PKINIT_ALLOW_UPN, +diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h +index d385759145..1c1ceb5a41 100644 +--- a/src/plugins/preauth/pkinit/pkinit_trace.h ++++ b/src/plugins/preauth/pkinit/pkinit_trace.h +@@ -83,6 +83,17 @@ + + #define TRACE_PKINIT_DH_GROUP_UNAVAILABLE(c, name) \ + TRACE(c, "PKINIT key exchange group {str} unsupported", name) ++#define TRACE_PKINIT_DH_INVALID_MIN_BITS(c, str) \ ++ TRACE(c, "Invalid pkinit_dh_min_bits value {str}, using default", str) ++#define TRACE_PKINIT_DH_NEGOTIATED_GROUP(c, desc) \ ++ TRACE(c, "PKINIT accepting KDC key exchange group preference {str}", desc) ++#define TRACE_PKINIT_DH_PROPOSING_GROUP(c, desc) \ ++ TRACE(c, "PKINIT using {str} key exchange group", desc) ++#define TRACE_PKINIT_DH_RECEIVED_GROUP(c, desc) \ ++ TRACE(c, "PKINIT received {str} key from client for key exchange", desc) ++#define TRACE_PKINIT_DH_REJECTING_GROUP(c, desc, mindesc) \ ++ TRACE(c, "PKINIT client key has group {str}, need at least {str}", \ ++ desc, mindesc) + + #define TRACE_PKINIT_OPENSSL_ERROR(c, msg) \ + TRACE(c, "PKINIT OpenSSL error: {str}", msg) +diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py +index 62e6c426d3..f8f2debc1b 100755 +--- a/src/tests/t_pkinit.py ++++ b/src/tests/t_pkinit.py +@@ -172,6 +172,15 @@ realm.pkinit(realm.user_princ, expected_trace=msgs) + realm.klist(realm.user_princ) + realm.run([kvno, realm.host_princ]) + ++# Test each Diffie-Hellman group except 1024-bit (which doesn't work ++# in OpenSSL 3.0) and the default 2048-bit group. ++for g in ('4096', 'P-256', 'P-384', 'P-521'): ++ mark('Diffie-Hellman group ' + g) ++ group_conf = {'realms': {'$realm': {'pkinit_dh_min_bits': g}}} ++ group_env = realm.special_env(g, True, krb5_conf=group_conf) ++ realm.pkinit(realm.user_princ, expected_trace=('PKINIT using ' + g,), ++ env=group_env) ++ + # Try using multiple configured pkinit_identities, to make sure we + # fall back to the second one when the first one cannot be read. + id_conf = {'realms': {'$realm': {'pkinit_identities': [file_identity + 'X', +@@ -190,11 +199,14 @@ realm.start_kdc(env=minbits_env) + msgs = ('Sending unauthenticated request', + '/Additional pre-authentication required', + 'Preauthenticating using KDC method data', ++ 'PKINIT using 2048-bit DH key exchange group', + 'Preauth module pkinit (16) (real) returned: 0/Success', + ' preauth for next request: PA-FX-COOKIE (133), PA-PK-AS-REQ (16)', + '/Key parameters not accepted', + 'Preauth tryagain input types (16): 109, PA-FX-COOKIE (133)', ++ 'PKINIT accepting KDC key exchange group preference P-384', + 'trying again with KDC-provided parameters', ++ 'PKINIT using P-384 key exchange group', + 'Preauth module pkinit (16) tryagain returned: 0/Success', + ' preauth for next request: PA-PK-AS-REQ (16), PA-FX-COOKIE (133)') + realm.pkinit(realm.user_princ, expected_trace=msgs) +-- +2.47.1 + diff --git a/SOURCES/0028-Add-ecdsa-with-sha512-256-to-supportedCMSTypes.patch b/SOURCES/0028-Add-ecdsa-with-sha512-256-to-supportedCMSTypes.patch new file mode 100644 index 0000000..fc8b72b --- /dev/null +++ b/SOURCES/0028-Add-ecdsa-with-sha512-256-to-supportedCMSTypes.patch @@ -0,0 +1,78 @@ +From b61ba0bb29d80811d2e0efc893c2932c57b2c807 Mon Sep 17 00:00:00 2001 +From: Julien Rische +Date: Wed, 21 Jun 2023 18:27:11 +0200 +Subject: [PATCH] Add ecdsa-with-sha512/256 to supportedCMSTypes + +Elliptic curve certificates are already supported for PKINIT +pre-authentication, but their associated signature types aren't +advertized. Add ecdsa-with-sha512 and ecdsa-with-sha256 OIDs to the +supportedCMSTypes list sent by the client. + +[ghudson@mit.edu: edited commit message] + +ticket: 9100 (new) +(cherry picked from commit 9913e5c92c4e5cb76d6ae58386f744766d2e6454) +--- + src/plugins/preauth/pkinit/pkinit_constants.c | 38 +++++++++++++++++++ + 1 file changed, 38 insertions(+) + +diff --git a/src/plugins/preauth/pkinit/pkinit_constants.c b/src/plugins/preauth/pkinit/pkinit_constants.c +index 10f8688ec2..905e90d29c 100644 +--- a/src/plugins/preauth/pkinit/pkinit_constants.c ++++ b/src/plugins/preauth/pkinit/pkinit_constants.c +@@ -64,14 +64,52 @@ static char sha512WithRSAEncr_oid[9] = { + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d + }; + ++/* RFC 3279 ecdsa-with-SHA1: iso(1) member-body(2) us(840) ansi-X9-62(10045) ++ * signatures(4) 1 */ ++static char ecdsaWithSha1_oid[] = { ++ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01 ++}; ++ ++/* RFC 5758 ecdsa-with-SHA256: iso(1) member-body(2) us(840) ansi-X9-62(10045) ++ * signatures(4) ecdsa-with-SHA2(3) 2 */ ++static char ecdsaWithSha256_oid[] = { ++ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 ++}; ++ ++/* RFC 5758 ecdsa-with-SHA384: iso(1) member-body(2) us(840) ansi-X9-62(10045) ++ * signatures(4) ecdsa-with-SHA2(3) 3 */ ++static char ecdsaWithSha384_oid[] = { ++ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 ++}; ++ ++/* RFC 5758 ecdsa-with-SHA512: iso(1) member-body(2) us(840) ansi-X9-62(10045) ++ * signatures(4) ecdsa-with-SHA2(3) 4 */ ++static char ecdsaWithSha512_oid[] = { ++ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 ++}; ++ + const krb5_data sha256WithRSAEncr_id = { + KV5M_DATA, sizeof(sha256WithRSAEncr_oid), sha256WithRSAEncr_oid + }; + const krb5_data sha512WithRSAEncr_id = { + KV5M_DATA, sizeof(sha512WithRSAEncr_oid), sha512WithRSAEncr_oid + }; ++const krb5_data ecdsaWithSha1_id = { ++ KV5M_DATA, sizeof(ecdsaWithSha1_oid), ecdsaWithSha1_oid ++}; ++const krb5_data ecdsaWithSha256_id = { ++ KV5M_DATA, sizeof(ecdsaWithSha256_oid), ecdsaWithSha256_oid ++}; ++const krb5_data ecdsaWithSha384_id = { ++ KV5M_DATA, sizeof(ecdsaWithSha384_oid), ecdsaWithSha384_oid ++}; ++const krb5_data ecdsaWithSha512_id = { ++ KV5M_DATA, sizeof(ecdsaWithSha512_oid), ecdsaWithSha512_oid ++}; + + krb5_data const * const supported_cms_algs[] = { ++ &ecdsaWithSha512_id, ++ &ecdsaWithSha256_id, + &sha512WithRSAEncr_id, + &sha256WithRSAEncr_id, + NULL +-- +2.47.1 + diff --git a/SOURCES/0029-Get-rid-of-pkinit_crypto_openssl.h.patch b/SOURCES/0029-Get-rid-of-pkinit_crypto_openssl.h.patch new file mode 100644 index 0000000..88f8409 --- /dev/null +++ b/SOURCES/0029-Get-rid-of-pkinit_crypto_openssl.h.patch @@ -0,0 +1,264 @@ +From 47075e6033f6d000a588f7b35850f685f6fbfca5 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Sun, 30 Jul 2023 01:07:38 -0400 +Subject: [PATCH] Get rid of pkinit_crypto_openssl.h + +Fold pkinit_crypto_openssl.h into the one source file where it was +used. Also clean up the include of , as htonl() is no +longer used after commit 1c87ce6c44a9de0824580a2d72a8a202237e01f4. + +(cherry picked from commit b3352945fb8836f8b4095e0b8aad04b54aca3152) +--- + src/plugins/preauth/pkinit/deps | 2 +- + .../preauth/pkinit/pkinit_crypto_openssl.c | 85 +++++++++++- + .../preauth/pkinit/pkinit_crypto_openssl.h | 121 ------------------ + 3 files changed, 83 insertions(+), 125 deletions(-) + delete mode 100644 src/plugins/preauth/pkinit/pkinit_crypto_openssl.h + +diff --git a/src/plugins/preauth/pkinit/deps b/src/plugins/preauth/pkinit/deps +index 58320aa801..b6f4476fe8 100644 +--- a/src/plugins/preauth/pkinit/deps ++++ b/src/plugins/preauth/pkinit/deps +@@ -112,4 +112,4 @@ pkinit_crypto_openssl.so pkinit_crypto_openssl.po $(OUTPRE)pkinit_crypto_openssl + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + pkcs11.h pkinit.h pkinit_accessor.h pkinit_crypto.h \ +- pkinit_crypto_openssl.c pkinit_crypto_openssl.h pkinit_trace.h ++ pkinit_crypto_openssl.c pkinit_trace.h +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +index 75036f8655..8d1216724c 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +@@ -30,20 +30,99 @@ + */ + + #include "k5-int.h" +-#include "pkinit_crypto_openssl.h" + #include "k5-buf.h" + #include "k5-err.h" + #include "k5-hex.h" +-#include ++#include "pkinit.h" + #include +-#include + ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + #if OPENSSL_VERSION_NUMBER >= 0x30000000L + #include + #include ++#include + #include + #endif + ++#define DN_BUF_LEN 256 ++#define MAX_CREDS_ALLOWED 20 ++ ++struct _pkinit_cred_info { ++ char *name; ++ X509 *cert; ++ EVP_PKEY *key; ++#ifndef WITHOUT_PKCS11 ++ CK_BYTE_PTR cert_id; ++ int cert_id_len; ++#endif ++}; ++typedef struct _pkinit_cred_info *pkinit_cred_info; ++ ++struct _pkinit_identity_crypto_context { ++ pkinit_cred_info creds[MAX_CREDS_ALLOWED+1]; ++ STACK_OF(X509) *my_certs; /* available user certs */ ++ char *identity; /* identity name for user cert */ ++ int cert_index; /* cert to use out of available certs*/ ++ EVP_PKEY *my_key; /* available user keys if in filesystem */ ++ STACK_OF(X509) *trustedCAs; /* available trusted ca certs */ ++ STACK_OF(X509) *intermediateCAs; /* available intermediate ca certs */ ++ STACK_OF(X509_CRL) *revoked; /* available crls */ ++ int pkcs11_method; ++ krb5_prompter_fct prompter; ++ void *prompter_data; ++#ifndef WITHOUT_PKCS11 ++ char *p11_module_name; ++ CK_SLOT_ID slotid; ++ char *token_label; ++ char *cert_label; ++ /* These are crypto-specific. */ ++ struct plugin_file_handle *p11_module; ++ CK_SESSION_HANDLE session; ++ CK_FUNCTION_LIST_PTR p11; ++ uint8_t *cert_id; ++ size_t cert_id_len; ++ CK_MECHANISM_TYPE mech; ++#endif ++ krb5_boolean defer_id_prompt; ++ pkinit_deferred_id *deferred_ids; ++}; ++ ++struct _pkinit_plg_crypto_context { ++ EVP_PKEY *dh_1024; ++ EVP_PKEY *dh_2048; ++ EVP_PKEY *dh_4096; ++ EVP_PKEY *ec_p256; ++ EVP_PKEY *ec_p384; ++ EVP_PKEY *ec_p521; ++ ASN1_OBJECT *id_pkinit_authData; ++ ASN1_OBJECT *id_pkinit_DHKeyData; ++ ASN1_OBJECT *id_pkinit_rkeyData; ++ ASN1_OBJECT *id_pkinit_san; ++ ASN1_OBJECT *id_ms_san_upn; ++ ASN1_OBJECT *id_pkinit_KPClientAuth; ++ ASN1_OBJECT *id_pkinit_KPKdc; ++ ASN1_OBJECT *id_ms_kp_sc_logon; ++ ASN1_OBJECT *id_kp_serverAuth; ++}; ++ ++struct _pkinit_req_crypto_context { ++ X509 *received_cert; ++ EVP_PKEY *client_pkey; ++}; ++ + static krb5_error_code pkinit_init_pkinit_oids(pkinit_plg_crypto_context ); + static void pkinit_fini_pkinit_oids(pkinit_plg_crypto_context ); + +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h +deleted file mode 100644 +index b7a3358800..0000000000 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h ++++ /dev/null +@@ -1,121 +0,0 @@ +-/* +- * COPYRIGHT (C) 2006,2007 +- * THE REGENTS OF THE UNIVERSITY OF MICHIGAN +- * ALL RIGHTS RESERVED +- * +- * Permission is granted to use, copy, create derivative works +- * and redistribute this software and such derivative works +- * for any purpose, so long as the name of The University of +- * Michigan is not used in any advertising or publicity +- * pertaining to the use of distribution of this software +- * without specific, written prior authorization. If the +- * above copyright notice or any other identification of the +- * University of Michigan is included in any copy of any +- * portion of this software, then the disclaimer below must +- * also be included. +- * +- * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION +- * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY +- * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF +- * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +- * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF +- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +- * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE +- * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR +- * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING +- * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN +- * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF +- * SUCH DAMAGES. +- */ +- +-#ifndef _PKINIT_CRYPTO_OPENSSL_H +-#define _PKINIT_CRYPTO_OPENSSL_H +- +-#include "pkinit.h" +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#if OPENSSL_VERSION_NUMBER >= 0x30000000L +-#include +-#include +-#endif +- +-#define DN_BUF_LEN 256 +-#define MAX_CREDS_ALLOWED 20 +- +-struct _pkinit_cred_info { +- char *name; +- X509 *cert; +- EVP_PKEY *key; +-#ifndef WITHOUT_PKCS11 +- CK_BYTE_PTR cert_id; +- int cert_id_len; +-#endif +-}; +-typedef struct _pkinit_cred_info * pkinit_cred_info; +- +-struct _pkinit_identity_crypto_context { +- pkinit_cred_info creds[MAX_CREDS_ALLOWED+1]; +- STACK_OF(X509) *my_certs; /* available user certs */ +- char *identity; /* identity name for user cert */ +- int cert_index; /* cert to use out of available certs*/ +- EVP_PKEY *my_key; /* available user keys if in filesystem */ +- STACK_OF(X509) *trustedCAs; /* available trusted ca certs */ +- STACK_OF(X509) *intermediateCAs; /* available intermediate ca certs */ +- STACK_OF(X509_CRL) *revoked; /* available crls */ +- int pkcs11_method; +- krb5_prompter_fct prompter; +- void *prompter_data; +-#ifndef WITHOUT_PKCS11 +- char *p11_module_name; +- CK_SLOT_ID slotid; +- char *token_label; +- char *cert_label; +- /* These are crypto-specific */ +- struct plugin_file_handle *p11_module; +- CK_SESSION_HANDLE session; +- CK_FUNCTION_LIST_PTR p11; +- uint8_t *cert_id; +- size_t cert_id_len; +- CK_MECHANISM_TYPE mech; +-#endif +- krb5_boolean defer_id_prompt; +- pkinit_deferred_id *deferred_ids; +-}; +- +-struct _pkinit_plg_crypto_context { +- EVP_PKEY *dh_1024; +- EVP_PKEY *dh_2048; +- EVP_PKEY *dh_4096; +- EVP_PKEY *ec_p256; +- EVP_PKEY *ec_p384; +- EVP_PKEY *ec_p521; +- ASN1_OBJECT *id_pkinit_authData; +- ASN1_OBJECT *id_pkinit_DHKeyData; +- ASN1_OBJECT *id_pkinit_rkeyData; +- ASN1_OBJECT *id_pkinit_san; +- ASN1_OBJECT *id_ms_san_upn; +- ASN1_OBJECT *id_pkinit_KPClientAuth; +- ASN1_OBJECT *id_pkinit_KPKdc; +- ASN1_OBJECT *id_ms_kp_sc_logon; +- ASN1_OBJECT *id_kp_serverAuth; +-}; +- +-struct _pkinit_req_crypto_context { +- X509 *received_cert; +- EVP_PKEY *client_pkey; +-}; +- +-#endif /* _PKINIT_CRYPTO_OPENSSL_H */ +-- +2.47.1 + diff --git a/SOURCES/0030-Use-SoftHSMv2-for-PKCS11-PKINIT-tests.patch b/SOURCES/0030-Use-SoftHSMv2-for-PKCS11-PKINIT-tests.patch new file mode 100644 index 0000000..ff7aa66 --- /dev/null +++ b/SOURCES/0030-Use-SoftHSMv2-for-PKCS11-PKINIT-tests.patch @@ -0,0 +1,157 @@ +From 7ffb7ce9de02121622d79227105f0028e5c6ad11 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Mon, 26 Feb 2024 19:03:38 -0500 +Subject: [PATCH] Use SoftHSMv2 for PKCS11 PKINIT tests + +Instead of softpkcs11, use SoftHSMv2 to mock the PKCS11 token for +PKINIT tests. Use pkcs11-tool from OpenSC to initialize the token and +import a certificate and key. SoftHSM does not support PIN-less +tokens (see https://github.com/opendnssec/SoftHSMv2/issues/480) so +remove that test for now. + +(cherry picked from commit 8ab61608236883fdc5c2d43f4bd1ff2094401d19) +--- + .github/workflows/build.yml | 2 +- + src/tests/t_pkinit.py | 82 ++++++++++++++++++++----------------- + 2 files changed, 45 insertions(+), 39 deletions(-) + +diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml +index 68a4788adb..d7ae86b150 100644 +--- a/.github/workflows/build.yml ++++ b/.github/workflows/build.yml +@@ -33,7 +33,7 @@ jobs: + if: startsWith(matrix.os, 'ubuntu') + run: | + sudo apt-get update -qq +- sudo apt-get install -y bison gettext keyutils ldap-utils libcmocka-dev libldap2-dev libkeyutils-dev libsasl2-dev libssl-dev python3-kdcproxy python3-pip slapd tcsh ++ sudo apt-get install -y bison gettext keyutils ldap-utils libcmocka-dev libldap2-dev libkeyutils-dev libsasl2-dev libssl-dev python3-kdcproxy python3-pip slapd tcsh softhsm2 opensc + pip3 install pyrad + - name: Build + env: +diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py +index f8f2debc1b..4435746429 100755 +--- a/src/tests/t_pkinit.py ++++ b/src/tests/t_pkinit.py +@@ -1,11 +1,10 @@ + from k5test import * ++import re + + # Skip this test if pkinit wasn't built. + if not pkinit_enabled: + skip_rest('PKINIT tests', 'PKINIT module not built') + +-soft_pkcs11 = os.path.join(buildtop, 'tests', 'softpkcs11', 'softpkcs11.so') +- + # Construct a krb5.conf fragment configuring pkinit. + user_pem = os.path.join(pkinit_certs, 'user.pem') + privkey_pem = os.path.join(pkinit_certs, 'privkey.pem') +@@ -55,9 +54,6 @@ p12_upn2_identity = 'PKCS12:%s' % user_upn2_p12 + p12_upn3_identity = 'PKCS12:%s' % user_upn3_p12 + p12_generic_identity = 'PKCS12:%s' % generic_p12 + p12_enc_identity = 'PKCS12:%s' % user_enc_p12 +-p11_identity = 'PKCS11:' + soft_pkcs11 +-p11_token_identity = ('PKCS11:module_name=' + soft_pkcs11 + +- ':slotid=1:token=SoftToken (token)') + + # Start a realm with the test kdb module for the following UPN SAN tests. + realm = K5Realm(kdc_conf=alias_kdc_conf, create_kdb=False, pkinit=True) +@@ -389,53 +385,63 @@ realm.klist(realm.user_princ) + realm.kinit(realm.user_princ, flags=['-X', 'X509_user_identity=,'], + expected_code=1, expected_msg='Preauthentication failed while') + +-softpkcs11rc = os.path.join(os.getcwd(), 'testdir', 'soft-pkcs11.rc') +-realm.env['SOFTPKCS11RC'] = softpkcs11rc ++softhsm2 = '/usr/lib/softhsm/libsofthsm2.so' ++if not os.path.exists(softhsm2): ++ skip_rest('PKCS11 tests', 'SoftHSMv2 required') ++pkcs11_tool = which('pkcs11-tool') ++if not pkcs11_tool: ++ skip_rest('PKCS11 tests', 'pkcs11-tool from OpenSC required') ++tool_cmd = [pkcs11_tool, '--module', softhsm2] ++ ++# Prepare a SoftHSM token. ++softhsm2_conf = os.path.join(realm.testdir, 'softhsm2.conf') ++softhsm2_tokens = os.path.join(realm.testdir, 'tokens') ++os.mkdir(softhsm2_tokens) ++realm.env['SOFTHSM2_CONF'] = softhsm2_conf ++with open(softhsm2_conf, 'w') as f: ++ f.write('directories.tokendir = %s\n' % softhsm2_tokens) ++realm.run(tool_cmd + ['--init-token', '--label', 'user', ++ '--so-pin', 'sopin', '--init-pin', '--pin', 'userpin']) ++realm.run(tool_cmd + ['-w', user_pem, '-y', 'cert']) ++realm.run(tool_cmd + ['-w', privkey_pem, '-y', 'privkey', ++ '-l', '--pin', 'userpin']) ++ ++# Extract the slot ID generated by SoftHSM. ++out = realm.run(tool_cmd + ['-L']) ++m = re.search(r'slot ID 0x([0-9a-f]+)\n', out) ++if not m: ++ fail('could not extract slot ID from SoftHSM token') ++slot_id = int(m.group(1), 16) ++ ++p11_attr = 'X509_user_identity=PKCS11:' + softhsm2 ++p11_token_identity = ('PKCS11:module_name=%s:slotid=%d:token=user' % ++ (softhsm2, slot_id)) + +-# PKINIT with PKCS11: identity, with no need for a PIN. +-mark('PKCS11 identity, no PIN') +-conf = open(softpkcs11rc, 'w') +-conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem, privkey_pem)) +-conf.close() +-# Expect to succeed without having to supply any more information. +-realm.kinit(realm.user_princ, +- flags=['-X', 'X509_user_identity=%s' % p11_identity]) ++mark('PKCS11 identity, with PIN (prompter)') ++realm.kinit(realm.user_princ, flags=['-X', p11_attr], password='userpin') + realm.klist(realm.user_princ) + realm.run([kvno, realm.host_princ]) + +-# PKINIT with PKCS11: identity, with a PIN supplied by the prompter. +-mark('PKCS11 identity, with PIN (prompter)') +-os.remove(softpkcs11rc) +-conf = open(softpkcs11rc, 'w') +-conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem, +- privkey_enc_pem)) +-conf.close() +-# Expect failure if the responder does nothing, and there's no prompter ++mark('PKCS11 identity, unavailable PIN') + realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p11_token_identity, +- '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ], +- expected_code=2) +-realm.kinit(realm.user_princ, +- flags=['-X', 'X509_user_identity=%s' % p11_identity], +- password='encrypted') +-realm.klist(realm.user_princ) +-realm.run([kvno, realm.host_princ]) ++ '-X', p11_attr, realm.user_princ], expected_code=2) + +-# Supply the wrong PIN. + mark('PKCS11 identity, wrong PIN') + expected_trace = ('PKINIT client has no configured identity; giving up',) + realm.kinit(realm.user_princ, +- flags=['-X', 'X509_user_identity=%s' % p11_identity], ++ flags=['-X', p11_attr], + password='wrong', expected_code=1, expected_trace=expected_trace) + + # PKINIT with PKCS11: identity, with a PIN supplied by the responder. +-# Supply the response in raw form. ++# Supply the response in raw form. Expect the PIN_COUNT_LOW flag (1) ++# to be set due to the previous test. + mark('PKCS11 identity, with PIN (responder)') +-realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p11_token_identity, +- '-r', 'pkinit={"%s": "encrypted"}' % p11_token_identity, +- '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ]) ++realm.run(['./responder', '-x', 'pkinit={"%s": 1}' % p11_token_identity, ++ '-r', 'pkinit={"%s": "userpin"}' % p11_token_identity, ++ '-X', p11_attr, realm.user_princ]) + # Supply the response through the convenience API. +-realm.run(['./responder', '-X', 'X509_user_identity=%s' % p11_identity, +- '-p', '%s=%s' % (p11_token_identity, 'encrypted'), ++realm.run(['./responder', '-X', p11_attr, ++ '-p', '%s=%s' % (p11_token_identity, 'userpin'), + realm.user_princ]) + realm.klist(realm.user_princ) + realm.run([kvno, realm.host_princ]) +-- +2.47.1 + diff --git a/SOURCES/0031-Simplify-PKINIT-cert-representation.patch b/SOURCES/0031-Simplify-PKINIT-cert-representation.patch new file mode 100644 index 0000000..21bc856 --- /dev/null +++ b/SOURCES/0031-Simplify-PKINIT-cert-representation.patch @@ -0,0 +1,202 @@ +From 35d11a545757efe9c57a6a8b26fc6396e7d0eb5f Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Fri, 9 Feb 2024 17:32:40 -0500 +Subject: [PATCH] Simplify PKINIT cert representation + +In the _pkinit_identity_crypto_context structure, the my_certs field +is a stack which only ever contains one cert and is only ever used to +retrieve that one cert. The cert_index field is always 0. Replace +these fields with a my_cert field pointing directly to the X509 +certificate. + +Simplify crypto_cert_select_default() by making it call +crypto_cert_select() with index 0 after verifying the certificate +count. + +(cherry picked from commit f95dfb7908456f9563cee66706216a21df8d791f) +--- + .../preauth/pkinit/pkinit_crypto_openssl.c | 74 +++++-------------- + 1 file changed, 20 insertions(+), 54 deletions(-) + +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +index 8d1216724c..875ac4206b 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +@@ -73,10 +73,9 @@ typedef struct _pkinit_cred_info *pkinit_cred_info; + + struct _pkinit_identity_crypto_context { + pkinit_cred_info creds[MAX_CREDS_ALLOWED+1]; +- STACK_OF(X509) *my_certs; /* available user certs */ ++ X509 *my_cert; /* selected user or KDC cert */ + char *identity; /* identity name for user cert */ +- int cert_index; /* cert to use out of available certs*/ +- EVP_PKEY *my_key; /* available user keys if in filesystem */ ++ EVP_PKEY *my_key; /* selected cert key if in filesystem */ + STACK_OF(X509) *trustedCAs; /* available trusted ca certs */ + STACK_OF(X509) *intermediateCAs; /* available intermediate ca certs */ + STACK_OF(X509_CRL) *revoked; /* available crls */ +@@ -1489,8 +1488,7 @@ pkinit_init_certs(pkinit_identity_crypto_context ctx) + + for (i = 0; i < MAX_CREDS_ALLOWED; i++) + ctx->creds[i] = NULL; +- ctx->my_certs = NULL; +- ctx->cert_index = 0; ++ ctx->my_cert = NULL; + ctx->my_key = NULL; + ctx->trustedCAs = NULL; + ctx->intermediateCAs = NULL; +@@ -1506,8 +1504,8 @@ pkinit_fini_certs(pkinit_identity_crypto_context ctx) + if (ctx == NULL) + return; + +- if (ctx->my_certs != NULL) +- sk_X509_pop_free(ctx->my_certs, X509_free); ++ if (ctx->my_cert != NULL) ++ X509_free(ctx->my_cert); + + if (ctx->my_key != NULL) + EVP_PKEY_free(ctx->my_key); +@@ -1696,7 +1694,6 @@ cms_signeddata_create(krb5_context context, + ASN1_OCTET_STRING *digest = NULL; + unsigned int alg_len = 0, digest_len = 0; + unsigned char *y = NULL; +- X509 *cert = NULL; + ASN1_OBJECT *oid = NULL, *oid_copy; + + /* Start creating PKCS7 data. */ +@@ -1715,7 +1712,7 @@ cms_signeddata_create(krb5_context context, + if (oid == NULL) + goto cleanup; + +- if (id_cryptoctx->my_certs != NULL) { ++ if (id_cryptoctx->my_cert != NULL) { + X509_STORE *certstore = NULL; + X509_STORE_CTX *certctx; + STACK_OF(X509) *certstack = NULL; +@@ -1726,8 +1723,6 @@ cms_signeddata_create(krb5_context context, + if ((cert_stack = sk_X509_new_null()) == NULL) + goto cleanup; + +- cert = sk_X509_value(id_cryptoctx->my_certs, id_cryptoctx->cert_index); +- + certstore = X509_STORE_new(); + if (certstore == NULL) + goto cleanup; +@@ -1736,7 +1731,7 @@ cms_signeddata_create(krb5_context context, + certctx = X509_STORE_CTX_new(); + if (certctx == NULL) + goto cleanup; +- X509_STORE_CTX_init(certctx, certstore, cert, ++ X509_STORE_CTX_init(certctx, certstore, id_cryptoctx->my_cert, + id_cryptoctx->intermediateCAs); + X509_STORE_CTX_trusted_stack(certctx, id_cryptoctx->trustedCAs); + if (!X509_verify_cert(certctx)) { +@@ -1764,13 +1759,13 @@ cms_signeddata_create(krb5_context context, + if (!ASN1_INTEGER_set(p7si->version, 1)) + goto cleanup; + if (!X509_NAME_set(&p7si->issuer_and_serial->issuer, +- X509_get_issuer_name(cert))) ++ X509_get_issuer_name(id_cryptoctx->my_cert))) + goto cleanup; + /* because ASN1_INTEGER_set is used to set a 'long' we will do + * things the ugly way. */ + ASN1_INTEGER_free(p7si->issuer_and_serial->serial); + if (!(p7si->issuer_and_serial->serial = +- ASN1_INTEGER_dup(X509_get_serialNumber(cert)))) ++ ASN1_INTEGER_dup(X509_get_serialNumber(id_cryptoctx->my_cert)))) + goto cleanup; + + /* will not fill-out EVP_PKEY because it's on the smartcard */ +@@ -3311,7 +3306,7 @@ pkinit_check_kdc_pkid(krb5_context context, + PKCS7_ISSUER_AND_SERIAL *is = NULL; + const unsigned char *p = pdid_buf; + int status = 1; +- X509 *kdc_cert = sk_X509_value(id_cryptoctx->my_certs, id_cryptoctx->cert_index); ++ X509 *kdc_cert = id_cryptoctx->my_cert; + + *valid_kdcPkId = 0; + pkiDebug("found kdcPkId in AS REQ\n"); +@@ -4783,7 +4778,8 @@ cleanup: + } + + /* +- * Set the certificate in idctx->creds[cred_index] as the selected certificate. ++ * Set the certificate in idctx->creds[cred_index] as the selected certificate, ++ * stealing pointers from it. + */ + krb5_error_code + crypto_cert_select(krb5_context context, pkinit_identity_crypto_context idctx, +@@ -4795,20 +4791,17 @@ crypto_cert_select(krb5_context context, pkinit_identity_crypto_context idctx, + return ENOENT; + + ci = idctx->creds[cred_index]; +- /* copy the selected cert into our id_cryptoctx */ +- if (idctx->my_certs != NULL) +- sk_X509_pop_free(idctx->my_certs, X509_free); +- idctx->my_certs = sk_X509_new_null(); +- sk_X509_push(idctx->my_certs, ci->cert); +- free(idctx->identity); ++ ++ idctx->my_cert = ci->cert; ++ ci->cert = NULL; ++ + /* hang on to the selected credential name */ ++ free(idctx->identity); + if (ci->name != NULL) + idctx->identity = strdup(ci->name); + else + idctx->identity = NULL; + +- ci->cert = NULL; /* Don't free it twice */ +- idctx->cert_index = 0; + if (idctx->pkcs11_method != 1) { + idctx->my_key = ci->key; + ci->key = NULL; /* Don't free it twice */ +@@ -4837,41 +4830,14 @@ crypto_cert_select_default(krb5_context context, + + retval = crypto_cert_get_count(id_cryptoctx, &cert_count); + if (retval) +- goto errout; ++ return retval; + + if (cert_count != 1) { + TRACE_PKINIT_NO_DEFAULT_CERT(context, cert_count); +- retval = EINVAL; +- goto errout; +- } +- /* copy the selected cert into our id_cryptoctx */ +- if (id_cryptoctx->my_certs != NULL) { +- sk_X509_pop_free(id_cryptoctx->my_certs, X509_free); ++ return EINVAL; + } +- id_cryptoctx->my_certs = sk_X509_new_null(); +- sk_X509_push(id_cryptoctx->my_certs, id_cryptoctx->creds[0]->cert); +- id_cryptoctx->creds[0]->cert = NULL; /* Don't free it twice */ +- id_cryptoctx->cert_index = 0; +- /* hang on to the selected credential name */ +- if (id_cryptoctx->creds[0]->name != NULL) +- id_cryptoctx->identity = strdup(id_cryptoctx->creds[0]->name); +- else +- id_cryptoctx->identity = NULL; + +- if (id_cryptoctx->pkcs11_method != 1) { +- id_cryptoctx->my_key = id_cryptoctx->creds[0]->key; +- id_cryptoctx->creds[0]->key = NULL; /* Don't free it twice */ +- } +-#ifndef WITHOUT_PKCS11 +- else { +- id_cryptoctx->cert_id = id_cryptoctx->creds[0]->cert_id; +- id_cryptoctx->creds[0]->cert_id = NULL; /* Don't free it twice */ +- id_cryptoctx->cert_id_len = id_cryptoctx->creds[0]->cert_id_len; +- } +-#endif +- retval = 0; +-errout: +- return retval; ++ return crypto_cert_select(context, id_cryptoctx, 0); + } + + +-- +2.47.1 + diff --git a/SOURCES/0032-Support-PKCS11-EC-client-certs-in-PKINIT.patch b/SOURCES/0032-Support-PKCS11-EC-client-certs-in-PKINIT.patch new file mode 100644 index 0000000..2de6dd8 --- /dev/null +++ b/SOURCES/0032-Support-PKCS11-EC-client-certs-in-PKINIT.patch @@ -0,0 +1,1768 @@ +From c462b59384e9f5658ac8e875092dd45244fd8422 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Wed, 21 Feb 2024 15:29:02 -0500 +Subject: [PATCH] Support PKCS11 EC client certs in PKINIT + +Move the digest computation and DigestInfo encoding from +cms_signeddata_create() to pkinit_sign_data_pkcs11(), and +conditionalize the DigestInfo encoding on the key type. Use CKM_ECDSA +instead of CKM_RSA_PKCS for EC keys, and convert the resulting +signature from the PKS11 encoding to the ASN.1 encoding required by +CMS. + +Regenerate the test certificates with an additional EC client cert. +Add test cases for EC client certs with and without PKCS11. + +ticket: 9112 (new) +(cherry picked from commit f745c9a9bd6c0c73b944182173f1ac305d03dc3a) +--- + .../preauth/pkinit/pkinit_crypto_openssl.c | 319 +++++++++++------- + src/tests/pkinit-certs/ca.pem | 32 +- + src/tests/pkinit-certs/eckey.pem | 5 + + src/tests/pkinit-certs/ecuser.pem | 24 ++ + src/tests/pkinit-certs/generic.p12 | Bin 2469 -> 2560 bytes + src/tests/pkinit-certs/generic.pem | 38 +-- + src/tests/pkinit-certs/kdc.pem | 32 +- + src/tests/pkinit-certs/make-certs.sh | 11 +- + src/tests/pkinit-certs/privkey-enc.pem | 60 ++-- + src/tests/pkinit-certs/privkey.pem | 55 +-- + src/tests/pkinit-certs/user-enc.p12 | Bin 2829 -> 2920 bytes + src/tests/pkinit-certs/user-upn.p12 | Bin 2821 -> 2912 bytes + src/tests/pkinit-certs/user-upn.pem | 32 +- + src/tests/pkinit-certs/user-upn2.p12 | Bin 2805 -> 2896 bytes + src/tests/pkinit-certs/user-upn2.pem | 34 +- + src/tests/pkinit-certs/user-upn3.p12 | Bin 2821 -> 2912 bytes + src/tests/pkinit-certs/user-upn3.pem | 32 +- + src/tests/pkinit-certs/user.p12 | Bin 2829 -> 2920 bytes + src/tests/pkinit-certs/user.pem | 30 +- + src/tests/t_pkinit.py | 20 ++ + 20 files changed, 437 insertions(+), 287 deletions(-) + create mode 100644 src/tests/pkinit-certs/eckey.pem + create mode 100644 src/tests/pkinit-certs/ecuser.pem + +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +index 875ac4206b..b33b696954 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +@@ -93,7 +93,6 @@ struct _pkinit_identity_crypto_context { + CK_FUNCTION_LIST_PTR p11; + uint8_t *cert_id; + size_t cert_id_len; +- CK_MECHANISM_TYPE mech; + #endif + krb5_boolean defer_id_prompt; + pkinit_deferred_id *deferred_ids; +@@ -283,7 +282,6 @@ compat_get0_EC(const EVP_PKEY *pkey) + #if OPENSSL_VERSION_NUMBER < 0x30000000L + /* OpenSSL 3.0 changes several preferred function names. */ + #define EVP_PKEY_parameters_eq EVP_PKEY_cmp_parameters +-#define EVP_MD_CTX_get0_md EVP_MD_CTX_md + #define EVP_PKEY_get_size EVP_PKEY_size + #define EVP_PKEY_get_bits EVP_PKEY_bits + +@@ -1683,17 +1681,12 @@ cms_signeddata_create(krb5_context context, + STACK_OF(X509) * cert_stack = NULL; + ASN1_OCTET_STRING *digest_attr = NULL; + EVP_MD_CTX *ctx; +- const EVP_MD *md_tmp = NULL; +- unsigned char md_data[EVP_MAX_MD_SIZE], md_data2[EVP_MAX_MD_SIZE]; +- unsigned char *digestInfo_buf = NULL, *abuf = NULL; +- unsigned int md_len, md_len2, alen, digestInfo_len; ++ unsigned char md_data[EVP_MAX_MD_SIZE], *abuf = NULL; ++ unsigned int md_len, alen; + STACK_OF(X509_ATTRIBUTE) * sk; + unsigned char *sig = NULL; + unsigned int sig_len = 0; + X509_ALGOR *alg = NULL; +- ASN1_OCTET_STRING *digest = NULL; +- unsigned int alg_len = 0, digest_len = 0; +- unsigned char *y = NULL; + ASN1_OBJECT *oid = NULL, *oid_copy; + + /* Start creating PKCS7 data. */ +@@ -1795,7 +1788,6 @@ cms_signeddata_create(krb5_context context, + goto cleanup; + EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); + EVP_DigestUpdate(ctx, data, data_len); +- md_tmp = EVP_MD_CTX_get0_md(ctx); + EVP_DigestFinal_ex(ctx, md_data, &md_len); + EVP_MD_CTX_free(ctx); + +@@ -1820,63 +1812,8 @@ cms_signeddata_create(krb5_context context, + if (abuf == NULL) + goto cleanup2; + +-#ifndef WITHOUT_PKCS11 +- /* +- * Some tokens can only do RSAEncryption without a hash. To compute +- * sha256WithRSAEncryption, encode the algorithm ID for the hash +- * function and the hash value into an ASN.1 value of type DigestInfo: +- * DigestInfo ::= SEQUENCE { +- * digestAlgorithm AlgorithmIdentifier, +- * digest OCTET STRING +- * } +- */ +- if (id_cryptoctx->pkcs11_method == 1 && +- id_cryptoctx->mech == CKM_RSA_PKCS) { +- pkiDebug("mech = CKM_RSA_PKCS\n"); +- ctx = EVP_MD_CTX_new(); +- if (ctx == NULL) +- goto cleanup; +- EVP_DigestInit_ex(ctx, md_tmp, NULL); +- EVP_DigestUpdate(ctx, abuf, alen); +- EVP_DigestFinal_ex(ctx, md_data2, &md_len2); +- EVP_MD_CTX_free(ctx); +- +- alg = X509_ALGOR_new(); +- if (alg == NULL) +- goto cleanup2; +- X509_ALGOR_set0(alg, OBJ_nid2obj(NID_sha256), V_ASN1_NULL, NULL); +- alg_len = i2d_X509_ALGOR(alg, NULL); +- +- digest = ASN1_OCTET_STRING_new(); +- if (digest == NULL) +- goto cleanup2; +- ASN1_OCTET_STRING_set(digest, md_data2, (int)md_len2); +- digest_len = i2d_ASN1_OCTET_STRING(digest, NULL); +- +- digestInfo_len = ASN1_object_size(1, (int)(alg_len + digest_len), +- V_ASN1_SEQUENCE); +- y = digestInfo_buf = malloc(digestInfo_len); +- if (digestInfo_buf == NULL) +- goto cleanup2; +- ASN1_put_object(&y, 1, (int)(alg_len + digest_len), V_ASN1_SEQUENCE, +- V_ASN1_UNIVERSAL); +- i2d_X509_ALGOR(alg, &y); +- i2d_ASN1_OCTET_STRING(digest, &y); +-#ifdef DEBUG_SIG +- pkiDebug("signing buffer\n"); +- print_buffer(digestInfo_buf, digestInfo_len); +- print_buffer_bin(digestInfo_buf, digestInfo_len, "/tmp/pkcs7_tosign"); +-#endif +- retval = pkinit_sign_data(context, id_cryptoctx, digestInfo_buf, +- digestInfo_len, &sig, &sig_len); +- } else +-#endif +- { +- pkiDebug("mech = %s\n", +- id_cryptoctx->pkcs11_method == 1 ? "CKM_SHA256_RSA_PKCS" : "FS"); +- retval = pkinit_sign_data(context, id_cryptoctx, abuf, alen, +- &sig, &sig_len); +- } ++ retval = pkinit_sign_data(context, id_cryptoctx, abuf, alen, ++ &sig, &sig_len); + #ifdef DEBUG_SIG + print_buffer(sig, sig_len); + #endif +@@ -1930,14 +1867,6 @@ cms_signeddata_create(krb5_context context, + + cleanup2: + if (p7si) { +-#ifndef WITHOUT_PKCS11 +- if (id_cryptoctx->pkcs11_method == 1 && +- id_cryptoctx->mech == CKM_RSA_PKCS) { +- free(digestInfo_buf); +- if (digest != NULL) +- ASN1_OCTET_STRING_free(digest); +- } +-#endif + if (alg != NULL) + X509_ALGOR_free(alg); + } +@@ -3657,8 +3586,7 @@ cleanup: + * Look for a key that's: + * 1. private + * 2. capable of the specified operation (usually signing or decrypting) +- * 3. RSA (this may be wrong but it's all we can do for now) +- * 4. matches the id of the cert we chose ++ * 3. matches the id of the cert we chose + * + * You must call pkinit_get_certs before calling pkinit_find_private_key + * (that's because we need the ID of the private key) +@@ -3678,7 +3606,6 @@ pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, + CK_OBJECT_CLASS cls; + CK_ATTRIBUTE attrs[4]; + CK_ULONG count; +- CK_KEY_TYPE keytype; + unsigned int nattrs = 0; + int r; + #ifdef PKINIT_USE_KEY_USAGE +@@ -3705,12 +3632,6 @@ pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, + nattrs++; + #endif + +- keytype = CKK_RSA; +- attrs[nattrs].type = CKA_KEY_TYPE; +- attrs[nattrs].pValue = &keytype; +- attrs[nattrs].ulValueLen = sizeof keytype; +- nattrs++; +- + attrs[nattrs].type = CKA_ID; + attrs[nattrs].pValue = id_cryptoctx->cert_id; + attrs[nattrs].ulValueLen = id_cryptoctx->cert_id_len; +@@ -3749,6 +3670,116 @@ pkinit_sign_data_fs(krb5_context context, + } + + #ifndef WITHOUT_PKCS11 ++/* ++ * DER-encode a DigestInfo sequence containing the algorithm md and the digest ++ * mdbytes. ++ * ++ * DigestInfo ::= SEQUENCE { ++ * digestAlgorithm AlgorithmIdentifier, ++ * digest OCTET STRING ++ * } ++ */ ++static krb5_error_code ++encode_digestinfo(krb5_context context, const EVP_MD *md, ++ const uint8_t *mdbytes, size_t mdlen, ++ uint8_t **encoding_out, size_t *len_out) ++{ ++ krb5_boolean ok = FALSE; ++ X509_ALGOR *alg = NULL; ++ ASN1_OCTET_STRING *digest = NULL; ++ uint8_t *buf, *p; ++ int alg_len, digest_len, len; ++ ++ *encoding_out = NULL; ++ *len_out = 0; ++ ++ alg = X509_ALGOR_new(); ++ if (alg == NULL || ++ !X509_ALGOR_set0(alg, OBJ_nid2obj(EVP_MD_nid(md)), V_ASN1_NULL, NULL)) ++ goto cleanup; ++ alg_len = i2d_X509_ALGOR(alg, NULL); ++ if (alg_len < 0) ++ goto cleanup; ++ ++ digest = ASN1_OCTET_STRING_new(); ++ if (digest == NULL || !ASN1_OCTET_STRING_set(digest, mdbytes, mdlen)) ++ goto cleanup; ++ digest_len = i2d_ASN1_OCTET_STRING(digest, NULL); ++ if (digest_len < 0) ++ goto cleanup; ++ ++ len = ASN1_object_size(1, alg_len + digest_len, V_ASN1_SEQUENCE); ++ p = buf = malloc(len); ++ if (buf == NULL) ++ goto cleanup; ++ ASN1_put_object(&p, 1, alg_len + digest_len, V_ASN1_SEQUENCE, ++ V_ASN1_UNIVERSAL); ++ i2d_X509_ALGOR(alg, &p); ++ i2d_ASN1_OCTET_STRING(digest, &p); ++ ++ *encoding_out = buf; ++ *len_out = len; ++ ok = TRUE; ++ ++cleanup: ++ X509_ALGOR_free(alg); ++ ASN1_OCTET_STRING_free(digest); ++ if (!ok) ++ return oerr(context, 0, _("Failed to DER encode DigestInfo")); ++ return 0; ++} ++ ++/* Extract the r and s values from a PKCS11 ECDSA signature and re-encode them ++ * in the DER representation of an ECDSA-Sig-Value for use in CMS. */ ++static krb5_error_code ++convert_pkcs11_ecdsa_sig(krb5_context context, ++ const uint8_t *p11sig, unsigned int p11siglen, ++ uint8_t **sig_out, unsigned int *sig_len_out) ++{ ++ krb5_boolean ok = FALSE; ++ BIGNUM *r = NULL, *s = NULL; ++ ECDSA_SIG *sig = NULL; ++ int len; ++ uint8_t *p; ++ ++ *sig_out = NULL; ++ *sig_len_out = 0; ++ ++ if (p11siglen % 2 != 0) ++ return EINVAL; ++ ++ /* Extract the r and s values from the PKCS11 signature. */ ++ r = BN_bin2bn(p11sig, p11siglen / 2, NULL); ++ s = BN_bin2bn(p11sig + p11siglen / 2, p11siglen / 2, NULL); ++ if (r == NULL || s == NULL) ++ goto cleanup; ++ ++ /* Create an ECDSA-Sig-Value object and transfer ownership of r and s. */ ++ sig = ECDSA_SIG_new(); ++ if (sig == NULL || !ECDSA_SIG_set0(sig, r, s)) ++ goto cleanup; ++ r = s = NULL; ++ ++ /* DER-encode the ECDSA-Sig-Value object. */ ++ len = i2d_ECDSA_SIG(sig, NULL); ++ if (len < 0) ++ goto cleanup; ++ p = *sig_out = malloc(len); ++ if (*sig_out == NULL) ++ goto cleanup; ++ *sig_len_out = len; ++ i2d_ECDSA_SIG(sig, &p); ++ ok = TRUE; ++ ++cleanup: ++ BN_free(r); ++ BN_free(s); ++ ECDSA_SIG_free(sig); ++ if (!ok) ++ return oerr(context, 0, _("Failed to convert PKCS11 ECDSA signature")); ++ return 0; ++} ++ + static krb5_error_code + pkinit_sign_data_pkcs11(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, +@@ -3757,27 +3788,88 @@ pkinit_sign_data_pkcs11(krb5_context context, + unsigned char **sig, + unsigned int *sig_len) + { ++ krb5_error_code ret; + CK_OBJECT_HANDLE obj; + CK_ULONG len; + CK_MECHANISM mech; +- unsigned char *cp; ++ CK_SESSION_HANDLE session; ++ CK_FUNCTION_LIST_PTR p11; ++ CK_ATTRIBUTE attr; ++ CK_KEY_TYPE keytype; ++ EVP_MD_CTX *ctx; ++ const EVP_MD *md = EVP_sha256(); ++ unsigned int mdlen; ++ uint8_t mdbuf[EVP_MAX_MD_SIZE], *dinfo = NULL, *sigbuf = NULL, *input; ++ size_t dinfo_len, input_len; + int r; + ++ *sig = NULL; ++ *sig_len = 0; ++ + if (pkinit_open_session(context, id_cryptoctx)) { + pkiDebug("can't open pkcs11 session\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } ++ p11 = id_cryptoctx->p11; ++ session = id_cryptoctx->session; + +- pkinit_find_private_key(id_cryptoctx, CKA_SIGN, &obj); ++ ret = pkinit_find_private_key(id_cryptoctx, CKA_SIGN, &obj); ++ if (ret) ++ return ret; ++ ++ attr.type = CKA_KEY_TYPE; ++ attr.pValue = &keytype; ++ attr.ulValueLen = sizeof(keytype); ++ r = p11->C_GetAttributeValue(session, obj, &attr, 1); ++ if (r) { ++ pkiDebug("C_GetAttributeValue: %s\n", pkcs11err(r)); ++ ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; ++ } ++ ++ /* ++ * We would ideally use CKM_SHA256_RSA_PKCS and CKM_ECDSA_SHA256, but ++ * historically many cards seem to be confused about whether they are ++ * capable of mechanisms or not. To be safe we compute the digest ++ * ourselves and use CKM_RSA_PKCS and CKM_ECDSA. ++ */ ++ ctx = EVP_MD_CTX_new(); ++ if (ctx == NULL) { ++ ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; ++ } ++ EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); ++ EVP_DigestUpdate(ctx, data, data_len); ++ EVP_DigestFinal_ex(ctx, mdbuf, &mdlen); ++ EVP_MD_CTX_free(ctx); + +- mech.mechanism = id_cryptoctx->mech; ++ if (keytype == CKK_RSA) { ++ /* For RSA we must also encode the digest in a DigestInfo sequence. */ ++ mech.mechanism = CKM_RSA_PKCS; ++ ret = encode_digestinfo(context, md, mdbuf, mdlen, &dinfo, &dinfo_len); ++ if (ret) ++ goto cleanup; ++ input = dinfo; ++ input_len = dinfo_len; ++ } else if (keytype == CKK_EC) { ++ mech.mechanism = CKM_ECDSA; ++ input = mdbuf; ++ input_len = mdlen; ++ } else { ++ ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ k5_setmsg(context, ret, ++ _("PKCS11 certificate has unsupported key type %lu"), ++ keytype); ++ goto cleanup; ++ } + mech.pParameter = NULL; + mech.ulParameterLen = 0; + +- if ((r = id_cryptoctx->p11->C_SignInit(id_cryptoctx->session, &mech, +- obj)) != CKR_OK) { ++ r = p11->C_SignInit(session, &mech, obj); ++ if (r != CKR_OK) { + pkiDebug("C_SignInit: %s\n", pkcs11err(r)); +- return KRB5KDC_ERR_PREAUTH_FAILED; ++ ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; + } + + /* +@@ -3785,28 +3877,38 @@ pkinit_sign_data_pkcs11(krb5_context context, + * get that. So guess, and if it's too small, re-malloc. + */ + len = PK_SIGLEN_GUESS; +- cp = malloc((size_t) len); +- if (cp == NULL) +- return ENOMEM; ++ sigbuf = k5alloc(len, &ret); ++ if (sigbuf == NULL) ++ goto cleanup; + +- r = id_cryptoctx->p11->C_Sign(id_cryptoctx->session, data, +- (CK_ULONG) data_len, cp, &len); ++ r = p11->C_Sign(session, input, input_len, sigbuf, &len); + if (r == CKR_BUFFER_TOO_SMALL || (r == CKR_OK && len >= PK_SIGLEN_GUESS)) { +- free(cp); ++ free(sigbuf); + pkiDebug("C_Sign realloc %d\n", (int) len); +- cp = malloc((size_t) len); +- r = id_cryptoctx->p11->C_Sign(id_cryptoctx->session, data, +- (CK_ULONG) data_len, cp, &len); ++ sigbuf = k5alloc(len, &ret); ++ if (sigbuf == NULL) ++ goto cleanup; ++ r = p11->C_Sign(session, input, input_len, sigbuf, &len); + } + if (r != CKR_OK) { + pkiDebug("C_Sign: %s\n", pkcs11err(r)); +- return KRB5KDC_ERR_PREAUTH_FAILED; ++ ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; + } +- pkiDebug("sign %d -> %d\n", (int) data_len, (int) len); +- *sig_len = len; +- *sig = cp; + +- return 0; ++ if (keytype == CKK_EC) { ++ /* PKCS11 ECDSA signatures must be re-encoded for CMS. */ ++ ret = convert_pkcs11_ecdsa_sig(context, sigbuf, len, sig, sig_len); ++ } else { ++ *sig_len = len; ++ *sig = sigbuf; ++ sigbuf = NULL; ++ } ++ ++cleanup: ++ free(dinfo); ++ free(sigbuf); ++ return ret; + } + #endif + +@@ -4388,15 +4490,6 @@ pkinit_get_certs_pkcs11(krb5_context context, + return 0; + } + +- /* +- * We'd like to use CKM_SHA256_RSA_PKCS for signing if it's available, but +- * historically many cards seem to be confused about whether they are +- * capable of mechanisms or not. The safe thing seems to be to ignore the +- * mechanism list, always use CKM_RSA_PKCS and calculate the sha256 digest +- * ourselves. +- */ +- id_cryptoctx->mech = CKM_RSA_PKCS; +- + cls = CKO_CERTIFICATE; + attrs[0].type = CKA_CLASS; + attrs[0].pValue = &cls; +diff --git a/src/tests/pkinit-certs/ca.pem b/src/tests/pkinit-certs/ca.pem +index 63d31c1f5f..6c782bcde5 100644 +--- a/src/tests/pkinit-certs/ca.pem ++++ b/src/tests/pkinit-certs/ca.pem +@@ -3,27 +3,27 @@ MIIE5TCCA82gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBpzELMAkGA1UEBhMCVVMx + FjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJyaWRnZTEMMAoG + A1UECgwDTUlUMSkwJwYDVQQLDCBJbnNlY3VyZSBQS0lOSVQgS2VyYmVyb3MgdGVz + dCBDQTEzMDEGA1UEAwwqcGtpbml0IHRlc3Qgc3VpdGUgQ0E7IGRvIG5vdCB1c2Ug +-b3RoZXJ3aXNlMB4XDTIxMTAwODIxMTEzMFoXDTMyMDkyMDIxMTEzMFowgacxCzAJ ++b3RoZXJ3aXNlMB4XDTI0MDIxNTA0NTkwN1oXDTM1MDEyODA0NTkwN1owgacxCzAJ + BgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlDYW1i + cmlkZ2UxDDAKBgNVBAoMA01JVDEpMCcGA1UECwwgSW5zZWN1cmUgUEtJTklUIEtl + cmJlcm9zIHRlc3QgQ0ExMzAxBgNVBAMMKnBraW5pdCB0ZXN0IHN1aXRlIENBOyBk + byBub3QgdXNlIG90aGVyd2lzZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +-ggEBAM+lV5iaVats0yBFN4FBe6bovloNe3d0F9qMuhKqlECv6cFra75gSGmHJz6t +-GTK8zITU7sni429azTZC9IQnUt/2lW8dWzpZD1T5Vt1DYvYFqVzjhNfzeEDK88ig +-ENfzaX/cY2P76arJr0cewGaauzaux8heYW1CjBxWmk6kWq4aD+5jggchvBeOGEE2 +-NkV3MPbXut8fu+3NzuuIG7Z0ilwQv+KUvQ8QQb9VCwdsDh/ERsQ4loC9P4jtuWCJ +-ikIE78GxDcOMoC1ftJtW/mBCS2iCHipXrp2BDDJMyHxZjHpl0VoDR7koWGtD3sos +-EwUkXVvWIuKs432h2dXQ+u8HaBsCAwEAAaOCARgwggEUMB0GA1UdDgQWBBT0F6X7 +-1QRftDiSeNSY3bks3nK0IzCB1AYDVR0jBIHMMIHJgBT0F6X71QRftDiSeNSY3bks +-3nK0I6GBraSBqjCBpzELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0 ++ggEBAJv9Sbc2QSbHWnZjk55JfeOdPGUsmKOcT/N7C0/0mOQq4tUCmha7ntpBoIJd ++UBDhMQayG3QHruQX7aogtOx8hoLoLUaNKgxzEZ0OLbDRMc2M+vTDpBROITGI1KPv ++QtthlS4ocqKvqBCze66N9LufzAju61CyKdB3pCykPrgDVVScfsZ1t2zCbK0SF2cf ++ZAdIyCLoGLeQ95/NL3SIx0CX9gU47AVmBkSQ+LExJRhbUSIg+puKbqJ0XVILR1B2 ++ezgik2ObFND0hsRUS4v8pKnIDz0HXR2AneTESY+atjbzzelGA2zH86p4tLg0PanQ ++4x4+gpkQhzSr5Cmi3QX4XahSrmUCAwEAAaOCARgwggEUMB0GA1UdDgQWBBSSP/pz ++leX5zVcZ9hpI5GG2eQ+pqjCB1AYDVR0jBIHMMIHJgBSSP/pzleX5zVcZ9hpI5GG2 ++eQ+pqqGBraSBqjCBpzELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0 + dHMxEjAQBgNVBAcMCUNhbWJyaWRnZTEMMAoGA1UECgwDTUlUMSkwJwYDVQQLDCBJ + bnNlY3VyZSBQS0lOSVQgS2VyYmVyb3MgdGVzdCBDQTEzMDEGA1UEAwwqcGtpbml0 + IHRlc3Qgc3VpdGUgQ0E7IGRvIG5vdCB1c2Ugb3RoZXJ3aXNlggEBMAsGA1UdDwQE +-AwIB/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBT2FJVPS+U +-0MXa1HUOETuUPrVff7VeIvyAPm9IgX1zNbCvktCc4d7ErNB3P5ng8aZz4MKqwzuX +-HVhUxbF7JKfyUI41lcixPG+k+U9mzBJaozWT+K1OhdUF//mGPxaxe5jyUhDiQArD +-/6vulX0/B+1iuIa1sCfoeelzqQcYHqhZdWn6bBdcDWNARHIXWs5zPeKA975+d5TW +-rofE7T8nNQJvcZoVjCSfcYXhP82D/0sA+wPCt3fgbBZdvJ89xwvIlzBtiwC++Zbe +-37Rt5av0+ykpR7nmh2jyG+ItzE73nYKdBrUI5J6JLSbUcQTw4jeXHwDULUHZ6fXg +-TBEM2v1VW4Df ++AwIB/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAfx04Uqh0D ++myOR1PSqEEbMWJxZXYoESnjjH4Co4doceVBTuKix/2lplD4wcvA7aMXpmkvGfP38 ++dPrN1jvGd4bi/djTuxab9qB7rOeswAt+NyVHReUmuIMwgcW1UD7HXErg4EsOMjGD ++2XGhJYxGnwdURmnFwoO3yLLwo5K+C4rqPm3PbnI3W0sCA+IXepQTxuXK3dSplMMm ++0Pejw3es2s3oI9WaD2JRXvFuylw4UWYX+cyFRb+wN55Gh0rPVdxDhKCkbWNt/gTi ++/DbC+5pyQXkmy07OEGrmh4+5ae9hwejr9AukF2IZJB+oFP4i1mt9xyAOXImnWOzB ++SdHD08WHl5Gq + -----END CERTIFICATE----- +diff --git a/src/tests/pkinit-certs/eckey.pem b/src/tests/pkinit-certs/eckey.pem +new file mode 100644 +index 0000000000..14c2efd2ac +--- /dev/null ++++ b/src/tests/pkinit-certs/eckey.pem +@@ -0,0 +1,5 @@ ++-----BEGIN PRIVATE KEY----- ++MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgSB3T7ihe3JUeIKZI ++PCDqATKN/dNugQsaC5AKiBPC6ymhRANCAAQy0E88e1CX16/2wL2T+nE0pmlb7wBM ++0hOh6m3m2uDbVsAIRJfhEjHWsT2ODCoBvGDV6vBeIOUjE/Ro9EwnYBW5 ++-----END PRIVATE KEY----- +diff --git a/src/tests/pkinit-certs/ecuser.pem b/src/tests/pkinit-certs/ecuser.pem +new file mode 100644 +index 0000000000..585e53d8c5 +--- /dev/null ++++ b/src/tests/pkinit-certs/ecuser.pem +@@ -0,0 +1,24 @@ ++-----BEGIN CERTIFICATE----- ++MIIECDCCAvCgAwIBAgIBBDANBgkqhkiG9w0BAQsFADCBpzELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJyaWRnZTEMMAoG ++A1UECgwDTUlUMSkwJwYDVQQLDCBJbnNlY3VyZSBQS0lOSVQgS2VyYmVyb3MgdGVz ++dCBDQTEzMDEGA1UEAwwqcGtpbml0IHRlc3Qgc3VpdGUgQ0E7IGRvIG5vdCB1c2Ug ++b3RoZXJ3aXNlMB4XDTI0MDIxNTA0NTkwN1oXDTM1MDEyODA0NTkwN1owSjELMAkG ++A1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxFDASBgNVBAoMC0tSQlRF ++U1QuQ09NMQ0wCwYDVQQDDAR1c2VyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE ++MtBPPHtQl9ev9sC9k/pxNKZpW+8ATNIToept5trg21bACESX4RIx1rE9jgwqAbxg ++1erwXiDlIxP0aPRMJ2AVuaOCAWQwggFgMB0GA1UdDgQWBBR5MaRx7ub5YBwsS0CF ++Li18nsl49zCB1AYDVR0jBIHMMIHJgBSSP/pzleX5zVcZ9hpI5GG2eQ+pqqGBraSB ++qjCBpzELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNV ++BAcMCUNhbWJyaWRnZTEMMAoGA1UECgwDTUlUMSkwJwYDVQQLDCBJbnNlY3VyZSBQ ++S0lOSVQgS2VyYmVyb3MgdGVzdCBDQTEzMDEGA1UEAwwqcGtpbml0IHRlc3Qgc3Vp ++dGUgQ0E7IGRvIG5vdCB1c2Ugb3RoZXJ3aXNlggEBMAsGA1UdDwQEAwID6DAMBgNV ++HRMBAf8EAjAAMDkGA1UdEQQyMDCgLgYGKwYBBQICoCQwIqANGwtLUkJURVNULkNP ++TaERMA+gAwIBAaEIMAYbBHVzZXIwEgYDVR0lBAswCQYHKwYBBQIDBDANBgkqhkiG ++9w0BAQsFAAOCAQEAfwlONLYPo0BNN2NyQZM3wkoldvFqidcoZiYALOcBcmllMP7H ++XQ/+en4TmbKR0RUJN6AjR9yEo92fHAYOB2L7AzR8AkOiRLjp/Pdg5kUHFTdKenTK ++DvpeiJELz9chk/vaMv1T9qvOwH2bVAyS8GrUc5n0ui5F61PrquLAmm+dpKyHDY60 ++DdFaebS2gYsmy4bBv0mgcMZ+ZXnzXYmLNtdVQ3SgVGO7M8eyCqPbe/o0Lw4Gz+l0 ++xgpFkptdlEogsOaJBzjrgWyBnWw6MkyyLiSY+iOxFpBGkwCxi1gtQwbcp4gMwaxc ++p5+JPM/JBfglBX1lpRhhxL8EGQvpryN9MT530w== ++-----END CERTIFICATE----- +diff --git a/src/tests/pkinit-certs/generic.p12 b/src/tests/pkinit-certs/generic.p12 +index 35c27415bcb07c479990133882655bce3fe3bd72..55a248137ca7b82654252808422e97337ed95a6a 100644 +GIT binary patch +delta 2529 +zcmV<72_E*P6Mz&VFoFsE0s#Xsf(fz)2`Yw2hW8Bt2LYgh38Msp37;^637e53JAbV+ +z63K|{jPm6S=lRhUG)C?|)hzs!}J6Z>esz%vM91VoEu)Pmv^_jw4QQ*{P;%zdH= +z8s3InWP5X2dL3JP%_r_AH_wr3!haB>cU&;sQHeY0h(*0{Urg+^g7yVKn`IE0+Y~a< +z<+xCb5BfX!dU%zZc~;wYZFOctxMS?Ch*eD^8-zy8#7*(m&=G8Yhq%X1&fk&#wqvO` +z_dV6f%Lq>$}y?fWJ0eZbaT_3xCB5v<1XX +zhesXP-h3le9uU$XQav@c@^ng6qjNOuTgo57tfg;nGUhmeDh5PY6l0w(`tmfVgVRd9 +zI*Dp$(4kaUY$_|u8*tJ)z~Krm!cf$))c$ks%D6w-z)!1oA_bqAdZA}A1PNl78+^by +zVRwaGflgdSeO1RqxQGi;-G4bE0H%>kz6g9{O9DNB5M5tLz%C?ula24llm} +zuabL^!h`a+uPu%ySk>~=QyaYKaBuM-cq;N~+Yx$45s+L!{%M=B-hZOqUNh4Z-0?vO-hD#{!>=(UZ$|e*T(Ppu?Da=)T;Z(vy(zB2AS5Op(0bOE%ijXo4T48j +zaWpTAg-^G1r}2Wp1*k*bZcWwf`iU-QS;py!e2L#5(1)IJP}}evf+~Y&;L^{uM-p#P +z$CG)Ic1TsdW7ZAj_9OvHhVSGr5Tr)o_wWooY?lqp0j8d)wxI&i!7%mWDC=nXromP*=gpE(B1cq6 +z@&j;>({0S<2hI|cciYPo@g%a*p@w1QGgUypb46QUs!RVP9)D=_dWE&lLg-u?xw~PV +z8h$a6Har0<@wSwfG;mYk()2u9489&2LgL1C(X*xX1j1|82eY!A`42Tp_64UG7SgBu +z%wrY}ctA8@Orn_s)L0sRiOYhfl{V{IX +zM*Nb94b`ZcV1L~qp38AGXFWP*5hp^{z@{c)LZAu9VXKJ{o5DhRdf!Y&dE}=hwC#io +zYZtdIiA~U5r(Up^;hSx%T>+Jy<}L55P1hamg|^{b1f$(AuO#g5sY1S!!RH0q+MJHB +z4KRWQga!#JhDe6@4FLxMpn?T;1cC)|FoFebFoFeX27e1GhDe6@4FL=a0Ro_c1u-y! +z1uZaF1_>&LNQUz5YzW6GvH2i8WJEF=@#Jf&|c95Qocq9uI_J +zuEKYIs(pnOmNhS39%39z*|>! +zQn!$n2&<*PQxpN5z%hjmlZIh7OhJBIERDLutbco%u}C>L6m-jbjU`E0z4N4hv7qv} +z^8gz +zK!5#Z(o{x~w1P9(r4Li1M%kTWTj4^9LM>3^D&+%>&>Wli9sfvq8guK6okCa1xutOKr6 +ze5ic%+9n`|zI{F%*l8Fr;ljDGAvLV0jf_)^F#+Z_s_@|PN(w%P!=LYJK87-dtKya2 +z-ou2tBdt^%>*Aqa4Oz$fR`pK-Kt1~8U({s7fQm9sgMRC|HbAW@!MkNsfz< +zs3^xE3`}tjOvaZ`mTy27G6T*&3x76}mvTS&uHM)V*>8 +zLvM!WSi)FK>L5EKYmVS=wl>j?$b4-)uQ5|+rMxsUIRUb~2(_i}gtCLG(Lj^DK^6HR +z9|Bvq^{Tc`3RDQ#EExyBL%EMeJ0A)4WmSkOXqfGF$5-3gA<;I^%nD!<^?!uBBky_h +zDtHZ3Hq?a{K8#>eqP`A1yryjk_q9*A{X2bwX11sbz$$Y)U2w$6y=G^r0u5K6r-Qq$ +zhmU#{eAbmCy5=l7y`BIj6sdNhVHC%_79bPl +ziqj1XqSWmBQHa9luW4F^7k`C(;)-1%xig{k$ZRTikT+J#^4~y14n$+@8>ZlRhjX@X +zGv{`|4XV@GM}bVDI$XXh4edI-nqiEw2@N*ip5xg23vTl~U@sV*_`HJxr#5B4$dV6v +zj=|^1mfn3wd(>scr_%*luv9D=!6_Jf0%mi@W8$z1!3`<@lUmc73wTM| +ztMxU2wp4lXT+wF?cD6j|rz=JhmfCBDAl-Ij#Q0W%F$P)3fwn}<2+d$G9GIQzMh!YN +zAmjmBVsnk6AgHmakbkjQY;aSX$`ujO5N8damZY7qviDEls~HZTCkILT#rJxJnsB*n +zR>-H**-OEFgmF#KsXF__e;4CL7kK-*wNi<+Exq~-VcGD9%`qh~BL)d7hDe6@4FL%i +zF%|?A!W!(19gkH}sK#VEJUUJdl+OXWFhMXeFbxI?V1`HmWi|r@0s#d81RywUr7PU4+J&7;H`yIZWU8rteK!mx%-j>llc3nny_xmpD&=el80aE%9m{fV68_AYFzhm@kKP%T=aiATz>)K7usww +z(0*{b68eczuAI2a3uHsb3Zn_F)9~g+LCvtun(x>N9_kuVVuYlA^cxgA()Q^}p{@VX +z(pQJ#h!L# +zvT%~2SR2lMq2|uvwt-65uo+g%PdM+yCR*0d3<~^2SWHuSkyu|U{2*phDUa6z^!f#K +z&e!gf!i)wC0}3Cr;t4QJ>qXxqtd29tYZ!QSIJXcb1=)#Zz@NztpMM)iLM4FN{(b4O +z7;Y8sDSuKgUUFZq^5D#5vcVSW7Jv7Yd(aBQZLxL`TMK);Odg}Pn?i_&$%=^rbRuT) +z1@-3i4P15rwSp}Q52aT>>NISAeYnh|qF(@TkGZY4j&#|GeCZ}Hv|Wo|3Ildq{En?` +z33W*|;8`HbhQf?M^M5j}nDMonW~5ihFn!pqa@$I}d3&;Sf_zYJf +zK3966V3l`s?KpMq7Xkws7rQ0uNx?O)nr5OZ&sjxPS8n6V!bbhl+S`tADP6n()KD|d +z-4;CP6oPK*x2YwH4h?UM!7FQ+S?s;2-TRK1t94vz+%<~h=YKH5b;L89yyIr-R20Xh +zS{BJAi-^$up9m8hh*-25HX~YLbxrHdA82beU_`X^W`y^J_2X$56A1j~=kc`==Z_+T +zu7n}%#;**bdW+$vg-Mi7)lR@16R`>eP+7#cXM{AT8(O1C6<8Jjm?oO_Dk+uOv3t&g +zP!zO+$`Utaxql8|%C$?vj}~CsWJnUybmONs+6V!t>=}LX3`Ea~#O_UYaY4adZ=6kJ +zWazr~1Ui&-TH9eCh?b{xL&MfNVV`KjC{9Nh#fgY%F57NeV`~Z=1e&f>y{fZPK8Y+& +z12BRGIR*(ThDe6@4FLxMpn?S|1cC)7FoFdlFoFdh27e1GhDe6@4FL=a0Ro_c1m-Y; +z1mZ9p1_~;MNQUxYm$0NS^prVcA%SO0uwo +zHTQ=?Wn+3w3*FrDC(OA-xz1k4gg0%e7 +zE|q{bGT1^9c;Po6)t2QAoislaez~##_3so3$~8c|6ThDALVuQ_`csrzZi7niVJ9&B&mxm=-_q))60k=1>ymZmg$D41NgWqT6$()f5qclF-{owN-!( +zDNM-9lt=)MEIj|1m8It)GAoJ%qaW@RZQc!WT>eKQbxMSUwCCXnn=J7dW==)=q%_QmbS-PsoBVmQ8#q>Z^57uw%&$COK#CU_J%s^HpCp-Pso +zsVG6ZqD#N95LV4&Mw?3o*)hrMz~EOMuzxqgdGzSOzDoDF?Pkop!&U7s;C7a9@Bi+r +zp!&vTiAsBFWBiz9fh!LHbIQHHOQPIy8weJzOs7nB3foOb*z7OelAz +zF$EsP@)6NmCAs=uFa;EVKna5xx0wGC#1`kHKO_2-y{6nHp9b*qZx8n~sZv2}KYy=9 +z;~-}X`6hf1FeQEp+V1&}(%k_e+XYC3sTNhk<2T!iAyy{PA(IYE9GAHj(LQE#V91#~ +zMpN1OiY)j(O~~5A3sooGn~qp$_FCm)P6?T^VJIP^FO{K4O+egG1sF_F)0>`OQOio4 +zjE6GDi!3W7g|OZ^z;M7OTLGbU+pY!hzrsCEIoUkENh3X5HhDb9>|ytO0Stc +z@^I;i)OpS|&hTxBQ&rT4S3nmdg~fDy@qjnF^LgIQbKcXMGfqm}?RX#B>Gf;lzxO}jM- +z^t@NcdY7+{Q-)q4=Ko$b^?&x{b!znTkhh!0ERCBt;t0ez_1?u1D*pnqDZ~gg>wdj_ +zDezCIv8*1y!-2=uWr$XJ4OSw>q5$^1c@yM?IK-sh^c19jqpxi*kgsE^^W-y_Nejad +zd3khp1|E-OCe?G*$q+&?|L(>VjwhpmXG}Pxk5vO(Qyn{y81;~`)PL~7xU*N-!)((Q +zs!t34$4XA5po!lQq66@%NADy&c}(Eg2(@_lQp3?MRq!|?PO=AXq;qSylg+*;=831x +zV?Dd^3!*hvgv4ud@Ta2hPE24|C@l*$Kh8kG4%2x!jBss%!yz?NiaKJR*V?l>TPY!I +zH_XRAWAxGcTje&~Pg*f0Fe3&DDuzgg_YDCF6)_eB6wv3{Iz-K-_+dQJ5g?!#l~?ug +zDljoHAutIB1uG5%0vZJX1QbP$4lejcX32OA_0p;gH()NMq7(!OS_-~il6^%c0s;sC +Dy- privkey.pem + openssl rsa -in privkey.pem -out privkey-enc.pem -des3 -passout pass:encrypted + ++# Generate an EC private key. ++openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 > eckey.pem ++ + # Generate a "CA" certificate. + SUBJECT=ca openssl req -config openssl.cnf -new -x509 -extensions exts_ca \ + -set_serial 1 -days $DAYS -key privkey.pem -out ca.pem + + serial=2 + gen_cert() { +- SUBJECT=$1 openssl req -config openssl.cnf -new -key privkey.pem -out csr ++ keyfile=${4-privkey.pem} ++ SUBJECT=$1 openssl req -config openssl.cnf -new -key $keyfile -out csr + SUBJECT=$1 openssl x509 -extfile openssl.cnf -extensions $2 \ + -set_serial $serial -days $DAYS -req -CA ca.pem -CAkey privkey.pem \ + -in csr -out $3 +@@ -152,6 +156,9 @@ gen_cert user exts_client user.pem + gen_pkcs12 user.pem user.p12 + gen_pkcs12 user.pem user-enc.p12 encrypted + ++# Generate an EC client certificate. ++gen_cert user exts_client ecuser.pem eckey.pem ++ + # Generate a client certificate and PKCS#12 bundle with a UPN SAN. + gen_cert user exts_upn_client user-upn.pem + gen_pkcs12 user-upn.pem user-upn.p12 +diff --git a/src/tests/pkinit-certs/privkey-enc.pem b/src/tests/pkinit-certs/privkey-enc.pem +index 29d2f3d38c..fd36246ed4 100644 +--- a/src/tests/pkinit-certs/privkey-enc.pem ++++ b/src/tests/pkinit-certs/privkey-enc.pem +@@ -1,30 +1,30 @@ +------BEGIN RSA PRIVATE KEY----- +-Proc-Type: 4,ENCRYPTED +-DEK-Info: DES-EDE3-CBC,5FFF1E71BFFB65E3 +- +-p89x5YEL+Mb6IPZXEkkr0KC4Wj+JtgE3VKdTT0wEcRD74QVv+dbbZt62WgmpJtId +-ph0Ial2z5Mws8L/aTkPdW2H/bEroApLu4TfUV+w67KcWgrc8gOg73d6gEObqx8li +-qGbs7FC1cI1WfDfnNOnCbD66e5+bTI8fDuchaieNRqzROd9RHhmlBHgylTmf55us +-laGuwLq2cZk/+Xz0M8PPx07uauGkAK0fyfifn/JR3PsGsE9s334osVQMjbjyT0VE +-rm8HGm3PvZHHDUnkOh7AGKyEtsIa5fJAULUjugp2lQJqOigC4HVn8a33xfLI0F1+ +-2nH9MZ+Ap1rtI1cJX8CDn/Ij9oFt01scLxynYekYej11zFiR6qHC0sspxu0Yi8l0 +-puBPXCI0GzyF9I53ukjGeibTtssz5yw1r+2oVasR4bvfXczPjqTQCBsPSUayNNhw +-RgT7k4QTY2OlrK/5XdILBzBlsvfndXgGOwEDw4YE7PMzMmz69vPMK7CfedUqtuXq +-bGBks58tzeOa4NSfVDOuFLI+LMkoYWMSjPGD/I0trX41xCU+O6PZOnDyt5ZWl1Tm +-klJpsB7rUcwsP8d4w4QGhyyV6Mo2MTlnTILr4CwwvmDMBch3yzwbfKdeywsFQh0S +-NMrG3aYNO7csRRTD6aGvYcBCbavWq7Ujsb/fV7SOIS26f4VEqewvOFlFEXm66zaz +-GJ0IcjtNHYNIIIW4690djxPqlGgbIZTblBSBlT+iOW5HrhXvrLeMmwAPxInU5dK+ +-ypk2MGc4SzemkDi8H9jDW3dwbgcvVD9wn0glhVLQKWvP6F73UUdVEXMCZ+960xnR +-gxeEwDdIpzXNadWdON1kRbqI2KesRY/XQErGHDOvf2gNSM9V2gPz+5humvcu3mXY +-r4537On4+IdzetEVtI7D0slgojs+jN8waigpkLFB5RVl8PnzblMuWOkHNA86rrp+ +-h6wNqv9kHLgPjpAyB1l/7w4VqXLXeC4PdaGc2fcpdNWOncUnHROmDmYvdTocqhIF +-bAsEFV7QZoTgDB7J6vLsmbtfawtHMSb81V/wTJWRrtY/gJCrkJXR2pTYAZlPX6vK +-aK7K2NuhJFMnrQD+kxsrloSEyfsZmHtk0mAVXJw4wSxlH3eGQ+Jphb/M2wtsnWV1 +-w0fehxL2Vd5SyBBctAGhUirhRngbOO/E8IioymrziQ88vJZs2DxvbuNG4WKTuTwj +-CIggXohCNKdqrwL2HAynm2FVEWhbKrQwe4kjZc64WjccR4cy9vv+dxFfrKl+vZ1o +-Wvb0WXND7fiSBrPo7OfaYM5HjrcvIRP1AtMuArhuQYVARmawUG0l7dFLN97Rh9M+ +-Ud9vBIfQYlubnTGVVm/5xrUh2isQbp2vrZLfMrUNXMQm0vSxKgGkAxqNUuklJC06 +-LvCtEWMYXiBmB1zP4khwCHmHB+/E1gHBAutCzhpPu86ayEtNHBHIFkqKvZSg/UuZ +-+ygDdTJV00I2neIdeQcyG+vPg6huIDIHpG5u6eQn5sLqVkhr+apeNcskMWpdkpFS +-Lo62KUZDR3yB83ne63c3IGex0hWhVojJOAxykpGp6OD9uFn6Xn7x2Q== +------END RSA PRIVATE KEY----- ++-----BEGIN ENCRYPTED PRIVATE KEY----- ++MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIBw7aG13XYxwCAggA ++MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECPWyEPoKz4WhBIIEyKHdx+pkDxax ++dCCUZHsJ54boZxh+7f7xmO9Rjm+6+3cE+WCjPsiGHPUDtOXLxWwcrG0RAmA1GmrE ++yZbclwEMF8LcWQ3EUDMCJXBs7CEtA4XDH+EW1KsZwP+cA53ZFFikGj3sW6Ix5GLi ++Df311Eumhp3GABU57siNn+tMZJAorInth5lXBJFQoE3KJbBrSN9iQKZTOpgr4G3B ++G+qzBwrUKnZrGIp42t8op4VkB8sA6xoHh/huJB5pNygt9OZUQ+xdxvNQq+5/kJ2I ++mP/JRPSuN4GtnNA4fBB6tPv8t0L8hActkWlQ1rSJwWnWge3t4r5/3FBcAbl+zq3k ++t8A0LWgjsiQRmlKRN7GrzorOUKFv+7YAq6rc1Ek79qitUgEiFkwZZySt5+yPstMW ++vpaq2V0yDHf5Ds9uXffprhSAjnfXdT4NTg5eMeH65OEedUpVVzHauoGfFkDGaq8L ++8XgWPZPaz6GQFpU5SGk8FZn0OLLJHnHQDYo+ViL2XSuuqY8Jd7fmpzqVoHOU8k9Q ++/ONKW+E6uvkpNH6NbknceA/ip1bcdfwA/uRBckXjCc5uR0oB18M4UQPuKlcGev39 ++mcdlvzQJxl2EWbB8ULazzuzOVfCAEwKc96qOkDAY94CB69f/KhBOd2QqHzdxrQ+3 +++K+YduhbfP49Vxaq4NIklS/kSSv4GEBHzEwtFxX4oqN4Er+UkBSB423nvlkSLd1g ++tR4M30lJyzmHtOEpSOZYLakviz36ZOCV/DsxrfziNG/0RB/mPLm/B5L+StqjJrTY ++Pjo3QHKb+6ShhTi+jZ8tqXa68+TZO3Q7eTgqrcn8mq9jfama0KQF/13kmUsrFXTS ++wk/nbSP10z+MhO68z7o3j+Q0Co/cXkQke4slvc3DqLNvpQdDMPLKQVxtkPBq5czr ++dbk5K2GYFLNWO5Tv2RgBGomznoAGSolz5ozIqxffVHAK4NGfhihgLO/6GujDANVz ++EX/2/IacRg0L0x7//O/GHomiFvWYnDbHhRNicERe/ji1TCxJ5glqntFjOXDumwi6 ++f+mQWNWlQWtKq0IOnlHrBB+vqykAj+e+FROqJjuNI6hu4CNnrBK3Hf+NY+rXdn7l ++iCTD3ojdufqo0JDZe8dXea+B7Zu7WNAxnpW8D018DJxR2hoBvT4Po1CBaHLfxAkT ++ZGeXMjp1vZ348xBSppFpIpYjFRQBeBgSezzA66o3YIcDeHu2bTzg73DiUXNgV3RG ++OyJHmsOmN9Gax/Cx4z6/Ff7seisXpIMRU9TDrRCFKAcPHXAl3R4L6guK0I5OGwz3 ++GSMxsx3PGitj0x+1ynW/Tf+EJQD33ognc+kuQfNL0XW2tNJoibZIs1WgdbDwD9RD ++X7rbb9GfSJlQUnBFG/EKU7SGmFZUVMz7we8vckZ1PfeIKfH7OWrZ2i1WxIF2WO1K ++BX4TXp0KKt+aCwf1GInQ/6aYgh5g8W2iKuz2HJeZIN+ohciNmpOynsFmHGXdbvnO ++Kw+msZEQb5AvhXf4ToiSwZLSwq3qAILN8fOQQ9ta1DjJuUtITpe6ys9xhlnriUkm ++KrY50GkimLdD6XszC2uNulAuh3o0nZplqxC9IOLh+uasEU/+xqtwTaaYBljTpH2C ++8FPAEFFUVy6lsngJEQvdjw== ++-----END ENCRYPTED PRIVATE KEY----- +diff --git a/src/tests/pkinit-certs/privkey.pem b/src/tests/pkinit-certs/privkey.pem +index 007b6275df..2a25dc19cf 100644 +--- a/src/tests/pkinit-certs/privkey.pem ++++ b/src/tests/pkinit-certs/privkey.pem +@@ -1,27 +1,28 @@ +------BEGIN RSA PRIVATE KEY----- +-MIIEoAIBAAKCAQEAz6VXmJpVq2zTIEU3gUF7pui+Wg17d3QX2oy6EqqUQK/pwWtr +-vmBIaYcnPq0ZMrzMhNTuyeLjb1rNNkL0hCdS3/aVbx1bOlkPVPlW3UNi9gWpXOOE +-1/N4QMrzyKAQ1/Npf9xjY/vpqsmvRx7AZpq7Nq7HyF5hbUKMHFaaTqRarhoP7mOC +-ByG8F44YQTY2RXcw9te63x+77c3O64gbtnSKXBC/4pS9DxBBv1ULB2wOH8RGxDiW +-gL0/iO25YImKQgTvwbENw4ygLV+0m1b+YEJLaIIeKleunYEMMkzIfFmMemXRWgNH +-uShYa0PeyiwTBSRdW9Yi4qzjfaHZ1dD67wdoGwIDAQABAoIBAEpnKYMR0h6xyNjo +-VGIpT6BYB1UHPbVo0N9Ly6TCoIqpPe5DioDVyTye5A4OQlgu1G3ISqPme6478ApA +-ZZMw7/42QgdlknnOzbKaAWkZK02Sa8RP9hrXL8CvuDisOjzXCHd7RdXevzSmPfsS +-5sgdK3YFnKqMPwbCcKf61CHXvHJjWGuTIHIRh8P7gJelA4ahO0kYQ8aRXv3ldquO +-ukSI5gyk9CN+aAHqt25kEmt9oOgk+8kfKpnk+5gkOCY2YOFDDckD7nL1VIIrDxwG +-SmU598qjVwycDairWUY8uSuPCOLgbvDM9N8cERDMsyNQL63GE8ZZyHZsJ3Pbwdfs +-JVHh5ekCgYEA/CwhaT9D0WQ49GQdeI7aqazHEYDmqPdE2/qbmr67tPMZzX8AAk9j +-r4aMT+oIdtIMPdoQNNcBP6NYZLlAoMbLoAzHmWJnF5/YWLnS2Wg9OuXUOBn3jk1l +-SWelJfAKGeBld5fpSLTdHjRAwJrNCX+mc0IZIiEw2IvGUPgKGX08bX8CgYEA0swx +-xCDgvfoaKueInw/rUIcKxrSxK3pDhaR01Dg2pwSo7Vj9W01zf33qe+mjma6+U2SB +-fk+/O2VXDuEOmVDLwvp6PkmUeRE5PyH7urTMEjy5ELNGiZd9zHoG/zJnRgPwTjuW +-yguvjVGJwI1IvmODuA7Xc7iHFlvGNuxXZjPkS2UCgYA0nFxoIdvbTsaXLl/7rAow +-xixOGY+GBvil0HYwZcSxrtpeRjXRRZDtqOuTLKeRaqdFLD6fV5AaH9EsSn4STQdk +-n+XwuVf61M2FTVeRJi9IH3UUM06zsLAGDYqmDJt+5JMmzVnNYnaTe6FazbEjXy9x +-8oNd3IDdXOQGNomc4cT+rwKBgBbABOr25Wp7cJGK1XrdO/c/69DQNYLMujbVLeqt +-enCCFz0uaoGNFVcAHutqpsZyToYvha49KxVc9Y1cirfPOX58i+7nAAgk7Lm8kC9x +-Tcj2Fr8PqiA1YlVMIi8uoGi1Ch1XXwnFQxgMYcKPPPeXQ+L8bxJFKwcltnm8/h3A +-ofXlAn9AW6fYZLSzOfNQTMnuukhuAtZcEW9NlJHbej305zK89J66S8wroQs5iOla +-5GG+S4YaZh5sVGw+mnS+FCw7cQCUk40kXwX3yTrxlX1qGSCFCQnFdJow+5NVg4D+ +-dzDKzniH71OZZFxTqiiz76XxiaW/rS1uOfP/WSVR9NBLpV5n +------END RSA PRIVATE KEY----- ++-----BEGIN PRIVATE KEY----- ++MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCb/Um3NkEmx1p2 ++Y5OeSX3jnTxlLJijnE/zewtP9JjkKuLVApoWu57aQaCCXVAQ4TEGsht0B67kF+2q ++ILTsfIaC6C1GjSoMcxGdDi2w0THNjPr0w6QUTiExiNSj70LbYZUuKHKir6gQs3uu ++jfS7n8wI7utQsinQd6QspD64A1VUnH7GdbdswmytEhdnH2QHSMgi6Bi3kPefzS90 ++iMdAl/YFOOwFZgZEkPixMSUYW1EiIPqbim6idF1SC0dQdns4IpNjmxTQ9IbEVEuL ++/KSpyA89B10dgJ3kxEmPmrY2883pRgNsx/OqeLS4ND2p0OMePoKZEIc0q+Qpot0F +++F2oUq5lAgMBAAECggEADwzB9vY6FPa46KE01dm7VqGN+SjzVR24rQIbFkzAD4t/ ++tRN6MGVLrz0TsmA0YFyJsV6vvWMcYY9Zc8eSDRr6k1i5PYxTGT5k3aVHjT6xsmY+ ++tCzIANmE5FWSRnrIFYh1ry1h2gZejbXzYeT6TrvdIKOEepWl6SIR6eiy0Ggp7G7C ++SjlpT96ZtdE2RnlvcxcACtwhe3vPbkLmTCOEqeZ6LHCHIHiK4KdJgJ08OjU7Kgsr +++vmnwTJsH5s0b5IIznfWajO4JNOpqjzFDjDctGYBwp5xF4zu3u4bKe9aleM0q/jl ++ZkibxLsFAh3Xkh89nxr3E6oBLm0F8r8M7PK5wpMShQKBgQDAipf6T6XUY+ugkKw+ ++301LyoAch6WV9oT6uOJsAttmcUpUr6NXhRT3OM4oqyYsAc5JW2wbz+n6lED3j6Ez ++QEKSIFrYpjrYr9D7hqvISI9JT0PhVSPXECfifEyIR9xmLvV9WQq7NRCJMi26X9ab ++Grqpw1HNlPA/rdcc/dY0p25DlwKBgQDPZqxSnwnTa6X+r0UdR8l6kc9VuESotpbE ++0ziF222bpXmZ2GKiEU1buFORHih/e3yDvKvq+p2apyUKnEEVQg/TL8/Jzya7fEOI ++lTXcNQ/f78ef+nwEAxdRVQkWXFWHvvKUHm1rGCIY7zeOLnQ9JjBQkgG8zhUamAP1 ++owLBBTstYwKBgQC+yNX9Du0HvpbdfF1g0025OwekvXiDV0m/UnHxiwcxxDJeJceZ ++0mHK8nu9apGha4ynvbIrAOMdC8gwRh76NMOCHhNGt7h5vAU9Jt2S0OtCPgvJ/N5N ++nVGYJ4iCRYqLqh5QvWlXxSYEfDc5hPuWp26tBsBJEDrbLnuH27JkbD9jMwKBgQCM ++f1VFMw+I9WehvEHpr/PA4H2/5/A7ClXgR+YGZ7s8sUBLA9btSyNIevnBWNi+Y3za ++ETm1GMkjNw9UvL0qFXJ68eylHXtzjp6BK/MslZWHcfudWCYi4aUuJ5jcWPhn2Oaj ++iGk/Hz4Z/hN4cee0dOZN7lrW+BQ7y7cC88at00lfWQKBgQC7YeW02aUPw9jMJh1x ++lDfBh+E5sdRwRQIvh3BuyTd+m/LI+3b9RSy+LIL2KFJucwKm9zR9fy33tHF2S5En ++Q+inhyXfOEygal5Rzxe3Pfx+pGZbzr6IXkhquHtjuFBwJJCrSeR66V2xDmzJfCj4 ++TY+CzwOJ/EltH4ZjPwEmE0S7+w== ++-----END PRIVATE KEY----- +diff --git a/src/tests/pkinit-certs/user-enc.p12 b/src/tests/pkinit-certs/user-enc.p12 +index 1cc3aa3da67160fd9298b9e2d624a80c5225245b..69780bf82d1452d5dcac91e5be550f5eee876583 100644 +GIT binary patch +delta 2892 +zcmV-S3$yf%7U&itFoFwY0s#Xsf(sf32`Yw2hW8Bt2LYgh3kw8-3kNWQ3j>iNJAYJI +zdvzN7hNA)k2mpYB1u&JU7+xCwe0Kqqz8d_+6eNN5bCxnq;S}(P;s|YSOhpyniUDCM +z$(1g#xF>v`h_!Z%^c0Yt!ew3_&P4(J0{#(9L7ePjfyd?bB;_QXFR7CVbnjKE +zff_LH>L^0?(6j~#3lev>bjhWkNPqvbKN_||)xXx>C4@AfTFlZU&lEHPKi4d4nj{f+ +zP!qS+8ibCntvC-FbX2Bh1;e5rJ^2HAbUA`MID37ixE)(i7Ff}3>6U!A7&UF;M}TrV +zs}FygKNP+*;c1FU9mKlF^p}|GEw>3l&8?f;h5;kVci=EJL){2P?KXfBQh!XdFp%kd +z*Ocb%wx_0U2R5IZ6yZz5d98E>a-~6_Nzx7S^X30+Efi5p^xb?~9+l&zoMeqIyzty9 +zzsz@?=R*_{cx2I*TK8_AuFLy??NA&|dO~=b>}Z8gy-$;V6$A;>=0#FrJ9Jh^u8)2W +zt?+rI1Fh%@eL0#j)BLG)A3wR#E)w3fN%6wZm#@DhR=+YxKGK(9yT*!=-Ns++Ssg0N3T2 +zKq%m4iv@P~LQo^`Mo(vF<$H-+DTiQc__AMQ#@XpYeg-&|lK+0f`+xn?YDUnk!*m-S +z;3?iZQ;n6bCF)Qt5(ITP`w+u&Ly;u}uQ1cMO&(bc!cfKO(a7W1_to~F^2+x*NgW)a +zRgZ@G)H!U6`FfcAt1chDfZQqj=#Y%;epbIDezJsIK-L+yD*p{<_X+yO+c(edMB^wL)S(YO~>7lcBt24a8iz +z>BqxgH5kvEzcWp$^y>%zfu0v_^AWYG6ydYCMdBu*~jpcY$>CJvKJs_U)yy}@2d4 +zseJR~7;BP{vz-tmn*ed*fv=l$4IYo;0VNXLW0o&Q!hbzT8q!Cnf0=w_#lXRiI)uQh +zV%mHI&J^p_;AT_-H)BKz?DO2vN05+sYJzW4{Ma%7L9plT_fE45Nv3yqwF;+zhVQTO +zpOQc^lLD9bh12?4t8>L1*vVNL%HEQGQ^g7Iqzw08Z-Z}ai+*rY33XPO~LIe+^&!Lx#pTd1JNYDu^}EZyH0 +z$TBU>AwxbT4nFscHMfohn3#mbtq1>m!CLz7(PkxiHtbu7bgqiuh(zg{?jAs}eZzI% +zFoFey1_>&LNQU+thDZTr0|Wso1Q7OWhVDa0j>1F2`SnsQsFehQ1kfy2Q!4pK76IBl` +z;zFNdALdBbb<1BjS7BgZ(Uvwb_x)%e2gv`eSYyFYtolD-;4XL1$)~RR)=d7Pt9XT2 +z;H%o^nkJ=d$#!Bj@GdAIKg$I2neN|tFeWg{;Q5)7zZpSn;T1olS+KVY_lBjjwSTe) +zb)VtP1PSkBMrT6Ioku_4UD?1eYXo!w@)n$E=yr0DHeN_iT)WQ6>M^o-i+u>O9PoIg +zAGit!#=pHqe(^&@p}lf=ph>TGzR7daU+;xp%fM56%$^@8^LiN>2W8{#gG*mrwrQTCXiGT6C0P}9P +zs|HR1en}U7kxre1`Zf~z=zusxmp&0{@$79lWY4|Pg;*&g_Oe+p8NJOzK_L!6qxHN1 +z?Wo5%?qF-5mPXn6j1@^xU2U4-hu3KNU$R*4M1)ga?HW~OZ>B1>f>cal_H>l{EfYi(x*>vJWha%>%~F<{Y3;G~g=5W0 +zNP;AzGh~ceJN%XEcgTUHj%>^4C#ZxH?6p$Mqsj%0UB(oLJ@l>MUVqg|)dT=|$YBt( +zu2-yM-d8h1I*-*B1twRw?8)`M&%5?ZL)4|XXI)mEAX@=CT2zIQ?xLXZbZ=q$w|RNc +zZ>3Z*V+x9d8*W<~L~B*X-R_HY{J-_qK&HaBE?`9LaGp#ZD$Om(@(Ey?#+yaX4a>b#%;z-;9V$w~Zhztczb-lw^6M1A3tw?M@RWNlUS#ne~ROieMk?E65E{^AK +z7h#?hzu<-)YhJC=Yew$y%NLL*@kQxtPEqNegvafKM+CXxV1H3%kxZd0Z?KMjenWP2 +zDEQZC;eP=48N|TQyell~0OIZYWa1A0K=#U@plX6Lw4tVn=FBwtcl==!VhSZ>7a#GL +zeLF|Y>4kb43XBgD=c(8lo0cmG5qOT1mNt)#aYl&g8eo(_C@x%x=Z0?)2V4r^U|~%j +zwef5hK?t&1Cx5gs-#K!O$Fo6@oZR^_4{UsI +z_6exMhy7&>jRUy#zp9Kzw!Qn~s3Tj;OJ%7vk6=QC$ObT%xXiHLaQjioZwiaA<0ClW +zwggOa)Wf1nmF08GoHj59|z3`)c&@iDicIM+kgJ +zRISnBfo4M)P>kfKHzfzj!^f^60#?=KX|!FK2seV>)(=(-F(oh~1_>&LNQU+@7L67nFoFvS0s#Xsf(p+D2`Yw2hW8Bt2LYgh3cv({3cN6a3b>IXJAYaW +zkCdOgiwFV&2mpYB1u!3O<&^}aGBO*zs#A~w5-^3s&_cc&jq;YL57~y$hvIsVoE;K_ +z@z;j^#4S|l0+R+;ItHJprmPBs9a_CK@fJSNtw`GPWz{liz2|+I@@I=qN +zd*k07Kk-OlQ37X@O=rz$K8j9)sDGPk-J>07oO@p^2gA5p`z!5za|7wrxe^F4OVF>Xh1Hq{RgYQb-)_;_0(RzORrqP +zH-6>9rCa#pY~43l3lM}D^Of?GJR5SgvoXe*9$n}6%d~(8ikvS`(#B<(zkf$~%Lonm +zNfw1+O)XYi?faC|BPm2d9_|?t7S{=Ai6*tpKfHTW$n&0tGKGpq#c70A92fv&u=zpl +zndE=f^VVig+2W8a6z{qV5}E0L^?@yQ(w=_afPb8CzW<$heOh7oL&Bp!JVA(4`j!s% +zx?fmOzd<+D;8wY*a3Q5ymw&$?>x3h&8^CM`GvEb?bI<46b@_&FrCy5mUzL7?O +z75ZMcP~PA0fwL>T(tjNAt}i#t^{zcGAz$K1>g*LjbfwlZDxSI=f3@WTNBRvsniHoJ +zrf`Aw?(*vKOp;T9&*{>E?K{SpwH17yQf5N|SCfKDNiq!E!Kg0z0xGTa87-U-d{!(c +zOck-NH6ki*cGdv_BJYIDt1zG2GRSfp(v|3ukshay-y5XaAb(9oViC?Tq3k5i`Feri +zttHdosBb8hps5^^GXj>(m2-YD;2H7o=p>1+rtl&MShE!M2?ed!cM9FV+zrx08{dcg +z$P_4=1NVpARU=pkQzG@(0n8E$!qClc>?KM@^_=a-tr@90VCRgJK0TybnLc+Qssc&Z +zXfn=HIBPvYFn0)M_`f4e;hA?uo%xSz0OeW +zNFM_sq}piLP@uBVmRVni#!=stFR|Ks&x8Q}?A;OK`2ejw&Tj(Xhx_g=L!>2WVRs-z +zGh6W&y&1fnPPNbM)lHyo;rWG^)bHE>E{|PL^ +zqx$n%KM`zvAgWp^r!U~!wxypufmE8|);fuwpKp5XXz~8Je^dVJD= +zOMjqU-55=b#50HSV6Y3_+?CRuZf!2M)0P$P%bi%ACPtPF;+gqmM41S)bV{HH*6?l7 +zFoFd+1_>&LNQU?#%Ea+EPCb^EOHXH_=gdD)Xp+~Q2LY2kYjfS}$1nj95o?56!tflz0;}d?$ieK^6&}s%L3Jl^~}=VgTY4GhW%*_#a|; +zj@K33^y1^ +zK#A=G{dRZu_!rdMFb6QVMS(Ip`>W|< +z4z!ekC+@j>Qjlx8(qW-9zHdj|;^B76#Ng{%EWjRq(IuJ6<)&awN4K+UwwNJ;UgX}E +zcaJ_73k>u#l9eP;Li?1Szkl)Oo#2|EAHYvAy25&G%=FCq2i{a!yl36`>q*b8T+*;s +zGejdSSpV~R13c(;mCzMMN6iU9ob!=eBh3r+awrj_y=yEp3JoOQx6Z$xK1o|4KRZDM +zQBnXVwZ>=c$&42g?R^ZF45)g&{)y-{_@gyo?dqxX9ErtT{JxI*8-G*_P}pLrW-1E$ +zHaN5nvDs*jbx_bgcL_D@$4QnDKIv6ws`(`gn9V-+tiycOjA@wP5X_WY$OELGX +z5KUX5YQ@_olJG;^oZ2yRmCnEb1D`a+vxyqG-vy~yGiouy808|&S{7?MV?BdI~5 +z$r$t9jm4bXD^b)>rkVn3#X)v{8>DuBwKbv=IxjOWC+r+8lMwH&{W6*4g#n;A; +zO4M7AXKtNumgi-Bj**jJ?fCr5H|=aBrlV9HN^WPkhM=+(yMNVi^GonVr&qU7AC9>b +z49|BZN0T}tH2DM}@daN982O;;XiJbw%m1(>&0zStXTCHiqL~wKB?P27Kz?h){H&yT +z0a6!f5zd}BisU?Bf9`(d956SlCT>3gvm?yLEEgOHleI(q&4=!s~U)JFb`+L~}IiD*+*g0&iUOsbSGYgzb>NdTp +zqqUpm?qjji{*LRy^gF1p#_?VHi4*L7pt_M9egYpb2L19^P%~?_Q6MTIso>1niUlBz +z&LNQUXqJJ21=NG&!mOU3kJ +zFflM8FbM_)D-Ht!8U+9Z6sQzP_9i;N@>ZIW53D_

e+>;wo&%Za_$K5C!>0tf)< +Cv^tIe + +diff --git a/src/tests/pkinit-certs/user-upn.p12 b/src/tests/pkinit-certs/user-upn.p12 +index bf47384a8a654fa77d9d9161c801292292ccf4ab..e91cc8a0c04869d6cf9d66f5b1b051e9f3f6ac58 100644 +GIT binary patch +delta 2884 +zcmV-K3%m4%7T^{lFoFwQ0s#Xsf(sG`2`Yw2hW8Bt2LYgh3j+j#3jZ*I3j2{FJAXS! +z*@zwch1miE2mpYB1t^994w-1MgeIP0Shaj_WdPD*<`ao4RB4%B;1!qUi4&p(CDJ;S)D+_sG5VV~ +zQ$)uegg-)nY>J@=`X=55yd*|FF1h2T?_^$o;jzHLsjhabCH_1ucm<4^|1)>iEPp)l +zr`fWM_jd&6^poG?mz%Xq#a40WKPFn(tA{`_dWfKHg0-O<52jv5Q93&va(_@T3CUj< +z0WY&G7%<~VqQ@)3_!$**I?b)=vizO@^WQRD>b+MudQ}{)4vvgomus3Di-i09fLtc+ +z-@6;hAFAv$JCwV0#Qwkzw4gup&uc9{IQXP*mmZ3|aHIBk1EhHL{5PG-IM}|iqFq*# +zTzie!)o?eYrcWJV8pynY)qg1cN-hb-i$7d|Xg&|At@X!`uGqu$VvXYj(63TzYiT*e +zzW?uzBT%#jm7P0PLD3XAQq9F4uLwUsBh?d!2y?u4^5CNAf!qRUU$VQVyK#iF_TP~B +z#onH-o7tzcO6Q^v7GcM`(gz_*e5VOL8@m5}KSnn=rP`yTs7AlY-G5#76XN2TW%<;| +zGoyMDsL9-cF4Xpe-W5T+Uth$S-ql98cUnVq>Fu!0m}q&99V|Vo@OB-Jqh;JGF3lTl +zvmeBJsI*#7V2pY%RTS&g!XUO!Vv>U!_Gxc&XQxd)6Vl|xvPV*NZcZ|!>8=+@_J^zd +z9wjwHLS)!-9u#h@0DrRg5ix4vy>8NYpgnyo-c>t$;4lh^dK4cFHi5Db5SD*&_r}nE +zb@WLfb~v1}6lu6hAY|QXKAGFH_e#O)?iYYb%+{9Gb~n3Lw910jG8+H1C+Si~eJNkt +zy{Cn@nO?tKx*Jc2CypM;3C5Z)4>l}6nPzpM+EoUGx1|H;+JBOrINg(y!@Qtu+=tKH +zmuO}7joP%Ttf`54n~u$-R@L^0b?dmZ8^S3-NKF-!Xh*$WxmaR*5M;R&u#Fl$H2hD{ +zwIM{B+CWT%(V?I^>XtmqmeR>KDCIFNHWpyaa|`t$5m}*g +zK>&|Ey{Z|rxBD#9Abq+Up(9cq2#O?&gp~B8mu@ +zAhNB4+O5c#8OB5Wub0v^LYpy+YvD>1~DmyCA^7g%D +zFAAxaq1oCJ*#e;kqPUvQB?{$)UCWtFos&tE6ngi`#J(zz5BT3#jp;8k^KwF}Mx&M%q=hNf(NuFoFey1_>&L +zNQU+thDZTr0|Wso1Q5U)0W`6LNiYv(7JIIJGrI(W1klCz!q3{|#X5KUyo}{Ke$8*4 +z&z8gsBnxa}BkX@Nu7*?GShxRsy(nf4M1NX$7U7f*_v;_v8QmAi1RA=90>Q6mk>K0H +z#oV_k5pRI;GVQ3j<_i&6FF@?OiYbr0)nN~B0s-eZp5E>oV_kZd6{)+kM5N6N?Ld3H +z)wfb0M94{)uRKzoHZUlqQQ{`;zEjVeTbOk-Mt}G& +z!k=6|Mjv480NzyfPdJIgH0@|a0zl$H>5Af0Dne8-zz4}2lHd!s3 +zxSf>)2GJi2L?K|q`h_O#om>P@(Vb=sW3vWx9xU|H8mxx +z6`(10w19d!;Gs`p2)_6Hl$RK`TEV!p)VPK! +zaxWY{)O03zer|)d_jRj(krvwkkSyN(Nh-WRmF#Qp>Jy!6>^RCj-Npj)j(_z>y@QU- +z@ZL#n!*Zsb&e&}8Rg@SJl5p|B7l(u1YJ(3tl-#D_t_yL08PNf15V2}X)#%GD2*o9PK3%fNCJ*_+= +z%xwkK;lE=s`le+~$?pnn34dok-|t4dj=d{~HrZq*Li94h5LRg+?=$WT*{8sh(pWO; +zx4S(^BW~+)O`Mt*c>{9;je)afv!lqC@}XAxj=mGhXt4NR2R;a7!SOWb$nSJCjeA_^ +z8`)x3k+zM~B(Pl+i3OFdw$_7b0uOA@8<-%=1$m2$wl7Ili8G~0T{II* +z)Mkvjg@Tuz5BIgMk9$ViI62{JqE>~?qz!0*3CZk477 +zouHyH@yBwvdw;FKYYH>yE~}|^QYs(;?8`w5)-WxH@SgoCFKDpSStZV*Oll+`gp&LNQU<5RG#b4BEJ^04G$AzyQk*SVS?I1uWrC}u~MGWb!KEd3HOLs +z1vIdCmj9fXy&snurXzgb$5TcD!>K45?spJD`)LcPDS6?a;V`RNpkjf`EoBh??RTlK +zh#hCXlH;295z7xk!RW`#2Q)Ldjem0>WbPylh7u9DMA``T!d19RnS;bk#uauLlP^WH +zKr2J}sUw8JM}#iNO|VH +zR4xW^b$*AM-mS+(-PO-kX_m@{K<)}Cx}IT^la`VVIVrkTV5%o6Ay#2R8Gm&i@n79z +zMbt?rY=qLv@A2$}MWsJj7#)`LB6Fc>iB5mgaDRksov6|o?YhvA=5QPUwqt8gPxXm@ +zR_v)ai(Uik*bb`DQSA(I#xt|$#v~V3ot*8sDww1rNSiodn=fVmod!NYECS3>?B`l4 +zC9_gt>z615h!;`Y)47ErO@DLZp?MNZ_5Ff_svYCM@Z87f#e9llSG=s7vqkKA{x@24 +zAVHwi9Lk{D(6=eEA%KS{p?QMBW;vLeF4d(a+;9T5J>N-hUruMt&Th(NZX; +zg;H3BzJdyILh*xPTJA$FnWa>4#A|v2o(ajRc^g2K@{+AKE$`~-#P(nh!v**PJe7C3 +z5_&Y5eczza5Qm@ocj@WYs%2J6H1Isdq_%)2l)_`knW#8;tT=){0p6uFqfI#J^T298IsQ +z>I$oO)gY?L%QiQOULxj$9;TxW2Y=Wjja17-rN}yw#1v{vNuhG1XVYTnq3NV0hn?>i +zO}u9T7vs~SBvUb7|BD&zbC>M(@O_=3VZdYIA-K57Xq{W +zgEb$eyZnkRT?lF3gg`Yn;`GqP_vCc{mR^{i!AHxKO^qcyAr#HKLym1G=Weg&=3^|K +z>rS<d`@~O{(9z`ZiTM?@dR*2yj!7Q3+L9Y=oJz%W-V7M76z? +z?R_NTw?*Y%k(hWGe-P+8Xyu18Tkzz-&8A65YgT>Eu^NU)aO;dKf+zvIh^!8ssUq`v +zG>S>q@Zu)SaYQJydZt+TmR>5$RB*L2L)*Iql1J@C5{SP9k06@-k=3HjQN5y=Z!6>M +z#MH3t|9_5JmWH8lzjUZp;8L7rnh;$az|w6Ql{t|{YprFA_j_1tr^r=ZC0*U&Vlo(SJ1cWrAPSUH^wc|vrE)kR +z?cXa(1TcEN!ANk0%zj|K^A`j$w&^r-JHUqN9e==jAPlP4|2}*i-^~N4H&WVuhuT%A +z4NMZ&L +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 +z*%_E<-(3CfoWq-JeM2LC3qAF09L +zg{J|A1^Xaq=epb>O)~!z=%%-`yOXl3%PjAvF)FBpL%PQ8wTn<)p=DR)-dB_0il)sdbjsGpBc)#n@$o$#SnZI(Vvx(l2?*rJ%SpcdNjjQlrd=@Beh+$_|6F +z8@Tz~KUJ!eZqW~?AV$XyLUVIO%YSv(JkC{tT#X%aYc-b9(YI*AI#| +z@MwIoBm5eVk#W`oKz6qodHvmIS>8*z%=9UmJ|M-{*v1k`kwThPN|G=ssmY6 +zdlK$V$NK_C^k`Q0!lno85{Vus_qos^`Lp0zfxv(h17en6C*u(a08qe@1AkqVn0)b} +z_Xt97qce$VS@%WK!5l~<&G+JOPggXM5_*(NH(7lp8{Jpo+{LndeBBFeK|Qn +z9UGVTdha8uxo_%X9MFciV&krC4;DpRftzbyI!nYxd{sPcCixi!0qFJckW+X*!~7dN +z#qLCJS{#C^lb%_p>cE3Gff5>B7@+UaH2G|x!e5o5P%R%B6vqemlYj0D+)w-FeI>(* +z0Z%64=+!6EARd(`dHMu&ZEii^ztJXgr@}do_>hq1`p{HS5F~=?zcN*LufsIudMT_~!-%1k2Z3CDO0m;OS8?o*&w56(0V%Y*=| +z&6KdgoIuBBWarFK(|-&5b6I2rue5K;BNY@TV|7>-A62B=7|2=;u_KME9kfRGO7R!8 +zk!=M>VYHe3w$voveBEaD`6q1-Cq1zx1)QL{#hAICUqkOdQrii@<%vvEX91z>D9Io3 +z3%c+cI9lYE^hh^AiBrSmRh~J=uQ6BwGHyUW)6{>GMKCjH$A4cY;o4ZsWe1;L-{B`) +zTrX5nB)#PSQGLx(y_osNPd*Bwj48Brqm=*ep +z7?tQkxb##(ZQ^)93Wc5!#dewm80kEjmklRcG6?}+1z!$p^Jk2Oe_JMX8L;k`RGGFE7$_Hv%ZKPxRiZAvez?hfXPXwNwRuE|3@d +zdj-&lv42j6S5hNDUC;L)_JH0QA@L78%wofg%ep^s#(#TzP%4Z8>ZGBHIXyyhuf+Yi +zM3xX3mKikVT7r?YF(oh~1_>&LNQU; +zK`=2e4F(BdhDZTr0|WvA1povfY+(!i_~~rbp^C#yt5i7iN(PLf-J39FdDrAamk=e% +S1PFt)bqU_LN^6M%0tf&&^grJK + +delta 2776 +zcmV;}3Mci@7WEY(FoFv40s#Xsf(o|=2`Yw2hW8Bt2LYgh3aA8v3ZyWC3ZRi9JAWcz +zf~^6)AbAJ`;A0$m*A;RX;&9P-0dy)<6ic +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 +zmqUCjZU-u2d&A^l!nNL>Tjtf +zJkzy;9b{e}lVX%pscpa}CD|T&4_lG8*YS?dpSkncWepv2e&;2HS{*4*!|BT*m2A?f +zyjVW1HUQ(_M3rpp5R3I>q#~s-)|9FwJ%rOTwxl2FMp&4tsCFbTVXO2tVoG)vz`fmN +z)R<$E#pT35KAYbTw|@bcx$u3h{1odhts-|_3YJ4Ayla(y0OOI4`C7W%+BMtMu9DR +zLN@8T_7`usWF{4ezK+d|fuB@k4tw^8R!x#XZ^&}mz29aP_raW@!^P-{vLMa|@PF|S +zhs(}tr|&S{zJE{7YU?ak8oNr`=W`fgsS4~&^0h#~(VJRT +zNzH~lq@$yZ(W^@?`c_n0MVf0+f4^>KZJcWBRpBJ1NoZM-&i&-9Xoe! +zmv9edP|3x)fnY^m)?k@H>w04+$h`_@j98wtxMb_Y2{QVtv2Vhjf7qLrz^P +zzfCU6R%@{mG9Yn6kX(&PQbCcl4jzWK;LKYtF(oh~1_>&LNQUg +zWkVjqlI}v+>hWZNZUj*Kq67$ty|isR%^-9F0tf)%!(&YV + +delta 2792 +zcmV_>MN{htcbO{qC=J&uM?~NGanbgE-J*^bff< +zUL85U4k$LojjclJ{^#hk69ix0P=6p3{W=cW@;J}jXSEPY*K)tO5KXO= +zDp34gOZf$JI>T(od;fQLTl{d}D?LMSYm|boNM-&DpOI*gNxdhZ}bdPsP-V3jIx6 +zS#YC|Y%#Qcz2$cGZXgSwA( +zp^gh0BF&;oP;t5r`vDq}MSpv(oTT?XWo$BF3HTfG#Cm-G$X#COyar;r`l_yWRsuyp +zjrLZxM$trJR(FC|;&F1_fvv +zlT~jFp~Eovc#$rZ%#NJT+P;Ef80Oaf9R{ +zOpW_P@)6@t2VYKWW}3x8$#CYa;%anc&G^~Ur2V-rd#L>n7J)~*jORUD0N#m1LiDFl +zB&?Q*64N(fqjrGx*oF|qZ|R<>da*~kLSkj=XRG<@r3LG9Z`*3FTNX%lmC5B4T~(J> +zFv-n}iKXB3?7ZeYuYc&9GS74XBxDCRdBBF|yIE$fUxbe0K(Obu;3tumH)59PWVH*+ +zz+FmMZ^+~l+V!l1r0ZO}N2<6B_PMH&?VYqs=-LS%mX1X@sp)x{beSD;A)~7{YY~TU-k$-{C;hMVpNq;mVqC$gb{2Xtz#6@FT +z{VWSY?mme#0Q8+1Z2R$5&R0^uNckWNtn&l62raaz_Rv@fJ}H`6N6&t$-qmQZi`0V~WS +z!noU8xPAZ|(OodN!Y_ylW^%s>!I$n`^*O|{`Nql$VSjtzYgo;WtVGwfOGr;9?$|xB +zcs%xxE&&}izyxcgm)2gfVyWCAT`*z4tvURt&T~Yv;4D~@cMQ$sbGeT!7+;p0p5QBw +zMU{9IVf{eaHyT}&Q{kmv1!msC>y;CP;=8GdD!=>6MDu6%gpdLzN`zEs=jdV7SuMgm +zSef9I4u8xGa&@P0{hVpaizD*w9ysr@;s~)?UPY$$=xMStm%-8_cVQq(W*E)bMfTx~ +ztiPTUhk1JJr{K03MHZ?_uNADjS{qhZS>Plq2pIDcZSe +zpdVNTFx+6=J0GuhMj`dM%6hP&L +zNQUr;S>kF`=lUV`cN+L-;%3g5Ai66u6H%n +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 +zio`$Z>ZF=$DSVbD4xi;!NC!?`eIqFD35= +zQc_8A~;(#IIWCvOKf`P&2)Js($WT +z1z-??Ow@SVupz2ChgP2^Y=0o#*~e?*$0TAcDclj?IADVt8o^gntLJZBu=o +zlRp{i%IZDaPfUE-$LP%)Ua~T(Bdt0G=DGbexi&hghHP;^^f<}zRlnJ3GgV60GtOPk +zcib>&Ab~e+Z6==732Fcs!<-3gh>(3UIWk_h>u`vv-;vJvM|KhjTN9G*@n5_xbZnmMK +zlqp0G!V*18sL0-95F*8PQnOybN@p#c%kb*&^aa55JSKbSAd@qO=Ht +z422L +zEQT%EbQpx1)#*Gv)47 +z!L`_0ZJB+>X&W4?m4Xpk&GUR45OCpS-Ac@br?bG$K5WOH27l@cvz6M|^4ikdCcj~? +z@A&ePKz3o=>_;pjNUjLI{a*i~A2Cm`&wy=Wnk}HZ!)%{@qd4Cdgv0g1#?}yCqq93|4$^$^+_&k +zJa~z}zH{53ynke+y3lMnjQq2xp0Aelu-HmWXLFfn2Zo%7n87xsG^uG}b2CZ*u1s9Z +zdy6l*(2{|l#uH(Fg~MQyrygWvH^>pmCkM|1n_(Oa%d6l_c*y)EJe|rcT48Q$4-q9{ +zd;$`4t5f-+&fM5}8vs44&d`E*VzZ!Kx~GhFv9KFoMt^WQmMpIlho1dt%_BO(+`2Lp +zatNwfJ+p4vZMjP*m8(};v7%^J>Z;rDPy{%kK4LhfuLy_D30LYt***zyVP-NnNeD<@ +zGY~g=yXsBrn_X=!tmk!)LM6?HS%;t(HMJ%Q_8-enJgcrn_sO6=M%6GmrDCuouDW~1p-LREA&0BHSeEKQnj3ME3_){Ru4kMWs9nx6eJ4`1vHrOoBO6WNZGR{?^ +zEfRoM=%swpDejYTaEFI0cJp^Ht@L%c+@lQy^nXs75zrK4$%sP!y)m<*xW9H%@fLjRzS +zFoFd+1_>&LNQUeJ7Gnm8p68cPkyaMp;ydm +z;5`lZ-8TBOTClg{8b2gEfR$AC$(l*<$HmjXkv +zk2XU*+fTwXqcsMYaZ3q-p;PK((PrSVUOgD!_f+ +zxEwhbf?;sTV9;{2qj=e+thVm4&q+rqRZvI3E$OXDELB<0qt8X4|WJgR& +zA;mly>0fgus?*HuYZkDa8&PzLYY`{)VN|6AD5DVz9hPB0H|cXeKd~iK$$60*1@6KW +z6781`Wt^~gq(V@LHV`!^8UF^$=qzP9Z~St~brXD2cffcLQ)%SW+3k{*m46rbsM@(z +zVmdvB5fH3}m=m^afNN7hu#6Tv465|&pl|0QVo8cOgDzsf96G6OAIc1>JH)_kiJ%CKS4>>XVItK +z0df&%Ir0A#%dzm{a-{xL-+y}K1k-jf%0*=Wi*tj00J0}10?%6)ypUZJ9ksJA^uCwA +zU5oo_6hE*n7H%zwo}j{S>kkC#VGo5P%uaH-y; +z9xeJ!zLp4E-us08h0F``IjL9Q%D-E<6S*Z@f^7;~n+PU-j1vc-W`Anul8%C=xSb5u +z7r0>5j^Of_V(=`5Jkb~jw{)rQzSz?lmA1B5fIF^AldA0&srn(q6$tx(<>kN35n4cE +z?Y`#QJY`_$YGKyHz0lTxyY@^*MkI>@K_P<$huaWnmtRl$s=QCNW>Q)%O~1b7x8OZa>s!LI}xafM3IiiEeV1X6v0v^m&_bRB7S;B9{kMdqBpB#o;WHIcK +zXZti{8yqh{hTB(4Q&s{4ZNd91F&>cQH%{n1i;7|BuXezD%73Buu9D%H$?>u=EQI*3@^(1e84N*6QZYbr+~cRYKueVXW#rd?}8~s6XVg0MS^}l +z>j!&zJjkZ0C0Bijh>6a2w@ocN@A@brXHXZn+AJ3kASY`zaQm-BZ>L{LVlV^$8R&lo +z$7k~Gs8nAN#(&H;;d!1sK~d&LNQUXqJJ21=NG&!mOU3kJ +zFflM8FbM_)D-Ht!8U+9Z6sSLOY_+{}*ZArxG2W!WZkmtKW&{Xzdz-Lrn>2+20tf&? +Cp*q$8 + +diff --git a/src/tests/pkinit-certs/user.pem b/src/tests/pkinit-certs/user.pem +index 182ea599ac..7493de52c1 100644 +--- a/src/tests/pkinit-certs/user.pem ++++ b/src/tests/pkinit-certs/user.pem +@@ -3,26 +3,26 @@ MIIE0zCCA7ugAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBpzELMAkGA1UEBhMCVVMx + FjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJyaWRnZTEMMAoG + A1UECgwDTUlUMSkwJwYDVQQLDCBJbnNlY3VyZSBQS0lOSVQgS2VyYmVyb3MgdGVz + dCBDQTEzMDEGA1UEAwwqcGtpbml0IHRlc3Qgc3VpdGUgQ0E7IGRvIG5vdCB1c2Ug +-b3RoZXJ3aXNlMB4XDTIxMTAwODIxMTEzMFoXDTMyMDkyMDIxMTEzMFowSjELMAkG ++b3RoZXJ3aXNlMB4XDTI0MDIxNTA0NTkwN1oXDTM1MDEyODA0NTkwN1owSjELMAkG + A1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxFDASBgNVBAoMC0tSQlRF + U1QuQ09NMQ0wCwYDVQQDDAR1c2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +-CgKCAQEAz6VXmJpVq2zTIEU3gUF7pui+Wg17d3QX2oy6EqqUQK/pwWtrvmBIaYcn +-Pq0ZMrzMhNTuyeLjb1rNNkL0hCdS3/aVbx1bOlkPVPlW3UNi9gWpXOOE1/N4QMrz +-yKAQ1/Npf9xjY/vpqsmvRx7AZpq7Nq7HyF5hbUKMHFaaTqRarhoP7mOCByG8F44Y +-QTY2RXcw9te63x+77c3O64gbtnSKXBC/4pS9DxBBv1ULB2wOH8RGxDiWgL0/iO25 +-YImKQgTvwbENw4ygLV+0m1b+YEJLaIIeKleunYEMMkzIfFmMemXRWgNHuShYa0Pe +-yiwTBSRdW9Yi4qzjfaHZ1dD67wdoGwIDAQABo4IBZDCCAWAwHQYDVR0OBBYEFPQX +-pfvVBF+0OJJ41JjduSzecrQjMIHUBgNVHSMEgcwwgcmAFPQXpfvVBF+0OJJ41Jjd +-uSzecrQjoYGtpIGqMIGnMQswCQYDVQQGEwJVUzEWMBQGA1UECAwNTWFzc2FjaHVz ++CgKCAQEAm/1JtzZBJsdadmOTnkl94508ZSyYo5xP83sLT/SY5Cri1QKaFrue2kGg ++gl1QEOExBrIbdAeu5BftqiC07HyGgugtRo0qDHMRnQ4tsNExzYz69MOkFE4hMYjU ++o+9C22GVLihyoq+oELN7ro30u5/MCO7rULIp0HekLKQ+uANVVJx+xnW3bMJsrRIX ++Zx9kB0jIIugYt5D3n80vdIjHQJf2BTjsBWYGRJD4sTElGFtRIiD6m4puonRdUgtH ++UHZ7OCKTY5sU0PSGxFRLi/ykqcgPPQddHYCd5MRJj5q2NvPN6UYDbMfzqni0uDQ9 ++qdDjHj6CmRCHNKvkKaLdBfhdqFKuZQIDAQABo4IBZDCCAWAwHQYDVR0OBBYEFJI/ +++nOV5fnNVxn2GkjkYbZ5D6mqMIHUBgNVHSMEgcwwgcmAFJI/+nOV5fnNVxn2Gkjk ++YbZ5D6mqoYGtpIGqMIGnMQswCQYDVQQGEwJVUzEWMBQGA1UECAwNTWFzc2FjaHVz + ZXR0czESMBAGA1UEBwwJQ2FtYnJpZGdlMQwwCgYDVQQKDANNSVQxKTAnBgNVBAsM + IEluc2VjdXJlIFBLSU5JVCBLZXJiZXJvcyB0ZXN0IENBMTMwMQYDVQQDDCpwa2lu + aXQgdGVzdCBzdWl0ZSBDQTsgZG8gbm90IHVzZSBvdGhlcndpc2WCAQEwCwYDVR0P + BAQDAgPoMAwGA1UdEwEB/wQCMAAwOQYDVR0RBDIwMKAuBgYrBgEFAgKgJDAioA0b + C0tSQlRFU1QuQ09NoREwD6ADAgEBoQgwBhsEdXNlcjASBgNVHSUECzAJBgcrBgEF +-AgMEMA0GCSqGSIb3DQEBCwUAA4IBAQAOBeCDK6Eg6Cu8TZ7xeAw2AbTpaW04nNSV +-Fmm0aIskMgLl2a5KEmalG7rnArRXv5IZVYFjJ6X0MzjOx+BgaGUCvN8jz1fuO3Hp +-iGhxPDzKjFMWJeY/z5bQRueSI6RCC8DzH8iPdlPUQ8ZhnukhY1Vt47wqraf197uT +-0XP21qQr1uRY+ZcLSBKZuKe9ZP3ijh57MOLvYDdAFxVp77JLznpk+oU18ujAtYgZ +-7naIGYtSQRkIi970jk82hSpc9B/KN8UcDuo+DQHWPQaDf39s30qoxooZBoue5ipp +-LQHuVaX5Hoi83cWbsVluce/JsW8GfbuC8+8CosAmzJly183f8++9 ++AgMEMA0GCSqGSIb3DQEBCwUAA4IBAQBRWsxPb9miF9xf8rEIfVko0qBy8doEJsPE ++IVD9Jz/Ml/TBZRLbi1b94l15Fto/Z6XKf8jrnBs4krf6tU2D5PUZXZYZ6tr/2kkY ++IpmoOkEoQX8gtcZfaq2OJzsKHnAJT159EVydyYahHU66i4aNvho74oAafrVTyk8B ++PHCHFs0MUct8DoNwrbnfH0cjqEdVOmjjvBN0yA+RxOa543XnQqkSmCuIJKoD6pUa ++07rE372iERgIjDnzCogiEo9cCBBqDfgsbr0ah1QbWJTJvnsFuxT43tBNurRjNPoX ++Jj6xAzhQLCuvqtKtWlAUOHut18YbVGXVT+3tm7+C6iA44JvMl9m1 + -----END CERTIFICATE----- +diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py +index 4435746429..91d4630a0a 100755 +--- a/src/tests/t_pkinit.py ++++ b/src/tests/t_pkinit.py +@@ -7,8 +7,10 @@ if not pkinit_enabled: + + # Construct a krb5.conf fragment configuring pkinit. + user_pem = os.path.join(pkinit_certs, 'user.pem') ++ecuser_pem = os.path.join(pkinit_certs, 'ecuser.pem') + privkey_pem = os.path.join(pkinit_certs, 'privkey.pem') + privkey_enc_pem = os.path.join(pkinit_certs, 'privkey-enc.pem') ++privkey_ec_pem = os.path.join(pkinit_certs, 'eckey.pem') + user_p12 = os.path.join(pkinit_certs, 'user.p12') + user_enc_p12 = os.path.join(pkinit_certs, 'user-enc.p12') + user_upn_p12 = os.path.join(pkinit_certs, 'user-upn.p12') +@@ -42,6 +44,7 @@ alias_kdc_conf = {'realms': {'$realm': { + + file_identity = 'FILE:%s,%s' % (user_pem, privkey_pem) + file_enc_identity = 'FILE:%s,%s' % (user_pem, privkey_enc_pem) ++ec_identity = 'FILE:%s,%s' % (ecuser_pem, privkey_ec_pem) + dir_identity = 'DIR:%s' % path + dir_enc_identity = 'DIR:%s' % path_enc + dir_file_identity = 'FILE:%s,%s' % (os.path.join(path, 'user.crt'), +@@ -177,6 +180,11 @@ for g in ('4096', 'P-256', 'P-384', 'P-521'): + realm.pkinit(realm.user_princ, expected_trace=('PKINIT using ' + g,), + env=group_env) + ++# Test with an EC client cert. ++mark('EC client cert') ++realm.kinit(realm.user_princ, ++ flags=['-X', 'X509_user_identity=%s' % ec_identity]) ++ + # Try using multiple configured pkinit_identities, to make sure we + # fall back to the second one when the first one cannot be read. + id_conf = {'realms': {'$realm': {'pkinit_identities': [file_identity + 'X', +@@ -446,4 +454,16 @@ realm.run(['./responder', '-X', p11_attr, + realm.klist(realm.user_princ) + realm.run([kvno, realm.host_princ]) + ++mark('PKCS11 identity, EC client cert') ++shutil.rmtree(softhsm2_tokens) ++os.mkdir(softhsm2_tokens) ++realm.run(tool_cmd + ['--init-token', '--label', 'user', ++ '--so-pin', 'sopin', '--init-pin', '--pin', 'userpin']) ++realm.run(tool_cmd + ['-w', ecuser_pem, '-y', 'cert']) ++realm.run(tool_cmd + ['-w', privkey_ec_pem, '-y', 'privkey', ++ '-l', '--pin', 'userpin']) ++realm.kinit(realm.user_princ, flags=['-X', p11_attr], password='userpin') ++realm.klist(realm.user_princ) ++realm.run([kvno, realm.host_princ]) ++ + success('PKINIT tests') +-- +2.47.1 + diff --git a/SOURCES/0033-Improve-PKCS11-error-reporting-in-PKINIT.patch b/SOURCES/0033-Improve-PKCS11-error-reporting-in-PKINIT.patch new file mode 100644 index 0000000..b6750dd --- /dev/null +++ b/SOURCES/0033-Improve-PKCS11-error-reporting-in-PKINIT.patch @@ -0,0 +1,599 @@ +From 6c4c659859bb6fde1b0631dea2ac03070fbe1a26 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Fri, 23 Feb 2024 13:51:26 -0500 +Subject: [PATCH] Improve PKCS11 error reporting in PKINIT + +Create a helper p11err() to set extended error message for failed +PKCS11 operations, and use it instead of pkiDebug() and pkcs11error(). + +ticket: 9113 (new) +(cherry picked from commit 98afb314d13939cbee19c69885dcb655db8460da) +--- + .../preauth/pkinit/pkinit_crypto_openssl.c | 262 ++++++++++-------- + src/plugins/preauth/pkinit/pkinit_trace.h | 9 - + 2 files changed, 142 insertions(+), 129 deletions(-) + +diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +index b33b696954..87eb677c49 100644 +--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c ++++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +@@ -161,9 +161,11 @@ static krb5_error_code pkinit_create_sequence_of_principal_identifiers + int type, krb5_pa_data ***e_data_out); + + #ifndef WITHOUT_PKCS11 +-static krb5_error_code pkinit_find_private_key +-(pkinit_identity_crypto_context, CK_ATTRIBUTE_TYPE usage, +- CK_OBJECT_HANDLE *objp); ++static krb5_error_code ++pkinit_find_private_key(krb5_context context, ++ pkinit_identity_crypto_context id_cryptoctx, ++ CK_ATTRIBUTE_TYPE usage, ++ CK_OBJECT_HANDLE *objp); + static krb5_error_code pkinit_login + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + CK_TOKEN_INFO *tip, const char *password); +@@ -180,6 +182,8 @@ static krb5_error_code pkinit_sign_data_pkcs11 + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **sig, unsigned int *sig_len); ++ ++static krb5_error_code p11err(krb5_context context, CK_RV rv, const char *op); + #endif /* WITHOUT_PKCS11 */ + + static krb5_error_code pkinit_sign_data_fs +@@ -197,9 +201,6 @@ create_krb5_invalidCertificates(krb5_context context, + static krb5_error_code + create_identifiers_from_stack(STACK_OF(X509) *sk, + krb5_external_principal_identifier *** ids); +-static const char * +-pkcs11err(int err); +- + + #if OPENSSL_VERSION_NUMBER < 0x10100000L + +@@ -944,8 +945,9 @@ cleanup: + + #endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + ++#ifndef WITHOUT_PKC11 + static struct pkcs11_errstrings { +- short code; ++ CK_RV code; + char *text; + } pkcs11_errstrings[] = { + { 0x0, "ok" }, +@@ -1035,6 +1037,7 @@ static struct pkcs11_errstrings { + { 0x200, "function rejected" }, + { -1, NULL } + }; ++#endif + + MAKE_INIT_FUNCTION(pkinit_openssl_init); + +@@ -1563,6 +1566,8 @@ pkinit_fini_pkcs11(pkinit_identity_crypto_context ctx) + free(ctx->token_label); + free(ctx->cert_id); + free(ctx->cert_label); ++ ctx->p11_module_name = ctx->token_label = ctx->cert_label = NULL; ++ ctx->cert_id = NULL; + #endif + } + +@@ -3344,48 +3349,53 @@ pkinit_pkcs7type2oid(pkinit_plg_crypto_context cryptoctx, int pkcs7_type) + } + + #ifndef WITHOUT_PKCS11 +-static struct plugin_file_handle * ++static krb5_error_code + load_pkcs11_module(krb5_context context, const char *modname, +- CK_FUNCTION_LIST_PTR_PTR p11p) ++ struct plugin_file_handle **handle_out, ++ CK_FUNCTION_LIST_PTR_PTR p11_out) + { + struct plugin_file_handle *handle = NULL; +- CK_RV (*getflist)(CK_FUNCTION_LIST_PTR_PTR); ++ CK_RV rv, (*getflist)(CK_FUNCTION_LIST_PTR_PTR); + struct errinfo einfo = EMPTY_ERRINFO; +- const char *errmsg = NULL; ++ const char *errmsg = NULL, *failure; + void (*sym)(void); + long err; +- CK_RV rv; + + TRACE_PKINIT_PKCS11_OPEN(context, modname); + err = krb5int_open_plugin(modname, &handle, &einfo); + if (err) { +- errmsg = k5_get_error(&einfo, err); +- TRACE_PKINIT_PKCS11_OPEN_FAILED(context, errmsg); ++ failure = _("Cannot load PKCS11 module"); + goto error; + } + + err = krb5int_get_plugin_func(handle, "C_GetFunctionList", &sym, &einfo); + if (err) { +- errmsg = k5_get_error(&einfo, err); +- TRACE_PKINIT_PKCS11_GETSYM_FAILED(context, errmsg); ++ failure = _("Cannot find C_GetFunctionList in PKCS11 module"); + goto error; + } + + getflist = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR))sym; +- rv = (*getflist)(p11p); ++ rv = (*getflist)(p11_out); + if (rv != CKR_OK) { +- TRACE_PKINIT_PKCS11_GETFLIST_FAILED(context, pkcs11err(rv)); ++ failure = _("Cannot retrieve function list in PKCS11 module"); + goto error; + } + +- return handle; ++ *handle_out = handle; ++ return 0; + + error: +- k5_free_error(&einfo, errmsg); ++ if (err) { ++ errmsg = k5_get_error(&einfo, err); ++ k5_setmsg(context, err, _("%s: %s"), failure, errmsg); ++ } else { ++ err = KRB5KDC_ERR_PREAUTH_FAILED; ++ k5_setmsg(context, err, "%s", failure); ++ } + k5_clear_error(&einfo); + if (handle != NULL) + krb5int_close_plugin(handle); +- return NULL; ++ return err; + } + + static krb5_error_code +@@ -3393,12 +3403,13 @@ pkinit_login(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + CK_TOKEN_INFO *tip, const char *password) + { ++ krb5_error_code ret = 0; ++ CK_RV rv; + krb5_data rdat; + char *prompt; + const char *warning; + krb5_prompt kprompt; + krb5_prompt_type prompt_type; +- int r = 0; + + if (tip->flags & CKF_PROTECTED_AUTHENTICATION_PATH) { + rdat.data = NULL; +@@ -3407,7 +3418,7 @@ pkinit_login(krb5_context context, + rdat.data = strdup(password); + rdat.length = strlen(password); + } else if (id_cryptoctx->prompter == NULL) { +- r = KRB5_LIBOS_CANTREADPWD; ++ ret = KRB5_LIBOS_CANTREADPWD; + rdat.data = NULL; + } else { + if (tip->flags & CKF_USER_PIN_LOCKED) +@@ -3431,31 +3442,28 @@ pkinit_login(krb5_context context, + + /* PROMPTER_INVOCATION */ + k5int_set_prompt_types(context, &prompt_type); +- r = (*id_cryptoctx->prompter)(context, id_cryptoctx->prompter_data, +- NULL, NULL, 1, &kprompt); ++ ret = (*id_cryptoctx->prompter)(context, id_cryptoctx->prompter_data, ++ NULL, NULL, 1, &kprompt); + k5int_set_prompt_types(context, 0); + free(prompt); + } + +- if (r == 0) { +- r = id_cryptoctx->p11->C_Login(id_cryptoctx->session, CKU_USER, +- (u_char *) rdat.data, rdat.length); +- +- if (r != CKR_OK) { +- TRACE_PKINIT_PKCS11_LOGIN_FAILED(context, pkcs11err(r)); +- r = KRB5KDC_ERR_PREAUTH_FAILED; +- } ++ if (!ret) { ++ rv = id_cryptoctx->p11->C_Login(id_cryptoctx->session, CKU_USER, ++ (uint8_t *)rdat.data, rdat.length); ++ if (rv != CKR_OK) ++ ret = p11err(context, rv, "C_Login"); + } + free(rdat.data); + +- return r; ++ return ret; + } + + static krb5_error_code + pkinit_open_session(krb5_context context, + pkinit_identity_crypto_context cctx) + { +- CK_ULONG i, pret; ++ CK_ULONG i, rv; + unsigned char *cp; + size_t label_len; + CK_ULONG count = 0; +@@ -3469,30 +3477,35 @@ pkinit_open_session(krb5_context context, + return 0; /* session already open */ + + /* Load module */ +- cctx->p11_module = load_pkcs11_module(context, cctx->p11_module_name, +- &cctx->p11); +- if (cctx->p11_module == NULL) +- return KRB5KDC_ERR_PREAUTH_FAILED; ++ ret = load_pkcs11_module(context, cctx->p11_module_name, &cctx->p11_module, ++ &cctx->p11); ++ if (ret) ++ goto cleanup; + + /* Init */ +- pret = cctx->p11->C_Initialize(NULL); +- if (pret != CKR_OK) { +- pkiDebug("C_Initialize: %s\n", pkcs11err(pret)); +- return KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = cctx->p11->C_Initialize(NULL); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_Initialize"); ++ goto cleanup; + } + + /* Get the list of available slots */ +- if (cctx->p11->C_GetSlotList(TRUE, NULL, &count) != CKR_OK) +- return KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = cctx->p11->C_GetSlotList(TRUE, NULL, &count); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_GetSlotList"); ++ goto cleanup; ++ } + if (count == 0) { + TRACE_PKINIT_PKCS11_NO_TOKEN(context); +- return KRB5KDC_ERR_PREAUTH_FAILED; ++ ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; + } +- slotlist = calloc(count, sizeof(CK_SLOT_ID)); ++ slotlist = k5calloc(count, sizeof(CK_SLOT_ID), &ret); + if (slotlist == NULL) +- return ENOMEM; +- if (cctx->p11->C_GetSlotList(TRUE, slotlist, &count) != CKR_OK) { +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ goto cleanup; ++ rv = cctx->p11->C_GetSlotList(TRUE, slotlist, &count); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_GetSlotList"); + goto cleanup; + } + +@@ -3503,19 +3516,17 @@ pkinit_open_session(krb5_context context, + continue; + + /* Open session */ +- pret = cctx->p11->C_OpenSession(slotlist[i], CKF_SERIAL_SESSION, +- NULL, NULL, &cctx->session); +- if (pret != CKR_OK) { +- pkiDebug("C_OpenSession: %s\n", pkcs11err(pret)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = cctx->p11->C_OpenSession(slotlist[i], CKF_SERIAL_SESSION, ++ NULL, NULL, &cctx->session); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_OpenSession"); + goto cleanup; + } + + /* Get token info */ +- pret = cctx->p11->C_GetTokenInfo(slotlist[i], &tinfo); +- if (pret != CKR_OK) { +- pkiDebug("C_GetTokenInfo: %s\n", pkcs11err(pret)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = cctx->p11->C_GetTokenInfo(slotlist[i], &tinfo); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_GetTokenInfo"); + goto cleanup; + } + +@@ -3577,6 +3588,10 @@ pkinit_open_session(krb5_context context, + + ret = 0; + cleanup: ++ /* On error, finalize the PKCS11 fields to ensure that we don't mistakenly ++ * short-circuit with success on the next call. */ ++ if (ret) ++ pkinit_fini_pkcs11(cctx); + free(slotlist); + free(p11name); + return ret; +@@ -3598,16 +3613,17 @@ cleanup: + * If there are more than one, we just take the first one. + */ + +-krb5_error_code +-pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, ++static krb5_error_code ++pkinit_find_private_key(krb5_context context, ++ pkinit_identity_crypto_context id_cryptoctx, + CK_ATTRIBUTE_TYPE usage, + CK_OBJECT_HANDLE *objp) + { + CK_OBJECT_CLASS cls; + CK_ATTRIBUTE attrs[4]; + CK_ULONG count; ++ CK_RV rv; + unsigned int nattrs = 0; +- int r; + #ifdef PKINIT_USE_KEY_USAGE + CK_BBOOL true_false; + #endif +@@ -3637,18 +3653,21 @@ pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, + attrs[nattrs].ulValueLen = id_cryptoctx->cert_id_len; + nattrs++; + +- r = id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, attrs, nattrs); +- if (r != CKR_OK) { +- pkiDebug("krb5_pkinit_sign_data: C_FindObjectsInit: %s\n", +- pkcs11err(r)); +- return KRB5KDC_ERR_PREAUTH_FAILED; +- } ++ rv = id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, attrs, ++ nattrs); ++ if (rv != CKR_OK) ++ return p11err(context, rv, _("C_FindObjectsInit")); + +- r = id_cryptoctx->p11->C_FindObjects(id_cryptoctx->session, objp, 1, &count); ++ rv = id_cryptoctx->p11->C_FindObjects(id_cryptoctx->session, objp, 1, ++ &count); + id_cryptoctx->p11->C_FindObjectsFinal(id_cryptoctx->session); +- pkiDebug("found %d private keys (%s)\n", (int)count, pkcs11err(r)); +- if (r != CKR_OK || count < 1) ++ if (rv != CKR_OK) ++ return p11err(context, rv, _("C_FindObjects")); ++ if (count < 1) { ++ k5_setmsg(context, KRB5KDC_ERR_PREAUTH_FAILED, ++ _("Found no private keys in PKCS11 token")); + return KRB5KDC_ERR_PREAUTH_FAILED; ++ } + return 0; + } + #endif +@@ -3796,34 +3815,32 @@ pkinit_sign_data_pkcs11(krb5_context context, + CK_FUNCTION_LIST_PTR p11; + CK_ATTRIBUTE attr; + CK_KEY_TYPE keytype; ++ CK_RV rv; + EVP_MD_CTX *ctx; + const EVP_MD *md = EVP_sha256(); + unsigned int mdlen; + uint8_t mdbuf[EVP_MAX_MD_SIZE], *dinfo = NULL, *sigbuf = NULL, *input; + size_t dinfo_len, input_len; +- int r; + + *sig = NULL; + *sig_len = 0; + +- if (pkinit_open_session(context, id_cryptoctx)) { +- pkiDebug("can't open pkcs11 session\n"); +- return KRB5KDC_ERR_PREAUTH_FAILED; +- } ++ ret = pkinit_open_session(context, id_cryptoctx); ++ if (ret) ++ return ret; + p11 = id_cryptoctx->p11; + session = id_cryptoctx->session; + +- ret = pkinit_find_private_key(id_cryptoctx, CKA_SIGN, &obj); ++ ret = pkinit_find_private_key(context, id_cryptoctx, CKA_SIGN, &obj); + if (ret) + return ret; + + attr.type = CKA_KEY_TYPE; + attr.pValue = &keytype; + attr.ulValueLen = sizeof(keytype); +- r = p11->C_GetAttributeValue(session, obj, &attr, 1); +- if (r) { +- pkiDebug("C_GetAttributeValue: %s\n", pkcs11err(r)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = p11->C_GetAttributeValue(session, obj, &attr, 1); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_GetAttributeValue"); + goto cleanup; + } + +@@ -3865,10 +3882,9 @@ pkinit_sign_data_pkcs11(krb5_context context, + mech.pParameter = NULL; + mech.ulParameterLen = 0; + +- r = p11->C_SignInit(session, &mech, obj); +- if (r != CKR_OK) { +- pkiDebug("C_SignInit: %s\n", pkcs11err(r)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = p11->C_SignInit(session, &mech, obj); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_SignInit"); + goto cleanup; + } + +@@ -3881,18 +3897,17 @@ pkinit_sign_data_pkcs11(krb5_context context, + if (sigbuf == NULL) + goto cleanup; + +- r = p11->C_Sign(session, input, input_len, sigbuf, &len); +- if (r == CKR_BUFFER_TOO_SMALL || (r == CKR_OK && len >= PK_SIGLEN_GUESS)) { ++ rv = p11->C_Sign(session, input, input_len, sigbuf, &len); ++ if (rv == CKR_BUFFER_TOO_SMALL || ++ (rv == CKR_OK && len >= PK_SIGLEN_GUESS)) { + free(sigbuf); +- pkiDebug("C_Sign realloc %d\n", (int) len); + sigbuf = k5alloc(len, &ret); + if (sigbuf == NULL) + goto cleanup; +- r = p11->C_Sign(session, input, input_len, sigbuf, &len); ++ rv = p11->C_Sign(session, input, input_len, sigbuf, &len); + } +- if (r != CKR_OK) { +- pkiDebug("C_Sign: %s\n", pkcs11err(r)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_Sign"); + goto cleanup; + } + +@@ -4348,13 +4363,14 @@ reassemble_pkcs11_name(pkinit_identity_opts *idopts) + } + + static krb5_error_code +-load_one_cert(CK_FUNCTION_LIST_PTR p11, CK_SESSION_HANDLE session, +- pkinit_identity_opts *idopts, pkinit_cred_info *cred_out) ++load_one_cert(krb5_context context, CK_FUNCTION_LIST_PTR p11, ++ CK_SESSION_HANDLE session, pkinit_identity_opts *idopts, ++ pkinit_cred_info *cred_out) + { + krb5_error_code ret; + CK_ATTRIBUTE attrs[2]; + CK_BYTE_PTR cert = NULL, cert_id = NULL; +- CK_RV pret; ++ CK_RV rv; + const unsigned char *cp; + CK_OBJECT_HANDLE obj; + CK_ULONG count; +@@ -4364,8 +4380,8 @@ load_one_cert(CK_FUNCTION_LIST_PTR p11, CK_SESSION_HANDLE session, + *cred_out = NULL; + + /* Look for X.509 cert. */ +- pret = p11->C_FindObjects(session, &obj, 1, &count); +- if (pret != CKR_OK || count <= 0) ++ rv = p11->C_FindObjects(session, &obj, 1, &count); ++ if (rv != CKR_OK || count <= 0) + return 0; + + /* Get cert and id len. */ +@@ -4375,10 +4391,9 @@ load_one_cert(CK_FUNCTION_LIST_PTR p11, CK_SESSION_HANDLE session, + attrs[1].type = CKA_ID; + attrs[1].pValue = NULL; + attrs[1].ulValueLen = 0; +- pret = p11->C_GetAttributeValue(session, obj, attrs, 2); +- if (pret != CKR_OK && pret != CKR_BUFFER_TOO_SMALL) { +- pkiDebug("C_GetAttributeValue: %s\n", pkcs11err(pret)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = p11->C_GetAttributeValue(session, obj, attrs, 2); ++ if (rv != CKR_OK && rv != CKR_BUFFER_TOO_SMALL) { ++ ret = p11err(context, rv, "C_GetAttributeValue"); + goto cleanup; + } + +@@ -4393,10 +4408,9 @@ load_one_cert(CK_FUNCTION_LIST_PTR p11, CK_SESSION_HANDLE session, + attrs[0].pValue = cert; + attrs[1].type = CKA_ID; + attrs[1].pValue = cert_id; +- pret = p11->C_GetAttributeValue(session, obj, attrs, 2); +- if (pret != CKR_OK) { +- pkiDebug("C_GetAttributeValue: %s\n", pkcs11err(pret)); +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ rv = p11->C_GetAttributeValue(session, obj, attrs, 2); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_GetAttributeValue"); + goto cleanup; + } + +@@ -4406,7 +4420,8 @@ load_one_cert(CK_FUNCTION_LIST_PTR p11, CK_SESSION_HANDLE session, + cp = (unsigned char *)cert; + x = d2i_X509(NULL, &cp, (int)attrs[0].ulValueLen); + if (x == NULL) { +- ret = KRB5KDC_ERR_PREAUTH_FAILED; ++ ret = oerr(context, 0, ++ _("Failed to decode X509 certificate from PKCS11 token")); + goto cleanup; + } + +@@ -4444,7 +4459,7 @@ pkinit_get_certs_pkcs11(krb5_context context, + int i; + unsigned int nattrs; + krb5_error_code ret; +- CK_RV pret; ++ CK_RV rv; + + /* Copy stuff from idopts -> id_cryptoctx */ + if (idopts->p11_module_name != NULL) { +@@ -4516,16 +4531,16 @@ pkinit_get_certs_pkcs11(krb5_context context, + nattrs++; + } + +- pret = id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, attrs, +- nattrs); +- if (pret != CKR_OK) { +- pkiDebug("C_FindObjectsInit: %s\n", pkcs11err(pret)); ++ rv = id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, attrs, ++ nattrs); ++ if (rv != CKR_OK) { ++ ret = p11err(context, rv, "C_FindObjectsInit"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + for (i = 0; i < MAX_CREDS_ALLOWED; i++) { +- ret = load_one_cert(id_cryptoctx->p11, id_cryptoctx->session, idopts, +- &id_cryptoctx->creds[i]); ++ ret = load_one_cert(context, id_cryptoctx->p11, id_cryptoctx->session, ++ idopts, &id_cryptoctx->creds[i]); + if (ret) + return ret; + if (id_cryptoctx->creds[i] == NULL) +@@ -5510,19 +5525,26 @@ print_pubkey(BIGNUM * key, char *msg) + } + #endif + +-static const char * +-pkcs11err(int err) ++#ifndef WITHOUT_PKCS11 ++static krb5_error_code ++p11err(krb5_context context, CK_RV rv, const char *op) + { ++ krb5_error_code code = KRB5KDC_ERR_PREAUTH_FAILED; + int i; ++ const char *msg; + +- for (i = 0; pkcs11_errstrings[i].text != NULL; i++) +- if (pkcs11_errstrings[i].code == err) ++ for (i = 0; pkcs11_errstrings[i].text != NULL; i++) { ++ if (pkcs11_errstrings[i].code == rv) + break; +- if (pkcs11_errstrings[i].text != NULL) +- return (pkcs11_errstrings[i].text); ++ } ++ msg = pkcs11_errstrings[i].text; ++ if (msg == NULL) ++ msg = "unknown PKCS11 error"; + +- return "unknown PKCS11 error"; ++ krb5_set_error_message(context, code, _("PKCS11 error (%s): %s"), op, msg); ++ return code; + } ++#endif + + /* + * Add an item to the pkinit_identity_crypto_context's list of deferred +diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h +index 1c1ceb5a41..1faa6816d7 100644 +--- a/src/plugins/preauth/pkinit/pkinit_trace.h ++++ b/src/plugins/preauth/pkinit/pkinit_trace.h +@@ -98,21 +98,12 @@ + #define TRACE_PKINIT_OPENSSL_ERROR(c, msg) \ + TRACE(c, "PKINIT OpenSSL error: {str}", msg) + +-#define TRACE_PKINIT_PKCS11_GETFLIST_FAILED(c, errstr) \ +- TRACE(c, "PKINIT PKCS11 C_GetFunctionList failed: {str}", errstr) +-#define TRACE_PKINIT_PKCS11_GETSYM_FAILED(c, errstr) \ +- TRACE(c, "PKINIT unable to find PKCS11 plugin symbol " \ +- "C_GetFunctionList: {str}", errstr) +-#define TRACE_PKINIT_PKCS11_LOGIN_FAILED(c, errstr) \ +- TRACE(c, "PKINIT PKCS11 C_Login failed: {str}", errstr) + #define TRACE_PKINIT_PKCS11_NO_MATCH_TOKEN(c) \ + TRACE(c, "PKINIT PKCS#11 module has no matching tokens") + #define TRACE_PKINIT_PKCS11_NO_TOKEN(c) \ + TRACE(c, "PKINIT PKCS#11 module shows no slots with tokens") + #define TRACE_PKINIT_PKCS11_OPEN(c, name) \ + TRACE(c, "PKINIT opening PKCS#11 module \"{str}\"", name) +-#define TRACE_PKINIT_PKCS11_OPEN_FAILED(c, errstr) \ +- TRACE(c, "PKINIT PKCS#11 module open failed: {str}", errstr) + #define TRACE_PKINIT_PKCS11_SLOT(c, slot, len, label) \ + TRACE(c, "PKINIT PKCS#11 slotid {int} token {lenstr}", \ + slot, len, label) +-- +2.47.1 + diff --git a/SOURCES/0034-Set-missing-mask-flags-for-kdb5_util-operations.patch b/SOURCES/0034-Set-missing-mask-flags-for-kdb5_util-operations.patch new file mode 100644 index 0000000..9ed43f1 --- /dev/null +++ b/SOURCES/0034-Set-missing-mask-flags-for-kdb5_util-operations.patch @@ -0,0 +1,61 @@ +From e076f3c851b04bdee33798555c47220afbc5fe08 Mon Sep 17 00:00:00 2001 +From: Julien Rische +Date: Thu, 1 Aug 2024 10:56:07 +0200 +Subject: [PATCH] Set missing mask flags for kdb5_util operations + +Set KADM5_TL_DATA for the use_mkey and update_princ_encryption +commands. (Commit c877f13c8985d820583b0d7ac1bb4c5dc36e677e did this +for the add_new_mkey and purge_mkeys commands.) Set appropriate flags +for the add_random_key command. + +[ghudson@mit.edu: combined two commits; pruned out proposed mask flag +additions for values represented within key data or tl-data (like +KADM5_MKVNO), as those flags are currently only used in the kadm5 +protocol, not to communicate with the KDB module] + +ticket: 9158 (new) +(cherry picked from commit 4ed7da378940198cf4415f86d4eb013de6ac6455) +--- + src/kadmin/dbutil/kdb5_mkey.c | 4 +++- + src/kadmin/dbutil/kdb5_util.c | 3 +++ + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/kadmin/dbutil/kdb5_mkey.c b/src/kadmin/dbutil/kdb5_mkey.c +index aceb0a9b80..ac5c51d05e 100644 +--- a/src/kadmin/dbutil/kdb5_mkey.c ++++ b/src/kadmin/dbutil/kdb5_mkey.c +@@ -525,6 +525,8 @@ kdb5_use_mkey(int argc, char *argv[]) + goto cleanup_return; + } + ++ master_entry->mask |= KADM5_TL_DATA; ++ + if ((retval = krb5_db_put_principal(util_context, master_entry))) { + com_err(progname, retval, + _("while adding master key entry to the database")); +@@ -814,7 +816,7 @@ update_princ_encryption_1(void *cb, krb5_db_entry *ent) + goto fail; + } + +- ent->mask |= KADM5_KEY_DATA; ++ ent->mask |= KADM5_KEY_DATA | KADM5_TL_DATA; + + if ((retval = krb5_db_put_principal(util_context, ent))) { + com_err(progname, retval, _("while updating principal '%s' key data " +diff --git a/src/kadmin/dbutil/kdb5_util.c b/src/kadmin/dbutil/kdb5_util.c +index b9b61e3f91..073010674e 100644 +--- a/src/kadmin/dbutil/kdb5_util.c ++++ b/src/kadmin/dbutil/kdb5_util.c +@@ -600,6 +600,9 @@ add_random_key(int argc, char **argv) + exit_status++; + return; + } ++ ++ dbent->mask |= KADM5_ATTRIBUTES | KADM5_KEY_DATA | KADM5_TL_DATA; ++ + ret = krb5_db_put_principal(util_context, dbent); + krb5_db_free_principal(util_context, dbent); + if (ret) { +-- +2.47.1 + diff --git a/SOURCES/0035-Prevent-overflow-when-calculating-ulog-block-size.patch b/SOURCES/0035-Prevent-overflow-when-calculating-ulog-block-size.patch new file mode 100644 index 0000000..3af66a7 --- /dev/null +++ b/SOURCES/0035-Prevent-overflow-when-calculating-ulog-block-size.patch @@ -0,0 +1,64 @@ +From 81e50cfb3b83a62d2a1e604a0854a9f346bdd6f9 Mon Sep 17 00:00:00 2001 +From: Zoltan Borbely +Date: Tue, 28 Jan 2025 16:39:25 -0500 +Subject: [PATCH] Prevent overflow when calculating ulog block size + +In kdb_log.c:resize(), log an error and fail if the update size is +larger than the largest possible block size (2^16-1). + +CVE-2025-24528: + +In MIT krb5 release 1.7 and later with incremental propagation +enabled, an authenticated attacker can cause kadmind to write beyond +the end of the mapped region for the iprop log file, likely causing a +process crash. + +[ghudson@mit.edu: edited commit message and added CVE description] + +ticket: 9159 (new) +tags: pullup +target_version: 1.21-next + +(cherry picked from commit 78ceba024b64d49612375be4a12d1c066b0bfbd0) +--- + src/lib/kdb/kdb_log.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/lib/kdb/kdb_log.c b/src/lib/kdb/kdb_log.c +index e9b95fce59..c805ebd988 100644 +--- a/src/lib/kdb/kdb_log.c ++++ b/src/lib/kdb/kdb_log.c +@@ -183,7 +183,7 @@ extend_file_to(int fd, unsigned int new_size) + */ + static krb5_error_code + resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd, +- unsigned int recsize) ++ unsigned int recsize, const kdb_incr_update_t *upd) + { + unsigned int new_block, new_size; + +@@ -195,6 +195,12 @@ resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd, + new_block *= ULOG_BLOCK; + new_size += ulogentries * new_block; + ++ if (new_block > UINT16_MAX) { ++ syslog(LOG_ERR, _("ulog overflow caused by principal %.*s"), ++ upd->kdb_princ_name.utf8str_t_len, ++ upd->kdb_princ_name.utf8str_t_val); ++ return KRB5_LOG_ERROR; ++ } + if (new_size > MAXLOGLEN) + return KRB5_LOG_ERROR; + +@@ -291,7 +297,7 @@ store_update(kdb_log_context *log_ctx, kdb_incr_update_t *upd) + recsize = sizeof(kdb_ent_header_t) + upd_size; + + if (recsize > ulog->kdb_block) { +- retval = resize(ulog, ulogentries, log_ctx->ulogfd, recsize); ++ retval = resize(ulog, ulogentries, log_ctx->ulogfd, recsize, upd); + if (retval) + return retval; + } +-- +2.48.1 + diff --git a/SPECS/krb5.spec b/SPECS/krb5.spec index 9fed100..463d645 100644 --- a/SPECS/krb5.spec +++ b/SPECS/krb5.spec @@ -34,7 +34,7 @@ # # baserelease is what we have standardized across Fedora and what # rpmdev-bumpspec knows how to handle. -%global baserelease 3 +%global baserelease 6 # This should be e.g. beta1 or %%nil %global pre_release %nil @@ -106,6 +106,18 @@ Patch0020: 0020-Avoid-strict-prototype-compiler-errors.patch Patch0021: 0021-Fix-leak-in-KDC-NDR-encoding.patch Patch0022: 0022-Fix-two-unlikely-memory-leaks.patch Patch0023: 0023-Fix-vulnerabilities-in-GSS-message-token-handling.patch +Patch0024: 0024-Remove-PKINIT-RSA-support.patch +Patch0025: 0025-Fix-various-issues-detected-by-static-analysis.patch +Patch0026: 0026-Generate-and-verify-message-MACs-in-libkrad.patch +Patch0027: 0027-PKINIT-ECDH-support.patch +Patch0028: 0028-Add-ecdsa-with-sha512-256-to-supportedCMSTypes.patch +Patch0029: 0029-Get-rid-of-pkinit_crypto_openssl.h.patch +Patch0030: 0030-Use-SoftHSMv2-for-PKCS11-PKINIT-tests.patch +Patch0031: 0031-Simplify-PKINIT-cert-representation.patch +Patch0032: 0032-Support-PKCS11-EC-client-certs-in-PKINIT.patch +Patch0033: 0033-Improve-PKCS11-error-reporting-in-PKINIT.patch +Patch0034: 0034-Set-missing-mask-flags-for-kdb5_util-operations.patch +Patch0035: 0035-Prevent-overflow-when-calculating-ulog-block-size.patch License: MIT URL: https://web.mit.edu/kerberos/www/ @@ -134,6 +146,8 @@ BuildRequires: net-tools, rpcbind BuildRequires: hostname BuildRequires: iproute BuildRequires: python3-pyrad +BuildRequires: opensc +BuildRequires: softhsm %endif # Need KDFs. This is the "real" version @@ -670,6 +684,26 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* Wed Jan 29 2025 Julien Rische - 1.21.1-6 +- Prevent overflow when calculating ulog block size (CVE-2025-24528) + Resolves: RHEL-76759 + +* Fri Jan 17 2025 Julien Rische - 1.21.1-5 +- Support PKCS11 EC client certs in PKINIT + Resolves: RHEL-74374 +- kdb5_util: fix DB entry flags on modification + Resolves: RHEL-56059 +- Add ECDH support for PKINIT (RFC5349) + Resolves: RHEL-4902 + +* Thu Oct 17 2024 Julien Rische - 1.21.1-4 +- libkrad: implement support for Message-Authenticator (CVE-2024-3596) + Resolves: RHEL-55423 +- Fix various issues detected by static analysis + Resolves: RHEL-58216 +- Remove RSA protocol for PKINIT + Resolves: RHEL-15323 + * Fri Jul 05 2024 Julien Rische - 1.21.1-3 - CVE-2024-37370 CVE-2024-37371 Fix vulnerabilities in GSS message token handling