F-6547 - Reject TLS KeyUpdate on QUIC connections

QUIC performs key updates at the packet-protection layer via the Key
Phase bit, so RFC 9001 section 6 requires a QUIC endpoint to reject any
received TLS KeyUpdate handshake message as a fatal unexpected_message
connection error and to never send one. The TLS 1.3 receive path
processed the message normally, rotating traffic secrets and possibly
emitting a prohibited KeyUpdate response, and the send path allowed a
QUIC connection to originate a KeyUpdate.

Guard the key_update case in SanityCheckTls13MsgReceived so a QUIC
connection aborts with a fatal unexpected_message alert, and guard
Tls13UpdateKeys so a QUIC connection cannot send a KeyUpdate. Add a
QUIC unit test that feeds a post-handshake KeyUpdate and confirms the
connection is refused.
This commit is contained in:
Tobias Frauenschläger
2026-06-29 13:14:24 +02:00
parent e8865748f2
commit 154f2e2ea4
2 changed files with 70 additions and 1 deletions
+19 -1
View File
@@ -13568,6 +13568,16 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type)
case key_update:
/* Valid on both sides. */
#ifdef WOLFSSL_QUIC
/* RFC 9001 Section 6: QUIC performs key updates at the QUIC
* packet-protection layer, so a TLS KeyUpdate message must be
* rejected as a fatal unexpected_message connection error. */
if (WOLFSSL_IS_QUIC(ssl)) {
WOLFSSL_MSG("KeyUpdate received over QUIC");
WOLFSSL_ERROR_VERBOSE(SANITY_MSG_E);
return SANITY_MSG_E;
}
#endif
/* Check state.
* Client and server must have received finished message from other
* side.
@@ -14974,6 +14984,13 @@ int Tls13UpdateKeys(WOLFSSL* ssl)
if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version))
return BAD_FUNC_ARG;
#ifdef WOLFSSL_QUIC
/* RFC 9001 Section 6: a QUIC connection must not send a TLS KeyUpdate;
* key updates are handled at the QUIC packet-protection layer. */
if (WOLFSSL_IS_QUIC(ssl))
return BAD_FUNC_ARG;
#endif
#ifdef WOLFSSL_DTLS13
/* we are already waiting for the ack of a sent key update message. We can't
send another one before receiving its ack. Either wolfSSL_update_keys()
@@ -14993,7 +15010,8 @@ int Tls13UpdateKeys(WOLFSSL* ssl)
* calling wolfSSL_write() will have the message sent when ready.
*
* ssl The SSL/TLS object.
* returns BAD_FUNC_ARG when ssl is NULL, or not using TLS v1.3,
* returns BAD_FUNC_ARG when ssl is NULL, not using TLS v1.3, or running over
* QUIC (RFC 9001 handles key updates at the QUIC packet-protection layer),
* WOLFSSL_ERROR_WANT_WRITE when non-blocking I/O is not ready to write,
* WOLFSSL_SUCCESS on success and otherwise failure.
*/
+51
View File
@@ -1470,6 +1470,56 @@ static int test_quic_server_hello_fail(int verbose) {
return EXPECT_RESULT();
}
static int test_quic_key_update_rejected(int verbose) {
EXPECT_DECLS;
WOLFSSL_CTX * ctx_c = NULL;
WOLFSSL_CTX * ctx_s = NULL;
QuicTestContext tclient, tserver;
QuicConversation conv;
uint8_t lbuffer[16];
size_t len;
int ret;
ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile,
WOLFSSL_FILETYPE_PEM));
ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile,
WOLFSSL_FILETYPE_PEM));
/* complete a normal QUIC handshake */
QuicTestContext_init(&tclient, ctx_c, "client", verbose);
QuicTestContext_init(&tserver, ctx_s, "server", verbose);
QuicConversation_init(&conv, &tclient, &tserver);
QuicConversation_do(&conv);
/* RFC 9001 section 6: a QUIC connection must not send a TLS KeyUpdate;
* key updates are handled at the QUIC packet-protection layer. The
* public wolfSSL_update_keys() must refuse on a QUIC connection. */
ExpectIntEQ(wolfSSL_update_keys(tserver.ssl),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* RFC 9001 section 6: a TLS KeyUpdate handshake message must be rejected
* as a fatal unexpected_message connection error when running over QUIC.
* Feed a key_update (update_not_requested) as post-handshake CRYPTO data
* and confirm the server refuses to process it. */
len = fake_record(key_update, OPAQUE8_LEN, lbuffer);
lbuffer[HANDSHAKE_HEADER_SZ] = update_not_requested;
ExpectIntEQ(wolfSSL_provide_quic_data(tserver.ssl,
wolfssl_encryption_application, lbuffer, len), WOLFSSL_SUCCESS);
ret = wolfSSL_process_quic_post_handshake(tserver.ssl);
ExpectIntEQ(ret, WC_NO_ERR_TRACE(SANITY_MSG_E));
QuicTestContext_free(&tclient);
QuicTestContext_free(&tserver);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
printf(" test_quic_key_update_rejected: %s\n",
EXPECT_RESULT() ? pass : fail);
return EXPECT_RESULT();
}
/* This has gotten a bit out of hand. */
#if (defined(OPENSSL_ALL) || (defined(OPENSSL_EXTRA) && \
(defined(HAVE_STUNNEL) || defined(WOLFSSL_NGINX) || \
@@ -2005,6 +2055,7 @@ int QuicTest(void)
#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
if ((ret = test_quic_server_hello(verbose)) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_server_hello_fail(verbose)) != TEST_SUCCESS) goto leave;
if ((ret = test_quic_key_update_rejected(verbose)) != TEST_SUCCESS) goto leave;
#ifdef REALLY_HAVE_ALPN_AND_SNI
if ((ret = test_quic_alpn(verbose)) != TEST_SUCCESS) goto leave;
#endif /* REALLY_HAVE_ALPN_AND_SNI */