diff --git a/ChangeLog.md b/ChangeLog.md index d027365474..6e09b1e892 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,17 @@ +# wolfSSL Release (unreleased) + +## Enhancements + +* TLS 1.3: zero traffic key staging buffers in `SetKeysSide()` once a + CryptoCB callback has imported the AES key into a Secure Element + (`aes->devCtx != NULL`). Clears `keys->{client,server}_write_key` + on the provisioned side(s) after cipher init succeeds. The static + IV buffers (`keys->{client,server}_write_IV`, + `keys->aead_{enc,dec}_imp_IV`) are intentionally left intact because + `BuildTls13Nonce()` reads them on every AEAD record to construct the + per-record nonce. Scoped to TLS 1.3, non-DTLS, non-QUIC; requires + `WOLF_CRYPTO_CB` and `WOLF_CRYPTO_CB_AES_SETKEY`. + # wolfSSL Release 5.9.1 (Apr. 8, 2026) Release 5.9.1 has been developed according to wolfSSL's development and QA diff --git a/src/keys.c b/src/keys.c index 190a5d4dd3..c21b58c7f3 100644 --- a/src/keys.c +++ b/src/keys.c @@ -3591,6 +3591,75 @@ int SetKeysSide(WOLFSSL* ssl, enum encrypt_side side) ssl->heap, ssl->devId, ssl->rng, ssl->options.tls1_3); } + /* Zero the TLS-layer staging key buffers once the CryptoCB callback + * has imported the key into a Secure Element. + * + * Convention: after a successful wc_AesSetKey / wc_AesGcmSetKey where + * the CryptoCB handled the key import, the callback leaves + * aes->devCtx != NULL and the software key schedule (aes->key, + * aes->devKey, aes->gcm.H / aes->gcm.M0) is NOT populated. The TLS + * layer may therefore destroy its staging copy of the traffic key. + * + * Only the key buffers (client_write_key / server_write_key) are + * zeroed. The static IVs (client_write_IV / server_write_IV) and + * the AEAD implicit-IV copies (aead_{enc,dec}_imp_IV) are NOT + * zeroed: BuildTls13Nonce() in tls13.c reads keys->aead_*_imp_IV on + * every AEAD record to construct the per-record nonce + * (nonce = static_iv XOR seq_num, RFC 8446 Section 5.3). Zeroing + * them would break the record path or, if applied symmetrically on + * both peers, silently degenerate the nonce to the bare sequence + * number and break interop with any unpatched peer. The static_iv + * is not a confidentiality-critical secret in the same sense as + * the traffic key; losing it does not compromise plaintext. + * + * Scope: + * - TLS 1.3 only. TLS 1.2 additionally reads + * keys->{client,server}_write_key for rehandshake/secure + * renegotiation flows. + * - Non-DTLS. Dtls13EpochCopyKeys (called from Dtls13NewEpoch) + * references keys->*_write_key for epoch switching; DTLS 1.3 + * needs separate analysis. + * - Non-QUIC. QUIC traffic secrets live outside these buffers + * but the interaction with stack-installed QUIC handlers has + * not been audited; exclude until it is. + * + * When called with ENCRYPT_SIDE_ONLY or DECRYPT_SIDE_ONLY, only the + * buffer consumed by this call is zeroed; the complementary buffer + * is written in a later SetKeysSide() from its own DeriveTls13Keys() + * and StoreKeys() pair (StoreKeys gates on PROVISION_CLIENT / + * PROVISION_SERVER so only the provisioned side is written). + * + * Ordering: this block must run AFTER SetKeys() (so offload has + * happened) and BEFORE Dtls13SetRecordNumberKeys() / + * wolfSSL_quic_keys_active() below, in case a future refactor in + * either starts reading keys->*_write_key. The DTLS and QUIC gates + * in this block mean neither currently executes on the same ssl, + * but keep the order explicit. */ +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_AES_SETKEY) + if (ret == 0 && ssl->options.tls1_3 && !ssl->options.dtls + && !WOLFSSL_IS_QUIC(ssl)) { + int encOffloaded = (wc_encrypt != NULL && wc_encrypt->aes != NULL && + wc_encrypt->aes->devCtx != NULL); + int decOffloaded = (wc_decrypt != NULL && wc_decrypt->aes != NULL && + wc_decrypt->aes->devCtx != NULL); + + if (encOffloaded || decOffloaded) { + if (ssl->options.side == WOLFSSL_CLIENT_END) { + if (encOffloaded) + ForceZero(keys->client_write_key, ssl->specs.key_size); + if (decOffloaded) + ForceZero(keys->server_write_key, ssl->specs.key_size); + } + else { + if (encOffloaded) + ForceZero(keys->server_write_key, ssl->specs.key_size); + if (decOffloaded) + ForceZero(keys->client_write_key, ssl->specs.key_size); + } + } + } +#endif /* WOLF_CRYPTO_CB && WOLF_CRYPTO_CB_AES_SETKEY */ + #ifdef WOLFSSL_DTLS13 if (ret == 0 && ssl->options.dtls && IsAtLeastTLSv1_3(ssl->version)) ret = Dtls13SetRecordNumberKeys(ssl, side); diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 3d4eda1eec..ebe88d31ed 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -31,8 +31,16 @@ #include #include #include +/* is required because the CryptoCB TLS 1.3 key-zeroing + * tests below inspect session state (ssl->keys.*_write_key, + * ssl->encrypt.aes->devCtx) to verify that the TLS-layer staging buffers are + * zeroed after a CryptoCB-driven AES-GCM key offload. The tests live here + * rather than in test_tls13.c because they exercise a CryptoCB-AES + * interaction and share the existing AES test harness. */ +#include #include #include +#include #if defined(HAVE_SELFTEST) || (defined(HAVE_FIPS_VERSION) && \ (HAVE_FIPS_VERSION <= 2)) @@ -7867,6 +7875,9 @@ int test_wc_AesSivEncryptDecrypt(void) #include +/* Test CryptoCB device IDs (must be unique across test_aes.c): + * 7 = AES setkey + AES-GCM offload (see TEST_CRYPTOCB_AES_DEVID) + * 9 = TLS 1.3 key-zeroing offload (see TEST_TLS13_ZERO_DEVID) */ #define TEST_CRYPTOCB_AES_DEVID 7 /* Test state tracking */ @@ -8598,6 +8609,394 @@ int test_wc_CryptoCb_AesGcm_EncryptDecrypt(void) #endif /* WOLF_CRYPTO_CB && WOLF_CRYPTO_CB_AES_SETKEY && !NO_AES && HAVE_AESGCM */ +/*----------------------------------------------------------------------------* + | CryptoCB AES-GCM TLS 1.3 Key Zeroing Tests + *----------------------------------------------------------------------------*/ + +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_AES_SETKEY) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + +#define TEST_TLS13_ZERO_DEVID 9 +#define TEST_TLS13_ZERO_MAX_SLOTS 16 + +typedef struct { + byte key[AES_256_KEY_SIZE]; + word32 keySz; + int valid; +} Tls13ZeroKeySlot; + +static Tls13ZeroKeySlot tls13ZeroSlots[TEST_TLS13_ZERO_MAX_SLOTS]; +static word32 tls13ZeroSlotCount = 0; + +/* Try to reclaim a slot previously invalidated by the FREE path + * (valid == 0) before expanding the pool. Without this, a long-running + * handshake + multiple KeyUpdate cycles can exhaust TEST_TLS13_ZERO_MAX_SLOTS + * even though most slots have been freed. */ +static Tls13ZeroKeySlot* tls13Zero_AllocSlot(void) +{ + word32 i; + for (i = 0; i < tls13ZeroSlotCount; i++) { + if (!tls13ZeroSlots[i].valid) + return &tls13ZeroSlots[i]; + } + if (tls13ZeroSlotCount >= (word32)TEST_TLS13_ZERO_MAX_SLOTS) + return NULL; + return &tls13ZeroSlots[tls13ZeroSlotCount++]; +} + +static int test_Tls13Zero_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)ctx; + + if (devId != TEST_TLS13_ZERO_DEVID) + return CRYPTOCB_UNAVAILABLE; + + if (info->algo_type == WC_ALGO_TYPE_CIPHER && + info->cipher.type == WC_CIPHER_AES && + info->cipher.aessetkey.aes != NULL) { + + Aes* aes = info->cipher.aessetkey.aes; + const byte* key = info->cipher.aessetkey.key; + word32 keySz = info->cipher.aessetkey.keySz; + Tls13ZeroKeySlot* slot; + + if (key == NULL || keySz == 0 || keySz > AES_256_KEY_SIZE) + return BAD_FUNC_ARG; + + slot = tls13Zero_AllocSlot(); + if (slot == NULL) + return MEMORY_E; + + XMEMCPY(slot->key, key, keySz); + slot->keySz = keySz; + slot->valid = 1; + aes->devCtx = slot; + return 0; + } + + if (info->algo_type == WC_ALGO_TYPE_CIPHER && + info->cipher.type == WC_CIPHER_AES_GCM && + info->cipher.enc) { + + Aes* aes = info->cipher.aesgcm_enc.aes; + Tls13ZeroKeySlot* slot; + Aes tempAes; + int ret; + + if (aes == NULL || aes->devCtx == NULL) + return BAD_FUNC_ARG; + + slot = (Tls13ZeroKeySlot*)aes->devCtx; + if (!slot->valid) + return BAD_STATE_E; + + ret = wc_AesInit(&tempAes, NULL, INVALID_DEVID); + if (ret != 0) return ret; + ret = wc_AesGcmSetKey(&tempAes, slot->key, slot->keySz); + if (ret != 0) { wc_AesFree(&tempAes); return ret; } + ret = wc_AesGcmEncrypt(&tempAes, + info->cipher.aesgcm_enc.out, + info->cipher.aesgcm_enc.in, + info->cipher.aesgcm_enc.sz, + info->cipher.aesgcm_enc.iv, + info->cipher.aesgcm_enc.ivSz, + info->cipher.aesgcm_enc.authTag, + info->cipher.aesgcm_enc.authTagSz, + info->cipher.aesgcm_enc.authIn, + info->cipher.aesgcm_enc.authInSz); + wc_AesFree(&tempAes); + return ret; + } + + if (info->algo_type == WC_ALGO_TYPE_CIPHER && + info->cipher.type == WC_CIPHER_AES_GCM && + !info->cipher.enc) { + + Aes* aes = info->cipher.aesgcm_dec.aes; + Tls13ZeroKeySlot* slot; + Aes tempAes; + int ret; + + if (aes == NULL || aes->devCtx == NULL) + return BAD_FUNC_ARG; + + slot = (Tls13ZeroKeySlot*)aes->devCtx; + if (!slot->valid) + return BAD_STATE_E; + + ret = wc_AesInit(&tempAes, NULL, INVALID_DEVID); + if (ret != 0) return ret; + ret = wc_AesGcmSetKey(&tempAes, slot->key, slot->keySz); + if (ret != 0) { wc_AesFree(&tempAes); return ret; } + ret = wc_AesGcmDecrypt(&tempAes, + info->cipher.aesgcm_dec.out, + info->cipher.aesgcm_dec.in, + info->cipher.aesgcm_dec.sz, + info->cipher.aesgcm_dec.iv, + info->cipher.aesgcm_dec.ivSz, + info->cipher.aesgcm_dec.authTag, + info->cipher.aesgcm_dec.authTagSz, + info->cipher.aesgcm_dec.authIn, + info->cipher.aesgcm_dec.authInSz); + wc_AesFree(&tempAes); + return ret; + } + +#ifdef WOLF_CRYPTO_CB_FREE + if (info->algo_type == WC_ALGO_TYPE_FREE && + info->free.algo == WC_ALGO_TYPE_CIPHER && + info->free.type == WC_CIPHER_AES) { + + Aes* aes = (Aes*)info->free.obj; + if (aes != NULL && aes->devCtx != NULL) { + Tls13ZeroKeySlot* slot = (Tls13ZeroKeySlot*)aes->devCtx; + ForceZero(slot, sizeof(*slot)); + aes->devCtx = NULL; + } + return 0; + } +#endif + + return CRYPTOCB_UNAVAILABLE; +} + +/* Test helper; not constant-time. Fine for zero-fill assertions in unit + * tests, NOT for comparing secrets. */ +static int isBufferAllZero(const byte* buf, word32 sz) +{ + word32 i; + for (i = 0; i < sz; i++) { + if (buf[i] != 0) + return 0; + } + return 1; +} + +#endif /* WOLF_CRYPTO_CB && WOLF_CRYPTO_CB_AES_SETKEY && !NO_AES && HAVE_AESGCM + * && WOLFSSL_TLS13 && HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES + * && !NO_WOLFSSL_CLIENT && !NO_WOLFSSL_SERVER */ + +int test_wc_CryptoCb_Tls13_Key_Zero_After_Offload(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_AES_SETKEY) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte msg[] = "hello"; + byte reply[sizeof(msg)]; + word32 keySz; + word32 ivSz; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(tls13ZeroSlots, 0, sizeof(tls13ZeroSlots)); + tls13ZeroSlotCount = 0; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_TLS13_ZERO_DEVID, + test_Tls13Zero_CryptoCb, NULL), 0); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_c, TEST_TLS13_ZERO_DEVID), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_s, TEST_TLS13_ZERO_DEVID), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetDevId(ssl_c, TEST_TLS13_ZERO_DEVID), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetDevId(ssl_s, TEST_TLS13_ZERO_DEVID), + WOLFSSL_SUCCESS); + + /* Pin the ciphersuite to AES-GCM. The zeroing under test is gated on + * AES offload (devCtx set by our CryptoCB); negotiating ChaCha20 or + * any non-AES suite leaves encrypt.aes / decrypt.aes unset and turns + * the test into either a no-op (offload never runs) or a crash when + * we later dereference ssl_c->encrypt.aes. Offer both AES-GCM sizes + * so the pin succeeds regardless of WOLFSSL_AES_128 / WOLFSSL_AES_256 + * build configuration. */ + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, + "TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, + "TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384"), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL && ssl_s != NULL) { + keySz = ssl_c->specs.key_size; + ivSz = ssl_c->specs.iv_size; + ExpectTrue(keySz > 0); + ExpectTrue(ivSz > 0); + + ExpectTrue(isBufferAllZero(ssl_c->keys.client_write_key, keySz)); + ExpectTrue(isBufferAllZero(ssl_c->keys.server_write_key, keySz)); + ExpectTrue(isBufferAllZero(ssl_s->keys.client_write_key, keySz)); + ExpectTrue(isBufferAllZero(ssl_s->keys.server_write_key, keySz)); + + /* The static IVs must be preserved: BuildTls13Nonce() reads + * keys->aead_{enc,dec}_imp_IV on every AEAD record to build the + * per-record nonce (RFC 8446 Section 5.3). If a future change + * starts zeroing these, both peers in this memio test would + * silently agree on a degenerate all-zero IV and the handshake + * would still pass, but the resulting wire format is + * non-interoperable with any unpatched TLS 1.3 peer. Assert + * both the source buffers (client/server_write_IV) and the + * AEAD copies BuildTls13Nonce() actually reads stay populated, + * so a regression that zeroes either one is caught here. */ + ExpectTrue(!isBufferAllZero(ssl_c->keys.client_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_c->keys.server_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.client_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.server_write_IV, ivSz)); + + ExpectTrue(!isBufferAllZero(ssl_c->keys.aead_enc_imp_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_c->keys.aead_dec_imp_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.aead_enc_imp_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.aead_dec_imp_IV, ivSz)); + + /* Guard the Aes pointer dereferences: even though the Expect* + * macros short-circuit after a prior failure via EXPECT_SUCCESS(), + * a handshake that succeeded but negotiated a non-AES suite + * would leave these NULL while _ret is still TEST_SUCCESS. */ + ExpectNotNull(ssl_c->encrypt.aes); + ExpectNotNull(ssl_c->decrypt.aes); + ExpectNotNull(ssl_s->encrypt.aes); + ExpectNotNull(ssl_s->decrypt.aes); + if (ssl_c->encrypt.aes && ssl_c->decrypt.aes && + ssl_s->encrypt.aes && ssl_s->decrypt.aes) { + ExpectPtrNE(ssl_c->encrypt.aes->devCtx, NULL); + ExpectPtrNE(ssl_c->decrypt.aes->devCtx, NULL); + ExpectPtrNE(ssl_s->encrypt.aes->devCtx, NULL); + ExpectPtrNE(ssl_s->decrypt.aes->devCtx, NULL); + } + + ExpectIntEQ(wolfSSL_write(ssl_c, msg, sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(msg, reply, sizeof(msg)), 0); + + ExpectIntEQ(wolfSSL_write(ssl_s, msg, sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_c, reply, sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(msg, reply, sizeof(msg)), 0); + + /* Force a KeyUpdate so SetKeysSide runs again with a fresh + * offload and we can re-check that the staging buffers remain + * zeroed. wolfSSL_update_keys is always available when + * WOLFSSL_TLS13 is defined, which is part of the test gate. */ + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_write(ssl_c, msg, sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(msg, reply, sizeof(msg)), 0); + + ExpectIntEQ(wolfSSL_write(ssl_s, msg, sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_c, reply, sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(msg, reply, sizeof(msg)), 0); + + keySz = ssl_c->specs.key_size; + ivSz = ssl_c->specs.iv_size; + ExpectTrue(isBufferAllZero(ssl_c->keys.client_write_key, keySz)); + ExpectTrue(isBufferAllZero(ssl_c->keys.server_write_key, keySz)); + ExpectTrue(isBufferAllZero(ssl_s->keys.client_write_key, keySz)); + ExpectTrue(isBufferAllZero(ssl_s->keys.server_write_key, keySz)); + + /* Same invariant as the post-handshake block above: the static + * IVs (both the source *_write_IV buffers and the AEAD copies + * BuildTls13Nonce() actually reads) are required on every + * record and must survive SetKeysSide after KeyUpdate. */ + ExpectTrue(!isBufferAllZero(ssl_c->keys.client_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_c->keys.server_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.client_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.server_write_IV, ivSz)); + + ExpectTrue(!isBufferAllZero(ssl_c->keys.aead_enc_imp_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_c->keys.aead_dec_imp_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.aead_enc_imp_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.aead_dec_imp_IV, ivSz)); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wc_CryptoCb_UnRegisterDevice(TEST_TLS13_ZERO_DEVID); +#endif + return EXPECT_RESULT(); +} + +int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void) +{ + EXPECT_DECLS; +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_AES_SETKEY) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + word32 keySz; + word32 ivSz; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Pin the ciphersuite for the same reason as the offload test: so the + * regression assertions below reference the same buffers the offload + * test expects to see zeroed (or not zeroed, here). See the companion + * comment in test_wc_CryptoCb_Tls13_Key_Zero_After_Offload. */ + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, + "TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, + "TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384"), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL && ssl_s != NULL) { + keySz = ssl_c->specs.key_size; + ivSz = ssl_c->specs.iv_size; + ExpectTrue(keySz > 0); + ExpectTrue(ivSz > 0); + + /* Check each buffer independently. AND-combining these would + * mask the case where one buffer was never populated, which + * would produce a confusing "regression, keys were zeroed" + * failure when the real issue is upstream. */ + ExpectTrue(!isBufferAllZero(ssl_c->keys.client_write_key, keySz)); + ExpectTrue(!isBufferAllZero(ssl_c->keys.server_write_key, keySz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.client_write_key, keySz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.server_write_key, keySz)); + + ExpectTrue(!isBufferAllZero(ssl_c->keys.client_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_c->keys.server_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.client_write_IV, ivSz)); + ExpectTrue(!isBufferAllZero(ssl_s->keys.server_write_IV, ivSz)); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + /******************************************************************************* * Monte Carlo tests for AES modes ******************************************************************************/ diff --git a/tests/api/test_aes.h b/tests/api/test_aes.h index 51f0ba3efc..133170ff17 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -94,6 +94,24 @@ int test_wc_CryptoCb_AesSetKey(void); int test_wc_CryptoCb_AesGcm_EncryptDecrypt(void); #endif +/* These test functions always have a (possibly empty) definition in + * test_aes.c so that callers can reference them unconditionally. Declare + * the prototypes unconditionally to satisfy -Wmissing-prototypes. The + * TEST_CRYPTOCB_TLS13_KEY_ZERO_DECL macro below, however, only registers + * them with the test harness when the real bodies are compiled in. */ +int test_wc_CryptoCb_Tls13_Key_Zero_After_Offload(void); +int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void); +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_AES_SETKEY) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +#define TEST_CRYPTOCB_TLS13_KEY_ZERO_DECL \ + , TEST_DECL_GROUP("aes", test_wc_CryptoCb_Tls13_Key_Zero_After_Offload) \ + , TEST_DECL_GROUP("aes", test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload) +#else +#define TEST_CRYPTOCB_TLS13_KEY_ZERO_DECL +#endif + #if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_AES_SETKEY) && \ !defined(NO_AES) && defined(HAVE_AESGCM) #define TEST_CRYPTOCB_AES_SETKEY_DECL , TEST_DECL_GROUP("aes", test_wc_CryptoCb_AesSetKey), \ @@ -153,7 +171,8 @@ int test_wc_CryptoCb_AesGcm_EncryptDecrypt(void); TEST_DECL_GROUP("aes", test_wc_AesCcm_MonteCarlo), \ TEST_DECL_GROUP("aes", test_wc_AesCfb_MonteCarlo), \ TEST_DECL_GROUP("aes", test_wc_AesOfb_MonteCarlo) \ - TEST_CRYPTOCB_AES_SETKEY_DECL + TEST_CRYPTOCB_AES_SETKEY_DECL \ + TEST_CRYPTOCB_TLS13_KEY_ZERO_DECL #if defined(WOLFSSL_AES_EAX) && defined(WOLFSSL_AES_256) && \ (!defined(HAVE_FIPS) || FIPS_VERSION_GE(5, 3)) && !defined(HAVE_SELFTEST)