diff --git a/0027-PKINIT-ECDH-support.patch b/0027-PKINIT-ECDH-support.patch new file mode 100644 index 0000000..76ad325 --- /dev/null +++ b/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/0028-Add-ecdsa-with-sha512-256-to-supportedCMSTypes.patch b/0028-Add-ecdsa-with-sha512-256-to-supportedCMSTypes.patch new file mode 100644 index 0000000..fc8b72b --- /dev/null +++ b/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/0029-Get-rid-of-pkinit_crypto_openssl.h.patch b/0029-Get-rid-of-pkinit_crypto_openssl.h.patch new file mode 100644 index 0000000..88f8409 --- /dev/null +++ b/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/0030-Use-SoftHSMv2-for-PKCS11-PKINIT-tests.patch b/0030-Use-SoftHSMv2-for-PKCS11-PKINIT-tests.patch new file mode 100644 index 0000000..ff7aa66 --- /dev/null +++ b/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/0031-Simplify-PKINIT-cert-representation.patch b/0031-Simplify-PKINIT-cert-representation.patch new file mode 100644 index 0000000..21bc856 --- /dev/null +++ b/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/0032-Support-PKCS11-EC-client-certs-in-PKINIT.patch b/0032-Support-PKCS11-EC-client-certs-in-PKINIT.patch new file mode 100644 index 0000000..2de6dd8 --- /dev/null +++ b/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 +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 +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/0033-Improve-PKCS11-error-reporting-in-PKINIT.patch b/0033-Improve-PKCS11-error-reporting-in-PKINIT.patch new file mode 100644 index 0000000..b6750dd --- /dev/null +++ b/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/0034-Set-missing-mask-flags-for-kdb5_util-operations.patch b/0034-Set-missing-mask-flags-for-kdb5_util-operations.patch new file mode 100644 index 0000000..9ed43f1 --- /dev/null +++ b/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/krb5.spec b/krb5.spec index d3764f2..46bc516 100644 --- a/krb5.spec +++ b/krb5.spec @@ -34,7 +34,7 @@ # # baserelease is what we have standardized across Fedora and what # rpmdev-bumpspec knows how to handle. -%global baserelease 4 +%global baserelease 5 # This should be e.g. beta1 or %%nil %global pre_release %nil @@ -109,6 +109,14 @@ 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 License: MIT URL: https://web.mit.edu/kerberos/www/ @@ -137,6 +145,8 @@ BuildRequires: net-tools, rpcbind BuildRequires: hostname BuildRequires: iproute BuildRequires: python3-pyrad +BuildRequires: opensc +BuildRequires: softhsm %endif # Need KDFs. This is the "real" version @@ -673,6 +683,14 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* 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