From b340e27847ac2b80bb2e4435f8db6c1bd8c82d0a Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 3 Jun 2026 15:25:09 +0200 Subject: [PATCH 01/16] test_suite_ssl: Add early data drop tests Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 6 ++ tests/suites/test_suite_ssl.function | 153 +++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index dbe0dfe44c..86cd311e01 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3586,3 +3586,9 @@ send_invalid_sig_alg:MBEDTLS_SSL_SIG_ECDSA:MBEDTLS_SSL_HASH_SHA512:MBEDTLS_ERR_S TLS 1.2 NewSessionTicket: failing ticket callback leaves lifetime unset depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY write_new_session_ticket_mem + +TLS 1.3 drop early data +tls13_drop_early_data:0 + +TLS 1.3 drop early data and insert an EndOfEarlyData +tls13_drop_early_data:1 diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index aba395a8ce..3b80db0a79 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6778,3 +6778,156 @@ exit: MD_OR_USE_PSA_DONE(); } /* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_EARLY_DATA:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED:MBEDTLS_TEST_HAS_DEFAULT_EC_GROUP:PSA_WANT_ALG_SHA_256:MBEDTLS_X509_RSASSA_PSS_SUPPORT:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_SSL_SESSION_TICKETS */ +void tls13_drop_early_data(int insert_end_of_early_data) +{ + int ret = -1; + mbedtls_test_ssl_endpoint client_ep, server_ep; + memset(&client_ep, 0, sizeof(client_ep)); + memset(&server_ep, 0, sizeof(server_ep)); + + mbedtls_test_handshake_test_options client_options, server_options; + mbedtls_test_init_handshake_options(&client_options); + mbedtls_test_init_handshake_options(&server_options); + + mbedtls_ssl_session saved_session; + mbedtls_ssl_session_init(&saved_session); + + const char *early_data = "This is early data."; + size_t early_data_len = strlen(early_data); + unsigned char buf[256]; + + PSA_INIT(); + + /* + * Run first handshake to get a ticket from the server. + */ + + client_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED; + server_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED; + ret = mbedtls_test_get_tls13_ticket(&client_options, &server_options, + &saved_session); + TEST_EQUAL(ret, 0); + + /* + * Prepare for handshake with the ticket. + */ + ret = mbedtls_test_ssl_endpoint_init(&client_ep, MBEDTLS_SSL_IS_CLIENT, + &client_options); + TEST_EQUAL(ret, 0); + + ret = mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER, + &server_options); + TEST_EQUAL(ret, 0); + + mbedtls_ssl_conf_session_tickets_cb(&server_ep.conf, + mbedtls_test_ticket_write, + mbedtls_test_ticket_parse, + NULL); + + ret = mbedtls_test_mock_socket_connect(&(client_ep.socket), + &(server_ep.socket), 1024); + TEST_EQUAL(ret, 0); + + ret = mbedtls_ssl_set_session(&(client_ep.ssl), &saved_session); + TEST_EQUAL(ret, 0); + + /* + * Initiate handshake with ticket, up to the point where the client has + * written the ClientHello and only the ClientHello in its output socket + * buffer. + */ +#if defined(MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE) + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + &(client_ep.ssl), &(server_ep.ssl), + MBEDTLS_SSL_CLIENT_CCS_AFTER_CLIENT_HELLO), 0); +#else + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + &(client_ep.ssl), &(server_ep.ssl), + MBEDTLS_SSL_SERVER_HELLO), 0); +#endif + TEST_EQUAL(mbedtls_ssl_flush_output(&client_ep.ssl), 0); + + if (insert_end_of_early_data) { + /* + * Append an EndOfEarlyData message at the end of the record, just + * after the ClientHello message. + */ + unsigned char *client_output_buffer = client_ep.socket.output->buffer; + size_t record_size = MBEDTLS_GET_UINT16_BE(client_output_buffer, 3); + + MBEDTLS_PUT_UINT16_BE(record_size + 4, client_output_buffer, 3); + unsigned char end_of_early_data[] = { 0x05, 0x00, 0x00, 0x00 }; + ret = mbedtls_test_ssl_buffer_put(client_ep.socket.output, + end_of_early_data, + sizeof(end_of_early_data)); + TEST_EQUAL(ret, (int) sizeof(end_of_early_data)); + } + + /* + * The server read the record containing the ClientHello and possibly the + * EndOfEarlyData message and parse the ClientHello. + */ + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + &(server_ep.ssl), &(client_ep.ssl), + MBEDTLS_SSL_SERVER_HELLO), 0); + + /* The client application writes early data */ + ret = mbedtls_ssl_write_early_data(&(client_ep.ssl), + (unsigned char *) early_data, + early_data_len); + TEST_EQUAL(ret, early_data_len); + + /* Drop early data */ + TEST_ASSERT(client_ep.socket.output->content_length <= sizeof(buf)); + mbedtls_test_ssl_buffer_get(client_ep.socket.output, buf, sizeof(buf)); + TEST_EQUAL(server_ep.socket.input->content_length, 0); + + /* + * Resume the handshake, up to the point where the client is about to write + * the EndOfEarlyData message. + */ + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + &(client_ep.ssl), &(server_ep.ssl), + MBEDTLS_SSL_END_OF_EARLY_DATA), 0); + TEST_EQUAL(client_ep.ssl.state, MBEDTLS_SSL_END_OF_EARLY_DATA); + + if (insert_end_of_early_data) { + /* + * Let the client write the EndOfEarlyData message and then read it + * from the output socket buffer, preventing it to reach the server as + * we have already inserted one. + */ + ret = mbedtls_ssl_handshake_step(&client_ep.ssl); + TEST_EQUAL(ret, 0); + ret = mbedtls_ssl_flush_output(&client_ep.ssl); + TEST_EQUAL(ret, 0); + TEST_ASSERT(client_ep.socket.output->content_length <= sizeof(buf)); + mbedtls_test_ssl_buffer_get(client_ep.socket.output, buf, sizeof(buf)); + } + + ret = mbedtls_test_move_handshake_to_state(&(server_ep.ssl), + &(client_ep.ssl), + MBEDTLS_SSL_HANDSHAKE_OVER); + if (insert_end_of_early_data) { + TEST_EQUAL(ret, 0); + } else { + /* + * If we have just dropped the early data then the EndOfEarlyData + * message is encrypted with the record number 1 while the server + * expects it to be encrypted with record number 0. Thus the + * handshake fails with MBEDTLS_ERR_SSL_INVALID_MAC. + */ + TEST_EQUAL(ret, MBEDTLS_ERR_SSL_INVALID_MAC); + } + +exit: + mbedtls_test_ssl_endpoint_free(&client_ep); + mbedtls_test_ssl_endpoint_free(&server_ep); + mbedtls_test_free_handshake_options(&client_options); + mbedtls_test_free_handshake_options(&server_options); + mbedtls_ssl_session_free(&saved_session); + PSA_DONE(); +} +/* END_CASE */ From 3861588b47d045afcc66d9ba8b73b8217e87d2ce Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Fri, 22 May 2026 18:10:49 +0200 Subject: [PATCH 02/16] tls13: Add record boundary checks RFC 8446 (TLS 1.3 specification), Section 5.1, states: Handshake messages MAY be coalesced into a single TLSPlaintext record or fragmented across several records, provided that: ... -Handshake messages MUST NOT span key changes. Implementations MUST verify that all messages immediately preceding a key change align with a record boundary; if not, then they MUST terminate the connection with an "unexpected_message" alert. Because the ClientHello, EndOfEarlyData, ServerHello, Finished, and KeyUpdate messages can immediately precede a key change, implementations MUST send these messages in alignment with a record boundary. Signed-off-by: Ronald Cron --- library/ssl_misc.h | 1 + library/ssl_tls13_client.c | 8 ++-- library/ssl_tls13_generic.c | 10 +++-- library/ssl_tls13_server.c | 4 +- tests/suites/test_suite_ssl.function | 65 ++++++++++------------------ 5 files changed, 36 insertions(+), 52 deletions(-) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index f0ca823f33..d85dbc6e1d 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -2028,6 +2028,7 @@ static inline int mbedtls_ssl_tls13_key_exchange_mode_with_ephemeral( MBEDTLS_CHECK_RETURN_CRITICAL int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, unsigned hs_type, + unsigned record_boundary, unsigned char **buf, size_t *buf_len); diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c index ecdf507eab..e4138fa2c7 100644 --- a/library/ssl_tls13_client.c +++ b/library/ssl_tls13_client.c @@ -2002,7 +2002,7 @@ static int ssl_tls13_process_server_hello(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> %s", __func__)); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_SERVER_HELLO, &buf, &buf_len)); + ssl, MBEDTLS_SSL_HS_SERVER_HELLO, 1, &buf, &buf_len)); ret = ssl_tls13_preprocess_server_hello(ssl, buf, buf + buf_len); if (ret < 0) { @@ -2195,7 +2195,7 @@ static int ssl_tls13_process_encrypted_extensions(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse encrypted extensions")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS, + ssl, MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS, 0, &buf, &buf_len)); /* Process the message contents */ @@ -2530,7 +2530,7 @@ static int ssl_tls13_process_certificate_request(mbedtls_ssl_context *ssl) size_t buf_len; MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CERTIFICATE_REQUEST, + ssl, MBEDTLS_SSL_HS_CERTIFICATE_REQUEST, 0, &buf, &buf_len)); MBEDTLS_SSL_PROC_CHK(ssl_tls13_parse_certificate_request( @@ -3025,7 +3025,7 @@ static int ssl_tls13_process_new_session_ticket(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse new session ticket")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_NEW_SESSION_TICKET, + ssl, MBEDTLS_SSL_HS_NEW_SESSION_TICKET, 0, &buf, &buf_len)); /* diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 078daea352..90891567ec 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -47,6 +47,7 @@ const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, unsigned hs_type, + unsigned record_boundary, unsigned char **buf, size_t *buf_len) { @@ -58,7 +59,8 @@ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, } if (ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE || - ssl->in_msg[0] != hs_type) { + ssl->in_msg[0] != hs_type || + (record_boundary && (ssl->in_hslen != ssl->in_msglen))) { MBEDTLS_SSL_DEBUG_MSG(1, ("Receive unexpected handshake message.")); MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE, MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); @@ -339,7 +341,7 @@ int mbedtls_ssl_tls13_process_certificate_verify(mbedtls_ssl_context *ssl) MBEDTLS_SSL_PROC_CHK( mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CERTIFICATE_VERIFY, &buf, &buf_len)); + ssl, MBEDTLS_SSL_HS_CERTIFICATE_VERIFY, 0, &buf, &buf_len)); /* Need to calculate the hash of the transcript first * before reading the message since otherwise it gets @@ -691,7 +693,7 @@ int mbedtls_ssl_tls13_process_certificate(mbedtls_ssl_context *ssl) size_t buf_len; MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CERTIFICATE, + ssl, MBEDTLS_SSL_HS_CERTIFICATE, 0, &buf, &buf_len)); /* Parse the certificate chain sent by the peer. */ @@ -1110,7 +1112,7 @@ int mbedtls_ssl_tls13_process_finished_message(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse finished message")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_FINISHED, &buf, &buf_len)); + ssl, MBEDTLS_SSL_HS_FINISHED, 1, &buf, &buf_len)); /* Preprocessing step: Compute handshake digest */ MBEDTLS_SSL_PROC_CHK(ssl_tls13_preprocess_finished_message(ssl)); diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c index 38f2fe9370..0db142c50a 100644 --- a/library/ssl_tls13_server.c +++ b/library/ssl_tls13_server.c @@ -1936,7 +1936,7 @@ static int ssl_tls13_process_client_hello(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse client hello")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CLIENT_HELLO, + ssl, MBEDTLS_SSL_HS_CLIENT_HELLO, 1, &buf, &buflen)); MBEDTLS_SSL_PROC_CHK_NEG(ssl_tls13_parse_client_hello(ssl, buf, @@ -3027,7 +3027,7 @@ static int ssl_tls13_process_end_of_early_data(mbedtls_ssl_context *ssl) size_t buf_len; MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_END_OF_EARLY_DATA, + ssl, MBEDTLS_SSL_HS_END_OF_EARLY_DATA, 1, &buf, &buf_len)); MBEDTLS_SSL_PROC_CHK(ssl_tls13_parse_end_of_early_data( diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 3b80db0a79..d058401148 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -4124,7 +4124,7 @@ void tls13_server_certificate_msg_invalid_vector_len() ret = mbedtls_ssl_tls13_fetch_handshake_msg(&(client_ep.ssl), MBEDTLS_SSL_HS_CERTIFICATE, - &buf, &buf_len); + 0, &buf, &buf_len); TEST_EQUAL(ret, 0); end = buf + buf_len; @@ -6865,14 +6865,21 @@ void tls13_drop_early_data(int insert_end_of_early_data) TEST_EQUAL(ret, (int) sizeof(end_of_early_data)); } - /* - * The server read the record containing the ClientHello and possibly the - * EndOfEarlyData message and parse the ClientHello. - */ - TEST_EQUAL(mbedtls_test_move_handshake_to_state( - &(server_ep.ssl), &(client_ep.ssl), - MBEDTLS_SSL_SERVER_HELLO), 0); + /* The server read the record containing the ClientHello. */ + ret = mbedtls_test_move_handshake_to_state(&(server_ep.ssl), + &(client_ep.ssl), + MBEDTLS_SSL_SERVER_HELLO); + if (insert_end_of_early_data) { + /* + * If an EndOfEarlyData message was appended to the record containing + * the ClientHello then the end of the ClientHello does not align with + * the record boundary and the handshake fails. + */ + TEST_EQUAL(ret, MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); + goto exit; + } + TEST_EQUAL(ret, 0); /* The client application writes early data */ ret = mbedtls_ssl_write_early_data(&(client_ep.ssl), (unsigned char *) early_data, @@ -6884,43 +6891,17 @@ void tls13_drop_early_data(int insert_end_of_early_data) mbedtls_test_ssl_buffer_get(client_ep.socket.output, buf, sizeof(buf)); TEST_EQUAL(server_ep.socket.input->content_length, 0); - /* - * Resume the handshake, up to the point where the client is about to write - * the EndOfEarlyData message. - */ - TEST_EQUAL(mbedtls_test_move_handshake_to_state( - &(client_ep.ssl), &(server_ep.ssl), - MBEDTLS_SSL_END_OF_EARLY_DATA), 0); - TEST_EQUAL(client_ep.ssl.state, MBEDTLS_SSL_END_OF_EARLY_DATA); - - if (insert_end_of_early_data) { - /* - * Let the client write the EndOfEarlyData message and then read it - * from the output socket buffer, preventing it to reach the server as - * we have already inserted one. - */ - ret = mbedtls_ssl_handshake_step(&client_ep.ssl); - TEST_EQUAL(ret, 0); - ret = mbedtls_ssl_flush_output(&client_ep.ssl); - TEST_EQUAL(ret, 0); - TEST_ASSERT(client_ep.socket.output->content_length <= sizeof(buf)); - mbedtls_test_ssl_buffer_get(client_ep.socket.output, buf, sizeof(buf)); - } - + /* Resume the handshake */ ret = mbedtls_test_move_handshake_to_state(&(server_ep.ssl), &(client_ep.ssl), MBEDTLS_SSL_HANDSHAKE_OVER); - if (insert_end_of_early_data) { - TEST_EQUAL(ret, 0); - } else { - /* - * If we have just dropped the early data then the EndOfEarlyData - * message is encrypted with the record number 1 while the server - * expects it to be encrypted with record number 0. Thus the - * handshake fails with MBEDTLS_ERR_SSL_INVALID_MAC. - */ - TEST_EQUAL(ret, MBEDTLS_ERR_SSL_INVALID_MAC); - } + /* + * The early data were dropped. The EndOfEarlyData message is encrypted + * with the record number 1, but the server expects it to be encrypted + * with record number 0. Thus the handshake fails with + * MBEDTLS_ERR_SSL_INVALID_MAC. + */ + TEST_EQUAL(ret, MBEDTLS_ERR_SSL_INVALID_MAC); exit: mbedtls_test_ssl_endpoint_free(&client_ep); From c8fefcde05bc3bae9e344719308593fefe893180 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 8 Jun 2026 13:06:08 +0200 Subject: [PATCH 03/16] test_suite_ssl: Test ClientHello on record boundary check Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 3 + tests/suites/test_suite_ssl.function | 95 ++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 86cd311e01..43554ce112 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3592,3 +3592,6 @@ tls13_drop_early_data:0 TLS 1.3 drop early data and insert an EndOfEarlyData tls13_drop_early_data:1 + +TLS 1.3 ClientHello record boundary alignment enforcement +tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_CLIENT_HELLO diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index d058401148..772e64f02b 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6912,3 +6912,98 @@ exit: PSA_DONE(); } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_DEBUG_C:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_TEST_HAS_DEFAULT_EC_GROUP:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_SHA_256:MBEDTLS_X509_RSASSA_PSS_SUPPORT:PSA_WANT_ALG_RSA_PKCS1V15_SIGN */ +void tls13_record_boundary_alignment(int endpoint, int handshake_message) +{ + int ret = -1; + int target_state, peer_target_state, encrypted; + mbedtls_test_ssl_endpoint client_ep, server_ep; + + mbedtls_test_handshake_test_options client_options, server_options; + mbedtls_test_init_handshake_options(&client_options); + mbedtls_test_init_handshake_options(&server_options); + + mbedtls_test_mock_socket *socket = NULL; + mbedtls_ssl_context *ssl_ctx = NULL; + mbedtls_ssl_context *peer_ssl_ctx = NULL; + unsigned char dummy_data[] = { 0x00 }; + + PSA_INIT(); + + switch (handshake_message) { + case MBEDTLS_SSL_HS_CLIENT_HELLO: + TEST_EQUAL(endpoint, MBEDTLS_SSL_IS_SERVER); + target_state = MBEDTLS_SSL_CLIENT_HELLO; + peer_target_state = MBEDTLS_SSL_SERVER_HELLO; + encrypted = 0; + break; + + default: + TEST_ASSERT(0); + } + + if (endpoint == MBEDTLS_SSL_IS_CLIENT) { + ssl_ctx = &client_ep.ssl; + peer_ssl_ctx = &server_ep.ssl; + socket = &client_ep.socket; + } else { + ssl_ctx = &server_ep.ssl; + peer_ssl_ctx = &client_ep.ssl; + socket = &server_ep.socket; + } + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&client_ep, MBEDTLS_SSL_IS_CLIENT, + &client_options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER, + &server_options), 0); + TEST_EQUAL(mbedtls_test_mock_socket_connect(&(client_ep.socket), + &(server_ep.socket), 1024), 0); + + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + ssl_ctx, peer_ssl_ctx, target_state), 0); + TEST_EQUAL(mbedtls_ssl_flush_output(ssl_ctx), 0); + /* + * Advance the peer further through the handshake so that it emits the + * messages needed by the endpoint under test to proceed through its next + * step. + */ + if (peer_target_state != -1) { + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + peer_ssl_ctx, ssl_ctx, peer_target_state), 0); + TEST_EQUAL(mbedtls_ssl_flush_output(peer_ssl_ctx), 0); + } + TEST_EQUAL(ssl_ctx->state, target_state); + + if (encrypted) { + TEST_ASSERT(0); + } else { + /* + * In case of the unencrypted ClientHello or ServerHello, add an + * additional byte to the record that contains the handshake message. + * The byte is added in the socket buffer, before to be fetched by + * `mbedtls_ssl_read_record()`, simulating a man-in-the-middle tampering + * with the record. + */ + unsigned char *input_buffer = socket->input->buffer; + size_t record_size = MBEDTLS_GET_UINT16_BE(input_buffer, 3); + ret = mbedtls_test_ssl_buffer_put(socket->input, dummy_data, + sizeof(dummy_data)); + TEST_EQUAL(ret, (int) sizeof(dummy_data)); + MBEDTLS_PUT_UINT16_BE(record_size + sizeof(dummy_data), input_buffer, 3); + } + + ret = mbedtls_ssl_handshake_step(ssl_ctx); + TEST_EQUAL(ret, MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); + TEST_EQUAL(ssl_ctx->in_msglen, + ssl_ctx->in_hslen + sizeof(dummy_data)); + TEST_EQUAL(ssl_ctx->in_msgtype, MBEDTLS_SSL_MSG_HANDSHAKE); + TEST_EQUAL(ssl_ctx->in_msg[0], handshake_message); + +exit: + mbedtls_test_ssl_endpoint_free(&client_ep); + mbedtls_test_ssl_endpoint_free(&server_ep); + mbedtls_test_free_handshake_options(&client_options); + mbedtls_test_free_handshake_options(&server_options); + PSA_DONE(); +} +/* END_CASE */ From 93eb43ed4fd09c51a6dc7a434d347199b1c9cf97 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 8 Jun 2026 13:18:20 +0200 Subject: [PATCH 04/16] test_suite_ssl: Test ServerHello on record boundary enforcement Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 3 +++ tests/suites/test_suite_ssl.function | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 43554ce112..575b26a9e3 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3595,3 +3595,6 @@ tls13_drop_early_data:1 TLS 1.3 ClientHello record boundary alignment enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_CLIENT_HELLO + +TLS 1.3 ServerHello record boundary alignement enforcement +tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_SERVER_HELLO diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 772e64f02b..767c6a607c 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6938,6 +6938,13 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) encrypted = 0; break; + case MBEDTLS_SSL_HS_SERVER_HELLO: + TEST_EQUAL(endpoint, MBEDTLS_SSL_IS_CLIENT); + target_state = MBEDTLS_SSL_SERVER_HELLO; + peer_target_state = MBEDTLS_SSL_ENCRYPTED_EXTENSIONS; + encrypted = 0; + break; + default: TEST_ASSERT(0); } From 53ac018b24e11762ba43521c0fb2d5e01c6e13ca Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 8 Jun 2026 14:10:19 +0200 Subject: [PATCH 05/16] test_suite_ssl: Test server Finished on record boundary enforcement Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 3 ++ tests/suites/test_suite_ssl.function | 78 +++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 575b26a9e3..7b9a81ce17 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3598,3 +3598,6 @@ tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_CLIENT_HELL TLS 1.3 ServerHello record boundary alignement enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_SERVER_HELLO + +TLS 1.3 server Finished record boundary alignement enforcement +tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_FINISHED diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 767c6a607c..617cbd58ae 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -552,6 +552,51 @@ exit: #endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED etc */ +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && defined(MBEDTLS_DEBUG_C) && \ + defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_SRV_C) && \ + defined(MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE) && \ + defined(MBEDTLS_TEST_HAS_DEFAULT_EC_GROUP) && \ + defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED) && \ + defined(PSA_WANT_ALG_SHA_256) && \ + defined(MBEDTLS_X509_RSASSA_PSS_SUPPORT) && \ + defined(PSA_WANT_ALG_RSA_PKCS1V15_SIGN) +typedef struct { + /* Context for `tls_tweak_in_msglen()`. The debug callback + * watches for "<= read record" in the SSL debug trace. When the pattern is + * detected, it increases by `nb_bytes` the 'in_msglen' field of the + * associated SSL context. + */ + mbedtls_ssl_context *ssl; + int armed; + int nb_bytes; + int triggered; +} tls_tweak_in_msglen_context; + +static void tls_tweak_in_msglen(void *ctx, int level, + const char *file, int line, + const char *str) +{ + tls_tweak_in_msglen_context *tweak_in_msglen_ctx = ctx; + + (void) level; + (void) file; + (void) line; + + if (!tweak_in_msglen_ctx->armed) { + return; + } + if (strstr(str, "<= read record") == NULL) { + return; + } + + tweak_in_msglen_ctx->ssl->in_msglen += tweak_in_msglen_ctx->nb_bytes; + tweak_in_msglen_ctx->armed = 0; + tweak_in_msglen_ctx->triggered++; +} +#endif + + + /* END_HEADER */ /* BEGIN_DEPENDENCIES @@ -6923,6 +6968,9 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) mbedtls_test_init_handshake_options(&client_options); mbedtls_test_init_handshake_options(&server_options); + tls_tweak_in_msglen_context tweak_in_msglen_ctx; + memset(&tweak_in_msglen_ctx, 0, sizeof(tweak_in_msglen_ctx)); + mbedtls_test_mock_socket *socket = NULL; mbedtls_ssl_context *ssl_ctx = NULL; mbedtls_ssl_context *peer_ssl_ctx = NULL; @@ -6945,6 +6993,14 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) encrypted = 0; break; + case MBEDTLS_SSL_HS_FINISHED: + if (endpoint == MBEDTLS_SSL_IS_CLIENT) { + target_state = MBEDTLS_SSL_SERVER_FINISHED; + peer_target_state = MBEDTLS_SSL_CLIENT_CERTIFICATE; + } + encrypted = 1; + break; + default: TEST_ASSERT(0); } @@ -6953,12 +7009,19 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) ssl_ctx = &client_ep.ssl; peer_ssl_ctx = &server_ep.ssl; socket = &client_ep.socket; + client_options.cli_log_obj = &tweak_in_msglen_ctx; + client_options.cli_log_fun = tls_tweak_in_msglen; } else { ssl_ctx = &server_ep.ssl; peer_ssl_ctx = &client_ep.ssl; socket = &server_ep.socket; + server_options.srv_log_obj = &tweak_in_msglen_ctx; + server_options.srv_log_fun = tls_tweak_in_msglen; } + mbedtls_debug_set_threshold(2); + tweak_in_msglen_ctx.ssl = ssl_ctx; + TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&client_ep, MBEDTLS_SSL_IS_CLIENT, &client_options), 0); TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER, @@ -6982,7 +7045,15 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) TEST_EQUAL(ssl_ctx->state, target_state); if (encrypted) { - TEST_ASSERT(0); + /* + * If the message we want to test the record boundary enforcement is + * encrypted, we cannot manipulate easily the record that contains it. + * In that case, we tamper with the record size after the record has been + * fetched from the socket buffer and decrypted by + * `mbedtls_ssl_read_record()`. + */ + tweak_in_msglen_ctx.armed = 1; + tweak_in_msglen_ctx.nb_bytes = sizeof(dummy_data); } else { /* * In case of the unencrypted ClientHello or ServerHello, add an @@ -7006,11 +7077,16 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) TEST_EQUAL(ssl_ctx->in_msgtype, MBEDTLS_SSL_MSG_HANDSHAKE); TEST_EQUAL(ssl_ctx->in_msg[0], handshake_message); + if (encrypted) { + TEST_EQUAL(tweak_in_msglen_ctx.triggered, 1); + } + exit: mbedtls_test_ssl_endpoint_free(&client_ep); mbedtls_test_ssl_endpoint_free(&server_ep); mbedtls_test_free_handshake_options(&client_options); mbedtls_test_free_handshake_options(&server_options); + mbedtls_debug_set_threshold(0); PSA_DONE(); } /* END_CASE */ From cbef7256ce41778617e76c7c84b629d74338586d Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 8 Jun 2026 16:21:57 +0200 Subject: [PATCH 06/16] test_suite_ssl: Test client Finished on record boundary enforcement Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 3 +++ tests/suites/test_suite_ssl.function | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 7b9a81ce17..eecb4abfbd 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3601,3 +3601,6 @@ tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_SERVER_HELL TLS 1.3 server Finished record boundary alignement enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_FINISHED + +TLS 1.3 client Finished record boundary alignement enforcement +tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_FINISHED diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 617cbd58ae..e4857e0b21 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6997,6 +6997,9 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) if (endpoint == MBEDTLS_SSL_IS_CLIENT) { target_state = MBEDTLS_SSL_SERVER_FINISHED; peer_target_state = MBEDTLS_SSL_CLIENT_CERTIFICATE; + } else { + target_state = MBEDTLS_SSL_CLIENT_FINISHED; + peer_target_state = MBEDTLS_SSL_FLUSH_BUFFERS; } encrypted = 1; break; From 2de65b79a3e2b7930b9b4a3eb90bc4813fa61548 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 9 Jun 2026 13:20:49 +0200 Subject: [PATCH 07/16] test_suite_ssl: Test EndOfEarlyData on record boundary enforcement Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 4 ++++ tests/suites/test_suite_ssl.function | 32 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index eecb4abfbd..c856da44ff 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3604,3 +3604,7 @@ tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_FINISHED TLS 1.3 client Finished record boundary alignement enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_FINISHED + +TLS 1.3 EndOfEarlyData record boundary alignement enforcement +depends_on:MBEDTLS_SSL_EARLY_DATA:MBEDTLS_SSL_SESSION_TICKETS +tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_END_OF_EARLY_DATA diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index e4857e0b21..c7b15824b4 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6968,6 +6968,9 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) mbedtls_test_init_handshake_options(&client_options); mbedtls_test_init_handshake_options(&server_options); + mbedtls_ssl_session saved_session; + mbedtls_ssl_session_init(&saved_session); + tls_tweak_in_msglen_context tweak_in_msglen_ctx; memset(&tweak_in_msglen_ctx, 0, sizeof(tweak_in_msglen_ctx)); @@ -7004,6 +7007,21 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) encrypted = 1; break; + +#if defined(MBEDTLS_SSL_EARLY_DATA) && defined(MBEDTLS_SSL_SESSION_TICKETS) + case MBEDTLS_SSL_HS_END_OF_EARLY_DATA: + TEST_EQUAL(endpoint, MBEDTLS_SSL_IS_SERVER); + target_state = MBEDTLS_SSL_END_OF_EARLY_DATA; + peer_target_state = MBEDTLS_SSL_CLIENT_CERTIFICATE; + encrypted = 1; + client_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED; + server_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED; + ret = mbedtls_test_get_tls13_ticket(&client_options, &server_options, + &saved_session); + TEST_EQUAL(ret, 0); + break; +#endif + default: TEST_ASSERT(0); } @@ -7029,6 +7047,19 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) &client_options), 0); TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER, &server_options), 0); + +#if defined(MBEDTLS_SSL_EARLY_DATA) && defined(MBEDTLS_SSL_SESSION_TICKETS) + if (handshake_message == MBEDTLS_SSL_HS_END_OF_EARLY_DATA) { + mbedtls_ssl_conf_session_tickets_cb(&server_ep.conf, + mbedtls_test_ticket_write, + mbedtls_test_ticket_parse, + NULL); + + ret = mbedtls_ssl_set_session(&(client_ep.ssl), &saved_session); + TEST_EQUAL(ret, 0); + } +#endif + TEST_EQUAL(mbedtls_test_mock_socket_connect(&(client_ep.socket), &(server_ep.socket), 1024), 0); @@ -7090,6 +7121,7 @@ exit: mbedtls_test_free_handshake_options(&client_options); mbedtls_test_free_handshake_options(&server_options); mbedtls_debug_set_threshold(0); + mbedtls_ssl_session_free(&saved_session); PSA_DONE(); } /* END_CASE */ From e55d0b76ed5980c1ac46169b37bdbe0ea3fbb466 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 10 Jun 2026 10:47:57 +0200 Subject: [PATCH 08/16] Add change log Signed-off-by: Ronald Cron --- ChangeLog.d/drop-early-data.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ChangeLog.d/drop-early-data.txt diff --git a/ChangeLog.d/drop-early-data.txt b/ChangeLog.d/drop-early-data.txt new file mode 100644 index 0000000000..dc597a23f0 --- /dev/null +++ b/ChangeLog.d/drop-early-data.txt @@ -0,0 +1,7 @@ +Security + * Fixed a TLS 1.3 record-boundary validation issue that could allow + unauthenticated plaintext data to be processed across a key change. In + servers with MBEDTLS_SSL_EARLY_DATA enabled, this could be exploited by a + man-in-the-middle attacker to cause loss of 0-RTT early data. + Reported by Ben Smyth. + CVE-2026-TODO. From c494497256057f2fda69d9bb260f37f975d03c35 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Thu, 11 Jun 2026 19:23:15 +0200 Subject: [PATCH 09/16] Fix typos Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.data | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index c856da44ff..a08bb2b961 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3596,15 +3596,15 @@ tls13_drop_early_data:1 TLS 1.3 ClientHello record boundary alignment enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_CLIENT_HELLO -TLS 1.3 ServerHello record boundary alignement enforcement +TLS 1.3 ServerHello record boundary alignment enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_SERVER_HELLO -TLS 1.3 server Finished record boundary alignement enforcement +TLS 1.3 server Finished record boundary alignment enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_FINISHED -TLS 1.3 client Finished record boundary alignement enforcement +TLS 1.3 client Finished record boundary alignment enforcement tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_FINISHED -TLS 1.3 EndOfEarlyData record boundary alignement enforcement +TLS 1.3 EndOfEarlyData record boundary alignment enforcement depends_on:MBEDTLS_SSL_EARLY_DATA:MBEDTLS_SSL_SESSION_TICKETS tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_END_OF_EARLY_DATA From 86315b62a833cc28ff0c3a3c659854f62e0ff4b8 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Thu, 11 Jun 2026 19:38:42 +0200 Subject: [PATCH 10/16] test_suite_ssl: Add initialization to all zeroes of test endpoint structs Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.function | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index c7b15824b4..f6eeeabe1f 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6963,6 +6963,8 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) int ret = -1; int target_state, peer_target_state, encrypted; mbedtls_test_ssl_endpoint client_ep, server_ep; + memset(&client_ep, 0, sizeof(client_ep)); + memset(&server_ep, 0, sizeof(server_ep)); mbedtls_test_handshake_test_options client_options, server_options; mbedtls_test_init_handshake_options(&client_options); From 0c9d3d4a0fb50216993d4142335a2888929bcd72 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Thu, 11 Jun 2026 19:40:36 +0200 Subject: [PATCH 11/16] tls13: Improve argument name Signed-off-by: Ronald Cron --- library/ssl_misc.h | 2 +- library/ssl_tls13_generic.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index d85dbc6e1d..8d9323c6de 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -2028,7 +2028,7 @@ static inline int mbedtls_ssl_tls13_key_exchange_mode_with_ephemeral( MBEDTLS_CHECK_RETURN_CRITICAL int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, unsigned hs_type, - unsigned record_boundary, + unsigned require_record_boundary, unsigned char **buf, size_t *buf_len); diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 90891567ec..1465de1ec0 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -47,7 +47,7 @@ const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, unsigned hs_type, - unsigned record_boundary, + unsigned require_record_boundary, unsigned char **buf, size_t *buf_len) { @@ -60,7 +60,7 @@ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, if (ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE || ssl->in_msg[0] != hs_type || - (record_boundary && (ssl->in_hslen != ssl->in_msglen))) { + (require_record_boundary && (ssl->in_hslen != ssl->in_msglen))) { MBEDTLS_SSL_DEBUG_MSG(1, ("Receive unexpected handshake message.")); MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE, MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); From 6c557a70f6a2992282e5a342714be4c89130b8f4 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 15 Jun 2026 14:09:17 +0200 Subject: [PATCH 12/16] tls13: Rework mbedtls_ssl_tls13_fetch_handshake_msg() Signed-off-by: Ronald Cron --- library/ssl_tls13_generic.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 1465de1ec0..92dfcc3dfc 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -53,19 +53,23 @@ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, { int ret; - if ((ret = mbedtls_ssl_read_record(ssl, 0)) != 0) { + ret = mbedtls_ssl_read_record(ssl, 0); + if (ret != 0) { MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_read_record", ret); - goto cleanup; + goto error; } + ret = MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; if (ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE || - ssl->in_msg[0] != hs_type || - (require_record_boundary && (ssl->in_hslen != ssl->in_msglen))) { + ssl->in_msg[0] != hs_type) { MBEDTLS_SSL_DEBUG_MSG(1, ("Receive unexpected handshake message.")); - MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE, - MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); - ret = MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; - goto cleanup; + goto error; + } + + if (require_record_boundary && (ssl->in_hslen != ssl->in_msglen)) { + MBEDTLS_SSL_DEBUG_MSG(1, ("Handshake message %s end not aligned with a record boundary", + mbedtls_ssl_get_hs_msg_name(hs_type))); + goto error; } /* @@ -75,11 +79,15 @@ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, * uint24 length; * ... */ - *buf = ssl->in_msg + 4; + *buf = ssl->in_msg + 4; *buf_len = ssl->in_hslen - 4; + return 0; -cleanup: - +error: + if (ret == MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE) { + MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE, + MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); + } return ret; } From 1a58c59470788ac93e101b4c4869f0e46975c973 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 15 Jun 2026 14:18:34 +0200 Subject: [PATCH 13/16] Various improvements of tls13_(drop_early_data|record_boundary_alignment)() Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.function | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index f6eeeabe1f..1f6d542eb4 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6957,6 +6957,7 @@ exit: PSA_DONE(); } /* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_DEBUG_C:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_TEST_HAS_DEFAULT_EC_GROUP:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_SHA_256:MBEDTLS_X509_RSASSA_PSS_SUPPORT:PSA_WANT_ALG_RSA_PKCS1V15_SIGN */ void tls13_record_boundary_alignment(int endpoint, int handshake_message) { @@ -7025,7 +7026,7 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) #endif default: - TEST_ASSERT(0); + TEST_FAIL("Handshake message type not supported"); } if (endpoint == MBEDTLS_SSL_IS_CLIENT) { @@ -7073,11 +7074,10 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) * messages needed by the endpoint under test to proceed through its next * step. */ - if (peer_target_state != -1) { - TEST_EQUAL(mbedtls_test_move_handshake_to_state( - peer_ssl_ctx, ssl_ctx, peer_target_state), 0); - TEST_EQUAL(mbedtls_ssl_flush_output(peer_ssl_ctx), 0); - } + TEST_ASSERT(peer_target_state != -1); + TEST_EQUAL(mbedtls_test_move_handshake_to_state( + peer_ssl_ctx, ssl_ctx, peer_target_state), 0); + TEST_EQUAL(mbedtls_ssl_flush_output(peer_ssl_ctx), 0); TEST_EQUAL(ssl_ctx->state, target_state); if (encrypted) { From e08cea612f8931fef9af83c0ec0248f8c1aa31c3 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Mon, 15 Jun 2026 15:16:36 +0200 Subject: [PATCH 14/16] tls13_record_boundary_alignement: Append empty hs msg Append an empty handshake message to expand the record instead of just a zero byte. Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.function | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 1f6d542eb4..f48543d4c9 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6980,7 +6980,7 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) mbedtls_test_mock_socket *socket = NULL; mbedtls_ssl_context *ssl_ctx = NULL; mbedtls_ssl_context *peer_ssl_ctx = NULL; - unsigned char dummy_data[] = { 0x00 }; + unsigned char additional_empty_hs_msg[4] = { 0, 0, 0, 0 }; PSA_INIT(); @@ -6989,6 +6989,7 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) TEST_EQUAL(endpoint, MBEDTLS_SSL_IS_SERVER); target_state = MBEDTLS_SSL_CLIENT_HELLO; peer_target_state = MBEDTLS_SSL_SERVER_HELLO; + additional_empty_hs_msg[0] = MBEDTLS_SSL_HS_CERTIFICATE; encrypted = 0; break; @@ -6996,6 +6997,7 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) TEST_EQUAL(endpoint, MBEDTLS_SSL_IS_CLIENT); target_state = MBEDTLS_SSL_SERVER_HELLO; peer_target_state = MBEDTLS_SSL_ENCRYPTED_EXTENSIONS; + additional_empty_hs_msg[0] = MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS; encrypted = 0; break; @@ -7007,6 +7009,10 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) target_state = MBEDTLS_SSL_CLIENT_FINISHED; peer_target_state = MBEDTLS_SSL_FLUSH_BUFFERS; } + /* Finished messages are not followed by any handshake message, + * insert an empty Finished message at the end of the record. + */ + additional_empty_hs_msg[0] = MBEDTLS_SSL_HS_FINISHED; encrypted = 1; break; @@ -7016,6 +7022,7 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) TEST_EQUAL(endpoint, MBEDTLS_SSL_IS_SERVER); target_state = MBEDTLS_SSL_END_OF_EARLY_DATA; peer_target_state = MBEDTLS_SSL_CLIENT_CERTIFICATE; + additional_empty_hs_msg[0] = MBEDTLS_SSL_HS_FINISHED; encrypted = 1; client_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED; server_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED; @@ -7089,7 +7096,7 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) * `mbedtls_ssl_read_record()`. */ tweak_in_msglen_ctx.armed = 1; - tweak_in_msglen_ctx.nb_bytes = sizeof(dummy_data); + tweak_in_msglen_ctx.nb_bytes = sizeof(additional_empty_hs_msg); } else { /* * In case of the unencrypted ClientHello or ServerHello, add an @@ -7100,16 +7107,17 @@ void tls13_record_boundary_alignment(int endpoint, int handshake_message) */ unsigned char *input_buffer = socket->input->buffer; size_t record_size = MBEDTLS_GET_UINT16_BE(input_buffer, 3); - ret = mbedtls_test_ssl_buffer_put(socket->input, dummy_data, - sizeof(dummy_data)); - TEST_EQUAL(ret, (int) sizeof(dummy_data)); - MBEDTLS_PUT_UINT16_BE(record_size + sizeof(dummy_data), input_buffer, 3); + ret = mbedtls_test_ssl_buffer_put(socket->input, additional_empty_hs_msg, + sizeof(additional_empty_hs_msg)); + TEST_EQUAL(ret, (int) sizeof(additional_empty_hs_msg)); + MBEDTLS_PUT_UINT16_BE(record_size + sizeof(additional_empty_hs_msg), + input_buffer, 3); } ret = mbedtls_ssl_handshake_step(ssl_ctx); TEST_EQUAL(ret, MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); TEST_EQUAL(ssl_ctx->in_msglen, - ssl_ctx->in_hslen + sizeof(dummy_data)); + ssl_ctx->in_hslen + sizeof(additional_empty_hs_msg)); TEST_EQUAL(ssl_ctx->in_msgtype, MBEDTLS_SSL_MSG_HANDSHAKE); TEST_EQUAL(ssl_ctx->in_msg[0], handshake_message); From 74b9ab200820537b8f5cf8e55bd4f42d912a4c78 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 16 Jun 2026 18:41:56 +0200 Subject: [PATCH 15/16] tls13_fetch_handshake_msg(): Remove require_record_boundary parameter Deduce it from the handshake message type Signed-off-by: Ronald Cron --- library/ssl_misc.h | 1 - library/ssl_tls13_client.c | 8 ++++---- library/ssl_tls13_generic.c | 29 +++++++++++++++++++++++----- library/ssl_tls13_server.c | 4 ++-- tests/suites/test_suite_ssl.function | 2 +- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index 8d9323c6de..f0ca823f33 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -2028,7 +2028,6 @@ static inline int mbedtls_ssl_tls13_key_exchange_mode_with_ephemeral( MBEDTLS_CHECK_RETURN_CRITICAL int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, unsigned hs_type, - unsigned require_record_boundary, unsigned char **buf, size_t *buf_len); diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c index e4138fa2c7..ecdf507eab 100644 --- a/library/ssl_tls13_client.c +++ b/library/ssl_tls13_client.c @@ -2002,7 +2002,7 @@ static int ssl_tls13_process_server_hello(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> %s", __func__)); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_SERVER_HELLO, 1, &buf, &buf_len)); + ssl, MBEDTLS_SSL_HS_SERVER_HELLO, &buf, &buf_len)); ret = ssl_tls13_preprocess_server_hello(ssl, buf, buf + buf_len); if (ret < 0) { @@ -2195,7 +2195,7 @@ static int ssl_tls13_process_encrypted_extensions(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse encrypted extensions")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS, 0, + ssl, MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS, &buf, &buf_len)); /* Process the message contents */ @@ -2530,7 +2530,7 @@ static int ssl_tls13_process_certificate_request(mbedtls_ssl_context *ssl) size_t buf_len; MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CERTIFICATE_REQUEST, 0, + ssl, MBEDTLS_SSL_HS_CERTIFICATE_REQUEST, &buf, &buf_len)); MBEDTLS_SSL_PROC_CHK(ssl_tls13_parse_certificate_request( @@ -3025,7 +3025,7 @@ static int ssl_tls13_process_new_session_ticket(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse new session ticket")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_NEW_SESSION_TICKET, 0, + ssl, MBEDTLS_SSL_HS_NEW_SESSION_TICKET, &buf, &buf_len)); /* diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 92dfcc3dfc..0cdce76903 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -47,11 +47,30 @@ const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, unsigned hs_type, - unsigned require_record_boundary, unsigned char **buf, size_t *buf_len) { - int ret; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + int require_record_boundary; + + /* Require record-boundary alignment by default. The exceptions below are + * handshake messages that may legitimately be followed by additional + * handshake messages in the same record. Using the default behaviour for a + * message that should be exempt is an interoperability bug; exempting a + * message that should not be exempt may have security implications. + */ + switch (hs_type) { + case MBEDTLS_SSL_HS_NEW_SESSION_TICKET: + case MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS: + case MBEDTLS_SSL_HS_CERTIFICATE: + case MBEDTLS_SSL_HS_CERTIFICATE_REQUEST: + case MBEDTLS_SSL_HS_CERTIFICATE_VERIFY: + require_record_boundary = 0; + break; + + default: + require_record_boundary = 1; + } ret = mbedtls_ssl_read_record(ssl, 0); if (ret != 0) { @@ -349,7 +368,7 @@ int mbedtls_ssl_tls13_process_certificate_verify(mbedtls_ssl_context *ssl) MBEDTLS_SSL_PROC_CHK( mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CERTIFICATE_VERIFY, 0, &buf, &buf_len)); + ssl, MBEDTLS_SSL_HS_CERTIFICATE_VERIFY, &buf, &buf_len)); /* Need to calculate the hash of the transcript first * before reading the message since otherwise it gets @@ -701,7 +720,7 @@ int mbedtls_ssl_tls13_process_certificate(mbedtls_ssl_context *ssl) size_t buf_len; MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CERTIFICATE, 0, + ssl, MBEDTLS_SSL_HS_CERTIFICATE, &buf, &buf_len)); /* Parse the certificate chain sent by the peer. */ @@ -1120,7 +1139,7 @@ int mbedtls_ssl_tls13_process_finished_message(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse finished message")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_FINISHED, 1, &buf, &buf_len)); + ssl, MBEDTLS_SSL_HS_FINISHED, &buf, &buf_len)); /* Preprocessing step: Compute handshake digest */ MBEDTLS_SSL_PROC_CHK(ssl_tls13_preprocess_finished_message(ssl)); diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c index 0db142c50a..38f2fe9370 100644 --- a/library/ssl_tls13_server.c +++ b/library/ssl_tls13_server.c @@ -1936,7 +1936,7 @@ static int ssl_tls13_process_client_hello(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse client hello")); MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_CLIENT_HELLO, 1, + ssl, MBEDTLS_SSL_HS_CLIENT_HELLO, &buf, &buflen)); MBEDTLS_SSL_PROC_CHK_NEG(ssl_tls13_parse_client_hello(ssl, buf, @@ -3027,7 +3027,7 @@ static int ssl_tls13_process_end_of_early_data(mbedtls_ssl_context *ssl) size_t buf_len; MBEDTLS_SSL_PROC_CHK(mbedtls_ssl_tls13_fetch_handshake_msg( - ssl, MBEDTLS_SSL_HS_END_OF_EARLY_DATA, 1, + ssl, MBEDTLS_SSL_HS_END_OF_EARLY_DATA, &buf, &buf_len)); MBEDTLS_SSL_PROC_CHK(ssl_tls13_parse_end_of_early_data( diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index f48543d4c9..f655b1cf90 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -4169,7 +4169,7 @@ void tls13_server_certificate_msg_invalid_vector_len() ret = mbedtls_ssl_tls13_fetch_handshake_msg(&(client_ep.ssl), MBEDTLS_SSL_HS_CERTIFICATE, - 0, &buf, &buf_len); + &buf, &buf_len); TEST_EQUAL(ret, 0); end = buf + buf_len; From b188f399f926af64d06fdb842cbbceed2209f6a8 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 23 Jun 2026 10:34:33 +0200 Subject: [PATCH 16/16] Check ServerHello record boundary alignment only in TLS 1.3 case Signed-off-by: Ronald Cron --- library/ssl_tls13_client.c | 13 +++++++++++++ library/ssl_tls13_generic.c | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c index ecdf507eab..e8b6063f32 100644 --- a/library/ssl_tls13_client.c +++ b/library/ssl_tls13_client.c @@ -2016,6 +2016,19 @@ static int ssl_tls13_process_server_hello(mbedtls_ssl_context *ssl) goto cleanup; } + /* + * TLS 1.3 is negotiated. Check that ServerHello/HRR ends at a record + * boundary. For HRR, which does not trigger a key update, this checks + * that HRR terminates the server flight. + */ + if (ssl->in_hslen != ssl->in_msglen) { + MBEDTLS_SSL_DEBUG_MSG(1, ("ServerHello end not aligned with a record boundary")); + ret = MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; + MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE, + MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE); + goto cleanup; + } + MBEDTLS_SSL_PROC_CHK(ssl_tls13_parse_server_hello(ssl, buf, buf + buf_len, is_hrr)); diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 0cdce76903..abb8b4f3e4 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -65,10 +65,20 @@ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl, case MBEDTLS_SSL_HS_CERTIFICATE: case MBEDTLS_SSL_HS_CERTIFICATE_REQUEST: case MBEDTLS_SSL_HS_CERTIFICATE_VERIFY: + /* + * For TLS 1.3, ServerHello (including HelloRetryRequest) must end at a + * record boundary, but not for TLS 1.2. At this point the ServerHello + * has not yet been processed, so we do not know whether it negotiates + * TLS 1.3 or TLS 1.2. Defer the boundary check until the protocol + * version is known to be TLS 1.3. + */ + case MBEDTLS_SSL_HS_SERVER_HELLO: require_record_boundary = 0; break; default: + /* ClientHello, EndOfEarlyData, Finished, and KeyUpdate. + */ require_record_boundary = 1; }