From 83f28d1fb96c86f31ff9820c4f5c439f0324f973 Mon Sep 17 00:00:00 2001 From: Joachim Bauernberger Date: Sun, 25 Oct 2026 18:15:43 +0100 Subject: [PATCH 1/2] tls13: Protect post-Finished server records with application keys A TLS 1.3 server switched its outbound transform to the application traffic keys only in mbedtls_ssl_tls13_handshake_wrapup(), which runs after the client's second flight has been processed. Any alert the server sent while processing the client Certificate, CertificateVerify or Finished message was therefore protected with the handshake traffic keys, so the peer could not decrypt it and reported a MAC failure instead of the alert. Switch the outbound transform to the application keys right after the server Finished message, as required by RFC 8446 section 4.4.4. The inbound transform keeps the handshake (or early-data) keys until the client's second flight has been read. The later switch in mbedtls_ssl_tls13_handshake_wrapup() is now redundant on the server but harmless: on the success path the server sends no record between its Finished and wrapup, so the outbound sequence number is still zero. Signed-off-by: Joachim Bauernberger --- library/ssl_tls13_server.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/ssl_tls13_server.c b/library/ssl_tls13_server.c index 38f2fe9370..741ac7e46b 100644 --- a/library/ssl_tls13_server.c +++ b/library/ssl_tls13_server.c @@ -2888,6 +2888,14 @@ static int ssl_tls13_write_server_finished(mbedtls_ssl_context *ssl) return ret; } + /* RFC 8446 4.4.4: records after the server Finished use the application + * keys. Switch the outbound transform now; inbound stays on the handshake + * (or early-data) keys until the client's second flight. */ + MBEDTLS_SSL_DEBUG_MSG( + 1, ("Switch to application keys for outbound traffic " + "( K_send = application )")); + mbedtls_ssl_set_outbound_transform(ssl, ssl->transform_application); + #if defined(MBEDTLS_SSL_EARLY_DATA) if (ssl->handshake->early_data_accepted) { /* See RFC 8446 section A.2 for more information */ From 4c214ce86a428e20931c035bfa6f24a38b8ca1c6 Mon Sep 17 00:00:00 2001 From: Joachim Bauernberger Date: Sun, 25 Oct 2026 18:15:52 +0100 Subject: [PATCH 2/2] tls13: Send the certificate alerts required by RFC 8446 RFC 8446 section 4.4.2.4 says a server that requires a client certificate and receives none must abort the handshake with a "certificate_required" alert, and a client that receives an empty server Certificate message must abort with a "decode_error" alert. In both cases the code pended "no_certificate" (alert 41), which is reserved in TLS 1.3 (RFC 8446 section 6.2). Send certificate_required and decode_error instead, and correct the server-side comment, which wrongly claimed that no alert was sent. Tighten the ssl-opt.sh test for a missing client certificate so that it checks that the server sends alert 116, the client decodes it, and no MAC failure is reported. The test fails before the transform and alert fixes and passes after them. Signed-off-by: Joachim Bauernberger --- ChangeLog.d/tls13-post-finished-alerts.txt | 13 +++++++++++++ library/ssl_tls13_generic.c | 13 ++++++------- tests/ssl-opt.sh | 3 +++ 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 ChangeLog.d/tls13-post-finished-alerts.txt diff --git a/ChangeLog.d/tls13-post-finished-alerts.txt b/ChangeLog.d/tls13-post-finished-alerts.txt new file mode 100644 index 0000000000..79eaabf758 --- /dev/null +++ b/ChangeLog.d/tls13-post-finished-alerts.txt @@ -0,0 +1,13 @@ +Bugfix + * In a TLS 1.3 server, protect alerts sent after the server Finished + message, such as those sent when a required client certificate is + missing or invalid, with the application traffic keys, as required by + RFC 8446 section 4.4.4. Previously these alerts were encrypted with the + handshake traffic keys, so the peer reported a MAC failure instead of + decoding the alert. Fixes #10877. + * In TLS 1.3, send the alerts described in RFC 8446 section 4.4.2.4. A + server that requires a client certificate and receives none now sends a + certificate_required alert, and a client that receives an empty server + Certificate message now sends a decode_error alert. Both previously sent + the no_certificate alert, which is reserved in TLS 1.3. Fixes #10720, + fixes #10900, fixes #10724, fixes #10884. diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 60c0f2348c..c430b8dbac 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -677,16 +677,14 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_SRV_C) if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) { - /* The client was asked for a certificate but didn't send - * one. The client should know what's going on, so we - * don't send an alert. - */ + /* No client certificate: optional auth continues, required auth + * aborts with certificate_required (RFC 8446 4.4.2.4). */ ssl->session_negotiate->verify_result = MBEDTLS_X509_BADCERT_MISSING; if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL) { return 0; } else { MBEDTLS_SSL_PEND_FATAL_ALERT( - MBEDTLS_SSL_ALERT_MSG_NO_CERT, + MBEDTLS_SSL_ALERT_MSG_CERT_REQUIRED, MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE); return MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE; } @@ -698,9 +696,10 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl) * certificate chain. (Last paragraph before 4.4.2.1 in RFC 8446: "The * server's certificate_list MUST always be non-empty.") With authmode * optional/none, we continue the handshake if we can't validate the - * server's cert, but we still break it if no certificate was sent. */ + * server's cert, but we still break it if no certificate was sent. + * RFC 8446, section 4.4.2.4 requires a "decode_error" alert here. */ if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) { - MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_NO_CERT, + MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_DECODE_ERROR, MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE); return MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE; } diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index fd57757aaa..1efcc8ad78 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -13310,7 +13310,10 @@ run_test "TLS 1.3: Server side check - mbedtls with client empty certificate" -s "tls13 server state: MBEDTLS_SSL_SERVER_CERTIFICATE" \ -s "=> write certificate request" \ -s "SSL - No client certification received from the client, but required by the authentication mode" \ + -s "send alert level=2 message=116" \ -c "client state: MBEDTLS_SSL_CERTIFICATE_REQUEST" \ + -c "is a fatal alert message (msg 116)" \ + -C "0x7180" \ -s "=> parse client hello" \ -s "<= parse client hello"