Declare QUIC record length from bytes remaining

This commit is contained in:
Yosuke Shimizu
2026-08-05 14:12:26 +09:00
parent d6708600a2
commit f6bd188eee
2 changed files with 293 additions and 9 deletions
+8 -8
View File
@@ -201,15 +201,15 @@ static sword32 quic_record_transfer(QuicRecord* qr, byte* buf, word32 sz)
}
len = qr->end - qr->start;
/* We check if the buf is at least RECORD_HEADER_SZ */
if (sz < RECORD_HEADER_SZ) {
return WOLFSSL_FATAL_ERROR;
}
if (qr->rec_hdr_remain == 0) {
/* start a new TLS record */
rlen = (qr->len <= (word32)MAX_RECORD_SIZE) ?
qr->len : (word32)MAX_RECORD_SIZE;
/* a new TLS record needs room for its header, the body of a record
* already started can be handed out in smaller pieces */
if (sz < RECORD_HEADER_SZ) {
return WOLFSSL_FATAL_ERROR;
}
/* start a new TLS record over the bytes left to transfer */
rlen = (len <= (word32)MAX_RECORD_SIZE) ?
len : (word32)MAX_RECORD_SIZE;
offset += add_rec_header(buf, rlen,
(qr->level == wolfssl_encryption_early_data) ?
application_data : handshake);
+285 -1
View File
@@ -450,6 +450,72 @@ static int test_quic_record_cap(void) {
return EXPECT_RESULT();
}
/* size of a handshake message that does not fit into a single TLS record,
* yet stays below what the record layer accepts for one message */
#define QUIC_SPLIT_MSG_SZ ((word32)MAX_RECORD_SIZE + 1024)
static int test_quic_record_split(void) {
EXPECT_DECLS;
static const byte tp_params[] = {0, 1, 2, 3, 4, 5, 6, 7};
/* message sizes around the point where a second TLS record is needed,
* the +1..+4 tails are shorter than a record header */
static const word32 msg_sizes[] = {
(word32)MAX_RECORD_SIZE - HANDSHAKE_HEADER_SZ, /* one record */
(word32)MAX_RECORD_SIZE - HANDSHAKE_HEADER_SZ + 1,
(word32)MAX_RECORD_SIZE - HANDSHAKE_HEADER_SZ + 2,
(word32)MAX_RECORD_SIZE - HANDSHAKE_HEADER_SZ + 3,
(word32)MAX_RECORD_SIZE - HANDSHAKE_HEADER_SZ + 4,
QUIC_SPLIT_MSG_SZ
};
WOLFSSL_CTX * ctx = NULL;
WOLFSSL * ssl = NULL;
uint8_t * msg = NULL;
size_t len = 0;
size_t i;
ExpectNotNull(msg = (uint8_t*)XMALLOC(
QUIC_SPLIT_MSG_SZ + HANDSHAKE_HEADER_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER));
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
ExpectTrue(wolfSSL_CTX_set_quic_method(ctx, &dummy_method)
== WOLFSSL_SUCCESS);
for (i = 0; i < XELEM_CNT(msg_sizes); i++) {
if (msg != NULL) {
XMEMSET(msg, 0, msg_sizes[i] + HANDSHAKE_HEADER_SZ);
len = fake_record(server_hello, msg_sizes[i], msg);
}
ExpectNotNull(ssl = wolfSSL_new(ctx));
ExpectTrue(wolfSSL_set_quic_transport_params(ssl, tp_params,
sizeof(tp_params))
== WOLFSSL_SUCCESS);
/* send the ClientHello, then wait for the server flight */
ExpectIntNE(wolfSSL_connect(ssl), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl, 0),
WC_NO_ERR_TRACE(SSL_ERROR_WANT_READ));
ExpectTrue(provide_data(ssl, wolfssl_encryption_initial, msg, len, 0));
/* every record handed to the record layer must be complete, so the
* zeroed ServerHello is parsed and rejected on its version */
ExpectIntNE(wolfSSL_connect(ssl), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl, 0),
WC_NO_ERR_TRACE(VERSION_ERROR));
wolfSSL_free(ssl);
ssl = NULL;
}
wolfSSL_CTX_free(ctx);
XFREE(msg, NULL, DYNAMIC_TYPE_TMP_BUFFER);
printf(" test_quic_record_split: %s\n",
(EXPECT_SUCCESS()) ? pass : fail);
return EXPECT_RESULT();
}
static int test_quic_crypt(void) {
EXPECT_DECLS;
WOLFSSL_CTX * ctx = NULL;
@@ -1120,7 +1186,8 @@ typedef struct {
char rec_log[16*1024];
int sent_early_data;
int accept_early_data;
char early_data[16*1024];
/* holds more than one TLS record worth of early data */
char early_data[32*1024];
size_t early_data_len;
} QuicConversation;
@@ -1423,6 +1490,128 @@ static int test_quic_server_hello(int verbose) {
return EXPECT_RESULT();
}
/* how far the ClientHello is driven past MAX_RECORD_SIZE, and the payload
* the probe run uses to measure everything else in it */
#define QUIC_BIG_TP_MARGIN 1024
#define QUIC_TP_PROBE_SZ 16
/* wolfSSL rejects a transport parameter payload above this */
#define QUIC_TP_MAX_SZ 65535
/* length of the ClientHello this client sends with a payload of tp_sz
* transport parameter bytes in it */
static size_t quic_client_hello_len(WOLFSSL_CTX *ctx, const byte *tp,
size_t tp_sz, int verbose)
{
QuicTestContext tctx;
size_t len;
QuicTestContext_init(&tctx, ctx, "probe", verbose);
wolfSSL_set_quic_transport_version(tctx.ssl, TLSX_KEY_QUIC_TP_PARAMS);
AssertIntEQ(wolfSSL_set_quic_transport_params(tctx.ssl, tp, tp_sz),
WOLFSSL_SUCCESS);
AssertIntNE(wolfSSL_connect(tctx.ssl), WOLFSSL_SUCCESS);
AssertIntEQ(wolfSSL_get_error(tctx.ssl, 0),
WC_NO_ERR_TRACE(SSL_ERROR_WANT_READ));
len = tctx.output.len;
QuicTestContext_free(&tctx);
return len;
}
static int test_quic_big_client_hello(int verbose) {
EXPECT_DECLS;
WOLFSSL_CTX * ctx_c = NULL;
WOLFSSL_CTX * ctx_s = NULL;
QuicTestContext tclient, tserver;
QuicConversation conv;
byte probe[QUIC_TP_PROBE_SZ];
byte * tp_params = NULL;
const uint8_t * peer_params = NULL;
size_t peer_len = 0;
size_t base_len = 0;
size_t tp_sz = 0;
size_t i;
ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile,
WOLFSSL_FILETYPE_PEM));
ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile,
WOLFSSL_FILETYPE_PEM));
/* the size of a ClientHello varies a lot with the enabled key shares,
* so measure one and size the transport parameters from that */
XMEMSET(probe, 0x42, sizeof(probe));
if (ctx_c != NULL) {
base_len = quic_client_hello_len(ctx_c, probe, sizeof(probe), verbose);
}
ExpectTrue(base_len > (size_t)QUIC_TP_PROBE_SZ);
ExpectTrue(base_len < (size_t)MAX_RECORD_SIZE);
if (base_len > (size_t)QUIC_TP_PROBE_SZ &&
base_len < (size_t)MAX_RECORD_SIZE) {
tp_sz = (size_t)MAX_RECORD_SIZE + QUIC_BIG_TP_MARGIN
- (base_len - (size_t)QUIC_TP_PROBE_SZ);
}
ExpectTrue(tp_sz > 0);
ExpectTrue(tp_sz <= (size_t)QUIC_TP_MAX_SZ);
/* the ClientHello still has to fit into a single handshake message */
ExpectTrue((size_t)MAX_RECORD_SIZE + QUIC_BIG_TP_MARGIN
< (size_t)MAX_HANDSHAKE_SZ);
if (tp_sz > 0) {
ExpectNotNull(tp_params = (byte*)XMALLOC(tp_sz, NULL,
DYNAMIC_TYPE_TMP_BUFFER));
}
if (tp_params != NULL) {
/* a pattern, so a mis-ordered reassembly is detected as well */
for (i = 0; i < tp_sz; i++) {
tp_params[i] = (byte)(i & 0xff);
}
}
QuicTestContext_init(&tclient, ctx_c, "client", verbose);
QuicTestContext_init(&tserver, ctx_s, "server", verbose);
/* a single transport parameter extension, the default sends both the
* v1 and the draft one */
wolfSSL_set_quic_transport_version(tclient.ssl, TLSX_KEY_QUIC_TP_PARAMS);
ExpectTrue(wolfSSL_set_quic_transport_params(tclient.ssl, tp_params,
tp_sz)
== WOLFSSL_SUCCESS);
/* the ClientHello has to span more than one TLS record, or this test
* checks nothing */
ExpectIntNE(wolfSSL_connect(tclient.ssl), WOLFSSL_SUCCESS);
ExpectTrue(tclient.output.len > (size_t)MAX_RECORD_SIZE);
/* the server reassembles the ClientHello from several TLS records */
QuicConversation_init(&conv, &tclient, &tserver);
conv.started = 1;
QuicConversation_do(&conv);
ExpectIntEQ(tclient.output.len, 0);
ExpectIntEQ(tserver.output.len, 0);
ExpectTrue(wolfSSL_quic_read_level(tclient.ssl)
== wolfssl_encryption_application);
ExpectTrue(wolfSSL_quic_read_level(tserver.ssl)
== wolfssl_encryption_application);
/* and got the parameters out of it unchanged */
wolfSSL_get_peer_quic_transport_params(tserver.ssl, &peer_params,
&peer_len);
ExpectIntEQ(peer_len, tp_sz);
ExpectNotNull(peer_params);
ExpectIntEQ(XMEMCMP(peer_params, tp_params, tp_sz), 0);
QuicTestContext_free(&tclient);
QuicTestContext_free(&tserver);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
XFREE(tp_params, NULL, DYNAMIC_TYPE_TMP_BUFFER);
printf(" test_quic_big_client_hello: %s\n",
EXPECT_SUCCESS() ? pass : fail);
return EXPECT_RESULT();
}
static int test_quic_server_hello_fail(int verbose) {
EXPECT_DECLS;
WOLFSSL_CTX * ctx_c = NULL;
@@ -1928,6 +2117,98 @@ static int test_quic_early_data(int verbose) {
printf(" test_quic_early_data: %s\n", EXPECT_SUCCESS() ? pass : fail);
return EXPECT_RESULT();
}
/* early data that does not fit into a single TLS record */
#define QUIC_BIG_ED_SZ ((size_t)MAX_RECORD_SIZE + 1024)
static size_t output_level_len(const QuicTestContext *ctx,
WOLFSSL_ENCRYPTION_LEVEL level)
{
const OutputBuffer * out = &ctx->output;
size_t len = 0;
while (out != NULL) {
if (out->level == level) {
len += out->len;
}
out = out->next;
}
return len;
}
static int test_quic_big_early_data(int verbose) {
EXPECT_DECLS;
WOLFSSL_CTX * ctx_c = NULL;
WOLFSSL_CTX * ctx_s = NULL;
QuicTestContext tclient, tserver;
QuicConversation conv;
byte * early_data = NULL;
size_t ed_written = 0;
WOLFSSL_SESSION * session = NULL;
size_t i;
ExpectNotNull(early_data = (byte*)XMALLOC(QUIC_BIG_ED_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER));
if (early_data != NULL) {
/* a pattern, so a mis-ordered reassembly is detected as well */
for (i = 0; i < QUIC_BIG_ED_SZ; i++) {
early_data[i] = (byte)(i & 0xff);
}
}
ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
wolfSSL_CTX_UseSessionTicket(ctx_c);
ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile,
WOLFSSL_FILETYPE_PEM));
ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile,
WOLFSSL_FILETYPE_PEM));
/* a first handshake, only to obtain a session ticket */
QuicTestContext_init(&tclient, ctx_c, "client", verbose);
QuicTestContext_init(&tserver, ctx_s, "server", verbose);
wolfSSL_set_quic_early_data_enabled(tserver.ssl, 1);
QuicConversation_init(&conv, &tclient, &tserver);
QuicConversation_do(&conv);
ExpectTrue(tclient.ticket_len > 0);
ExpectNotNull(session = wolfSSL_get1_session(tclient.ssl));
QuicTestContext_free(&tclient);
QuicTestContext_free(&tserver);
/* resume, sending early data over more than one TLS record */
QuicTestContext_init(&tserver, ctx_s, "server", verbose);
QuicTestContext_init(&tclient, ctx_c, "client", verbose);
ExpectIntEQ(wolfSSL_set_session(tclient.ssl, session), WOLFSSL_SUCCESS);
wolfSSL_set_quic_early_data_enabled(tserver.ssl, 1);
QuicConversation_init(&conv, &tclient, &tserver);
/* make QuicConversation_do() use wolfSSL_read_early_data() */
conv.accept_early_data = 1;
QuicConversation_start(&conv, early_data, QUIC_BIG_ED_SZ, &ed_written);
ExpectIntEQ(ed_written, QUIC_BIG_ED_SZ);
/* the early data has to span more than one record, or the split path
* this exercises is never reached */
ExpectTrue(output_level_len(&tclient, wolfssl_encryption_early_data)
> (size_t)MAX_RECORD_SIZE);
QuicConversation_do(&conv);
ExpectIntEQ(wolfSSL_get_early_data_status(tclient.ssl),
WOLFSSL_EARLY_DATA_ACCEPTED);
/* the server reassembled it byte-identically */
ExpectIntEQ(conv.early_data_len, QUIC_BIG_ED_SZ);
ExpectIntEQ(XMEMCMP(conv.early_data, early_data, QUIC_BIG_ED_SZ), 0);
QuicTestContext_free(&tclient);
QuicTestContext_free(&tserver);
wolfSSL_SESSION_free(session);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
XFREE(early_data, NULL, DYNAMIC_TYPE_TMP_BUFFER);
printf(" test_quic_big_early_data: %s\n",
EXPECT_SUCCESS() ? pass : fail);
return EXPECT_RESULT();
}
#endif /* WOLFSSL_EARLY_DATA */
static int new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session)
@@ -2049,11 +2330,13 @@ int QuicTest(void)
#ifndef NO_WOLFSSL_CLIENT
if ((ret = test_provide_quic_data()) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_record_cap()) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_record_split()) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_crypt()) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_client_hello(verbose)) != TEST_SUCCESS) goto leave;
#endif
#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
if ((ret = test_quic_server_hello(verbose)) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_big_client_hello(verbose)) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_server_hello_fail(verbose)) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_key_update_rejected(verbose)) != TEST_SUCCESS) goto leave;
#ifdef REALLY_HAVE_ALPN_AND_SNI
@@ -2064,6 +2347,7 @@ int QuicTest(void)
if ((ret = test_quic_resumption(verbose)) != TEST_SUCCESS) goto leave;
#ifdef WOLFSSL_EARLY_DATA
if ((ret = test_quic_early_data(verbose)) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_big_early_data(verbose)) != TEST_SUCCESS) goto leave;
#endif /* WOLFSSL_EARLY_DATA */
if ((ret = test_quic_session_export(verbose)) != TEST_SUCCESS) goto leave;
#endif /* HAVE_SESSION_TICKET */