Skip to content

fix: d2i_ECPrivateKey derives public key when absent from DER (ZD-21732)#10362

Open
MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
MarkAtwood:fix/d2i-ecprivatekey-pubkey-derive
Open

fix: d2i_ECPrivateKey derives public key when absent from DER (ZD-21732)#10362
MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
MarkAtwood:fix/d2i-ecprivatekey-pubkey-derive

Conversation

@MarkAtwood
Copy link
Copy Markdown

Summary

RFC 5915 makes the publicKey [1] field optional in ECPrivateKey DER
encoding. When a caller imports a private-only DER (produced by OpenSSL,
mbedTLS, an HSM, or any other implementation that omits the field),
wc_EccPrivateKeyDecode sets type = ECC_PRIVATEKEY_ONLY and leaves
pubkey uninitialised. Every downstream operation — ECDSA sign, ECDH,
key export — then runs against uninitialised memory, producing wrong
output or a crash.

Fix: after wc_EccPrivateKeyDecode succeeds, check for
ECC_PRIVATEKEY_ONLY and call wc_ecc_make_pub(key, NULL) to derive
and cache the public point before SetECKeyExternal runs. This matches
OpenSSL's d2i_ECPrivateKey behaviour.

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_cmp specifically by deriving
the 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_pubkey in tests/api/test_ossl_ec.c:

  • Input: hardcoded RFC 5915 private-only P-256 DER (no publicKey field),
    test vector generated by pyca/cryptography and cross-checked with OpenSSL
  • Checks EC_KEY_check_key passes (public point on curve, priv/pub consistent)
  • Exports public key via i2o_ECPublicKey and byte-compares against
    oracle-computed expected value — independent of the code under test
  • Exercises ECDSA sign + verify with the derived key

Full ./wolfcrypt/test/testwolfcrypt and ./tests/unit.test pass.

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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_ECPrivateKey when the decoded key is ECC_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.

Comment thread tests/api/test_ossl_ec.c
Comment on lines +1683 to +1686
p = pub;
ExpectIntEQ(i2o_ECPublicKey(key, &p), pubLen);
ExpectIntEQ(XMEMCMP(pub, kExpectedPub, (word32)pubLen), 0);
XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER);
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Comment thread src/pk_ec.c
Comment on lines +3436 to +3443
* 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;
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
* 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

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #10362

Scan targets checked: wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@MarkAtwood MarkAtwood changed the title fix: d2i_ECPrivateKey derives public key when absent from DER fix: d2i_ECPrivateKey derives public key when absent from DER (ZD-21732) Apr 30, 2026
@github-actions
Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants