From 0281978c44e9ea41969d2fc1de1b43662337aea3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 12 Aug 2026 16:04:04 +0200 Subject: [PATCH 1/4] F-8254: PSA AES: abort the cipher operation on wc_AesSetIV() so a new IV takes effect --- tests/api/test_aes.c | 56 ++++++++++++++++++++++++++++++++ tests/api/test_aes.h | 2 ++ wolfcrypt/src/aes.c | 10 ++++++ wolfcrypt/src/port/psa/psa_aes.c | 45 +++++++++++++++++++------ wolfssl/wolfcrypt/port/psa/psa.h | 1 + 5 files changed, 104 insertions(+), 10 deletions(-) diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 6c137431f1c..8f9ab0250e2 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -226,6 +226,62 @@ int test_wc_AesSetIV(void) return EXPECT_RESULT(); } /* test_wc_AesSetIV */ +/* + * wc_AesSetIV() must restart the cipher stream, not just record a new IV: + * encrypting the same data twice from the same IV has to yield the same + * ciphertext both times. + */ +int test_wc_AesSetIV_RestartsStream(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_128) && !defined(WOLFSSL_KCAPI) && \ + (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER)) + Aes aes; + byte key16[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + byte iv[] = "1234567890abcdef"; + byte plain[2 * WC_AES_BLOCK_SIZE]; + byte first[sizeof(plain)]; + byte second[sizeof(plain)]; + + XMEMSET(plain, 0x5a, sizeof(plain)); + +#ifdef HAVE_AES_CBC + XMEMSET(first, 0, sizeof(first)); + XMEMSET(second, 0, sizeof(second)); + + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&aes, key16, (word32)sizeof(key16), iv, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCbcEncrypt(&aes, first, plain, sizeof(plain)), 0); + /* Rewind to the original IV - the second run must be independent of the + * first one, not chained onto it. */ + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesCbcEncrypt(&aes, second, plain, sizeof(plain)), 0); + ExpectBufEQ(second, first, sizeof(first)); + wc_AesFree(&aes); +#endif + +#ifdef WOLFSSL_AES_COUNTER + XMEMSET(first, 0, sizeof(first)); + XMEMSET(second, 0, sizeof(second)); + + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesSetKeyDirect(&aes, key16, (word32)sizeof(key16), iv, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, first, plain, sizeof(plain)), 0); + /* Rewind the counter block. */ + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, second, plain, sizeof(plain)), 0); + ExpectBufEQ(second, first, sizeof(first)); + wc_AesFree(&aes); +#endif +#endif + return EXPECT_RESULT(); +} /* test_wc_AesSetIV_RestartsStream */ + /******************************************************************************* * AES Direct diff --git a/tests/api/test_aes.h b/tests/api/test_aes.h index 5e67f630bb1..81737f9de32 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -26,6 +26,7 @@ int test_wc_AesSetKey(void); int test_wc_AesSetIV(void); +int test_wc_AesSetIV_RestartsStream(void); int test_wc_AesEncryptDecryptDirect(void); int test_wc_AesEcbEncryptDecrypt(void); int test_wc_AesCbcEncryptDecrypt(void); @@ -185,6 +186,7 @@ int test_wc_CryptoCb_AesKeyWrapEcbCompose(void); #define TEST_AES_DECLS \ TEST_DECL_GROUP("aes", test_wc_AesSetKey), \ TEST_DECL_GROUP("aes", test_wc_AesSetIV), \ + TEST_DECL_GROUP("aes", test_wc_AesSetIV_RestartsStream), \ TEST_DECL_GROUP("aes", test_wc_AesEncryptDecryptDirect), \ TEST_DECL_GROUP("aes", test_wc_AesEcbEncryptDecrypt), \ TEST_DECL_GROUP("aes", test_wc_AesCbcEncryptDecrypt), \ diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index a3aa862885b..2e9de9be4ab 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -6123,6 +6123,16 @@ int wc_AesSetIV(Aes* aes, const byte* iv) aes->left = 0; #endif +#if defined(WOLFSSL_HAVE_PSA) && !defined(WOLFSSL_PSA_NO_AES) + { + /* PSA takes the IV at operation setup time only, so an operation + * already in progress must be aborted for the new IV to take effect. */ + int ret = wc_psa_aes_reset_ctx(aes); + if (ret != 0) + return ret; + } +#endif + return 0; } diff --git a/wolfcrypt/src/port/psa/psa_aes.c b/wolfcrypt/src/port/psa/psa_aes.c index b789d0860fe..36f713d699d 100644 --- a/wolfcrypt/src/port/psa/psa_aes.c +++ b/wolfcrypt/src/port/psa/psa_aes.c @@ -116,6 +116,38 @@ int wc_psa_aes_get_key_size(Aes *aes, word32 *keySize) return 0; } +/** + * wc_psa_aes_reset_ctx() - abort the cipher operation in progress, if any + * @aes: Aes object + * + * After this call the next wc_psa_aes_encrypt_decrypt() sets up a fresh + * operation, re-reading the IV from aes->reg. The imported key (if any) is + * left untouched. + * + * returns: 0 on success, BAD_FUNC_ARG for bad argument, WC_HW_E on PSA error + */ +int wc_psa_aes_reset_ctx(Aes *aes) +{ + psa_status_t s; + + if (aes == NULL) + return BAD_FUNC_ARG; + + if (aes->ctx_initialized == 0) + return 0; + + PSA_LOCK(); + s = psa_cipher_abort(&aes->psa_ctx); + PSA_UNLOCK(); + + aes->ctx_initialized = 0; + + if (s != PSA_SUCCESS) + return WC_HW_E; + + return 0; +} + /** * wc_psa_aes_set_key() - set key / iv to object *aes * @aes: object to set the key into @@ -137,19 +169,12 @@ int wc_psa_aes_get_key_size(Aes *aes, word32 *keySize) int wc_psa_aes_set_key(Aes *aes, const uint8_t *key, size_t key_length, uint8_t *iv, psa_algorithm_t alg, int dir) { - psa_status_t s; int ret; /* the object was already used for other encryption. Reset the context */ - if (aes->ctx_initialized) { - PSA_LOCK(); - s = psa_cipher_abort(&aes->psa_ctx); - PSA_UNLOCK(); - if (s != PSA_SUCCESS) - return WC_HW_E; - - aes->ctx_initialized = 0; - } + ret = wc_psa_aes_reset_ctx(aes); + if (ret != 0) + return ret; /* a key was already imported, destroy it first */ if (aes->key_id != PSA_KEY_ID_NULL) { diff --git a/wolfssl/wolfcrypt/port/psa/psa.h b/wolfssl/wolfcrypt/port/psa/psa.h index edbffae26cf..f721ca6f8f6 100644 --- a/wolfssl/wolfcrypt/port/psa/psa.h +++ b/wolfssl/wolfcrypt/port/psa/psa.h @@ -92,6 +92,7 @@ WOLFSSL_API int wc_psa_get_random(unsigned char *out, word32 sz); int wc_psa_aes_init(Aes *aes); int wc_psa_aes_free(Aes *aes); +int wc_psa_aes_reset_ctx(Aes *aes); int wc_psa_aes_get_key_size(Aes *aes, word32 *keySize); int wc_psa_aes_set_key(Aes *aes, const uint8_t *key, size_t key_length, uint8_t *iv, From 4292ec5653db6353668bb90680effb89747f36ec Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 12 Aug 2026 16:40:29 +0200 Subject: [PATCH 2/4] tests: extend test_wc_AesSetIV_RestartsStream to cover KCAPI CBC --- tests/api/test_aes.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 8f9ab0250e2..7e9b7e29990 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -234,8 +234,11 @@ int test_wc_AesSetIV(void) int test_wc_AesSetIV_RestartsStream(void) { EXPECT_DECLS; -#if !defined(NO_AES) && defined(WOLFSSL_AES_128) && !defined(WOLFSSL_KCAPI) && \ - (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER)) +#if !defined(NO_AES) && defined(WOLFSSL_AES_128) && \ + (defined(HAVE_AES_CBC) || (defined(WOLFSSL_AES_COUNTER) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI))) Aes aes; byte key16[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, @@ -264,7 +267,10 @@ int test_wc_AesSetIV_RestartsStream(void) wc_AesFree(&aes); #endif -#ifdef WOLFSSL_AES_COUNTER +#if defined(WOLFSSL_AES_COUNTER) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) XMEMSET(first, 0, sizeof(first)); XMEMSET(second, 0, sizeof(second)); From 190b6ee113df853b08c006d3a7d79756d86936e2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 12 Aug 2026 16:40:29 +0200 Subject: [PATCH 3/4] KCAPI AES: tear down the CBC stream on wc_AesSetIV() so a new IV takes effect --- wolfcrypt/src/aes.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 2e9de9be4ab..dea64a9c2f7 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -6123,6 +6123,17 @@ int wc_AesSetIV(Aes* aes, const byte* iv) aes->left = 0; #endif +#ifdef WOLFSSL_KCAPI_AES + /* The kernel keeps the chaining state and takes the IV at stream setup + * time only, so tear the stream down for the new IV to take effect. It is + * set up again, from aes->reg, on the next cipher operation. */ + if (aes->init != 0) { + kcapi_cipher_destroy(aes->handle); + aes->handle = NULL; + aes->init = 0; + } +#endif + #if defined(WOLFSSL_HAVE_PSA) && !defined(WOLFSSL_PSA_NO_AES) { /* PSA takes the IV at operation setup time only, so an operation From d539b06564e8b404dd0131ec1a758832be7f9178 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 12 Aug 2026 17:57:35 +0200 Subject: [PATCH 4/4] PSA AES: tear down the object when psa_cipher_abort() fails in wc_psa_aes_reset_ctx() --- wolfcrypt/src/port/psa/psa_aes.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/port/psa/psa_aes.c b/wolfcrypt/src/port/psa/psa_aes.c index 36f713d699d..6e32cfb5533 100644 --- a/wolfcrypt/src/port/psa/psa_aes.c +++ b/wolfcrypt/src/port/psa/psa_aes.c @@ -120,9 +120,14 @@ int wc_psa_aes_get_key_size(Aes *aes, word32 *keySize) * wc_psa_aes_reset_ctx() - abort the cipher operation in progress, if any * @aes: Aes object * - * After this call the next wc_psa_aes_encrypt_decrypt() sets up a fresh - * operation, re-reading the IV from aes->reg. The imported key (if any) is - * left untouched. + * After a successful call the next wc_psa_aes_encrypt_decrypt() sets up a + * fresh operation, re-reading the IV from aes->reg. The imported key (if any) + * is left untouched. + * + * If the abort itself fails, psa_ctx may still hold resources and is no longer + * trustworthy for a fresh setup, so the whole object is torn down: the next + * operation then fails loudly rather than silently carrying on with the stale + * chaining state that this function exists to discard. * * returns: 0 on success, BAD_FUNC_ARG for bad argument, WC_HW_E on PSA error */ @@ -140,10 +145,13 @@ int wc_psa_aes_reset_ctx(Aes *aes) s = psa_cipher_abort(&aes->psa_ctx); PSA_UNLOCK(); + /* cleared before the check so wc_psa_aes_free() below doesn't abort twice */ aes->ctx_initialized = 0; - if (s != PSA_SUCCESS) + if (s != PSA_SUCCESS) { + wc_psa_aes_free(aes); return WC_HW_E; + } return 0; }