Merge pull request #10246 from sameehj/aes-gcm-fix

Zero TLS 1.3 traffic keys after AES SE offload
This commit is contained in:
Daniel Pouzzner
2026-04-23 13:26:59 -05:00
committed by GitHub
4 changed files with 560 additions and 59 deletions
+14
View File
@@ -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
+69
View File
@@ -3592,6 +3592,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);
+457 -58
View File
@@ -31,8 +31,16 @@
#include <wolfssl/wolfcrypt/aes.h>
#include <wolfssl/wolfcrypt/wc_encrypt.h>
#include <wolfssl/wolfcrypt/types.h>
/* <wolfssl/internal.h> 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 <wolfssl/internal.h>
#include <tests/api/api.h>
#include <tests/api/test_aes.h>
#include <tests/utils.h>
#if defined(HAVE_SELFTEST) || (defined(HAVE_FIPS_VERSION) && \
(HAVE_FIPS_VERSION <= 2))
@@ -5447,7 +5455,7 @@ int test_wc_AesXtsStream(void)
};
const word32 tweakLen = (word32)sizeof(tweak);
XtsAes aes;
XtsAesStreamData stream;
XtsAesStreamData xtsStream;
byte plain3[WC_AES_BLOCK_SIZE * 3]; /* block-aligned plaintext */
byte expEnc[sizeof(vector)]; /* expected ciphertext (non-aligned) */
byte expEnc3[WC_AES_BLOCK_SIZE * 3]; /* expected ciphertext (3 blocks) */
@@ -5473,90 +5481,90 @@ int test_wc_AesXtsStream(void)
/* --- Stream encrypt: Init + Final(non-aligned, 24 bytes) --- */
XMEMSET(enc, 0, sizeof(enc));
XMEMSET(&stream, 0, sizeof(stream));
XMEMSET(&xtsStream, 0, sizeof(xtsStream));
ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32),
AES_ENCRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, enc, vector, sizeof(vector),
&stream), 0);
&xtsStream), 0);
ExpectBufEQ(enc, expEnc, sizeof(expEnc));
wc_AesXtsFree(&aes);
/* --- Stream encrypt: Init + Update(2 blocks) + Final(1 block) --- */
XMEMSET(enc, 0, sizeof(enc));
XMEMSET(&stream, 0, sizeof(stream));
XMEMSET(&xtsStream, 0, sizeof(xtsStream));
ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32),
AES_ENCRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3,
WC_AES_BLOCK_SIZE * 2, &stream), 0);
WC_AES_BLOCK_SIZE * 2, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes,
enc + WC_AES_BLOCK_SIZE * 2,
plain3 + WC_AES_BLOCK_SIZE * 2,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectBufEQ(enc, expEnc3, sizeof(expEnc3));
wc_AesXtsFree(&aes);
/* --- Stream encrypt: Init + Update(1 block) x3 via individual calls +
* Final(0 bytes) --- */
XMEMSET(enc, 0, sizeof(enc));
XMEMSET(&stream, 0, sizeof(stream));
XMEMSET(&xtsStream, 0, sizeof(xtsStream));
ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32),
AES_ENCRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc,
plain3, WC_AES_BLOCK_SIZE, &stream), 0);
plain3, WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes,
enc + WC_AES_BLOCK_SIZE,
plain3 + WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE, &stream), 0);
plain3 + WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes,
enc + WC_AES_BLOCK_SIZE * 2,
plain3 + WC_AES_BLOCK_SIZE * 2, WC_AES_BLOCK_SIZE, &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, NULL, NULL, 0, &stream), 0);
plain3 + WC_AES_BLOCK_SIZE * 2, WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, NULL, NULL, 0, &xtsStream), 0);
ExpectBufEQ(enc, expEnc3, sizeof(expEnc3));
wc_AesXtsFree(&aes);
#ifdef HAVE_AES_DECRYPT
/* --- Stream decrypt: Init + Final(non-aligned, 24 bytes) --- */
XMEMSET(dec, 0, sizeof(dec));
XMEMSET(&stream, 0, sizeof(stream));
XMEMSET(&xtsStream, 0, sizeof(xtsStream));
ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32),
AES_DECRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, &stream), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, dec, expEnc, sizeof(expEnc),
&stream), 0);
&xtsStream), 0);
ExpectBufEQ(dec, vector, sizeof(vector));
wc_AesXtsFree(&aes);
/* --- Stream decrypt: Init + Update(2 blocks) + Final(1 block) --- */
XMEMSET(dec, 0, sizeof(dec));
XMEMSET(&stream, 0, sizeof(stream));
XMEMSET(&xtsStream, 0, sizeof(xtsStream));
ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32),
AES_DECRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, &stream), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, dec, expEnc3,
WC_AES_BLOCK_SIZE * 2, &stream), 0);
WC_AES_BLOCK_SIZE * 2, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptFinal(&aes,
dec + WC_AES_BLOCK_SIZE * 2,
expEnc3 + WC_AES_BLOCK_SIZE * 2,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectBufEQ(dec, plain3, sizeof(plain3));
wc_AesXtsFree(&aes);
#endif /* HAVE_AES_DECRYPT */
/* --- Bad args --- */
XMEMSET(&stream, 0, sizeof(stream));
XMEMSET(&xtsStream, 0, sizeof(xtsStream));
/* NULL aes */
ExpectIntEQ(wc_AesXtsEncryptInit(NULL, tweak, tweakLen, &stream),
ExpectIntEQ(wc_AesXtsEncryptInit(NULL, tweak, tweakLen, &xtsStream),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* NULL tweak */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, NULL, tweakLen, &stream),
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, NULL, tweakLen, &xtsStream),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* NULL stream */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* sz not a multiple of block size */
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3, 1, &stream),
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3, 1, &xtsStream),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* NULL stream to Update */
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3,
@@ -5566,9 +5574,9 @@ int test_wc_AesXtsStream(void)
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, enc, vector, sizeof(vector), NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
#ifdef HAVE_AES_DECRYPT
ExpectIntEQ(wc_AesXtsDecryptInit(NULL, tweak, tweakLen, &stream),
ExpectIntEQ(wc_AesXtsDecryptInit(NULL, tweak, tweakLen, &xtsStream),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, NULL, tweakLen, &stream),
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, NULL, tweakLen, &xtsStream),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
@@ -5625,9 +5633,9 @@ int test_wc_AesXtsStream_MidStreamState(void)
0x66,0x6f,0x72,0x20, 0x61,0x6c,0x6c,0x20
};
/* One full block for the subsequent (illegal) Update call. */
static const byte block[WC_AES_BLOCK_SIZE] = { 0 };
static const byte oneBlock[WC_AES_BLOCK_SIZE] = { 0 };
XtsAes aes;
XtsAesStreamData stream;
XtsAesStreamData xtsStream;
byte enc[24];
byte dummy[WC_AES_BLOCK_SIZE];
@@ -5638,14 +5646,14 @@ int test_wc_AesXtsStream_MidStreamState(void)
* ------------------------------------------------------------------ */
ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64),
AES_ENCRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, sizeof(tweak), &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, sizeof(tweak), &xtsStream), 0);
/* Final processes all 24 bytes; bytes_crypted_with_this_tweak becomes 24
* (not a multiple of WC_AES_BLOCK_SIZE=16). */
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, enc, plain24, sizeof(plain24),
&stream), 0);
&xtsStream), 0);
/* The subsequent Update must be rejected because the stream is "done". */
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, dummy, block, sizeof(block),
&stream), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, dummy, oneBlock, sizeof(oneBlock),
&xtsStream), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
wc_AesXtsFree(&aes);
#ifdef HAVE_AES_DECRYPT
@@ -5655,11 +5663,11 @@ int test_wc_AesXtsStream_MidStreamState(void)
XMEMSET(&aes, 0, sizeof(aes));
ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64),
AES_DECRYPTION, NULL, INVALID_DEVID), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, sizeof(tweak), &stream), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, sizeof(tweak), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, enc, enc, sizeof(enc),
&stream), 0);
ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, dummy, block, sizeof(block),
&stream), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
&xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, dummy, oneBlock, sizeof(oneBlock),
&xtsStream), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
wc_AesXtsFree(&aes);
#endif
#endif
@@ -5716,7 +5724,7 @@ int test_wc_AesXtsStream_ReinitAfterFinal(void)
0x20,0x74,0x6f,0x20, 0x63,0x6f,0x6d,0x65
};
XtsAes aes;
XtsAesStreamData stream;
XtsAesStreamData xtsStream;
byte ct1[sizeof(plain)], ct2[sizeof(plain)], ct3[sizeof(plain)];
#ifdef HAVE_AES_DECRYPT
byte pt[sizeof(plain)];
@@ -5730,42 +5738,42 @@ int test_wc_AesXtsStream_ReinitAfterFinal(void)
* One full block via Update, the remaining 24 bytes via Final.
* Note: AesXtsEncryptFinal forwards to the Update path, so the Final
* size must be >= WC_AES_BLOCK_SIZE when sz > 0. */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct1, plain,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct1 + WC_AES_BLOCK_SIZE,
plain + WC_AES_BLOCK_SIZE,
sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0);
sizeof(plain) - WC_AES_BLOCK_SIZE, &xtsStream), 0);
/* ---- Session 2: re-init with same tweak -> must match ---- */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct2, plain,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct2 + WC_AES_BLOCK_SIZE,
plain + WC_AES_BLOCK_SIZE,
sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0);
sizeof(plain) - WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectBufEQ(ct2, ct1, sizeof(ct1));
/* ---- Session 3: re-init with different tweak -> must differ ---- */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak2, sizeof(tweak2), &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak2, sizeof(tweak2), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct3, plain,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct3 + WC_AES_BLOCK_SIZE,
plain + WC_AES_BLOCK_SIZE,
sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0);
sizeof(plain) - WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntNE(XMEMCMP(ct3, ct1, sizeof(ct1)), 0);
/* ---- Session 4: re-init after abandoned (no Final) session ---- */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak2, sizeof(tweak2), &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak2, sizeof(tweak2), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct3, plain,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
/* Abandon - re-init with tweak1, must give session-1 output. */
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0);
ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct2, plain,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct2 + WC_AES_BLOCK_SIZE,
plain + WC_AES_BLOCK_SIZE,
sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0);
sizeof(plain) - WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectBufEQ(ct2, ct1, sizeof(ct1));
wc_AesXtsFree(&aes);
@@ -5777,21 +5785,21 @@ int test_wc_AesXtsStream_ReinitAfterFinal(void)
AES_DECRYPTION, NULL, INVALID_DEVID), 0);
/* Session A: decrypt ct1 with tweak1 -> plaintext. */
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak1, sizeof(tweak1), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, pt, ct1,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, pt + WC_AES_BLOCK_SIZE,
ct1 + WC_AES_BLOCK_SIZE,
sizeof(ct1) - WC_AES_BLOCK_SIZE, &stream), 0);
sizeof(ct1) - WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectBufEQ(pt, plain, sizeof(plain));
/* Session B: re-init and decrypt again -> same plaintext. */
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0);
ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak1, sizeof(tweak1), &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, pt, ct1,
WC_AES_BLOCK_SIZE, &stream), 0);
WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, pt + WC_AES_BLOCK_SIZE,
ct1 + WC_AES_BLOCK_SIZE,
sizeof(ct1) - WC_AES_BLOCK_SIZE, &stream), 0);
sizeof(ct1) - WC_AES_BLOCK_SIZE, &xtsStream), 0);
ExpectBufEQ(pt, plain, sizeof(plain));
wc_AesXtsFree(&aes);
@@ -7867,6 +7875,9 @@ int test_wc_AesSivEncryptDecrypt(void)
#include <wolfssl/wolfcrypt/cryptocb.h>
/* 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 @@ out:
#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
******************************************************************************/
+20 -1
View File
@@ -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)