Merge pull request from ronald-cron-arm/tls13-srv-max-early-data-size

TLS 1.3: Enforce max_early_data_size on server
This commit is contained in:
Ronald Cron 2024-03-09 00:16:07 +00:00 committed by GitHub
commit 7e1f9f290f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 420 additions and 11 deletions

@ -1859,7 +1859,8 @@ struct mbedtls_ssl_context {
* within a single datagram. */
#endif /* MBEDTLS_SSL_PROTO_DTLS */
#if defined(MBEDTLS_SSL_EARLY_DATA) && defined(MBEDTLS_SSL_SRV_C)
#if defined(MBEDTLS_SSL_EARLY_DATA)
#if defined(MBEDTLS_SSL_SRV_C)
/*
* One of:
* MBEDTLS_SSL_EARLY_DATA_NO_DISCARD
@ -1868,6 +1869,8 @@ struct mbedtls_ssl_context {
*/
uint8_t MBEDTLS_PRIVATE(discard_early_data_record);
#endif
uint32_t MBEDTLS_PRIVATE(total_early_data_size); /*!< Number of received/written early data bytes */
#endif /* MBEDTLS_SSL_EARLY_DATA */
/*
* Record layer (outgoing data)

@ -2150,6 +2150,9 @@ int mbedtls_ssl_tls13_write_early_data_ext(mbedtls_ssl_context *ssl,
unsigned char *buf,
const unsigned char *end,
size_t *out_len);
int mbedtls_ssl_tls13_check_early_data_len(mbedtls_ssl_context *ssl,
size_t early_data_len);
#endif /* MBEDTLS_SSL_EARLY_DATA */
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */

@ -4005,7 +4005,11 @@ static int ssl_prepare_record_content(mbedtls_ssl_context *ssl,
MBEDTLS_SSL_EARLY_DATA_TRY_TO_DEPROTECT_AND_DISCARD)) {
MBEDTLS_SSL_DEBUG_MSG(
3, ("EarlyData: deprotect and discard app data records."));
/* TODO: Add max_early_data_size check here, see issue 6347 */
ret = mbedtls_ssl_tls13_check_early_data_len(ssl, rec->data_len);
if (ret != 0) {
return ret;
}
ret = MBEDTLS_ERR_SSL_CONTINUE_PROCESSING;
}
#endif /* MBEDTLS_SSL_EARLY_DATA && MBEDTLS_SSL_SRV_C */
@ -4129,9 +4133,15 @@ static int ssl_prepare_record_content(mbedtls_ssl_context *ssl,
*/
if (ssl->discard_early_data_record == MBEDTLS_SSL_EARLY_DATA_DISCARD) {
if (rec->type == MBEDTLS_SSL_MSG_APPLICATION_DATA) {
ret = mbedtls_ssl_tls13_check_early_data_len(ssl, rec->data_len);
if (ret != 0) {
return ret;
}
MBEDTLS_SSL_DEBUG_MSG(
3, ("EarlyData: Ignore application message before 2nd ClientHello"));
/* TODO: Add max_early_data_size check here, see issue 6347 */
return MBEDTLS_ERR_SSL_CONTINUE_PROCESSING;
} else if (rec->type == MBEDTLS_SSL_MSG_HANDSHAKE) {
ssl->discard_early_data_record = MBEDTLS_SSL_EARLY_DATA_NO_DISCARD;

@ -1101,6 +1101,7 @@ static int ssl_handshake_init(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_SSL_SRV_C)
ssl->discard_early_data_record = MBEDTLS_SSL_EARLY_DATA_NO_DISCARD;
#endif
ssl->total_early_data_size = 0;
#endif /* MBEDTLS_SSL_EARLY_DATA */
/* Initialize structures */

@ -1454,6 +1454,54 @@ int mbedtls_ssl_tls13_write_early_data_ext(mbedtls_ssl_context *ssl,
return 0;
}
#if defined(MBEDTLS_SSL_SRV_C)
int mbedtls_ssl_tls13_check_early_data_len(mbedtls_ssl_context *ssl,
size_t early_data_len)
{
/*
* This function should be called only while an handshake is in progress
* and thus a session under negotiation. Add a sanity check to detect a
* misuse.
*/
if (ssl->session_negotiate == NULL) {
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
}
/* RFC 8446 section 4.6.1
*
* A server receiving more than max_early_data_size bytes of 0-RTT data
* SHOULD terminate the connection with an "unexpected_message" alert.
* Note that if it is still possible to send early_data_len bytes of early
* data, it means that early_data_len is smaller than max_early_data_size
* (type uint32_t) and can fit in an uint32_t. We use this further
* down.
*/
if (early_data_len >
(ssl->session_negotiate->max_early_data_size -
ssl->total_early_data_size)) {
MBEDTLS_SSL_DEBUG_MSG(
2, ("EarlyData: Too much early data received, %u + %" MBEDTLS_PRINTF_SIZET " > %u",
ssl->total_early_data_size, early_data_len,
ssl->session_negotiate->max_early_data_size));
MBEDTLS_SSL_PEND_FATAL_ALERT(
MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE,
MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE);
return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
}
/*
* early_data_len has been checked to be less than max_early_data_size
* that is uint32_t. Its cast to an uint32_t below is thus safe. We need
* the cast to appease some compilers.
*/
ssl->total_early_data_size += (uint32_t) early_data_len;
return 0;
}
#endif /* MBEDTLS_SSL_SRV_C */
#endif /* MBEDTLS_SSL_EARLY_DATA */
/* Reset SSL context and update hash for handling HRR.

@ -2906,17 +2906,14 @@ static int ssl_tls13_end_of_early_data_coordinate(mbedtls_ssl_context *ssl)
}
if (ssl->in_msgtype == MBEDTLS_SSL_MSG_APPLICATION_DATA) {
MBEDTLS_SSL_DEBUG_MSG(3, ("Received early data"));
/* RFC 8446 section 4.6.1
*
* A server receiving more than max_early_data_size bytes of 0-RTT data
* SHOULD terminate the connection with an "unexpected_message" alert.
*
* TODO: Add received data size check here.
*/
if (ssl->in_offt == NULL) {
MBEDTLS_SSL_DEBUG_MSG(3, ("Received early data"));
/* Set the reading pointer */
ssl->in_offt = ssl->in_msg;
ret = mbedtls_ssl_tls13_check_early_data_len(ssl, ssl->in_msglen);
if (ret != 0) {
return ret;
}
}
return SSL_GOT_EARLY_DATA;
}
@ -3134,6 +3131,7 @@ static int ssl_tls13_prepare_new_session_ticket(mbedtls_ssl_context *ssl,
ssl->conf->max_early_data_size > 0) {
mbedtls_ssl_tls13_session_set_ticket_flags(
session, MBEDTLS_SSL_TLS1_3_TICKET_ALLOW_EARLY_DATA);
session->max_early_data_size = ssl->conf->max_early_data_size;
}
#endif /* MBEDTLS_SSL_EARLY_DATA */

@ -114,6 +114,7 @@ typedef struct mbedtls_test_handshake_test_options {
void (*cli_log_fun)(void *, int, const char *, int, const char *);
int resize_buffers;
int early_data;
int max_early_data_size;
#if defined(MBEDTLS_SSL_CACHE_C)
mbedtls_ssl_cache_context *cache;
#endif

@ -67,6 +67,7 @@ void mbedtls_test_init_handshake_options(
opts->legacy_renegotiation = MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION;
opts->resize_buffers = 1;
opts->early_data = MBEDTLS_SSL_EARLY_DATA_DISABLED;
opts->max_early_data_size = -1;
#if defined(MBEDTLS_SSL_CACHE_C)
TEST_CALLOC(opts->cache, 1);
mbedtls_ssl_cache_init(opts->cache);
@ -825,6 +826,13 @@ int mbedtls_test_ssl_endpoint_init(
#if defined(MBEDTLS_SSL_EARLY_DATA)
mbedtls_ssl_conf_early_data(&(ep->conf), options->early_data);
#if defined(MBEDTLS_SSL_SRV_C)
if (endpoint_type == MBEDTLS_SSL_IS_SERVER &&
(options->max_early_data_size >= 0)) {
mbedtls_ssl_conf_max_early_data_size(&(ep->conf),
options->max_early_data_size);
}
#endif
#endif
#if defined(MBEDTLS_SSL_CACHE_C) && defined(MBEDTLS_SSL_SRV_C)

@ -3309,3 +3309,39 @@ tls13_write_early_data:TEST_EARLY_DATA_SERVER_REJECTS
TLS 1.3 write early data, hello retry request
tls13_write_early_data:TEST_EARLY_DATA_HRR
TLS 1.3 srv, max early data size, dflt, wsz=96
tls13_srv_max_early_data_size:TEST_EARLY_DATA_ACCEPTED:-1:96
TLS 1.3 srv, max early data size, dflt, wsz=128
tls13_srv_max_early_data_size:TEST_EARLY_DATA_ACCEPTED:-1:128
TLS 1.3 srv, max early data size, 3, wsz=2
tls13_srv_max_early_data_size:TEST_EARLY_DATA_ACCEPTED:3:2
TLS 1.3 srv, max early data size, 3, wsz=3
tls13_srv_max_early_data_size:TEST_EARLY_DATA_ACCEPTED:3:3
TLS 1.3 srv, max early data size, 98, wsz=23
tls13_srv_max_early_data_size:TEST_EARLY_DATA_ACCEPTED:98:23
TLS 1.3 srv, max early data size, 98, wsz=49
tls13_srv_max_early_data_size:TEST_EARLY_DATA_ACCEPTED:98:49
TLS 1.3 srv, max early data size, server rejects, dflt, wsz=128
tls13_srv_max_early_data_size:TEST_EARLY_DATA_SERVER_REJECTS:-1:128
TLS 1.3 srv, max early data size, server rejects, 3, wsz=3
tls13_srv_max_early_data_size:TEST_EARLY_DATA_SERVER_REJECTS:3:3
TLS 1.3 srv, max early data size, server rejects, 98, wsz=49
tls13_srv_max_early_data_size:TEST_EARLY_DATA_SERVER_REJECTS:98:49
TLS 1.3 srv, max early data size, HRR, dflt, wsz=128
tls13_srv_max_early_data_size:TEST_EARLY_DATA_HRR:-1:128
TLS 1.3 srv, max early data size, HRR, 3, wsz=3
tls13_srv_max_early_data_size:TEST_EARLY_DATA_HRR:3:3
TLS 1.3 srv, max early data size, HRR, 98, wsz=49
tls13_srv_max_early_data_size:TEST_EARLY_DATA_HRR:97:0

@ -18,6 +18,47 @@
#define TEST_EARLY_DATA_SERVER_REJECTS 2
#define TEST_EARLY_DATA_HRR 3
#if (!defined(MBEDTLS_SSL_PROTO_TLS1_2)) && \
defined(MBEDTLS_SSL_EARLY_DATA) && defined(MBEDTLS_SSL_CLI_C) && \
defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_DEBUG_C) && \
defined(MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE) && \
defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED) && \
defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED) && \
defined(MBEDTLS_MD_CAN_SHA256) && \
defined(MBEDTLS_ECP_HAVE_SECP256R1) && defined(MBEDTLS_ECP_HAVE_SECP384R1) && \
defined(MBEDTLS_PK_CAN_ECDSA_VERIFY) && defined(MBEDTLS_SSL_SESSION_TICKETS)
/*
* Test function to write early data for negative tests where
* mbedtls_ssl_write_early_data() cannot be used.
*/
static int write_early_data(mbedtls_ssl_context *ssl,
unsigned char *buf, size_t len)
{
int ret = mbedtls_ssl_get_max_out_record_payload(ssl);
TEST_ASSERT(ret > 0);
TEST_LE_U(len, (size_t) ret);
ret = mbedtls_ssl_flush_output(ssl);
TEST_EQUAL(ret, 0);
TEST_EQUAL(ssl->out_left, 0);
ssl->out_msglen = len;
ssl->out_msgtype = MBEDTLS_SSL_MSG_APPLICATION_DATA;
if (len > 0) {
memcpy(ssl->out_msg, buf, len);
}
ret = mbedtls_ssl_write_record(ssl, 1);
TEST_EQUAL(ret, 0);
ret = len;
exit:
return ret;
}
#endif
/* END_HEADER */
/* BEGIN_DEPENDENCIES
@ -4168,6 +4209,10 @@ void tls13_write_early_data(int scenario)
break;
case TEST_EARLY_DATA_HRR:
/*
* Remove server support for the group negotiated in
* mbedtls_test_get_tls13_ticket() forcing a HelloRetryRequest.
*/
server_options.group_list = group_list + 1;
break;
@ -4457,3 +4502,259 @@ exit:
PSA_DONE();
}
/* END_CASE */
/*
* The !MBEDTLS_SSL_PROTO_TLS1_2 dependency of tls13_early_data() below is
* a temporary workaround to not run the test in Windows-2013 where there is
* an issue with mbedtls_vsnprintf().
*/
/* BEGIN_CASE depends_on:!MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_EARLY_DATA:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_SRV_C:MBEDTLS_DEBUG_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_MD_CAN_SHA256:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PK_CAN_ECDSA_VERIFY:MBEDTLS_SSL_SESSION_TICKETS */
void tls13_srv_max_early_data_size(int scenario, int max_early_data_size_arg, int write_size_arg)
{
int ret = -1;
mbedtls_test_ssl_endpoint client_ep, server_ep;
mbedtls_test_handshake_test_options client_options;
mbedtls_test_handshake_test_options server_options;
mbedtls_ssl_session saved_session;
mbedtls_test_ssl_log_pattern server_pattern = { NULL, 0 };
uint16_t group_list[3] = {
MBEDTLS_SSL_IANA_TLS_GROUP_SECP256R1,
MBEDTLS_SSL_IANA_TLS_GROUP_SECP384R1,
MBEDTLS_SSL_IANA_TLS_GROUP_NONE
};
char pattern[128];
unsigned char *buf_write = NULL;
uint32_t write_size = (uint32_t) write_size_arg;
unsigned char *buf_read = NULL;
uint32_t read_size;
uint32_t expanded_early_data_chunk_size = 0;
uint32_t written_early_data_size = 0;
uint32_t max_early_data_size;
mbedtls_platform_zeroize(&client_ep, sizeof(client_ep));
mbedtls_platform_zeroize(&server_ep, sizeof(server_ep));
mbedtls_test_init_handshake_options(&client_options);
mbedtls_test_init_handshake_options(&server_options);
mbedtls_ssl_session_init(&saved_session);
PSA_INIT();
TEST_CALLOC(buf_write, write_size);
/*
* Allocate a smaller buffer for early data reading to exercise the reading
* of data in one record in multiple calls.
*/
read_size = (write_size / 2) + 1;
TEST_CALLOC(buf_read, read_size);
/*
* Run first handshake to get a ticket from the server.
*/
client_options.pk_alg = MBEDTLS_PK_ECDSA;
client_options.group_list = group_list;
client_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED;
server_options.pk_alg = MBEDTLS_PK_ECDSA;
server_options.group_list = group_list;
server_options.early_data = MBEDTLS_SSL_EARLY_DATA_ENABLED;
server_options.max_early_data_size = max_early_data_size_arg;
ret = mbedtls_test_get_tls13_ticket(&client_options, &server_options,
&saved_session);
TEST_EQUAL(ret, 0);
/*
* Prepare for handshake with the ticket.
*/
server_options.srv_log_fun = mbedtls_test_ssl_log_analyzer;
server_options.srv_log_obj = &server_pattern;
server_pattern.pattern = pattern;
switch (scenario) {
case TEST_EARLY_DATA_ACCEPTED:
break;
case TEST_EARLY_DATA_SERVER_REJECTS:
server_options.early_data = MBEDTLS_SSL_EARLY_DATA_DISABLED;
ret = mbedtls_snprintf(pattern, sizeof(pattern),
"EarlyData: deprotect and discard app data records.");
TEST_ASSERT(ret < (int) sizeof(pattern));
mbedtls_debug_set_threshold(3);
break;
case TEST_EARLY_DATA_HRR:
/*
* Remove server support for the group negotiated in
* mbedtls_test_get_tls13_ticket() forcing an HelloRetryRequest.
*/
server_options.group_list = group_list + 1;
ret = mbedtls_snprintf(
pattern, sizeof(pattern),
"EarlyData: Ignore application message before 2nd ClientHello");
TEST_ASSERT(ret < (int) sizeof(pattern));
mbedtls_debug_set_threshold(3);
break;
default:
TEST_FAIL("Unknown scenario.");
}
ret = mbedtls_test_ssl_endpoint_init(&client_ep, MBEDTLS_SSL_IS_CLIENT,
&client_options, NULL, NULL, NULL);
TEST_EQUAL(ret, 0);
ret = mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER,
&server_options, NULL, NULL, NULL);
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);
max_early_data_size = saved_session.max_early_data_size;
ret = mbedtls_ssl_set_session(&(client_ep.ssl), &saved_session);
TEST_EQUAL(ret, 0);
/*
* Start an handshake based on the ticket up to the point where early data
* can be sent from client side. Then send in a loop as much early data as
* possible without going over the maximum permitted size for the ticket.
* Finally, do a last writting to go past that maximum permitted size and
* check that we detect it.
*/
TEST_EQUAL(mbedtls_test_move_handshake_to_state(
&(client_ep.ssl), &(server_ep.ssl),
MBEDTLS_SSL_SERVER_HELLO), 0);
TEST_ASSERT(client_ep.ssl.early_data_status !=
MBEDTLS_SSL_EARLY_DATA_STATUS_NOT_SENT);
ret = mbedtls_ssl_handshake(&(server_ep.ssl));
TEST_EQUAL(ret, MBEDTLS_ERR_SSL_WANT_READ);
/*
* Write and if possible read as much as possible chunks of write_size
* bytes data without getting over the max_early_data_size limit.
*/
do {
uint32_t read_early_data_size = 0;
/*
* The contents of the early data are not very important, write a
* pattern that varies byte-by-byte and is different for every chunk of
* early data.
*/
if ((written_early_data_size + write_size) > max_early_data_size) {
break;
}
/*
* If the server rejected early data, base the determination of when
* to stop the loop on the expanded size (padding and encryption
* expansion) of early data on server side and the number of early data
* received so far by the server (multiple of the expanded size).
*/
if ((expanded_early_data_chunk_size != 0) &&
((server_ep.ssl.total_early_data_size +
expanded_early_data_chunk_size) > max_early_data_size)) {
break;
}
for (size_t i = 0; i < write_size; i++) {
buf_write[i] = (unsigned char) (written_early_data_size + i);
}
ret = write_early_data(&(client_ep.ssl), buf_write, write_size);
TEST_EQUAL(ret, write_size);
written_early_data_size += write_size;
switch (scenario) {
case TEST_EARLY_DATA_ACCEPTED:
while (read_early_data_size < write_size) {
ret = mbedtls_ssl_handshake(&(server_ep.ssl));
TEST_EQUAL(ret, MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA);
ret = mbedtls_ssl_read_early_data(&(server_ep.ssl),
buf_read, read_size);
TEST_ASSERT(ret > 0);
TEST_MEMORY_COMPARE(buf_read, ret,
buf_write + read_early_data_size, ret);
read_early_data_size += ret;
TEST_EQUAL(server_ep.ssl.total_early_data_size,
written_early_data_size);
}
break;
case TEST_EARLY_DATA_SERVER_REJECTS: /* Intentional fallthrough */
case TEST_EARLY_DATA_HRR:
ret = mbedtls_ssl_handshake(&(server_ep.ssl));
/*
* In this write loop we try to always stay below the
* max_early_data_size limit but if max_early_data_size is very
* small we may exceed the max_early_data_size limit on the
* first write. In TEST_EARLY_DATA_SERVER_REJECTS/
* TEST_EARLY_DATA_HRR scenario, this is for sure the case if
* max_early_data_size is smaller than the smallest possible
* inner content/protected record. Take into account this
* possibility here but only for max_early_data_size values
* that are close to write_size. Below, '1' is for the inner
* type byte and '16' is to take into account some AEAD
* expansion (tag, ...).
*/
if (ret == MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE) {
if (scenario == TEST_EARLY_DATA_SERVER_REJECTS) {
TEST_LE_U(max_early_data_size,
write_size + 1 +
MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY);
} else {
TEST_LE_U(max_early_data_size,
write_size + 1 + 16 +
MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY);
}
goto exit;
}
TEST_ASSERT(ret == MBEDTLS_ERR_SSL_WANT_READ);
TEST_EQUAL(server_pattern.counter, 1);
server_pattern.counter = 0;
if (expanded_early_data_chunk_size == 0) {
expanded_early_data_chunk_size = server_ep.ssl.total_early_data_size;
}
break;
}
TEST_LE_U(server_ep.ssl.total_early_data_size, max_early_data_size);
} while (1);
mbedtls_debug_set_threshold(3);
ret = write_early_data(&(client_ep.ssl), buf_write, write_size);
TEST_EQUAL(ret, write_size);
ret = mbedtls_snprintf(pattern, sizeof(pattern),
"EarlyData: Too much early data received");
TEST_ASSERT(ret < (int) sizeof(pattern));
ret = mbedtls_ssl_handshake(&(server_ep.ssl));
TEST_EQUAL(ret, MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE);
TEST_EQUAL(server_pattern.counter, 1);
exit:
mbedtls_test_ssl_endpoint_free(&client_ep, NULL);
mbedtls_test_ssl_endpoint_free(&server_ep, NULL);
mbedtls_test_free_handshake_options(&client_options);
mbedtls_test_free_handshake_options(&server_options);
mbedtls_ssl_session_free(&saved_session);
mbedtls_free(buf_write);
mbedtls_free(buf_read);
mbedtls_debug_set_threshold(0);
PSA_DONE();
}
/* END_CASE */