Skip to content

Commit 0d8acd2

Browse files
committed
evp: fix EVP_PKEY2PKCS8 returning NULL for private-key-only EC keys
When an EC_KEY is created via EC_KEY_new + EC_KEY_set_group + EC_KEY_set_private_key (no public point set), SetECKeyInternal incorrectly marks the internal ecc_key as ECC_PRIVATEKEY (instead of ECC_PRIVATEKEY_ONLY) because pub_key is always non-NULL — EC_KEY_new always allocates it as an empty, zero-initialised EC_POINT. ECC_populate_EVP_PKEY only calls wc_ecc_make_pub for ECC_PRIVATEKEY_ONLY keys, so the zero public-key point was serialised into the DER stored in pkey->pkey.ptr. After commit 929dd99 made wc_ecc_import_x963_ex always pass untrusted=1, the re-decode inside wolfSSL_EVP_PKEY2PKCS8 → wolfSSL_d2i_PrivateKey_EVP correctly rejected that zero point with an on-curve failure, causing EVP_PKEY2PKCS8 to return NULL. Fix: in ECC_populate_EVP_PKEY, also call wc_ecc_make_pub when the key type is ECC_PRIVATEKEY but pubkey.x is zero (meaning the public key was never actually populated). This reconstructs the public key from the private scalar so that the encoded DER contains a valid on-curve point. Reproduces the failure seen in SoftHSM's ECDHTests::testPKCS8 and ECDSATests::testPKCS8 which use exactly this pattern.
1 parent a46a2f6 commit 0d8acd2

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

tests/api.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16265,6 +16265,76 @@ static int test_wolfSSL_BUF(void)
1626516265
return EXPECT_RESULT();
1626616266
}
1626716267

