From 4c9fe4ffcd0d443288cabaa41b03e7eaac0f2e06 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 20 Oct 2026 13:32:58 -0700 Subject: [PATCH 1/4] ecdh: use X963KDF as the KDF type name OpenSSL's ECDH takes and reports "X963KDF", which CMS and EVP_PKEY_CTX_set_ecdh_kdf_type send. We accepted only "X942KDF-ASN1" (running X9.63), so CMS EC recipients failed. Refused when wolfSSL has no X9.63 KDF. Like the default provider, fail the derive when no KDF output length is set. The test now sets the KDF as CMS does and compares with the default provider. --- README.md | 2 +- src/wp_ecdh_exch.c | 9 +++-- test/test_ecc.c | 97 ++++++++++++++++++++-------------------------- 3 files changed, 49 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 3db632e3..cc22e38b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ takes the private key length from the group. ### ECC * ECDSA (signing, verification) -* ECDH (key exchange) +* ECDH (key exchange, optional X9.63 KDF as used by CMS) * Key generation * Curves: P-192, P-224, P-256, P-384, P-521 diff --git a/src/wp_ecdh_exch.c b/src/wp_ecdh_exch.c index b4bb0188..ee0d6b0c 100644 --- a/src/wp_ecdh_exch.c +++ b/src/wp_ecdh_exch.c @@ -234,7 +234,7 @@ static int wp_ecdh_kdf_derive(wp_EcdhCtx* ctx, unsigned char* key, return 0; } - if (keySize < ctx->keyLen) { + if ((ctx->keyLen == 0) || (keySize < ctx->keyLen)) { ok = 0; } if (ok) { @@ -460,10 +460,11 @@ static int wp_ecdh_set_param_kdf(wp_EcdhCtx* ctx, const OSSL_PARAM params[]) if (kdf[0] == '\0') { ctx->kdfType = WP_KDF_NONE; } - else if (XSTRCMP(kdf, OSSL_KDF_NAME_X942KDF_ASN1) == 0) { - /* Only support the non ASN1 variant. */ +#ifdef HAVE_X963_KDF + else if (XSTRCMP(kdf, OSSL_KDF_NAME_X963KDF) == 0) { ctx->kdfType = WP_KDF_X963; } +#endif else { ok = 0; } @@ -604,7 +605,7 @@ static int wp_ecdh_get_params_kdf(wp_EcdhCtx* ctx, OSSL_PARAM params[]) if (p != NULL) { const char* type = ""; if (ctx->kdfType == WP_KDF_X963) { - type = OSSL_KDF_NAME_X942KDF_ASN1; + type = OSSL_KDF_NAME_X963KDF; } if (ok && (!OSSL_PARAM_set_utf8_string(p, type))) { ok = 0; diff --git a/test/test_ecc.c b/test/test_ecc.c index 87003a73..798d2770 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -1064,7 +1064,11 @@ int test_ecdh_invalid_kdf_strings(void *data) (char *)"X", (char *)"X942", (char *)"X942KDF", - (char *)"X942KDF-AS" + (char *)"X942KDF-AS", + (char *)OSSL_KDF_NAME_X942KDF_ASN1, +#ifndef HAVE_X963_KDF + (char *)OSSL_KDF_NAME_X963KDF, +#endif }; size_t i; @@ -1264,56 +1268,18 @@ int test_ecdh_invalid_curve_peer(void *data) #endif /* USE_ECC_B_PARAM */ #if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) -/* Apply X9.63 KDF using OpenSSL's reference implementation so we can compare - * against wolfProvider's output. */ -static int test_ecdh_x963_kdf_ref(const unsigned char* secret, size_t secLen, - const char* mdName, const unsigned char* ukm, size_t ukmLen, - unsigned char* out, size_t outLen) -{ - int err = 0; - EVP_KDF *kdf = NULL; - EVP_KDF_CTX *kctx = NULL; - OSSL_PARAM params[4]; - OSSL_PARAM *p = params; - - kdf = EVP_KDF_fetch(osslLibCtx, OSSL_KDF_NAME_X963KDF, NULL); - err = kdf == NULL; - if (err == 0) { - kctx = EVP_KDF_CTX_new(kdf); - err = kctx == NULL; - } - if (err == 0) { - *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, - (char*)mdName, 0); - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, - (unsigned char*)secret, secLen); - if (ukm != NULL && ukmLen > 0) { - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, - (unsigned char*)ukm, ukmLen); - } - *p = OSSL_PARAM_construct_end(); - - err = EVP_KDF_derive(kctx, out, outLen, params) <= 0; - } - - EVP_KDF_CTX_free(kctx); - EVP_KDF_free(kdf); - return err; -} - -/* Run wolfProvider's ECDH derive with X9.63 KDF parameters set, returning the - * KDF output. */ -static int test_ecdh_derive_with_x963(EVP_PKEY *key, EVP_PKEY *peerKey, - const char* mdName, size_t outLen, const unsigned char* ukm, size_t ukmLen, - unsigned char *out) +/* ECDH derive with X9.63 KDF set the way CMS does (cms_ec.c). */ +static int test_ecdh_derive_with_x963(OSSL_LIB_CTX* libCtx, EVP_PKEY *key, + EVP_PKEY *peerKey, const char* mdName, size_t outLen, + const unsigned char* ukm, size_t ukmLen, unsigned char *out) { int err = 0; EVP_PKEY_CTX *ctx = NULL; - OSSL_PARAM params[5]; + OSSL_PARAM params[4]; OSSL_PARAM *p = params; size_t derivedLen = outLen; - ctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); + ctx = EVP_PKEY_CTX_new_from_pkey(libCtx, key, NULL); err = ctx == NULL; if (err == 0) { err = EVP_PKEY_derive_init(ctx) != 1; @@ -1322,10 +1288,10 @@ static int test_ecdh_derive_with_x963(EVP_PKEY *key, EVP_PKEY *peerKey, err = EVP_PKEY_derive_set_peer(ctx, peerKey) != 1; } if (err == 0) { - /* wolfProvider currently maps the X942KDF-ASN1 type string to its - * internal X963 KDF implementation (wc_X963_KDF). */ - *p++ = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_TYPE, - (char*)OSSL_KDF_NAME_X942KDF_ASN1, 0); + err = EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, + EVP_PKEY_ECDH_KDF_X9_63) != 1; + } + if (err == 0) { *p++ = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_DIGEST, (char*)mdName, 0); *p++ = OSSL_PARAM_construct_size_t(OSSL_EXCHANGE_PARAM_KDF_OUTLEN, @@ -1338,6 +1304,13 @@ static int test_ecdh_derive_with_x963(EVP_PKEY *key, EVP_PKEY *peerKey, err = EVP_PKEY_CTX_set_params(ctx, params) != 1; } + if (err == 0) { + err = EVP_PKEY_CTX_get_ecdh_kdf_type(ctx) != EVP_PKEY_ECDH_KDF_X9_63; + } + if (err == 0) { + err = (EVP_PKEY_derive(ctx, NULL, &derivedLen) != 1) || + (derivedLen != outLen); + } if (err == 0) { err = EVP_PKEY_derive(ctx, out, &derivedLen) != 1; } @@ -1356,6 +1329,8 @@ int test_ecdh_x963_kdf(void *data) int err = 0; EVP_PKEY *keyA = NULL; EVP_PKEY *keyB = NULL; + EVP_PKEY *osslKeyA = NULL; + EVP_PKEY *osslKeyB = NULL; const unsigned char *p; unsigned char wpOut[64]; unsigned char refOut[64]; @@ -1392,6 +1367,18 @@ int test_ecdh_x963_kdf(void *data) sizeof(ecc_peerkey_der_256), wpLibCtx, NULL); err = keyB == NULL; } + if (err == 0) { + p = ecc_key_der_256; + osslKeyA = d2i_PrivateKey_ex(EVP_PKEY_EC, NULL, &p, + sizeof(ecc_key_der_256), osslLibCtx, NULL); + err = osslKeyA == NULL; + } + if (err == 0) { + p = ecc_peerkey_der_256; + osslKeyB = d2i_PrivateKey_ex(EVP_PKEY_EC, NULL, &p, + sizeof(ecc_peerkey_der_256), osslLibCtx, NULL); + err = osslKeyB == NULL; + } for (i = 0; (err == 0) && (i < sizeof(cases) / sizeof(cases[0])); ++i) { const unsigned char *ukmPtr = cases[i].withUkm ? ukm : NULL; @@ -1400,18 +1387,18 @@ int test_ecdh_x963_kdf(void *data) memset(wpOut, 0, sizeof(wpOut)); memset(refOut, 0, sizeof(refOut)); - err = test_ecdh_derive_with_x963(keyA, keyB, cases[i].md, + err = test_ecdh_derive_with_x963(wpLibCtx, keyA, keyB, cases[i].md, cases[i].outLen, ukmPtr, ukmLen, wpOut); if (err == 0) { - err = test_ecdh_x963_kdf_ref(ecc_derived_256, - sizeof(ecc_derived_256), cases[i].md, ukmPtr, ukmLen, refOut, - cases[i].outLen); + err = test_ecdh_derive_with_x963(osslLibCtx, osslKeyA, + osslKeyB, cases[i].md, cases[i].outLen, ukmPtr, ukmLen, + refOut); } if (err == 0 && memcmp(wpOut, refOut, cases[i].outLen) != 0) { PRINT_ERR_MSG("X9.63 KDF output mismatch (md=%s outLen=%zu ukm=%d)", cases[i].md, cases[i].outLen, cases[i].withUkm); PRINT_BUFFER("wolfProvider", wpOut, cases[i].outLen); - PRINT_BUFFER("OpenSSL X963KDF", refOut, cases[i].outLen); + PRINT_BUFFER("OpenSSL", refOut, cases[i].outLen); err = 1; } /* Any tail bytes beyond outLen must remain untouched - guards against @@ -1431,6 +1418,8 @@ int test_ecdh_x963_kdf(void *data) EVP_PKEY_free(keyA); EVP_PKEY_free(keyB); + EVP_PKEY_free(osslKeyA); + EVP_PKEY_free(osslKeyB); return err; } From 302632a6035d90a9170d61b9a3e1af8d42b894d1 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 20 Oct 2026 13:32:58 -0700 Subject: [PATCH 2/4] dh: reject the X9.42 KDF type "X942KDF-ASN1" was mapped onto wc_X963_KDF, deriving a different key than X9.42. wolfCrypt has no X9.42 KDF, so fail closed and drop the unused KDF derive path. --- README.md | 3 + src/wp_dh_exch.c | 141 ++++------------------ test/test_dh.c | 305 +---------------------------------------------- test/unit.c | 3 - test/unit.h | 3 - 5 files changed, 27 insertions(+), 428 deletions(-) diff --git a/README.md b/README.md index cc22e38b..8454af1a 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ from a named group or existing parameters accepts a generator only when the parameters already have it. A non-zero `priv_len` is refused because wolfSSL takes the private key length from the group. +Key exchange refuses the X9.42 KDF (`X942KDF-ASN1`) because wolfCrypt has no +X9.42 KDF, so CMS with DH recipients is not supported. + ### ECC * ECDSA (signing, verification) * ECDH (key exchange, optional X9.63 KDF as used by CMS) diff --git a/src/wp_dh_exch.c b/src/wp_dh_exch.c index cc655f32..6b11c474 100644 --- a/src/wp_dh_exch.c +++ b/src/wp_dh_exch.c @@ -31,12 +31,6 @@ #ifdef WP_HAVE_DH -/** No KDF applied to derived secret. */ -#define WP_KDF_NONE 0 -/** X9.63 KDF applied to derived secret. */ -#define WP_KDF_X963 1 - - /** * DH key exchange context. */ @@ -52,8 +46,7 @@ typedef struct wp_DhCtx { /** Pad secret output with zeros. */ int pad; - /** KDF type to apply to calculated secret. */ - int kdfType; + /* KDF parameters are kept for get/set parity; no DH KDF is applied. */ /** Digest to use with KDF. */ enum wc_HashType kdfMd; /** Name of digest to use with KDF. */ @@ -150,7 +143,6 @@ static wp_DhCtx* wp_dh_dupctx(wp_DhCtx* src) if (ok) { /* Copy remaining fields across. */ dst->pad = src->pad; - dst->kdfType = src->kdfType; dst->kdfMd = src->kdfMd; XMEMCPY(dst->kdfMdName, src->kdfMdName, sizeof(dst->kdfMdName)); dst->ukmLen = src->ukmLen; @@ -206,54 +198,6 @@ static int wp_dh_init(wp_DhCtx* ctx, wp_Dh* dh, const OSSL_PARAM params[]) return ok; } -/** - * Derive key from secret KDF to produce output. - * - * @param [in] ctx DH key exchange context object. - * @param [in] key Buffer to hold key derived from secret. - * @param [out] keyLen Length of derived key in bytes. - * @param [in] keySize Length of buffer in bytes. - * @param [in] sec Secret data. - * @param [in] secLen Length of secret data in bytes. - * @return 1 on success. - * @return 0 on failure. - */ -static int wp_dh_kdf_derive(wp_DhCtx* ctx, unsigned char* key, - size_t* keyLen, size_t keySize, unsigned char* sec, size_t secLen) -{ - int ok = 1; - - WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_kdf_derive"); - - if (keySize < ctx->keyLen) { - ok = 0; - } - if (ok) { -#ifdef HAVE_X963_KDF - int rc; - /* TODO: support X9.42 KDF that includes ASN.1 encoding. */ - rc = wc_X963_KDF(ctx->kdfMd, sec, (word32)secLen, ctx->ukm, - (word32)ctx->ukmLen, key, (word32)ctx->keyLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_X963_KDF", rc); - ok = 0; - } - else { - *keyLen = ctx->keyLen; - } -#else - (void)key; - (void)keyLen; - (void)sec; - (void)secLen; - ok = 0; -#endif - } - - WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); - return ok; -} - #if (LIBWOLFSSL_VERSION_HEX < 0x05009002) || defined(WOLFSSL_KCAPI_DH) || \ defined(HAVE_FIPS) /** @@ -398,8 +342,6 @@ static int wp_dh_derive_secret(wp_DhCtx* ctx, unsigned char* secret, /** * Derive a secret/key using DH key exchange. * - * Can put the DH secret through a KDF. - * * @param [in] ctx DH key exchange context object. * @param [out] secret Buffer to hold secret/key. * @param [out] secLen Length of secret/key data in bytes. @@ -411,10 +353,7 @@ static int wp_dh_derive(wp_DhCtx* ctx, unsigned char* secret, size_t* secLen, size_t secSize) { int ok = 1; - int done = 0; - unsigned char* out = NULL; size_t outLen = 0; - unsigned char* tmp = NULL; size_t maxLen = 0; WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_derive"); @@ -425,59 +364,26 @@ static int wp_dh_derive(wp_DhCtx* ctx, unsigned char* secret, /* No output buffer, return maximum size only. */ if (ok && (secret == NULL)) { - if (ctx->kdfType == WP_KDF_NONE) { - *secLen = wp_dh_get_size(ctx->key); - } - else { - *secLen = ctx->keyLen; - } - done = 1; + *secLen = wp_dh_get_size(ctx->key); } - - if ((!done) && ok) { + else if (ok) { /* Calculate maximum secret length. */ maxLen = wp_dh_get_size(ctx->key); - if (ctx->kdfType == WP_KDF_NONE) { - /* Output of DH key exchange directly into caller's buffer. */ - if (secSize < maxLen) { - ok = 0; - } - else { - out = secret; - outLen = secSize; - } - } - else if (ctx->kdfType == WP_KDF_X963) { - /* Output of DH key exchange goes into temporary buffer. */ - tmp = OPENSSL_malloc(maxLen); - if (tmp == NULL) { - ok = 0; - } - if (ok) { - out = tmp; - outLen = maxLen; - } - } - } - - if ((!done) && ok && (out != NULL)) { - /* DH key exchange derivation using wolfSSL. */ - ok = wp_dh_derive_secret(ctx, out, &outLen, maxLen); - } - - if ((!done) && ok) { - if (ctx->kdfType == WP_KDF_X963) { - /* Put output through KDF using wolfSSL. */ - ok = wp_dh_kdf_derive(ctx, secret, secLen, secSize, out, outLen); + /* Output of DH key exchange directly into caller's buffer. */ + if (secSize < maxLen) { + ok = 0; } else { + outLen = secSize; + /* DH key exchange derivation using wolfSSL. */ + ok = wp_dh_derive_secret(ctx, secret, &outLen, maxLen); + } + if (ok) { *secLen = outLen; } } - OPENSSL_clear_free(tmp, maxLen); - WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -547,9 +453,9 @@ static int wp_dh_set_peer(wp_DhCtx* ctx, wp_Dh* peer) } /** - * Set the KDF algorithm from the parameters. + * Check the KDF algorithm from the parameters. Only none is supported. * - * @param [in, out] ctx DH key exchange context object. + * @param [in] ctx DH key exchange context object. Unused. * @param [in] params Array of parameters and values. * @return 1 on success. * @return 0 on failure. @@ -566,18 +472,14 @@ static int wp_dh_set_param_kdf(wp_DhCtx* ctx, const OSSL_PARAM params[]) ok = 0; } if (ok && (kdf != NULL)) { - if (kdf[0] == '\0') { - ctx->kdfType = WP_KDF_NONE; - } - else if (XSTRCMP(kdf, OSSL_KDF_NAME_X942KDF_ASN1) == 0) { - /* Only support the non ASN1 variant. */ - ctx->kdfType = WP_KDF_X963; - } - else { + /* wolfCrypt has no X9.42 KDF. */ + if (kdf[0] != '\0') { + ERR_raise(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED); ok = 0; } } + (void)ctx; WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -692,7 +594,7 @@ static const OSSL_PARAM* wp_dh_gettable_ctx_params(wp_DhCtx* ctx, /** * Get the KDF algorithm from the context and put into parameters. * - * @param [in] ctx DH key exchange context object. + * @param [in] ctx DH key exchange context object. Unused. * @param [in, out] params Array of parameters and values. * @return 1 on success. * @return 0 on failure. @@ -706,15 +608,12 @@ static int wp_dh_get_params_kdf(wp_DhCtx* ctx, OSSL_PARAM params[]) p = OSSL_PARAM_locate(params, OSSL_EXCHANGE_PARAM_KDF_TYPE); if (p != NULL) { - const char* type = ""; - if (ctx->kdfType == WP_KDF_X963) { - type = OSSL_KDF_NAME_X942KDF_ASN1; - } - if (ok && (!OSSL_PARAM_set_utf8_string(p, type))) { + if (!OSSL_PARAM_set_utf8_string(p, "")) { ok = 0; } } + (void)ctx; WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } diff --git a/test/test_dh.c b/test/test_dh.c index c851d3ea..955b940e 100644 --- a/test/test_dh.c +++ b/test/test_dh.c @@ -552,7 +552,10 @@ int test_dh_invalid_kdf_strings(void *data) (char *)"X", (char *)"X942", (char *)"X942KDF", - (char *)"X942KDF-AS" + (char *)"X942KDF-AS", + /* wolfCrypt has no X9.42 KDF. */ + (char *)OSSL_KDF_NAME_X942KDF_ASN1, + (char *)OSSL_KDF_NAME_X963KDF }; size_t i; @@ -1312,306 +1315,6 @@ int test_dh_derive_small_buffer(void *data) return err; } -#if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) -/* Apply X9.63 KDF using OpenSSL's reference implementation. */ -static int test_dh_x963_kdf_ref(const unsigned char* secret, size_t secLen, - const char* mdName, const unsigned char* ukm, size_t ukmLen, - unsigned char* out, size_t outLen) -{ - int err = 0; - EVP_KDF *kdf = NULL; - EVP_KDF_CTX *kctx = NULL; - OSSL_PARAM params[4]; - OSSL_PARAM *p = params; - - kdf = EVP_KDF_fetch(osslLibCtx, OSSL_KDF_NAME_X963KDF, NULL); - err = kdf == NULL; - if (err == 0) { - kctx = EVP_KDF_CTX_new(kdf); - err = kctx == NULL; - } - if (err == 0) { - *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, - (char*)mdName, 0); - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, - (unsigned char*)secret, secLen); - if (ukm != NULL && ukmLen > 0) { - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, - (unsigned char*)ukm, ukmLen); - } - *p = OSSL_PARAM_construct_end(); - - err = EVP_KDF_derive(kctx, out, outLen, params) <= 0; - } - - EVP_KDF_CTX_free(kctx); - EVP_KDF_free(kdf); - return err; -} - -/* Derive raw DH shared secret (no KDF) using wolfProvider. Caller frees. */ -static int test_dh_derive_raw(EVP_PKEY *key, EVP_PKEY *peerKey, - unsigned char **pSecret, size_t *pSecretLen) -{ - int err = 0; - EVP_PKEY_CTX *ctx = NULL; - unsigned char *secret = NULL; - size_t len = 0; - - ctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); - err = ctx == NULL; - if (err == 0) { - err = EVP_PKEY_derive_init(ctx) <= 0; - } - if (err == 0) { - err = EVP_PKEY_derive_set_peer(ctx, peerKey) <= 0; - } - if (err == 0) { - err = EVP_PKEY_derive(ctx, NULL, &len) <= 0; - } - if (err == 0) { - secret = (unsigned char*)OPENSSL_malloc(len); - err = secret == NULL; - } - if (err == 0) { - err = EVP_PKEY_derive(ctx, secret, &len) <= 0; - } - if (err == 0) { - *pSecret = secret; - *pSecretLen = len; - secret = NULL; - } - - OPENSSL_free(secret); - EVP_PKEY_CTX_free(ctx); - return err; -} - -static int test_dh_derive_x963_raw(EVP_PKEY *key, EVP_PKEY *peerKey, - const char* mdName, size_t outLen, const unsigned char* ukm, size_t ukmLen, - unsigned char *out, size_t outBufLen, int *deriveRet, size_t *derivedLen) -{ - int err = 0; - EVP_PKEY_CTX *ctx = NULL; - OSSL_PARAM params[5]; - OSSL_PARAM *p = params; - - *deriveRet = 0; - *derivedLen = outBufLen; - - ctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); - err = ctx == NULL; - if (err == 0) { - err = EVP_PKEY_derive_init(ctx) <= 0; - } - if (err == 0) { - err = EVP_PKEY_derive_set_peer(ctx, peerKey) <= 0; - } - if (err == 0) { - /* wolfProvider maps the X942KDF-ASN1 type string to its internal X963 - * KDF implementation (wc_X963_KDF). */ - *p++ = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_TYPE, - (char*)OSSL_KDF_NAME_X942KDF_ASN1, 0); - *p++ = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_DIGEST, - (char*)mdName, 0); - *p++ = OSSL_PARAM_construct_size_t(OSSL_EXCHANGE_PARAM_KDF_OUTLEN, - &outLen); - if (ukm != NULL && ukmLen > 0) { - *p++ = OSSL_PARAM_construct_octet_string( - OSSL_EXCHANGE_PARAM_KDF_UKM, (unsigned char*)ukm, ukmLen); - } - *p = OSSL_PARAM_construct_end(); - - err = EVP_PKEY_CTX_set_params(ctx, params) != 1; - } - if (err == 0) { - *deriveRet = EVP_PKEY_derive(ctx, out, derivedLen); - } - - EVP_PKEY_CTX_free(ctx); - return err; -} - -/* Derive via wolfProvider with X9.63 KDF parameters set, requiring success and - * exactly outLen bytes of output. */ -static int test_dh_derive_with_x963(EVP_PKEY *key, EVP_PKEY *peerKey, - const char* mdName, size_t outLen, const unsigned char* ukm, size_t ukmLen, - unsigned char *out, size_t outBufLen) -{ - int err = 0; - int deriveRet = 0; - size_t derivedLen = 0; - - err = test_dh_derive_x963_raw(key, peerKey, mdName, outLen, ukm, ukmLen, - out, outBufLen, &deriveRet, &derivedLen); - if (err == 0 && deriveRet <= 0) { - err = 1; - } - if (err == 0 && derivedLen != outLen) { - PRINT_ERR_MSG("KDF output length %zu != requested %zu", derivedLen, - outLen); - err = 1; - } - - return err; -} - -/** - * Test DH key derivation through the X9.63 KDF path. - * - * The provider's WP_KDF_X963 branch in wp_dh_derive (1) allocates a temporary - * buffer sized to the prime length, (2) runs the raw DH agreement into it, - * (3) feeds the result through wc_X963_KDF, and (4) securely frees the - * temporary. We validate by computing the same KDF output independently via - * OpenSSL's X963KDF and comparing. - */ -int test_dh_x963_kdf(void *data) -{ - int err = 0; - DH *dh = NULL; - EVP_PKEY *params = NULL; - BIGNUM *p = NULL; - BIGNUM *g = NULL; - EVP_PKEY_CTX *kgCtx = NULL; - EVP_PKEY *keyA = NULL; - EVP_PKEY *keyB = NULL; - unsigned char *raw = NULL; - size_t rawLen = 0; - unsigned char wpOut[96]; - unsigned char refOut[96]; - unsigned char tooSmallBuf[8]; - size_t tooSmallLen; - static const unsigned char ukm[] = { - 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, 0x07, 0x18, - 0x29, 0x3a, 0x4b, 0x5c, 0x6d, 0x7e, 0x8f, 0x90 - }; - static const struct { - const char* md; - size_t outLen; - int withUkm; - } cases[] = { - { "SHA256", 16, 0 }, - { "SHA256", 32, 0 }, - { "SHA256", 48, 0 }, - { "SHA256", 64, 0 }, - { "SHA256", 32, 1 }, - }; - size_t i; - - (void)data; - - PRINT_MSG("DH X9.63 KDF derivation"); - - dh = DH_new(); - err = dh == NULL; - if (err == 0) { - p = BN_bin2bn(dh_p, sizeof(dh_p), NULL); - err = p == NULL; - } - if (err == 0) { - g = BN_bin2bn(dh_g, sizeof(dh_g), NULL); - err = g == NULL; - } - if (err == 0) { - err = DH_set0_pqg(dh, p, NULL, g) == 0; - if (err == 0) { - p = NULL; - g = NULL; - } - } - if (err == 0) { - params = EVP_PKEY_new(); - err = params == NULL; - } - if (err == 0) { - err = EVP_PKEY_set1_DH(params, dh) != 1; - } - - /* Generate one fresh key pair for both ends. */ - if (err == 0) { - kgCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, params, NULL); - err = kgCtx == NULL; - } - if (err == 0) { - err = EVP_PKEY_keygen_init(kgCtx) != 1; - } - if (err == 0) { - err = EVP_PKEY_keygen(kgCtx, &keyA) != 1; - } - if (err == 0) { - err = EVP_PKEY_keygen(kgCtx, &keyB) != 1; - } - - /* Snapshot the raw DH shared secret once - it's the same input the KDF - * branch feeds to wc_X963_KDF regardless of requested output length. */ - if (err == 0) { - err = test_dh_derive_raw(keyA, keyB, &raw, &rawLen); - } - - for (i = 0; (err == 0) && (i < sizeof(cases) / sizeof(cases[0])); ++i) { - const unsigned char *ukmPtr = cases[i].withUkm ? ukm : NULL; - size_t ukmLen = cases[i].withUkm ? sizeof(ukm) : 0; - - memset(wpOut, 0, sizeof(wpOut)); - memset(refOut, 0, sizeof(refOut)); - - err = test_dh_derive_with_x963(keyA, keyB, cases[i].md, - cases[i].outLen, ukmPtr, ukmLen, wpOut, sizeof(wpOut)); - if (err == 0) { - err = test_dh_x963_kdf_ref(raw, rawLen, cases[i].md, ukmPtr, - ukmLen, refOut, cases[i].outLen); - } - if (err == 0 && memcmp(wpOut, refOut, cases[i].outLen) != 0) { - PRINT_ERR_MSG("X9.63 KDF output mismatch (md=%s outLen=%zu ukm=%d)", - cases[i].md, cases[i].outLen, cases[i].withUkm); - PRINT_BUFFER("wolfProvider", wpOut, cases[i].outLen); - PRINT_BUFFER("OpenSSL X963KDF", refOut, cases[i].outLen); - err = 1; - } - /* No bytes beyond the requested length should have been written. */ - if (err == 0) { - size_t j; - for (j = cases[i].outLen; j < sizeof(wpOut); ++j) { - if (wpOut[j] != 0) { - PRINT_ERR_MSG("KDF wrote past requested length at byte %zu", - j); - err = 1; - break; - } - } - } - } - - /* Failure mode: caller's buffer smaller than the requested KDF output. - * The derive itself must fail rather than truncate or overflow, so assert - * on the raw EVP_PKEY_derive() result. */ - if (err == 0) { - int deriveRet = 1; - size_t derivedLen = 0; - - tooSmallLen = sizeof(tooSmallBuf); - err = test_dh_derive_x963_raw(keyA, keyB, "SHA256", 32, NULL, 0, - tooSmallBuf, tooSmallLen, &deriveRet, &derivedLen); - if (err == 0 && deriveRet > 0) { - PRINT_ERR_MSG("DH X963 KDF derive accepted under-sized buffer " - "(ret=%d, len=%zu)", deriveRet, derivedLen); - err = 1; - } - } - - OPENSSL_free(raw); - EVP_PKEY_CTX_free(kgCtx); - EVP_PKEY_free(keyA); - EVP_PKEY_free(keyB); - EVP_PKEY_free(params); - BN_free(p); - BN_free(g); - DH_free(dh); - - return err; -} -#endif /* HAVE_X963_KDF && WP_HAVE_SHA256 */ - /* Pass an oversize OSSL_PARAM (>1024 bytes) for the FFC P component to * EVP_PKEY_fromdata so wp_mp_read_unsigned_bin_le (wp_params.c) hits its * size-guard branch via wp_dh_import_group. Expect failure rather than the diff --git a/test/unit.c b/test/unit.c index be7f72a2..06fd1284 100644 --- a/test/unit.c +++ b/test/unit.c @@ -374,9 +374,6 @@ TEST_CASE test_case[] = { TEST_DECL(test_dh_krb5_keygen, NULL), TEST_DECL(test_dh_pad, NULL), TEST_DECL(test_dh_derive_small_buffer, NULL), -#if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) - TEST_DECL(test_dh_x963_kdf, NULL), -#endif TEST_DECL(test_dh_fromdata_oversize, NULL), TEST_DECL(test_dh_param_check_explicit, NULL), TEST_DECL(test_dh_import_group_no_nul, NULL), diff --git a/test/unit.h b/test/unit.h index 35bc245b..a61fa936 100644 --- a/test/unit.h +++ b/test/unit.h @@ -437,9 +437,6 @@ int test_dh_encoder_import_object(void *data); int test_dh_krb5_keygen(void *data); int test_dh_pad(void *data); int test_dh_derive_small_buffer(void *data); -#if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) -int test_dh_x963_kdf(void *data); -#endif int test_dh_fromdata_oversize(void *data); int test_dh_param_check_explicit(void *data); int test_dh_import_group_no_nul(void *data); From cc2dc70196d984848b87925f279d669e5b5be9c4 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 20 Oct 2026 13:32:58 -0700 Subject: [PATCH 3/4] aes: answer key wrap output size queries The default provider returns the wrapped/unwrapped length when EVP_CipherUpdate gets a NULL output. CMS key agreement (cms_kari.c) relies on it. --- src/wp_aes_wrap.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/wp_aes_wrap.c b/src/wp_aes_wrap.c index 26a8e5ea..56f7e4f9 100644 --- a/src/wp_aes_wrap.c +++ b/src/wp_aes_wrap.c @@ -331,6 +331,7 @@ static int wp_aes_wrap_dinit(wp_AesWrapCtx *ctx, const unsigned char *key, * * @param [in] ctx AES wrap context object. * @param [out] out Buffer to hold encrypted/decrypted result. + * May be NULL to get the output length. * @param [out] outLen Length of encrypted/decrypted data in bytes. * @param [in] outSize Size of output buffer in bytes. * @param [in] in Data to encrypt/decrypt. @@ -355,6 +356,20 @@ static int wp_aes_wrap_update(wp_AesWrapCtx *ctx, unsigned char *out, if (ok && (inLen == 0)) { *outLen = 0; } + else if (ok && (out == NULL)) { + /* Size query. */ + if ((inLen < 2 * KEYWRAP_BLOCK_SIZE) || + ((inLen % KEYWRAP_BLOCK_SIZE) != 0)) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_INPUT_LENGTH); + ok = 0; + } + else if (ctx->wrap) { + *outLen = inLen + KEYWRAP_BLOCK_SIZE; + } + else { + *outLen = inLen - KEYWRAP_BLOCK_SIZE; + } + } else if (ok) { int rc; word32 outSz = (word32)outSize; From 2c930c27aeb2c1fd26561187c0f7e777bf5421b7 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 20 Oct 2026 13:32:58 -0700 Subject: [PATCH 4/4] test: CMS EC key agreement round trip against default Encrypt/decrypt with an EC recipient across wolfProvider and the default provider (ECDH X9.63 KDF and AES key wrap). --- test/test_ecc.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 5 +++ test/unit.h | 4 +++ 3 files changed, 97 insertions(+) diff --git a/test/test_ecc.c b/test/test_ecc.c index 798d2770..31eaf28c 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include @@ -1424,6 +1426,92 @@ int test_ecdh_x963_kdf(void *data) return err; } #endif /* HAVE_X963_KDF && WP_HAVE_SHA256 */ + +#if defined(HAVE_X963_KDF) && defined(HAVE_AES_KEYWRAP) && \ + defined(WP_HAVE_AESCBC) && defined(WP_HAVE_SHA1) && !defined(OPENSSL_NO_CMS) +/* CMS EC recipient (cms_kari.c): encrypt in encCtx, decrypt in decCtx. */ +static int test_ecdh_cms_kari_dir(OSSL_LIB_CTX *encCtx, OSSL_LIB_CTX *decCtx) +{ + static const char msg[] = "CMS EC key agreement"; + int err; + const unsigned char *p = ecc_key_der_256; + X509 *cert = NULL; + EVP_PKEY *key = NULL; + STACK_OF(X509) *recips = NULL; + EVP_CIPHER *cipher = NULL; + CMS_ContentInfo *cms = NULL; + CMS_ContentInfo *dec = NULL; + BIO *in = NULL; + BIO *der = NULL; + BIO *out = NULL; + char *outData = NULL; + + in = BIO_new_file(CERTS_DIR "/server-ecc.pem", "r"); + cert = X509_new_ex(encCtx, NULL); + err = (in == NULL) || (cert == NULL) || + (PEM_read_bio_X509(in, &cert, NULL, NULL) == NULL); + BIO_free(in); + in = NULL; + if (err == 0) { + recips = sk_X509_new_null(); + err = (recips == NULL) || (sk_X509_push(recips, cert) != 1); + } + if (err == 0) { + cipher = EVP_CIPHER_fetch(encCtx, "AES-128-CBC", NULL); + in = BIO_new_mem_buf(msg, sizeof(msg) - 1); + der = BIO_new(BIO_s_mem()); + out = BIO_new(BIO_s_mem()); + err = (cipher == NULL) || (in == NULL) || (der == NULL) || + (out == NULL); + } + if (err == 0) { + cms = CMS_encrypt_ex(recips, in, cipher, CMS_BINARY, encCtx, NULL); + err = (cms == NULL) || (i2d_CMS_bio(der, cms) != 1); + } + if (err == 0) { + key = d2i_PrivateKey_ex(EVP_PKEY_EC, NULL, &p, + sizeof(ecc_key_der_256), decCtx, NULL); + dec = CMS_ContentInfo_new_ex(decCtx, NULL); + err = (key == NULL) || (dec == NULL) || + (d2i_CMS_bio(der, &dec) == NULL); + } + if (err == 0) { + err = CMS_decrypt(dec, key, NULL, NULL, out, CMS_BINARY) != 1; + } + if (err == 0) { + err = (BIO_get_mem_data(out, &outData) != (long)(sizeof(msg) - 1)) || + (memcmp(outData, msg, sizeof(msg) - 1) != 0); + } + + BIO_free(out); + BIO_free(der); + BIO_free(in); + CMS_ContentInfo_free(dec); + CMS_ContentInfo_free(cms); + EVP_CIPHER_free(cipher); + sk_X509_free(recips); + EVP_PKEY_free(key); + X509_free(cert); + + return err; +} + +int test_ecdh_cms_kari(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("CMS EC recipient: wolfProvider encrypt, OpenSSL decrypt"); + err = test_ecdh_cms_kari_dir(wpLibCtx, osslLibCtx); + if (err == 0) { + PRINT_MSG("CMS EC recipient: OpenSSL encrypt, wolfProvider decrypt"); + err = test_ecdh_cms_kari_dir(osslLibCtx, wpLibCtx); + } + + return err; +} +#endif /* HAVE_X963_KDF && HAVE_AES_KEYWRAP && WP_HAVE_AESCBC ... */ #endif /* WP_HAVE_EC_P256 */ #ifdef WP_HAVE_EC_P192 diff --git a/test/unit.c b/test/unit.c index 06fd1284..e6ec3457 100644 --- a/test/unit.c +++ b/test/unit.c @@ -482,6 +482,11 @@ TEST_CASE test_case[] = { #if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) TEST_DECL(test_ecdh_x963_kdf, NULL), #endif + #if defined(HAVE_X963_KDF) && defined(HAVE_AES_KEYWRAP) && \ + defined(WP_HAVE_AESCBC) && defined(WP_HAVE_SHA1) && \ + !defined(OPENSSL_NO_CMS) + TEST_DECL(test_ecdh_cms_kari, NULL), + #endif #endif #ifdef WP_HAVE_ECDSA TEST_DECL(test_ecdsa_p256_pkey, NULL), diff --git a/test/unit.h b/test/unit.h index a61fa936..e25ccce4 100644 --- a/test/unit.h +++ b/test/unit.h @@ -542,6 +542,10 @@ int test_ecdh_p256(void *data); #if defined(HAVE_X963_KDF) && defined(WP_HAVE_SHA256) int test_ecdh_x963_kdf(void *data); #endif +#if defined(HAVE_X963_KDF) && defined(HAVE_AES_KEYWRAP) && \ + defined(WP_HAVE_AESCBC) && defined(WP_HAVE_SHA1) && !defined(OPENSSL_NO_CMS) +int test_ecdh_cms_kari(void *data); +#endif #endif /* WP_HAVE_EC_P256 */ #ifdef WP_HAVE_EC_P384 int test_ecdh_p384(void *data);