From c102b31fde11d2bb9839443a2b6415a4964fcd54 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 30 Sep 2026 10:18:06 -0600 Subject: [PATCH] Test the ML-DSA key caches after small-mem key regeneration --- .github/configs/pq-all.json | 10 ++-- tests/api/test_mldsa.c | 94 +++++++++++++++++++++++++++++++++++++ tests/api/test_mldsa.h | 2 + 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/.github/configs/pq-all.json b/.github/configs/pq-all.json index b5ffa27978..131a1af1de 100644 --- a/.github/configs/pq-all.json +++ b/.github/configs/pq-all.json @@ -175,12 +175,12 @@ "LDFLAGS=-fsanitize=undefined", "CPPFLAGS=-DWOLFSSL_MLDSA_SIGN_SMALL_MEM -DWOLFSSL_MLDSA_VERIFY_SMALL_MEM -DWOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM -DWOLFSSL_MLDSA_TEST_REJ_NTT_FAIL -DWOLFSSL_MLDSA_CHECK_INVNTT_BOUND"]}, {"name": "ubsan-mldsa-small-smallmem-poly64", "minutes": 2.4, - "comment": "As ubsan-mldsa-small-smallmem, with the 64-bit t64 accumulator layout", + "comment": "As ubsan-mldsa-small-smallmem, with the 64-bit t64 accumulator layout; WC_MLDSA_CACHE_MATRIX_A makes CheckKey allocate matrix A after small-mem key generation", "configure": ["--disable-shared", "--disable-intelasm", "--enable-dilithium=yes,small", "CFLAGS=-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer", "LDFLAGS=-fsanitize=undefined", - "CPPFLAGS=-DWOLFSSL_MLDSA_SIGN_SMALL_MEM -DWOLFSSL_MLDSA_VERIFY_SMALL_MEM -DWOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM -DWOLFSSL_MLDSA_SMALL_MEM_POLY64 -DWOLFSSL_MLDSA_TEST_REJ_NTT_FAIL -DWOLFSSL_MLDSA_CHECK_INVNTT_BOUND"]}, + "CPPFLAGS=-DWOLFSSL_MLDSA_SIGN_SMALL_MEM -DWOLFSSL_MLDSA_VERIFY_SMALL_MEM -DWOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM -DWOLFSSL_MLDSA_SMALL_MEM_POLY64 -DWOLFSSL_MLDSA_TEST_REJ_NTT_FAIL -DWOLFSSL_MLDSA_CHECK_INVNTT_BOUND -DWC_MLDSA_CACHE_MATRIX_A"]}, {"name": "ubsan-mldsa-sign-precalc", "minutes": 2.4, "comment": "Runs the WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC scratch layout, and the non-SMALL key generation and verify small-mem paths, at the default alignment of 0, where MLDSA_REJ_NTT_POLY_H_SIZE is odd", "configure": ["--disable-shared", "--disable-intelasm", "--enable-dilithium", @@ -325,8 +325,10 @@ "configure": ["--enable-cryptonly", "--enable-mldsa", "CPPFLAGS=-DWC_MLDSA_CACHE_MATRIX_A -DWOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC_A=1"]}, {"name": "mldsa-cache-small-keygen", "minutes": 2, - "comment": "Every key cache alongside the small memory key generation, which must drop the caches of the key it replaces", - "configure": ["--enable-cryptonly", "--enable-mldsa", + "comment": "Every key cache alongside the small memory key generation, which must drop the caches of the key it replaces; under UBSan", + "configure": ["--enable-cryptonly", "--enable-mldsa", "--disable-shared", + "CFLAGS=-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer", + "LDFLAGS=-fsanitize=undefined", "CPPFLAGS=-DWC_MLDSA_CACHE_PRIV_VECTORS -DWC_MLDSA_CACHE_PUB_VECTORS -DWOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM"]}, {"name": "mldsa-no-check-key-small-mem", "minutes": 2, "comment": "Key pair checking compiled out alongside the small memory key generation and the smallest memory signer, which leaves the whole vector helpers with no caller", diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index 9cfb0cc04e..02dbcc9546 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -1520,6 +1520,13 @@ int test_mldsa_check_key(void) ExpectIntEQ(wc_MlDsaKey_SetParams(checkKey, WC_ML_DSA_87), 0); #endif ExpectIntEQ(wc_MlDsaKey_MakeKey(checkKey, &rng), 0); + /* A freshly generated key checks out. Small-mem key generation never + * caches A, so this also covers CheckKey allocating the matrix cache. */ + ExpectIntEQ(wc_MlDsaKey_CheckKey(checkKey), 0); + /* Regenerating into the same object must not reuse the A that CheckKey + * just cached for the previous key. */ + ExpectIntEQ(wc_MlDsaKey_MakeKey(checkKey, &rng), 0); + ExpectIntEQ(wc_MlDsaKey_CheckKey(checkKey), 0); ExpectIntEQ(wc_MlDsaKey_ExportKey(NULL, NULL, NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); @@ -7906,6 +7913,93 @@ int test_mldsa_make_key_from_seed(void) return EXPECT_RESULT(); } +/* Regenerating a key into the same object must not sign or verify with the + * vectors cached for the previous key. */ +int test_mldsa_make_key_twice(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WC_MLDSA_HAVE_NATIVE) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) + wc_MlDsaKey* key; + wc_MlDsaKey* verifyKey; + WC_RNG rng; + byte* sig; + byte* pub; + word32 sigLen; + word32 pubLen; + byte msg[] = "make key twice"; + int res; + int i; + + XMEMSET(&rng, 0, sizeof(rng)); + key = (wc_MlDsaKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + verifyKey = (wc_MlDsaKey*)XMALLOC(sizeof(*verifyKey), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(verifyKey); + sig = (byte*)XMALLOC(MLDSA_MAX_SIG_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(sig); + pub = (byte*)XMALLOC(MLDSA_MAX_PUB_KEY_SIZE, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(pub); + if (key != NULL) { + XMEMSET(key, 0, sizeof(*key)); + } + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0); +#ifndef WOLFSSL_NO_ML_DSA_44 + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_44), 0); +#elif !defined(WOLFSSL_NO_ML_DSA_65) + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_65), 0); +#else + ExpectIntEQ(wc_MlDsaKey_SetParams(key, WC_ML_DSA_87), 0); +#endif + + /* The first pass fills the caches; the second must not reuse them. */ + for (i = 0; i < 2; i++) { + ExpectIntEQ(wc_MlDsaKey_MakeKey(key, &rng), 0); + + sigLen = MLDSA_MAX_SIG_SIZE; + ExpectIntEQ(wc_MlDsaKey_SignCtx(key, NULL, 0, sig, &sigLen, msg, + sizeof(msg), &rng), 0); + + /* Same object: uses any cached public vector. */ + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(key, sig, sigLen, NULL, 0, msg, + sizeof(msg), &res), 0); + ExpectIntEQ(res, 1); + + /* Fresh object with only the new public key: catches a signature + * made from cached private vectors of the previous key. */ + pubLen = MLDSA_MAX_PUB_KEY_SIZE; + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(key, pub, &pubLen), 0); + if (verifyKey != NULL) { + XMEMSET(verifyKey, 0, sizeof(*verifyKey)); + } + ExpectIntEQ(wc_MlDsaKey_Init(verifyKey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(verifyKey, + (key != NULL) ? key->level : 0), 0); + ExpectIntEQ(wc_MlDsaKey_ImportPubRaw(verifyKey, pub, pubLen), 0); + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(verifyKey, sig, sigLen, NULL, 0, + msg, sizeof(msg), &res), 0); + ExpectIntEQ(res, 1); + wc_MlDsaKey_Free(verifyKey); + } + + wc_MlDsaKey_Free(key); + wc_FreeRng(&rng); + XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(verifyKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, 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 a05882f0ef..74f4cbe0b1 100644 --- a/tests/api/test_mldsa.h +++ b/tests/api/test_mldsa.h @@ -39,6 +39,7 @@ 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_key_twice(void); int test_mldsa_sig_kats(void); int test_mldsa_sign_ctx_kats(void); int test_mldsa_verify_ctx_kats(void); @@ -87,6 +88,7 @@ int test_mldsa_make_key_rej_ntt_fail(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_key_twice), \ 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), \