-
Notifications
You must be signed in to change notification settings - Fork 1k
Fix ML-DSA MakePublicKey derivation, verify guards, and ASN derivation testing #10985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
39ca493
f3f7c95
ddc7fe5
e5044af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2781,3 +2781,248 @@ int test_wc_AsnFeatureCoverage(void) | |||||||||||||||||||
| #endif /* !NO_ASN && HAVE_ECC && USE_CERT_BUFFERS_256 && !HAVE_FIPS */ | ||||||||||||||||||||
| return EXPECT_RESULT(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 [Medium] test_wc_EccPrivateKeyDecode_derive_pub guard omits !defined(WC_NO_RNG) and asserts behaviour the code deliberately skips there Two independent failures in a Suggestion:
Suggested change
Recommendation: Add
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] ECC OOM guard omits WOLFSSL_MEM_FAIL_COUNT / WOLFSSL_FORCE_MALLOC_FAIL_TEST exclusions the ML-DSA harness has The ML-DSA OOM harness added in the same diff excludes both macros (tests/api/test_mldsa.c:8154-8161 and :8295-8301) because each injects its own allocation failures inside Also raised by the The new ECC allocation-failure loop installs its own allocators and calibrates a The parallel ML-DSA test added by the same PR (
Either one makes the calibration assertion Suggestion:
Suggested change
Recommendation: Add |
||||||||||||||||||||
| !defined(WOLFSSL_STATIC_MEMORY) && !defined(NO_ASN) && \ | ||||||||||||||||||||
| defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \ | ||||||||||||||||||||
| defined(HAVE_ECC_KEY_EXPORT) && \ | ||||||||||||||||||||
| defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \ | ||||||||||||||||||||
| !defined(HAVE_SELFTEST) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM) | ||||||||||||||||||||
| /* Fail Nth alloc to target public key derive. */ | ||||||||||||||||||||
| static int ecc_oom_failed = 0; | ||||||||||||||||||||
| static int ecc_oom_inject = 0; | ||||||||||||||||||||
| static int ecc_oom_count = 0; | ||||||||||||||||||||
| static int ecc_oom_fail_at = 0; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Custom malloc for testing OOM. */ | ||||||||||||||||||||
| #ifdef WOLFSSL_DEBUG_MEMORY | ||||||||||||||||||||
| static void* ecc_oom_malloc_cb(size_t size, const char* func, | ||||||||||||||||||||
| unsigned int line) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| (void)func; | ||||||||||||||||||||
| (void)line; | ||||||||||||||||||||
| #else | ||||||||||||||||||||
| static void* ecc_oom_malloc_cb(size_t size) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
| if (ecc_oom_inject) { | ||||||||||||||||||||
| ecc_oom_count++; | ||||||||||||||||||||
| if (!ecc_oom_failed && | ||||||||||||||||||||
| (ecc_oom_fail_at != 0) && (ecc_oom_count == ecc_oom_fail_at)) { | ||||||||||||||||||||
| ecc_oom_failed = 1; | ||||||||||||||||||||
| return NULL; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return malloc(size); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Custom free for testing OOM. */ | ||||||||||||||||||||
| #ifdef WOLFSSL_DEBUG_MEMORY | ||||||||||||||||||||
| static void ecc_oom_free_cb(void* ptr, const char* func, unsigned int line) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| (void)func; | ||||||||||||||||||||
| (void)line; | ||||||||||||||||||||
| #else | ||||||||||||||||||||
| static void ecc_oom_free_cb(void* ptr) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
| free(ptr); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Custom realloc for testing OOM. */ | ||||||||||||||||||||
| #ifdef WOLFSSL_DEBUG_MEMORY | ||||||||||||||||||||
| static void* ecc_oom_realloc_cb(void* ptr, size_t size, const char* func, | ||||||||||||||||||||
| unsigned int line) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| (void)func; | ||||||||||||||||||||
| (void)line; | ||||||||||||||||||||
| #else | ||||||||||||||||||||
| static void* ecc_oom_realloc_cb(void* ptr, size_t size) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
| return realloc(ptr, size); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| #endif /* USE_WOLFSSL_MEMORY && ... */ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Decode should best-effort derive omitted SEC1 public point. */ | ||||||||||||||||||||
| int test_wc_EccPrivateKeyDecode_derive_pub(void) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| EXPECT_DECLS; | ||||||||||||||||||||
| #if !defined(NO_ASN) && defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \ | ||||||||||||||||||||
|
Frauschi marked this conversation as resolved.
|
||||||||||||||||||||
| !defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \ | ||||||||||||||||||||
| defined(HAVE_ECC_KEY_EXPORT) && \ | ||||||||||||||||||||
| defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \ | ||||||||||||||||||||
| !defined(HAVE_SELFTEST) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM) | ||||||||||||||||||||
| ecc_key fullKey; | ||||||||||||||||||||
| ecc_key privOnlyKey; | ||||||||||||||||||||
| WC_RNG rng; | ||||||||||||||||||||
| word32 idx; | ||||||||||||||||||||
| byte privOnlyDer[256]; | ||||||||||||||||||||
| int privOnlyDerSz = 0; | ||||||||||||||||||||
| byte fullPub[256]; | ||||||||||||||||||||
| word32 fullPubSz = sizeof(fullPub); | ||||||||||||||||||||
| byte derivedPub[256]; | ||||||||||||||||||||
| word32 derivedPubSz = sizeof(derivedPub); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| XMEMSET(&fullKey, 0, sizeof(fullKey)); | ||||||||||||||||||||
| XMEMSET(&privOnlyKey, 0, sizeof(privOnlyKey)); | ||||||||||||||||||||
| /* wc_FreeRng() below runs unconditionally, so rng must be safe to free | ||||||||||||||||||||
| * even if wc_InitRng() fails. */ | ||||||||||||||||||||
| XMEMSET(&rng, 0, sizeof(rng)); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ExpectIntEQ(wc_InitRng(&rng), 0); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ExpectIntEQ(wc_ecc_init(&fullKey), 0); | ||||||||||||||||||||
| idx = 0; | ||||||||||||||||||||
| ExpectIntEQ(wc_EccPrivateKeyDecode(ecc_clikey_der_256, &idx, &fullKey, | ||||||||||||||||||||
| sizeof_ecc_clikey_der_256), 0); | ||||||||||||||||||||
| ExpectIntEQ(fullKey.type, ECC_PRIVATEKEY); | ||||||||||||||||||||
| PRIVATE_KEY_UNLOCK(); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_export_x963(&fullKey, fullPub, &fullPubSz), 0); | ||||||||||||||||||||
| PRIVATE_KEY_LOCK(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Re-encode as private-key-only SEC1 DER. */ | ||||||||||||||||||||
| ExpectIntGT(privOnlyDerSz = wc_EccPrivateKeyToDer(&fullKey, privOnlyDer, | ||||||||||||||||||||
| sizeof(privOnlyDer)), 0); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* No RNG set: derivation still runs, blinding with a temporary RNG when | ||||||||||||||||||||
| * ECC_TIMING_RESISTANT is on. */ | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); | ||||||||||||||||||||
| idx = 0; | ||||||||||||||||||||
| ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, | ||||||||||||||||||||
| (word32)privOnlyDerSz), 0); | ||||||||||||||||||||
| ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY); | ||||||||||||||||||||
| PRIVATE_KEY_UNLOCK(); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz), | ||||||||||||||||||||
| 0); | ||||||||||||||||||||
| PRIVATE_KEY_LOCK(); | ||||||||||||||||||||
| ExpectIntEQ(derivedPubSz, fullPubSz); | ||||||||||||||||||||
| ExpectBufEQ(derivedPub, fullPub, fullPubSz); | ||||||||||||||||||||
| wc_ecc_free(&privOnlyKey); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Setting an RNG blinds the scalar mult; same derived point. */ | ||||||||||||||||||||
| derivedPubSz = sizeof(derivedPub); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0); | ||||||||||||||||||||
| idx = 0; | ||||||||||||||||||||
| ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, | ||||||||||||||||||||
| (word32)privOnlyDerSz), 0); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Public point derived, key fully usable. */ | ||||||||||||||||||||
| ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY); | ||||||||||||||||||||
| PRIVATE_KEY_UNLOCK(); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz), | ||||||||||||||||||||
| 0); | ||||||||||||||||||||
| PRIVATE_KEY_LOCK(); | ||||||||||||||||||||
| ExpectIntEQ(derivedPubSz, fullPubSz); | ||||||||||||||||||||
| ExpectBufEQ(derivedPub, fullPub, fullPubSz); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| wc_ecc_free(&privOnlyKey); | ||||||||||||||||||||
| wc_ecc_free(&fullKey); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) | ||||||||||||||||||||
| /* devId key left ECC_PRIVATEKEY_ONLY: device derives it. */ | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_init_ex(&privOnlyKey, NULL, 1), 0); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0); | ||||||||||||||||||||
| idx = 0; | ||||||||||||||||||||
| ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, | ||||||||||||||||||||
| (word32)privOnlyDerSz), 0); | ||||||||||||||||||||
| ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY); | ||||||||||||||||||||
| wc_ecc_free(&privOnlyKey); | ||||||||||||||||||||
| #endif | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ | ||||||||||||||||||||
| !defined(WOLFSSL_STATIC_MEMORY) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| wolfSSL_Malloc_cb prevMalloc = NULL; | ||||||||||||||||||||
| wolfSSL_Free_cb prevFree = NULL; | ||||||||||||||||||||
| wolfSSL_Realloc_cb prevRealloc = NULL; | ||||||||||||||||||||
| int allocatorsSet = 0; | ||||||||||||||||||||
| int totalAllocCount = 0; | ||||||||||||||||||||
| int i; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc), | ||||||||||||||||||||
| 0); | ||||||||||||||||||||
| ExpectIntEQ(wolfSSL_SetAllocators(ecc_oom_malloc_cb, ecc_oom_free_cb, | ||||||||||||||||||||
| ecc_oom_realloc_cb), 0); | ||||||||||||||||||||
| if (EXPECT_SUCCESS()) { | ||||||||||||||||||||
| allocatorsSet = 1; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Count the allocations one decode-with-derive makes. Injection is | ||||||||||||||||||||
| * armed only around the decode so wc_ecc_init() is never starved. */ | ||||||||||||||||||||
| ecc_oom_count = 0; | ||||||||||||||||||||
| ecc_oom_fail_at = 0; | ||||||||||||||||||||
| ecc_oom_failed = 0; | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); | ||||||||||||||||||||
| idx = 0; | ||||||||||||||||||||
| ecc_oom_inject = 1; | ||||||||||||||||||||
| ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, | ||||||||||||||||||||
| (word32)privOnlyDerSz), 0); | ||||||||||||||||||||
| ecc_oom_inject = 0; | ||||||||||||||||||||
| totalAllocCount = ecc_oom_count; | ||||||||||||||||||||
| wc_ecc_free(&privOnlyKey); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* Fail each allocation in turn. Whatever fails, decode must never | ||||||||||||||||||||
| * report a derived public key it does not have: the key comes back | ||||||||||||||||||||
| * either fully derived and correct, or still ECC_PRIVATEKEY_ONLY. */ | ||||||||||||||||||||
| for (i = 1; EXPECT_SUCCESS() && (i <= totalAllocCount); i++) { | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] ECC OOM loop never asserts the injected failure actually fired
Suggestion:
Suggested change
Recommendation: Assert |
||||||||||||||||||||
| int decodeRet; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ecc_oom_count = 0; | ||||||||||||||||||||
| ecc_oom_fail_at = i; | ||||||||||||||||||||
| ecc_oom_failed = 0; | ||||||||||||||||||||
| derivedPubSz = sizeof(derivedPub); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); | ||||||||||||||||||||
| idx = 0; | ||||||||||||||||||||
| ecc_oom_inject = 1; | ||||||||||||||||||||
| decodeRet = wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, | ||||||||||||||||||||
| (word32)privOnlyDerSz); | ||||||||||||||||||||
| ecc_oom_inject = 0; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /* A failure inside the decode itself is fine; only the | ||||||||||||||||||||
| * best-effort derivation is required to be non-fatal. */ | ||||||||||||||||||||
| if (decodeRet == 0) { | ||||||||||||||||||||
| ExpectIntNE(privOnlyKey.type, ECC_PUBLICKEY); | ||||||||||||||||||||
| if (privOnlyKey.type == ECC_PRIVATEKEY) { | ||||||||||||||||||||
| PRIVATE_KEY_UNLOCK(); | ||||||||||||||||||||
| ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, | ||||||||||||||||||||
| &derivedPubSz), 0); | ||||||||||||||||||||
| PRIVATE_KEY_LOCK(); | ||||||||||||||||||||
| ExpectIntEQ(derivedPubSz, fullPubSz); | ||||||||||||||||||||
| ExpectBufEQ(derivedPub, fullPub, fullPubSz); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| else { | ||||||||||||||||||||
| ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| wc_ecc_free(&privOnlyKey); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ecc_oom_inject = 0; | ||||||||||||||||||||
| ecc_oom_fail_at = 0; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (allocatorsSet) { | ||||||||||||||||||||
| (void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| #endif /* USE_WOLFSSL_MEMORY */ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| wc_FreeRng(&rng); | ||||||||||||||||||||
| #endif /* !NO_ASN && HAVE_ECC && !NO_ECC_MAKE_PUB && | ||||||||||||||||||||
| * !WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE && HAVE_ECC_KEY_EXPORT && | ||||||||||||||||||||
| * USE_CERT_BUFFERS_256 && !HAVE_FIPS && !HAVE_SELFTEST && | ||||||||||||||||||||
| * !WOLF_CRYPTO_CB_ONLY_ECC */ | ||||||||||||||||||||
| return EXPECT_RESULT(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
There was a problem hiding this comment.
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
styleTwo small doc mismatches. (1) The new
wc_MlDsaKey_MakePublicKeyblock lists0 / BAD_FUNC_ARG / MEMORY_E / Other negativebut omitsPUBLIC_KEY_E, which is the function's most interesting documented failure (t0 ortrmismatch, wc_mldsa.c:11634 and :11650) — and every export function's block in the same file does list it. (2) Thewc_MlDsaKey_PrivateKeyDecodeaddition says the public key is "derived best-effort on demand by export functions", but the export functions now hard-fail withPUBLIC_KEY_E/MEMORY_Ewhen derivation fails (as those same doc blocks state); "best-effort" describes the ECC path, not this one, and reads as a contradiction.Suggestion:
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."