From 36fa68176de8f20e8342b508717c79dbbd43af39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 20 Jul 2026 19:19:30 +0200 Subject: [PATCH 01/11] Bound ECDSA r/s size in PSoC6 hardware verify path psoc6_ecc_verify_hash_ex serialized the signature r and s components into a fixed 132-byte stack buffer using mp_to_unsigned_bin without checking their sizes. The values come from attacker-supplied ASN.1 in DecodeECC_DSA_Sig with no magnitude cap beyond sp_int capacity, so an oversized r or s wrote past signature_buf, a pre-authentication stack overflow reachable during TLS signature verification. The generic path guards this with wc_ecc_check_r_s_range, but that check is compiled out on WOLFSSL_PSOC6_CRYPTO builds and the port function performed no r/s validation of its own. Reject any r or s whose serialized size exceeds the key size before writing into the buffer. Fixes F-6778. --- wolfcrypt/src/port/cypress/psoc6_crypto.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wolfcrypt/src/port/cypress/psoc6_crypto.c b/wolfcrypt/src/port/cypress/psoc6_crypto.c index 655e4c2be4e..5dd818bee87 100644 --- a/wolfcrypt/src/port/cypress/psoc6_crypto.c +++ b/wolfcrypt/src/port/cypress/psoc6_crypto.c @@ -2107,6 +2107,11 @@ int psoc6_ecc_verify_hash_ex(MATH_INT_T* r, MATH_INT_T* s, const byte* hash, if (keySz > MAX_ECC_KEYSIZE) return -BAD_FUNC_ARG; + /* Reject r or s values that would overflow their keySz slot in + * signature_buf when serialized. */ + if (rSz > keySz || sSz > keySz) + return -BAD_FUNC_ARG; + /* Prepare ECC key */ ecc_key.type = PK_PUBLIC; ecc_key.curveID = psoc6_get_curve_id(keySz); From 6c4c0a864441e7a5a19341ae51372441cf1b3349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 20 Jul 2026 19:36:41 +0200 Subject: [PATCH 02/11] Reject unset key in wc_Chacha_Process wc_Chacha_Process validated only its pointer arguments and then produced keystream directly from the context state. A zero-initialized ChaCha context, common for static or global storage, that received a nonce via wc_Chacha_SetIV but never had wc_Chacha_SetKey called would encrypt with an all-zero, attacker-predictable key and still return success. This is the same fail-open class already guarded against in wc_Arc4Process. Add a keySet flag to the ChaCha struct, set it in wc_Chacha_SetKey, and return MISSING_KEY from wc_Chacha_Process when the key was never set. Fixes F-6893. --- doc/dox_comments/header_files-ja/chacha.h | 1 + doc/dox_comments/header_files/chacha.h | 2 ++ tests/api/test_chacha.c | 25 +++++++++++++++++++++++ tests/api/test_chacha.h | 4 +++- wolfcrypt/src/chacha.c | 4 ++++ wolfssl/wolfcrypt/chacha.h | 1 + 6 files changed, 36 insertions(+), 1 deletion(-) diff --git a/doc/dox_comments/header_files-ja/chacha.h b/doc/dox_comments/header_files-ja/chacha.h index 968827018d7..d1e587472ab 100644 --- a/doc/dox_comments/header_files-ja/chacha.h +++ b/doc/dox_comments/header_files-ja/chacha.h @@ -33,6 +33,7 @@ int wc_Chacha_SetIV(ChaCha* ctx, const byte* inIv, word32 counter); \return 0 入力の暗号化または復号に成功した場合に返されます \return BAD_FUNC_ARG ctx入力引数の処理中にエラーが発生した場合に返されます + \return MISSING_KEY 処理前にwc_Chacha_SetKeyでキーが設定されていない場合に返されます \param ctx ivを設定するChaCha構造体へのポインタ \param output 出力暗号文または復号された平文を格納するバッファへのポインタ diff --git a/doc/dox_comments/header_files/chacha.h b/doc/dox_comments/header_files/chacha.h index 890b8270208..499c7ffd5cc 100644 --- a/doc/dox_comments/header_files/chacha.h +++ b/doc/dox_comments/header_files/chacha.h @@ -41,6 +41,8 @@ int wc_Chacha_SetIV(ChaCha* ctx, const byte* inIv, word32 counter); \return 0 Returned upon successfully encrypting or decrypting the input \return BAD_FUNC_ARG returned if there is an error processing the ctx input argument + \return MISSING_KEY returned if wc_Chacha_SetKey was not called to set a + key before processing \param ctx pointer to the ChaCha structure on which to set the iv \param output pointer to a buffer in which to store the output ciphertext diff --git a/tests/api/test_chacha.c b/tests/api/test_chacha.c index 46c2beba7fd..80a8758c384 100644 --- a/tests/api/test_chacha.c +++ b/tests/api/test_chacha.c @@ -703,3 +703,28 @@ int test_wc_Chacha_XChachaSetKey(void) #endif return EXPECT_RESULT(); } /* END test_wc_Chacha_XChachaSetKey */ + +/* + * A zero-initialized context that only receives a nonce, with the key setup + * omitted, must not silently encrypt with an all-zero key. wc_Chacha_Process + * has to reject the missing key instead of returning success. + */ +int test_wc_Chacha_MissingKey(void) +{ + EXPECT_DECLS; +#ifdef HAVE_CHACHA + ChaCha ctx; + const byte iv[CHACHA_IV_BYTES] = { 0 }; + const byte input[32] = { 0 }; + byte cipher[32]; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(cipher, 0, sizeof(cipher)); + + /* Nonce set, but wc_Chacha_SetKey() deliberately skipped. */ + ExpectIntEQ(wc_Chacha_SetIV(&ctx, iv, 0), 0); + ExpectIntEQ(wc_Chacha_Process(&ctx, cipher, input, sizeof(input)), + WC_NO_ERR_TRACE(MISSING_KEY)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_Chacha_MissingKey */ diff --git a/tests/api/test_chacha.h b/tests/api/test_chacha.h index 57246b79486..7ef8c8fa502 100644 --- a/tests/api/test_chacha.h +++ b/tests/api/test_chacha.h @@ -32,6 +32,7 @@ int test_wc_Chacha_CounterOverflow(void); int test_wc_Chacha_InPlace(void); int test_wc_Chacha_UnalignedBuffers(void); int test_wc_Chacha_XChachaSetKey(void); +int test_wc_Chacha_MissingKey(void); #define TEST_CHACHA_DECLS \ TEST_DECL_GROUP("chacha", test_wc_Chacha_SetKey), \ @@ -41,6 +42,7 @@ int test_wc_Chacha_XChachaSetKey(void); TEST_DECL_GROUP("chacha", test_wc_Chacha_CounterOverflow), \ TEST_DECL_GROUP("chacha", test_wc_Chacha_InPlace), \ TEST_DECL_GROUP("chacha", test_wc_Chacha_UnalignedBuffers), \ - TEST_DECL_GROUP("chacha", test_wc_Chacha_XChachaSetKey) + TEST_DECL_GROUP("chacha", test_wc_Chacha_XChachaSetKey), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_MissingKey) #endif /* WOLFCRYPT_TEST_CHACHA_H */ diff --git a/wolfcrypt/src/chacha.c b/wolfcrypt/src/chacha.c index bc4ec134ecf..0d9e0ad7750 100644 --- a/wolfcrypt/src/chacha.c +++ b/wolfcrypt/src/chacha.c @@ -257,6 +257,7 @@ int wc_Chacha_SetKey(ChaCha* ctx, const byte* key, word32 keySz) #endif ctx->left = 0; /* resets state */ + ctx->keySet = 1; return 0; } @@ -370,6 +371,9 @@ int wc_Chacha_Process(ChaCha* ctx, byte* output, const byte* input, if (ctx == NULL || input == NULL || output == NULL) return BAD_FUNC_ARG; + if (!ctx->keySet) + return MISSING_KEY; + #ifdef USE_INTEL_CHACHA_SPEEDUP /* handle left overs */ if (msglen > 0 && ctx->left > 0) { diff --git a/wolfssl/wolfcrypt/chacha.h b/wolfssl/wolfcrypt/chacha.h index b7b15e7dff5..8528ee925d7 100644 --- a/wolfssl/wolfcrypt/chacha.h +++ b/wolfssl/wolfcrypt/chacha.h @@ -95,6 +95,7 @@ typedef struct ChaCha { #elif defined(USE_RISCV_CHACHA_SPEEDUP) ALIGN8 word32 over[CHACHA_CHUNK_WORDS]; #endif + WC_BITFIELD keySet:1; /* set to 1 once a key is set */ } ChaCha; /** From bcd91c31453246d1768dc51c90461b7be95ab974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 20 Jul 2026 20:11:32 +0200 Subject: [PATCH 03/11] Reject identity-point ECDH shared secret in non-SP path The non-SP-math path of wc_ecc_shared_secret_gen_sync ran the scalar multiplication and affine map, then copied the x-coordinate to the output without checking whether the result was the point at infinity. ecc_map_ex reports the infinity case by setting x, y to zero and z to one and returning success, so a shared secret that computed to infinity was returned as an all-zero secret with a success code. SP 800-56Ar3 5.7.1.2 requires an error and stop in that case. Add an explicit check after the affine map that returns ECC_INF_E when the result is the identity point. Fixes F-6770. --- doc/dox_comments/header_files/ecc.h | 2 + tests/api/test_ecc.c | 71 +++++++++++++++++++++++++++++ tests/api/test_ecc.h | 2 + wolfcrypt/src/ecc.c | 9 ++++ 4 files changed, 84 insertions(+) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index c754d19c9a4..25f48c11782 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -316,6 +316,8 @@ void wc_ecc_key_free(ecc_key* key); shared key \return MP_MEM may be returned if there is an error while computing the shared key + \return ECC_INF_E returned when the computed shared secret is the point at + infinity \param private_key pointer to the ecc_key structure containing the local private key diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 732c0df513d..2f32ef9eabf 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -578,6 +578,77 @@ int test_wc_ecc_shared_secret(void) return EXPECT_RESULT(); } /* END tests_wc_ecc_shared_secret */ +/* + * A shared secret that computes to the point at infinity must be rejected + * (SP 800-56Ar3 5.7.1.2), not returned as an all-zero secret. Setting the + * private scalar to the curve order makes k times the peer point the identity + * for any peer point. Uses secp224r1, which is not single precision + * accelerated, so the non-SP ECDH path runs even in SP builds that offload + * P-256. + */ +int test_wc_ecc_shared_secret_at_infinity(void) +{ + EXPECT_DECLS; + /* ECC_INF_E rejection is not present in the frozen ecc.c of older + * FIPS-certified modules, so restrict to non-FIPS or FIPS v7 and later, + * and skip the CAVP selftest build. */ +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + !defined(HAVE_SELFTEST) && \ + defined(HAVE_ECC) && defined(HAVE_ECC_DHE) && !defined(WC_NO_RNG) && \ + (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && \ + (ECC_MIN_KEY_SZ <= 224) && \ + !defined(WOLFSSL_SP_MATH) && !defined(WOLFSSL_VALIDATE_ECC_IMPORT) && \ + !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_CRYPTOCELL) && !defined(WOLFSSL_SE050) && \ + !defined(WOLFSSL_KCAPI_ECC) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) + ecc_key key; + ecc_key pubKey; + WC_RNG rng; + byte out[MAX_ECC_BYTES]; + word32 outlen = (word32)sizeof(out); + /* A valid SECP224R1 public point. */ + const char* qx = + "b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"; + const char* qy = + "bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"; + /* Order n of SECP224R1. Every valid point has order n on this curve, so + * a private scalar equal to n makes n times the peer point the identity. */ + const char* order = + "ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d"; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&pubKey, 0, sizeof(pubKey)); + XMEMSET(&rng, 0, sizeof(rng)); + + PRIVATE_KEY_UNLOCK(); + + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_ecc_init(&pubKey), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + + ExpectIntEQ(wc_ecc_import_raw(&key, qx, qy, order, "SECP224R1"), 0); + ExpectIntEQ(wc_ecc_import_raw(&pubKey, qx, qy, NULL, "SECP224R1"), 0); + +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ExpectIntEQ(wc_ecc_set_rng(&key, &rng), 0); +#endif + + ExpectIntEQ(wc_ecc_shared_secret(&key, &pubKey, out, &outlen), + WC_NO_ERR_TRACE(ECC_INF_E)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_ecc_free(&pubKey); + wc_ecc_free(&key); +#ifdef FP_ECC + wc_ecc_fp_free(); +#endif + PRIVATE_KEY_LOCK(); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_shared_secret_at_infinity */ + #if defined(HAVE_ECC) && defined(HAVE_ECC_DHE) && !defined(WC_NO_RNG) && \ (defined(HAVE_ECC384) || defined(HAVE_ECC521) || \ defined(HAVE_ALL_CURVES)) && \ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index b99f29ae2df..6d662ad6a52 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -36,6 +36,7 @@ int test_wc_ecc_size(void); int test_wc_ecc_params(void); int test_wc_ecc_signVerify_hash(void); int test_wc_ecc_shared_secret(void); +int test_wc_ecc_shared_secret_at_infinity(void); int test_wc_ecc_shared_secret_size_bounds(void); int test_wc_ecc_export_x963(void); int test_wc_ecc_export_x963_ex(void); @@ -79,6 +80,7 @@ int test_wc_EccDecisionCoverage4(void); TEST_DECL_GROUP("ecc", test_wc_ecc_params), \ TEST_DECL_GROUP("ecc", test_wc_ecc_signVerify_hash), \ TEST_DECL_GROUP("ecc", test_wc_ecc_shared_secret), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_shared_secret_at_infinity), \ TEST_DECL_GROUP("ecc", test_wc_ecc_shared_secret_size_bounds), \ TEST_DECL_GROUP("ecc", test_wc_ecc_export_x963), \ TEST_DECL_GROUP("ecc", test_wc_ecc_export_x963_ex), \ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 117adfd5ae5..8f780e609dc 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -5043,6 +5043,15 @@ int wc_ecc_shared_secret_gen_sync(ecc_key* private_key, ecc_point* point, /* Use constant time map if compiled in */ err = ecc_map_ex(result, curve->prime, mp, 1); } + if (err == MP_OKAY) { + /* SP 800-56Ar3 5.7.1.2: a real scalar must not produce a shared + * secret at the point at infinity. A zero scalar (e.g. a key whose + * private value is held in secure hardware) is not a computed + * secret, so leave that case to the offload path. */ + if (!mp_iszero(k) && wc_ecc_point_is_at_infinity(result)) { + err = ECC_INF_E; + } + } if (err == MP_OKAY) { x = mp_unsigned_bin_size(curve->prime); if (*outlen < (word32)x || x < mp_unsigned_bin_size(result->x)) { From e8f157b17849b527274ada5f7f37d994a9b655bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 09:09:02 +0200 Subject: [PATCH 04/11] Use random-witness primality test for untrusted DH modulus wc_DhSetKey_ex loads DH parameters as untrusted and validates that the modulus is prime, but it passed no RNG, so the check fell back to a Miller-Rabin test using the fixed small-prime bases 2 through 19. That test is defeatable: a composite crafted as a strong pseudoprime to those known bases passes as prime, letting an attacker supply a composite modulus with a smooth factorization for small-subgroup recovery of the private exponent and shared secret. When no RNG is supplied on the untrusted path, create a temporary RNG so mp_prime_is_prime_ex runs with random witnesses, which such crafted composites cannot reliably pass. Named FFDHE primes still short-circuit the check, and builds without an RNG keep the deterministic test. Fixes F-6776. --- tests/api/test_dh.c | 31 +++++++++++++++++++++++++++++++ tests/api/test_dh.h | 2 ++ wolfcrypt/src/dh.c | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/tests/api/test_dh.c b/tests/api/test_dh.c index 30a4c8b575b..e42601da52d 100644 --- a/tests/api/test_dh.c +++ b/tests/api/test_dh.c @@ -316,6 +316,37 @@ int test_wc_DhSetKey(void) return EXPECT_RESULT(); } +/* + * wc_DhSetKey_ex validates untrusted parameters, so it must reject a composite + * modulus even when that composite is a strong pseudoprime to the fixed + * small-prime Miller-Rabin bases. n = 341550071728321 = 10670053 * 32010157 is + * a strong pseudoprime to bases 2, 3, 5, 7, 11, 13, 17 and 19, which the + * deterministic fixed-base test wrongly accepts as prime. Random-witness + * testing rejects it. As with the library's own primality check, rejection is + * probabilistic: eight random Miller-Rabin rounds leave a negligible + * (well under 1e-4) chance of accepting the composite, so a one-off failure + * here is statistical, not a regression. + */ +int test_wc_DhSetKey_ex_pseudoprime(void) +{ + EXPECT_DECLS; +#if !defined(NO_DH) && !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) && \ + !defined(WC_NO_RNG) + DhKey key; + /* n = 341550071728321, big-endian. */ + byte p[] = { 0x01, 0x36, 0xA3, 0x52, 0xB2, 0xC8, 0xC1 }; + byte g[] = { 0x02 }; + + XMEMSET(&key, 0, sizeof(key)); + + ExpectIntEQ(wc_InitDhKey(&key), 0); + ExpectIntEQ(wc_DhSetKey_ex(&key, p, sizeof(p), g, sizeof(g), NULL, 0), + WC_NO_ERR_TRACE(DH_CHECK_PUB_E)); + wc_FreeDhKey(&key); +#endif + return EXPECT_RESULT(); +} + /* * Testing wc_DhSetNamedKey(), wc_DhGetNamedKeyParamSize(), * wc_DhCopyNamedKey() and wc_DhCmpNamedKey(). diff --git a/tests/api/test_dh.h b/tests/api/test_dh.h index 23a4a0bb265..3aa50855eeb 100644 --- a/tests/api/test_dh.h +++ b/tests/api/test_dh.h @@ -27,6 +27,7 @@ int test_wc_DhPublicKeyDecode(void); int test_wc_DhAgree_subgroup_check(void); int test_wc_DhSetKey(void); +int test_wc_DhSetKey_ex_pseudoprime(void); int test_wc_DhSetNamedKey_and_helpers(void); int test_wc_DhGenerateKeyPair_bad_args(void); int test_wc_DhGenerateKeyPair_and_Agree(void); @@ -42,6 +43,7 @@ int test_wc_DhGenerateKeyPair_CheckDhLN(void); TEST_DECL_GROUP("dh", test_wc_DhPublicKeyDecode), \ TEST_DECL_GROUP("dh", test_wc_DhAgree_subgroup_check), \ TEST_DECL_GROUP("dh", test_wc_DhSetKey), \ + TEST_DECL_GROUP("dh", test_wc_DhSetKey_ex_pseudoprime), \ TEST_DECL_GROUP("dh", test_wc_DhSetNamedKey_and_helpers), \ TEST_DECL_GROUP("dh", test_wc_DhGenerateKeyPair_bad_args), \ TEST_DECL_GROUP("dh", test_wc_DhGenerateKeyPair_and_Agree), \ diff --git a/wolfcrypt/src/dh.c b/wolfcrypt/src/dh.c index 5a6daa98531..75f9b3f5801 100644 --- a/wolfcrypt/src/dh.c +++ b/wolfcrypt/src/dh.c @@ -2670,10 +2670,39 @@ static int _DhSetKey(DhKey* key, const byte* p, word32 pSz, const byte* g, else #endif { - if (rng != NULL) - ret = mp_prime_is_prime_ex(keyP, 8, &isPrime, rng); +#ifndef WC_NO_RNG + WC_RNG* checkRng = rng; + WC_RNG* tmpRng = NULL; + + /* A fixed-base Miller-Rabin test can be fooled by a crafted + * composite, so use random witnesses when an RNG is available. + * Create a temporary RNG when the caller did not supply one. */ + if (checkRng == NULL) { + tmpRng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), key->heap, + DYNAMIC_TYPE_RNG); + if ((tmpRng != NULL) && + (wc_InitRng_ex(tmpRng, key->heap, INVALID_DEVID) == 0)) { + checkRng = tmpRng; + } + } + + /* Fall back to the deterministic test when no RNG could be + * obtained rather than failing the parameter load. */ + if (checkRng != NULL) + ret = mp_prime_is_prime_ex(keyP, 8, &isPrime, checkRng); else ret = mp_prime_is_prime(keyP, 8, &isPrime); + + if (tmpRng != NULL) { + /* wc_FreeRng is safe on a partially-constructed RNG and + * releases any resources a failed wc_InitRng_ex left behind. */ + wc_FreeRng(tmpRng); + XFREE(tmpRng, key->heap, DYNAMIC_TYPE_RNG); + } +#else + (void)rng; + ret = mp_prime_is_prime(keyP, 8, &isPrime); +#endif } if (ret == 0 && isPrime == 0) From 874fec31195eb9075a632541cc999d4478fc4a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 10:57:43 +0200 Subject: [PATCH 05/11] Add negative tests for wc_ecc_check_key public-key checks wc_ecc_check_key validates a public key's coordinate range, that the point is on the curve, and its order, but the software path had no negative coverage: the existing test only exercised a valid key and NULL, and the off-curve case lived in the crypto-callback test, which validates the device path rather than the software on-curve check. A deletion of either the on-curve check or the coordinate-range checks therefore passed the suite. Add a test that imports secp256r1 public keys that are off the curve and out of coordinate range, asserting IS_POINT_E and ECC_OUT_OF_RANGE_E respectively, exercising the software validation path. Fixes F-6620. --- tests/api/test_ecc.c | 60 ++++++++++++++++++++++++++++++++++++++++++++ tests/api/test_ecc.h | 2 ++ 2 files changed, 62 insertions(+) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 2f32ef9eabf..86b82307be2 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -295,6 +295,66 @@ int test_wc_ecc_check_key(void) return EXPECT_RESULT(); } /* END test_wc_ecc_check_key */ +/* + * Negative coverage for the public-key checks in wc_ecc_check_key. A point off + * the curve must be rejected with IS_POINT_E, and a coordinate outside + * [0, p-1] with ECC_OUT_OF_RANGE_E. Uses secp224r1, which is not single + * precision accelerated, so the software validation path runs even in SP + * builds that offload P-256. + */ +int test_wc_ecc_check_key_invalid_pubkey(void) +{ + EXPECT_DECLS; + /* Older FIPS-certified modules ship a frozen source tree where these + * imports and checks behave differently, so restrict to non-FIPS or + * FIPS v7 and later, and skip the CAVP selftest build. */ +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + !defined(HAVE_SELFTEST) && \ + defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) && \ + !defined(NO_ECC_CHECK_PUBKEY_ORDER) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && \ + (ECC_MIN_KEY_SZ <= 224) && \ + !defined(WOLFSSL_VALIDATE_ECC_IMPORT) && !defined(WOLFSSL_SP_MATH) && \ + !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_SE050) && \ + !defined(WOLFSSL_STM32_PKA) && !defined(WOLFSSL_KCAPI_ECC) + ecc_key key; + const char* qx = + "b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"; + const char* qy = + "bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"; + /* Qy with its low bit flipped: still less than p, but not on the curve. */ + const char* qyOffCurve = + "bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e35"; + /* p, the SECP224R1 field prime, used as an out-of-range coordinate. */ + const char* pModulus = + "ffffffffffffffffffffffffffffffff000000000000000000000001"; + + /* Point not on the curve: rejected by the on-curve check. */ + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_ecc_import_raw(&key, qx, qyOffCurve, NULL, "SECP224R1"), 0); + ExpectIntEQ(wc_ecc_check_key(&key), WC_NO_ERR_TRACE(IS_POINT_E)); + wc_ecc_free(&key); + + /* Qx == p: rejected by the coordinate-range check. */ + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_ecc_import_raw(&key, pModulus, qy, NULL, "SECP224R1"), 0); + ExpectIntEQ(wc_ecc_check_key(&key), WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + wc_ecc_free(&key); + + /* Qy == p: rejected by the coordinate-range check. */ + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_ecc_import_raw(&key, qx, pModulus, NULL, "SECP224R1"), 0); + ExpectIntEQ(wc_ecc_check_key(&key), WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + wc_ecc_free(&key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_check_key_invalid_pubkey */ + /* * Testing wc_ecc_get_generator() */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 6d662ad6a52..405773af0dc 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -31,6 +31,7 @@ int test_wc_ecc_get_curve_id_from_dp_params(void); int test_wc_ecc_make_key(void); int test_wc_ecc_init(void); int test_wc_ecc_check_key(void); +int test_wc_ecc_check_key_invalid_pubkey(void); int test_wc_ecc_get_generator(void); int test_wc_ecc_size(void); int test_wc_ecc_params(void); @@ -75,6 +76,7 @@ int test_wc_EccDecisionCoverage4(void); TEST_DECL_GROUP("ecc", test_wc_ecc_make_key), \ TEST_DECL_GROUP("ecc", test_wc_ecc_init), \ TEST_DECL_GROUP("ecc", test_wc_ecc_check_key), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_check_key_invalid_pubkey), \ TEST_DECL_GROUP("ecc", test_wc_ecc_get_generator), \ TEST_DECL_GROUP("ecc", test_wc_ecc_size), \ TEST_DECL_GROUP("ecc", test_wc_ecc_params), \ From 18db268b2ed83834baed37100b7cdab22185ea75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 11:29:26 +0200 Subject: [PATCH 06/11] Add negative test for Ed448 signature S-range check Ed448 verification rejects a non-canonical signature scalar S (S >= L) per RFC 8032, and that range check is the only guard against a malleated signature: because L times the base point is the identity, (R, S + L) recomputes the same R and would otherwise verify. The check had no negative coverage, so a deletion or boundary mutation passed the suite while all canonical KAT signatures kept working. Add a test that signs a message, then verifies crafted signatures whose S half equals the order, exceeds it in a high or low byte, and equals S + L, asserting BAD_FUNC_ARG, plus an in-range wrong S asserting SIG_VERIFY_E. Fixes F-6777. --- tests/api/test_ed448.c | 109 +++++++++++++++++++++++++++++++++++++++++ tests/api/test_ed448.h | 2 + 2 files changed, 111 insertions(+) diff --git a/tests/api/test_ed448.c b/tests/api/test_ed448.c index 2fe404b9c9a..e3b7a464028 100644 --- a/tests/api/test_ed448.c +++ b/tests/api/test_ed448.c @@ -177,6 +177,115 @@ int test_wc_ed448_sign_msg(void) return EXPECT_RESULT(); } /* END test_wc_ed448_sign_msg */ +/* + * RFC 8032 requires the Ed448 signature scalar S to be canonical (S < L). + * Because L times the base point is the identity, a malleated signature with + * S' = S + L recomputes the same R, so the S-range check is the only guard + * against it. Confirm a signature with S >= L (including the malleability case + * S + L) is rejected with BAD_FUNC_ARG, while an in-range but wrong S fails + * verification with SIG_VERIFY_E. + */ +int test_wc_ed448_verify_sig_S_range(void) +{ + EXPECT_DECLS; + /* The S-range rejection may be absent in the frozen ed448.c of older + * FIPS-certified modules, so restrict to non-FIPS or FIPS v7 and later. */ +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + defined(HAVE_ED448) && defined(HAVE_ED448_SIGN) && \ + defined(HAVE_ED448_VERIFY) + ed448_key key; + WC_RNG rng; + byte msg[] = "Everybody gets Friday off.\n"; + byte sig[ED448_SIG_SIZE]; + byte badSig[ED448_SIG_SIZE]; + word32 msglen = sizeof(msg); + word32 siglen = sizeof(sig); + int verify_ok = 0; + int i; + int carry; + int sum; + /* Ed448 group order L, little-endian, 57 bytes. */ + static const byte order[] = { + 0xf3, 0x44, 0x58, 0xab, 0x92, 0xc2, 0x78, 0x23, + 0x55, 0x8f, 0xc5, 0x8d, 0x72, 0xc2, 0x6c, 0x21, + 0x90, 0x36, 0xd6, 0xae, 0x49, 0xdb, 0x4e, 0xc4, + 0xe9, 0x23, 0xca, 0x7c, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0x00 + }; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(sig, 0, sizeof(sig)); + + ExpectIntEQ(wc_ed448_init(&key), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ed448_make_key(&rng, ED448_KEY_SIZE, &key), 0); + + /* Produce a valid signature and confirm it verifies. */ + ExpectIntEQ(wc_ed448_sign_msg(msg, msglen, sig, &siglen, &key, NULL, 0), 0); + ExpectIntEQ(siglen, ED448_SIG_SIZE); + ExpectIntEQ(wc_ed448_verify_msg(sig, siglen, msg, msglen, &verify_ok, &key, + NULL, 0), 0); + ExpectIntEQ(verify_ok, 1); + + /* Malleability: S' = S + L. The same R is recomputed, so only the S-range + * check can reject it. */ + XMEMCPY(badSig, sig, ED448_SIG_SIZE); + carry = 0; + for (i = 0; i < (int)sizeof(order); i++) { + sum = (int)badSig[ED448_SIG_SIZE / 2 + i] + (int)order[i] + carry; + badSig[ED448_SIG_SIZE / 2 + i] = (byte)(sum & 0xff); + carry = sum >> 8; + } + verify_ok = 1; + ExpectIntEQ(wc_ed448_verify_msg(badSig, ED448_SIG_SIZE, msg, msglen, + &verify_ok, &key, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(verify_ok, 0); + + /* S exactly equal to L. */ + XMEMCPY(badSig, sig, ED448_SIG_SIZE); + XMEMCPY(badSig + ED448_SIG_SIZE / 2, order, sizeof(order)); + verify_ok = 1; + ExpectIntEQ(wc_ed448_verify_msg(badSig, ED448_SIG_SIZE, msg, msglen, + &verify_ok, &key, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(verify_ok, 0); + + /* S greater than L in a high byte. */ + XMEMCPY(badSig, sig, ED448_SIG_SIZE); + XMEMCPY(badSig + ED448_SIG_SIZE / 2, order, sizeof(order)); + badSig[ED448_SIG_SIZE / 2 + 55] = 0x40; + verify_ok = 1; + ExpectIntEQ(wc_ed448_verify_msg(badSig, ED448_SIG_SIZE, msg, msglen, + &verify_ok, &key, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(verify_ok, 0); + + /* S greater than L in a low byte. */ + XMEMCPY(badSig, sig, ED448_SIG_SIZE); + XMEMCPY(badSig + ED448_SIG_SIZE / 2, order, sizeof(order)); + badSig[ED448_SIG_SIZE / 2 + 0] = 0xf4; + verify_ok = 1; + ExpectIntEQ(wc_ed448_verify_msg(badSig, ED448_SIG_SIZE, msg, msglen, + &verify_ok, &key, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(verify_ok, 0); + + /* S below L: passes the range check, fails verification instead. */ + XMEMCPY(badSig, sig, ED448_SIG_SIZE); + XMEMCPY(badSig + ED448_SIG_SIZE / 2, order, sizeof(order)); + badSig[ED448_SIG_SIZE / 2 + 0] = 0xf2; + verify_ok = 1; + ExpectIntEQ(wc_ed448_verify_msg(badSig, ED448_SIG_SIZE, msg, msglen, + &verify_ok, &key, NULL, 0), WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ExpectIntEQ(verify_ok, 0); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_ed448_free(&key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ed448_verify_sig_S_range */ + /* * Test that wc_ed448_sign_msg() rejects a public-key-only key object. * A key with pubKeySet=1 but privKeySet=0 must not silently sign. diff --git a/tests/api/test_ed448.h b/tests/api/test_ed448.h index 169c88a39ce..53209b21651 100644 --- a/tests/api/test_ed448.h +++ b/tests/api/test_ed448.h @@ -27,6 +27,7 @@ int test_wc_ed448_make_key(void); int test_wc_ed448_init(void); int test_wc_ed448_sign_msg(void); +int test_wc_ed448_verify_sig_S_range(void); int test_wc_ed448_sign_msg_pubonly_fails(void); int test_wc_ed448_import_public(void); int test_wc_ed448_import_private_key(void); @@ -47,6 +48,7 @@ int test_wc_ed448_check_key_decisions(void); TEST_DECL_GROUP("ed448", test_wc_ed448_make_key), \ TEST_DECL_GROUP("ed448", test_wc_ed448_init), \ TEST_DECL_GROUP("ed448", test_wc_ed448_sign_msg), \ + TEST_DECL_GROUP("ed448", test_wc_ed448_verify_sig_S_range), \ TEST_DECL_GROUP("ed448", test_wc_ed448_sign_msg_pubonly_fails), \ TEST_DECL_GROUP("ed448", test_wc_ed448_import_public), \ TEST_DECL_GROUP("ed448", test_wc_ed448_import_private_key), \ From bfb5a33eacd39fa884261e0672e31d146f7d3b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 13:06:02 +0200 Subject: [PATCH 07/11] Surface hardware failures in Intel QAT sync cipher IntelQaSymCipher ran cpaCySymPerformOp synchronously but never translated its completion status into the return code. Only the AES-GCM decrypt auth check, driven by verifyResult, could set an error. For AES-CBC, AES-GCM encrypt, and 3DES-CBC a non-success status left ret at zero, so the exit path copied the working buffer to the output and returned success. On encrypt that buffer still holds the plaintext copy, so a hardware failure returned success with plaintext written to the ciphertext output. Translate a non-success perform-op status into ASYNC_OP_E, matching the asynchronous port, so hardware failures are surfaced instead of returning zero with unprocessed output. Fixes F-6623. --- wolfcrypt/src/port/intel/quickassist_sync.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/port/intel/quickassist_sync.c b/wolfcrypt/src/port/intel/quickassist_sync.c index 91cb76ae8b2..f9f0309a131 100644 --- a/wolfcrypt/src/port/intel/quickassist_sync.c +++ b/wolfcrypt/src/port/intel/quickassist_sync.c @@ -1085,10 +1085,13 @@ static int IntelQaSymCipher(IntelQaDev* dev, byte* out, const byte* in, status = cpaCySymPerformOp(dev->handle, dev, opData, bufferList, bufferList, &verifyResult); - if (symOperation == CPA_CY_SYM_OP_ALGORITHM_CHAINING && - cipherAlgorithm == CPA_CY_SYM_CIPHER_AES_GCM && - cipherDirection == CPA_CY_SYM_CIPHER_DIRECTION_DECRYPT && - hashAlgorithm == CPA_CY_SYM_HASH_AES_GCM) { + if (status != CPA_STATUS_SUCCESS) { + ret = ASYNC_OP_E; + } + else if (symOperation == CPA_CY_SYM_OP_ALGORITHM_CHAINING && + cipherAlgorithm == CPA_CY_SYM_CIPHER_AES_GCM && + cipherDirection == CPA_CY_SYM_CIPHER_DIRECTION_DECRYPT && + hashAlgorithm == CPA_CY_SYM_HASH_AES_GCM) { if (verifyResult == CPA_FALSE) { ret = AES_GCM_AUTH_E; } From f49d0f2de608038bc00767532e0fb365db1086d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 13:12:50 +0200 Subject: [PATCH 08/11] Fix transposed register and mask in ESP32-C3 RSA unlock The ESP32-C3 deactivation path in esp_mp_hw_unlock passed its arguments to DPORT_REG_CLR_BIT and DPORT_REG_SET_BIT in the wrong order and added a stray DR_REG_RSA_BASE offset. The macros take (register address, bit mask), but the code used the bit mask as part of the register address and the register as the bit mask. As a result the RSA clock-enable and memory power-down bits were never updated at deactivation, and reads and writes landed at a bogus peripheral address. The accelerator was left powered up after every bignum and RSA operation. Pass the register address first and the bit mask second and drop the offset, mirroring the activation path and the other targets. Fixes F-6779. --- wolfcrypt/src/port/Espressif/esp32_mp.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/wolfcrypt/src/port/Espressif/esp32_mp.c b/wolfcrypt/src/port/Espressif/esp32_mp.c index 9ca4d90b3e9..124d3d23584 100644 --- a/wolfcrypt/src/port/Espressif/esp32_mp.c +++ b/wolfcrypt/src/port/Espressif/esp32_mp.c @@ -567,12 +567,10 @@ static int esp_mp_hw_unlock(void) * This releases the RSA Accelerator from reset.*/ portENTER_CRITICAL_SAFE(&wc_rsa_reg_lock); { - DPORT_REG_CLR_BIT( - (volatile void *)(DR_REG_RSA_BASE + SYSTEM_CRYPTO_RSA_CLK_EN), - SYSTEM_PERIP_CLK_EN1_REG); - DPORT_REG_SET_BIT( - (volatile void *)(DR_REG_RSA_BASE + SYSTEM_RSA_MEM_PD), - SYSTEM_RSA_PD_CTRL_REG); + DPORT_REG_CLR_BIT((volatile void *)(SYSTEM_PERIP_CLK_EN1_REG), + SYSTEM_CRYPTO_RSA_CLK_EN); + DPORT_REG_SET_BIT((volatile void *)(SYSTEM_RSA_PD_CTRL_REG), + SYSTEM_RSA_MEM_PD); } portEXIT_CRITICAL_SAFE(&wc_rsa_reg_lock); #elif defined(CONFIG_IDF_TARGET_ESP32C6) From 65ee8cf9eed5dd0cd3d0fbc56de691e5655bdc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 13:20:16 +0200 Subject: [PATCH 09/11] Avoid unaligned word32 read of RSA key id under SE050 wc_InitRsaKey_Id cast the byte array key->id to word32* and dereferenced it to recover the SE050 key id. key->id is a byte array with no alignment guarantee inside RsaKey, so the dereference is an unaligned 32-bit read that faults or mis-reads on strict-alignment targets, and it violates strict aliasing. Read the value with readUnalignedWord32 instead, which does an alignment-safe byte copy, matching wc_ecc_init_id. Fixes F-6624. --- wolfcrypt/src/rsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/rsa.c b/wolfcrypt/src/rsa.c index 096c0e50e21..0f72614a8e8 100644 --- a/wolfcrypt/src/rsa.c +++ b/wolfcrypt/src/rsa.c @@ -387,8 +387,8 @@ int wc_InitRsaKey_Id(RsaKey* key, unsigned char* id, int len, void* heap, { int ret = 0; #if defined(WOLFSSL_SE050) && !defined(WOLFSSL_SE050_NO_RSA) - /* SE050 TLS users store a word32 at id, need to cast back */ - word32* keyPtr = NULL; + /* SE050 TLS users store a word32 at id, need to read it back */ + word32 keyId = 0; #endif if (key == NULL) @@ -403,8 +403,8 @@ int wc_InitRsaKey_Id(RsaKey* key, unsigned char* id, int len, void* heap, #if defined(WOLFSSL_SE050) && !defined(WOLFSSL_SE050_NO_RSA) /* Set SE050 ID from word32, populate RsaKey with public from SE050 */ if (len == (int)sizeof(word32)) { - keyPtr = (word32*)key->id; - ret = wc_RsaUseKeyId(key, *keyPtr, 0); + keyId = readUnalignedWord32(key->id); + ret = wc_RsaUseKeyId(key, keyId, 0); } #endif } From bd6f144f73f5f8ea9a1398884f8c897985908a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 15:16:44 +0200 Subject: [PATCH 10/11] Use alignment-safe reads in ML-KEM mlkem_cbd_eta3 The little-endian large-code path of mlkem_cbd_eta3 cast the caller-supplied byte buffer to word32* and read through it. The buffer has no alignment guarantee, so on strict-alignment targets such as Cortex-M3 and M4 with unaligned-access trapping enabled the read faults or returns wrong values, and the cast violates strict aliasing. Read each word with readUnalignedWord32, which does an alignment-safe byte copy, matching the neighboring mlkem_cbd_eta2. Fixes F-6781. --- wolfcrypt/src/wc_mlkem_poly.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wolfcrypt/src/wc_mlkem_poly.c b/wolfcrypt/src/wc_mlkem_poly.c index 2ab7299cfe1..98148d116fe 100644 --- a/wolfcrypt/src/wc_mlkem_poly.c +++ b/wolfcrypt/src/wc_mlkem_poly.c @@ -3957,12 +3957,14 @@ static void mlkem_cbd_eta3(sword16* p, const byte* r) #else /* Calculate eight integer coefficients at a time. */ for (i = 0; i < MLKEM_N; i += 16) { - const word32* r32 = (const word32*)r; + word32 r0 = readUnalignedWord32(r); + word32 r1 = readUnalignedWord32(r + 4); + word32 r2 = readUnalignedWord32(r + 8); /* Take the next 12 bytes, little endian, as 24 bit values. */ - word32 t0 = r32[0] & 0xffffff; - word32 t1 = ((r32[0] >> 24) | (r32[1] << 8)) & 0xffffff; - word32 t2 = ((r32[1] >> 16) | (r32[2] << 16)) & 0xffffff; - word32 t3 = r32[2] >> 8 ; + word32 t0 = r0 & 0xffffff; + word32 t1 = ((r0 >> 24) | (r1 << 8)) & 0xffffff; + word32 t2 = ((r1 >> 16) | (r2 << 16)) & 0xffffff; + word32 t3 = r2 >> 8 ; word32 d0; word32 d1; word32 d2; From d7d80ad35e01f76227550c4d449fc093e4cd4b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 21 Jul 2026 15:26:51 +0200 Subject: [PATCH 11/11] Use alignment-safe writes in ML-KEM mlkem_vec_compress_10_c The little-endian large-code path of mlkem_vec_compress_10_c cast the output byte buffer to word32* and issued five 32-bit stores through it. That buffer is the caller-supplied ML-KEM ciphertext, which has no alignment guarantee, so on strict-alignment targets the store bus-faults and the cast violates strict aliasing. Write each word with writeUnalignedWord32, which does an alignment-safe byte copy, matching mldsa_encode_w1_88_c and the neighboring ML-KEM sampling code. Fixes F-6782. --- wolfcrypt/src/wc_mlkem_poly.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/wolfcrypt/src/wc_mlkem_poly.c b/wolfcrypt/src/wc_mlkem_poly.c index 98148d116fe..31cace546a3 100644 --- a/wolfcrypt/src/wc_mlkem_poly.c +++ b/wolfcrypt/src/wc_mlkem_poly.c @@ -5073,18 +5073,22 @@ static void mlkem_vec_compress_10_c(byte* r, sword16* v, unsigned int k) sword16 t14 = TO_COMP_WORD_10(v, i, j, 14); sword16 t15 = TO_COMP_WORD_10(v, i, j, 15); - word32* r32 = (word32*)r; /* Pack sixteen 10-bit values into byte array. */ - r32[0] = (word32)t0 | ((word32)t1 << 10) | - ((word32)t2 << 20) | ((word32)t3 << 30); - r32[1] = ((word32)t3 >> 2) | ((word32)t4 << 8) | - ((word32)t5 << 18) | ((word32)t6 << 28); - r32[2] = ((word32)t6 >> 4) | ((word32)t7 << 6) | - ((word32)t8 << 16) | ((word32)t9 << 26); - r32[3] = ((word32)t9 >> 6) | ((word32)t10 << 4) | - ((word32)t11 << 14) | ((word32)t12 << 24); - r32[4] = ((word32)t12 >> 8) | ((word32)t13 << 2) | - ((word32)t14 << 12) | ((word32)t15 << 22); + writeUnalignedWord32(r + 0, + (word32)t0 | ((word32)t1 << 10) | + ((word32)t2 << 20) | ((word32)t3 << 30)); + writeUnalignedWord32(r + 4, + ((word32)t3 >> 2) | ((word32)t4 << 8) | + ((word32)t5 << 18) | ((word32)t6 << 28)); + writeUnalignedWord32(r + 8, + ((word32)t6 >> 4) | ((word32)t7 << 6) | + ((word32)t8 << 16) | ((word32)t9 << 26)); + writeUnalignedWord32(r + 12, + ((word32)t9 >> 6) | ((word32)t10 << 4) | + ((word32)t11 << 14) | ((word32)t12 << 24)); + writeUnalignedWord32(r + 16, + ((word32)t12 >> 8) | ((word32)t13 << 2) | + ((word32)t14 << 12) | ((word32)t15 << 22)); /* Move over set bytes. */ r += 20;