From 789ecb1ff784dd005df03cab98a61f3e555b02d6 Mon Sep 17 00:00:00 2001 From: Thomas Cook Date: Fri, 4 Sep 2026 18:07:15 -0400 Subject: [PATCH] RFC 8446 6.2 - no writes after fatal alert --- src/internal.c | 8 +++++ tests/api.c | 8 ++--- tests/api/test_tls13.c | 80 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/internal.c b/src/internal.c index 94252a2166..de02fbfbcb 100644 --- a/src/internal.c +++ b/src/internal.c @@ -29020,6 +29020,14 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) return WOLFSSL_FATAL_ERROR; } + /* RFC 8446 6.2 - no new data write after fatal alert or otherwise closed */ + if (ssl->options.isClosed) { + WOLFSSL_MSG("Connection is closed, not sending new data"); + if (ssl->error == 0) + ssl->error = SOCKET_PEER_CLOSED_E; + return WOLFSSL_FATAL_ERROR; + } + #ifdef WOLFSSL_THREADED_CRYPT ret = SendAsyncData(ssl); if (ret != 0) { diff --git a/tests/api.c b/tests/api.c index 88999c0cfb..00e007bdd0 100644 --- a/tests/api.c +++ b/tests/api.c @@ -15141,11 +15141,11 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) if (0 < (idx = wolfSSL_read(ssl, input, sizeof(input)-1))) { input[idx] = 0; fprintf(stderr, "Client message: %s\n", input); - } - AssertIntEQ(privateNameLen, wolfSSL_write(ssl, privateName, - privateNameLen)); - ((func_args*)args)->return_code = TEST_SUCCESS; + AssertIntEQ(privateNameLen, wolfSSL_write(ssl, privateName, + privateNameLen)); + ((func_args*)args)->return_code = TEST_SUCCESS; + } } if (callbacks->on_result) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 5f2980bedb..e8def9935c 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -10176,6 +10176,86 @@ int test_tls12_fatal_alert_closes_and_evicts(void) return EXPECT_RESULT(); } +/* RFC 8446 Section 6.2: "Whenever an implementation encounters a fatal + * error condition, it SHOULD send an appropriate fatal alert and MUST + * close the connection without sending or receiving any additional data." + * + * This drives the client into sending a fatal unexpected_message alert the + * same way test_tls13_post_handshake_auth_no_ext() does, then checks that a + * subsequent wolfSSL_write() is refused rather than queuing a new + * application data record on the connection it just tore down. */ +int test_tls13_no_app_data_after_fatal_alert(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + char readBuf[8]; + char msg[] = "should never be sent"; + int c_len_after_alert; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_no_ticket_TLSv13(ssl_s), 0); + + /* Intentionally do NOT call wolfSSL_allow_post_handshake_auth() on the + * client so the post_handshake_auth extension is omitted from the + * ClientHello. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Force the server to send an unsolicited post-handshake + * CertificateRequest, as in test_tls13_post_handshake_auth_no_ext(). */ + if (ssl_s != NULL) + ssl_s->options.postHandshakeAuth = 1; + ExpectIntEQ(wolfSSL_clear_group_messages(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + ExpectIntGT(test_ctx.c_len, 0); + + /* The client rejects it and transmits a fatal unexpected_message + * alert - the connection is now closed. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(OUT_OF_ORDER_E)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, unexpected_message); + ExpectIntEQ(h.last_tx.level, alert_fatal); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.isClosed, 1); + + c_len_after_alert = test_ctx.c_len; + + /* Clear the error the failed read left behind so the write's own + * SOCKET_PEER_CLOSED_E can be told apart from it. */ + if (ssl_c != NULL) + ssl_c->error = WOLFSSL_ERROR_NONE; + + /* A write attempted after the fatal alert must be refused instead of + * building and queuing a new application data record. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->error, WC_NO_ERR_TRACE(SOCKET_PEER_CLOSED_E)); + ExpectIntNE(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), 0); + /* No new record was appended to what the alert itself already sent. */ + ExpectIntEQ(test_ctx.c_len, c_len_after_alert); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* RFC 9846 Section 4.7.3, write-duplicate path. The read side of a write dup * cannot send, so a peer KeyUpdate(update_requested) is delegated to the write * side via dupWrite->keyUpdateRespond and sent from diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index b4adb9d01d..3ba17c03e4 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -126,6 +126,7 @@ int test_tls13_early_data_AEAD_limit_exact(void); int test_tls13_user_canceled_fatal_level(void); int test_tls13_user_canceled_encrypted(void); int test_tls12_fatal_alert_closes_and_evicts(void); +int test_tls13_no_app_data_after_fatal_alert(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); int test_tls13_x25519_keyshare_masks_reserved_bit(void); @@ -233,6 +234,7 @@ int test_tls13_x25519_keyshare_masks_reserved_bit(void); TEST_DECL_GROUP("tls13", test_tls13_user_canceled_fatal_level), \ TEST_DECL_GROUP("tls13", test_tls13_user_canceled_encrypted), \ TEST_DECL_GROUP("tls13", test_tls12_fatal_alert_closes_and_evicts), \ + TEST_DECL_GROUP("tls13", test_tls13_no_app_data_after_fatal_alert), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit)