Skip to content

Commit 1ee6dc8

Browse files
author
Emma Stensland
committed
Fix ML-DSA missing public key guards and memory allocation, add ECC pubkey derivation logs
1 parent 4c68523 commit 1ee6dc8

12 files changed

Lines changed: 1334 additions & 221 deletions

File tree

doc/dox_comments/header_files/asn_public.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,11 @@ int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz);
22672267
input, parses the private key, and uses it to generate an ecc_key object,
22682268
which it stores in key.
22692269
2270+
For a private-only encoding (no public point present), the public key is
2271+
derived best-effort as a side effect (a scalar multiplication); failure
2272+
to derive it does not fail the decode. Under ECC_TIMING_RESISTANT, this
2273+
only happens if an RNG was already set on the key via wc_ecc_set_rng().
2274+
22702275
\return 0 On successfully decoding the private key and storing the result
22712276
in the ecc_key struct
22722277
\return ASN_PARSE_E: Returned if there is an error parsing the der file

doc/dox_comments/header_files/wc_mldsa.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,27 @@ int wc_MlDsaKey_MakeKey(wc_MlDsaKey* key, WC_RNG* rng);
257257
*/
258258
int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed);
259259

260+
/*!
261+
\ingroup ML_DSA
262+
263+
\brief Derives and caches the public key (t1 and the public seed)
264+
for a wc_MlDsaKey that already has its private key set, e.g. after
265+
wc_MlDsaKey_ImportPrivRaw() or decoding a private-key-only DER. A
266+
no-op returning 0 if the public key is already set.
267+
268+
\return 0 on success, or if the public key was already set.
269+
\return BAD_FUNC_ARG if key is NULL, key->params is NULL, or the
270+
private key is not set.
271+
\return MEMORY_E on allocation failure.
272+
\return Other negative when an error occurs.
273+
274+
\param [in,out] key Pointer to a wc_MlDsaKey with the private key set.
275+
276+
\sa wc_MlDsaKey_ImportPrivRaw
277+
\sa wc_MlDsaKey_MakeKey
278+
*/
279+
int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key);
280+
260281
/*!
261282
\ingroup ML_DSA
262283
@@ -817,6 +838,10 @@ int wc_MlDsaKey_ExportKey(wc_MlDsaKey* key, byte* priv, word32 *privSz,
817838
818839
Only available when WOLFSSL_MLDSA_NO_ASN1 is not defined.
819840
841+
For a private-only encoding (no public point present), the public
842+
key is derived and cached best-effort as a side effect; failure to
843+
derive it does not fail the decode.
844+
820845
\return 0 on success.
821846
\return BAD_FUNC_ARG if any required pointer is NULL.
822847
\return ASN_PARSE_E on malformed encoding.
@@ -829,6 +854,7 @@ int wc_MlDsaKey_ExportKey(wc_MlDsaKey* key, byte* priv, word32 *privSz,
829854
830855
\sa wc_MlDsaKey_PrivateKeyToDer
831856
\sa wc_MlDsaKey_PublicKeyDecode
857+
\sa wc_MlDsaKey_MakePublicKey
832858
*/
833859
int wc_MlDsaKey_PrivateKeyDecode(wc_MlDsaKey* key, const byte* input,
834860
word32 inSz, word32* inOutIdx);

