From b6324ad11edec9113cd4d35c70e8e9176073d84f Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Thu, 23 Jul 2026 11:30:54 -0600 Subject: [PATCH] Fix ML-DSA missing public key guards and memory allocation, add ECC pubkey derivation logs --- tests/api/test_asn.c | 60 +++++ tests/api/test_asn.h | 4 +- tests/api/test_mldsa.c | 173 +++++++++++++++ tests/api/test_mldsa.h | 4 + wolfcrypt/src/asn.c | 25 +++ wolfcrypt/src/asn_orig.c | 25 +++ wolfcrypt/src/wc_mldsa.c | 419 ++++++++++++++++++++++++++++++++++- wolfssl/wolfcrypt/wc_mldsa.h | 4 + 8 files changed, 707 insertions(+), 7 deletions(-) diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index ab0644f11c5..1ae0b004c11 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -2481,3 +2481,63 @@ int test_wc_AsnFeatureCoverage(void) #endif /* !NO_ASN && HAVE_ECC && USE_CERT_BUFFERS_256 && !HAVE_FIPS */ return EXPECT_RESULT(); } + +/* wc_EccPrivateKeyDecode should derive and cache the public point (best + * effort) when it decodes a SEC1 private key whose optional public point + * was omitted, so the key comes out fully usable. */ +int test_wc_EccPrivateKeyDecode_derive_pub(void) +{ + EXPECT_DECLS; +#if !defined(NO_ASN) && defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \ + defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \ + !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; + word32 idx; + byte privOnlyDer[256]; + int privOnlyDerSz; + byte fullPub[256]; + word32 fullPubSz = sizeof(fullPub); + byte derivedPub[256]; + word32 derivedPubSz = sizeof(derivedPub); + + XMEMSET(&fullKey, 0, sizeof(fullKey)); + XMEMSET(&privOnlyKey, 0, sizeof(privOnlyKey)); + + 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 a private-key-only SEC1 DER (no public point). */ + ExpectIntGT(privOnlyDerSz = wc_EccPrivateKeyToDer(&fullKey, privOnlyDer, + sizeof(privOnlyDer)), 0); + + ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0); + idx = 0; + ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey, + (word32)privOnlyDerSz), 0); + + /* The public point should have been derived automatically, making the + * key fully usable rather than left as ECC_PRIVATEKEY_ONLY. */ + 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); +#endif /* !NO_ASN && HAVE_ECC && !NO_ECC_MAKE_PUB && USE_CERT_BUFFERS_256 && + * !HAVE_FIPS */ + return EXPECT_RESULT(); +} diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index 4bd375179ef..a18c712cadc 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -44,6 +44,7 @@ int test_ToTraditional_ex_negative(void); int test_ToTraditional_ex_mldsa_bad_params(void); int test_wc_AsnDecisionCoverage(void); int test_wc_AsnFeatureCoverage(void); +int test_wc_EccPrivateKeyDecode_derive_pub(void); #define TEST_ASN_DECLS \ TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \ @@ -65,6 +66,7 @@ int test_wc_AsnFeatureCoverage(void); TEST_DECL_GROUP("asn", test_ToTraditional_ex_negative), \ TEST_DECL_GROUP("asn", test_ToTraditional_ex_mldsa_bad_params), \ TEST_DECL_GROUP("asn", test_wc_AsnDecisionCoverage), \ - TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage) + TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage), \ + TEST_DECL_GROUP("asn", test_wc_EccPrivateKeyDecode_derive_pub) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index 5a91f220c33..9840b92d585 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -7763,6 +7763,179 @@ int test_mldsa_make_key_from_seed(void) return EXPECT_RESULT(); } +int test_mldsa_make_public_key(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + wc_MlDsaKey* key; + + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + + /* NULL key. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); + + /* Private key not set yet. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Import a known private-only key (no public key attached) and derive + * the public key from it. */ + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_44_key, + sizeof_bench_mldsa_44_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(key->p, bench_mldsa_44_pubkey, + sizeof_bench_mldsa_44_pubkey), 0); + + /* No-op when the public key is already set. */ + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_44 */ + +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_65), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_65_key, + sizeof_bench_mldsa_65_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(key->p, bench_mldsa_65_pubkey, + sizeof_bench_mldsa_65_pubkey), 0); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_65 */ + +#ifndef WOLFSSL_NO_ML_DSA_87 + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_87), 0); + + ExpectIntEQ(wc_MlDsaKey_ImportPrivRaw(key, bench_mldsa_87_key, + sizeof_bench_mldsa_87_key), 0); + ExpectIntEQ(key->pubKeySet, 0); + + ExpectIntEQ(wc_MlDsaKey_MakePublicKey(key), 0); + ExpectIntEQ(key->pubKeySet, 1); + ExpectIntEQ(XMEMCMP(key->p, bench_mldsa_87_pubkey, + sizeof_bench_mldsa_87_pubkey), 0); + + wc_MlDsaKey_Free(key); +#endif /* !WOLFSSL_NO_ML_DSA_87 */ + + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +int test_mldsa_private_key_decode_derives_public_key(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) + wc_MlDsaKey* privKey; + wc_MlDsaKey* pubKey; + word32 idx; + + privKey = (wc_MlDsaKey*)XMALLOC(sizeof(*privKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(privKey); + pubKey = (wc_MlDsaKey*)XMALLOC(sizeof(*pubKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(pubKey); + if (privKey != NULL) { + XMEMSET(privKey, 0, sizeof(*privKey)); + } + if (pubKey != NULL) { + XMEMSET(pubKey, 0, sizeof(*pubKey)); + } + +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_MlDsaKey_Init(privKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey, WC_ML_DSA_44), 0); + + /* mldsa44_priv_only holds a private-key-only DER (no embedded public + * point). wc_MlDsaKey_PrivateKeyDecode should derive and cache the + * public key as a best-effort side effect of decoding it. */ + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey, mldsa44_priv_only, + sizeof_mldsa44_priv_only, &idx), 0); + ExpectIntEQ(privKey->pubKeySet, 1); + + /* Confirm the derived public key matches the known public key for the + * same key pair. */ + ExpectIntEQ(wc_MlDsaKey_Init(pubKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(pubKey, WC_ML_DSA_44), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(pubKey, mldsa44_pub_spki, + sizeof_mldsa44_pub_spki, &idx), 0); + ExpectIntEQ(XMEMCMP(privKey->p, pubKey->p, WC_MLDSA_44_PUB_KEY_SIZE), 0); + + wc_MlDsaKey_Free(privKey); + wc_MlDsaKey_Free(pubKey); +#endif /* !WOLFSSL_NO_ML_DSA_44 */ + +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_MlDsaKey_Init(privKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey, WC_ML_DSA_65), 0); + + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey, mldsa65_priv_only, + sizeof_mldsa65_priv_only, &idx), 0); + ExpectIntEQ(privKey->pubKeySet, 1); + + ExpectIntEQ(wc_MlDsaKey_Init(pubKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(pubKey, WC_ML_DSA_65), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(pubKey, mldsa65_pub_spki, + sizeof_mldsa65_pub_spki, &idx), 0); + ExpectIntEQ(XMEMCMP(privKey->p, pubKey->p, WC_MLDSA_65_PUB_KEY_SIZE), 0); + + wc_MlDsaKey_Free(privKey); + wc_MlDsaKey_Free(pubKey); +#endif /* !WOLFSSL_NO_ML_DSA_65 */ + +#ifndef WOLFSSL_NO_ML_DSA_87 + ExpectIntEQ(wc_MlDsaKey_Init(privKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(privKey, WC_ML_DSA_87), 0); + + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(privKey, mldsa87_priv_only, + sizeof_mldsa87_priv_only, &idx), 0); + ExpectIntEQ(privKey->pubKeySet, 1); + + ExpectIntEQ(wc_MlDsaKey_Init(pubKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(pubKey, WC_ML_DSA_87), 0); + idx = 0; + ExpectIntEQ(wc_MlDsaKey_PublicKeyDecode(pubKey, mldsa87_pub_spki, + sizeof_mldsa87_pub_spki, &idx), 0); + ExpectIntEQ(XMEMCMP(privKey->p, pubKey->p, WC_MLDSA_87_PUB_KEY_SIZE), 0); + + wc_MlDsaKey_Free(privKey); + wc_MlDsaKey_Free(pubKey); +#endif /* !WOLFSSL_NO_ML_DSA_87 */ + + XFREE(privKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + int test_mldsa_sig_kats(void) { EXPECT_DECLS; diff --git a/tests/api/test_mldsa.h b/tests/api/test_mldsa.h index 631ebabdc15..2d1f1742d3e 100644 --- a/tests/api/test_mldsa.h +++ b/tests/api/test_mldsa.h @@ -38,6 +38,8 @@ int test_mldsa_public_der_decode(void); int test_mldsa_der(void); int test_mldsa_oneasymkey_version(void); int test_mldsa_make_key_from_seed(void); +int test_mldsa_make_public_key(void); +int test_mldsa_private_key_decode_derives_public_key(void); int test_mldsa_sig_kats(void); int test_mldsa_sign_ctx_kats(void); int test_mldsa_verify_ctx_kats(void); @@ -75,6 +77,8 @@ int test_mldsa_legacy_shim(void); TEST_DECL_GROUP("mldsa", test_mldsa_der), \ TEST_DECL_GROUP("mldsa", test_mldsa_oneasymkey_version), \ TEST_DECL_GROUP("mldsa", test_mldsa_make_key_from_seed), \ + TEST_DECL_GROUP("mldsa", test_mldsa_make_public_key), \ + TEST_DECL_GROUP("mldsa", test_mldsa_private_key_decode_derives_public_key), \ TEST_DECL_GROUP("mldsa", test_mldsa_sig_kats), \ TEST_DECL_GROUP("mldsa", test_mldsa_sign_ctx_kats), \ TEST_DECL_GROUP("mldsa", test_mldsa_verify_ctx_kats), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 14ef4609cd4..3241207bda3 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -32719,6 +32719,31 @@ int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, key, curve_id); } + /* Based on ecc.c's HAVE_ECC_MAKE_PUB condition, which is local to + * that file. This guard is intentionally a superset: QNX_CAAM and + * IMXRT1170_CAAM are added because those ports store the private + * scalar as an opaque hardware "black key" where a software point + * multiply would be meaningless or unsafe. */ +#if !defined(NO_ECC_MAKE_PUB) && !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) + if ((ret == 0) && (key->type == ECC_PRIVATEKEY_ONLY)) { + /* SEC1 allows omitting the public point; derive it. Best-effort: + * failure must not fail decoding of an otherwise valid key. */ + int pubRet; +#ifdef ECC_TIMING_RESISTANT + pubRet = wc_ecc_make_pub_ex(key, NULL, key->rng); +#else + pubRet = wc_ecc_make_pub_ex(key, NULL, NULL); +#endif + if (pubRet != 0) { + WOLFSSL_MSG("Best-effort ECC public key derivation failed"); + } + } +#endif + FREE_ASNGETDATA(dataASN, key != NULL ? key->heap : NULL); return ret; } diff --git a/wolfcrypt/src/asn_orig.c b/wolfcrypt/src/asn_orig.c index ccab6504257..6cdc74e69a7 100644 --- a/wolfcrypt/src/asn_orig.c +++ b/wolfcrypt/src/asn_orig.c @@ -7707,6 +7707,31 @@ int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx, ecc_key* key, (word32)pubSz, key, curve_id); } + /* Based on ecc.c's HAVE_ECC_MAKE_PUB condition, which is local to + * that file. This guard is intentionally a superset: QNX_CAAM and + * IMXRT1170_CAAM are added because those ports store the private + * scalar as an opaque hardware "black key" where a software point + * multiply would be meaningless or unsafe. */ +#if !defined(NO_ECC_MAKE_PUB) && !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) + if ((ret == 0) && (key->type == ECC_PRIVATEKEY_ONLY)) { + /* SEC1 allows omitting the public point; derive it. Best-effort: + * failure must not fail decoding of an otherwise valid key. */ + int pubRet; +#ifdef ECC_TIMING_RESISTANT + pubRet = wc_ecc_make_pub_ex(key, NULL, key->rng); +#else + pubRet = wc_ecc_make_pub_ex(key, NULL, NULL); +#endif + if (pubRet != 0) { + WOLFSSL_MSG("Best-effort ECC public key derivation failed"); + } + } +#endif + WC_FREE_VAR_EX(priv, key->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(pub, key->heap, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfcrypt/src/wc_mldsa.c b/wolfcrypt/src/wc_mldsa.c index b564167111e..1ce8376dc82 100644 --- a/wolfcrypt/src/wc_mldsa.c +++ b/wolfcrypt/src/wc_mldsa.c @@ -10289,6 +10289,389 @@ int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed) return ret; } + +#if !defined(WOLFSSL_MLDSA_ASSIGN_KEY) +/* Derive and cache the public key (t1) from an already-imported private + * key with no public key of its own, eg one decoded from a private-key-only + * DER/PEM. Recomputes t1 from rho/s1/s2 the same way keygen does. No-op if + * the public key is already set. + * + * Respects the same memory-optimisation macros as mldsa_make_key_from_seed: + * WC_MLDSA_CACHE_MATRIX_A - reuse key->a + * WC_MLDSA_CACHE_PRIV_VECTORS - reuse key->s1/s2/t0 + * WC_MLDSA_FIXED_ARRAY - all buffers live in the key struct + * WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM - stream matrix A one poly at a time + * + * @param [in, out] key ML-DSA key with prvKeySet already true. + * @return 0 on success, or if the public key was already set. + * @return BAD_FUNC_ARG when key, key->params is NULL, or prvKeySet is + * false. + * @return MEMORY_E on allocation failure. + * @return Other negative when an error occurs. + */ +int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key) +{ + int ret = 0; + const wc_MlDsaParams* params = NULL; + sword32* s1 = NULL; + sword32* s2 = NULL; + sword32* t = NULL; + sword32* a = NULL; + byte* t0Scratch = NULL; + unsigned int allocSz = 0; + unsigned int t0ScratchSz = 0; +#ifdef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + byte* h = NULL; +#ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + sword64* t64 = NULL; +#endif +#endif + int didAlloc = 0; + + if (key == NULL) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && (!key->prvKeySet)) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && (key->params == NULL)) { + ret = BAD_FUNC_ARG; + } + + if ((ret == 0) && (!key->pubKeySet)) { + params = key->params; + + #if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) + ret = mldsa_alloc_pub_buf(key); + #endif + + /* --- Allocate / reuse matrix A --------------------------------- */ + #ifdef WC_MLDSA_CACHE_MATRIX_A + #ifndef WC_MLDSA_FIXED_ARRAY + if ((ret == 0) && (key->a == NULL)) { + key->a = (sword32*)XMALLOC(params->aSz, key->heap, + DYNAMIC_TYPE_MLDSA); + if (key->a == NULL) { + ret = MEMORY_E; + } + } + #endif + if (ret == 0) { + a = key->a; + } + #endif /* WC_MLDSA_CACHE_MATRIX_A */ + + /* --- Allocate / reuse private vectors s1, s2, t ---------------- */ + #ifdef WC_MLDSA_CACHE_PRIV_VECTORS + #ifndef WC_MLDSA_FIXED_ARRAY + if ((ret == 0) && (key->s1 == NULL)) { + key->s1 = (sword32*)XMALLOC(params->aSz, key->heap, + DYNAMIC_TYPE_MLDSA); + if (key->s1 == NULL) { + ret = MEMORY_E; + } + else { + key->s2 = key->s1 + params->s1Sz / sizeof(*s1); + key->t0 = key->s2 + params->s2Sz / sizeof(*s2); + } + } + #endif + if (ret == 0) { + s1 = key->s1; + s2 = key->s2; + t = key->t0; + } + #endif /* WC_MLDSA_CACHE_PRIV_VECTORS */ + + /* --- Compute dynamic allocation size for remaining buffers ----- */ + if (ret == 0) { + /* t0 encoding is D bits per coefficient for each of the k + * polynomials in the vector, regardless of security level. */ + t0ScratchSz = (unsigned int)params->k * + (MLDSA_D * MLDSA_N / 8); + + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + allocSz = (unsigned int)params->s1Sz + params->s2Sz + + params->s2Sz; + #endif + #ifndef WC_MLDSA_CACHE_MATRIX_A + allocSz += params->aSz; + #endif + #else /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + /* s1-l, s2-k, t-k, h, a-1 (one poly for streaming) */ + allocSz = (unsigned int)params->s1Sz + params->s2Sz + + params->s2Sz + + (unsigned int)MLDSA_REJ_NTT_POLY_H_SIZE + + (unsigned int)MLDSA_POLY_SIZE; + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + allocSz += (unsigned int)MLDSA_POLY_SIZE * 2U; + #endif + #endif /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + } + + /* --- Allocate the dynamic portion ------------------------------ */ + #ifdef WC_MLDSA_FIXED_ARRAY + /* Everything lives in the key struct; no dynamic allocation. */ + if (ret == 0) { + s1 = key->s1; + s2 = key->s2; + t = key->t0; + a = key->a; + } + #else + if ((ret == 0) && (allocSz > 0)) { + s1 = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + if (s1 == NULL) { + ret = MEMORY_E; + } + else { + didAlloc = 1; + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + s2 = s1 + params->s1Sz / sizeof(*s1); + t = s2 + params->s2Sz / sizeof(*s2); + #endif + #ifndef WC_MLDSA_CACHE_MATRIX_A + { + /* Matrix A is appended at the end of the block. */ + sword32* base = s1; + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + base = t + params->s2Sz / sizeof(*t); + #endif + a = base; + } + #endif + #else /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + s2 = s1 + params->s1Sz / sizeof(*s1); + t = s2 + params->s2Sz / sizeof(*s2); + h = (byte*)(t + params->s2Sz / sizeof(*t)); + a = (sword32*)(h + MLDSA_REJ_NTT_POLY_H_SIZE); + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + t64 = (sword64*)(a + MLDSA_N); + #endif + #endif /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + } + } + #endif /* WC_MLDSA_FIXED_ARRAY */ + + if ((ret == 0) && (t0Scratch != NULL || t0ScratchSz == 0)) { + /* t0Scratch already set (shouldn't happen, but guard). */ + } + else if (ret == 0) { + t0Scratch = (byte*)XMALLOC(t0ScratchSz, key->heap, + DYNAMIC_TYPE_MLDSA); + if (t0Scratch == NULL) { + ret = MEMORY_E; + } + } + + if (ret == 0) { + const byte* rho = key->k; + const byte* s1p = key->k + MLDSA_PUB_SEED_SZ + MLDSA_K_SZ + + MLDSA_TR_SZ; + const byte* s2p = s1p + params->s1EncSz; + byte* t1 = key->p + MLDSA_PUB_SEED_SZ; + + mldsa_vec_decode_eta_bits(s1p, params->eta, s1, params->l); + mldsa_vec_decode_eta_bits(s2p, params->eta, s2, params->k); + + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + /* Standard path: expand full matrix A, then multiply. */ + ret = mldsa_expand_a(&key->shake, rho, params->k, params->l, a, + key->heap); + if (ret == 0) { + XMEMCPY(key->p, rho, MLDSA_PUB_SEED_SZ); + + mldsa_vec_ntt_small_full(s1, params->l); + mldsa_matrix_mul(t, a, s1, params->k, params->l); + #ifdef WOLFSSL_MLDSA_SMALL + mldsa_vec_red(t, params->k); + #endif + mldsa_vec_invntt_full(t, params->k); + mldsa_vec_add(t, s2, params->k); + mldsa_vec_make_pos(t, params->k); + mldsa_vec_encode_t0_t1(t, params->k, t0Scratch, t1); + + key->pubKeySet = 1; + } + #else /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + /* Small-mem path: stream matrix A one polynomial at a time. */ + XMEMCPY(key->p, rho, MLDSA_PUB_SEED_SZ); + + mldsa_vec_ntt_small_full(s1, params->l); + { + byte aseed[MLDSA_GEN_A_SEED_SZ]; + sword32* s2t = s2; + sword32* tt = t; + unsigned int r; + unsigned int s; + + XMEMCPY(aseed, rho, MLDSA_PUB_SEED_SZ); + for (r = 0; (ret == 0) && (r < params->k); r++) { + sword32* s1t = s1; + unsigned int e; + + aseed[MLDSA_PUB_SEED_SZ + 1] = (byte)r; + for (s = 0; (ret == 0) && (s < params->l); s++) { + aseed[MLDSA_PUB_SEED_SZ + 0] = (byte)s; + ret = mldsa_rej_ntt_poly_ex(&key->shake, aseed, a, h); + if (ret != 0) { + break; + } + #ifndef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + if (s == 0) { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + tt[e] = mldsa_mont_red( + (sword64)a[e] * s1t[e]); + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + tt[e+0] = mldsa_mont_red( + (sword64)a[e+0] * s1t[e+0]); + tt[e+1] = mldsa_mont_red( + (sword64)a[e+1] * s1t[e+1]); + tt[e+2] = mldsa_mont_red( + (sword64)a[e+2] * s1t[e+2]); + tt[e+3] = mldsa_mont_red( + (sword64)a[e+3] * s1t[e+3]); + tt[e+4] = mldsa_mont_red( + (sword64)a[e+4] * s1t[e+4]); + tt[e+5] = mldsa_mont_red( + (sword64)a[e+5] * s1t[e+5]); + tt[e+6] = mldsa_mont_red( + (sword64)a[e+6] * s1t[e+6]); + tt[e+7] = mldsa_mont_red( + (sword64)a[e+7] * s1t[e+7]); + } + #endif + } + else { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + tt[e] += mldsa_mont_red( + (sword64)a[e] * s1t[e]); + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + tt[e+0] += mldsa_mont_red( + (sword64)a[e+0] * s1t[e+0]); + tt[e+1] += mldsa_mont_red( + (sword64)a[e+1] * s1t[e+1]); + tt[e+2] += mldsa_mont_red( + (sword64)a[e+2] * s1t[e+2]); + tt[e+3] += mldsa_mont_red( + (sword64)a[e+3] * s1t[e+3]); + tt[e+4] += mldsa_mont_red( + (sword64)a[e+4] * s1t[e+4]); + tt[e+5] += mldsa_mont_red( + (sword64)a[e+5] * s1t[e+5]); + tt[e+6] += mldsa_mont_red( + (sword64)a[e+6] * s1t[e+6]); + tt[e+7] += mldsa_mont_red( + (sword64)a[e+7] * s1t[e+7]); + } + #endif + } + #else /* WOLFSSL_MLDSA_SMALL_MEM_POLY64 */ + if (s == 0) { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + t64[e] = (sword64)a[e] * s1t[e]; + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + t64[e+0] = (sword64)a[e+0] * s1t[e+0]; + t64[e+1] = (sword64)a[e+1] * s1t[e+1]; + t64[e+2] = (sword64)a[e+2] * s1t[e+2]; + t64[e+3] = (sword64)a[e+3] * s1t[e+3]; + t64[e+4] = (sword64)a[e+4] * s1t[e+4]; + t64[e+5] = (sword64)a[e+5] * s1t[e+5]; + t64[e+6] = (sword64)a[e+6] * s1t[e+6]; + t64[e+7] = (sword64)a[e+7] * s1t[e+7]; + } + #endif + } + else { + #ifdef WOLFSSL_MLDSA_SMALL + for (e = 0; e < MLDSA_N; e++) { + t64[e] += (sword64)a[e] * s1t[e]; + } + #else + for (e = 0; e < MLDSA_N; e += 8) { + t64[e+0] += (sword64)a[e+0] * s1t[e+0]; + t64[e+1] += (sword64)a[e+1] * s1t[e+1]; + t64[e+2] += (sword64)a[e+2] * s1t[e+2]; + t64[e+3] += (sword64)a[e+3] * s1t[e+3]; + t64[e+4] += (sword64)a[e+4] * s1t[e+4]; + t64[e+5] += (sword64)a[e+5] * s1t[e+5]; + t64[e+6] += (sword64)a[e+6] * s1t[e+6]; + t64[e+7] += (sword64)a[e+7] * s1t[e+7]; + } + #endif + } + #endif /* WOLFSSL_MLDSA_SMALL_MEM_POLY64 */ + s1t += MLDSA_N; + } + #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 + for (e = 0; e < MLDSA_N; e++) { + tt[e] = mldsa_mont_red(t64[e]); + } + #endif + mldsa_invntt_full(tt); + mldsa_add(tt, s2t); + mldsa_make_pos(tt); + + tt += MLDSA_N; + s2t += MLDSA_N; + } + } + if (ret == 0) { + mldsa_vec_encode_t0_t1(t, params->k, t0Scratch, t1); + + key->pubKeySet = 1; + } + #endif /* WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM */ + + #ifdef WC_MLDSA_CACHE_MATRIX_A + if (ret == 0) { + key->aSet = 1; + } + #endif + } + + /* --- Cleanup --------------------------------------------------- */ + #ifndef WC_MLDSA_CACHE_PRIV_VECTORS + if (didAlloc && s1 != NULL) { + #ifndef WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM + /* Only s1/s2/t are secret; trailing matrix A is public. */ + ForceZero(s1, (unsigned int)params->s1Sz + 2U * params->s2Sz); + #else + /* In small-mem mode the t64 accumulator follows the public + * rejection-sampling / A region and holds A o NTT(s1), from + * which s1 is recoverable. Zeroize the entire allocation. */ + ForceZero(s1, allocSz); + #endif + } + if (didAlloc) { + XFREE(s1, key->heap, DYNAMIC_TYPE_MLDSA); + } + #endif /* !WC_MLDSA_CACHE_PRIV_VECTORS */ + #if !defined(WC_MLDSA_CACHE_PRIV_VECTORS) && \ + !defined(WC_MLDSA_CACHE_MATRIX_A) && \ + !defined(WC_MLDSA_FIXED_ARRAY) + (void)didAlloc; + #endif + if (t0Scratch != NULL) { + ForceZero(t0Scratch, t0ScratchSz); + } + XFREE(t0Scratch, key->heap, DYNAMIC_TYPE_MLDSA); + } + + return ret; +} +#endif /* !WOLFSSL_MLDSA_ASSIGN_KEY */ #endif #ifndef WOLFSSL_MLDSA_NO_SIGN @@ -10634,6 +11017,9 @@ int wc_MlDsaKey_VerifyCtx(wc_MlDsaKey* key, const byte* sig, word32 sigLen, if ((key == NULL) || (sig == NULL) || (msg == NULL) || (res == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->pubKeySet)) { + ret = BAD_FUNC_ARG; + } if ((ret == 0) && (ctx == NULL) && (ctxLen > 0)) { ret = BAD_FUNC_ARG; } @@ -10691,6 +11077,9 @@ int wc_MlDsaKey_Verify(wc_MlDsaKey* key, const byte* sig, word32 sigLen, if ((key == NULL) || (sig == NULL) || (msg == NULL) || (res == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->pubKeySet)) { + ret = BAD_FUNC_ARG; + } #ifdef WOLF_CRYPTO_CB if (ret == 0) { @@ -10743,6 +11132,9 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen, if ((key == NULL) || (sig == NULL) || (hash == NULL) || (res == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->pubKeySet)) { + ret = BAD_FUNC_ARG; + } if ((ret == 0) && (ctx == NULL) && (ctxLen > 0)) { ret = BAD_FUNC_ARG; } @@ -10797,6 +11189,9 @@ int wc_MlDsaKey_VerifyMu(wc_MlDsaKey* key, const byte* sig, word32 sigLen, (mu == NULL) || (res == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->pubKeySet)) { + ret = BAD_FUNC_ARG; + } if ((ret == 0) && (muLen != MLDSA_MU_SZ)) { ret = BAD_FUNC_ARG; } @@ -12314,6 +12709,18 @@ int wc_MlDsaKey_PrivateKeyDecode(wc_MlDsaKey* key, const byte* input, { /* No public key data, only import private key data. */ ret = wc_MlDsaKey_ImportPrivRaw(key, privKey, privKeyLen); +#if !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) + if (ret == 0) { + /* Derive t1 so the key is fully usable. Best-effort: + * failure must not fail decoding of an otherwise valid + * key. */ + int pubRet = wc_MlDsaKey_MakePublicKey(key); + if (pubRet != 0) { + WOLFSSL_MSG("Best-effort ML-DSA public key derivation " + "failed"); + } + } +#endif } else { /* Not a problem of ASN.1 structure, but the contents is invalid */ @@ -12869,16 +13276,16 @@ int wc_MlDsaKey_PrivateKeyToDer(wc_MlDsaKey* key, byte* output, word32 len) else #endif if (key->level == WC_ML_DSA_44) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_44_KEY_SIZE, NULL, 0, output, - len, ML_DSA_44k); + ret = SetAsymKeyDer(key->k, WC_MLDSA_44_KEY_SIZE, NULL, 0, + output, len, ML_DSA_44k); } else if (key->level == WC_ML_DSA_65) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_65_KEY_SIZE, NULL, 0, output, - len, ML_DSA_65k); + ret = SetAsymKeyDer(key->k, WC_MLDSA_65_KEY_SIZE, NULL, 0, + output, len, ML_DSA_65k); } else if (key->level == WC_ML_DSA_87) { - ret = SetAsymKeyDer(key->k, WC_MLDSA_87_KEY_SIZE, NULL, 0, output, - len, ML_DSA_87k); + ret = SetAsymKeyDer(key->k, WC_MLDSA_87_KEY_SIZE, NULL, 0, + output, len, ML_DSA_87k); } } diff --git a/wolfssl/wolfcrypt/wc_mldsa.h b/wolfssl/wolfcrypt/wc_mldsa.h index 12384878e43..aec729706b0 100644 --- a/wolfssl/wolfcrypt/wc_mldsa.h +++ b/wolfssl/wolfcrypt/wc_mldsa.h @@ -698,6 +698,10 @@ WOLFSSL_API int wc_MlDsaKey_MakeKey(wc_MlDsaKey* key, WC_RNG* rng); WOLFSSL_API int wc_MlDsaKey_MakeKeyFromSeed(wc_MlDsaKey* key, const byte* seed); +#if !defined(WOLFSSL_MLDSA_ASSIGN_KEY) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) +WOLFSSL_API +int wc_MlDsaKey_MakePublicKey(wc_MlDsaKey* key); +#endif /* Legacy sign API without context parameter (pre-FIPS 204). * Only available when WOLFSSL_MLDSA_NO_CTX is defined.