From f6995ee4b5ffec9d0c30917df24fcd44e5ca92cd Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 23 Jun 2026 11:22:55 +0000 Subject: [PATCH 01/15] AES: reject mode operations when no key has been set (F-6151) wc_AesInit zeroes the Aes struct, leaving keylen and rounds at 0. The CTR/CBC/GCM/ECB/CFB/OFB entry points never verified that a key had been installed with wc_AesSetKey, so calling them right after wc_AesInit ran the software cipher against an all-zero key schedule and returned success instead of an error. For CTR this exposes a reconstructable keystream; for GCM the hash subkey H is also zero, making the tag forgeable. Add the keylen == 0 guard already used by wc_AesXtsEncrypt to the software mode paths. The check is placed after the crypto callback fall-through so device-managed keys are unaffected. Hardware paths that call wc_AesGetKeySize already reject this case via rounds == 0. Add aes_no_key_set_test(), run from aes_test(), which inits an Aes context and confirms every mode rejects use before a key is set. --- wolfcrypt/src/aes.c | 64 ++++++++++++++++++++ wolfcrypt/test/test.c | 133 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 46a89a6ad8..337dfbcbdf 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -7038,6 +7038,13 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) /* fall-through when unavailable */ } #endif + + /* Software/HW key schedule required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_AES) /* if async and byte count above threshold */ if (aes->asyncDev.marker == WOLFSSL_ASYNC_MARKER_AES && @@ -7266,6 +7273,13 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) /* fall-through when unavailable */ } #endif + + /* Software/HW key schedule required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_AES) /* if async and byte count above threshold */ if (aes->asyncDev.marker == WOLFSSL_ASYNC_MARKER_AES && @@ -7745,6 +7759,12 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) } #endif + /* Software/HW key schedule required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + /* consume any unused bytes left in aes->tmp */ processed = min(aes->left, sz); xorbufout(out, in, (byte*)aes->tmp + WC_AES_BLOCK_SIZE - aes->left, @@ -10966,6 +10986,12 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, } #endif + /* Software/HW key schedule (and hash subkey H) required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_AES) /* if async and byte count above threshold */ /* only 12-byte IV is supported in HW */ @@ -11778,6 +11804,12 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, } #endif + /* Software/HW key schedule (and hash subkey H) required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_AES) /* if async and byte count above threshold */ /* only 12-byte IV is supported in HW */ @@ -15798,6 +15830,12 @@ static WARN_UNUSED_RESULT int _AesEcbEncrypt( return DCPAesEcbEncrypt(aes, out, in, sz); #endif + /* Software key schedule required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + VECTOR_REGISTERS_PUSH; #if defined(WOLFSSL_RISCV_ASM) @@ -15899,6 +15937,12 @@ static WARN_UNUSED_RESULT int _AesEcbDecrypt( return DCPAesEcbDecrypt(aes, out, in, sz); #endif + /* Software key schedule required from here on. */ + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + VECTOR_REGISTERS_PUSH; #if defined(WOLFSSL_RISCV_ASM) @@ -16045,6 +16089,10 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, if (sz == 0) { return 0; } + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } if (aes->left > 0) { /* consume any unused bytes left in aes->tmp */ @@ -16128,6 +16176,10 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, if (sz == 0) { return 0; } + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } if (aes->left > 0) { /* consume any unused bytes left in aes->tmp */ @@ -16356,6 +16408,10 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB8( if (sz == 0) { return 0; } + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } VECTOR_REGISTERS_PUSH; @@ -16416,6 +16472,10 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( if (sz == 0) { return 0; } + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } VECTOR_REGISTERS_PUSH; @@ -16574,6 +16634,10 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, if (sz == 0) { return 0; } + if (aes->keylen == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } if (aes->left > 0) { /* consume any unused bytes left in aes->tmp */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 40924f6983..b2a936ae74 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -16503,12 +16503,145 @@ static wc_test_ret_t aes_ecb_direct_test(void) } #endif /* HAVE_AES_ECB || WOLFSSL_AES_DIRECT */ +#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \ + defined(HAVE_AESGCM) || defined(HAVE_AES_ECB) || \ + defined(WOLFSSL_AES_CFB) || defined(WOLFSSL_AES_OFB) +#define WC_TEST_HAVE_AES_NO_KEY_SET +/* Ensure AES mode APIs fail when used before wc_AesSetKey installs a key, + * instead of running with the all-zero key schedule left by wc_AesInit. */ +static wc_test_ret_t aes_no_key_set_test(void) +{ + wc_test_ret_t ret = 0; +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + Aes *aes = NULL; +#else + Aes aes[1]; +#endif + byte plain[WC_AES_BLOCK_SIZE]; + byte cipher[WC_AES_BLOCK_SIZE]; +#ifdef HAVE_AESGCM + byte iv[WC_AES_BLOCK_SIZE]; + byte tag[WC_AES_BLOCK_SIZE]; +#endif + + XMEMSET(plain, 0, sizeof(plain)); + XMEMSET(cipher, 0, sizeof(cipher)); +#ifdef HAVE_AESGCM + XMEMSET(iv, 0, sizeof(iv)); + XMEMSET(tag, 0, sizeof(tag)); +#endif + +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + aes = wc_AesNew(HEAP_HINT, devId, &ret); + if (aes == NULL) + return WC_TEST_RET_ENC_EC(ret); +#else + ret = wc_AesInit(aes, HEAP_HINT, devId); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); +#endif + + /* No wc_AesSetKey: aes->keylen is 0, so every mode must reject the call. */ +#ifdef HAVE_AES_CBC + if (wc_AesCbcEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef HAVE_AES_DECRYPT + if (wc_AesCbcDecrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif +#endif /* HAVE_AES_CBC */ + +#ifdef WOLFSSL_AES_COUNTER + if (wc_AesCtrEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif + +#ifdef HAVE_AESGCM + if (wc_AesGcmEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE, iv, sizeof(iv), + tag, sizeof(tag), NULL, 0) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_AesGcmDecrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE, iv, sizeof(iv), + tag, sizeof(tag), NULL, 0) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif + +#ifdef HAVE_AES_ECB + if (wc_AesEcbEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef HAVE_AES_DECRYPT + if (wc_AesEcbDecrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif +#endif /* HAVE_AES_ECB */ + +#ifdef WOLFSSL_AES_CFB + if (wc_AesCfbEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef HAVE_AES_DECRYPT + if (wc_AesCfbDecrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif +#if !defined(WOLFSSL_NO_AES_CFB_1_8) + if (wc_AesCfb1Encrypt(aes, cipher, plain, 8) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_AesCfb8Encrypt(aes, cipher, plain, 1) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef HAVE_AES_DECRYPT + if (wc_AesCfb1Decrypt(aes, cipher, plain, 8) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_AesCfb8Decrypt(aes, cipher, plain, 1) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif +#endif /* !WOLFSSL_NO_AES_CFB_1_8 */ +#endif /* WOLFSSL_AES_CFB */ + +#ifdef WOLFSSL_AES_OFB + if (wc_AesOfbEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef HAVE_AES_DECRYPT + if (wc_AesOfbDecrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif +#endif /* WOLFSSL_AES_OFB */ + + ret = 0; /* success */ + out: + +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + wc_AesDelete(aes, &aes); +#else + wc_AesFree(aes); +#endif + + return ret; +} +#endif /* any AES mode for aes_no_key_set_test */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aes_test(void) { wc_test_ret_t ret = 0; WOLFSSL_ENTER("aes_test"); +#ifdef WC_TEST_HAVE_AES_NO_KEY_SET + ret = aes_no_key_set_test(); + if (ret != 0) + return ret; +#endif + #ifndef HAVE_RENESAS_SYNC ret = aes_key_size_test(); if (ret != 0) From 26b7048b647b5b0033931592f0b9dfa512479cbc Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 23 Jun 2026 11:26:39 +0000 Subject: [PATCH 02/15] RSA-PSS: test signature-to-message hash binding (F-6157) wc_RsaPSS_CheckPadding_ex2 binds a PSS signature to the message with a single comparison of the recomputed hash against the encoded hash H. Every positive PSS test passes the correct digest and the negative tests use a wrong salt length that fails before that comparison is reached, so deleting the comparison left all default tests passing even though any well-formed PSS structure would then verify against any message. After each successful sign/verify round-trip in rsa_pss_test, flip one bit of the message digest, keep the correct salt length, and require BAD_PADDING_E. This makes the hash comparison the deciding check and catches its removal. --- wolfcrypt/test/test.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index b2a936ae74..95b1d5df3c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28452,6 +28452,9 @@ static wc_test_ret_t rsa_decode_test(RsaKey* keyPub) static wc_test_ret_t rsa_pss_test(WC_RNG* rng, RsaKey* key) { byte digest[WC_MAX_DIGEST_SIZE]; +#ifndef WOLFSSL_MICROCHIP_TA100 + byte tamperedDigest[WC_MAX_DIGEST_SIZE]; +#endif wc_test_ret_t ret = 0; const char inStr[] = TEST_STRING; word32 inLen = (word32)TEST_STRING_SZ; @@ -28579,6 +28582,27 @@ static wc_test_ret_t rsa_pss_test(WC_RNG* rng, RsaKey* key) #endif if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_rsa_pss); + + /* A well-formed PSS structure must not verify against a different + * message hash. Flip one digest bit, keep the correct salt length, + * and confirm the signature-to-message binding rejects it. */ + XMEMCPY(tamperedDigest, digest, digestSz); + tamperedDigest[0] ^= 0x01; +#if defined(HAVE_SELFTEST) && \ + (!defined(HAVE_SELFTEST_VERSION) || (HAVE_SELFTEST_VERSION < 2)) + ret = wc_RsaPSS_CheckPadding_ex(tamperedDigest, digestSz, plain, + plainSz, hash[j], -1); +#elif defined(HAVE_SELFTEST) && (HAVE_SELFTEST_VERSION == 2) + ret = wc_RsaPSS_CheckPadding_ex(tamperedDigest, digestSz, plain, + plainSz, hash[j], -1, 0); +#else + ret = wc_RsaPSS_CheckPadding_ex2(tamperedDigest, digestSz, plain, + plainSz, hash[j], -1, wc_RsaEncryptSize(key)*8, + HEAP_HINT); +#endif + if (ret != WC_NO_ERR_TRACE(BAD_PADDING_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_rsa_pss); + ret = 0; #endif /* WOLFSSL_MICROCHIP_TA100 */ #ifdef RSA_PSS_TEST_WRONG_PARAMS From b9da934a1a7372908f71663f4a59cc9c3ab86178 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 23 Jun 2026 13:44:39 +0000 Subject: [PATCH 03/15] AES: track key-set state with a dedicated flag (F-6151) Replace the keylen == 0 guards with a new Aes.keySet bitfield, set by the key-installation paths and checked in the mode APIs. keylen alone is not a reliable "key installed" signal across backends: some ports map a zero key length to a default (e.g. PSOC6 maps keylen 0 to AES-128), so a keylen-based check placed only in the software paths left those builds running with the all-zero key schedule and would fail aes_no_key_set_test. keySet is set in wc_AesSetKeyLocal (the funnel for the software SetKey/SetKeyDirect/GcmSetKey paths) and in the PSOC6 wc_AesSetKey wrapper, and checked in the public CBC/CTR/GCM/ECB/CFB/OFB entry points, including the PSOC6 port variants. --- wolfcrypt/src/aes.c | 56 +++++++++++++++++++++++++++++++---------- wolfssl/wolfcrypt/aes.h | 5 ++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 337dfbcbdf..d84daafba7 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -5176,7 +5176,10 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, int wc_AesSetKey(Aes* aes, const byte* userKey, word32 keylen, const byte* iv, int dir) { - return wc_Psoc6_Aes_SetKey(aes, userKey, keylen, iv, dir); + int ret = wc_Psoc6_Aes_SetKey(aes, userKey, keylen, iv, dir); + if (ret == 0 && aes != NULL) + aes->keySet = 1; + return ret; } #if defined(WOLFSSL_AES_DIRECT) @@ -5492,6 +5495,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) if (ret == 0) { /* Callback succeeded - SE owns the key */ aes->keylen = (int)keylen; + aes->keySet = 1; if (iv != NULL) XMEMCPY(aes->reg, iv, WC_AES_BLOCK_SIZE); else @@ -5608,6 +5612,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) * reads it as the source of truth for the configured key size. */ aes->keylen = (int)keylen; aes->rounds = (keylen / 4) + 6; + aes->keySet = 1; #if defined(WOLFSSL_AES_COUNTER) || defined(WOLFSSL_AES_CFB) || \ defined(WOLFSSL_AES_OFB) || defined(WOLFSSL_AES_XTS) || \ defined(WOLFSSL_AES_CTS) @@ -5637,6 +5642,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) aes->keylen = (int)keylen; aes->rounds = (keylen/4) + 6; + aes->keySet = 1; ret = wc_AesSetIV(aes, iv); if (ret != 0) return ret; @@ -6969,12 +6975,20 @@ int wc_AesSetIV(Aes* aes, const byte* iv) int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) { + if (aes == NULL || aes->keySet == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } return wc_Psoc6_Aes_CbcEncrypt(aes, out, in, sz); } #if defined(HAVE_AES_DECRYPT) int wc_AesCbcDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) { + if (aes == NULL || aes->keySet == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } return wc_Psoc6_Aes_CbcDecrypt(aes, out, in, sz); } #endif /* HAVE_AES_DECRYPT */ @@ -7040,7 +7054,7 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* Software/HW key schedule required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -7275,7 +7289,7 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* Software/HW key schedule required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -7760,7 +7774,7 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* Software/HW key schedule required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -10987,7 +11001,7 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* Software/HW key schedule (and hash subkey H) required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -11805,7 +11819,7 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* Software/HW key schedule (and hash subkey H) required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -15782,6 +15796,10 @@ int wc_AesEcbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) { if ((in == NULL) || (out == NULL) || (aes == NULL)) return BAD_FUNC_ARG; + if (aes->keySet == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } return wc_Psoc6_Aes_EcbEncrypt(aes, out, in, sz); } @@ -15793,6 +15811,10 @@ int wc_AesEcbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) { if ((in == NULL) || (out == NULL) || (aes == NULL)) return BAD_FUNC_ARG; + if (aes->keySet == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } return wc_Psoc6_Aes_EcbDecrypt(aes, out, in, sz); } @@ -15831,7 +15853,7 @@ static WARN_UNUSED_RESULT int _AesEcbEncrypt( #endif /* Software key schedule required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -15938,7 +15960,7 @@ static WARN_UNUSED_RESULT int _AesEcbDecrypt( #endif /* Software key schedule required from here on. */ - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16051,12 +16073,20 @@ int wc_AesEcbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) int wc_AesCfbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) { + if (aes == NULL || aes->keySet == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } return wc_Psoc6_Aes_CfbEncrypt(aes, out, in, sz); } #ifdef HAVE_AES_DECRYPT int wc_AesCfbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) { + if (aes == NULL || aes->keySet == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } return wc_Psoc6_Aes_CfbDecrypt(aes, out, in, sz); } #endif /* HAVE_AES_DECRYPT */ @@ -16089,7 +16119,7 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, if (sz == 0) { return 0; } - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16176,7 +16206,7 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, if (sz == 0) { return 0; } - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16408,7 +16438,7 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB8( if (sz == 0) { return 0; } - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16472,7 +16502,7 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( if (sz == 0) { return 0; } - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16634,7 +16664,7 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, if (sz == 0) { return 0; } - if (aes->keylen == 0) { + if (aes->keySet == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } diff --git a/wolfssl/wolfcrypt/aes.h b/wolfssl/wolfcrypt/aes.h index 183aad072f..fc1594e599 100644 --- a/wolfssl/wolfcrypt/aes.h +++ b/wolfssl/wolfcrypt/aes.h @@ -294,6 +294,11 @@ struct Aes { #endif int keylen; + /* Set to 1 once a key has been installed (wc_AesSetKey/SetKeyDirect/ + * GcmSetKey). Checked by the mode APIs so they fail instead of running + * with the all-zero key schedule left by wc_AesInit. */ + WC_BITFIELD keySet:1; + ALIGN16 word32 reg[WC_AES_BLOCK_SIZE / sizeof(word32)]; /* for CBC mode */ ALIGN16 word32 tmp[WC_AES_BLOCK_SIZE / sizeof(word32)]; /* same */ From fc56c30cccf30273b54c38f96d89250ba53dbc31 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 23 Jun 2026 17:00:57 +0000 Subject: [PATCH 04/15] AES: rename key-set flag and set it on every key install (F-6151) Address review of the keySet bitfield: - Rename keySet to keyInstalled. struct Aes already has a Cavium-only keySet member (HAVE_CAVIUM_OCTEON_SYNC), so an unconditional keySet bitfield was a duplicate that would not compile on that build. - Set keyInstalled on every key install, not just the software funnel. The ARM, PPC64 and other hardware wc_AesSetKey/SetKeyDirect variants populate keylen/rounds directly; without also setting the flag they would have made the shared mode APIs reject a validly-keyed context. The flag is now set at every keylen-assignment point in aes.c and in the PSOC6 SetKey wrapper. --- wolfcrypt/src/aes.c | 53 ++++++++++++++++++++++++----------------- wolfssl/wolfcrypt/aes.h | 5 ++-- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index d84daafba7..f461ba198a 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -4522,6 +4522,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, rk = aes->key; aes->keylen = keylen; + aes->keyInstalled = 1; aes->rounds = keylen/4 + 6; XMEMCPY(rk, userKey, keylen); #ifdef WOLF_CRYPTO_CB @@ -4611,6 +4612,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, #endif aes->keylen = keylen; + aes->keyInstalled = 1; aes->rounds = keylen/4 + 6; XMEMCPY(aes->key, userKey, keylen); @@ -4695,6 +4697,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, #endif aes->keylen = keylen; + aes->keyInstalled = 1; aes->rounds = keylen/4 + 6; XMEMCPY(aes->key, userKey, keylen); ret = nrf51_aes_set_key(userKey); @@ -4754,6 +4757,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, #endif aes->keylen = keylen; + aes->keyInstalled = 1; aes->rounds = keylen/4 + 6; XMEMCPY(aes->key, userKey, keylen); @@ -4816,6 +4820,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, } aes->keylen = keylen; + aes->keyInstalled = 1; aes->rounds = keylen/4 + 6; XMEMCPY(aes->key, userKey, keylen); @@ -4881,6 +4886,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, aes->keylen = (int)keylen; aes->rounds = (keylen/4) + 6; + aes->keyInstalled = 1; #ifndef WOLFSSL_ARMASM_NO_HW_CRYPTO AES_set_key_AARCH32(userKey, keylen, (byte*)aes->key, dir); @@ -4933,6 +4939,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, if (ret == 0) { /* Callback succeeded - SE owns the key */ aes->keylen = (int)keylen; + aes->keyInstalled = 1; if (iv != NULL) XMEMCPY(aes->reg, iv, WC_AES_BLOCK_SIZE); else @@ -4993,6 +5000,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, aes->keylen = (int)keylen; aes->rounds = (keylen/4) + 6; + aes->keyInstalled = 1; /* Determine base vs vector-crypto before the (dispatched) key setup so * the schedule matches the mode functions that later consume it. */ @@ -5041,6 +5049,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, if (ret == 0) { /* Callback succeeded - SE owns the key */ aes->keylen = (int)keylen; + aes->keyInstalled = 1; if (iv != NULL) XMEMCPY(aes->reg, iv, WC_AES_BLOCK_SIZE); else @@ -5178,7 +5187,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, { int ret = wc_Psoc6_Aes_SetKey(aes, userKey, keylen, iv, dir); if (ret == 0 && aes != NULL) - aes->keySet = 1; + aes->keyInstalled = 1; return ret; } @@ -5495,7 +5504,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) if (ret == 0) { /* Callback succeeded - SE owns the key */ aes->keylen = (int)keylen; - aes->keySet = 1; + aes->keyInstalled = 1; if (iv != NULL) XMEMCPY(aes->reg, iv, WC_AES_BLOCK_SIZE); else @@ -5612,7 +5621,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) * reads it as the source of truth for the configured key size. */ aes->keylen = (int)keylen; aes->rounds = (keylen / 4) + 6; - aes->keySet = 1; + aes->keyInstalled = 1; #if defined(WOLFSSL_AES_COUNTER) || defined(WOLFSSL_AES_CFB) || \ defined(WOLFSSL_AES_OFB) || defined(WOLFSSL_AES_XTS) || \ defined(WOLFSSL_AES_CTS) @@ -5642,7 +5651,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) aes->keylen = (int)keylen; aes->rounds = (keylen/4) + 6; - aes->keySet = 1; + aes->keyInstalled = 1; ret = wc_AesSetIV(aes, iv); if (ret != 0) return ret; @@ -6975,7 +6984,7 @@ int wc_AesSetIV(Aes* aes, const byte* iv) int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) { - if (aes == NULL || aes->keySet == 0) { + if (aes == NULL || aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -6985,7 +6994,7 @@ int wc_AesSetIV(Aes* aes, const byte* iv) #if defined(HAVE_AES_DECRYPT) int wc_AesCbcDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) { - if (aes == NULL || aes->keySet == 0) { + if (aes == NULL || aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -7054,7 +7063,7 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* Software/HW key schedule required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -7289,7 +7298,7 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* Software/HW key schedule required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -7774,7 +7783,7 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif /* Software/HW key schedule required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -11001,7 +11010,7 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* Software/HW key schedule (and hash subkey H) required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -11819,7 +11828,7 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* Software/HW key schedule (and hash subkey H) required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -15796,7 +15805,7 @@ int wc_AesEcbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) { if ((in == NULL) || (out == NULL) || (aes == NULL)) return BAD_FUNC_ARG; - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -15811,7 +15820,7 @@ int wc_AesEcbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) { if ((in == NULL) || (out == NULL) || (aes == NULL)) return BAD_FUNC_ARG; - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -15853,7 +15862,7 @@ static WARN_UNUSED_RESULT int _AesEcbEncrypt( #endif /* Software key schedule required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -15960,7 +15969,7 @@ static WARN_UNUSED_RESULT int _AesEcbDecrypt( #endif /* Software key schedule required from here on. */ - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16073,7 +16082,7 @@ int wc_AesEcbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) int wc_AesCfbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) { - if (aes == NULL || aes->keySet == 0) { + if (aes == NULL || aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16083,7 +16092,7 @@ int wc_AesCfbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #ifdef HAVE_AES_DECRYPT int wc_AesCfbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) { - if (aes == NULL || aes->keySet == 0) { + if (aes == NULL || aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16119,7 +16128,7 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, if (sz == 0) { return 0; } - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16206,7 +16215,7 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, if (sz == 0) { return 0; } - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16438,7 +16447,7 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB8( if (sz == 0) { return 0; } - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16502,7 +16511,7 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( if (sz == 0) { return 0; } - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } @@ -16664,7 +16673,7 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, if (sz == 0) { return 0; } - if (aes->keySet == 0) { + if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } diff --git a/wolfssl/wolfcrypt/aes.h b/wolfssl/wolfcrypt/aes.h index fc1594e599..d8b80d8307 100644 --- a/wolfssl/wolfcrypt/aes.h +++ b/wolfssl/wolfcrypt/aes.h @@ -296,8 +296,9 @@ struct Aes { /* Set to 1 once a key has been installed (wc_AesSetKey/SetKeyDirect/ * GcmSetKey). Checked by the mode APIs so they fail instead of running - * with the all-zero key schedule left by wc_AesInit. */ - WC_BITFIELD keySet:1; + * with the all-zero key schedule left by wc_AesInit. Distinct from the + * Cavium-only keySet field below. */ + WC_BITFIELD keyInstalled:1; ALIGN16 word32 reg[WC_AES_BLOCK_SIZE / sizeof(word32)]; /* for CBC mode */ ALIGN16 word32 tmp[WC_AES_BLOCK_SIZE / sizeof(word32)]; /* same */ From c32ba473e9be12817714b2ab9e64f3543ff896c4 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 24 Jun 2026 11:43:42 +0000 Subject: [PATCH 05/15] AES: reject no-key use on PIC32MZ and RISC-V backends (F-6151) aes_no_key_set_test runs on every CI backend and exposed two ports whose own mode functions ran with an unset key. Other hardware backends (STM32, the secure-element sims) already reject this via wc_AesGetKeySize. - PIC32MZ: guard wc_AesCbcEncrypt/Decrypt. keyInstalled is already set through wc_AesSetKeyLocal, the generic SetKey funnel these use. - RISC-V assembly: set keyInstalled in all three wc_AesSetKey variants and guard CBC/ECB/GCM encrypt and decrypt. CTR already rejects via its rounds switch. --- wolfcrypt/src/aes.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index f461ba198a..987f9c7994 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -6884,6 +6884,11 @@ int wc_AesSetIV(Aes* aes, const byte* iv) { int ret; + if (aes == NULL || aes->keyInstalled == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + if (sz == 0) return 0; @@ -6914,6 +6919,11 @@ int wc_AesSetIV(Aes* aes, const byte* iv) int ret; byte scratch[WC_AES_BLOCK_SIZE]; + if (aes == NULL || aes->keyInstalled == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + if (sz == 0) return 0; From 67fcd9600fbbdec984e935a28d8961c9b6d0886b Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 24 Jun 2026 12:46:55 +0000 Subject: [PATCH 06/15] test: skip AES no-key test under FIPS and selftest (F-6151) Under FIPS/selftest the AES functions come from the validated module, which does not reject use before a key is installed, so aes_no_key_set_test failed on the CAVP selftest and FIPS customer-config CI jobs. The keyInstalled guard is a non-FIPS hardening, so gate the test on !HAVE_FIPS && !HAVE_SELFTEST. The FIPS CASTs (wc_RunAllCast_fips) pass, confirming the change does not affect validated AES. --- wolfcrypt/test/test.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 95b1d5df3c..8a0f5221f2 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -16503,9 +16503,13 @@ static wc_test_ret_t aes_ecb_direct_test(void) } #endif /* HAVE_AES_ECB || WOLFSSL_AES_DIRECT */ -#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \ +/* The keyInstalled guard is a non-FIPS hardening; under FIPS/selftest the AES + * functions come from the validated module and don't reject a missing key, so + * this test does not apply there. */ +#if (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \ defined(HAVE_AESGCM) || defined(HAVE_AES_ECB) || \ - defined(WOLFSSL_AES_CFB) || defined(WOLFSSL_AES_OFB) + defined(WOLFSSL_AES_CFB) || defined(WOLFSSL_AES_OFB)) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) #define WC_TEST_HAVE_AES_NO_KEY_SET /* Ensure AES mode APIs fail when used before wc_AesSetKey installs a key, * instead of running with the all-zero key schedule left by wc_AesInit. */ From 6700a9eac924443edfd78cdfc04798b5ebabedca Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 24 Jun 2026 18:17:03 +0000 Subject: [PATCH 07/15] RSA-PSS test: use ret-independent failure code for tamper check (F-6157) Address review feedback on the negative tampered-digest assertion, which encoded ret via WC_TEST_RET_ENC_EC(ret). That is in fact non-zero even for ret == 0, but switch to WC_TEST_RET_ENC_NC so the failure code is unambiguously non-zero and independent of ret. Verified by mutation: deleting the signature-to-message hash compare still fails the test. --- wolfcrypt/test/test.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 8a0f5221f2..70b30a0c96 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -28604,8 +28604,12 @@ static wc_test_ret_t rsa_pss_test(WC_RNG* rng, RsaKey* key) plainSz, hash[j], -1, wc_RsaEncryptSize(key)*8, HEAP_HINT); #endif + /* Negative test: any result other than BAD_PADDING_E is a + * failure. In particular a 0 return (tampered digest wrongly + * accepted) must fail the test, so use a ret-independent, + * non-zero code rather than encoding ret. */ if (ret != WC_NO_ERR_TRACE(BAD_PADDING_E)) - ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_rsa_pss); + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_rsa_pss); ret = 0; #endif /* WOLFSSL_MICROCHIP_TA100 */ From 38ea277ed3b72bf26aefc4f8f2b013749a5c58a7 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 14 Jul 2026 12:35:17 +0000 Subject: [PATCH 08/15] AES-CCM: reject use before a key is set (F-6151) CCM has the same zero-key exposure as GCM: CBC-MAC and CTR run against the all-zero key schedule left by wc_AesInit. Add the keyInstalled guard to the software CCM encrypt/decrypt paths, mirroring GCM, and to the RISC-V port so it returns BAD_FUNC_ARG instead of KEYUSAGE_E. Extend aes_no_key_set_test with CCM cases. --- wolfcrypt/src/aes.c | 12 ++++++++++++ wolfcrypt/test/test.c | 17 ++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 987f9c7994..58644aaf7d 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -15028,6 +15028,12 @@ int wc_AesCcmEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz, } #endif + /* Software/HW key schedule required from here on. */ + if (aes->keyInstalled == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + XMEMSET(A, 0, sizeof(A)); XMEMCPY(B+1, nonce, nonceSz); @@ -15192,6 +15198,12 @@ int wc_AesCcmDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz, } #endif + /* Software/HW key schedule required from here on. */ + if (aes->keyInstalled == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } + o = out; oSz = inSz; XMEMSET(A, 0, sizeof A); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 70b30a0c96..1083f3444b 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -16507,7 +16507,7 @@ static wc_test_ret_t aes_ecb_direct_test(void) * functions come from the validated module and don't reject a missing key, so * this test does not apply there. */ #if (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \ - defined(HAVE_AESGCM) || defined(HAVE_AES_ECB) || \ + defined(HAVE_AESGCM) || defined(HAVE_AESCCM) || defined(HAVE_AES_ECB) || \ defined(WOLFSSL_AES_CFB) || defined(WOLFSSL_AES_OFB)) && \ !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) #define WC_TEST_HAVE_AES_NO_KEY_SET @@ -16523,14 +16523,14 @@ static wc_test_ret_t aes_no_key_set_test(void) #endif byte plain[WC_AES_BLOCK_SIZE]; byte cipher[WC_AES_BLOCK_SIZE]; -#ifdef HAVE_AESGCM +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) byte iv[WC_AES_BLOCK_SIZE]; byte tag[WC_AES_BLOCK_SIZE]; #endif XMEMSET(plain, 0, sizeof(plain)); XMEMSET(cipher, 0, sizeof(cipher)); -#ifdef HAVE_AESGCM +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) XMEMSET(iv, 0, sizeof(iv)); XMEMSET(tag, 0, sizeof(tag)); #endif @@ -16572,6 +16572,17 @@ static wc_test_ret_t aes_no_key_set_test(void) ERROR_OUT(WC_TEST_RET_ENC_NC, out); #endif +#ifdef HAVE_AESCCM + if (wc_AesCcmEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE, iv, 13, + tag, sizeof(tag), NULL, 0) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef HAVE_AES_DECRYPT + if (wc_AesCcmDecrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE, iv, 13, + tag, sizeof(tag), NULL, 0) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif +#endif /* HAVE_AESCCM */ + #ifdef HAVE_AES_ECB if (wc_AesEcbEncrypt(aes, cipher, plain, WC_AES_BLOCK_SIZE) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) From 5e48953b95c6c9b223d76088301dc7785b203f61 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 14 Jul 2026 12:35:39 +0000 Subject: [PATCH 09/15] AES: check for a key before the sz == 0 early return (F-6151) In the software CFB/CFB1/CFB8/OFB helpers the sz == 0 early return came before the keyInstalled check, so a no-key call succeeded with sz == 0 but failed with sz > 0. Check the key first; crypto callbacks already ran in the callers. CBC keeps its sz == 0 return first because the DCP dispatch below it cannot handle sz == 0 (reads out + sz - 16); comment the ordering there. --- wolfcrypt/src/aes.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 58644aaf7d..a508fddba6 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -7033,6 +7033,8 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) } if (sz == 0) { + /* Keep above the DCP/crypto-cb dispatches: they must not see + * sz == 0. A missing key is only reported when there is work. */ return 0; } @@ -7252,6 +7254,8 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) } if (sz == 0) { + /* Keep above the DCP/crypto-cb dispatches: they must not see + * sz == 0. A missing key is only reported when there is work. */ return 0; } @@ -16147,13 +16151,13 @@ static WARN_UNUSED_RESULT int AesCfbEncrypt_C(Aes* aes, byte* out, if ((aes == NULL) || (out == NULL) || (in == NULL)) { return BAD_FUNC_ARG; } - if (sz == 0) { - return 0; - } if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } + if (sz == 0) { + return 0; + } if (aes->left > 0) { /* consume any unused bytes left in aes->tmp */ @@ -16234,13 +16238,13 @@ static WARN_UNUSED_RESULT int AesCfbDecrypt_C(Aes* aes, byte* out, if ((aes == NULL) || (out == NULL) || (in == NULL)) { return BAD_FUNC_ARG; } - if (sz == 0) { - return 0; - } if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } + if (sz == 0) { + return 0; + } if (aes->left > 0) { /* consume any unused bytes left in aes->tmp */ @@ -16466,13 +16470,13 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB8( return BAD_FUNC_ARG; } - if (sz == 0) { - return 0; - } if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } + if (sz == 0) { + return 0; + } VECTOR_REGISTERS_PUSH; @@ -16530,13 +16534,13 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( return BAD_FUNC_ARG; } - if (sz == 0) { - return 0; - } if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } + if (sz == 0) { + return 0; + } VECTOR_REGISTERS_PUSH; @@ -16692,13 +16696,13 @@ static WARN_UNUSED_RESULT int AesOfbCrypt_C(Aes* aes, byte* out, const byte* in, if ((aes == NULL) || (out == NULL) || (in == NULL)) { return BAD_FUNC_ARG; } - if (sz == 0) { - return 0; - } if (aes->keyInstalled == 0) { WOLFSSL_MSG("AES key not set"); return BAD_FUNC_ARG; } + if (sz == 0) { + return 0; + } if (aes->left > 0) { /* consume any unused bytes left in aes->tmp */ From 1e796261379e80f8b45d1625f9121ebd3049897e Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 15 Jul 2026 02:31:39 +0000 Subject: [PATCH 10/15] Address review: set keyInstalled in HW port key setup (F-6151) The keyInstalled guard added to the shared aes.c mode functions is only set by aes.c's own key schedule and the RISC-V port. Ports that ship their own compile-time wc_AesSetKey/wc_AesGcmSetKey left keyInstalled at 0, so any AES mode that falls through to a guarded aes.c path returned BAD_FUNC_ARG after a correct key setup. Set keyInstalled at the key-install point of the affected ports so the shared mode guards accept a validly-keyed context: - silabs, af_alg, devcrypto, ti, caam: wc_AesSetKey - af_alg, kcapi: wc_AesGcmSetKey --- wolfcrypt/src/port/af_alg/afalg_aes.c | 4 ++++ wolfcrypt/src/port/caam/caam_aes.c | 2 ++ wolfcrypt/src/port/devcrypto/devcrypto_aes.c | 2 ++ wolfcrypt/src/port/kcapi/kcapi_aes.c | 3 +++ wolfcrypt/src/port/silabs/silabs_aes.c | 3 +++ wolfcrypt/src/port/ti/ti-aes.c | 2 ++ 6 files changed, 16 insertions(+) diff --git a/wolfcrypt/src/port/af_alg/afalg_aes.c b/wolfcrypt/src/port/af_alg/afalg_aes.c index efbf635859..5ee66a3e98 100644 --- a/wolfcrypt/src/port/af_alg/afalg_aes.c +++ b/wolfcrypt/src/port/af_alg/afalg_aes.c @@ -146,6 +146,8 @@ int wc_AesSetKey(Aes* aes, const byte* userKey, word32 keylen, #endif aes->keylen = keylen; aes->rounds = keylen/4 + 6; + /* Mark key installed so the shared aes.c mode guards accept this context. */ + aes->keyInstalled = 1; #ifdef WOLFSSL_AES_COUNTER aes->left = 0; @@ -556,6 +558,8 @@ int wc_AesGcmSetKey(Aes* aes, const byte* key, word32 len) aes->keylen = len; aes->rounds = len/4 + 6; aes->dir = AES_ENCRYPTION; + /* Mark key installed so the shared aes.c mode guards accept this context. */ + aes->keyInstalled = 1; if (aes->rdFd > WC_SOCK_NOTSET) { (void)close(aes->rdFd); diff --git a/wolfcrypt/src/port/caam/caam_aes.c b/wolfcrypt/src/port/caam/caam_aes.c index 4ab3ee7f94..79e6a8056a 100644 --- a/wolfcrypt/src/port/caam/caam_aes.c +++ b/wolfcrypt/src/port/caam/caam_aes.c @@ -88,6 +88,8 @@ int wc_AesSetKey(Aes* aes, const byte* key, word32 len, default: return BAD_FUNC_ARG; } + /* Mark key installed so the shared aes.c mode guards accept this context. */ + aes->keyInstalled = 1; if ((ret = wc_AesSetIV(aes, iv)) != 0) { return ret; diff --git a/wolfcrypt/src/port/devcrypto/devcrypto_aes.c b/wolfcrypt/src/port/devcrypto/devcrypto_aes.c index 62cd624f01..30f4661bb0 100644 --- a/wolfcrypt/src/port/devcrypto/devcrypto_aes.c +++ b/wolfcrypt/src/port/devcrypto/devcrypto_aes.c @@ -123,6 +123,8 @@ int wc_AesSetKey(Aes* aes, const byte* userKey, word32 keylen, #endif aes->keylen = keylen; aes->rounds = keylen/4 + 6; + /* Mark key installed so the shared aes.c mode guards accept this context. */ + aes->keyInstalled = 1; #if defined(WOLFSSL_AES_COUNTER) || defined(WOLFSSL_AES_CFB) || \ defined(WOLFSSL_AES_OFB) || defined(WOLFSSL_AES_XTS) diff --git a/wolfcrypt/src/port/kcapi/kcapi_aes.c b/wolfcrypt/src/port/kcapi/kcapi_aes.c index 0aab365a3c..d2cd8be371 100644 --- a/wolfcrypt/src/port/kcapi/kcapi_aes.c +++ b/wolfcrypt/src/port/kcapi/kcapi_aes.c @@ -208,6 +208,9 @@ int wc_AesGcmSetKey(Aes* aes, const byte* key, word32 len) if (ret == 0) { aes->keylen = len; aes->rounds = len/4 + 6; + /* Mark key installed so the shared aes.c mode guards accept this + * context. */ + aes->keyInstalled = 1; /* save key until type is known i.e. CBC, ECB, ... */ XMEMCPY((byte*)(aes->devKey), key, len); diff --git a/wolfcrypt/src/port/silabs/silabs_aes.c b/wolfcrypt/src/port/silabs/silabs_aes.c index 498acea21c..10b6995514 100644 --- a/wolfcrypt/src/port/silabs/silabs_aes.c +++ b/wolfcrypt/src/port/silabs/silabs_aes.c @@ -87,6 +87,9 @@ int wc_AesSetKey(Aes* aes, const byte* userKey, word32 keylen, aes->ctx.key.storage.location.buffer.pointer = (void*)aes->key; aes->ctx.key.storage.location.buffer.size = keylen; aes->ctx.key.size = keylen; + /* Mark key installed so the shared aes.c mode guards accept this + * context. */ + aes->keyInstalled = 1; } return ret; diff --git a/wolfcrypt/src/port/ti/ti-aes.c b/wolfcrypt/src/port/ti/ti-aes.c index 4d77dcfe16..4cc820b5a0 100644 --- a/wolfcrypt/src/port/ti/ti-aes.c +++ b/wolfcrypt/src/port/ti/ti-aes.c @@ -97,6 +97,8 @@ int wc_AesSetKey(Aes* aes, const byte* key, word32 len, const byte* iv, int dir) } aes->keylen = len; aes->rounds = len / 4 + 6; + /* Mark key installed so the shared aes.c mode guards accept this context. */ + aes->keyInstalled = 1; XMEMCPY(aes->key, key, len); #if defined(WOLFSSL_AES_COUNTER) || defined(WOLFSSL_AES_CFB) || \ From a2683719ae5dd237bf7bb6f8948af2a1c63d25ec Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 16 Jul 2026 02:22:10 +0000 Subject: [PATCH 11/15] Address review: set keyInstalled in SECO CAAM key setup (F-6151) --- wolfcrypt/src/aes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index a508fddba6..6034bc2c15 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -5593,6 +5593,7 @@ static void AesSetKey_C(Aes* aes, const byte* key, word32 keySz, int dir) return WC_HW_E; } aes->blackKey = keyIdOut; + aes->keyInstalled = 1; return 0; } #endif From d27229ed896dbe14315c9dfa5a256af435d26702 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 16 Jul 2026 02:22:10 +0000 Subject: [PATCH 12/15] Address review: use INVALID_DEVID in aes_no_key_set_test (F-6151) --- wolfcrypt/test/test.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 1083f3444b..9b50e8e73a 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -16535,12 +16535,16 @@ static wc_test_ret_t aes_no_key_set_test(void) XMEMSET(tag, 0, sizeof(tag)); #endif + /* The keyInstalled guard is pure-software hardening placed after the + * crypto-cb dispatch, so use INVALID_DEVID here: a registered device would + * route these calls to the callback (against a zero key) and never reach + * the guard. */ #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) - aes = wc_AesNew(HEAP_HINT, devId, &ret); + aes = wc_AesNew(HEAP_HINT, INVALID_DEVID, &ret); if (aes == NULL) return WC_TEST_RET_ENC_EC(ret); #else - ret = wc_AesInit(aes, HEAP_HINT, devId); + ret = wc_AesInit(aes, HEAP_HINT, INVALID_DEVID); if (ret != 0) return WC_TEST_RET_ENC_EC(ret); #endif From ed7118051fecbc976bb8d80a7421855417b7bb5a Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 20 Jul 2026 02:21:54 +0000 Subject: [PATCH 13/15] Fix CI: reject no-key AES-CBC on RISC-V backend (F-6151) The RISC-V assembly CBC encrypt/decrypt paths return before the generic keyInstalled guard, so wc_AesCbcEncrypt/Decrypt accepted a call with no key set. aes_no_key_set_test caught this on the riscv64-o0 config. Add the guard to the RISC-V dispatch, matching the other backends. --- wolfcrypt/src/aes.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 6034bc2c15..3d4dfa3056 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -7050,6 +7050,10 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) #endif #if defined(WOLFSSL_RISCV_ASM) + if (aes->keyInstalled == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } AES_CBC_encrypt_RISCV64(in, out, sz, (byte*)aes->reg, (byte*)aes->key, (int)aes->rounds); (void)blocks; @@ -7287,6 +7291,10 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) } #if defined(WOLFSSL_RISCV_ASM) + if (aes->keyInstalled == 0) { + WOLFSSL_MSG("AES key not set"); + return BAD_FUNC_ARG; + } AES_CBC_decrypt_RISCV64(in, out, sz, (byte*)aes->reg, (byte*)aes->key, (int)aes->rounds); (void)blocks; From 37b9d4da54d94691591be1d4e6192f33fcf0773d Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 21 Jul 2026 15:59:31 +0000 Subject: [PATCH 14/15] Address review: set keyInstalled in FREESCALE_LTC/MMCAU key setup (F-6151) The keyInstalled guard added to the shared aes.c mode functions is set by every in-aes.c key-schedule branch except FREESCALE_LTC and FREESCALE_MMCAU. Their wc_AesSetKeyLocal installs the key but left keyInstalled at 0, so any guarded AES mode that falls through to a shared aes.c path returned an error after a valid key setup on those ports. Set keyInstalled at the key-install point in both branches, matching the other ports fixed earlier in this PR. --- wolfcrypt/src/aes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 3d4dfa3056..59eeba22b8 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -4650,6 +4650,7 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, aes->rounds = keylen/4 + 6; XMEMCPY(aes->key, userKey, keylen); + aes->keyInstalled = 1; #if defined(WOLFSSL_AES_COUNTER) || defined(WOLFSSL_AES_CFB) || \ defined(WOLFSSL_AES_OFB) || defined(WOLFSSL_AES_XTS) || \ @@ -5158,6 +5159,8 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock, #endif wolfSSL_CryptHwMutexUnLock(); + aes->keyInstalled = 1; + ret = wc_AesSetIV(aes, iv); } From 3917f2f935f1bcc9d1cb7a27f036fb7cdf4227e1 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 24 Jul 2026 03:05:20 +0000 Subject: [PATCH 15/15] Address review: reject no-key AES on AF_ALG backend (F-6151) The AF_ALG AES mode overrides route through wc_AesSetup, which called setsockopt(ALG_SET_KEY, key, 0) with keylen 0 and returned WC_AFALG_SOCK_E instead of BAD_FUNC_ARG when no key was installed, so aes_no_key_set_test failed on WOLFSSL_AFALG. Reject setup when keyInstalled is 0, matching the software backend's no-key guard. Covers CBC, CTR, ECB and GCM. --- wolfcrypt/src/port/af_alg/afalg_aes.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wolfcrypt/src/port/af_alg/afalg_aes.c b/wolfcrypt/src/port/af_alg/afalg_aes.c index 5ee66a3e98..8fefe25ff4 100644 --- a/wolfcrypt/src/port/af_alg/afalg_aes.c +++ b/wolfcrypt/src/port/af_alg/afalg_aes.c @@ -60,6 +60,10 @@ static int wc_AesSetup(Aes* aes, const char* type, const char* name, int ivSz, i byte* key = (byte*)aes->key; #endif + if (aes->keyInstalled == 0) { + return BAD_FUNC_ARG; + } + if (aes->alFd == WC_SOCK_NOTSET) { aes->alFd = wc_Afalg_Socket(); if (aes->alFd < 0) {