Skip to content

Commit 1def1f6

Browse files
committed
TLSv1.3 testing: add fuzz test of decryption
Fixes F-3478 Add a fuzzing test for each cipher that modifies a random byte at a random offset of an encrypted message and checks that the reading fails with an appropriate return and error code. Fuzzes both sides 10 times each for each cipher suite.
1 parent 3afa901 commit 1def1f6

2 files changed

Lines changed: 277 additions & 1 deletion

File tree

tests/api/test_tls13.c

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5873,3 +5873,269 @@ int test_tls13_clear_preserves_psk_dhe(void)
58735873
#endif
58745874
return EXPECT_RESULT();
58755875
}
5876+
5877+
#if defined(WOLFSSL_TLS13) && \
5878+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
5879+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
5880+
(defined(BUILD_TLS_AES_128_GCM_SHA256) || \
5881+
defined(BUILD_TLS_AES_256_GCM_SHA384) || \
5882+
defined(BUILD_TLS_CHACHA20_POLY1305_SHA256) || \
5883+
defined(BUILD_TLS_AES_128_CCM_SHA256) || \
5884+
defined(BUILD_TLS_AES_128_CCM_8_SHA256))
5885+
/* One iteration of the AEAD fuzz test: run a fresh handshake
5886+
* up to the point where the first AEAD-protected record from the side under
5887+
* test sits in the receiver's input buffer, flip one random byte of the
5888+
* encrypted payload to a random non-zero value, and confirm the receiver
5889+
* fails with VERIFY_MAC_ERROR. side==0 fuzzes the server's first encrypted
5890+
* record (EncryptedExtensions, read by the client). side==1 fuzzes the
5891+
* client's first encrypted record (Finished, read by the server). */
5892+
static int test_tls13_cipher_fuzz_once(WC_RNG* rng,
5893+
const char* cipher, int side)
5894+
{
5895+
EXPECT_DECLS;
5896+
WOLFSSL_CTX *ctx_c = NULL;
5897+
WOLFSSL_CTX *ctx_s = NULL;
5898+
WOLFSSL *ssl_c = NULL;
5899+
WOLFSSL *ssl_s = NULL;
5900+
struct test_memio_ctx test_ctx;
5901+
byte *buf = NULL;
5902+
int buf_len = 0;
5903+
int rec_off = 0;
5904+
int rec_len = 0;
5905+
int fuzz_off;
5906+
byte fuzz_xor;
5907+
word32 rand32;
5908+
int ret;
5909+
int err;
5910+
5911+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
5912+
test_ctx.c_ciphers = cipher;
5913+
test_ctx.s_ciphers = cipher;
5914+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
5915+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
5916+
5917+
/* Drive the handshake forward until the side being fuzzed has written
5918+
* its first AEAD-encrypted record into the peer's read buffer. The
5919+
* server's first encrypted record is queued after its first
5920+
* wolfSSL_accept() (EncryptedExtensions, immediately following
5921+
* ServerHello). The client's first encrypted record is queued once
5922+
* wolfSSL_connect() returns success and the client has sent its
5923+
* Finished. */
5924+
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
5925+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
5926+
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
5927+
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
5928+
if (side == 1) {
5929+
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
5930+
buf = test_ctx.s_buff;
5931+
buf_len = test_ctx.s_len;
5932+
}
5933+
else {
5934+
buf = test_ctx.c_buff;
5935+
buf_len = test_ctx.c_len;
5936+
}
5937+
5938+
/* Walk the TLS records in the target buffer and locate the first
5939+
* application_data record (content type 0x17), which holds the first
5940+
* encrypted handshake message. Plaintext records (ServerHello,
5941+
* ChangeCipherSpec for middlebox compatibility) precede it and must be
5942+
* skipped over. */
5943+
if (EXPECT_SUCCESS()) {
5944+
int off = 0;
5945+
while (off + 5 <= buf_len) {
5946+
int this_len = ((int)buf[off + 3] << 8) | (int)buf[off + 4];
5947+
if (buf[off] == 0x17) {
5948+
rec_off = off;
5949+
rec_len = this_len;
5950+
break;
5951+
}
5952+
off += 5 + this_len;
5953+
}
5954+
}
5955+
ExpectIntGT(rec_len, 0);
5956+
ExpectIntLE(rec_off + 5 + rec_len, buf_len);
5957+
5958+
/* Pick a random offset within the encrypted payload (skipping the
5959+
* 5-byte record header) and XOR it with a non-zero value so the byte
5960+
* is guaranteed to change. */
5961+
if (EXPECT_SUCCESS()) {
5962+
rand32 = 0;
5963+
ExpectIntEQ(wc_RNG_GenerateBlock(rng, (byte*)&rand32,
5964+
sizeof(rand32)), 0);
5965+
fuzz_off = rec_off + 5 + (int)(rand32 % (word32)rec_len);
5966+
do {
5967+
ExpectIntEQ(wc_RNG_GenerateByte(rng, &fuzz_xor), 0);
5968+
} while (EXPECT_SUCCESS() && fuzz_xor == 0);
5969+
buf[fuzz_off] ^= fuzz_xor;
5970+
}
5971+
5972+
/* Drive the receiving side. It must report VERIFY_MAC_ERROR - the
5973+
* corrupted cipher text or tag must surface as a hard error. */
5974+
if (EXPECT_SUCCESS()) {
5975+
if (side == 1) {
5976+
ret = wolfSSL_accept(ssl_s);
5977+
err = wolfSSL_get_error(ssl_s, ret);
5978+
}
5979+
else {
5980+
ret = wolfSSL_connect(ssl_c);
5981+
err = wolfSSL_get_error(ssl_c, ret);
5982+
}
5983+
ExpectIntEQ(ret, WOLFSSL_FATAL_ERROR);
5984+
ExpectTrue((err == VERIFY_MAC_ERROR) || (err == AES_GCM_AUTH_E) ||
5985+
(err == AES_CCM_AUTH_E));
5986+
}
5987+
5988+
wolfSSL_free(ssl_c);
5989+
wolfSSL_CTX_free(ctx_c);
5990+
wolfSSL_free(ssl_s);
5991+
wolfSSL_CTX_free(ctx_s);
5992+
return EXPECT_RESULT();
5993+
}
5994+
5995+
/* Run 10 fuzz iterations per side for a single cipher suite. */
5996+
static int test_tls13_cipher_fuzz_cs(WC_RNG* rng, const char* cipher)
5997+
{
5998+
EXPECT_DECLS;
5999+
int side;
6000+
int iter;
6001+
6002+
for (side = 0; side < 2 && EXPECT_SUCCESS(); side++) {
6003+
for (iter = 0; iter < 10 && EXPECT_SUCCESS(); iter++) {
6004+
int _r = test_tls13_cipher_fuzz_once(rng, cipher, side);
6005+
if (_r != TEST_SUCCESS) {
6006+
fprintf(stderr, "FAIL cipher=%s side=%d iter=%d\n",
6007+
cipher, side, iter);
6008+
}
6009+
ExpectIntEQ(_r, TEST_SUCCESS);
6010+
}
6011+
}
6012+
return EXPECT_RESULT();
6013+
}
6014+
#endif
6015+
6016+
/* Each per-cipher-suite test below runs the fuzz body (test_tls13_cipher_fuzz_cs)
6017+
* against a single AEAD cipher: it flips a random byte of the first encrypted
6018+
* record on each side of a TLS 1.3 handshake and expects the receiver to fail
6019+
* authentication. AEAD authentication makes it cryptographically infeasible
6020+
* for any single-byte change in the ciphertext or tag to leave authentication
6021+
* intact, so the receiver must report a hard auth error. */
6022+
6023+
int test_tls13_cipher_fuzz_aes128_gcm_sha256(void)
6024+
{
6025+
EXPECT_DECLS;
6026+
#if defined(WOLFSSL_TLS13) && \
6027+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6028+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6029+
defined(BUILD_TLS_AES_128_GCM_SHA256)
6030+
WC_RNG rng;
6031+
int rngInit = 0;
6032+
6033+
XMEMSET(&rng, 0, sizeof(rng));
6034+
ExpectIntEQ(wc_InitRng(&rng), 0);
6035+
if (EXPECT_SUCCESS())
6036+
rngInit = 1;
6037+
6038+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-GCM-SHA256"),
6039+
TEST_SUCCESS);
6040+
6041+
if (rngInit)
6042+
wc_FreeRng(&rng);
6043+
#endif
6044+
return EXPECT_RESULT();
6045+
}
6046+
6047+
int test_tls13_cipher_fuzz_aes256_gcm_sha384(void)
6048+
{
6049+
EXPECT_DECLS;
6050+
#if defined(WOLFSSL_TLS13) && \
6051+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6052+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6053+
defined(BUILD_TLS_AES_256_GCM_SHA384)
6054+
WC_RNG rng;
6055+
int rngInit = 0;
6056+
6057+
XMEMSET(&rng, 0, sizeof(rng));
6058+
ExpectIntEQ(wc_InitRng(&rng), 0);
6059+
if (EXPECT_SUCCESS())
6060+
rngInit = 1;
6061+
6062+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES256-GCM-SHA384"),
6063+
TEST_SUCCESS);
6064+
6065+
if (rngInit)
6066+
wc_FreeRng(&rng);
6067+
#endif
6068+
return EXPECT_RESULT();
6069+
}
6070+
6071+
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void)
6072+
{
6073+
EXPECT_DECLS;
6074+
#if defined(WOLFSSL_TLS13) && \
6075+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6076+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6077+
defined(BUILD_TLS_CHACHA20_POLY1305_SHA256)
6078+
WC_RNG rng;
6079+
int rngInit = 0;
6080+
6081+
XMEMSET(&rng, 0, sizeof(rng));
6082+
ExpectIntEQ(wc_InitRng(&rng), 0);
6083+
if (EXPECT_SUCCESS())
6084+
rngInit = 1;
6085+
6086+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng,
6087+
"TLS13-CHACHA20-POLY1305-SHA256"), TEST_SUCCESS);
6088+
6089+
if (rngInit)
6090+
wc_FreeRng(&rng);
6091+
#endif
6092+
return EXPECT_RESULT();
6093+
}
6094+
6095+
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void)
6096+
{
6097+
EXPECT_DECLS;
6098+
#if defined(WOLFSSL_TLS13) && \
6099+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6100+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6101+
defined(BUILD_TLS_AES_128_CCM_SHA256)
6102+
WC_RNG rng;
6103+
int rngInit = 0;
6104+
6105+
XMEMSET(&rng, 0, sizeof(rng));
6106+
ExpectIntEQ(wc_InitRng(&rng), 0);
6107+
if (EXPECT_SUCCESS())
6108+
rngInit = 1;
6109+
6110+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-CCM-SHA256"),
6111+
TEST_SUCCESS);
6112+
6113+
if (rngInit)
6114+
wc_FreeRng(&rng);
6115+
#endif
6116+
return EXPECT_RESULT();
6117+
}
6118+
6119+
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void)
6120+
{
6121+
EXPECT_DECLS;
6122+
#if defined(WOLFSSL_TLS13) && \
6123+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6124+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6125+
defined(BUILD_TLS_AES_128_CCM_8_SHA256)
6126+
WC_RNG rng;
6127+
int rngInit = 0;
6128+
6129+
XMEMSET(&rng, 0, sizeof(rng));
6130+
ExpectIntEQ(wc_InitRng(&rng), 0);
6131+
if (EXPECT_SUCCESS())
6132+
rngInit = 1;
6133+
6134+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-CCM-8-SHA256"),
6135+
TEST_SUCCESS);
6136+
6137+
if (rngInit)
6138+
wc_FreeRng(&rng);
6139+
#endif
6140+
return EXPECT_RESULT();
6141+
}

tests/api/test_tls13.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ int test_tls13_cert_with_extern_psk_sh_missing_key_share(void);
6868
int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
6969
int test_tls13_ticket_peer_cert_reverify(void);
7070
int test_tls13_clear_preserves_psk_dhe(void);
71+
int test_tls13_cipher_fuzz_aes128_gcm_sha256(void);
72+
int test_tls13_cipher_fuzz_aes256_gcm_sha384(void);
73+
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void);
74+
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void);
75+
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
7176

7277
#define TEST_TLS13_DECLS \
7378
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@@ -113,6 +118,11 @@ int test_tls13_clear_preserves_psk_dhe(void);
113118
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \
114119
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \
115120
TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \
116-
TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe)
121+
TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \
122+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_gcm_sha256), \
123+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes256_gcm_sha384), \
124+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_chacha20_poly1305_sha256), \
125+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_sha256), \
126+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256)
117127

118128
#endif /* WOLFCRYPT_TEST_TLS13_H */

0 commit comments

Comments
 (0)