Skip to content

Commit 89ab411

Browse files
committed
fix: d2i_ECPrivateKey derives public key when absent from DER
RFC 5915 makes the publicKey [1] field optional. When it is absent, wc_EccPrivateKeyDecode sets type = ECC_PRIVATEKEY_ONLY and leaves pubkey uninitialised. Any downstream operation (sign, ECDH, export) then runs against uninitialised memory. After decoding, check for ECC_PRIVATEKEY_ONLY and call wc_ecc_make_pub to derive and cache the public point before SetECKeyExternal runs. This matches OpenSSL d2i_ECPrivateKey behaviour. Add test_d2i_ECPrivateKey_no_pubkey: imports a hardcoded private-only P-256 DER (test vector from pyca/cryptography), checks EC_KEY_check_key, verifies public key bytes against oracle, and exercises ECDSA sign/verify. Fixes: Zendesk #21732 Supersedes: #9987
1 parent d00a137 commit 89ab411

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

src/pk_ec.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3431,6 +3431,20 @@ WOLFSSL_EC_KEY* wolfSSL_d2i_ECPrivateKey(WOLFSSL_EC_KEY** key,
34313431
/* Internal EC key setup. */
34323432
ret->inSet = 1;
34333433

3434+
/* When the RFC 5915 DER encoding omits the optional publicKey field,
3435+
* wc_EccPrivateKeyDecode leaves type == ECC_PRIVATEKEY_ONLY with the
3436+
* public point uninitialised. Derive the public point now so that
3437+
* all downstream operations (sign, ECDH, export) have a valid key,
3438+
* matching the behaviour of OpenSSL's d2i_ECPrivateKey. */
3439+
if (((ecc_key*)ret->internal)->type == ECC_PRIVATEKEY_ONLY) {
3440+
if (wc_ecc_make_pub((ecc_key*)ret->internal, NULL) != 0) {
3441+
WOLFSSL_MSG("wc_ecc_make_pub error deriving public key");
3442+
err = 1;
3443+
}
3444+
}
3445+
}
3446+
3447+
if (!err) {
34343448
/* Set the EC key from the internal values. */
34353449
if (SetECKeyExternal(ret) != 1) {
34363450
WOLFSSL_MSG("SetECKeyExternal error");

tests/api/test_ossl_ec.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,85 @@ int test_ECDH_compute_key(void)
16161616
return EXPECT_RESULT();
16171617
}
16181618

1619+
/* Test that d2i_ECPrivateKey derives the public point when the optional
1620+
* publicKey [1] field is absent from the RFC 5915 DER encoding.
1621+
*
1622+
* Without the fix, wc_EccPrivateKeyDecode sets type = ECC_PRIVATEKEY_ONLY and
1623+
* leaves pubkey uninitialised; every downstream operation (sign, ECDH, export)
1624+
* then runs against uninitialised memory.
1625+
*
1626+
* Test vector produced by pyca/cryptography and cross-checked with OpenSSL:
1627+
* private scalar: 519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464
1628+
* expected pub x: 1ccbe91c075fc7f4f033bfa248db8fccd3565de94bbfb12f3c59ff46c271bf83
1629+
* expected pub y: ce4014c68811f9a21a1fdb2c0e6113e06db7ca93b7404e78dc7ccd5ca89a4ca9
1630+
*/
1631+
int test_d2i_ECPrivateKey_no_pubkey(void)
1632+
{
1633+
EXPECT_DECLS;
1634+
#if defined(OPENSSL_EXTRA) && !defined(NO_ECC256) && !defined(NO_ECC_SECP) && \
1635+
defined(HAVE_ECC_KEY_IMPORT)
1636+
/* RFC 5915 ECPrivateKey DER with version + privateKey + parameters [0]
1637+
* but NO publicKey [1] field. */
1638+
static const byte kPrivOnlyDer[] = {
1639+
0x30, 0x31, /* SEQUENCE (49 bytes) */
1640+
0x02, 0x01, 0x01, /* version = 1 */
1641+
0x04, 0x20, /* privateKey (32 bytes) */
1642+
0x51, 0x9b, 0x42, 0x3d, 0x71, 0x5f, 0x8b, 0x58,
1643+
0x1f, 0x4f, 0xa8, 0xee, 0x59, 0xf4, 0x77, 0x1a,
1644+
0x5b, 0x44, 0xc8, 0x13, 0x0b, 0x4e, 0x3e, 0xac,
1645+
0xca, 0x54, 0xa5, 0x6d, 0xda, 0x72, 0xb4, 0x64,
1646+
0xa0, 0x0a, /* [0] parameters */
1647+
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
1648+
};
1649+
/* Expected uncompressed public key (04 || x || y), oracle: pyca/cryptography */
1650+
static const byte kExpectedPub[] = {
1651+
0x04,
1652+
0x1c, 0xcb, 0xe9, 0x1c, 0x07, 0x5f, 0xc7, 0xf4,
1653+
0xf0, 0x33, 0xbf, 0xa2, 0x48, 0xdb, 0x8f, 0xcc,
1654+
0xd3, 0x56, 0x5d, 0xe9, 0x4b, 0xbf, 0xb1, 0x2f,
1655+
0x3c, 0x59, 0xff, 0x46, 0xc2, 0x71, 0xbf, 0x83,
1656+
0xce, 0x40, 0x14, 0xc6, 0x88, 0x11, 0xf9, 0xa2,
1657+
0x1a, 0x1f, 0xdb, 0x2c, 0x0e, 0x61, 0x13, 0xe0,
1658+
0x6d, 0xb7, 0xca, 0x93, 0xb7, 0x40, 0x4e, 0x78,
1659+
0xdc, 0x7c, 0xcd, 0x5c, 0xa8, 0x9a, 0x4c, 0xa9
1660+
};
1661+
const byte* der = kPrivOnlyDer;
1662+
EC_KEY* key = NULL;
1663+
unsigned char* pub = NULL;
1664+
unsigned char* p = NULL;
1665+
byte hash[32];
1666+
byte sig[ECC_MAX_SIG_SIZE];
1667+
unsigned int sigSz = sizeof(sig);
1668+
int pubLen = 0;
1669+
1670+
XMEMSET(hash, 0xab, sizeof(hash));
1671+
1672+
/* Import private-only DER — must succeed and auto-derive the public key. */
1673+
ExpectNotNull(key = d2i_ECPrivateKey(NULL, &der, sizeof(kPrivOnlyDer)));
1674+
1675+
/* Structural validity: public point on curve, priv/pub consistent. */
1676+
ExpectIntEQ(EC_KEY_check_key(key), 1);
1677+
1678+
/* Public key bytes must match the oracle-computed expected value. */
1679+
ExpectIntEQ((pubLen = i2o_ECPublicKey(key, NULL)), (int)sizeof(kExpectedPub));
1680+
if (EXPECT_SUCCESS()) {
1681+
ExpectNotNull(pub = (unsigned char*)XMALLOC(pubLen, NULL,
1682+
DYNAMIC_TYPE_TMP_BUFFER));
1683+
p = pub;
1684+
ExpectIntEQ(i2o_ECPublicKey(key, &p), pubLen);
1685+
ExpectIntEQ(XMEMCMP(pub, kExpectedPub, (word32)pubLen), 0);
1686+
XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER);
1687+
}
1688+
1689+
/* ECDSA sign + verify must work with the derived public key. */
1690+
ExpectIntEQ(ECDSA_sign(0, hash, sizeof(hash), sig, &sigSz, key), 1);
1691+
ExpectIntEQ(ECDSA_verify(0, hash, sizeof(hash), sig, (int)sigSz, key), 1);
1692+
1693+
EC_KEY_free(key);
1694+
#endif /* OPENSSL_EXTRA && !NO_ECC256 && !NO_ECC_SECP && HAVE_ECC_KEY_IMPORT */
1695+
return EXPECT_RESULT();
1696+
}
1697+
16191698
#endif /* HAVE_ECC && !OPENSSL_NO_PK */
16201699

16211700

tests/api/test_ossl_ec.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ int test_wolfSSL_EC_get_builtin_curves(void);
4444
int test_wolfSSL_ECDSA_SIG(void);
4545
int test_ECDSA_size_sign(void);
4646
int test_ECDH_compute_key(void);
47+
int test_d2i_ECPrivateKey_no_pubkey(void);
4748

4849

4950
#define TEST_OSSL_EC_DECLS \
@@ -64,7 +65,8 @@ int test_ECDH_compute_key(void);
6465
TEST_DECL_GROUP("ossl_ec", test_wolfSSL_EC_get_builtin_curves), \
6566
TEST_DECL_GROUP("ossl_ec", test_wolfSSL_ECDSA_SIG), \
6667
TEST_DECL_GROUP("ossl_ec", test_ECDSA_size_sign), \
67-
TEST_DECL_GROUP("ossl_ec", test_ECDH_compute_key)
68+
TEST_DECL_GROUP("ossl_ec", test_ECDH_compute_key), \
69+
TEST_DECL_GROUP("ossl_ec", test_d2i_ECPrivateKey_no_pubkey)
6870

6971
#endif
7072

0 commit comments

Comments
 (0)