From 9b884271d6ec611927dcbaab8a953501c36f0d71 Mon Sep 17 00:00:00 2001 From: night1rider Date: Tue, 25 Aug 2026 00:47:15 -0600 Subject: [PATCH 1/4] Send close_notify after user_canceled even under quiet shutdown Clear quiet shutdown across the shutdown call in wolfSSL_SendUserCanceled so the RFC 9846 paired close_notify is emitted, and cover it in test_wolfSSL_SendUserCanceled. --- src/ssl_api_rw.c | 8 ++++++ tests/api.c | 74 ++++++++++++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 73f971f9293..37e8230b7c2 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -830,7 +830,15 @@ int wolfSSL_SendUserCanceled(WOLFSSL* ssl) WOLFSSL_ERROR(ssl->error); } else { + /* RFC 9846: user_canceled must be followed by close_notify. Quiet + * shutdown suppresses a standalone close_notify, but the alert just + * sent obligates the paired close_notify, so clear quiet shutdown + * across this shutdown call to guarantee it is sent, then restore + * the caller's setting. */ + int quietShutdown = ssl->options.quietShutdown; + ssl->options.quietShutdown = 0; ret = wolfSSL_shutdown(ssl); + ssl->options.quietShutdown = quietShutdown; } } diff --git a/tests/api.c b/tests/api.c index 3618a957733..8d0c2a055b6 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39173,40 +39173,60 @@ static int test_wolfSSL_SendUserCanceled(void) }; for (i = 0; i < sizeof(params)/sizeof(*params) && !EXPECT_FAIL(); i++) { - 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; + int quiet; + int quietMax = 0; + /* Run once normally and, where the quiet-shutdown compat API is + * available, once more with quiet shutdown enabled: quiet shutdown must + * not suppress the close_notify that RFC 9846 requires after + * user_canceled. */ + #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ + defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) + quietMax = 1; + #endif + for (quiet = 0; quiet <= quietMax && !EXPECT_FAIL(); quiet++) { + 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; - printf("Testing %s\n", params[i].tls_version); + printf("Testing %s%s\n", params[i].tls_version, + quiet ? " (quiet shutdown)" : ""); - XMEMSET(&h, 0, sizeof(h)); - XMEMSET(&test_ctx, 0, sizeof(test_ctx)); - ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, - params[i].client_meth, params[i].server_meth), 0); + XMEMSET(&h, 0, sizeof(h)); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, params[i].client_meth, params[i].server_meth), 0); - /* CH1 */ - ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); - ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* CH1 */ + ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); - ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), WOLFSSL_SHUTDOWN_NOT_DONE); + #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ + defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) + if (quiet) + wolfSSL_set_quiet_shutdown(ssl_s, 1); + #endif + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), + WOLFSSL_SHUTDOWN_NOT_DONE); - /* Alert closed connection */ - ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); - ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_ZERO_RETURN); + /* Alert closed connection */ + ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), + WOLFSSL_ERROR_ZERO_RETURN); - /* Last alert will be close notify because user_canceled should be - * followed by a close_notify */ - ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); - ExpectIntEQ(h.last_rx.code, close_notify); - ExpectIntEQ(h.last_rx.level, alert_warning); + /* Last alert will be close notify because user_canceled should be + * followed by a close_notify */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, close_notify); + ExpectIntEQ(h.last_rx.level, alert_warning); - wolfSSL_free(ssl_c); - wolfSSL_free(ssl_s); - wolfSSL_CTX_free(ctx_c); - wolfSSL_CTX_free(ctx_s); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } } #endif return EXPECT_RESULT(); From c9438c48088582ae4778a056fd64f331d0d2f9d8 Mon Sep 17 00:00:00 2001 From: night1rider Date: Tue, 25 Aug 2026 11:16:21 -0600 Subject: [PATCH 2/4] tests: harden SendUserCanceled quiet-shutdown checks Guard the quiet-shutdown setter against a NULL ssl_s and assert quietShutdown is restored after SendUserCanceled, per PR #11264 review. --- tests/api.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/api.c b/tests/api.c index 8d0c2a055b6..e88ea1facaf 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39205,7 +39205,7 @@ static int test_wolfSSL_SendUserCanceled(void) #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) - if (quiet) + if (quiet && ssl_s != NULL) wolfSSL_set_quiet_shutdown(ssl_s, 1); #endif ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), @@ -39222,6 +39222,11 @@ static int test_wolfSSL_SendUserCanceled(void) ExpectIntEQ(h.last_rx.code, close_notify); ExpectIntEQ(h.last_rx.level, alert_warning); + /* SendUserCanceled must restore the quiet-shutdown flag it + * cleared to emit the paired close_notify. */ + if (quiet && ssl_s != NULL) + ExpectIntEQ(ssl_s->options.quietShutdown, 1); + wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_c); From 6040e2a574b193cdce32b6900f1428a482b5e9d8 Mon Sep 17 00:00:00 2001 From: night1rider Date: Tue, 8 Sep 2026 14:17:07 -0600 Subject: [PATCH 3/4] Flush a buffered close_notify on quiet shutdown after user_canceled Have the wolfSSL_shutdown quiet path send a close_notify already left in the output buffer by WANT_WRITE, so the retry after wolfSSL_SendUserCanceled restores quiet shutdown still delivers it; cover it with a WANT_WRITE write callback and check restoration via wolfSSL_shutdown instead of ssl->options, per PR #11264 review. --- src/ssl_api_rw.c | 18 ++++++++++++-- tests/api.c | 64 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 37e8230b7c2..2fc241324c0 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -1057,8 +1057,22 @@ int wolfSSL_shutdown(WOLFSSL* ssl) ret = WOLFSSL_FATAL_ERROR; } else if (ssl->options.quietShutdown) { - WOLFSSL_MSG("quiet shutdown, no close notify sent"); - ret = WOLFSSL_SUCCESS; + int done = 0; + + /* Quiet shutdown means no close_notify is originated here. One that + * an earlier call already committed but could not send (WANT_WRITE) + * is not covered by that: wolfSSL_SendUserCanceled() clears quiet + * shutdown only for the call that sends the RFC 9846 paired + * close_notify and then restores it, so the retry that gets the + * alert out lands here. Flush it before reporting success. */ + if (ssl->options.sentNotify) { + done = wolfssl_shutdown_flush_alert(ssl, &ret); + } + if ((!done) || (ssl->error >= 0)) { + WOLFSSL_MSG("quiet shutdown, no close notify sent"); + ret = WOLFSSL_SUCCESS; + } + /* else the flush failed - ret and ssl->error already say why. */ } else { int done; diff --git a/tests/api.c b/tests/api.c index e88ea1facaf..98f82edb6e1 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39141,6 +39141,21 @@ static int test_tls_cert_store_unchanged(void) } #endif /* !WOLFSSL_TEST_APPLE_NATIVE_CERT_VALIDATION */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) +/* Write callback for test_wolfSSL_SendUserCanceled(): pass the server's first + * record (the user_canceled alert) to the memio buffer and report WANT_WRITE + * for the next one (the close_notify), leaving it in the output buffer. */ +static int test_SendUserCanceled_block_close_notify_cb(WOLFSSL* ssl, + char* data, int sz, void* ctx) +{ + struct test_memio_ctx* test_ctx = (struct test_memio_ctx*)ctx; + + if (test_ctx->c_msg_count >= 1) + return WOLFSSL_CBIO_ERR_WANT_WRITE; + return test_memio_write_cb(ssl, data, sz, ctx); +} +#endif + static int test_wolfSSL_SendUserCanceled(void) { EXPECT_DECLS; @@ -39175,15 +39190,20 @@ static int test_wolfSSL_SendUserCanceled(void) for (i = 0; i < sizeof(params)/sizeof(*params) && !EXPECT_FAIL(); i++) { int quiet; int quietMax = 0; + int wantWrite; /* Run once normally and, where the quiet-shutdown compat API is * available, once more with quiet shutdown enabled: quiet shutdown must * not suppress the close_notify that RFC 9846 requires after - * user_canceled. */ + * user_canceled. Each of those runs twice: once with the transport + * accepting the close_notify right away and once with it refusing + * (WANT_WRITE) so the alert is left buffered and a later + * wolfSSL_shutdown() has to get it out. */ #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) quietMax = 1; #endif for (quiet = 0; quiet <= quietMax && !EXPECT_FAIL(); quiet++) { + for (wantWrite = 0; wantWrite <= 1 && !EXPECT_FAIL(); wantWrite++) { WOLFSSL_CTX *ctx_c = NULL; WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_c = NULL; @@ -39191,8 +39211,9 @@ static int test_wolfSSL_SendUserCanceled(void) struct test_memio_ctx test_ctx; WOLFSSL_ALERT_HISTORY h; - printf("Testing %s%s\n", params[i].tls_version, - quiet ? " (quiet shutdown)" : ""); + printf("Testing %s%s%s\n", params[i].tls_version, + quiet ? " (quiet shutdown)" : "", + wantWrite ? " (close_notify WANT_WRITE)" : ""); XMEMSET(&h, 0, sizeof(h)); XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -39208,8 +39229,31 @@ static int test_wolfSSL_SendUserCanceled(void) if (quiet && ssl_s != NULL) wolfSSL_set_quiet_shutdown(ssl_s, 1); #endif - ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), - WOLFSSL_SHUTDOWN_NOT_DONE); + if (wantWrite) { + /* Let user_canceled through and refuse the close_notify. */ + if (ssl_s != NULL) { + wolfSSL_SSLSetIOSend(ssl_s, + test_SendUserCanceled_block_close_notify_cb); + } + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WOLFSSL_ERROR_WANT_WRITE); + /* Only user_canceled reached the wire so far. */ + ExpectIntEQ(test_ctx.c_msg_count, 1); + + /* Transport writable again: the retry must flush the + * buffered close_notify even though quiet shutdown has been + * restored in the meantime. */ + if (ssl_s != NULL) + wolfSSL_SSLSetIOSend(ssl_s, test_memio_write_cb); + ExpectIntEQ(wolfSSL_shutdown(ssl_s), + quiet ? WOLFSSL_SUCCESS : WOLFSSL_SHUTDOWN_NOT_DONE); + } + else { + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), + WOLFSSL_SHUTDOWN_NOT_DONE); + } + ExpectIntEQ(test_ctx.c_msg_count, 2); /* Alert closed connection */ ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); @@ -39223,15 +39267,19 @@ static int test_wolfSSL_SendUserCanceled(void) ExpectIntEQ(h.last_rx.level, alert_warning); /* SendUserCanceled must restore the quiet-shutdown flag it - * cleared to emit the paired close_notify. */ - if (quiet && ssl_s != NULL) - ExpectIntEQ(ssl_s->options.quietShutdown, 1); + * cleared to emit the paired close_notify: with it restored a + * further shutdown completes at once instead of waiting for the + * peer's close_notify. (The WANT_WRITE retry above already + * checked this.) */ + if (quiet && !wantWrite) + ExpectIntEQ(wolfSSL_shutdown(ssl_s), WOLFSSL_SUCCESS); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_c); wolfSSL_CTX_free(ctx_s); } + } } #endif return EXPECT_RESULT(); From 2eead7c5117e443ac52ee6a84cf29f6e21c1cbe1 Mon Sep 17 00:00:00 2001 From: night1rider Date: Tue, 8 Sep 2026 16:05:30 -0600 Subject: [PATCH 4/4] Restore quiet shutdown only after the user_canceled close_notify is sent Use a flag set by wolfSSL_SendUserCanceled so quiet shutdown never sends data on its own, and test alert order, a failed retry, and that the setting comes back. --- src/ssl_api_rw.c | 40 +++++++++------ tests/api.c | 125 ++++++++++++++++++++++++++++++++------------- wolfssl/internal.h | 3 ++ 3 files changed, 115 insertions(+), 53 deletions(-) diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 2fc241324c0..bb5b32308d0 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -838,7 +838,18 @@ int wolfSSL_SendUserCanceled(WOLFSSL* ssl) int quietShutdown = ssl->options.quietShutdown; ssl->options.quietShutdown = 0; ret = wolfSSL_shutdown(ssl); - ssl->options.quietShutdown = quietShutdown; + if (quietShutdown) { + if (ssl->error == WC_NO_ERR_TRACE(WANT_WRITE)) { + /* The close_notify is still in the output buffer. Leave + * quiet shutdown off so the caller's retry of + * wolfSSL_shutdown() flushes it, and have that call give + * the setting back once the flush reaches a decision. */ + ssl->options.quietShutdownRestore = 1; + } + else { + ssl->options.quietShutdown = 1; + } + } } } @@ -1057,22 +1068,8 @@ int wolfSSL_shutdown(WOLFSSL* ssl) ret = WOLFSSL_FATAL_ERROR; } else if (ssl->options.quietShutdown) { - int done = 0; - - /* Quiet shutdown means no close_notify is originated here. One that - * an earlier call already committed but could not send (WANT_WRITE) - * is not covered by that: wolfSSL_SendUserCanceled() clears quiet - * shutdown only for the call that sends the RFC 9846 paired - * close_notify and then restores it, so the retry that gets the - * alert out lands here. Flush it before reporting success. */ - if (ssl->options.sentNotify) { - done = wolfssl_shutdown_flush_alert(ssl, &ret); - } - if ((!done) || (ssl->error >= 0)) { - WOLFSSL_MSG("quiet shutdown, no close notify sent"); - ret = WOLFSSL_SUCCESS; - } - /* else the flush failed - ret and ssl->error already say why. */ + WOLFSSL_MSG("quiet shutdown, no close notify sent"); + ret = WOLFSSL_SUCCESS; } else { int done; @@ -1128,6 +1125,15 @@ int wolfSSL_shutdown(WOLFSSL* ssl) } } + /* wolfSSL_SendUserCanceled() turned quiet shutdown off so that the + * close_notify it left in the output buffer could be flushed here. Give + * the caller's setting back once the flush has reached a decision. */ + if ((ssl != NULL) && ssl->options.quietShutdownRestore && + (ssl->error != WC_NO_ERR_TRACE(WANT_WRITE))) { + ssl->options.quietShutdownRestore = 0; + ssl->options.quietShutdown = 1; + } + #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) /* reset WOLFSSL structure state for possible reuse */ if (ret == WOLFSSL_SUCCESS) { diff --git a/tests/api.c b/tests/api.c index 98f82edb6e1..90f3d02e6bc 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39154,6 +39154,35 @@ static int test_SendUserCanceled_block_close_notify_cb(WOLFSSL* ssl, return WOLFSSL_CBIO_ERR_WANT_WRITE; return test_memio_write_cb(ssl, data, sz, ctx); } + +/* Write callback for test_wolfSSL_SendUserCanceled(): the transport is gone + * for good. */ +static int test_SendUserCanceled_fail_write_cb(WOLFSSL* ssl, char* data, + int sz, void* ctx) +{ + (void)ssl; + (void)data; + (void)sz; + (void)ctx; + return WOLFSSL_CBIO_ERR_GENERAL; +} + +/* Description of the alert the server wrote as record number pos, or -1 when + * that record is not a plaintext alert. Both alerts go out before any keys are + * set up, so they are readable straight from the memio buffer. */ +static int test_SendUserCanceled_alert_desc(WOLFSSL* ssl, + const struct test_memio_ctx* test_ctx, int pos) +{ + const char* msg = NULL; + int msgSz = 0; + int hdrSz = wolfSSL_dtls(ssl) ? DTLS_RECORD_HEADER_SZ : RECORD_HEADER_SZ; + + if (test_memio_get_message(test_ctx, 1, &msg, &msgSz, pos) != 0) + return -1; + if (msgSz != hdrSz + ALERT_SIZE || msg[0] != alert) + return -1; + return (byte)msg[hdrSz + 1]; +} #endif static int test_wolfSSL_SendUserCanceled(void) @@ -39190,30 +39219,34 @@ static int test_wolfSSL_SendUserCanceled(void) for (i = 0; i < sizeof(params)/sizeof(*params) && !EXPECT_FAIL(); i++) { int quiet; int quietMax = 0; - int wantWrite; + int mode; /* Run once normally and, where the quiet-shutdown compat API is * available, once more with quiet shutdown enabled: quiet shutdown must * not suppress the close_notify that RFC 9846 requires after - * user_canceled. Each of those runs twice: once with the transport - * accepting the close_notify right away and once with it refusing - * (WANT_WRITE) so the alert is left buffered and a later - * wolfSSL_shutdown() has to get it out. */ + * user_canceled. Each of those runs in three modes: + * 0 - the transport takes the close_notify right away; + * 1 - it refuses it (WANT_WRITE) so the alert is left buffered and + * the retry of wolfSSL_shutdown() has to get it out; + * 2 - as 1, but the retry hits a permanent write failure. */ #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) || \ defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) quietMax = 1; #endif for (quiet = 0; quiet <= quietMax && !EXPECT_FAIL(); quiet++) { - for (wantWrite = 0; wantWrite <= 1 && !EXPECT_FAIL(); wantWrite++) { + for (mode = 0; mode <= 2 && !EXPECT_FAIL(); mode++) { 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; + int flushed = (mode != 2); printf("Testing %s%s%s\n", params[i].tls_version, quiet ? " (quiet shutdown)" : "", - wantWrite ? " (close_notify WANT_WRITE)" : ""); + mode == 1 ? " (close_notify WANT_WRITE)" : + mode == 2 ? " (close_notify WANT_WRITE, retry fails)" : + ""); XMEMSET(&h, 0, sizeof(h)); XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -39229,7 +39262,11 @@ static int test_wolfSSL_SendUserCanceled(void) if (quiet && ssl_s != NULL) wolfSSL_set_quiet_shutdown(ssl_s, 1); #endif - if (wantWrite) { + if (mode == 0) { + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), + WOLFSSL_SHUTDOWN_NOT_DONE); + } + else { /* Let user_canceled through and refuse the close_notify. */ if (ssl_s != NULL) { wolfSSL_SSLSetIOSend(ssl_s, @@ -39241,37 +39278,53 @@ static int test_wolfSSL_SendUserCanceled(void) /* Only user_canceled reached the wire so far. */ ExpectIntEQ(test_ctx.c_msg_count, 1); - /* Transport writable again: the retry must flush the - * buffered close_notify even though quiet shutdown has been - * restored in the meantime. */ - if (ssl_s != NULL) - wolfSSL_SSLSetIOSend(ssl_s, test_memio_write_cb); - ExpectIntEQ(wolfSSL_shutdown(ssl_s), - quiet ? WOLFSSL_SUCCESS : WOLFSSL_SHUTDOWN_NOT_DONE); + if (mode == 1) { + /* Transport writable again: the retry must flush the + * buffered close_notify whether or not the caller had + * quiet shutdown on. */ + if (ssl_s != NULL) + wolfSSL_SSLSetIOSend(ssl_s, test_memio_write_cb); + ExpectIntEQ(wolfSSL_shutdown(ssl_s), + WOLFSSL_SHUTDOWN_NOT_DONE); + } + else { + /* Transport broken: the failure must be reported, not + * turned into a successful quiet shutdown. */ + if (ssl_s != NULL) { + wolfSSL_SSLSetIOSend(ssl_s, + test_SendUserCanceled_fail_write_cb); + } + ExpectIntEQ(wolfSSL_shutdown(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), SOCKET_ERROR_E); + } } - else { - ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_s), - WOLFSSL_SHUTDOWN_NOT_DONE); + + /* RFC 9846: user_canceled first, then close_notify. */ + ExpectIntEQ(test_ctx.c_msg_count, flushed ? 2 : 1); + ExpectIntEQ(test_SendUserCanceled_alert_desc(ssl_s, &test_ctx, 0), + user_canceled); + if (flushed) { + ExpectIntEQ(test_SendUserCanceled_alert_desc(ssl_s, &test_ctx, + 1), close_notify); + + /* Alert closed connection */ + ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), + WOLFSSL_ERROR_ZERO_RETURN); + + /* Last alert will be close notify because user_canceled should + * be followed by a close_notify */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), + WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, close_notify); + ExpectIntEQ(h.last_rx.level, alert_warning); } - ExpectIntEQ(test_ctx.c_msg_count, 2); - /* Alert closed connection */ - ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); - ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), - WOLFSSL_ERROR_ZERO_RETURN); - - /* Last alert will be close notify because user_canceled should be - * followed by a close_notify */ - ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); - ExpectIntEQ(h.last_rx.code, close_notify); - ExpectIntEQ(h.last_rx.level, alert_warning); - - /* SendUserCanceled must restore the quiet-shutdown flag it - * cleared to emit the paired close_notify: with it restored a - * further shutdown completes at once instead of waiting for the - * peer's close_notify. (The WANT_WRITE retry above already - * checked this.) */ - if (quiet && !wantWrite) + /* Once the close_notify is out, or can never go out, the caller's + * quiet-shutdown setting must be back: a further shutdown then + * completes at once instead of waiting for the peer's + * close_notify. */ + if (quiet) ExpectIntEQ(wolfSSL_shutdown(ssl_s), WOLFSSL_SUCCESS); wolfSSL_free(ssl_c); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 18ab83ca268..5ec883be7a7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5493,6 +5493,9 @@ struct Options { #endif word16 partialWrite:1; /* only one msg per write call */ word16 quietShutdown:1; /* don't send close notify */ + word16 quietShutdownRestore:1; /* wolfSSL_SendUserCanceled() + * turned quietShutdown off until + * its close_notify is flushed */ word16 certOnly:1; /* stop once we get cert */ word16 groupMessages:1; /* group handshake messages */ word16 saveArrays:1; /* save array Memory for user get keys