16268+
/* Regression: EVP_PKEY_keygen for EC must populate pkey->pkey.ptr so that
16269+
* EVP_PKEY2PKCS8 can encode the key (reported via SoftHSM ECDHTests::testPKCS8
16270+
* and ECDSATests::testPKCS8 after commit that enabled on-curve validation). */
16271+
static int test_wolfSSL_EVP_PKEY2PKCS8_keygen(void)
16272+
{
16273+
EXPECT_DECLS;
16274+
#if (defined(OPENSSL_ALL) || defined(WOLFSSL_WPAS_SMALL)) && defined(HAVE_ECC)
16275+
EVP_PKEY_CTX *ctx = NULL;
16276+
EVP_PKEY *pkey = NULL;
16277+
PKCS8_PRIV_KEY_INFO *p8 = NULL;
16278+
16279+
ExpectNotNull(ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL));
16280+
ExpectIntGT(EVP_PKEY_keygen_init(ctx), 0);
16281+
ExpectIntGT(EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx,
16282+
NID_X9_62_prime256v1), 0);
16283+
ExpectIntGT(EVP_PKEY_keygen(ctx, &pkey), 0);
16284+
/* pkey->pkey.ptr must be populated so PKEY2PKCS8 can encode the key */
16285+
if (pkey != NULL) {
16286+
ExpectNotNull(pkey->pkey.ptr);
16287+
}
16288+
ExpectNotNull(p8 = EVP_PKEY2PKCS8(pkey));
16289+
16290+
PKCS8_PRIV_KEY_INFO_free(p8);
16291+
EVP_PKEY_free(pkey);
16292+
EVP_PKEY_CTX_free(ctx);
16293+
#endif
16294+
return EXPECT_RESULT();
16295+
}
16296+
16297+
/* Regression test for SoftHSM pattern: EC_KEY created with only group and
16298+
* private scalar set (no public key point), then wrapped in EVP_PKEY and
16299+
* passed to EVP_PKEY2PKCS8. Before the fix, ECC_populate_EVP_PKEY would
16300+
* serialise the zero public-key point that SetECKeyInternal copied from the
16301+
* uninitialised pub_key EC_POINT, and the subsequent re-decode in
16302+
* wolfSSL_EVP_PKEY2PKCS8 would fail the on-curve check. */
16303+
static int test_wolfSSL_EVP_PKEY2PKCS8_priv_only(void)
16304+
{
16305+
EXPECT_DECLS;
16306+
#if (defined(OPENSSL_ALL) || defined(WOLFSSL_WPAS_SMALL)) && defined(HAVE_ECC)
16307+
EC_KEY *full = NULL;
16308+
EC_KEY *priv_only = NULL;
16309+
EVP_PKEY *pkey = NULL;
16310+
PKCS8_PRIV_KEY_INFO *p8 = NULL;
16311+
const BIGNUM *priv_bn = NULL;
16312+
const EC_GROUP *grp = NULL;
16313+
16314+
/* Generate a full keypair to extract the private scalar from. */
16315+
ExpectNotNull(full = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
16316+
ExpectIntEQ(EC_KEY_generate_key(full), WOLFSSL_SUCCESS);
16317+
16318+
/* Build a key with only group + private scalar — no public point. */
16319+
ExpectNotNull(grp = EC_KEY_get0_group(full));
16320+
ExpectNotNull(priv_bn = EC_KEY_get0_private_key(full));
16321+
ExpectNotNull(priv_only = EC_KEY_new());
16322+
ExpectIntEQ(EC_KEY_set_group(priv_only, (EC_GROUP*)grp), WOLFSSL_SUCCESS);
16323+
ExpectIntEQ(EC_KEY_set_private_key(priv_only, priv_bn), WOLFSSL_SUCCESS);
16324+
16325+
/* Wrap in EVP_PKEY and call EVP_PKEY2PKCS8 — must not return NULL. */
16326+
ExpectNotNull(pkey = EVP_PKEY_new());
16327+
ExpectIntEQ(EVP_PKEY_set1_EC_KEY(pkey, priv_only), WOLFSSL_SUCCESS);
16328+
ExpectNotNull(p8 = EVP_PKEY2PKCS8(pkey));
16329+
16330+
PKCS8_PRIV_KEY_INFO_free(p8);
16331+
EVP_PKEY_free(pkey);
16332+
EC_KEY_free(priv_only);
16333+
EC_KEY_free(full);
16334+
#endif
16335+
return EXPECT_RESULT();
16336+
}
16337+
1626816338
static int test_wolfSSL_PKCS8_Compat(void)
1626916339
{
1627016340
EXPECT_DECLS;
@@ -34923,6 +34993,8 @@ TEST_CASE testCases[] = {
3492334993
TEST_DECL(test_wolfSSL_PKCS5),
3492434994

3492534995
/* OpenSSL PKCS8 API test */
34996+
TEST_DECL(test_wolfSSL_EVP_PKEY2PKCS8_keygen),
34997+
TEST_DECL(test_wolfSSL_EVP_PKEY2PKCS8_priv_only),
3492634998
TEST_DECL(test_wolfSSL_PKCS8_Compat),
3492734999
TEST_DECL(test_wolfSSL_PKCS8_d2i),
3492835000

wolfcrypt/src/evp.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3715,6 +3715,10 @@ int wolfSSL_EVP_PKEY_keygen_init(WOLFSSL_EVP_PKEY_CTX *ctx)
37153715
return WOLFSSL_SUCCESS;
37163716
}
37173717

3718+
#ifdef HAVE_ECC
3719+
static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key);
3720+
#endif
3721+
37183722
int wolfSSL_EVP_PKEY_keygen(WOLFSSL_EVP_PKEY_CTX *ctx,
37193723
WOLFSSL_EVP_PKEY **ppkey)
37203724
{
@@ -3769,6 +3773,8 @@ int wolfSSL_EVP_PKEY_keygen(WOLFSSL_EVP_PKEY_CTX *ctx,
37693773
ret = wolfSSL_EC_KEY_generate_key(pkey->ecc);
37703774
if (ret == WOLFSSL_SUCCESS) {
37713775
pkey->ownEcc = 1;
3776+
if (ECC_populate_EVP_PKEY(pkey, pkey->ecc) != WOLFSSL_SUCCESS)
3777+
ret = WOLFSSL_FAILURE;
37723778
}
37733779
}
37743780
break;
@@ -9521,7 +9527,15 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key)
95219527
else
95229528
#endif /* HAVE_PKCS8 */
95239529
{
9524-
if (ecc->type == ECC_PRIVATEKEY_ONLY) {
9530+
if (ecc->type == ECC_PRIVATEKEY_ONLY ||
9531+
(ecc->type == ECC_PRIVATEKEY &&
9532+
mp_iszero(ecc->pubkey.x))) {
9533+
/* Reconstruct public key from private scalar. This covers
9534+
* both ECC_PRIVATEKEY_ONLY keys and ECC_PRIVATEKEY keys whose
9535+
* public-key point was never populated (e.g. when only
9536+
* EC_KEY_set_private_key was called, SetECKeyInternal copies
9537+
* the zero-initialized pub_key point and marks the type as
9538+
* ECC_PRIVATEKEY, leaving pubkey.x == 0). */
95259539
if (wc_ecc_make_pub(ecc, NULL) != MP_OKAY) {
95269540
return WOLFSSL_FAILURE;
95279541
}

0 commit comments

Comments
 (0)