diff --git a/tests/api.c b/tests/api.c index 12a26d1e93..4643874167 100644 --- a/tests/api.c +++ b/tests/api.c @@ -32856,6 +32856,178 @@ static int test_wolfSSL_inject(void) int testAll = 1; int stopOnFail = 0; +/*----------------------------------------------------------------------------*/ +/* LMS tests */ +/*----------------------------------------------------------------------------*/ +int test_wc_LmsKey_sign_verify(void); +int test_wc_LmsKey_reload_cache(void); + +#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) + +#include +#include + +#define LMS_TEST_PRIV_KEY_FILE "/tmp/wolfssl_test_lms.key" + +static int test_lms_write_key(const byte* priv, word32 privSz, void* context) +{ + FILE* f = fopen((const char*)context, "wb"); + if (f == NULL) + return -1; + fwrite(priv, 1, privSz, f); + fclose(f); + return WC_LMS_RC_SAVED_TO_NV_MEMORY; +} + +static int test_lms_read_key(byte* priv, word32 privSz, void* context) +{ + FILE* f = fopen((const char*)context, "rb"); + if (f == NULL) + return -1; + if (fread(priv, 1, privSz, f) == 0) { + fclose(f); + return -1; + } + fclose(f); + return WC_LMS_RC_READ_TO_MEMORY; +} + +/* Helper: init an LMS key with callbacks and L1-H10-W8 params */ +static int test_lms_init_key(LmsKey* key, WC_RNG* rng) +{ + int ret; + + ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID); + if (ret != 0) return ret; + + ret = wc_LmsKey_SetParameters(key, 1, 10, 8); + if (ret != 0) return ret; + + ret = wc_LmsKey_SetWriteCb(key, test_lms_write_key); + if (ret != 0) return ret; + + ret = wc_LmsKey_SetReadCb(key, test_lms_read_key); + if (ret != 0) return ret; + + ret = wc_LmsKey_SetContext(key, (void*)LMS_TEST_PRIV_KEY_FILE); + if (ret != 0) return ret; + + (void)rng; + return 0; +} + +#endif /* WOLFSSL_HAVE_LMS && WOLFSSL_WC_LMS && !WOLFSSL_LMS_VERIFY_ONLY */ + +/* + * Test basic LMS sign/verify with multiple signings. + * Uses L1-H10-W8 (1024 total signatures, 32-entry leaf cache). + */ +int test_wc_LmsKey_sign_verify(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) + LmsKey key; + WC_RNG rng; + byte msg[] = "test message for LMS signing"; + byte sig[2048]; + word32 sigSz; + int i; + int numSigs = 5; + + ExpectIntEQ(wc_InitRng(&rng), 0); + + remove(LMS_TEST_PRIV_KEY_FILE); + ExpectIntEQ(test_lms_init_key(&key, &rng), 0); + ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0); + + for (i = 0; i < numSigs; i++) { + sigSz = sizeof(sig); + ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0); + ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0); + } + + wc_LmsKey_Free(&key); + wc_FreeRng(&rng); + remove(LMS_TEST_PRIV_KEY_FILE); +#endif + return EXPECT_RESULT(); +} + +/* + * Test LMS key reload after advancing past the leaf cache window. + * + * Reproduces a heap-buffer-overflow bug in wc_lms_treehash_init() where the + * leaf cache write uses (i * hash_len) instead of ((i - leaf->idx) * hash_len). + * When q > max_cb (default 32), wc_LmsKey_Reload calls wc_hss_init_auth_path + * which calls wc_lms_treehash_init with q > 0, causing writes past the end of + * the leaf cache buffer. + * + * Reproduction steps: + * 1. Generate L1-H10-W8 key (cacheBits=5, max_cb=32) + * 2. Sign 33 times to advance q past the cache window + * 3. Free the key and reload from persisted state + * 4. Sign and verify after reload + * + * Without the fix: heap-buffer-overflow at wc_lms_impl.c:1965 + * With the fix: all operations succeed, signatures verify + */ +int test_wc_LmsKey_reload_cache(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) + LmsKey key; + LmsKey vkey; + WC_RNG rng; + byte msg[] = "test message for LMS signing"; + byte sig[2048]; + word32 sigSz; + byte pub[64]; + word32 pubSz = sizeof(pub); + int i; + /* Sign 33 times to advance q past the 32-entry cache window. */ + int preSigs = 33; + + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Phase 1: Generate key and sign past cache window */ + remove(LMS_TEST_PRIV_KEY_FILE); + ExpectIntEQ(test_lms_init_key(&key, &rng), 0); + ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0); + + for (i = 0; i < preSigs; i++) { + sigSz = sizeof(sig); + ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0); + } + + /* Save public key for verification after reload */ + ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz), 0); + + wc_LmsKey_Free(&key); + + /* Phase 2: Reload key — triggers wc_lms_treehash_init with q=33 */ + ExpectIntEQ(test_lms_init_key(&key, &rng), 0); + ExpectIntEQ(wc_LmsKey_Reload(&key), 0); + + /* Phase 3: Sign after reload and verify with separate verify-only key */ + sigSz = sizeof(sig); + ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0); + + ExpectIntEQ(wc_LmsKey_Init(&vkey, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 10, 8), 0); + ExpectIntEQ(wc_LmsKey_ImportPubRaw(&vkey, pub, pubSz), 0); + ExpectIntEQ(wc_LmsKey_Verify(&vkey, sig, sigSz, msg, sizeof(msg)), 0); + + wc_LmsKey_Free(&vkey); + wc_LmsKey_Free(&key); + wc_FreeRng(&rng); + remove(LMS_TEST_PRIV_KEY_FILE); +#endif + return EXPECT_RESULT(); +} + TEST_CASE testCases[] = { TEST_DECL(test_fileAccess), @@ -32969,6 +33141,9 @@ TEST_CASE testCases[] = { TEST_MLKEM_DECLS, /* Dilithium */ TEST_MLDSA_DECLS, + /* LMS */ + TEST_DECL_GROUP("lms", test_wc_LmsKey_sign_verify), + TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_cache), /* Signature API */ TEST_SIGNATURE_DECLS, diff --git a/wolfcrypt/src/wc_lms_impl.c b/wolfcrypt/src/wc_lms_impl.c index 6aa5d2aa43..44191dbb33 100644 --- a/wolfcrypt/src/wc_lms_impl.c +++ b/wolfcrypt/src/wc_lms_impl.c @@ -1962,7 +1962,8 @@ static int wc_lms_treehash_init(LmsState* state, LmsPrivState* privState, /* Cache leaf node if in range. */ if ((ret == 0) && (i >= leaf->idx) && (i < leaf->idx + max_cb)) { - XMEMCPY(leaf->cache + i * params->hash_len, temp, params->hash_len); + XMEMCPY(leaf->cache + (i - leaf->idx) * params->hash_len, temp, + params->hash_len); } /* Store the node if on the authentication path. */