tests/api/test_asn.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,3 +2481,226 @@ int test_wc_AsnFeatureCoverage(void)
24812481
#endif /* !NO_ASN && HAVE_ECC && USE_CERT_BUFFERS_256 && !HAVE_FIPS */
24822482
return EXPECT_RESULT();
24832483
}
2484+
2485+
#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \
2486+
!defined(WOLFSSL_STATIC_MEMORY) && !defined(NO_ASN) && \
2487+
defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \
2488+
defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \
2489+
!defined(HAVE_SELFTEST) && \
2490+
!defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
2491+
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \
2492+
!defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \
2493+
!defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM)
2494+
/* Fail the Nth allocation (ecc_oom_fail_at) to target public key
2495+
* derivation, skipping earlier, config-dependent allocations. */
2496+
static int ecc_oom_failed = 0;
2497+
static int ecc_oom_inject = 0;
2498+
static int ecc_oom_count = 0;
2499+
static int ecc_oom_fail_at = 0;
2500+
2501+
/* Custom malloc for testing OOM.
2502+
* Returns allocated pointer or NULL on failure. */
2503+
#ifdef WOLFSSL_DEBUG_MEMORY
2504+
static void* ecc_oom_malloc_cb(size_t size, const char* func,
2505+
unsigned int line)
2506+
{
2507+
(void)func;
2508+
(void)line;
2509+
#else
2510+
static void* ecc_oom_malloc_cb(size_t size)
2511+
{
2512+
#endif
2513+
if (ecc_oom_inject) {
2514+
ecc_oom_count++;
2515+
if (!ecc_oom_failed &&
2516+
(ecc_oom_fail_at != 0) && (ecc_oom_count == ecc_oom_fail_at)) {
2517+
ecc_oom_failed = 1;
2518+
return NULL;
2519+
}
2520+
}
2521+
return malloc(size);
2522+
}
2523+
2524+
/* Custom free for testing OOM. */
2525+
#ifdef WOLFSSL_DEBUG_MEMORY
2526+
static void ecc_oom_free_cb(void* ptr, const char* func, unsigned int line)
2527+
{
2528+
(void)func;
2529+
(void)line;
2530+
#else
2531+
static void ecc_oom_free_cb(void* ptr)
2532+
{
2533+
#endif
2534+
free(ptr);
2535+
}
2536+
2537+
/* Custom realloc for testing OOM.
2538+
* Returns reallocated pointer or NULL on failure. */
2539+
#ifdef WOLFSSL_DEBUG_MEMORY
2540+
static void* ecc_oom_realloc_cb(void* ptr, size_t size, const char* func,
2541+
unsigned int line)
2542+
{
2543+
(void)func;
2544+
(void)line;
2545+
#else
2546+
static void* ecc_oom_realloc_cb(void* ptr, size_t size)
2547+
{
2548+
#endif
2549+
return realloc(ptr, size);
2550+
}
2551+
#endif /* USE_WOLFSSL_MEMORY && ... */
2552+
2553+
/* wc_EccPrivateKeyDecode should derive and cache the public point (best
2554+
* effort) when it decodes a SEC1 private key whose optional public point
2555+
* was omitted, so the key comes out fully usable. */
2556+
int test_wc_EccPrivateKeyDecode_derive_pub(void)
2557+
{
2558+
EXPECT_DECLS;
2559+
#if !defined(NO_ASN) && defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \
2560+
defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \
2561+
!defined(HAVE_SELFTEST) && \
2562+
!defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
2563+
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \
2564+
!defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \
2565+
!defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM)
2566+
ecc_key fullKey;
2567+
ecc_key privOnlyKey;
2568+
WC_RNG rng;
2569+
word32 idx;
2570+
byte privOnlyDer[256];
2571+
int privOnlyDerSz = 0;
2572+
byte fullPub[256];
2573+
word32 fullPubSz = sizeof(fullPub);
2574+
byte derivedPub[256];
2575+
word32 derivedPubSz = sizeof(derivedPub);
2576+
2577+
XMEMSET(&fullKey, 0, sizeof(fullKey));
2578+
XMEMSET(&privOnlyKey, 0, sizeof(privOnlyKey));
2579+
2580+
ExpectIntEQ(wc_InitRng(&rng), 0);
2581+
2582+
ExpectIntEQ(wc_ecc_init(&fullKey), 0);
2583+
idx = 0;
2584+
ExpectIntEQ(wc_EccPrivateKeyDecode(ecc_clikey_der_256, &idx, &fullKey,
2585+
sizeof_ecc_clikey_der_256), 0);
2586+
ExpectIntEQ(fullKey.type, ECC_PRIVATEKEY);
2587+
PRIVATE_KEY_UNLOCK();
2588+
ExpectIntEQ(wc_ecc_export_x963(&fullKey, fullPub, &fullPubSz), 0);
2589+
PRIVATE_KEY_LOCK();
2590+
2591+
/* Re-encode as a private-key-only SEC1 DER (no public point). */
2592+
ExpectIntGT(privOnlyDerSz = wc_EccPrivateKeyToDer(&fullKey, privOnlyDer,
2593+
sizeof(privOnlyDer)), 0);
2594+
2595+
#ifdef ECC_TIMING_RESISTANT
2596+
/* Without an RNG set, decode must not derive unblinded: the key is
2597+
* left as ECC_PRIVATEKEY_ONLY, same as before auto-derivation existed. */
2598+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
2599+
idx = 0;
2600+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
2601+
(word32)privOnlyDerSz), 0);
2602+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY);
2603+
wc_ecc_free(&privOnlyKey);
2604+
#endif
2605+
2606+
/* Opting into blinding (as every other blinded ECC op in this library
2607+
* requires) makes decode derive and cache the public point. */
2608+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
2609+
ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0);
2610+
idx = 0;
2611+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
2612+
(word32)privOnlyDerSz), 0);
2613+
2614+
/* The public point should have been derived automatically, making the
2615+
* key fully usable rather than left as ECC_PRIVATEKEY_ONLY. */
2616+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY);
2617+
PRIVATE_KEY_UNLOCK();
2618+
ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz),
2619+
0);
2620+
PRIVATE_KEY_LOCK();
2621+
ExpectIntEQ(derivedPubSz, fullPubSz);
2622+
ExpectBufEQ(derivedPub, fullPub, fullPubSz);
2623+
2624+
wc_ecc_free(&privOnlyKey);
2625+
wc_ecc_free(&fullKey);
2626+
2627+
#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \
2628+
!defined(WOLFSSL_STATIC_MEMORY)
2629+
{
2630+
wolfSSL_Malloc_cb prevMalloc = NULL;
2631+
wolfSSL_Free_cb prevFree = NULL;
2632+
wolfSSL_Realloc_cb prevRealloc = NULL;
2633+
int allocatorsSet = 0;
2634+
int baseAllocCount = 0;
2635+
int totalAllocCount = 0;
2636+
2637+
ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc),
2638+
0);
2639+
ExpectIntEQ(wolfSSL_SetAllocators(ecc_oom_malloc_cb, ecc_oom_free_cb,
2640+
ecc_oom_realloc_cb), 0);
2641+
if (EXPECT_SUCCESS()) {
2642+
allocatorsSet = 1;
2643+
}
2644+
2645+
/* Get baseline allocation count for a decode that skips derivation
2646+
* (ecc_clikey_der_256 already has a public point). */
2647+
ecc_oom_count = 0;
2648+
ecc_oom_fail_at = 0;
2649+
ecc_oom_inject = 1;
2650+
ExpectIntEQ(wc_ecc_init(&fullKey), 0);
2651+
idx = 0;
2652+
ExpectIntEQ(wc_EccPrivateKeyDecode(ecc_clikey_der_256, &idx, &fullKey,
2653+
sizeof_ecc_clikey_der_256), 0);
2654+
ecc_oom_inject = 0;
2655+
wc_ecc_free(&fullKey);
2656+
baseAllocCount = ecc_oom_count;
2657+
2658+
/* Count allocations for a decode that does derive, for comparison. */
2659+
ecc_oom_count = 0;
2660+
ecc_oom_inject = 1;
2661+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
2662+
ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0);
2663+
idx = 0;
2664+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
2665+
(word32)privOnlyDerSz), 0);
2666+
ecc_oom_inject = 0;
2667+
wc_ecc_free(&privOnlyKey);
2668+
totalAllocCount = ecc_oom_count;
2669+
2670+
/* Only derivation allocates more than the baseline -- if it
2671+
* allocates nothing (e.g. stack-only math build) there is nothing
2672+
* to fault-inject into. */
2673+
if (totalAllocCount > baseAllocCount) {
2674+
/* Test that a derivation failure leaves the key as
2675+
* ECC_PRIVATEKEY_ONLY but does not fail the overall decode. */
2676+
ecc_oom_failed = 0;
2677+
ecc_oom_count = 0;
2678+
ecc_oom_fail_at = baseAllocCount + 1;
2679+
ecc_oom_inject = 1;
2680+
2681+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
2682+
ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0);
2683+
2684+
idx = 0;
2685+
/* Decode should succeed, but leave the key as
2686+
* ECC_PRIVATEKEY_ONLY. */
2687+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
2688+
(word32)privOnlyDerSz), 0);
2689+
ExpectIntEQ(ecc_oom_failed, 1);
2690+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY);
2691+
2692+
wc_ecc_free(&privOnlyKey);
2693+
ecc_oom_inject = 0;
2694+
}
2695+
2696+
if (allocatorsSet) {
2697+
(void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc);
2698+
}
2699+
}
2700+
#endif /* USE_WOLFSSL_MEMORY */
2701+
2702+
wc_FreeRng(&rng);
2703+
#endif /* !NO_ASN && HAVE_ECC && !NO_ECC_MAKE_PUB && USE_CERT_BUFFERS_256 &&
2704+
* !HAVE_FIPS && !HAVE_SELFTEST */
2705+
return EXPECT_RESULT();
2706+
}

tests/api/test_asn.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ int test_ToTraditional_ex_negative(void);
4444
int test_ToTraditional_ex_mldsa_bad_params(void);
4545
int test_wc_AsnDecisionCoverage(void);
4646
int test_wc_AsnFeatureCoverage(void);
47+
int test_wc_EccPrivateKeyDecode_derive_pub(void);
4748

4849
#define TEST_ASN_DECLS \
4950
TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \
@@ -65,6 +66,7 @@ int test_wc_AsnFeatureCoverage(void);
6566
TEST_DECL_GROUP("asn", test_ToTraditional_ex_negative), \
6667
TEST_DECL_GROUP("asn", test_ToTraditional_ex_mldsa_bad_params), \
6768
TEST_DECL_GROUP("asn", test_wc_AsnDecisionCoverage), \
68-
TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage)
69+
TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage), \
70+
TEST_DECL_GROUP("asn", test_wc_EccPrivateKeyDecode_derive_pub)
6971

7072
#endif /* WOLFCRYPT_TEST_ASN_H */

0 commit comments

Comments
 (0)