Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .wolfssl_known_macro_extras
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,7 @@ WOLFSSL_NO_DH186
WOLFSSL_NO_DILITHIUM_LEGACY_GATES
WOLFSSL_NO_DILITHIUM_LEGACY_NAMES
WOLFSSL_NO_DTLS_SIZE_CHECK
WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE
WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK
WOLFSSL_NO_ETM_ALERT
WOLFSSL_NO_FENCE
Expand Down
12 changes: 12 additions & 0 deletions doc/dox_comments/header_files/asn_public.h
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,18 @@ int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz);
input, parses the private key, and uses it to generate an ecc_key object,
which it stores in key.

When the encoding carries only the private scalar, the public point is
derived on a best-effort basis; a failed derivation is logged but does not
fail the decode. Observable effects of a successful derivation: key->type
becomes ECC_PRIVATEKEY rather than ECC_PRIVATEKEY_ONLY, key->pubkey is
populated, and the decode costs an additional base-point scalar multiply.
Under ECC_TIMING_RESISTANT that multiply is blinded with the key's rng when
one was set via wc_ecc_set_rng() beforehand; no rng is created for the
derivation, so with none set the projective-coordinate randomization is
simply skipped. No derivation is attempted for a key with a devId set.
Define WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE to disable the derivation
entirely.

\return 0 On successfully decoding the private key and storing the result
in the ecc_key struct
\return ASN_PARSE_E: Returned if there is an error parsing the der file
Expand Down
51 changes: 51 additions & 0 deletions doc/dox_comments/header_files/wc_mldsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,31 @@ int wc_MlDsaKey_MakeKey(wc_MlDsaKey* key, WC_RNG* rng);
*/
int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed);

/*!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 [Low] Doxygen for the new/changed ML-DSA APIs is inconsistent with the implemented return codes
🔧 NIT style

Two small doc mismatches. (1) The new wc_MlDsaKey_MakePublicKey block lists 0 / BAD_FUNC_ARG / MEMORY_E / Other negative but omits PUBLIC_KEY_E, which is the function's most interesting documented failure (t0 or tr mismatch, wc_mldsa.c:11634 and :11650) — and every export function's block in the same file does list it. (2) The wc_MlDsaKey_PrivateKeyDecode addition says the public key is "derived best-effort on demand by export functions", but the export functions now hard-fail with PUBLIC_KEY_E/MEMORY_E when derivation fails (as those same doc blocks state); "best-effort" describes the ECC path, not this one, and reads as a contradiction.

Suggestion:

Suggested change
/*!
\return 0 on success or already set.
\return BAD_FUNC_ARG if invalid args, or the public key is not yet set
and key has a devId set.
\return MEMORY_E on allocation failure.
\return PUBLIC_KEY_E if the derived public key does not match the t0
and tr values stored in the private key.
\return Other negative on error.

Recommendation: Add PUBLIC_KEY_E to the MakePublicKey return list, and reword the PrivateKeyDecode note to "public key left unset; derived on demand by the export functions, which fail if derivation fails."

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed as recommended

\ingroup ML_DSA

\brief Derives public key for a wc_MlDsaKey with private key set.
No-op if already set.

Derives in software, so it is not supported on keys with a devId set,
unless the public key is already set - in that case this is a no-op
regardless of devId.

\return 0 on success or already set.
\return BAD_FUNC_ARG if invalid args, or the public key is not yet set
and key has a devId set.
\return MEMORY_E on allocation failure.
\return PUBLIC_KEY_E if the derived public key does not match the t0 or
tr values stored in the private key.
\return Other negative on error.

\param [in,out] key Pointer to wc_MlDsaKey.

\sa wc_MlDsaKey_ImportPrivRaw
\sa wc_MlDsaKey_MakeKey
*/
int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key);

