Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/ssl_api_rw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}

Expand Down Expand Up @@ -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) {
Expand Down
180 changes: 153 additions & 27 deletions tests/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions wolfssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading