From 432b39bff197076893d200b25263d27248d26e37 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 29 Sep 2026 07:56:59 -0500 Subject: [PATCH] Fragment TLS 1.3 NewSessionTicket to max fragment length --- src/internal.c | 5 +-- src/tls13.c | 63 ++++++++++++++++++++++++++++++------ tests/api/test_tls13.c | 73 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 4 ++- 4 files changed, 132 insertions(+), 13 deletions(-) diff --git a/src/internal.c b/src/internal.c index 5507c325982..67e00a7ebfd 100644 --- a/src/internal.c +++ b/src/internal.c @@ -43491,8 +43491,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (certDer != NULL && certDerSz > 0 && certDerSz <= MAX_TICKET_PEER_CERT_SZ #ifdef HAVE_MAX_FRAGMENT - /* We don't support fragmentation in - * SendTls13NewSessionTicket yet. */ + /* The ticket returns in a ClientHello, which is never + * fragmented and after a HelloRetryRequest must fit the + * reduced limit. */ && (!IsAtLeastTLSv1_3(ssl->version) || ssl->max_fragment == MAX_RECORD_SIZE) #endif diff --git a/src/tls13.c b/src/tls13.c index 7ca1e3e0458..39e4ba5ba06 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -14769,6 +14769,12 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl) int sendSz; word16 extSz; word32 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + word32 hsSz = 0; + word32 fragSz; + word32 fragOff; + word32 fragRoom = 0; + int maxFrag = 0; + byte* plain; WOLFSSL_START(WC_FUNC_NEW_SESSION_TICKET_SEND); WOLFSSL_ENTER("SendTls13NewSessionTicket"); @@ -14859,12 +14865,27 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl) sendSz = (int)(idx + length + MAX_MSG_EXTRA); + /* DTLS 1.3 fragments in Dtls13HandshakeSend(). Otherwise reserve room + * for the records ahead of the plaintext so neither overlaps. */ + if (!ssl->options.dtls) { + maxFrag = wolfssl_local_GetMaxPlaintextSize(ssl); + if (maxFrag <= 0) + return (maxFrag < 0) ? maxFrag : BUFFER_E; + hsSz = HANDSHAKE_HEADER_SZ + length; + if (hsSz > (word32)maxFrag) { + fragRoom = hsSz + ((hsSz + (word32)maxFrag - 1) / + (word32)maxFrag) * + (RECORD_HEADER_SZ + MAX_MSG_EXTRA); + sendSz = (int)(fragRoom + idx + length); + } + } + /* Check buffers are big enough and grow if needed. */ if ((ret = CheckAvailableSize(ssl, sendSz)) != 0) return ret; /* Get position in output buffer to write new message to. */ - output = GetOutputBuffer(ssl); + output = GetOutputBuffer(ssl) + fragRoom; /* Put the record and handshake headers on. */ AddTls13Headers(output, length, session_ticket, ssl); @@ -14914,7 +14935,7 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl) #endif if (idx > WOLFSSL_MAX_16BIT || - sendSz > (int)WOLFSSL_MAX_16BIT) { + (fragRoom == 0 && sendSz > (int)WOLFSSL_MAX_16BIT)) { return BAD_LENGTH_E; } @@ -14934,15 +14955,37 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl) (word16)idx, session_ticket, 0); #endif /* WOLFSSL_DTLS13 */ - /* This message is always encrypted. */ - sendSz = BuildTls13Message(ssl, output, sendSz, - output + RECORD_HEADER_SZ, - (word16)idx - RECORD_HEADER_SZ, - handshake, 0, 0, 0); - if (sendSz < 0) - return sendSz; + if (fragRoom > 0) { + /* RFC 8446 Section 5.1: split across adjacent handshake records so + * each fits the negotiated maximum fragment length. */ + plain = output + RECORD_HEADER_SZ; + for (fragOff = 0; fragOff < hsSz; fragOff += fragSz) { + fragSz = min(hsSz - fragOff, (word32)maxFrag); + output = GetOutputBuffer(ssl); + sendSz = BuildTls13Message(ssl, output, + (int)(RECORD_HEADER_SZ + fragSz + + MAX_MSG_EXTRA), + plain + fragOff, (int)fragSz, + handshake, 0, 0, 0); + if (sendSz < 0) + break; + ssl->buffers.outputBuffer.length += (word32)sendSz; + } + ForceZero(plain, hsSz); + if (sendSz < 0) + return sendSz; + } + else { + /* This message is always encrypted. */ + sendSz = BuildTls13Message(ssl, output, sendSz, + output + RECORD_HEADER_SZ, + (word16)idx - RECORD_HEADER_SZ, + handshake, 0, 0, 0); + if (sendSz < 0) + return sendSz; - ssl->buffers.outputBuffer.length += sendSz; + ssl->buffers.outputBuffer.length += sendSz; + } /* Always send as this is either directly after server's Finished or only * message after client's Finished. diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 23247f69fde..105e0cca2a7 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -13979,3 +13979,76 @@ int test_tls13_psk_key_zeroized(void) #endif return EXPECT_RESULT(); } + +/* A NewSessionTicket larger than the negotiated max fragment length must be + * split across records (RFC 8446 Section 5.1). With a 256 byte limit the + * ticket is split in builds whose internal ticket is large enough. */ +int test_tls13_new_session_ticket_max_fragment(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(HAVE_MAX_FRAGMENT) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + char readBuf[64]; + int off; + int recSz = 0; + int recCnt = 0; + + 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); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_8), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Process the ticket sent at the end of the handshake. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.c_len, 0); + + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS); + /* Each record holds at most 256 bytes plus content type and AEAD tag. */ + for (off = 0; EXPECT_SUCCESS() && off + RECORD_HEADER_SZ <= test_ctx.c_len; + off += RECORD_HEADER_SZ + recSz) { + ExpectIntEQ(test_ctx.c_buff[off], application_data); + recSz = (test_ctx.c_buff[off + 3] << 8) | test_ctx.c_buff[off + 4]; + ExpectIntLE(recSz, 256 + 1 + 16); + recCnt++; + } + ExpectIntEQ(off, test_ctx.c_len); + + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntGT(ssl_c->session->ticketLen, 0); + /* Handshake header, lifetime, age add, nonce, ticket length and empty + * extensions add 18 bytes to the ticket. */ + if (ssl_c->session->ticketLen + 18 > 256) + ExpectIntGE(recCnt, 2); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + /* Resume with the reassembled ticket. */ + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + 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); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_8), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_c), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index e81a98c2268..69d28bd4016 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -154,6 +154,7 @@ int test_tls13_cryptocb_async(void); int test_tls13_ticket_psk_modes(void); int test_tls13_psk_mode_mismatch_falls_back(void); int test_tls13_ticket_psk_modes_uses_policy(void); +int test_tls13_new_session_ticket_max_fragment(void); int test_tls13_send_session_ticket_psk_modes(void); int test_tls13_new_session_ticket_ext_framing(void); int test_tls13_new_session_ticket_keeps_ems(void); @@ -302,6 +303,7 @@ int test_tls13_psk_key_zeroized(void); TEST_DECL_GROUP("tls13", test_tls13_hs_secret_zeroized_psk_ke), \ TEST_DECL_GROUP("tls13", test_tls13_hs_secret_zeroized_sha384), \ TEST_DECL_GROUP("tls13", test_tls13_early_secret_zeroized), \ - TEST_DECL_GROUP("tls13", test_tls13_psk_key_zeroized) + TEST_DECL_GROUP("tls13", test_tls13_psk_key_zeroized), \ + TEST_DECL_GROUP("tls13", test_tls13_new_session_ticket_max_fragment) #endif /* WOLFCRYPT_TEST_TLS13_H */