/*!
\ingroup ML_DSA

Expand Down Expand Up @@ -453,6 +478,7 @@ int wc_MlDsaKey_SignWithSeed(wc_MlDsaKey* key, byte* sig, word32* sigLen,
\return 0 if verification completed (check res for the result).
\return BAD_FUNC_ARG if any required pointer is NULL or ctxLen is
invalid.
\return PUBLIC_KEY_E when the public key is not set.

\param [in,out] key Pointer to a wc_MlDsaKey with the public key.
\param [in] sig Signature bytes to verify.
Expand Down Expand Up @@ -480,6 +506,7 @@ int wc_MlDsaKey_VerifyCtx(wc_MlDsaKey* key, const byte* sig, word32 sigLen,
\return 0 if verification completed (check res for the result).
\return BAD_FUNC_ARG if any required pointer is NULL, ctxLen is
invalid, or hashAlg is unsupported.
\return PUBLIC_KEY_E when the public key is not set.

\param [in,out] key Pointer to a wc_MlDsaKey with the public key.
\param [in] sig Signature bytes to verify.
Expand Down Expand Up @@ -508,6 +535,7 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen,
\return 0 if verification completed (check res for the result).
\return BAD_FUNC_ARG if any required pointer is NULL or muLen is
not 64.
\return PUBLIC_KEY_E when the public key is not set.

\param [in,out] key Pointer to a wc_MlDsaKey with the public key.
\param [in] sig Signature bytes to verify.
Expand Down Expand Up @@ -756,9 +784,15 @@ int wc_MlDsaKey_ImportKey(wc_MlDsaKey* key, const byte* priv, word32 privSz,
\brief Exports the raw ML-DSA public key. On entry *outLen is the
size of out; on success it is updated to the bytes written.

If only the private key is set, the public key is derived and cached
in key. Don't share key across threads during this call.

\return 0 on success.
\return BAD_FUNC_ARG if any required pointer is NULL.
\return BUFFER_E if *outLen is smaller than the public key size.
\return MEMORY_E if deriving the public key fails to allocate.
\return PUBLIC_KEY_E if deriving the public key fails to verify
against the private key.

\param [in] key Pointer to a wc_MlDsaKey with a public key.
\param [out] out Buffer that receives the public key.
Expand Down Expand Up @@ -818,6 +852,10 @@ int wc_MlDsaKey_ExportKey(wc_MlDsaKey* key, byte* priv, word32 *privSz,

Only available when WOLFSSL_MLDSA_NO_ASN1 is not defined.

For a private-only encoding the public key is left unset. The export
functions derive it on demand, and fail with PUBLIC_KEY_E or MEMORY_E if
that derivation does not succeed.

\return 0 on success.
\return BAD_FUNC_ARG if any required pointer is NULL.
\return ASN_PARSE_E on malformed encoding.
Expand All @@ -830,6 +868,7 @@ int wc_MlDsaKey_ExportKey(wc_MlDsaKey* key, byte* priv, word32 *privSz,

\sa wc_MlDsaKey_PrivateKeyToDer
\sa wc_MlDsaKey_PublicKeyDecode
\sa wc_MlDsaKey_MakePublicKey
*/
int wc_MlDsaKey_PrivateKeyDecode(wc_MlDsaKey* key, const byte* input,
word32 inSz, word32* inOutIdx);
Expand Down Expand Up @@ -867,11 +906,17 @@ int wc_MlDsaKey_PublicKeyDecode(wc_MlDsaKey* key, const byte* input,

Pass NULL as output to query the required buffer size.

If only the private key is set, the public key is derived and cached
in key. Don't share key across threads during this call.

\return Size of the encoded DER in bytes on success.
\return BAD_FUNC_ARG if key is NULL or no parameter set is
selected.
\return BUFFER_E if output is non-NULL and inLen is smaller than
the required size.
\return MEMORY_E if deriving the public key fails to allocate.
\return PUBLIC_KEY_E if deriving the public key fails to verify
against the private key.

\param [in] key Pointer to a wc_MlDsaKey with a public key.
\param [out] output Buffer that receives the DER encoding, or
Expand All @@ -893,11 +938,17 @@ int wc_MlDsaKey_PublicKeyToDer(wc_MlDsaKey* key, byte* output,
PKCS#8 OneAsymmetricKey structure. Pass NULL as output to query
the required buffer size.

If only the private key is set, the public key is derived and cached
in key. Don't share key across threads during this call.

\return Size of the encoded DER in bytes on success.
\return BAD_FUNC_ARG if key is NULL or no parameter set is
selected.
\return MISSING_KEY if the private key has not been set.
\return BUFFER_E if output is non-NULL and inLen is too small.
\return MEMORY_E if deriving the public key fails to allocate.
\return PUBLIC_KEY_E if deriving the public key fails to verify
against the private key.

\param [in] key Pointer to a wc_MlDsaKey with the private key.
\param [out] output Buffer that receives the DER encoding, or
Expand Down
9 changes: 6 additions & 3 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -33090,10 +33090,13 @@ static int DecodePrivateKey_ex(WOLFSSL *ssl, byte keyType, const DerBuffer* key,

/* Set start of data to beginning of buffer. */
idx = 0;
/* Decode the key assuming it is an ECC private key. */
ret = wc_EccPrivateKeyDecode(key->buffer, &idx,
/* Decode the key assuming it is an ECC private key. Skip the
* best-effort public point derivation: this key is only ever used to

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚪ [Info] Comment claims handshake ECC key is sign-only; it also serves static ECDH · Logic errors

The new comment justifying derivePub = 0 states this key "is only ever used to sign", but the same decoded ssl->hsKey is used as the static-ECDH private key at src/internal.c:44431. Behaviour is unaffected (wc_ecc_shared_secret() accepts ECC_PRIVATEKEY_ONLY), but the stated rationale is wrong and could mislead later changes.

Fix: Extend the comment to mention the static-ECDH use and that wc_ecc_shared_secret() accepts an ECC_PRIVATEKEY_ONLY key.

* sign (CertificateVerify / ServerKeyExchange), which needs the
* private scalar alone, and this runs once per handshake. */
ret = EccPrivateKeyDecodeEx(key->buffer, &idx,
(ecc_key*)*hsKey,
key->length);
key->length, 0);
#ifdef WOLF_PRIVATE_KEY_ID
/* if using external key then allow using a public key */
if (ret != 0 && (ssl->devId != INVALID_DEVID
Expand Down
8 changes: 6 additions & 2 deletions src/sniffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2836,8 +2836,12 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session,
#endif
if (ret == 0) {
idx = 0;
ret = wc_EccPrivateKeyDecode(args->keyBuf->buffer, &idx,
&args->key->priv.ecc, args->keyBuf->length);
/* Skip the best-effort public point derivation done on
* decode: this key only ever feeds wc_ecc_shared_secret(),
* which accepts an ECC_PRIVATEKEY_ONLY key, and this runs
* once per sniffed session. */
ret = EccPrivateKeyDecodeEx(args->keyBuf->buffer, &idx,
&args->key->priv.ecc, args->keyBuf->length, 0);
if (ret != 0) {
SetError(ECC_DECODE_STR, error, session, FATAL_ERROR_STATE);
}
Expand Down
7 changes: 5 additions & 2 deletions src/ssl_api_hs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1263,8 +1263,11 @@ int wolfSSL_SetHsDoneCb(WOLFSSL* ssl, HandShakeDoneCb cb, void* user_ctx)
#endif
if (ssl->options.haveStaticECC && ssl->buffers.key != NULL) {
if (wc_ecc_init(key) >= 0) {
if (wc_EccPrivateKeyDecode(ssl->buffers.key->buffer, &idx,
key, ssl->buffers.key->length) != 0) {
/* Pure type/success probe - key is freed right below, so
* skip the best-effort public point derivation done on
* decode. */
if (EccPrivateKeyDecodeEx(ssl->buffers.key->buffer, &idx,
key, ssl->buffers.key->length, 0) != 0) {
ssl->options.haveECDSAsig = 0;
ssl->options.haveECC = 0;
ssl->options.haveStaticECC = 0;
Expand Down
8 changes: 7 additions & 1 deletion src/ssl_api_pk.c
Original file line number Diff line number Diff line change
Expand Up @@ -2290,6 +2290,10 @@ int wolfSSL_StaticEphemeralKeyLoad(WOLFSSL* ssl, int keyAlgo, void* keyPtr)
if (der != NULL) {
ecc_key* key = (ecc_key*)keyPtr;
WOLFSSL_MSG("Using static ECDH key");
/* Keep the best-effort public point derivation: the caller
* (TLSX_KeyShare_GenEccKey) exports this key's public point
* with wc_ecc_export_x963(), which fails ECC_PRIVATEONLY_E on
* an ECC_PRIVATEKEY_ONLY key. */
ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 [Low] Static ephemeral ECDH public-point derivation runs without scalar-mult blinding · Constant-time violations

This branch deliberately keeps the new decode-time wc_ecc_make_pub_ex() derivation, but no RNG is ever attached to key (callers such as TLSX_KeyShare_GenEccKey only wc_ecc_init_ex() it), so EccDerivePubBestEffort() passes key->rng == NULL and ecc_mulmod() skips projective-coordinate randomization for a base-point multiply of the long-lived static private scalar, repeated on every handshake.

Fix: Call wc_ecc_set_rng(key, ssl->rng) before the decode under ECC_TIMING_RESISTANT, mirroring the X25519 branch below and SetupKeys() in sniffer.c.

der->length);
}
Expand Down Expand Up @@ -2370,7 +2374,9 @@ static int DetectStaticEphemeralKeyType(const byte* keyBuf, unsigned int keySz,
ret = wc_ecc_init_ex(eccKey, heap, INVALID_DEVID);
}
if (ret == 0) {
ret = wc_EccPrivateKeyDecode(keyBuf, &idx, eccKey, keySz);
/* Pure type probe - key is freed right below, so skip the
* best-effort public point derivation done on decode. */
ret = EccPrivateKeyDecodeEx(keyBuf, &idx, eccKey, keySz, 0);
if (ret == 0) {
*keyAlgo = WC_PK_TYPE_ECDH;
}
Expand Down
6 changes: 4 additions & 2 deletions src/ssl_load.c
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,11 @@ static int ProcessBufferTryDecodeEcc(WOLFSSL_CTX* ctx, WOLFSSL* ssl,

/* Initialize ECC key. */
if (wc_ecc_init_ex(key, heap, devId) == 0) {
/* Decode as an ECC private key. */
/* Decode as an ECC private key. Skip the best-effort public point
* derivation - this only probes for the key format and size, and the
* key is freed below. */
idx = 0;
ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key, der->length);
ret = EccPrivateKeyDecodeEx(der->buffer, &idx, key, der->length, 0);
#ifdef WOLF_PRIVATE_KEY_ID
/* If that didn't work then maybe a public key if device ID or callback.
*/
Expand Down
Loading