Skip to content

Commit fc12de0

Browse files
Merge pull request #10513 from SparkiDev/tls13_aead_limit_fix
TLS 1.3: AEAD limit fixed
2 parents 2dd7947 + 8dae4b3 commit fc12de0

3 files changed

Lines changed: 227 additions & 4 deletions

File tree

tests/api/test_tls13.c

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6310,3 +6310,216 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void)
63106310
#endif
63116311
return EXPECT_RESULT();
63126312
}
6313+
6314+
/* Regression test for the AEAD record-protection limit constants in
6315+
* internal.h. The macros expand to w64From32(hi, lo). A prior version split
6316+
* the intended 32-bit constants into 16-bit halves and passed each half as
6317+
* a separate 32-bit argument, producing a 64-bit value many orders of
6318+
* magnitude larger than RFC 8446 / RFC 9147 require. That made
6319+
* CheckTLS13AEADSendLimit's key-update trigger effectively unreachable.
6320+
* Compare against the hard-coded spec values so a recurrence is caught even
6321+
* if the macro is reused on both sides of the comparison. */
6322+
int test_tls13_AEAD_limit_macros(void)
6323+
{
6324+
EXPECT_DECLS;
6325+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS)
6326+
w64wrapper limit;
6327+
6328+
/* RFC 8446 5.5: 2^24.5 ~= 23726566 (0x016A09E6). */
6329+
limit = AEAD_AES_LIMIT;
6330+
ExpectIntEQ(w64GetHigh32(limit), 0);
6331+
ExpectIntEQ(w64GetLow32(limit), 0x016A09E6);
6332+
6333+
#ifdef WOLFSSL_DTLS13
6334+
/* RFC 9147 (AES-CCM integrity): 2^23.5 ~= 11863283 (0x00B504F3). */
6335+
limit = DTLS_AEAD_AES_CCM_FAIL_LIMIT;
6336+
ExpectIntEQ(w64GetHigh32(limit), 0);
6337+
ExpectIntEQ(w64GetLow32(limit), 0x00B504F3);
6338+
6339+
/* Key-update threshold is half the fail limit: 5931641 (0x005A8279). */
6340+
limit = DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT;
6341+
ExpectIntEQ(w64GetHigh32(limit), 0);
6342+
ExpectIntEQ(w64GetLow32(limit), 0x005A8279);
6343+
#endif
6344+
#endif
6345+
return EXPECT_RESULT();
6346+
}
6347+
6348+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \
6349+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6350+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6351+
(defined(BUILD_TLS_AES_128_GCM_SHA256) || \
6352+
defined(BUILD_TLS_AES_256_GCM_SHA384) || \
6353+
defined(BUILD_TLS_AES_128_CCM_SHA256) || \
6354+
defined(BUILD_TLS_AES_128_CCM_8_SHA256))
6355+
/* Drive the client's encrypt sequence number towards the spec limit for
6356+
* `suite` and verify CheckTLS13AEADSendLimit's KeyUpdate trigger fires at
6357+
* exactly the right boundary.
6358+
*
6359+
* Two writes are exercised:
6360+
* 1. Counter set to limit - 2. After the write the counter must read
6361+
* limit - 1 (record incremented it by 1) and no KeyUpdate must have
6362+
* been emitted. CheckTLS13AEADSendLimit uses `seq >= limit`, so neither
6363+
* the pre-send check nor the trailing loop check (which runs once more
6364+
* after the last record before wolfSSL_write exits) is allowed to fire.
6365+
* 2. A second write follows with the counter already sitting at limit - 1
6366+
* from the previous record. The user record goes out at seq = limit-1,
6367+
* which bumps the counter to limit; the trailing limit check then
6368+
* fires SendTls13KeyUpdate. SetKeysSide zeroes the encrypt counter, so
6369+
* the post-write counter is 0.
6370+
*
6371+
* With the previous broken AEAD-limit macros the limit was unreachable, no
6372+
* KeyUpdate would ever fire, and the counter would simply advance to
6373+
* limit_lo + 1 in the second case instead of being reset.
6374+
*
6375+
* The AEAD nonce mixes in the record sequence number on both sides, so the
6376+
* server's decrypt counter has to be advanced in lockstep with the client's
6377+
* encrypt counter or the record fails the integrity check. */
6378+
static int test_tls13_AEAD_limit_triggers_KeyUpdate_cs(const char* suite,
6379+
word32 limit_hi, word32 limit_lo, int expected_bulk_cipher)
6380+
{
6381+
EXPECT_DECLS;
6382+
struct test_memio_ctx test_ctx;
6383+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
6384+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
6385+
const char msg[] = "post-limit-record";
6386+
char buf[sizeof(msg)];
6387+
int written;
6388+
6389+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
6390+
test_ctx.c_ciphers = suite;
6391+
test_ctx.s_ciphers = suite;
6392+
6393+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
6394+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
6395+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
6396+
6397+
if (EXPECT_SUCCESS() && ssl_c != NULL && ssl_s != NULL) {
6398+
/* Sanity check: the negotiated bulk cipher matches what the caller
6399+
* intends to exercise. If a build flag combination falls through to
6400+
* a different suite, the limit constant would be wrong. */
6401+
ExpectIntEQ(ssl_c->specs.bulk_cipher_algorithm, expected_bulk_cipher);
6402+
6403+
/* Stage the counters two below the limit so the first write stays
6404+
* comfortably below the trigger threshold. */
6405+
ssl_c->keys.sequence_number_hi = limit_hi;
6406+
ssl_c->keys.sequence_number_lo = limit_lo - 2;
6407+
ssl_s->keys.peer_sequence_number_hi = limit_hi;
6408+
ssl_s->keys.peer_sequence_number_lo = limit_lo - 2;
6409+
}
6410+
6411+
/* First write: below the limit, no KeyUpdate expected. */
6412+
written = wolfSSL_write(ssl_c, msg, (int)sizeof(msg));
6413+
ExpectIntEQ(written, (int)sizeof(msg));
6414+
6415+
if (EXPECT_SUCCESS() && ssl_c != NULL) {
6416+
/* The record bumped the counter from limit-2 to limit-1. A
6417+
* KeyUpdate would have zeroed it via SetKeysSide and bumped to 1. */
6418+
ExpectIntEQ((int)ssl_c->keys.sequence_number_hi, (int)limit_hi);
6419+
ExpectIntEQ(ssl_c->keys.sequence_number_lo, limit_lo - 1);
6420+
}
6421+
6422+
/* Server consumes the below-limit record with its existing keys. */
6423+
XMEMSET(buf, 0, sizeof(buf));
6424+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, (int)sizeof(buf)), (int)sizeof(msg));
6425+
ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0);
6426+
6427+
/* Second write: the client's counter is now at limit-1. Sending this
6428+
* record will push it to limit, at which point the trailing check
6429+
* inside SendData's loop fires SendTls13KeyUpdate. No manual counter
6430+
* adjustment is needed -- the counter is allowed to "naturally" reach
6431+
* the limit through the previous send. */
6432+
written = wolfSSL_write(ssl_c, msg, (int)sizeof(msg));
6433+
ExpectIntEQ(written, (int)sizeof(msg));
6434+
6435+
if (EXPECT_SUCCESS() && ssl_c != NULL) {
6436+
/* SendTls13KeyUpdate -> DeriveTls13Keys -> SetKeysSide zeroes the
6437+
* encrypt sequence number. The user record went out before the
6438+
* trigger fired, so no record was sent on the new keys. */
6439+
ExpectIntEQ((int)ssl_c->keys.sequence_number_hi, 0);
6440+
ExpectIntEQ((int)ssl_c->keys.sequence_number_lo, 0);
6441+
}
6442+
6443+
/* The server reads the user record (sent under the pre-update keys at
6444+
* seq = limit - 1) before it sees the KeyUpdate record. The KeyUpdate
6445+
* is consumed transparently on a subsequent read; for the test we just
6446+
* need to confirm the user data round-trips. */
6447+
XMEMSET(buf, 0, sizeof(buf));
6448+
{
6449+
int r = -1, attempts;
6450+
for (attempts = 0; attempts < 5; attempts++) {
6451+
r = wolfSSL_read(ssl_s, buf, (int)sizeof(buf));
6452+
if (r > 0)
6453+
break;
6454+
if (wolfSSL_get_error(ssl_s, r) != WOLFSSL_ERROR_WANT_READ)
6455+
break;
6456+
}
6457+
ExpectIntEQ(r, (int)sizeof(msg));
6458+
}
6459+
ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0);
6460+
6461+
wolfSSL_free(ssl_c);
6462+
wolfSSL_free(ssl_s);
6463+
wolfSSL_CTX_free(ctx_c);
6464+
wolfSSL_CTX_free(ctx_s);
6465+
6466+
return EXPECT_RESULT();
6467+
}
6468+
#endif
6469+
6470+
int test_tls13_AEAD_limit_KU_aes128_gcm_sha256(void)
6471+
{
6472+
EXPECT_DECLS;
6473+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \
6474+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6475+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6476+
defined(BUILD_TLS_AES_128_GCM_SHA256)
6477+
ExpectIntEQ(test_tls13_AEAD_limit_triggers_KeyUpdate_cs(
6478+
"TLS13-AES128-GCM-SHA256", 0, 0x016A09E6, wolfssl_aes_gcm),
6479+
TEST_SUCCESS);
6480+
#endif
6481+
return EXPECT_RESULT();
6482+
}
6483+
6484+
int test_tls13_AEAD_limit_KU_aes256_gcm_sha384(void)
6485+
{
6486+
EXPECT_DECLS;
6487+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \
6488+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6489+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6490+
defined(BUILD_TLS_AES_256_GCM_SHA384)
6491+
ExpectIntEQ(test_tls13_AEAD_limit_triggers_KeyUpdate_cs(
6492+
"TLS13-AES256-GCM-SHA384", 0, 0x016A09E6, wolfssl_aes_gcm),
6493+
TEST_SUCCESS);
6494+
#endif
6495+
return EXPECT_RESULT();
6496+
}
6497+
6498+
int test_tls13_AEAD_limit_KU_aes128_ccm_sha256(void)
6499+
{
6500+
EXPECT_DECLS;
6501+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \
6502+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6503+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6504+
defined(BUILD_TLS_AES_128_CCM_SHA256)
6505+
ExpectIntEQ(test_tls13_AEAD_limit_triggers_KeyUpdate_cs(
6506+
"TLS13-AES128-CCM-SHA256", 0, 0x016A09E6, wolfssl_aes_ccm),
6507+
TEST_SUCCESS);
6508+
#endif
6509+
return EXPECT_RESULT();
6510+
}
6511+
6512+
int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void)
6513+
{
6514+
EXPECT_DECLS;
6515+
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \
6516+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
6517+
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6518+
defined(BUILD_TLS_AES_128_CCM_8_SHA256)
6519+
ExpectIntEQ(test_tls13_AEAD_limit_triggers_KeyUpdate_cs(
6520+
"TLS13-AES128-CCM-8-SHA256", 0, 0x016A09E6, wolfssl_aes_ccm),
6521+
TEST_SUCCESS);
6522+
#endif
6523+
return EXPECT_RESULT();
6524+
}
6525+

