Skip to content

Commit c1cf8ff

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 5 times each for each cipher suite.
1 parent 3afa901 commit c1cf8ff

2 files changed

Lines changed: 283 additions & 1 deletion

File tree

tests/api/test_tls13.c

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5873,3 +5873,275 @@ 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+
fuzz_off = 0;
5964+
ExpectIntEQ(wc_RNG_GenerateBlock(rng, (byte*)&rand32,
5965+
sizeof(rand32)), 0);
5966+
if (EXPECT_SUCCESS()) {
5967+
fuzz_off = rec_off + 5 + (int)(rand32 % (word32)rec_len);
5968+
}
5969+
do {
5970+
ExpectIntEQ(wc_RNG_GenerateByte(rng, &fuzz_xor), 0);
5971+
} while (EXPECT_SUCCESS() && fuzz_xor == 0);
5972+
if (EXPECT_SUCCESS()) {
5973+
buf[fuzz_off] ^= fuzz_xor;
5974+
}
5975+
}
5976+
5977+
/* Drive the receiving side. It must report VERIFY_MAC_ERROR - the
5978+
* corrupted cipher text or tag must surface as a hard error. */
5979+
if (EXPECT_SUCCESS()) {
5980+
if (side == 1) {
5981+
ret = wolfSSL_accept(ssl_s);
5982+
err = wolfSSL_get_error(ssl_s, ret);
5983+
}
5984+
else {
5985+
ret = wolfSSL_connect(ssl_c);
5986+
err = wolfSSL_get_error(ssl_c, ret);
5987+
}
5988+
ExpectIntEQ(ret, WOLFSSL_FATAL_ERROR);
5989+
ExpectTrue((err == WC_NO_ERR_TRACE(VERIFY_MAC_ERROR)) ||
5990+
(err == WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) ||
5991+
(err == WC_NO_ERR_TRACE(AES_CCM_AUTH_E)));
5992+
}
5993+
5994+
wolfSSL_free(ssl_c);
5995+
wolfSSL_CTX_free(ctx_c);
5996+
wolfSSL_free(ssl_s);
5997+
wolfSSL_CTX_free(ctx_s);
5998+
return EXPECT_RESULT();
5999+
}
6000+
6001+
/* Run 5 fuzz iterations per side for a single cipher suite. */
6002+
static int test_tls13_cipher_fuzz_cs(WC_RNG* rng, const char* cipher)
6003+
{
6004+
EXPECT_DECLS;
6005+
int side;
6006+
int iter;
6007+
6008+
for (side = 0; side < 2 && EXPECT_SUCCESS(); side++) {
6009+
for (iter = 0; iter < 5 && EXPECT_SUCCESS(); iter++) {
6010+
int _r = test_tls13_cipher_fuzz_once(rng, cipher, side);
6011+
if (_r != TEST_SUCCESS) {
6012+
fprintf(stderr, "FAIL cipher=%s side=%d iter=%d\n",
6013+
cipher, side, iter);
6014+
}
6015+
ExpectIntEQ(_r, TEST_SUCCESS);
6016+
}
6017+
}
6018+
return EXPECT_RESULT();
6019+
}
6020+
#endif
6021+
6022+
/* Each per-cipher-suite test below runs the fuzz body (test_tls13_cipher_fuzz_cs)
6023+
* against a single AEAD cipher: it flips a random byte of the first encrypted
6024+
* record on each side of a TLS 1.3 handshake and expects the receiver to fail
6025+
* authentication. AEAD authentication makes it cryptographically infeasible
6026+
* for any single-byte change in the ciphertext or tag to leave authentication
6027+
* intact, so the receiver must report a hard auth error. */
6028+
6029+
int test_tls13_cipher_fuzz_aes128_gcm_sha256(void)
6030+
{
6031+
EXPECT_DECLS;
6032+
#if defined(WOLFSSL_TLS13) && \
6033+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6034+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6035+
defined(BUILD_TLS_AES_128_GCM_SHA256)
6036+
WC_RNG rng;
6037+
int rngInit = 0;
6038+
6039+
XMEMSET(&rng, 0, sizeof(rng));
6040+
ExpectIntEQ(wc_InitRng(&rng), 0);
6041+
if (EXPECT_SUCCESS())
6042+
rngInit = 1;
6043+
6044+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-GCM-SHA256"),
6045+
TEST_SUCCESS);
6046+
6047+
if (rngInit)
6048+
wc_FreeRng(&rng);
6049+
#endif
6050+
return EXPECT_RESULT();
6051+
}
6052+
6053+
int test_tls13_cipher_fuzz_aes256_gcm_sha384(void)
6054+
{
6055+
EXPECT_DECLS;
6056+
#if defined(WOLFSSL_TLS13) && \
6057+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6058+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6059+
defined(BUILD_TLS_AES_256_GCM_SHA384)
6060+
WC_RNG rng;
6061+
int rngInit = 0;
6062+
6063+
XMEMSET(&rng, 0, sizeof(rng));
6064+
ExpectIntEQ(wc_InitRng(&rng), 0);
6065+
if (EXPECT_SUCCESS())
6066+
rngInit = 1;
6067+
6068+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES256-GCM-SHA384"),
6069+
TEST_SUCCESS);
6070+
6071+
if (rngInit)
6072+
wc_FreeRng(&rng);
6073+
#endif
6074+
return EXPECT_RESULT();
6075+
}
6076+
6077+
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void)
6078+
{
6079+
EXPECT_DECLS;
6080+
#if defined(WOLFSSL_TLS13) && \
6081+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6082+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6083+
defined(BUILD_TLS_CHACHA20_POLY1305_SHA256)
6084+
WC_RNG rng;
6085+
int rngInit = 0;
6086+
6087+
XMEMSET(&rng, 0, sizeof(rng));
6088+
ExpectIntEQ(wc_InitRng(&rng), 0);
6089+
if (EXPECT_SUCCESS())
6090+
rngInit = 1;
6091+
6092+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng,
6093+
"TLS13-CHACHA20-POLY1305-SHA256"), TEST_SUCCESS);
6094+
6095+
if (rngInit)
6096+
wc_FreeRng(&rng);
6097+
#endif
6098+
return EXPECT_RESULT();
6099+
}
6100+
6101+
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void)
6102+
{
6103+
EXPECT_DECLS;
6104+
#if defined(WOLFSSL_TLS13) && \
6105+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6106+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6107+
defined(BUILD_TLS_AES_128_CCM_SHA256)
6108+
WC_RNG rng;
6109+
int rngInit = 0;
6110+
6111+
XMEMSET(&rng, 0, sizeof(rng));
6112+
ExpectIntEQ(wc_InitRng(&rng), 0);
6113+
if (EXPECT_SUCCESS())
6114+
rngInit = 1;
6115+
6116+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-CCM-SHA256"),
6117+
TEST_SUCCESS);
6118+
6119+
if (rngInit)
6120+
wc_FreeRng(&rng);
6121+
#endif
6122+
return EXPECT_RESULT();
6123+
}
6124+
6125+
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void)
6126+
{
6127+
EXPECT_DECLS;
6128+
#if defined(WOLFSSL_TLS13) && \
6129+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6130+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6131+
defined(BUILD_TLS_AES_128_CCM_8_SHA256)
6132+
WC_RNG rng;
6133+
int rngInit = 0;
6134+
6135+
XMEMSET(&rng, 0, sizeof(rng));
6136+
ExpectIntEQ(wc_InitRng(&rng), 0);
6137+
if (EXPECT_SUCCESS())
6138+
rngInit = 1;
6139+
6140+
ExpectIntEQ(test_tls13_cipher_fuzz_cs(&rng, "TLS13-AES128-CCM-8-SHA256"),
6141+
TEST_SUCCESS);
6142+
6143+
if (rngInit)
6144+
wc_FreeRng(&rng);
6145+
#endif
6146+
return EXPECT_RESULT();
6147+
}

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)