Merge pull request #10576 from ronald-cron-arm/dtls-client-hello-defragmentation

Add support for (D)TLS 1.2 client hello defragmentation
This commit is contained in:
Ronald Cron
2026-03-10 14:46:07 +00:00
committed by GitHub
11 changed files with 530 additions and 241 deletions
+49 -6
View File
@@ -2967,6 +2967,49 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl)
return MBEDTLS_ERR_SSL_INVALID_RECORD;
}
if (ssl->in_msg[0] == MBEDTLS_SSL_HS_CLIENT_HELLO &&
ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
if (ssl->state == MBEDTLS_SSL_CLIENT_HELLO
#if defined(MBEDTLS_SSL_RENEGOTIATION)
&& ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE
#endif
) {
/*
* When establishing the connection, the client may go through
* a series of ClientHello and HelloVerifyRequest requests and
* responses. The server intentionally does not keep trace of
* these initial round trips: minimum allocated ressources as
* long as the reachability of the client has not been
* confirmed. When receiving the "first ClientHello" from
* server perspective, we may thus need to adapt the next
* expected `message_seq` for the incoming and outgoing
* handshake messages.
*/
ssl->handshake->in_msg_seq = recv_msg_seq;
ssl->handshake->out_msg_seq = recv_msg_seq;
/* Epoch should be 0 for initial handshakes */
if (ssl->in_ctr[0] != 0 || ssl->in_ctr[1] != 0) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER;
}
memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2,
sizeof(ssl->cur_out_ctr) - 2);
} else if (mbedtls_ssl_is_handshake_over(ssl) == 1) {
/* In case of a post-handshake ClientHello that initiates a
* renegotiation check that the handshake message sequence
* number is zero.
*/
if (recv_msg_seq != 0) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message_seq: "
"%u (expected 0)",
recv_msg_seq));
return MBEDTLS_ERR_SSL_DECODE_ERROR;
}
}
}
if (ssl->handshake != NULL &&
((mbedtls_ssl_is_handshake_over(ssl) == 0 &&
recv_msg_seq != ssl->handshake->in_msg_seq) ||
@@ -4843,14 +4886,9 @@ static int ssl_get_next_record(mbedtls_ssl_context *ssl)
/* The record content type may change during decryption,
* so re-read it. */
ssl->in_msgtype = rec.type;
/* Also update the input buffer, because unfortunately
* the server-side ssl_parse_client_hello() reparses the
* record header when receiving a ClientHello initiating
* a renegotiation. */
ssl->in_hdr[0] = rec.type;
ssl->in_msg = rec.buf + rec.data_offset;
ssl->in_msglen = rec.data_len;
MBEDTLS_PUT_UINT16_BE(rec.data_len, ssl->in_len, 0);
return 0;
}
@@ -5504,6 +5542,11 @@ static int ssl_tls12_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
ssl->renego_status = MBEDTLS_SSL_RENEGOTIATION_PENDING;
}
#endif
/* Keep the ClientHello message for ssl_parse_client_hello() */
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
ssl->keep_current_message = 1;
}
ret = mbedtls_ssl_start_renegotiation(ssl);
if (ret != MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO &&
ret != 0) {
-7
View File
@@ -2892,13 +2892,6 @@ size_t mbedtls_ssl_get_output_max_frag_len(const mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_SSL_PROTO_DTLS)
size_t mbedtls_ssl_get_current_mtu(const mbedtls_ssl_context *ssl)
{
/* Return unlimited mtu for client hello messages to avoid fragmentation. */
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT &&
(ssl->state == MBEDTLS_SSL_CLIENT_HELLO ||
ssl->state == MBEDTLS_SSL_SERVER_HELLO)) {
return 0;
}
if (ssl->handshake == NULL || ssl->handshake->mtu == 0) {
return ssl->mtu;
}
+26 -170
View File
@@ -869,131 +869,35 @@ static int ssl_parse_client_hello(mbedtls_ssl_context *ssl)
MBEDTLS_SSL_DEBUG_MSG(2, ("=> parse client hello"));
int renegotiating;
#if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY)
read_record_header:
#endif
/*
* If renegotiating, then the input was read with mbedtls_ssl_read_record(),
* otherwise read it ourselves manually in order to support SSLv2
* ClientHello, which doesn't use the same record layer format.
* Otherwise in a scenario of TLS 1.3/TLS 1.2 version negotiation, the
* ClientHello has been already fully fetched by the TLS 1.3 code and the
* flag ssl->keep_current_message is raised.
* Fetch the expected ClientHello handshake message. Do not ask
* mbedtls_ssl_read_record() to update the handshake digest, because the
* ClientHello may already have been read in ssl_tls13_process_client_hello()
* or as a post-handshake message (renegotiation). In those cases we need
* to update the digest ourselves, and it is simpler to do so
* unconditionally than to track whether it is needed.
*/
renegotiating = 0;
#if defined(MBEDTLS_SSL_RENEGOTIATION)
renegotiating = (ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE);
#endif
if (!renegotiating && !ssl->keep_current_message) {
if ((ret = mbedtls_ssl_fetch_input(ssl, 5)) != 0) {
/* No alert on a read error. */
MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_fetch_input", ret);
return ret;
}
if ((ret = mbedtls_ssl_read_record(ssl, 0)) != 0) {
MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_read_record ", ret);
return ret;
}
buf = ssl->in_hdr;
MBEDTLS_SSL_DEBUG_BUF(4, "record header", buf, mbedtls_ssl_in_hdr_len(ssl));
/*
* TLS Client Hello
* Update the handshake checksum.
*
* Record layer:
* 0 . 0 message type
* 1 . 2 protocol version
* 3 . 11 DTLS: epoch + record sequence number
* 3 . 4 message length
* Note that the checksum must be updated before parsing the extensions
* because ssl_parse_session_ticket_ext() may decrypt the ticket in place
* and therefore modify the ClientHello message. This occurs when using
* the Mbed TLS ssl_ticket.c implementation.
*/
MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, message type: %d",
buf[0]));
if (buf[0] != MBEDTLS_SSL_MSG_HANDSHAKE) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
}
MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, message len.: %d",
MBEDTLS_GET_UINT16_BE(ssl->in_len, 0)));
MBEDTLS_SSL_DEBUG_MSG(3, ("client hello, protocol version: [%d:%d]",
buf[1], buf[2]));
/* For DTLS if this is the initial handshake, remember the client sequence
* number to use it in our next message (RFC 6347 4.2.1) */
#if defined(MBEDTLS_SSL_PROTO_DTLS)
if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM
#if defined(MBEDTLS_SSL_RENEGOTIATION)
&& ssl->renego_status == MBEDTLS_SSL_INITIAL_HANDSHAKE
#endif
) {
/* Epoch should be 0 for initial handshakes */
if (ssl->in_ctr[0] != 0 || ssl->in_ctr[1] != 0) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER;
}
memcpy(&ssl->cur_out_ctr[2], ssl->in_ctr + 2,
sizeof(ssl->cur_out_ctr) - 2);
#if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY)
if (mbedtls_ssl_dtls_replay_check(ssl) != 0) {
MBEDTLS_SSL_DEBUG_MSG(1, ("replayed record, discarding"));
ssl->next_record_offset = 0;
ssl->in_left = 0;
goto read_record_header;
}
/* No MAC to check yet, so we can update right now */
mbedtls_ssl_dtls_replay_update(ssl);
#endif
}
#endif /* MBEDTLS_SSL_PROTO_DTLS */
msg_len = MBEDTLS_GET_UINT16_BE(ssl->in_len, 0);
#if defined(MBEDTLS_SSL_RENEGOTIATION)
if (ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE) {
/* Set by mbedtls_ssl_read_record() */
msg_len = ssl->in_hslen;
} else
#endif
{
if (ssl->keep_current_message) {
ssl->keep_current_message = 0;
} else {
if (msg_len > MBEDTLS_SSL_IN_CONTENT_LEN) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER;
}
if ((ret = mbedtls_ssl_fetch_input(ssl,
mbedtls_ssl_in_hdr_len(ssl) + msg_len)) != 0) {
MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_fetch_input", ret);
return ret;
}
/* Done reading this record, get ready for the next one */
#if defined(MBEDTLS_SSL_PROTO_DTLS)
if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
ssl->next_record_offset = msg_len + mbedtls_ssl_in_hdr_len(ssl);
} else
#endif
ssl->in_left = 0;
}
ret = mbedtls_ssl_update_handshake_status(ssl);
if (0 != ret) {
MBEDTLS_SSL_DEBUG_RET(1, ("mbedtls_ssl_update_handshake_status"), ret);
return ret;
}
buf = ssl->in_msg;
MBEDTLS_SSL_DEBUG_BUF(4, "record contents", buf, msg_len);
ret = ssl->handshake->update_checksum(ssl, buf, msg_len);
if (0 != ret) {
MBEDTLS_SSL_DEBUG_RET(1, ("update_checksum"), ret);
return ret;
}
msg_len = ssl->in_hslen;
/*
* Handshake layer:
@@ -1003,64 +907,12 @@ read_record_header:
* 6 . 8 DTLS only: fragment offset
* 9 . 11 DTLS only: fragment length
*/
if (msg_len < mbedtls_ssl_hs_hdr_len(ssl)) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
return MBEDTLS_ERR_SSL_DECODE_ERROR;
}
MBEDTLS_SSL_DEBUG_MSG(3, ("client hello v3, handshake type: %d", buf[0]));
if (buf[0] != MBEDTLS_SSL_HS_CLIENT_HELLO) {
if ((ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE) ||
(buf[0] != MBEDTLS_SSL_HS_CLIENT_HELLO)) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message"));
return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
}
#if defined(MBEDTLS_SSL_PROTO_DTLS)
if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) {
/*
* Copy the client's handshake message_seq on initial handshakes,
* check sequence number on renego.
*/
#if defined(MBEDTLS_SSL_RENEGOTIATION)
if (ssl->renego_status == MBEDTLS_SSL_RENEGOTIATION_IN_PROGRESS) {
/* This couldn't be done in ssl_prepare_handshake_record() */
unsigned int cli_msg_seq = (unsigned int) MBEDTLS_GET_UINT16_BE(ssl->in_msg, 4);
if (cli_msg_seq != ssl->handshake->in_msg_seq) {
MBEDTLS_SSL_DEBUG_MSG(1, ("bad client hello message_seq: "
"%u (expected %u)", cli_msg_seq,
ssl->handshake->in_msg_seq));
return MBEDTLS_ERR_SSL_DECODE_ERROR;
}
ssl->handshake->in_msg_seq++;
} else
#endif
{
unsigned int cli_msg_seq = (unsigned int) MBEDTLS_GET_UINT16_BE(ssl->in_msg, 4);
ssl->handshake->out_msg_seq = cli_msg_seq;
ssl->handshake->in_msg_seq = cli_msg_seq + 1;
}
{
/*
* For now we don't support fragmentation, so make sure
* fragment_offset == 0 and fragment_length == length
*/
size_t fragment_offset, fragment_length, length;
fragment_offset = MBEDTLS_GET_UINT24_BE(ssl->in_msg, 6);
fragment_length = MBEDTLS_GET_UINT24_BE(ssl->in_msg, 9);
length = MBEDTLS_GET_UINT24_BE(ssl->in_msg, 1);
MBEDTLS_SSL_DEBUG_MSG(
4, ("fragment_offset=%u fragment_length=%u length=%u",
(unsigned) fragment_offset, (unsigned) fragment_length,
(unsigned) length));
if (fragment_offset != 0 || length != fragment_length) {
MBEDTLS_SSL_DEBUG_MSG(1, ("ClientHello fragmentation not supported"));
return MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE;
}
}
}
#endif /* MBEDTLS_SSL_PROTO_DTLS */
buf += mbedtls_ssl_hs_hdr_len(ssl);
msg_len -= mbedtls_ssl_hs_hdr_len(ssl);
@@ -1390,7 +1242,11 @@ read_record_header:
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
case MBEDTLS_TLS_EXT_SESSION_TICKET:
MBEDTLS_SSL_DEBUG_MSG(3, ("found session ticket extension"));
/*
* If the Mbed TLS ssl_ticket.c implementation is used, the
* ticket is decrypted in place. This modifies the ClientHello
* message in the input buffer.
*/
ret = ssl_parse_session_ticket_ext(ssl, ext + 4, ext_size);
if (ret != 0) {
return ret;