From 08ec24b1dc0b7f756ecdd46fe2460205dc973eb5 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:49:12 -0500 Subject: [PATCH 1/3] tls13: Accept a cookie extension in a HelloRetryRequest RFC 8446 section 4.2 forbids extension responses to extensions the client did not send, "with the exception of the "cookie" extension in the HelloRetryRequest". The client never sends a cookie in its first ClientHello, so mbedtls_ssl_tls13_check_received_extension() rejected every HelloRetryRequest that carries one with an unsupported_extension alert, although the client already parses the cookie and echoes it in its second ClientHello. A cookie in any other message is still rejected, by the per-message allowed extensions masks. Fixes #9543. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: JC-000 <3798556+JC-000@users.noreply.github.com> --- ChangeLog.d/tls13-hrr-cookie.txt | 5 +++++ library/ssl_tls13_generic.c | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 ChangeLog.d/tls13-hrr-cookie.txt diff --git a/ChangeLog.d/tls13-hrr-cookie.txt b/ChangeLog.d/tls13-hrr-cookie.txt new file mode 100644 index 0000000000..c7180671d9 --- /dev/null +++ b/ChangeLog.d/tls13-hrr-cookie.txt @@ -0,0 +1,5 @@ +Bugfix + * Fix the TLS 1.3 client rejecting a HelloRetryRequest that contains a + cookie extension with an unsupported_extension alert. Such + HelloRetryRequests are sent for example by Java (JSSE) servers and by + OpenSSL servers in stateless mode. Fixes #9543. diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 60c0f2348c..26b59ec9df 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -1658,6 +1658,17 @@ int mbedtls_ssl_tls13_check_received_extension( } ssl->handshake->received_extensions |= extension_mask; + + /* + * RFC 8446 section 4.2: the "cookie" extension in a HelloRetryRequest is + * the one extension response that may be sent although the client did + * not send the extension. + */ + if (hs_msg_type == MBEDTLS_SSL_TLS1_3_HS_HELLO_RETRY_REQUEST && + received_extension_type == MBEDTLS_TLS_EXT_COOKIE) { + return 0; + } + /* * If it is a message containing extension responses, check that we * previously sent the extension. From 213dbc3aded980ae777ee5b0bde7b84e68d604e1 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:49:12 -0500 Subject: [PATCH 2/3] Add tests for mbedtls_ssl_tls13_check_received_extension() Check that a HelloRetryRequest may carry a cookie although the client did not send one, while other unsolicited extensions are still rejected, and that a cookie is rejected in ServerHello and EncryptedExtensions. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: JC-000 <3798556+JC-000@users.noreply.github.com> --- tests/suites/test_suite_ssl.data | 15 +++++++++ tests/suites/test_suite_ssl.function | 49 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index bfa7565bc4..e16baf92ff 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3642,3 +3642,18 @@ 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 + +TLS 1.3: check received extension: HRR cookie, not sent +ssl_tls13_check_received_extension:MBEDTLS_SSL_TLS1_3_HS_HELLO_RETRY_REQUEST:MBEDTLS_TLS_EXT_COOKIE:0:0 + +TLS 1.3: check received extension: HRR key_share, not sent +ssl_tls13_check_received_extension:MBEDTLS_SSL_TLS1_3_HS_HELLO_RETRY_REQUEST:MBEDTLS_TLS_EXT_KEY_SHARE:0:MBEDTLS_ERR_SSL_UNSUPPORTED_EXTENSION + +TLS 1.3: check received extension: HRR key_share, sent +ssl_tls13_check_received_extension:MBEDTLS_SSL_TLS1_3_HS_HELLO_RETRY_REQUEST:MBEDTLS_TLS_EXT_KEY_SHARE:1:0 + +TLS 1.3: check received extension: ServerHello cookie, sent +ssl_tls13_check_received_extension:MBEDTLS_SSL_HS_SERVER_HELLO:MBEDTLS_TLS_EXT_COOKIE:1:MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER + +TLS 1.3: check received extension: EE cookie, not sent +ssl_tls13_check_received_extension:MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS:MBEDTLS_TLS_EXT_COOKIE:0:MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 361c0d2674..c9e6d7fb0d 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -2384,6 +2384,55 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_CLI_C */ +void ssl_tls13_check_received_extension(int hs_msg_type, + int extension_type, + int sent, + int expected_ret) +{ + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + uint32_t extension_mask = mbedtls_ssl_get_extension_mask(extension_type); + uint32_t allowed_mask = 0; + + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + MD_OR_USE_PSA_INIT(); + + switch (hs_msg_type) { + case MBEDTLS_SSL_HS_SERVER_HELLO: + allowed_mask = MBEDTLS_SSL_TLS1_3_ALLOWED_EXTS_OF_SH; + break; + case MBEDTLS_SSL_TLS1_3_HS_HELLO_RETRY_REQUEST: + allowed_mask = MBEDTLS_SSL_TLS1_3_ALLOWED_EXTS_OF_HRR; + break; + case MBEDTLS_SSL_HS_ENCRYPTED_EXTENSIONS: + allowed_mask = MBEDTLS_SSL_TLS1_3_ALLOWED_EXTS_OF_EE; + break; + default: + TEST_FAIL("Unsupported handshake message type"); + } + + TEST_EQUAL(mbedtls_ssl_config_defaults(&conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT), 0); + TEST_EQUAL(mbedtls_ssl_setup(&ssl, &conf), 0); + + ssl.handshake->sent_extensions = sent ? extension_mask : 0; + + TEST_EQUAL(mbedtls_ssl_tls13_check_received_extension(&ssl, hs_msg_type, + extension_type, + allowed_mask), + expected_ret); + +exit: + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SSL_PROTO_TLS1_3 */ void ssl_tls13_record_protection(int ciphersuite, int endpoint, From cd8c304fc4cf4476b2bff783d85c85449edf9e6a Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:49:12 -0500 Subject: [PATCH 3/3] ssl-opt.sh: Test a HelloRetryRequest with a cookie from OpenSSL openssl s_server -stateless puts a cookie in its HelloRetryRequest. It does not support -www in that mode, and input it reads before the handshake makes it start the handshake without -stateless, so provide its input after a delay. The OpenSSL server rejects a ChangeCipherSpec record before the second ClientHello in stateless mode, so the test needs the client's middlebox compatibility mode to be disabled. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: JC-000 <3798556+JC-000@users.noreply.github.com> --- tests/ssl-opt.sh | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index fd57757aaa..54d2637b25 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -1070,12 +1070,17 @@ is_polar() { esac } -# openssl s_server doesn't have -www with DTLS +# openssl s_server doesn't have -www with DTLS or with -stateless check_osrv_dtls() { case "$SRV_CMD" in *s_server*-dtls*) NEEDS_INPUT=1 SRV_CMD="$( echo $SRV_CMD | sed s/-www// )";; + *s_server*-stateless*) + # Input that s_server reads before the handshake makes it start + # the handshake without -stateless, so provide it later. + NEEDS_INPUT=2 + SRV_CMD="$( echo $SRV_CMD | sed s/-www// )";; *) NEEDS_INPUT=0;; esac } @@ -1086,6 +1091,10 @@ provide_input() { return fi + if [ $NEEDS_INPUT -eq 2 ]; then + sleep 1 + fi + while true; do echo "HTTP/1.0 200 OK" sleep 1 @@ -13665,6 +13674,23 @@ run_test "TLS 1.3 m->O HRR both with middlebox compat support" \ -c "Protocol is TLSv1.3" \ -c "Ignore ChangeCipherSpec in TLS 1.3 compatibility mode" +# With -stateless, the OpenSSL server puts a cookie in its HelloRetryRequest. +# It then rejects a ChangeCipherSpec record before the second ClientHello, +# so this only works without middlebox compatibility mode on the client. +requires_openssl_tls1_3_with_compatible_ephemeral +requires_config_disabled MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE +requires_config_enabled MBEDTLS_DEBUG_C +requires_config_enabled MBEDTLS_SSL_CLI_C +requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +run_test "TLS 1.3 m->O HRR with cookie" \ + "$O_NEXT_SRV -msg -tls1_3 -groups P-384 -stateless -num_tickets 0 -no_cache" \ + "$P_CLI debug_level=4 groups=secp256r1,secp384r1" \ + 0 \ + -c "received HelloRetryRequest message" \ + -c "client hello, adding cookie extension" \ + -c "Protocol is TLSv1.3" \ + -c "HTTP/1.0 200 OK" + requires_gnutls_tls1_3 requires_gnutls_next_no_ticket requires_gnutls_next_disable_tls13_compat