tests/api/test_tls13.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ int test_tls13_cipher_fuzz_aes256_gcm_sha384(void);
7676
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void);
7777
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void);
7878
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
79+
int test_tls13_AEAD_limit_macros(void);
80+
int test_tls13_AEAD_limit_KU_aes128_gcm_sha256(void);
81+
int test_tls13_AEAD_limit_KU_aes256_gcm_sha384(void);
82+
int test_tls13_AEAD_limit_KU_aes128_ccm_sha256(void);
83+
int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void);
7984

8085
#define TEST_TLS13_DECLS \
8186
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@@ -129,6 +134,11 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
129134
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes256_gcm_sha384), \
130135
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_chacha20_poly1305_sha256), \
131136
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_sha256), \
132-
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256)
137+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256), \
138+
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_macros), \
139+
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_gcm_sha256), \
140+
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes256_gcm_sha384), \
141+
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_sha256), \
142+
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256)
133143

134144
#endif /* WOLFCRYPT_TEST_TLS13_H */

wolfssl/internal.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ enum {
14191419
/* Limit is 2^24.5
14201420
* https://www.rfc-editor.org/rfc/rfc8446#section-5.5
14211421
* Without the fraction is 23726566 (0x016A09E6) */
1422-
#define AEAD_AES_LIMIT w64From32(0x016A, 0x09E6)
1422+
#define AEAD_AES_LIMIT w64From32(0, 0x016A09E6)
14231423
/* Limit is 2^23
14241424
* https://www.rfc-editor.org/rfc/rfc9147.html#name-integrity-limits */
14251425
#define DTLS_AEAD_AES_CCM_LIMIT w64From32(0, 1 << 22)
@@ -1436,8 +1436,8 @@ enum {
14361436
* https://www.rfc-editor.org/rfc/rfc9147.html#name-integrity-limits
14371437
* Without the fraction is 11863283 (0x00B504F3)
14381438
* Half of this value is 5931641 (0x005A8279) */
1439-
#define DTLS_AEAD_AES_CCM_FAIL_LIMIT w64From32(0x00B5, 0x04F3)
1440-
#define DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT w64From32(0x005A, 0x8279)
1439+
#define DTLS_AEAD_AES_CCM_FAIL_LIMIT w64From32(0, 0x00B504F3)
1440+
#define DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT w64From32(0, 0x005A8279)
14411441

14421442
/* Limit is (2^22 - 1) full messages [2^36 - 31 octets]
14431443
* https://www.rfc-editor.org/rfc/rfc8998.html#name-aead_sm4_gcm

0 commit comments

Comments
 (0)