From cbbdeb0e8ac3e663bc9b0cd3b8f89a17b178dd4e Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 26 Oct 2026 06:14:51 +0000 Subject: [PATCH 1/4] SLH-DSA: build with NO_ASN and leave out unused hash families wc_slhdsa.c included asn.h unconditionally and compiled its DER key encode/decode functions without a NO_ASN guard. asn.h needs an mp_int type, so a minimal verify-only build with no big-int math had to compile asn.c, sp_int.c and wolfmath.c just to link. Guard the include, the DER functions and their prototypes on NO_ASN. A NO_ASN + WOLFCRYPT_ONLY build also drops misc.c's c32toa/ato32, which SLH-DSA uses, so keep them for SLH-DSA as is already done for XMSS and LMS. The SHA2/SHAKE hash dispatch was decided at run time from the parameter set, so a SHA2-128s-only build with WOLFSSL_SLHDSA_NO_SHAKE still referenced SHA-512, which only the category 3 and 5 SHA2 sets use, and SHAKE-256. Compile the SHAKE code only when a SHAKE parameter set is built, and the SHA-512 branches only when a SHA2-192/256 set is (new WC_SLHDSA_NEED_SHA512). With a single family the hash macros call it directly, and SlhDsaKey holds only the hash state that is used. The pre-hash buffer bound now follows the hashes that are compiled in, so a SHA-256-only build no longer fails its static assert. The MC/DC white-box tests that call these static functions directly are guarded the same way. SLH-DSA-SHA2-128s verify-only on Cortex-M33 (-Os, Thumb-2 asm, gc-sections): wc_slhdsa plus linked hash code goes from 28,197 to 7,629 bytes, and SlhDsaKey from 1,160 to 480 bytes. Verified on QEMU mps2-an505 and on an STM32U585. --- .../test_slhdsa_hash_fault_whitebox.c | 4 + tests/unit-mcdc/test_slhdsa_whitebox.c | 24 ++- wolfcrypt/src/wc_slhdsa.c | 191 +++++++++++++++--- wolfssl/wolfcrypt/settings.h | 5 +- wolfssl/wolfcrypt/wc_slhdsa.h | 19 ++ 5 files changed, 214 insertions(+), 29 deletions(-) diff --git a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c index a2e1a14224e..2cd5ed616dd 100644 --- a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c @@ -477,6 +477,7 @@ static void wb_init_rows(int param) printf(" [wb] param %d: init hash-object rows exercised\n", param); } +#ifndef NO_ASN /* Import/export + DER encode/decode: the ASN-side residuals * (`(key->params != NULL) && ...`, `while (ret == 0 && *inOutIdx < seqEnd)`) * live here and cost nothing to drive. */ @@ -568,6 +569,7 @@ static void wb_der_rows(int param) (void)wc_SlhDsaKey_ExportPublic(&wb_key, pub, &idx); } +#endif /* !NO_ASN */ static void wb_run_param(int param) { @@ -594,8 +596,10 @@ static void wb_run_param(int param) wb_sweep_verify(param); if (!wb_expired()) wb_sweep_prehash(param); +#ifndef NO_ASN if (!wb_expired()) wb_der_rows(param); +#endif if (!wb_expired()) wb_sweep_import(param); if (!wb_expired()) diff --git a/tests/unit-mcdc/test_slhdsa_whitebox.c b/tests/unit-mcdc/test_slhdsa_whitebox.c index f6bef839f85..0bae73f6f13 100644 --- a/tests/unit-mcdc/test_slhdsa_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_whitebox.c @@ -47,8 +47,10 @@ static int wb_fail = 0; #ifdef WOLFSSL_HAVE_SLHDSA +#ifndef NO_ASN /* ------------------------------------------------------------------------- * * slhdsa_find_params(): found vs not-found (both halves of the table compare). + * Only compiled with the DER decoders that use it. * ------------------------------------------------------------------------- */ static void wb_find_params(void) { @@ -79,6 +81,7 @@ static void wb_find_params(void) WB_NOTE("slhdsa_find_params found/not-found pair exercised"); } +#endif /* !NO_ASN */ /* ------------------------------------------------------------------------- * * slhdsakey_base_2b(): nested for/while loop decisions. @@ -125,15 +128,18 @@ static void wb_ha_encode(void) adrs[i] = (word32)(0x01020300u + (word32)i); } +#ifndef WOLFSSL_SLHDSA_NO_SHAKE /* HA_Encode: the WOLFSSL_WC_SLHDSA_SMALL variant takes the for-loop body * (its "i < 8" decision), the default variant the unrolled writes. Either - * way this is the direct, sign-free drive of that arm. */ + * way this is the direct, sign-free drive of that arm. HA_Encode is only + * compiled when a SHAKE parameter set is. */ XMEMSET(address, 0, sizeof(address)); HA_Encode(adrs, address); if ((address[0] == 0) && (address[SLHDSA_HA_SZ - 1] != 0)) { /* purely to consume the output so it is not optimized away */ WB_NOTE("HA_Encode produced an unexpected all-zero prefix"); } +#endif #ifdef WOLFSSL_SLHDSA_SHA2 /* HA_Encode_Compressed is only compiled under WOLFSSL_SLHDSA_SHA2. */ @@ -176,9 +182,11 @@ static void wb_precompute_sha2_midstates(void) wb_fail = 1; } +#ifdef WC_SLHDSA_NEED_SHA512 /* n > 16 (categories 3, 5): true side. The function only reads * key->params->n and key->sk, so this crafted entry is a faithful - * exercise of the SHA-512 midstate branch. */ + * exercise of the SHA-512 midstate branch. The branch is only compiled + * when a category 3 or 5 SHA2 parameter set is. */ fakeParams.n = 24; key.hash.sha2.sha256_mid_inited = 0; key.hash.sha2.sha512_mid_inited = 0; @@ -186,6 +194,7 @@ static void wb_precompute_sha2_midstates(void) WB_NOTE("precompute_sha2_midstates(n=24) failed"); wb_fail = 1; } +#endif /* Free the hash objects directly (not via wc_SlhDsaKey_Free): fakeParams * isn't a real table entry, so SLHDSA_IS_SHA2(key->params->param) can't @@ -199,6 +208,7 @@ static void wb_precompute_sha2_midstates(void) if (key.hash.sha2.sha256_mid_inited) { wc_Sha256Free(&key.hash.sha2.sha256_mid); } +#ifdef WC_SLHDSA_NEED_SHA512 if (key.hash.sha2.sha512_inited) { wc_Sha512Free(&key.hash.sha2.sha512); } @@ -208,6 +218,7 @@ static void wb_precompute_sha2_midstates(void) if (key.hash.sha2.sha512_mid_inited) { wc_Sha512Free(&key.hash.sha2.sha512_mid); } +#endif WB_NOTE("slhdsakey_precompute_sha2_midstates n<=16/n>16 pair exercised"); } @@ -280,7 +291,9 @@ static void wb_h_msg_sha2_ctx_pairs(void) wb_fail = 1; } - /* SHA-512 branch (n > WC_SLHDSA_N_128). Four combinations: +#ifdef WC_SLHDSA_NEED_SHA512 + /* SHA-512 branch (n > WC_SLHDSA_N_128), only compiled when a category 3 + * or 5 SHA2 parameter set is. Four combinations: * A: hdr==NULL, ctxSz==0 -> both false * B: hdr!=NULL, ctxSz==0 -> hdr pair's true side * C: hdr!=NULL, ctxSz>0, ctx==NULL -> ctxSz>0 T, ctx!=NULL F @@ -312,6 +325,7 @@ static void wb_h_msg_sha2_ctx_pairs(void) WB_NOTE("h_msg_sha2(n=24,D) failed"); wb_fail = 1; } +#endif if (key.hash.sha2.sha256_inited) { wc_Sha256Free(&key.hash.sha2.sha256); @@ -319,12 +333,14 @@ static void wb_h_msg_sha2_ctx_pairs(void) if (key.hash.sha2.sha256_2_inited) { wc_Sha256Free(&key.hash.sha2.sha256_2); } +#ifdef WC_SLHDSA_NEED_SHA512 if (key.hash.sha2.sha512_inited) { wc_Sha512Free(&key.hash.sha2.sha512); } if (key.hash.sha2.sha512_2_inited) { wc_Sha512Free(&key.hash.sha2.sha512_2); } +#endif WB_NOTE("slhdsakey_h_msg_sha2 hdr/ctx independence pairs exercised"); } @@ -419,7 +435,9 @@ int main(void) printf(" WOLFSSL_HAVE_SLHDSA not defined; nothing to exercise\n"); return 0; #else +#ifndef NO_ASN wb_find_params(); +#endif wb_base_2b(); wb_ha_encode(); #ifdef WOLFSSL_SLHDSA_SHA2 diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index 3703a39e9de..d5cd32c3ba2 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -40,7 +40,9 @@ #error "SLH-DSA signing is required for the v7 FIPS module" #endif +#ifndef NO_ASN #include +#endif #if FIPS_VERSION3_GE(7,0,0) const unsigned int wolfCrypt_FIPS_slhdsa_ro_sanity[2] = @@ -344,8 +346,10 @@ typedef word32 HashAddress[8]; #define WC_HASHADDRESS_TYPE_DEFINED #endif -#ifndef WOLF_CRYPTO_CB_ONLY_SLHDSA +#if !defined(WOLF_CRYPTO_CB_ONLY_SLHDSA) && !defined(WOLFSSL_SLHDSA_NO_SHAKE) /* Encode a HashAddress. + * + * SHAKE only - SHA2 uses the compressed encoding. * * @param [in] adrs HashAddress to encode. * @param [out] address Buffer to encode into. @@ -369,7 +373,7 @@ static void HA_Encode(const word32* adrs, byte* address) } #endif } -#endif /* !WOLF_CRYPTO_CB_ONLY_SLHDSA */ +#endif /* !WOLF_CRYPTO_CB_ONLY_SLHDSA && !WOLFSSL_SLHDSA_NO_SHAKE */ /****************************************************************************** * Index Tree - 3 x 32-bit words @@ -483,6 +487,7 @@ static const SlhDsaParameters SlhDsaParams[] = * WOLF_CRYPTO_CB_ONLY_SLHDSA the registered device performs all of it. */ #ifndef WOLF_CRYPTO_CB_ONLY_SLHDSA +#ifndef WOLFSSL_SLHDSA_NO_SHAKE #ifndef WOLFSSL_WC_SLHDSA_SMALL /* Hash three data elements with SHAKE-256. * @@ -713,6 +718,7 @@ static int slhdsakey_hash_shake_4(wc_Shake* shake, const byte* data1, return wc_Shake256_Final(shake, hash, hash_len); #endif } +#endif /* !WOLFSSL_SLHDSA_NO_SHAKE */ /****************************************************************************** * SHA2 Hash Functions (FIPS 205, Section 11.2) @@ -765,7 +771,11 @@ static int slhdsakey_precompute_sha2_midstates(SlhDsaKey* key) int ret = 0; byte n = key->params->n; const byte* pk_seed = key->sk + 2 * n; +#ifdef WC_SLHDSA_NEED_SHA512 byte block[WC_SHA512_BLOCK_SIZE]; +#else + byte block[WC_SHA256_BLOCK_SIZE]; +#endif /* SHA-256 midstate: PK.seed || zeros to fill 64-byte block. */ XMEMSET(block, 0, WC_SHA256_BLOCK_SIZE); @@ -781,6 +791,7 @@ static int slhdsakey_precompute_sha2_midstates(SlhDsaKey* key) WC_SHA256_BLOCK_SIZE); } +#ifdef WC_SLHDSA_NEED_SHA512 /* SHA-512 midstate: PK.seed || zeros to fill 128-byte block. * Only needed for categories 3 and 5 (n > 16). */ if ((ret == 0) && (n > 16)) { @@ -797,6 +808,7 @@ static int slhdsakey_precompute_sha2_midstates(SlhDsaKey* key) WC_SHA512_BLOCK_SIZE); } } +#endif return ret; } @@ -880,7 +892,10 @@ static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed, /* Encode compressed address. */ HA_Encode_Compressed(adrs, address); - if (n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (n == WC_SLHDSA_N_128) +#endif + { /* Category 1: use SHA-256. */ byte digest[WC_SHA256_DIGEST_SIZE]; @@ -905,6 +920,7 @@ static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed, XMEMCPY(hash, digest, n); } } +#ifdef WC_SLHDSA_NEED_SHA512 else { /* Categories 3, 5: use SHA-512. */ byte digest[WC_SHA512_DIGEST_SIZE]; @@ -930,6 +946,7 @@ static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed, XMEMCPY(hash, digest, n); } } +#endif return ret; } @@ -959,7 +976,10 @@ static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed, /* Encode compressed address. */ HA_Encode_Compressed(adrs, address); - if (n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (n == WC_SLHDSA_N_128) +#endif + { /* Category 1: use SHA-256. */ byte digest[WC_SHA256_DIGEST_SIZE]; @@ -987,6 +1007,7 @@ static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed, XMEMCPY(hash, digest, n); } } +#ifdef WC_SLHDSA_NEED_SHA512 else { /* Categories 3, 5: use SHA-512. */ byte digest[WC_SHA512_DIGEST_SIZE]; @@ -1015,6 +1036,7 @@ static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed, XMEMCPY(hash, digest, n); } } +#endif return ret; } @@ -1100,7 +1122,12 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, HA_Encode_Compressed(adrs, address); - if (n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (n == WC_SLHDSA_N_128) +#else + (void)n; +#endif + { /* Category 1: SHA-256 -- use sha256_2 (T_l must not collide with * sha256 which is used by F and H). */ if (key->hash.sha2.sha256_2_inited) { @@ -1115,6 +1142,7 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, SLHDSA_HAC_SZ); } } +#ifdef WC_SLHDSA_NEED_SHA512 else { /* Categories 3, 5: SHA-512 -- use sha512_2 (T_l must not collide * with sha512 which is used by H). */ @@ -1130,6 +1158,7 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, SLHDSA_HAC_SZ); } } +#endif return ret; } @@ -1144,12 +1173,17 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, static int slhdsakey_hash_update_sha2(SlhDsaKey* key, const byte* data, word32 len) { - if (key->params->n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (key->params->n == WC_SLHDSA_N_128) +#endif + { return wc_Sha256Update(&key->hash.sha2.sha256_2, data, len); } +#ifdef WC_SLHDSA_NEED_SHA512 else { return wc_Sha512Update(&key->hash.sha2.sha512_2, data, len); } +#endif } /* SHA2 T_l streaming: finalize. @@ -1164,13 +1198,17 @@ static int slhdsakey_hash_final_sha2(SlhDsaKey* key, byte* hash, word32 len) int ret; byte n = key->params->n; - if (n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (n == WC_SLHDSA_N_128) +#endif + { byte digest[WC_SHA256_DIGEST_SIZE]; ret = wc_Sha256Final(&key->hash.sha2.sha256_2, digest); if (ret == 0) { XMEMCPY(hash, digest, (len < n) ? len : n); } } +#ifdef WC_SLHDSA_NEED_SHA512 else { byte digest[WC_SHA512_DIGEST_SIZE]; ret = wc_Sha512Final(&key->hash.sha2.sha512_2, digest); @@ -1178,6 +1216,7 @@ static int slhdsakey_hash_final_sha2(SlhDsaKey* key, byte* hash, word32 len) XMEMCPY(hash, digest, (len < n) ? len : n); } } +#endif return ret; } @@ -1188,16 +1227,19 @@ static int slhdsakey_hash_final_sha2(SlhDsaKey* key, byte* hash, word32 len) */ static void slhdsakey_hash_free_sha2(SlhDsaKey* key) { - byte n = key->params->n; - - if (n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (key->params->n == WC_SLHDSA_N_128) +#endif + { wc_Sha256Free(&key->hash.sha2.sha256_2); key->hash.sha2.sha256_2_inited = 0; } +#ifdef WC_SLHDSA_NEED_SHA512 else { wc_Sha512Free(&key->hash.sha2.sha512_2); key->hash.sha2.sha512_2_inited = 0; } +#endif return; } @@ -1221,7 +1263,9 @@ static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed, int ret = 0; word32 counter = 0; word32 done = 0; +#ifdef WC_SLHDSA_NEED_SHA512 byte n = key->params->n; +#endif while ((ret == 0) && (done < outLen)) { byte cBuf[4]; @@ -1229,7 +1273,10 @@ static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed, c32toa(counter, cBuf); - if (n == WC_SLHDSA_N_128) { + #ifdef WC_SLHDSA_NEED_SHA512 + if (n == WC_SLHDSA_N_128) + #endif + { /* Category 1: MGF1-SHA-256. */ byte digest[WC_SHA256_DIGEST_SIZE]; word32 cpLen = (left < WC_SHA256_DIGEST_SIZE) ? @@ -1254,6 +1301,7 @@ static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed, done += cpLen; } } + #ifdef WC_SLHDSA_NEED_SHA512 else { /* Categories 3, 5: MGF1-SHA-512. */ byte digest[WC_SHA512_DIGEST_SIZE]; @@ -1279,6 +1327,7 @@ static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed, done += cpLen; } } + #endif counter++; } @@ -1311,6 +1360,7 @@ static int slhdsakey_prf_msg_sha2(SlhDsaKey* key, const byte* sk_prf, int ret; Hmac hmac; int hmacType; +#ifdef WC_SLHDSA_NEED_SHA512 byte digest[WC_SHA512_DIGEST_SIZE]; if (n == WC_SLHDSA_N_128) { @@ -1319,6 +1369,11 @@ static int slhdsakey_prf_msg_sha2(SlhDsaKey* key, const byte* sk_prf, else { hmacType = WC_SHA512; } +#else + byte digest[WC_SHA256_DIGEST_SIZE]; + + hmacType = WC_SHA256; +#endif ret = wc_HmacInit(&hmac, key->heap, INVALID_DEVID); if (ret == 0) { @@ -1375,7 +1430,10 @@ static int slhdsakey_h_msg_sha2(SlhDsaKey* key, const byte* r, const byte* pk_seed = key->sk + 2 * n; const byte* pk_root = key->sk + 3 * n; - if (n == WC_SLHDSA_N_128) { +#ifdef WC_SLHDSA_NEED_SHA512 + if (n == WC_SLHDSA_N_128) +#endif + { /* Category 1: SHA-256 + MGF1-SHA-256. */ byte innerHash[WC_SHA256_DIGEST_SIZE]; /* Seed for MGF1: R || PK.seed || innerHash. */ @@ -1418,6 +1476,7 @@ static int slhdsakey_h_msg_sha2(SlhDsaKey* key, const byte* r, 2U * n + WC_SHA256_DIGEST_SIZE, md, mdLen); } } +#ifdef WC_SLHDSA_NEED_SHA512 else { /* Categories 3, 5: SHA-512 + MGF1-SHA-512. */ byte innerHash[WC_SHA512_DIGEST_SIZE]; @@ -1461,6 +1520,7 @@ static int slhdsakey_h_msg_sha2(SlhDsaKey* key, const byte* r, 2U * n + WC_SHA512_DIGEST_SIZE, md, mdLen); } } +#endif return ret; } @@ -1471,11 +1531,12 @@ static int slhdsakey_h_msg_sha2(SlhDsaKey* key, const byte* r, * Dispatching Hash Macros ******************************************************************************/ -/* When WOLFSSL_SLHDSA_SHA2 is defined, macros dispatch between SHAKE and SHA2 - * based on the key's parameter set. When not defined, macros call SHAKE - * directly (zero overhead). */ +/* When both SHAKE and SHA2 parameter sets are compiled in, macros dispatch + * between them based on the key's parameter set. When only one family is + * compiled in, macros call it directly (zero overhead) and the other family's + * hash code is not referenced. */ -#ifdef WOLFSSL_SLHDSA_SHA2 +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_NO_SHAKE) /* SHAKE wrapper functions for SHA2 dispatch macros. */ static int slhdsakey_hash_f_shake(SlhDsaKey* key, const byte* pk_seed, @@ -1545,6 +1606,21 @@ static int slhdsakey_hash_prf_shake(SlhDsaKey* key, const byte* pk_seed, slhdsakey_hash_h_2_sha2(k, pk_seed, adrs, m1, m2, n, o) : \ slhdsakey_hash_h_2_shake(k, pk_seed, adrs, m1, m2, n, o)) +#elif defined(WOLFSSL_SLHDSA_SHA2) + +/* Only SHA2 parameter sets compiled in. */ +#define HASH_PRF(k, pk_seed, sk_seed, adrs, n, o) \ + slhdsakey_hash_prf_sha2(k, pk_seed, sk_seed, adrs, n, o) + +#define HASH_F(k, pk_seed, adrs, m, n, o) \ + slhdsakey_hash_f_sha2(k, pk_seed, adrs, m, n, o) + +#define HASH_H(k, pk_seed, adrs, node, n, o) \ + slhdsakey_hash_h_sha2(k, pk_seed, adrs, node, n, o) + +#define HASH_H_2(k, pk_seed, adrs, m1, m2, n, o) \ + slhdsakey_hash_h_2_sha2(k, pk_seed, adrs, m1, m2, n, o) + #else /* !WOLFSSL_SLHDSA_SHA2 */ #ifndef WOLFSSL_WC_SLHDSA_SMALL @@ -1600,7 +1676,7 @@ static int slhdsakey_hash_prf_shake(SlhDsaKey* key, const byte* pk_seed, /* T_l streaming dispatch macros for the secondary hash (used by WOTS+ pk * compression and FORS root computation). */ -#ifdef WOLFSSL_SLHDSA_SHA2 +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_NO_SHAKE) #define HASH_T_START_ADDR(k, pk_seed, adrs, n) \ (SLHDSA_IS_SHA2((k)->params->param) ? \ @@ -1622,6 +1698,20 @@ static int slhdsakey_hash_prf_shake(SlhDsaKey* key, const byte* pk_seed, slhdsakey_hash_free_sha2(k) : \ slhdsakey_hash_free(&(k)->hash.shk.shake2)) +#elif defined(WOLFSSL_SLHDSA_SHA2) + +#define HASH_T_START_ADDR(k, pk_seed, adrs, n) \ + slhdsakey_hash_start_addr_sha2(k, pk_seed, adrs, n) + +#define HASH_T_UPDATE(k, d, l) \ + slhdsakey_hash_update_sha2(k, d, l) + +#define HASH_T_FINAL(k, o, l) \ + slhdsakey_hash_final_sha2(k, o, l) + +#define HASH_T_FREE(k) \ + slhdsakey_hash_free_sha2(k) + #else #define HASH_T_START_ADDR(k, pk_seed, adrs, n) \ @@ -1638,6 +1728,7 @@ static int slhdsakey_hash_prf_shake(SlhDsaKey* key, const byte* pk_seed, #endif /* WOLFSSL_SLHDSA_SHA2 */ +#ifndef WOLFSSL_SLHDSA_NO_SHAKE /* Start hashing with SHAKE-256. * * @param [in] shake SHAKE-256 object. @@ -1743,6 +1834,7 @@ static void slhdsakey_hash_free(wc_Shake* shake) { wc_Shake256_Free(shake); } +#endif /* !WOLFSSL_SLHDSA_NO_SHAKE */ /****************************************************************************** * Conversion functions @@ -8624,14 +8716,19 @@ int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap, ret = wc_InitSha256(&key->hash.sha2.sha256); if (ret == 0) key->hash.sha2.sha256_inited = 1; + #ifdef WC_SLHDSA_NEED_SHA512 if ((ret == 0) && (key->params->n > 16)) { ret = wc_InitSha512(&key->hash.sha2.sha512); if (ret == 0) key->hash.sha2.sha512_inited = 1; } + #endif } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* Initialize SHAKE-256 objects. */ ret = wc_InitShake256(&key->hash.shk.shake, key->heap, @@ -8641,6 +8738,7 @@ int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap, INVALID_DEVID); } } +#endif } (void)devId; @@ -8767,6 +8865,7 @@ void wc_SlhDsaKey_Free(SlhDsaKey* key) wc_Sha256Free(&key->hash.sha2.sha256_mid); key->hash.sha2.sha256_mid_inited = 0; } + #ifdef WC_SLHDSA_NEED_SHA512 if (key->hash.sha2.sha512_inited) { wc_Sha512Free(&key->hash.sha2.sha512); key->hash.sha2.sha512_inited = 0; @@ -8779,14 +8878,19 @@ void wc_SlhDsaKey_Free(SlhDsaKey* key) wc_Sha512Free(&key->hash.sha2.sha512_mid); key->hash.sha2.sha512_mid_inited = 0; } + #endif } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* Dispose of the SHAKE-256 objects. */ wc_Shake256_Free(&key->hash.shk.shake2); wc_Shake256_Free(&key->hash.shk.shake); } +#endif } #ifdef WOLF_PRIVATE_KEY_ID @@ -9258,8 +9362,11 @@ static int slhdsakey_sign_internal_msg(SlhDsaKey* key, const byte* m, sig += n; } } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* SHAKE: PRF_msg = SHAKE256(SK.prf || opt_rand || M, 8n). */ { @@ -9292,6 +9399,7 @@ static int slhdsakey_sign_internal_msg(SlhDsaKey* key, const byte* m, key->params->dl3); } } +#endif if (ret == 0) { ret = slhdsakey_sign(key, md, sig); } @@ -9384,8 +9492,11 @@ static int slhdsakey_sign_external(SlhDsaKey* key, const byte* ctx, byte ctxSz, sig += n; } } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* SHAKE: PRF_msg streaming with M' = hdr || ctx || msg. */ ret = slhdsakey_hash_start(&key->hash.shk.shake, key->sk + n, n); @@ -9430,6 +9541,7 @@ static int slhdsakey_sign_external(SlhDsaKey* key, const byte* ctx, byte ctxSz, key->params->dl3); } } +#endif if (ret == 0) { /* Alg 19. Steps 7-19 */ ret = slhdsakey_sign(key, md, sig); @@ -9937,7 +10049,9 @@ int wc_SlhDsaKey_Verify(SlhDsaKey* key, const byte* ctx, byte ctxSz, #else if (ret == 0) { byte md[SLHDSA_MAX_MD]; + #ifndef WOLFSSL_SLHDSA_NO_SHAKE byte n = key->params->n; + #endif byte hdr[2]; /* Alg 24, Step 4: Make M' header. */ @@ -9951,8 +10065,11 @@ int wc_SlhDsaKey_Verify(SlhDsaKey* key, const byte* ctx, byte ctxSz, md, (word32)key->params->dl1 + key->params->dl2 + key->params->dl3); } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* SHAKE: H_msg streaming. */ ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n); @@ -9976,6 +10093,7 @@ int wc_SlhDsaKey_Verify(SlhDsaKey* key, const byte* ctx, byte ctxSz, key->params->dl3); } } +#endif if (ret == 0) { /* Alg 24, Step 5: Verify M'. * Alg 20, Steps 4,6-18: Verify digest. */ @@ -10047,7 +10165,9 @@ int wc_SlhDsaKey_VerifyMsg(SlhDsaKey* key, const byte* mprime, #else if (ret == 0) { byte md[SLHDSA_MAX_MD]; + #ifndef WOLFSSL_SLHDSA_NO_SHAKE byte n = key->params->n; + #endif #ifdef WOLFSSL_SLHDSA_SHA2 if (SLHDSA_IS_SHA2(key->params->param)) { @@ -10058,8 +10178,11 @@ int wc_SlhDsaKey_VerifyMsg(SlhDsaKey* key, const byte* mprime, md, (word32)key->params->dl1 + key->params->dl2 + key->params->dl3); } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* SHAKE: H_msg = SHAKE(R || PK.seed || PK.root || M). */ ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n); @@ -10077,6 +10200,7 @@ int wc_SlhDsaKey_VerifyMsg(SlhDsaKey* key, const byte* mprime, key->params->dl3); } } +#endif if (ret == 0) { ret = slhdsakey_verify(key, md, sig); } @@ -10092,14 +10216,21 @@ int wc_SlhDsaKey_VerifyMsg(SlhDsaKey* key, const byte* mprime, * SLHDSA_OID_MAX_LEN + WC_MAX_DIGEST_SIZE. The PHM buffer fits in * WC_MAX_DIGEST_SIZE bytes because slhdsakey_validate_prehash enforces * hashSz == expectedLen[hashType] for every supported hashType and every - * supported expectedLen is <= WC_MAX_DIGEST_SIZE. The largest FIPS 205 - * approved PHM is 64 bytes (SHA-512 digest size, also the SHAKE256 PHM - * length fixed at 512 bits per Section 10.2.2). The static assert below - * catches a future hash being added whose digest exceeds the bound. The - * literal 64 is used directly because WC_SHA512_DIGEST_SIZE is only - * defined when SHA-512 is compiled in. */ + * supported expectedLen is <= WC_MAX_DIGEST_SIZE. Only pre-hash functions + * that are compiled in are supported, so the largest PHM is 64 bytes when + * SHA-512 or SHA-3 is (SHA-512, SHA3-512 and SHAKE256 - fixed at 512 bits per + * Section 10.2.2), 48 bytes with SHA-384 and otherwise 32 bytes (SHA-256). + * The static assert below catches a future hash being added whose digest + * exceeds the bound. Literals are used directly because the digest size + * defines are only defined when the hash is compiled in. */ #define SLHDSA_OID_MAX_LEN 11 -#define SLHDSA_LARGEST_APPROVED_PHM_LEN 64 +#if defined(WOLFSSL_SHA512) || defined(WOLFSSL_SHA3) + #define SLHDSA_LARGEST_APPROVED_PHM_LEN 64 +#elif defined(WOLFSSL_SHA384) + #define SLHDSA_LARGEST_APPROVED_PHM_LEN 48 +#else + #define SLHDSA_LARGEST_APPROVED_PHM_LEN 32 +#endif #define SLHDSA_PHMSG_MAX_LEN (SLHDSA_OID_MAX_LEN + \ WC_MAX_DIGEST_SIZE) wc_static_assert(WC_MAX_DIGEST_SIZE >= SLHDSA_LARGEST_APPROVED_PHM_LEN); @@ -10486,8 +10617,11 @@ static int slhdsakey_signhash_external(SlhDsaKey* key, const byte* ctx, sig += n; } } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* SHAKE: PRF_msg streaming. */ ret = slhdsakey_hash_start(&key->hash.shk.shake, key->sk + n, n); @@ -10538,6 +10672,7 @@ static int slhdsakey_signhash_external(SlhDsaKey* key, const byte* ctx, key->params->dl3); } } +#endif if (ret == 0) { /* Alg 19. Steps 7-19 */ ret = slhdsakey_sign(key, md, sig); @@ -10956,7 +11091,9 @@ int wc_SlhDsaKey_VerifyHash(SlhDsaKey* key, const byte* ctx, byte ctxSz, } } if (ret == 0) { + #ifndef WOLFSSL_SLHDSA_NO_SHAKE byte n = key->params->n; + #endif byte md[SLHDSA_MAX_MD]; byte hdr[2]; @@ -10977,8 +11114,11 @@ int wc_SlhDsaKey_VerifyHash(SlhDsaKey* key, const byte* ctx, byte ctxSz, phMsgLen, md, (word32)key->params->dl1 + key->params->dl2 + key->params->dl3); } + #ifndef WOLFSSL_SLHDSA_NO_SHAKE else + #endif #endif +#ifndef WOLFSSL_SLHDSA_NO_SHAKE { /* SHAKE: H_msg streaming. */ ret = slhdsakey_hash_start(&key->hash.shk.shake, sig, n); @@ -11005,6 +11145,7 @@ int wc_SlhDsaKey_VerifyHash(SlhDsaKey* key, const byte* ctx, byte ctxSz, key->params->dl3); } } +#endif if (ret == 0) { /* Alg 25, Step 21: Verify M'. * Alg 20, Steps 4,6-18: Verify digest. */ @@ -11496,6 +11637,7 @@ int wc_SlhDsaKey_SigSizeFromParam(enum SlhDsaParam param) return ret; } +#ifndef NO_ASN /* Find SlhDsaParameters entry for a given param enum. */ static const SlhDsaParameters* slhdsa_find_params(enum SlhDsaParam param) { @@ -12021,5 +12163,6 @@ int wc_SlhDsaKey_PrivateKeyToDer(SlhDsaKey* key, byte* output, word32 inLen) } #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ #endif /* WC_ENABLE_ASYM_KEY_EXPORT */ +#endif /* !NO_ASN */ #endif /* WOLFSSL_HAVE_SLHDSA */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index bfd796f1aa1..ca24cc7fa62 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -5663,8 +5663,9 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #define WOLFSSL_W64_WRAPPER #endif -/* wc_xmss and wc_lms require these misc.c functions. */ -#if defined(WOLFSSL_HAVE_XMSS) || defined(WOLFSSL_HAVE_LMS) +/* wc_xmss, wc_lms and wc_slhdsa require these misc.c functions. */ +#if defined(WOLFSSL_HAVE_XMSS) || defined(WOLFSSL_HAVE_LMS) || \ + defined(WOLFSSL_HAVE_SLHDSA) #undef WOLFSSL_NO_INT_ENCODE #undef WOLFSSL_NO_INT_DECODE #endif diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index c9ea9c260be..1c13f4c7379 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -742,6 +742,15 @@ enum SlhDsaParam { #define SLHDSA_IS_SHA2(p) 0 #endif +/* SHA-512 is used only by the SHA2 parameter sets of categories 3 and 5 + * (n = 24 and 32). The category 1 SHA2 sets use SHA-256 alone, so without + * the larger SHA2 sets no SHA-512 code or state is compiled in. */ +#if defined(WOLFSSL_SLHDSA_SHA2) && \ + (!defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_192) || \ + !defined(WOLFSSL_SLHDSA_PARAM_NO_SHA2_256)) + #define WC_SLHDSA_NEED_SHA512 +#endif + /* Pre-defined parameter values. */ typedef struct SlhDsaParameters { enum SlhDsaParam param; /* Parameter set id. */ @@ -801,32 +810,40 @@ struct SlhDsaKey { byte sk[32 * 4]; /* Hash objects for SHAKE or SHA2. */ union { +#ifndef WOLFSSL_SLHDSA_NO_SHAKE struct { /* Primary SHAKE-256 object. */ wc_Shake shake; /* Secondary SHAKE-256 object (T_l streaming). */ wc_Shake shake2; } shk; +#endif #ifdef WOLFSSL_SLHDSA_SHA2 struct { /* F, PRF (all cats) + H, T_l (cat 1). */ wc_Sha256 sha256; /* T_l streaming (cat 1), H_msg scratch. */ wc_Sha256 sha256_2; + #ifdef WC_SLHDSA_NEED_SHA512 /* H, T_l (cats 3, 5). */ wc_Sha512 sha512; /* H_msg streaming (cats 3, 5). */ wc_Sha512 sha512_2; + #endif /* Pre-computed midstate: PK.seed || pad(64 - n). */ wc_Sha256 sha256_mid; + #ifdef WC_SLHDSA_NEED_SHA512 /* Pre-computed midstate: PK.seed || pad(128 - n). */ wc_Sha512 sha512_mid; + #endif WC_BITFIELD sha256_inited:1; WC_BITFIELD sha256_2_inited:1; WC_BITFIELD sha256_mid_inited:1; + #ifdef WC_SLHDSA_NEED_SHA512 WC_BITFIELD sha512_inited:1; WC_BITFIELD sha512_2_inited:1; WC_BITFIELD sha512_mid_inited:1; + #endif } sha2; #endif } hash; @@ -921,6 +938,7 @@ WOLFSSL_API int wc_SlhDsaKey_PublicSizeFromParam(enum SlhDsaParam param); WOLFSSL_API int wc_SlhDsaKey_SigSizeFromParam(enum SlhDsaParam param); /* DER encode/decode */ +#ifndef NO_ASN #ifndef WOLFSSL_SLHDSA_VERIFY_ONLY WOLFSSL_API int wc_SlhDsaKey_PrivateKeyDecode(const byte* input, word32* inOutIdx, SlhDsaKey* key, word32 inSz); @@ -940,6 +958,7 @@ WOLFSSL_API int wc_SlhDsaKey_PrivateKeyToDer(SlhDsaKey* key, byte* output, WOLFSSL_API int wc_SlhDsaKey_PublicKeyToDer(SlhDsaKey* key, byte* output, word32 inLen, int withAlg); #endif +#endif /* !NO_ASN */ #endif /* WOLFSSL_HAVE_SLHDSA */ From a8662547fab7ff0d34097369078ba6a07f31ebc6 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 26 Oct 2026 09:57:12 +0000 Subject: [PATCH 2/4] SLH-DSA: fold the hash family checks at compile time Review follow-ups for the NO_ASN and hash family gating: - SLHDSA_IS_SHA2() is now a constant when only SHA2 parameter sets are compiled in. The runtime family checks then fold away like the HASH_* macros, and no SHA2-only path can fall through to a SHAKE branch that is compiled out. - Add SLHDSA_SHA2_USE_SHA256(n) so the SHA2 helpers keep a plain if/else with only the SHA-512 arm under WC_SLHDSA_NEED_SHA512, instead of an #ifdef'd if and a bare block. - Replace the pre-hash size static assert, which now mirrored WC_MAX_DIGEST_SIZE, with a check in slhdsakey_validate_prehash() that the expected PHM fits in WC_MAX_DIGEST_SIZE. - Update the white-box test rationale: in 128-bit-only builds the SHA-512 decisions are compiled out rather than unreachable. --- .../test_slhdsa_hash_fault_whitebox.c | 37 ++++---- tests/unit-mcdc/test_slhdsa_whitebox.c | 22 +++-- wolfcrypt/src/wc_slhdsa.c | 92 +++++++------------ wolfssl/wolfcrypt/wc_slhdsa.h | 7 +- 4 files changed, 73 insertions(+), 85 deletions(-) diff --git a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c index 2cd5ed616dd..4fd319c31c6 100644 --- a/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_hash_fault_whitebox.c @@ -69,18 +69,17 @@ * ImportPublic() (whose SHA-2 midstate precompute is the only caller of * slhdsakey_precompute_sha2_midstates()). * + * NOT COMPILED HERE: the SHA-512 code is only built with a category 3/5 SHA2 + * parameter set (WC_SLHDSA_NEED_SHA512), and this module's base config + * compiles ONLY the 128-bit sets (WOLFSSL_SLHDSA_PARAM_NO_192/256 and + * *_NO_SHA2_192/256), so these decisions are not in the build to cover: + * - `(ret == 0) && (n > 16)` / `(ret == 0) && (key->params->n > 16)` in + * slhdsakey_precompute_sha2_midstates() and wc_SlhDsaKey_Init(). + * - slhdsakey_h_msg_sha2()'s `(ret == 0) && (hdr != NULL)` and + * `(ret == 0) && (ctxSz > 0) && (ctx != NULL)` in its SHA-512 arm. + * * NOT REACHABLE HERE (documented residuals, mirrored in * the exclusion record): - * - `(ret == 0) && (n > 16)` / `(ret == 0) && (key->params->n > 16)`: the - * second operand needs a category 3/5 parameter set, and this module's base - * config compiles ONLY the 128-bit sets (WOLFSSL_SLHDSA_PARAM_NO_192/256 - * and *_NO_SHA2_192/256), so n is always 16. NEITHER operand pairs: with - * `n > 16` pinned false the decision is false in every vector, so flipping - * `ret == 0` cannot change the outcome either. - * - slhdsakey_h_msg_sha2()'s `(ret == 0) && (hdr != NULL)` and - * `(ret == 0) && (ctxSz > 0) && (ctx != NULL)`: those two live in the - * `else` arm of `if (n == WC_SLHDSA_N_128)`, i.e. the SHA-512 category 3/5 - * arm, which the 128-bit-only parameter table can never select. * - `(ret != 0) && WC_VAR_OK(sk)`: without WOLFSSL_SMALL_STACK, types.h * defines WC_VAR_OK(x) as the literal 1, so the operand is not merely * undriven -- it has no false side to drive. No slhdsa variant sets @@ -94,8 +93,9 @@ * skip stub. main() always returns 0. */ -/* wc_SlhDsaKey_Init()'s `(ret == 0) && (key->params->n > 16)` takes its ret - * from wc_InitSha256() and from nothing else, so the SHA context-init family +/* wc_SlhDsaKey_Init()'s hash-object init checks take their ret from + * wc_InitSha256() (and, with a category 3/5 SHA2 set compiled in, feed + * `(ret == 0) && (key->params->n > 16)`), so the SHA context-init family * has to be interposed too. It is opt-in, and armed only inside * wb_init_rows(); every other sweep here builds its keys while disarmed. */ #define MCDC_FH_WITH_SHA_INIT @@ -423,8 +423,9 @@ static void wb_sweep_checkkey(int param) /* Raw import: slhdsakey_precompute_sha2_midstates() is reached only from * ImportPrivate / ImportPublic / MakeKeyWithRandom, and an armed import is the - * cheapest way to make its `(ret == 0) && (n > 16)` guard see ret != 0 -- the - * import does almost nothing before the precompute, so a low fault index lands + * cheapest way to make its ret checks (and, with a category 3/5 SHA2 set + * compiled in, its `(ret == 0) && (n > 16)` guard) see ret != 0 -- the import + * does almost nothing before the precompute, so a low fault index lands * squarely inside it. */ static void wb_sweep_import(int param) { @@ -458,9 +459,11 @@ static void wb_sweep_import(int param) printf(" [wb] param %d: import sweep: %ld points\n", param, points); } -/* wc_SlhDsaKey_Init()'s `(ret == 0) && (key->params->n > 16)` -- the only - * assignment to ret before it is wc_InitSha256()'s, so the SHA context-init - * interposer is what drives the first operand false. */ +/* wc_SlhDsaKey_Init()'s hash-object init checks -- for the SHA2 sets the + * first assignment to ret is wc_InitSha256()'s, so the SHA context-init + * interposer is what drives `ret == 0` false, including in + * `(ret == 0) && (key->params->n > 16)` when a category 3/5 SHA2 set is + * compiled in. */ static void wb_init_rows(int param) { SlhDsaKey k2; diff --git a/tests/unit-mcdc/test_slhdsa_whitebox.c b/tests/unit-mcdc/test_slhdsa_whitebox.c index 0bae73f6f13..48c29697ef0 100644 --- a/tests/unit-mcdc/test_slhdsa_whitebox.c +++ b/tests/unit-mcdc/test_slhdsa_whitebox.c @@ -154,17 +154,20 @@ static void wb_ha_encode(void) * SHA-2 message-hash static functions: gap-closing supplement (see the uncovered-condition report). * * The tests/api DecisionCoverage additions close every arg-check reachable - * from the public API. What's left needs either a real n>16 SHA-2 param - * (every suite variant restricts to 128-bit only, see config_base's - * notes) or a ctx/ctxSz combination the public API itself rejects before - * ever reaching these static functions (ctx==NULL with ctxSz>0 is BAD_FUNC_ARG at - * the wc_SlhDsaKey_Sign/Verify layer). Both are driven directly here. + * from the public API. What's left is either the n>16 SHA-512 code or a + * ctx/ctxSz combination the public API itself rejects before ever reaching + * these static functions (ctx==NULL with ctxSz>0 is BAD_FUNC_ARG at the + * wc_SlhDsaKey_Sign/Verify layer). Both are driven directly here. The SHA-512 + * code is only compiled in with a category 3/5 SHA2 parameter set + * (WC_SLHDSA_NEED_SHA512), so in the 128-bit-only suite variants (see + * config_base's notes) it is not in the build at all. * ------------------------------------------------------------------------- */ #ifdef WOLFSSL_SLHDSA_SHA2 /* slhdsakey_precompute_sha2_midstates(): "(ret==0) && (n>16)" -- n>16 - * (SHA-512 midstate) half. A locally-built SlhDsaParameters with n=24 - * exercises it without any variant ever compiling a real n>16 param. */ + * (SHA-512 midstate) half, compiled in only with WC_SLHDSA_NEED_SHA512. A + * locally-built SlhDsaParameters with n=24 exercises it without needing a + * real category 3/5 key. */ static void wb_precompute_sha2_midstates(void) { SlhDsaKey key; @@ -261,8 +264,9 @@ static void wb_prf_msg_sha2_ctx(void) /* slhdsakey_h_msg_sha2(): SHA-256 branch's "ctx!=NULL" pair (same rationale * as wb_prf_msg_sha2_ctx above) plus the SHA-512 branch (n>16, category 3/5) - * hdr/ctx/ctxSz decisions -- unreachable from the API for the same reason as - * wb_precompute_sha2_midstates (no variant ever builds an n>16 param). */ + * hdr/ctx/ctxSz decisions, when WC_SLHDSA_NEED_SHA512 compiles that branch + * in -- driven with a crafted n=24 entry for the same reason as + * wb_precompute_sha2_midstates. */ static void wb_h_msg_sha2_ctx_pairs(void) { SlhDsaKey key; diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index d5cd32c3ba2..2598fa5ed7a 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -726,6 +726,15 @@ static int slhdsakey_hash_shake_4(wc_Shake* shake, const byte* data1, #ifdef WOLFSSL_SLHDSA_SHA2 +/* Whether a SHA2 parameter set with hash size n uses SHA-256 (category 1) + * rather than SHA-512 (categories 3 and 5) for H, T_l, H_msg and PRF_msg. + * Always SHA-256 when no category 3 or 5 SHA2 set is compiled in. */ +#ifdef WC_SLHDSA_NEED_SHA512 + #define SLHDSA_SHA2_USE_SHA256(n) ((n) == WC_SLHDSA_N_128) +#else + #define SLHDSA_SHA2_USE_SHA256(n) ((void)(n), 1) +#endif + /* Size of compressed HashAddress (ADRS^c) per FIPS 205 Section 11.2. */ #define SLHDSA_HAC_SZ 22 @@ -892,10 +901,7 @@ static int slhdsakey_hash_h_sha2(SlhDsaKey* key, const byte* pk_seed, /* Encode compressed address. */ HA_Encode_Compressed(adrs, address); -#ifdef WC_SLHDSA_NEED_SHA512 - if (n == WC_SLHDSA_N_128) -#endif - { + if (SLHDSA_SHA2_USE_SHA256(n)) { /* Category 1: use SHA-256. */ byte digest[WC_SHA256_DIGEST_SIZE]; @@ -976,10 +982,7 @@ static int slhdsakey_hash_h_2_sha2(SlhDsaKey* key, const byte* pk_seed, /* Encode compressed address. */ HA_Encode_Compressed(adrs, address); -#ifdef WC_SLHDSA_NEED_SHA512 - if (n == WC_SLHDSA_N_128) -#endif - { + if (SLHDSA_SHA2_USE_SHA256(n)) { /* Category 1: use SHA-256. */ byte digest[WC_SHA256_DIGEST_SIZE]; @@ -1122,12 +1125,7 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, HA_Encode_Compressed(adrs, address); -#ifdef WC_SLHDSA_NEED_SHA512 - if (n == WC_SLHDSA_N_128) -#else - (void)n; -#endif - { + if (SLHDSA_SHA2_USE_SHA256(n)) { /* Category 1: SHA-256 -- use sha256_2 (T_l must not collide with * sha256 which is used by F and H). */ if (key->hash.sha2.sha256_2_inited) { @@ -1173,10 +1171,7 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, static int slhdsakey_hash_update_sha2(SlhDsaKey* key, const byte* data, word32 len) { -#ifdef WC_SLHDSA_NEED_SHA512 - if (key->params->n == WC_SLHDSA_N_128) -#endif - { + if (SLHDSA_SHA2_USE_SHA256(key->params->n)) { return wc_Sha256Update(&key->hash.sha2.sha256_2, data, len); } #ifdef WC_SLHDSA_NEED_SHA512 @@ -1198,10 +1193,7 @@ static int slhdsakey_hash_final_sha2(SlhDsaKey* key, byte* hash, word32 len) int ret; byte n = key->params->n; -#ifdef WC_SLHDSA_NEED_SHA512 - if (n == WC_SLHDSA_N_128) -#endif - { + if (SLHDSA_SHA2_USE_SHA256(n)) { byte digest[WC_SHA256_DIGEST_SIZE]; ret = wc_Sha256Final(&key->hash.sha2.sha256_2, digest); if (ret == 0) { @@ -1227,10 +1219,9 @@ static int slhdsakey_hash_final_sha2(SlhDsaKey* key, byte* hash, word32 len) */ static void slhdsakey_hash_free_sha2(SlhDsaKey* key) { -#ifdef WC_SLHDSA_NEED_SHA512 - if (key->params->n == WC_SLHDSA_N_128) -#endif - { + byte n = key->params->n; + + if (SLHDSA_SHA2_USE_SHA256(n)) { wc_Sha256Free(&key->hash.sha2.sha256_2); key->hash.sha2.sha256_2_inited = 0; } @@ -1263,9 +1254,7 @@ static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed, int ret = 0; word32 counter = 0; word32 done = 0; -#ifdef WC_SLHDSA_NEED_SHA512 byte n = key->params->n; -#endif while ((ret == 0) && (done < outLen)) { byte cBuf[4]; @@ -1273,10 +1262,7 @@ static int slhdsakey_mgf1_sha2(SlhDsaKey* key, const byte* seed, c32toa(counter, cBuf); - #ifdef WC_SLHDSA_NEED_SHA512 - if (n == WC_SLHDSA_N_128) - #endif - { + if (SLHDSA_SHA2_USE_SHA256(n)) { /* Category 1: MGF1-SHA-256. */ byte digest[WC_SHA256_DIGEST_SIZE]; word32 cpLen = (left < WC_SHA256_DIGEST_SIZE) ? @@ -1362,17 +1348,17 @@ static int slhdsakey_prf_msg_sha2(SlhDsaKey* key, const byte* sk_prf, int hmacType; #ifdef WC_SLHDSA_NEED_SHA512 byte digest[WC_SHA512_DIGEST_SIZE]; +#else + byte digest[WC_SHA256_DIGEST_SIZE]; +#endif - if (n == WC_SLHDSA_N_128) { + if (SLHDSA_SHA2_USE_SHA256(n)) { hmacType = WC_SHA256; } +#ifdef WC_SLHDSA_NEED_SHA512 else { hmacType = WC_SHA512; } -#else - byte digest[WC_SHA256_DIGEST_SIZE]; - - hmacType = WC_SHA256; #endif ret = wc_HmacInit(&hmac, key->heap, INVALID_DEVID); @@ -1430,10 +1416,7 @@ static int slhdsakey_h_msg_sha2(SlhDsaKey* key, const byte* r, const byte* pk_seed = key->sk + 2 * n; const byte* pk_root = key->sk + 3 * n; -#ifdef WC_SLHDSA_NEED_SHA512 - if (n == WC_SLHDSA_N_128) -#endif - { + if (SLHDSA_SHA2_USE_SHA256(n)) { /* Category 1: SHA-256 + MGF1-SHA-256. */ byte innerHash[WC_SHA256_DIGEST_SIZE]; /* Seed for MGF1: R || PK.seed || innerHash. */ @@ -10215,25 +10198,13 @@ int wc_SlhDsaKey_VerifyMsg(SlhDsaKey* key, const byte* mprime, * input for the SHA-2 path is the concatenation OID || PHM, bounded by * SLHDSA_OID_MAX_LEN + WC_MAX_DIGEST_SIZE. The PHM buffer fits in * WC_MAX_DIGEST_SIZE bytes because slhdsakey_validate_prehash enforces - * hashSz == expectedLen[hashType] for every supported hashType and every - * supported expectedLen is <= WC_MAX_DIGEST_SIZE. Only pre-hash functions - * that are compiled in are supported, so the largest PHM is 64 bytes when - * SHA-512 or SHA-3 is (SHA-512, SHA3-512 and SHAKE256 - fixed at 512 bits per - * Section 10.2.2), 48 bytes with SHA-384 and otherwise 32 bytes (SHA-256). - * The static assert below catches a future hash being added whose digest - * exceeds the bound. Literals are used directly because the digest size - * defines are only defined when the hash is compiled in. */ + * hashSz == expectedLen[hashType] and rejects any expectedLen above + * WC_MAX_DIGEST_SIZE. That check catches a future hash being added whose + * digest exceeds the bound, and a WC_HASH_CUSTOM_MAX_DIGEST_SIZE set too + * small for the pre-hash functions compiled in. */ #define SLHDSA_OID_MAX_LEN 11 -#if defined(WOLFSSL_SHA512) || defined(WOLFSSL_SHA3) - #define SLHDSA_LARGEST_APPROVED_PHM_LEN 64 -#elif defined(WOLFSSL_SHA384) - #define SLHDSA_LARGEST_APPROVED_PHM_LEN 48 -#else - #define SLHDSA_LARGEST_APPROVED_PHM_LEN 32 -#endif #define SLHDSA_PHMSG_MAX_LEN (SLHDSA_OID_MAX_LEN + \ WC_MAX_DIGEST_SIZE) -wc_static_assert(WC_MAX_DIGEST_SIZE >= SLHDSA_LARGEST_APPROVED_PHM_LEN); #ifndef WOLF_CRYPTO_CB_ONLY_SLHDSA #ifdef WOLFSSL_SHA224 @@ -10369,6 +10340,8 @@ static int slhdsa_check_hash_for_n(enum wc_HashType hashType, byte n) * @param [out] oidLen Length of OID data for hash algorithm. * @return 0 on success. * @return BAD_LENGTH_E when hashSz does not equal the expected digest size. + * @return BUFFER_E when the expected digest size is larger than + * WC_MAX_DIGEST_SIZE. * @return NOT_COMPILED_IN when hash algorithm not supported. */ static int slhdsakey_validate_prehash(word32 hashSz, @@ -10471,6 +10444,11 @@ static int slhdsakey_validate_prehash(word32 hashSz, break; } + /* The PHM must fit in the WC_MAX_DIGEST_SIZE part of the pre-hash + * message buffers (SLHDSA_PHMSG_MAX_LEN). */ + if ((ret == 0) && (expectedLen > (word32)WC_MAX_DIGEST_SIZE)) { + ret = BUFFER_E; + } if ((ret == 0) && (hashSz != expectedLen)) { ret = BAD_LENGTH_E; } diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index 1c13f4c7379..6e4f034bb0c 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -735,8 +735,11 @@ enum SlhDsaParam { #error "WOLFSSL_HAVE_SLHDSA requires at least one parameter set" #endif -/* Helper macro to detect SHA2 parameter sets. */ -#ifdef WOLFSSL_SLHDSA_SHA2 +/* Helper macro to detect SHA2 parameter sets. Constant when only one hash + * family is compiled in. */ +#if defined(WOLFSSL_SLHDSA_SHA2) && defined(WOLFSSL_SLHDSA_NO_SHAKE) + #define SLHDSA_IS_SHA2(p) ((void)(p), 1) +#elif defined(WOLFSSL_SLHDSA_SHA2) #define SLHDSA_IS_SHA2(p) ((p) >= SLHDSA_SHA2_128S) #else #define SLHDSA_IS_SHA2(p) 0 From 7a3d23a4678195888e5e451118d275d4157dc13d Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 26 Oct 2026 15:19:30 +0000 Subject: [PATCH 3/4] SLH-DSA: drop cross-family re-init without SHAKE With only SHA2 parameter sets compiled in, SLHDSA_IS_SHA2() is constant, so the hash family re-init in wc_SlhDsaKey_PrivateKeyDecode() and wc_SlhDsaKey_PublicKeyDecode() and the SHAKE pre-hash choice in slhdsa_test() became dead code that clang -Wunreachable-code reports. The family can only change when both are compiled in, so compile them only then. --- wolfcrypt/src/wc_slhdsa.c | 10 ++++++---- wolfcrypt/test/test.c | 7 +++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index 2598fa5ed7a..c767acee12e 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -11711,7 +11711,8 @@ int wc_SlhDsaKey_PrivateKeyDecode(const byte* input, word32* inOutIdx, return ASN_PARSE_E; } -#ifdef WOLFSSL_SLHDSA_SHA2 +/* Only possible with both hash families compiled in. */ +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_NO_SHAKE) /* The key may have been initialised with a placeholder parameter set from a * different hash family (callers pass WC_SLHDSA_DEFAULT_PARAM, a SHAKE set * whenever any SHAKE parameter is built in). If the detected set uses a @@ -11734,7 +11735,7 @@ int wc_SlhDsaKey_PrivateKeyDecode(const byte* input, word32* inOutIdx, return ret; } } -#endif /* WOLFSSL_SLHDSA_SHA2 */ +#endif /* WOLFSSL_SLHDSA_SHA2 && !WOLFSSL_SLHDSA_NO_SHAKE */ /* RFC 9909: privateKey is a single OCTET STRING containing the raw key * (4*n bytes). Unlike Ed25519/Ed448, there is no nested inner OCTET @@ -11951,7 +11952,8 @@ int wc_SlhDsaKey_PublicKeyDecode(const byte* input, word32* inOutIdx, return ASN_PARSE_E; } -#ifdef WOLFSSL_SLHDSA_SHA2 +/* Only possible with both hash families compiled in. */ +#if defined(WOLFSSL_SLHDSA_SHA2) && !defined(WOLFSSL_SLHDSA_NO_SHAKE) /* The hash objects live in a union selected by family. Importing across * families writes the new family's state over the old one's, orphaning it, * so re-initialise for the detected set first. Mirrors the guard in @@ -11972,7 +11974,7 @@ int wc_SlhDsaKey_PublicKeyDecode(const byte* input, word32* inOutIdx, return ret; } } -#endif /* WOLFSSL_SLHDSA_SHA2 */ +#endif /* WOLFSSL_SLHDSA_SHA2 && !WOLFSSL_SLHDSA_NO_SHAKE */ oldFlags = key->flags; oldParams = key->params; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 7476b692473..4a6dd93653e 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -77748,8 +77748,11 @@ static wc_test_ret_t slhdsa_test_param(enum SlhDsaParam param) /* FIPS 205 sec. 10.2.2 approves SHA-256 only for category 1, so the * larger SHA-2 parameter sets pre-hash with SHA-512. */ #ifdef WOLFSSL_SLHDSA_SHA2 - enum wc_HashType phType = !SLHDSA_IS_SHA2(param) ? WC_HASH_TYPE_SHAKE256 - : (key->params->n == WC_SLHDSA_N_128) ? WC_HASH_TYPE_SHA256 + enum wc_HashType phType = + #ifndef WOLFSSL_SLHDSA_NO_SHAKE + !SLHDSA_IS_SHA2(param) ? WC_HASH_TYPE_SHAKE256 : + #endif + (key->params->n == WC_SLHDSA_N_128) ? WC_HASH_TYPE_SHA256 #ifdef WOLFSSL_SHA512 : WC_HASH_TYPE_SHA512; #else From 02bd8c2876e8c3613a9d6b0a89a185037fab7e98 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Mon, 26 Oct 2026 17:59:03 +0000 Subject: [PATCH 4/4] SLH-DSA: end slhdsakey_hash_update_sha2() with a return Without SHA-512 the SHA-256 branch is the only one left, so the function read as if control could reach its end without returning a value. Take the SHA-512 case as an early return instead and finish with the SHA-256 update, and say in the SLHDSA_SHA2_USE_SHA256() comment that it is constant true when no category 3 or 5 SHA2 set is compiled in. --- wolfcrypt/src/wc_slhdsa.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index c767acee12e..f60d92e1cce 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -728,7 +728,9 @@ static int slhdsakey_hash_shake_4(wc_Shake* shake, const byte* data1, /* Whether a SHA2 parameter set with hash size n uses SHA-256 (category 1) * rather than SHA-512 (categories 3 and 5) for H, T_l, H_msg and PRF_msg. - * Always SHA-256 when no category 3 or 5 SHA2 set is compiled in. */ + * Without a category 3 or 5 SHA2 set compiled in this is constant true, so the + * SHA-256 arm of each "if (SLHDSA_SHA2_USE_SHA256(n))" is always taken and its + * SHA-512 else arm is not compiled. */ #ifdef WC_SLHDSA_NEED_SHA512 #define SLHDSA_SHA2_USE_SHA256(n) ((n) == WC_SLHDSA_N_128) #else @@ -1171,14 +1173,12 @@ static int slhdsakey_hash_start_addr_sha2(SlhDsaKey* key, static int slhdsakey_hash_update_sha2(SlhDsaKey* key, const byte* data, word32 len) { - if (SLHDSA_SHA2_USE_SHA256(key->params->n)) { - return wc_Sha256Update(&key->hash.sha2.sha256_2, data, len); - } #ifdef WC_SLHDSA_NEED_SHA512 - else { + if (!SLHDSA_SHA2_USE_SHA256(key->params->n)) { return wc_Sha512Update(&key->hash.sha2.sha512_2, data, len); } #endif + return wc_Sha256Update(&key->hash.sha2.sha256_2, data, len); } /* SHA2 T_l streaming: finalize.