Merge pull request #1652 from ronald-cron-arm/hsm-do-not-span-key-change

Hsm do not span key change
This commit is contained in:
Gilles Peskine 2026-06-24 19:22:01 +02:00 committed by GitHub
commit 9baa16385d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 449 additions and 11 deletions

View File

@ -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.

View File

@ -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));

View File

@ -50,20 +50,55 @@ int mbedtls_ssl_tls13_fetch_handshake_msg(mbedtls_ssl_context *ssl,
unsigned char **buf,
size_t *buf_len)
{
int ret;
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
int require_record_boundary;
if ((ret = mbedtls_ssl_read_record(ssl, 0)) != 0) {
MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_read_record", ret);
goto cleanup;
/* 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:
/*
* 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;
}
ret = mbedtls_ssl_read_record(ssl, 0);
if (ret != 0) {
MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_read_record", ret);
goto error;
}
ret = MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
if (ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE ||
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;
}
/*
@ -73,11 +108,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;
}

View File

@ -3586,3 +3586,25 @@ 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
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 alignment enforcement
tls13_record_boundary_alignment:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_HS_SERVER_HELLO
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 alignment enforcement
tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_FINISHED
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

View File

@ -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
@ -6778,3 +6823,315 @@ 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. */
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,
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 */
ret = mbedtls_test_move_handshake_to_state(&(server_ep.ssl),
&(client_ep.ssl),
MBEDTLS_SSL_HANDSHAKE_OVER);
/*
* 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);
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 */
/* 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;
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);
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;
unsigned char additional_empty_hs_msg[4] = { 0, 0, 0, 0 };
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;
additional_empty_hs_msg[0] = MBEDTLS_SSL_HS_CERTIFICATE;
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;
additional_empty_hs_msg[0] = MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS;
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;
} else {
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;
#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;
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;
ret = mbedtls_test_get_tls13_ticket(&client_options, &server_options,
&saved_session);
TEST_EQUAL(ret, 0);
break;
#endif
default:
TEST_FAIL("Handshake message type not supported");
}
if (endpoint == MBEDTLS_SSL_IS_CLIENT) {
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,
&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);
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.
*/
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) {
/*
* 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(additional_empty_hs_msg);
} 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, 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(additional_empty_hs_msg));
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);
mbedtls_ssl_session_free(&saved_session);
PSA_DONE();
}
/* END_CASE */