fix: d2i_ECPrivateKey derives public key when absent from DER (ZD-21732)#10362
fix: d2i_ECPrivateKey derives public key when absent from DER (ZD-21732)#10362MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
Conversation
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: wolfSSL#9987
There was a problem hiding this comment.
Pull request overview
Fixes EC private-key import from RFC 5915 DER when the optional publicKey [1] field is omitted, aligning d2i_ECPrivateKey behavior with OpenSSL by deriving the public point after decode so downstream operations don’t operate on an unset/invalid public key.
Changes:
- Derive and cache the EC public key inside
wolfSSL_d2i_ECPrivateKeywhen the decoded key isECC_PRIVATEKEY_ONLY. - Add a new OpenSSL-API test that imports a private-only RFC 5915 EC key and validates derived public key + ECDSA sign/verify.
- Register the new test in the ossl_ec test declarations.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/pk_ec.c |
Derives the public point during d2i_ECPrivateKey import when absent in DER. |
tests/api/test_ossl_ec.c |
Adds coverage for importing private-only RFC 5915 EC keys and validating derived pubkey behavior. |
tests/api/test_ossl_ec.h |
Registers the new test in the ossl_ec test group. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| p = pub; | ||
| ExpectIntEQ(i2o_ECPublicKey(key, &p), pubLen); | ||
| ExpectIntEQ(XMEMCMP(pub, kExpectedPub, (word32)pubLen), 0); | ||
| XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER); |
There was a problem hiding this comment.
If XMALLOC fails, pub becomes NULL but the test still calls i2o_ECPublicKey(key, &p) with p == NULL and then unconditionally calls XFREE(pub, ...). In configs where XFREE(NULL, ...) is not safe (e.g. with WOLFSSL_XFREE_NO_NULLNESS_CHECK), this can crash the test and also potentially dereference a NULL output buffer. Please gate the p = pub, i2o_ECPublicKey, XMEMCMP, and XFREE steps on pub != NULL (or rework to let i2o_ECPublicKey allocate by passing &pub when pub is NULL).
| p = pub; | |
| ExpectIntEQ(i2o_ECPublicKey(key, &p), pubLen); | |
| ExpectIntEQ(XMEMCMP(pub, kExpectedPub, (word32)pubLen), 0); | |
| XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER); | |
| if (pub != NULL) { | |
| p = pub; | |
| ExpectIntEQ(i2o_ECPublicKey(key, &p), pubLen); | |
| ExpectIntEQ(XMEMCMP(pub, kExpectedPub, (word32)pubLen), 0); | |
| XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER); | |
| } |
| * public point uninitialised. Derive the public point now so that | ||
| * all downstream operations (sign, ECDH, export) have a valid key, | ||
| * matching the behaviour of OpenSSL's d2i_ECPrivateKey. */ | ||
| if (((ecc_key*)ret->internal)->type == ECC_PRIVATEKEY_ONLY) { | ||
| if (wc_ecc_make_pub((ecc_key*)ret->internal, NULL) != 0) { | ||
| WOLFSSL_MSG("wc_ecc_make_pub error deriving public key"); | ||
| err = 1; | ||
| } |
There was a problem hiding this comment.
This new unconditional wc_ecc_make_pub() call will make wolfSSL_d2i_ECPrivateKey fail for RFC 5915 private-only keys in builds where HAVE_ECC_MAKE_PUB is not enabled (e.g. some hardware/CB-only configurations), because wc_ecc_make_pub returns NOT_COMPILED_IN there. Previously, such builds would successfully import the private key (even though the pub point was unusable). If that compatibility matters, consider guarding the derivation with HAVE_ECC_MAKE_PUB (or otherwise ensuring the key still imports safely when derivation isn’t available).
| * public point uninitialised. Derive the public point now so that | |
| * all downstream operations (sign, ECDH, export) have a valid key, | |
| * matching the behaviour of OpenSSL's d2i_ECPrivateKey. */ | |
| if (((ecc_key*)ret->internal)->type == ECC_PRIVATEKEY_ONLY) { | |
| if (wc_ecc_make_pub((ecc_key*)ret->internal, NULL) != 0) { | |
| WOLFSSL_MSG("wc_ecc_make_pub error deriving public key"); | |
| err = 1; | |
| } | |
| * public point uninitialised. Derive the public point now when | |
| * supported so that downstream operations have a valid key, matching | |
| * the behaviour of OpenSSL's d2i_ECPrivateKey. In builds without | |
| * HAVE_ECC_MAKE_PUB, keep the historical import behaviour and leave | |
| * the key as private-only instead of failing import. */ | |
| if (((ecc_key*)ret->internal)->type == ECC_PRIVATEKEY_ONLY) { | |
| #ifdef HAVE_ECC_MAKE_PUB | |
| if (wc_ecc_make_pub((ecc_key*)ret->internal, NULL) != 0) { | |
| WOLFSSL_MSG("wc_ecc_make_pub error deriving public key"); | |
| err = 1; | |
| } | |
| #endif |
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10362
Scan targets checked: wolfssl-bugs, wolfssl-src
No new issues found in the changed files. ✅
Summary
RFC 5915 makes the
publicKey [1]field optional inECPrivateKeyDERencoding. When a caller imports a private-only DER (produced by OpenSSL,
mbedTLS, an HSM, or any other implementation that omits the field),
wc_EccPrivateKeyDecodesetstype = ECC_PRIVATEKEY_ONLYand leavespubkeyuninitialised. Every downstream operation — ECDSA sign, ECDH,key export — then runs against uninitialised memory, producing wrong
output or a crash.
Fix: after
wc_EccPrivateKeyDecodesucceeds, check forECC_PRIVATEKEY_ONLYand callwc_ecc_make_pub(key, NULL)to deriveand cache the public point before
SetECKeyExternalruns. This matchesOpenSSL's
d2i_ECPrivateKeybehaviour.Relation to existing work
This is the root-cause fix for the same bug addressed at the symptom
level in #9987 ("evp: fix EVP_PKEY_cmp for EC keys after DER
deserialization"). That PR fixed
EVP_PKEY_cmpspecifically by derivingthe public key at comparison time; this PR fixes the import function
itself so all downstream operations work correctly without per-callsite
workarounds. Supersedes #9987.
Also reported as Zendesk support ticket #21732.
Test
New test
test_d2i_ECPrivateKey_no_pubkeyintests/api/test_ossl_ec.c:publicKeyfield),test vector generated by pyca/cryptography and cross-checked with OpenSSL
EC_KEY_check_keypasses (public point on curve, priv/pub consistent)i2o_ECPublicKeyand byte-compares againstoracle-computed expected value — independent of the code under test
Full
./wolfcrypt/test/testwolfcryptand./tests/unit.testpass.