diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 73f971f929..bb5b32308d 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -830,7 +830,26 @@ 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); + 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; + } + } } } @@ -1106,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 3618a95773..90f3d02e6b 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39141,6 +39141,50 @@ 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); +} + +/* 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) { EXPECT_DECLS; @@ -39173,40 +39217,122 @@ 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; + 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 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 (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\n", params[i].tls_version); + printf("Testing %s%s%s\n", params[i].tls_version, + quiet ? " (quiet shutdown)" : "", + 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)); - 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 && ssl_s != NULL) + wolfSSL_set_quiet_shutdown(ssl_s, 1); + #endif + 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, + 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); + + 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); + } + } - /* Alert closed connection */ - ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); - ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_ZERO_RETURN); + /* 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); + } - /* 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); + /* 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); - 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(); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 18ab83ca26..5ec883be7a 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