Merge pull request #10461 from SparkiDev/tls13_cipher_fuzzing

TLSv1.3 testing: add fuzz test of decryption
This commit is contained in:
David Garske
2026-05-12 09:26:53 -07:00
committed by GitHub
2 changed files with 283 additions and 1 deletions
+272
View File
@@ -5873,3 +5873,275 @@ int test_tls13_clear_preserves_psk_dhe(void)
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
(defined(BUILD_TLS_AES_128_GCM_SHA256) || \
defined(BUILD_TLS_AES_256_GCM_SHA384) || \
defined(BUILD_TLS_CHACHA20_POLY1305_SHA256) || \
defined(BUILD_TLS_AES_128_CCM_SHA256) || \
defined(BUILD_TLS_AES_128_CCM_8_SHA256))
/* One iteration of the AEAD fuzz test: run a fresh handshake
* up to the point where the first AEAD-protected record from the side under
* test sits in the receiver's input buffer, flip one random byte of the
* encrypted payload to a random non-zero value, and confirm the receiver
* fails with VERIFY_MAC_ERROR. side==0 fuzzes the server's first encrypted
* record (EncryptedExtensions, read by the client). side==1 fuzzes the
* client's first encrypted record (Finished, read by the server). */
static int test_tls13_cipher_fuzz_once(WC_RNG* rng,
const char* cipher, int side)
{
EXPECT_DECLS;
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 *buf = NULL;
int buf_len = 0;
int rec_off = 0;
int rec_len = 0;
int fuzz_off;
byte fuzz_xor;
word32 rand32;
int ret;
int err;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.c_ciphers = cipher;
test_ctx.s_ciphers = cipher;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
/* Drive the handshake forward until the side being fuzzed has written
* its first AEAD-encrypted record into the peer's read buffer. The
* server's first encrypted record is queued after its first
* wolfSSL_accept() (EncryptedExtensions, immediately following
* ServerHello). The client's first encrypted record is queued once
* wolfSSL_connect() returns success and the client has sent its
* Finished. */
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
if (side == 1) {
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
buf = test_ctx.s_buff;
buf_len = test_ctx.s_len;
}
else {
buf = test_ctx.c_buff;
buf_len = test_ctx.c_len;
}
/* Walk the TLS records in the target buffer and locate the first
* application_data record (content type 0x17), which holds the first
* encrypted handshake message. Plaintext records (ServerHello,
* ChangeCipherSpec for middlebox compatibility) precede it and must be
* skipped over. */
if (EXPECT_SUCCESS()) {
int off = 0;
while (off + 5 <= buf_len) {
int this_len = ((int)buf[off + 3] << 8) | (int)buf[off + 4];
if (buf[off] == 0x17) {
rec_off = off;
rec_len = this_len;
break;
}
off += 5 + this_len;
}
}
ExpectIntGT(rec_len, 0);
ExpectIntLE(rec_off + 5 + rec_len, buf_len);
/* Pick a random offset within the encrypted payload (skipping the
* 5-byte record header) and XOR it with a non-zero value so the byte
* is guaranteed to change. */
if (EXPECT_SUCCESS()) {
rand32 = 0;
fuzz_off = 0;
ExpectIntEQ(wc_RNG_GenerateBlock(rng, (byte*)&rand32,
sizeof(rand32)), 0);
if (EXPECT_SUCCESS()) {
fuzz_off = rec_off + 5 + (int)(rand32 % (word32)rec_len);
}
do {
ExpectIntEQ(wc_RNG_GenerateByte(rng, &fuzz_xor), 0);
} while (EXPECT_SUCCESS() && fuzz_xor == 0);
if (EXPECT_SUCCESS()) {
buf[fuzz_off] ^= fuzz_xor;
}
}
/* Drive the receiving side. It must report VERIFY_MAC_ERROR - the
* corrupted cipher text or tag must surface as a hard error. */
if (EXPECT_SUCCESS()) {
if (side == 1) {
ret = wolfSSL_accept(ssl_s);
err = wolfSSL_get_error(ssl_s, ret);
}
else {
ret = wolfSSL_connect(ssl_c);
err = wolfSSL_get_error(ssl_c, ret);
}
ExpectIntEQ(ret, WOLFSSL_FATAL_ERROR);
ExpectTrue((err == WC_NO_ERR_TRACE(VERIFY_MAC_ERROR)) ||
(err == WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) ||
(err == WC_NO_ERR_TRACE(AES_CCM_AUTH_E)));
}
wolfSSL_free(ssl_c);
wolfSSL_CTX_free(ctx_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_s);
return EXPECT_RESULT();
}
/* Run 5 fuzz iterations per side for a single cipher suite. */
static int test_tls13_cipher_fuzz_cs(WC_RNG* rng, const char* cipher)
{
EXPECT_DECLS;
int side;
int iter;
for (side = 0; side < 2 && EXPECT_SUCCESS(); side++) {
for (iter = 0; iter < 5 && EXPECT_SUCCESS(); iter++) {
int _r = test_tls13_cipher_fuzz_once(rng, cipher, side);
if (_r != TEST_SUCCESS) {
fprintf(stderr, "FAIL cipher=%s side=%d iter=%d\n",
cipher, side, iter);
}
ExpectIntEQ(_r, TEST_SUCCESS);
}
}
return EXPECT_RESULT();
}
#endif
/* Each per-cipher-suite test below runs the fuzz body (test_tls13_cipher_fuzz_cs)
* against a single AEAD cipher: it flips a random byte of the first encrypted
* record on each side of a TLS 1.3 handshake and expects the receiver to fail
* authentication. AEAD authentication makes it cryptographically infeasible
* for any single-byte change in the ciphertext or tag to leave authentication
* intact, so the receiver must report a hard auth error. */
int test_tls13_cipher_fuzz_aes128_gcm_sha256(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
defined(BUILD_TLS_AES_128_GCM_SHA256)
WC_RNG rng;
int rngInit = 0;
XMEMSET(&rng, 0, sizeof(rng));
ExpectIntEQ(wc_InitRng(&rng), 0);
if (EXPECT_SUCCESS())
rngInit = 1;
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-GCM-SHA256"),
TEST_SUCCESS);
if (rngInit)
wc_FreeRng(&rng);
#endif
return EXPECT_RESULT();
}
int test_tls13_cipher_fuzz_aes256_gcm_sha384(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
defined(BUILD_TLS_AES_256_GCM_SHA384)
WC_RNG rng;
int rngInit = 0;
XMEMSET(&rng, 0, sizeof(rng));
ExpectIntEQ(wc_InitRng(&rng), 0);
if (EXPECT_SUCCESS())
rngInit = 1;
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES256-GCM-SHA384"),
TEST_SUCCESS);
if (rngInit)
wc_FreeRng(&rng);
#endif
return EXPECT_RESULT();
}
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
defined(BUILD_TLS_CHACHA20_POLY1305_SHA256)
WC_RNG rng;
int rngInit = 0;
XMEMSET(&rng, 0, sizeof(rng));
ExpectIntEQ(wc_InitRng(&rng), 0);
if (EXPECT_SUCCESS())
rngInit = 1;
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng,
"TLS13-CHACHA20-POLY1305-SHA256"), TEST_SUCCESS);
if (rngInit)
wc_FreeRng(&rng);
#endif
return EXPECT_RESULT();
}
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
defined(BUILD_TLS_AES_128_CCM_SHA256)
WC_RNG rng;
int rngInit = 0;
XMEMSET(&rng, 0, sizeof(rng));
ExpectIntEQ(wc_InitRng(&rng), 0);
if (EXPECT_SUCCESS())
rngInit = 1;
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-CCM-SHA256"),
TEST_SUCCESS);
if (rngInit)
wc_FreeRng(&rng);
#endif
return EXPECT_RESULT();
}
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
defined(BUILD_TLS_AES_128_CCM_8_SHA256)
WC_RNG rng;
int rngInit = 0;
XMEMSET(&rng, 0, sizeof(rng));
ExpectIntEQ(wc_InitRng(&rng), 0);
if (EXPECT_SUCCESS())
rngInit = 1;
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-CCM-8-SHA256"),
TEST_SUCCESS);
if (rngInit)
wc_FreeRng(&rng);
#endif
return EXPECT_RESULT();
}
+11 -1
View File
@@ -68,6 +68,11 @@ int test_tls13_cert_with_extern_psk_sh_missing_key_share(void);
int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
int test_tls13_ticket_peer_cert_reverify(void);
int test_tls13_clear_preserves_psk_dhe(void);
int test_tls13_cipher_fuzz_aes128_gcm_sha256(void);
int test_tls13_cipher_fuzz_aes256_gcm_sha384(void);
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void);
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void);
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
#define TEST_TLS13_DECLS \
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@@ -113,6 +118,11 @@ int test_tls13_clear_preserves_psk_dhe(void);
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \
TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \
TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe)
TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_gcm_sha256), \
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes256_gcm_sha384), \
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_chacha20_poly1305_sha256), \
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_sha256), \
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256)
#endif /* WOLFCRYPT_TEST_TLS13_H */