From 8075889b9f65628b02ccbfab372e472a4f6dcd86 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Thu, 20 Aug 2026 16:43:29 -0400 Subject: [PATCH 1/2] Ensure close_notify is sent after user_canceled during quiet shutdown --- src/ssl.c | 1 + src/ssl_api_rw.c | 18 ++++- tests/api/test_ssl_rw.c | 161 ++++++++++++++++++++++++++++++++++++++++ tests/api/test_ssl_rw.h | 6 ++ wolfssl/internal.h | 2 + 5 files changed, 187 insertions(+), 1 deletion(-) diff --git a/src/ssl.c b/src/ssl.c index da91ff10ac2..9f5e0ccc013 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5660,6 +5660,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.isClosed = 0; ssl->options.connReset = 0; ssl->options.sentNotify = 0; + ssl->options.sentUserCanceled = 0; ssl->options.closeNotify = 0; ssl->options.sendVerify = 0; ssl->options.serverState = NULL_STATE; diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 589c5950575..1354c5403e0 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -812,6 +812,10 @@ int wolfSSL_SendUserCanceled(WOLFSSL* ssl) if (ssl != NULL) { ssl->error = SendAlert(ssl, alert_warning, user_canceled); + if ((ssl->error == 0) || + (ssl->error == WC_NO_ERR_TRACE(WANT_WRITE))) { + ssl->options.sentUserCanceled = 1; + } if (ssl->error < 0) { WOLFSSL_ERROR(ssl->error); } @@ -1034,10 +1038,22 @@ int wolfSSL_shutdown(WOLFSSL* ssl) if (ssl == NULL) { ret = WOLFSSL_FATAL_ERROR; } - else if (ssl->options.quietShutdown) { + else if (ssl->options.quietShutdown && (!ssl->options.sentUserCanceled)) { WOLFSSL_MSG("quiet shutdown, no close notify sent"); ret = WOLFSSL_SUCCESS; } + else if (ssl->options.quietShutdown) { + /* A "user_canceled" alert has gone out so we need a "close_notify" to + * follow it per RFC 9846 Section 6.1. */ + ret = WOLFSSL_SUCCESS; + if (!wolfssl_shutdown_flush_alert(ssl, &ret)) { + (void)wolfssl_shutdown_send_close_notify(ssl, &ret); + } + if ((ret == WC_NO_ERR_TRACE(WOLFSSL_SHUTDOWN_NOT_DONE)) && + (ssl->error != WC_NO_ERR_TRACE(WANT_WRITE))) { + ret = WOLFSSL_SUCCESS; + } + } else { int done; diff --git a/tests/api/test_ssl_rw.c b/tests/api/test_ssl_rw.c index 2e461f9d500..38e6c25f77f 100644 --- a/tests/api/test_ssl_rw.c +++ b/tests/api/test_ssl_rw.c @@ -1062,6 +1062,167 @@ int test_wolfSSL_SendUserCanceled_paths(void) return EXPECT_RESULT(); } +/* Test that quiet shutdown does not suppress the close_notify that the + * user_canceled alert obliges wolfSSL to send. + * + * RFC 9846 Section 6.1 has a "close_notify" following "user_canceled" and has + * the peer keep reading until it arrives. Quiet shutdown may drop the + * close_notify that stands alone - that is what the option is for - but not + * the one the peer has been told to wait for. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_SendUserCanceled_quiet_shutdown(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) && (defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + char reply[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_c != NULL) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_set_quiet_shutdown(ssl_c, 1); + } + + /* Both alerts go out. Waiting for the peer's reply is what quiet shutdown + * skips, so the shutdown is done as far as this side is concerned. */ + if (ssl_c != NULL) { + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_SUCCESS); + } + + /* The server reads the user_canceled and then the close_notify, which is + * what it reports. Without the close_notify it would still be waiting. */ + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), WOLFSSL_ERROR_ZERO_RETURN); + ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), WOLFSSL_RECEIVED_SHUTDOWN); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* A quiet shutdown with no user_canceled behind it still sends nothing: + * that is the whole point of the option. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + } + + /* Nothing arrived, so the server is still waiting for a record. */ + ExpectIntLT(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Test that a deferred user_canceled alert still gets its close_notify under + * quiet shutdown. + * + * When the transport cannot take the user_canceled alert right away it is + * left in the output buffer and WANT_WRITE reported. The close_notify RFC + * 9846 Section 6.1 requires after it is owed all the same, so a retried + * wolfSSL_shutdown() has to flush the buffered alert and send the + * close_notify behind it rather than take the quiet shutdown shortcut. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) && (defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + char reply[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_c != NULL) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_set_quiet_shutdown(ssl_c, 1); + } + + /* The transport takes nothing, so the user_canceled alert is left in the + * output buffer with WANT_WRITE reported. */ + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + if (ssl_c != NULL) { + ExpectIntGT(ssl_c->buffers.outputBuffer.length, 0); + /* Nothing has been sent, so no close_notify has been attempted. */ + ExpectIntEQ(ssl_c->options.sentNotify, 0); + } + ExpectIntEQ(test_ctx.s_len, 0); + + /* Retrying while the transport is still blocked keeps the alert buffered + * and reports WANT_WRITE again - the quiet shutdown shortcut must not + * claim success and drop it. */ + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + if (ssl_c != NULL) { + ExpectIntGT(ssl_c->buffers.outputBuffer.length, 0); + } + ExpectIntEQ(test_ctx.s_len, 0); + + /* Once the transport takes data again the retry flushes the buffered + * user_canceled and sends the close_notify behind it. */ + test_memio_simulate_want_write(&test_ctx, 1, 0); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->buffers.outputBuffer.length, 0); + } + /* Two records went out: the user_canceled that was stuck in the buffer + * and the close_notify sent after it. The flags cannot be checked here - + * a successful shutdown resets them through wolfSSL_clear(). */ + ExpectIntEQ(test_ctx.s_msg_count, 2); + + /* The server reads the user_canceled and then the close_notify, which is + * what it reports. Without the close_notify it would still be waiting. */ + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), WOLFSSL_ERROR_ZERO_RETURN); + ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), WOLFSSL_RECEIVED_SHUTDOWN); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* Test that an error the read side recorded is the one the write reports. * * With a write duplicate in use the read side hands errors over through diff --git a/tests/api/test_ssl_rw.h b/tests/api/test_ssl_rw.h index 169638b9480..5babdf7f798 100644 --- a/tests/api/test_ssl_rw.h +++ b/tests/api/test_ssl_rw.h @@ -38,6 +38,8 @@ int test_wolfSSL_shutdown_repeat_after_done(void); int test_wolfSSL_shutdown_flush_no_notify(void); int test_wolfSSL_shutdown_quic_alert_refused(void); int test_wolfSSL_SendUserCanceled_paths(void); +int test_wolfSSL_SendUserCanceled_quiet_shutdown(void); +int test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write(void); int test_wolfSSL_write_dup_err(void); #define TEST_SSL_RW_DECLS \ @@ -56,6 +58,10 @@ int test_wolfSSL_write_dup_err(void); TEST_DECL_GROUP("ssl_rw", test_wolfSSL_shutdown_flush_no_notify), \ TEST_DECL_GROUP("ssl_rw", test_wolfSSL_shutdown_quic_alert_refused), \ TEST_DECL_GROUP("ssl_rw", test_wolfSSL_SendUserCanceled_paths), \ + TEST_DECL_GROUP("ssl_rw", \ + test_wolfSSL_SendUserCanceled_quiet_shutdown), \ + TEST_DECL_GROUP("ssl_rw", \ + test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write), \ TEST_DECL_GROUP("ssl_rw", test_wolfSSL_write_dup_err) #endif /* TESTS_API_SSL_RW_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index ebc0e7b5245..36a3de62ac5 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5453,6 +5453,8 @@ struct Options { word16 isClosed:1; /* if we consider conn closed */ word16 closeNotify:1; /* we've received a close notify */ word16 sentNotify:1; /* we've sent a close notify */ + word16 sentUserCanceled:1; /* we've sent a user_canceled and + * owe the peer a close notify */ word16 usingCompression:1; /* are we using compression */ word16 haveRSA:1; /* RSA available */ word16 haveECC:1; /* ECC available */ From c9555caf663d81a6d7aea481397b52457f068388 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Thu, 3 Sep 2026 13:16:02 -0400 Subject: [PATCH 2/2] Ensure close_notify is sent after user_canceled during quiet shutdown - clean up logic --- doc/dox_comments/header_files/ssl.h | 81 +++- src/internal.c | 49 +++ src/ssl.c | 8 + src/ssl_api_rw.c | 152 ++++++- tests/api/test_dtls.c | 10 + tests/api/test_ssl_rw.c | 661 +++++++++++++++++++++++++++- tests/api/test_ssl_rw.h | 7 + wolfssl/internal.h | 7 +- wolfssl/ssl.h | 13 +- 9 files changed, 961 insertions(+), 27 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index a58c6b4f202..061521b8316 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -827,6 +827,13 @@ int wolfSSL_dtls_set_export(WOLFSSL* ssl, wc_dtls_export func); passed to function then sz will be set to the size of buffer needed for serializing the WOLFSSL session. + One piece of shutdown state is not carried: a "user_canceled" alert sent + with wolfSSL_SendUserCanceled() owes the peer a close notify, and that + obligation is not part of the serialized form. A session exported between + the two alerts and imported elsewhere loses it, so a quiet shutdown on the + imported session sends nothing. Complete the shutdown before exporting to + avoid it. + \return Success If successful, the amount of the buffer used will be returned. \return Failure All unsuccessful return values will be less than 0. @@ -867,6 +874,13 @@ int wolfSSL_dtls_export(WOLFSSL* ssl, unsigned char* buf, WARNING: buf contains sensitive information about the state and is best to be encrypted before storing if stored. + As with wolfSSL_dtls_export(), a "user_canceled" alert sent with + wolfSSL_SendUserCanceled() owes the peer a close notify, and that + obligation is not part of the serialized form. A session exported between + the two alerts and imported elsewhere loses it, so a quiet shutdown on the + imported session sends nothing. Complete the shutdown before exporting to + avoid it. + \return the number of bytes written into buffer 'buf' \param ssl WOLFSSL structure to export the session from @@ -2541,6 +2555,31 @@ void wolfSSL_free(WOLFSSL* ssl); previously found none; call wolfSSL_ERR_clear_error() if leftover entries matter. + With quiet shutdown enabled through wolfSSL_set_quiet_shutdown() no close + notify is sent and SSL_SUCCESS is returned. That covers the already closed + or reset connection described above as well: quiet shutdown reports + success for it rather than SSL_FATAL_ERROR with SOCKET_PEER_CLOSED_E, or + SSL_SHUTDOWN_ALREADY_DONE_E where WOLFSSL_SHUTDOWNONCE is defined. + + The shutdown is recorded as complete, as OpenSSL records it, so + wolfSSL_get_error() reports SSL_ERROR_ZERO_RETURN, and under OPENSSL_EXTRA + or WOLFSSL_WPAS_SMALL wolfSSL_get_shutdown() reports a full bidirectional + shutdown. Other builds have no record to report - the option bits are + returned as they stand, so 0 when no alert was sent. Under OPENSSL_EXTRA + with WOLFSSL_ERROR_CODE_OPENSSL a later wolfSSL_read() reports failure + instead of returning buffered application data. + + The exception is a connection that has sent a "user_canceled" alert with + wolfSSL_SendUserCanceled(). RFC 9846 Section 6.1 obliges that alert to be + followed by a close notify, so quiet shutdown sends it. The call can + therefore perform I/O and return SSL_FATAL_ERROR. Ask wolfSSL_get_error() + which one it is: SSL_ERROR_WANT_WRITE means the alert is buffered and the + call should be repeated when the transport is ready, while any other error + is the transport's own and is not retryable. It never waits for the peer's + reply. On QUIC, a send_alert callback that refuses the alert leaves + nothing sent, and the result is SSL_SHUTDOWN_NOT_DONE just as it is + without quiet shutdown. + \param ssl pointer to the SSL session created with wolfSSL_new(). _Example_ @@ -2551,8 +2590,11 @@ void wolfSSL_free(WOLFSSL* ssl); WOLFSSL* ssl = 0; ... ret = wolfSSL_shutdown(ssl); - if (ret != 0) { - // failed to shut down SSL connection + if (ret == WOLFSSL_SHUTDOWN_NOT_DONE) { + // the peer has yet to reply - call again to complete the exchange + } + else if (ret != WOLFSSL_SUCCESS) { + // failed to shut down SSL connection, see wolfSSL_get_error() } \endcode @@ -2576,11 +2618,26 @@ int wolfSSL_shutdown(WOLFSSL* ssl); which is also WOLFSSL_FAILURE, so in that configuration the return value alone does not separate this case from the one below. \return WOLFSSL_FAILURE when ssl is NULL or the alert could not be sent. - Call wolfSSL_get_error() for the reason. + Call wolfSSL_get_error() for the reason. SSL_ERROR_WANT_WRITE usually + means the alert is queued, or held for the next send to retry, rather than + lost, so the peer is owed the close notify behind it; where the alert + could not be taken on at all nothing is owed and a quiet shutdown stays + silent. Either way, call wolfSSL_shutdown() again once the transport is + ready rather than abandoning the connection - it sends what is owed and + nothing more. \return WOLFSSL_FATAL_ERROR when the shutdown that follows the alert fails. Call wolfSSL_get_error() for the reason. \return SSL_SHUTDOWN_ALREADY_DONE_E when the connection was already shut - down and WOLFSSL_SHUTDOWNONCE is defined. + down and WOLFSSL_SHUTDOWNONCE is defined. Quiet shutdown reports + WOLFSSL_SUCCESS for that state instead. + + Quiet shutdown, set with wolfSSL_set_quiet_shutdown(), does not suppress + the close notify here: RFC 9846 Section 6.1 obliges it to follow the + "user_canceled" alert. What it skips is waiting for the peer's reply, so + the result is WOLFSSL_SUCCESS rather than WOLFSSL_SHUTDOWN_NOT_DONE once + both alerts have gone out. When the close notify could not be handed to + the transport at all - a QUIC send_alert callback that refuses it - + WOLFSSL_SHUTDOWN_NOT_DONE is still what is reported. \param ssl pointer to the SSL session, created with wolfSSL_new(). @@ -2592,7 +2649,21 @@ int wolfSSL_shutdown(WOLFSSL* ssl); ret = wolfSSL_SendUserCanceled(ssl); if (ret != WOLFSSL_SUCCESS) { - // failed to shut the connection down, see wolfSSL_get_error() + // Ask the error, not the return value: under WOLFSSL_ERROR_CODE_OPENSSL + // WOLFSSL_SHUTDOWN_NOT_DONE and WOLFSSL_FAILURE are both 0. + int err = wolfSSL_get_error(ssl, ret); + + if (err == WOLFSSL_ERROR_WANT_WRITE) { + // the alert is buffered - call wolfSSL_shutdown() again when the + // transport is ready so the close notify follows it + } + else if (err == WOLFSSL_ERROR_NONE) { + // both alerts are out and the peer has yet to reply - call + // wolfSSL_shutdown() again to finish the exchange + } + else { + // failed to shut the connection down, see wolfSSL_get_error() + } } \endcode diff --git a/src/internal.c b/src/internal.c index d5f3d561030..9f4b5769f31 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1382,6 +1382,11 @@ static int ExportOptions(WOLFSSL* ssl, byte* exp, word32 len, byte ver, exp[idx++] = options->isClosed; exp[idx++] = options->closeNotify; exp[idx++] = options->sentNotify; + /* options->sentUserCanceled is deliberately not exported alongside these: + * adding a field needs a WOLFSSL_EXPORT_VERSION bump, and the obligation + * only lives between the "user_canceled" and the "close_notify" behind + * it. A connection exported inside that window loses it, so a quiet + * shutdown on the imported object sends nothing. */ exp[idx++] = options->usingCompression; exp[idx++] = options->haveRSA; exp[idx++] = options->haveECC; @@ -1513,6 +1518,12 @@ static int ImportOptions(WOLFSSL* ssl, const byte* exp, word32 len, byte ver, int idx = 0; Options* options = &ssl->options; + /* Not on the wire - see ExportOptions() - so reset it up front rather + * than among the positional reads below, and before any of the early + * returns: an imported object must not inherit an obligation from the + * connection it used to hold, whether or not the decode completes. */ + options->sentUserCanceled = 0; + switch (ver) { case WOLFSSL_EXPORT_VERSION: if (len < DTLS_EXPORT_OPT_SZ) { @@ -8271,6 +8282,12 @@ int ReinitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) (void)ctx; ssl->options.shutdownDone = 0; + /* The obligation to send a "close_notify" belongs to the connection that + * sent the "user_canceled", not to the object. wolfSSL_clear() also + * clears it, but wolfSSL_shutdown() only calls that where the OpenSSL + * compatibility layer is built in, and a stale bit would have the next + * quiet shutdown send an alert the application asked it not to send. */ + ssl->options.sentUserCanceled = 0; if (ssl->session != NULL) ssl->session->side = (byte)ssl->options.side; @@ -29413,6 +29430,11 @@ static int SendAlert_ex(WOLFSSL* ssl, int severity, int type) if (ret) { WOLFSSL_MSG("QUIC send_alert callback error"); } + else if (type == user_canceled) { + /* Handed to the peer, so the "close_notify" behind it is owed - + * see the record-queued case below. */ + ssl->options.sentUserCanceled = 1; + } return ret; } #endif @@ -29421,6 +29443,12 @@ static int SendAlert_ex(WOLFSSL* ssl, int severity, int type) if (ssl->dupWrite && ssl->dupSide == READ_DUP_SIDE) { int notifyErr = 0; + /* Returning 0 here for a warning alert other than close_notify says + * "suppressed", not "sent" - nothing goes out, and returning before + * the pendingAlert assignment below is what leaves + * options.sentUserCanceled clear for it. wolfSSL_shutdown() reads + * that bit to decide whether a "close_notify" is owed, so a change + * that starts forwarding these alerts has to set it here. */ WOLFSSL_MSG("Read dup side cannot write alerts, notifying sibling"); if (type == close_notify) { @@ -29440,6 +29468,27 @@ static int SendAlert_ex(WOLFSSL* ssl, int severity, int type) ssl->pendingAlert.code = type; ssl->pendingAlert.level = severity; + if (type == user_canceled) { + /* From here the alert reaches the peer one way or another: it is + * queued below, or it stays in pendingAlert for the next SendAlert() + * to retry even if this attempt returns early. A failing send does + * not take back what is already in the output buffer either. So the + * "close_notify" RFC 9846 Section 6.1 requires behind it is owed from + * this point, and wolfSSL_shutdown() reads this to send it even under + * quiet shutdown. Recording it here rather than in + * wolfSSL_SendUserCanceled() keeps it where whether the alert was + * taken on is actually known: the paths that drop the alert outright, + * the read side of a write duplicate and an alert dropped over one + * already pending, both return before reaching this. + * + * It is recorded slightly early rather than late. An alert left in + * pendingAlert by a failed send can still be displaced by a later + * fatal alert, which drops it after this point - the obligation then + * stands for an alert the peer never saw, costing an unasked-for + * "close_notify" rather than a missing one. */ + ssl->options.sentUserCanceled = 1; + } + #ifdef OPENSSL_EXTRA if (ssl->CBIS != NULL) { ssl->CBIS(ssl, WOLFSSL_CB_ALERT, type); diff --git a/src/ssl.c b/src/ssl.c index 9f5e0ccc013..e111a301e68 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -996,6 +996,14 @@ static int DupSSL(WOLFSSL* dup, WOLFSSL* ssl) dup->dupSide = WRITE_DUP_SIDE; ssl->dupSide = READ_DUP_SIDE; + /* options was copied wholesale above, so a "user_canceled" already sent + * carries its sentUserCanceled obligation into both objects. Neither can + * discharge it alone once the alert is only half out: the "close_notify" + * owed behind it can only be sent from the write side, while an alert + * record still queued stays in this side's output buffer, which the + * duplicate does not take with it. Complete the shutdown before + * duplicating rather than relying on either side to finish it. */ + return 0; } diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 1354c5403e0..05345e5bc00 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -799,11 +799,23 @@ int wolfSSL_recv(WOLFSSL* ssl, void* data, int sz, int flags) #endif /* Send a user_canceled alert to the peer and shut down the connection. + * + * Records the alert in options.sentUserCanceled when it has been sent or + * buffered. That is what has wolfSSL_shutdown() send the "close_notify" RFC + * 9846 Section 6.1 obliges to follow it even when quiet shutdown is on, so a + * caller that gets WOLFSSL_FAILURE here with wolfSSL_get_error() reporting + * WANT_WRITE must still call wolfSSL_shutdown() again rather than give up: + * the alert is usually queued, or held for the next send to retry, with the + * close_notify owed behind it. Where SendAlert() could not take the alert on + * at all nothing is recorded and that shutdown stays silent, so retrying is + * right either way. * * @param [in, out] ssl SSL/TLS object. * @return WOLFSSL_SUCCESS on success. * @return WOLFSSL_SHUTDOWN_NOT_DONE when the shutdown is not complete. * @return WOLFSSL_FAILURE when ssl is NULL or sending the alert fails. + * @return WOLFSSL_FATAL_ERROR when the shutdown behind the alert fails. + * Call wolfSSL_get_error() for the reason. */ int wolfSSL_SendUserCanceled(WOLFSSL* ssl) { @@ -811,11 +823,11 @@ int wolfSSL_SendUserCanceled(WOLFSSL* ssl) WOLFSSL_ENTER("wolfSSL_SendUserCanceled"); if (ssl != NULL) { + /* SendAlert() records options.sentUserCanceled when this alert + * reached the peer or was queued where it still can. That is what has + * wolfSSL_shutdown() send the "close_notify" behind it even under + * quiet shutdown. */ ssl->error = SendAlert(ssl, alert_warning, user_canceled); - if ((ssl->error == 0) || - (ssl->error == WC_NO_ERR_TRACE(WANT_WRITE))) { - ssl->options.sentUserCanceled = 1; - } if (ssl->error < 0) { WOLFSSL_ERROR(ssl->error); } @@ -943,6 +955,59 @@ static int wolfssl_shutdown_send_close_notify(WOLFSSL* ssl, int* ret) return done; } +/* Whether this object owes the peer a "close_notify" behind a "user_canceled". + * + * @param [in] ssl SSL/TLS object. + * @return 1 when the alert has gone out and this side can still send the + * "close_notify" RFC 9846 Section 6.1 requires behind it. + * @return 0 otherwise. + */ +static int wolfssl_shutdown_owes_close_notify(const WOLFSSL* ssl) +{ + if (!ssl->options.sentUserCanceled) { + return 0; + } + +#ifdef HAVE_WRITE_DUP + /* The read side of a write duplicate cannot put an alert on the wire - + * its SendAlert() only notifies the sibling - so it cannot discharge the + * obligation and must not try. Both objects carry the bit when the + * duplicate is taken with one outstanding; the write side is the one that + * can act on it. */ + if ((ssl->dupWrite != NULL) && (ssl->dupSide == READ_DUP_SIDE)) { + WOLFSSL_MSG("read dup side cannot send the owed close notify"); + return 0; + } +#endif + + return 1; +} + +/* Get this side's alerts out: flush one the transport would not take earlier, + * then send the close_notify if it has not gone yet. + * + * Both callers need the same sequence, so the helpers' out-parameter + * convention is applied in one place rather than two. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in, out] ret Result for wolfSSL_shutdown() to return. Written only + * on the paths that reach a decision, as the two + * helpers document. + * @return 1 when no later step of the shutdown may run. + * @return 0 when the caller carries on with the rest of the shutdown. + */ +static int wolfssl_shutdown_send_alerts(WOLFSSL* ssl, int* ret) +{ + /* Try to flush the buffer first, it might contain the alert */ + int done = wolfssl_shutdown_flush_alert(ssl, ret); + + if (!done) { + done = wolfssl_shutdown_send_close_notify(ssl, ret); + } + + return done; +} + /* Wait for the peer's close_notify alert to complete a bidirectional shutdown. * * Called when this side has sent its close_notify but has not seen the @@ -1026,6 +1091,20 @@ static int wolfssl_shutdown_recv_close_notify(WOLFSSL* ssl) * after tearing down an already-aborted connection now finds an entry where * it previously found none. Clear it with wolfSSL_ERR_clear_error() if * leftover entries matter to the caller. + * + * With quiet shutdown (wolfSSL_set_quiet_shutdown()) nothing is sent and + * WOLFSSL_SUCCESS is reported, for the already closed or reset connection + * above as much as for a live one - the caller asked not to be told about the + * teardown, which is what OpenSSL's quiet shutdown does too. The shutdown is + * recorded as complete in options.shutdownDone either way. + * + * The exception is a connection that has sent a "user_canceled" alert: + * RFC 9846 Section 6.1 obliges a "close_notify" behind it, so quiet shutdown + * sends that one. The call can then do I/O and report WOLFSSL_FATAL_ERROR, + * to be retried when wolfSSL_get_error() returns WANT_WRITE and not otherwise + * - a send that fails outright reports it too, with the transport's error - + * or WOLFSSL_SHUTDOWN_NOT_DONE where the alert could not be handed to the + * transport at all. It never waits for the peer's reply. */ WOLFSSL_ABI int wolfSSL_shutdown(WOLFSSL* ssl) @@ -1038,30 +1117,63 @@ int wolfSSL_shutdown(WOLFSSL* ssl) if (ssl == NULL) { ret = WOLFSSL_FATAL_ERROR; } - else if (ssl->options.quietShutdown && (!ssl->options.sentUserCanceled)) { + else if (ssl->options.quietShutdown && + (!wolfssl_shutdown_owes_close_notify(ssl))) { WOLFSSL_MSG("quiet shutdown, no close notify sent"); + /* Nothing goes out, but the shutdown is over as far as this side is + * concerned, so record it the way the branch below and OpenSSL's + * quiet shutdown do. Clear the error with it, as + * wolfssl_shutdown_recv_close_notify() does: wolfSSL_get_error() + * looks at WANT_READ and WANT_WRITE before the shutdown state, so one + * left by an earlier call would outrank the record. Every other error + * goes too. Keeping one would not surface it anyway - shutdownDone + * already has wolfSSL_get_error() report ZERO_RETURN for anything + * that is not WANT_READ or WANT_WRITE. */ + ssl->options.shutdownDone = 1; + ssl->error = WOLFSSL_ERROR_NONE; ret = WOLFSSL_SUCCESS; } else if (ssl->options.quietShutdown) { - /* A "user_canceled" alert has gone out so we need a "close_notify" to - * follow it per RFC 9846 Section 6.1. */ - ret = WOLFSSL_SUCCESS; - if (!wolfssl_shutdown_flush_alert(ssl, &ret)) { - (void)wolfssl_shutdown_send_close_notify(ssl, &ret); + int done; + + WOLFSSL_MSG("quiet shutdown, sending close notify owed by " + "user_canceled"); + /* The result is left as the WOLFSSL_FATAL_ERROR this function starts + * with, which is what the helpers leave behind when they reach no + * decision - the same seeding the non-quiet path below relies on. A + * helper that decides without writing a result is then reported as a + * failure rather than read as a success. */ + done = wolfssl_shutdown_send_alerts(ssl, &ret); + + if ((!done) && (ret == WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR))) { + /* Undecided: no "close_notify" can ever go out - the connection + * is closed or reset, or one has been sent already. */ + ret = WOLFSSL_SUCCESS; } if ((ret == WC_NO_ERR_TRACE(WOLFSSL_SHUTDOWN_NOT_DONE)) && - (ssl->error != WC_NO_ERR_TRACE(WANT_WRITE))) { + (ssl->error == WOLFSSL_ERROR_NONE)) { + /* Only the peer's reply is missing, which quiet shutdown does not + * wait for. The error is checked because one path reaches this + * having sent nothing: a QUIC send_alert callback that refuses the + * alert makes SendAlert() return a positive value, which is + * neither the success that sets sentNotify nor the negative error + * the helper reports as a failure. */ ret = WOLFSSL_SUCCESS; } + if (ret == WOLFSSL_SUCCESS) { + /* The wolfSSL_clear() below clears sentNotify, so without this + * wolfSSL_get_shutdown() would report 0 for a connection whose + * "close_notify" has just gone out. The error goes with it, as in + * the branch above - an undecided result gets here with whatever + * the last operation recorded. */ + ssl->options.shutdownDone = 1; + ssl->error = WOLFSSL_ERROR_NONE; + } } else { int done; - /* Try to flush the buffer first, it might contain the alert */ - done = wolfssl_shutdown_flush_alert(ssl, &ret); - if (!done) { - done = wolfssl_shutdown_send_close_notify(ssl, &ret); - } + done = wolfssl_shutdown_send_alerts(ssl, &ret); #ifdef WOLFSSL_SHUTDOWNONCE if ((!done) && @@ -1108,6 +1220,14 @@ int wolfSSL_shutdown(WOLFSSL* ssl) } } + /* A completed shutdown discharges the obligation whichever path reached + * it: the "close_notify" is out, or it never can be. Doing it here rather + * than leaving it to the wolfSSL_clear() below keeps the bit's meaning + * independent of which compatibility layer is built in. */ + if ((ssl != NULL) && (ret == WOLFSSL_SUCCESS)) { + ssl->options.sentUserCanceled = 0; + } + #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) /* reset WOLFSSL structure state for possible reuse */ if (ret == WOLFSSL_SUCCESS) { diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 97c523863ff..713d933c9a1 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -3066,7 +3066,17 @@ int test_wolfSSL_dtls_export(void) ExpectNotNull(ssl = wolfSSL_new(ctx)); /* test importing version 3 */ + if (ssl != NULL) { + /* An obligation left by an earlier connection on this object must + * not survive: the imported session's shutdown state is what + * counts, and a stale bit would have the next quiet shutdown send + * an alert the application asked it not to send. */ + ssl->options.sentUserCanceled = 1; + } ExpectIntGE(wolfSSL_dtls_import(ssl, version_3, sizeof(version_3)), 0); + if (ssl != NULL) { + ExpectIntEQ(ssl->options.sentUserCanceled, 0); + } /* test importing bad length and bad version */ version_3[2]++; diff --git a/tests/api/test_ssl_rw.c b/tests/api/test_ssl_rw.c index 38e6c25f77f..47e55853a37 100644 --- a/tests/api/test_ssl_rw.c +++ b/tests/api/test_ssl_rw.c @@ -777,6 +777,26 @@ static const WOLFSSL_QUIC_METHOD test_ssl_rw_quic_method = { test_ssl_rw_quic_flush, test_ssl_rw_quic_send_alert_fail }; + +/* QUIC alert send that takes the alert. + * + * @return 1 always, which the QUIC layer reads as success. + */ +static int test_ssl_rw_quic_send_alert_ok(WOLFSSL* ssl, + WOLFSSL_ENCRYPTION_LEVEL level, uint8_t alertType) +{ + (void)ssl; + (void)level; + (void)alertType; + return 1; +} + +static const WOLFSSL_QUIC_METHOD test_ssl_rw_quic_method_ok = { + test_ssl_rw_quic_secrets, + test_ssl_rw_quic_add_hs, + test_ssl_rw_quic_flush, + test_ssl_rw_quic_send_alert_ok +}; #endif /* Test that a refused close_notify send does not undo a shutdown the peer @@ -796,6 +816,8 @@ int test_wolfSSL_shutdown_quic_alert_refused(void) !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; + WOLFSSL_CTX* ctx_ok = NULL; + WOLFSSL* ssl_ok = NULL; ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); ExpectIntEQ(wolfSSL_CTX_set_quic_method(ctx, &test_ssl_rw_quic_method), @@ -808,6 +830,38 @@ int test_wolfSSL_shutdown_quic_alert_refused(void) ExpectIntEQ(ssl->options.shutdownDone, 1); } + wolfSSL_free(ssl); + ssl = NULL; + + /* A user_canceled the QUIC callback accepts has reached the peer, so the + * close_notify behind it is owed - the alert never goes through the + * output buffer on this path. */ + ExpectNotNull(ctx_ok = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_quic_method(ctx_ok, + &test_ssl_rw_quic_method_ok), WOLFSSL_SUCCESS); + ExpectNotNull(ssl_ok = wolfSSL_new(ctx_ok)); + if (ssl_ok != NULL) { + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_ok), + WOLFSSL_SHUTDOWN_NOT_DONE); + ExpectIntEQ(ssl_ok->options.sentUserCanceled, 1); + } + wolfSSL_free(ssl_ok); + wolfSSL_CTX_free(ctx_ok); + + /* The same refusal on the quiet shutdown path, where a user_canceled owes + * the peer a close_notify and the peer's has not arrived. Nothing was + * sent, so there is no completed shutdown to report: the result has to + * stay "call again", as it is without quiet shutdown, rather than become + * the success the rest of that branch reports. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ssl->options.quietShutdown = 1; + ssl->options.sentUserCanceled = 1; + ExpectIntEQ(wolfSSL_shutdown(ssl), WOLFSSL_SHUTDOWN_NOT_DONE); + ExpectIntEQ(ssl->options.sentNotify, 0); + ExpectIntEQ(ssl->options.shutdownDone, 0); + } + wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif @@ -1062,6 +1116,41 @@ int test_wolfSSL_SendUserCanceled_paths(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) && (defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) +/* A transport that fails outright rather than asking to be called again. */ +static int test_ssl_rw_send_fail(WOLFSSL* ssl, char* buf, int sz, void* ctx) +{ + (void)ssl; + (void)buf; + (void)sz; + (void)ctx; + return WOLFSSL_CBIO_ERR_GENERAL; +} +#endif + +/* The shutdown state wolfSSL_get_shutdown() reports once a quiet shutdown has + * succeeded, having sent a close_notify and having sent nothing. + * + * Where the OpenSSL compatibility layer is built in, a successful + * wolfSSL_shutdown() records options.shutdownDone and then clears the option + * bits through wolfSSL_clear(); wolfSSL_get_shutdown() reads shutdownDone and + * reports a full bidirectional shutdown either way. Where it is not - + * wolfSSL_set_quiet_shutdown() is also available under OPENSSL_EXTRA_X509_SMALL + * and WOLFSSL_EXTRA - neither happens, and the option bits are reported as they + * stand. */ +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) + #define TEST_SHUTDOWN_STATE_NOTIFY_SENT \ + (WOLFSSL_SENT_SHUTDOWN | WOLFSSL_RECEIVED_SHUTDOWN) + #define TEST_SHUTDOWN_STATE_NOTHING_SENT \ + (WOLFSSL_SENT_SHUTDOWN | WOLFSSL_RECEIVED_SHUTDOWN) +#else + #define TEST_SHUTDOWN_STATE_NOTIFY_SENT WOLFSSL_SENT_SHUTDOWN + #define TEST_SHUTDOWN_STATE_NOTHING_SENT 0 +#endif + /* Test that quiet shutdown does not suppress the close_notify that the * user_canceled alert obliges wolfSSL to send. * @@ -1099,6 +1188,15 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown(void) * skips, so the shutdown is done as far as this side is concerned. */ if (ssl_c != NULL) { ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_SUCCESS); + /* Two records: the user_canceled and the close_notify owed behind it. + * This is the property the fix is about - the state macros below + * cannot tell the two apart where wolfSSL_clear() runs. */ + ExpectIntEQ(test_ctx.s_msg_count, 2); + /* The close_notify went out, so the shutdown state has to show it. + * The option bits cannot be checked directly - a successful shutdown + * clears them through wolfSSL_clear(). */ + ExpectIntEQ(wolfSSL_get_shutdown(ssl_c), + TEST_SHUTDOWN_STATE_NOTIFY_SENT); } /* The server reads the user_canceled and then the close_notify, which is @@ -1107,6 +1205,48 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown(void) ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), WOLFSSL_ERROR_ZERO_RETURN); ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), WOLFSSL_RECEIVED_SHUTDOWN); + /* The obligation belongs to the connection that sent the user_canceled, + * not to the object, so a successful shutdown gives it up - in every + * build, not only those that reach wolfSSL_clear(). */ + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.sentUserCanceled, 0); + } + + /* Reuse the client for a second connection. Reusing an object needs + * wolfSSL_clear(), which the shutdown above only ran in some builds, so + * call it here either way - and then set the bit to stand in for the + * builds where it survives the shutdown. ReinitSSL(), which the next + * handshake runs, has to clear it too, or this connection's quiet + * shutdown would send an alert the application asked it not to send. */ + wolfSSL_free(ssl_s); + ssl_s = NULL; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + /* Passing no client keeps ssl_c, and the contexts are reused as they are. + */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ssl_c != NULL) { + /* wolfSSL_clear() clears the obligation too - put one back first so + * that reset is checked rather than assumed. */ + ssl_c->options.sentUserCanceled = 1; + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_c->options.sentUserCanceled, 0); + + ssl_c->options.sentUserCanceled = 1; + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.sentUserCanceled, 0); + } + ExpectIntEQ(test_ctx.s_len, 0); + + /* Nothing is owed on this connection, so the quiet shutdown sends + * nothing. */ + if (ssl_c != NULL) { + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_ctx.s_len, 0); + wolfSSL_free(ssl_c); ssl_c = NULL; wolfSSL_free(ssl_s); @@ -1126,6 +1266,10 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown(void) if (ssl_c != NULL) { wolfSSL_set_quiet_shutdown(ssl_c, 1); ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + /* Sending nothing is not the same as not shutting down: this side is + * done, so the state says so even though no alert went out. */ + ExpectIntEQ(wolfSSL_get_shutdown(ssl_c), + TEST_SHUTDOWN_STATE_NOTHING_SENT); } /* Nothing arrived, so the server is still waiting for a record. */ @@ -1133,6 +1277,463 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown(void) ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), 0); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* With the connection already closed the close_notify can never go out, + * and quiet shutdown reports success rather than the WOLFSSL_FATAL_ERROR + * and SOCKET_PEER_CLOSED_E the ordinary path records for the same state + * (test_wolfSSL_SendUserCanceled_paths covers that). Nothing is left to + * do, and the caller asked not to be told about the teardown. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + ssl_c->options.isClosed = 1; + ssl_c->options.sentNotify = 0; + ssl_c->error = WOLFSSL_ERROR_NONE; + wolfSSL_set_quiet_shutdown(ssl_c, 1); + + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_SUCCESS); + /* The shutdown is reported complete even though the close_notify the + * user_canceled owes the peer could not be sent. */ + ExpectIntEQ(wolfSSL_get_shutdown(ssl_c), + TEST_SHUTDOWN_STATE_NOTHING_SENT); + } + + /* Only the user_canceled reached the peer. */ + ExpectIntEQ(test_ctx.s_msg_count, 1); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* The option carried in from the context rather than set on the object + * reaches the same branch: it is one flag either way, but nothing else + * covers wolfSSL_CTX_set_quiet_shutdown(). Build the contexts and the + * server first so the client can be created once the context has it. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + if (ctx_c != NULL) { + wolfSSL_CTX_set_quiet_shutdown(ctx_c, 1); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + /* The object inherited it from the context. */ + ExpectIntEQ(ssl_c->options.quietShutdown, 1); + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_SUCCESS); + } + + /* Both alerts go out, as they do with the object-level option. */ + ExpectIntEQ(test_ctx.s_msg_count, 2); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), WOLFSSL_ERROR_ZERO_RETURN); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* A user_canceled the transport refused outright is still queued in the + * output buffer, where a later flush could hand it to the peer, so the + * close_notify behind it is owed and the quiet shutdown must report the + * failure rather than claim to be done. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + wolfSSL_SSLSetIOSend(ssl_c, test_ssl_rw_send_fail); + + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + /* The send failed but the record is queued, so the obligation + * stands. */ + ExpectIntEQ(ssl_c->options.sentUserCanceled, 1); + + /* The shutdown behind it therefore tries to send, and reports the + * transport's refusal rather than a completed shutdown. */ + ExpectIntEQ(wolfSSL_shutdown(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(ssl_c->options.shutdownDone, 0); + } + /* Nothing reached the peer either way. */ + ExpectIntEQ(test_ctx.s_len, 0); + ExpectIntEQ(test_ctx.s_msg_count, 0); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* The other side of that: a user_canceled has gone out and the transport + * refuses the close_notify owed behind it. Neither of the results the + * quiet path rewrites applies - the failure is reported as it stands, and + * the shutdown is neither complete nor discharged. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + /* Stand in for a user_canceled that has already gone out. */ + ssl_c->options.sentUserCanceled = 1; + wolfSSL_SSLSetIOSend(ssl_c, test_ssl_rw_send_fail); + + ExpectIntEQ(wolfSSL_shutdown(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + /* Not the retryable WANT_WRITE - this one is the transport's own. */ + ExpectIntNE(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + ExpectIntEQ(ssl_c->options.shutdownDone, 0); + /* Still owed: the caller has not been told the shutdown is done. */ + ExpectIntEQ(ssl_c->options.sentUserCanceled, 1); + } + ExpectIntEQ(test_ctx.s_len, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Test that the read side of a write duplicate owes nothing. + * + * wolfSSL_write_dup() leaves the original as the read side, where SendAlert() + * reports success for a warning alert other than close_notify without sending + * it. Nothing reached the peer, so no close_notify is owed and a quiet + * shutdown has nothing to do - it must not go on to hand the write side a + * close_notify to report. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_SendUserCanceled_write_dup(void) +{ + EXPECT_DECLS; +#if defined(HAVE_WRITE_DUP) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) && (defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL* ssl_w = NULL; + struct test_memio_ctx test_ctx; + const char msg[] = "hello"; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The original becomes the read side; the duplicate is the write side. */ + ExpectNotNull(ssl_w = wolfSSL_write_dup(ssl_c)); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_SUCCESS); + } + + /* Nothing went out. */ + ExpectIntEQ(test_ctx.s_len, 0); + /* Nothing is owed either, so the write side has not been handed a + * close_notify to report and can still write. Were the user_canceled + * recorded as sent, the close_notify owed behind it would reach the write + * side as ZERO_RETURN and this would fail. */ + ExpectIntEQ(wolfSSL_write(ssl_w, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + + wolfSSL_free(ssl_w); + ssl_w = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* The other ordering: the alert went out before the duplicate was taken, + * so both objects carry the obligation. The read side still cannot put a + * close_notify on the wire, so its shutdown must stay silent rather than + * route the alert into the sibling and abort it. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + /* Stand in for a user_canceled sent before the duplicate. */ + ssl_c->options.sentUserCanceled = 1; + } + ExpectNotNull(ssl_w = wolfSSL_write_dup(ssl_c)); + + if (ssl_c != NULL) { + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + } + /* Nothing on the wire, and the write side is untouched. */ + ExpectIntEQ(test_ctx.s_len, 0); + if (ssl_w != NULL) { + ExpectIntEQ(ssl_w->dupWrite->dupErr, 0); + ExpectIntEQ(wolfSSL_write(ssl_w, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + } + + /* The write side carries the same obligation and can act on it, so its + * quiet shutdown does send the close_notify. */ + if (ssl_w != NULL) { + ExpectIntEQ(ssl_w->options.sentUserCanceled, 1); + test_ctx.s_len = 0; + test_ctx.s_msg_count = 0; + ExpectIntEQ(wolfSSL_shutdown(ssl_w), WOLFSSL_SUCCESS); + ExpectIntEQ(test_ctx.s_msg_count, 1); + } + + wolfSSL_free(ssl_w); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Test that a user_canceled SendAlert() dropped rather than queued owes + * nothing. + * + * With another alert already pending and no room to retry it, SendAlert() + * reports WANT_WRITE having neither queued this alert nor stashed it - a + * warning alert is not stashed over a pending one, so it is dropped. Nothing + * is owed for it, and the other alert still sitting in pendingAlert is what + * says so. Only DTLS reaches this: CheckAvailableSize() flushes to stay inside + * the MTU there, and can fail before pendingAlert is cleared. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_SendUserCanceled_alert_dropped(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS_MTU) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) && (defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + char msg[192]; + word32 buffered = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(msg, 'a', sizeof(msg)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + /* An MTU small enough that one buffered record leaves no room for an + * alert behind it. */ + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, 256), WOLFSSL_SUCCESS); + wolfSSL_set_quiet_shutdown(ssl_c, 1); + } + + /* Leave a record the blocked transport will not take. */ + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntLT(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + + if (ssl_c != NULL) { + buffered = ssl_c->buffers.outputBuffer.length; + ExpectIntGT(buffered, 0); + + /* Another alert is already waiting to go out, and there is no room to + * retry it. */ + ssl_c->pendingAlert.code = close_notify; + ssl_c->pendingAlert.level = alert_warning; + + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + + /* The user_canceled was neither queued behind the record nor stashed + * over the alert that was already pending. */ + ExpectIntEQ(ssl_c->buffers.outputBuffer.length, buffered); + ExpectIntEQ(ssl_c->pendingAlert.code, close_notify); + /* So nothing is owed, and the quiet shutdown behind it stays + * silent. */ + ExpectIntEQ(ssl_c->options.sentUserCanceled, 0); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_ctx.s_len, 0); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* With nothing else pending, the same blocked-flush failure keeps the + * user_canceled in pendingAlert for the next SendAlert() to retry. It + * still reaches the peer that way, so the close_notify behind it is owed + * even though no record was ever queued. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + if (ssl_c != NULL) { + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, 256), WOLFSSL_SUCCESS); + wolfSSL_set_quiet_shutdown(ssl_c, 1); + } + + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntLT(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), 0); + + if (ssl_c != NULL) { + buffered = ssl_c->buffers.outputBuffer.length; + ExpectIntGT(buffered, 0); + + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + + /* No record was queued, but the alert is held for retry. */ + ExpectIntEQ(ssl_c->buffers.outputBuffer.length, buffered); + ExpectIntEQ(ssl_c->pendingAlert.code, user_canceled); + ExpectIntEQ(ssl_c->options.sentUserCanceled, 1); + + /* So the quiet shutdown owes a close_notify and reports the blocked + * transport rather than a completed shutdown. */ + ExpectIntEQ(wolfSSL_shutdown(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(ssl_c->options.shutdownDone, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Test what a read reports after a quiet shutdown. + * + * A quiet shutdown records the shutdown as complete, which is what OpenSSL + * does. wolfSSL_get_error() reports ZERO_RETURN from then on, and where the + * OpenSSL read behaviour is compiled in wolfSSL_read() reports failure rather + * than handing back application data that arrived before the shutdown. Both + * follow from options.shutdownDone, so a change to when that is recorded + * shows up here. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_quiet_shutdown_read_after(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) && \ + !defined(WOLFSSL_NO_TLS12) && (defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + struct test_memio_ctx test_ctx; + const char msg[] = "hello wolfssl"; + char reply[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The server sends application data the client has not read yet. */ + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + } + + /* The shutdown is recorded as complete, so this reports ZERO_RETURN + * rather than the WOLFSSL_ERROR_NONE it would without that record. */ + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_ZERO_RETURN); + +#if defined(WOLFSSL_ERROR_CODE_OPENSSL) && defined(OPENSSL_EXTRA) + /* The same record has the read report failure rather than hand back the + * data the server sent before the shutdown. */ + ExpectIntLE(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_ZERO_RETURN); +#endif + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* An error an earlier call left behind must not outrank the record: + * wolfSSL_get_error() checks WANT_READ and WANT_WRITE before it looks at + * the shutdown state, so the quiet shutdown has to clear it. Only builds + * where wolfSSL_shutdown() calls wolfSSL_clear() get that for free. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Nothing has been sent, so this leaves WANT_READ behind. */ + ExpectIntLE(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_ZERO_RETURN); + wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_c); @@ -1178,7 +1779,8 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write(void) /* The transport takes nothing, so the user_canceled alert is left in the * output buffer with WANT_WRITE reported. */ test_memio_simulate_want_write(&test_ctx, 1, 1); - ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_SendUserCanceled(ssl_c), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); if (ssl_c != NULL) { ExpectIntGT(ssl_c->buffers.outputBuffer.length, 0); @@ -1208,6 +1810,7 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write(void) * and the close_notify sent after it. The flags cannot be checked here - * a successful shutdown resets them through wolfSSL_clear(). */ ExpectIntEQ(test_ctx.s_msg_count, 2); + ExpectIntEQ(wolfSSL_get_shutdown(ssl_c), TEST_SHUTDOWN_STATE_NOTIFY_SENT); /* The server reads the user_canceled and then the close_notify, which is * what it reports. Without the close_notify it would still be waiting. */ @@ -1215,6 +1818,62 @@ int test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write(void) ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), WOLFSSL_ERROR_ZERO_RETURN); ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), WOLFSSL_RECEIVED_SHUTDOWN); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* The other way round: the user_canceled went out cleanly and the + * close_notify owed behind it is the alert the transport will not take. + * The retry lands in a different arm of the flush - the buffered alert is + * this side's own close_notify, so sentNotify already counts it - and + * that arm is what turns into the success reported below. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) { + wolfSSL_set_quiet_shutdown(ssl_c, 1); + /* Stand in for a user_canceled that has already gone out. */ + ssl_c->options.sentUserCanceled = 1; + } + + /* The close_notify cannot go out, so it is left in the output buffer with + * WANT_WRITE reported: the shutdown is not complete and must not say it + * is. This is the "call again when the transport is ready" the + * documentation promises callers. */ + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_WANT_WRITE); + if (ssl_c != NULL) { + /* The alert is this side's own close_notify, so it counts as sent + * even while it is still sitting in the buffer. */ + ExpectIntEQ(ssl_c->options.sentNotify, 1); + ExpectIntEQ(ssl_c->options.shutdownDone, 0); + ExpectIntGT(ssl_c->buffers.outputBuffer.length, 0); + } + ExpectIntEQ(test_ctx.s_len, 0); + + /* Once the transport takes data the retry flushes it, and with only the + * peer's reply left to wait for the shutdown is reported complete. */ + test_memio_simulate_want_write(&test_ctx, 1, 0); + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SUCCESS); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->buffers.outputBuffer.length, 0); + } + /* One record this time: the close_notify on its own. */ + ExpectIntEQ(test_ctx.s_msg_count, 1); + ExpectIntEQ(wolfSSL_get_shutdown(ssl_c), TEST_SHUTDOWN_STATE_NOTIFY_SENT); + + /* It reached the peer. */ + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), WOLFSSL_ERROR_ZERO_RETURN); + ExpectIntEQ(wolfSSL_get_shutdown(ssl_s), WOLFSSL_RECEIVED_SHUTDOWN); + wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_c); diff --git a/tests/api/test_ssl_rw.h b/tests/api/test_ssl_rw.h index 5babdf7f798..af68ca7d661 100644 --- a/tests/api/test_ssl_rw.h +++ b/tests/api/test_ssl_rw.h @@ -40,6 +40,9 @@ int test_wolfSSL_shutdown_quic_alert_refused(void); int test_wolfSSL_SendUserCanceled_paths(void); int test_wolfSSL_SendUserCanceled_quiet_shutdown(void); int test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write(void); +int test_wolfSSL_SendUserCanceled_write_dup(void); +int test_wolfSSL_SendUserCanceled_alert_dropped(void); +int test_wolfSSL_quiet_shutdown_read_after(void); int test_wolfSSL_write_dup_err(void); #define TEST_SSL_RW_DECLS \ @@ -62,6 +65,10 @@ int test_wolfSSL_write_dup_err(void); test_wolfSSL_SendUserCanceled_quiet_shutdown), \ TEST_DECL_GROUP("ssl_rw", \ test_wolfSSL_SendUserCanceled_quiet_shutdown_want_write), \ + TEST_DECL_GROUP("ssl_rw", test_wolfSSL_SendUserCanceled_write_dup), \ + TEST_DECL_GROUP("ssl_rw", \ + test_wolfSSL_SendUserCanceled_alert_dropped), \ + TEST_DECL_GROUP("ssl_rw", test_wolfSSL_quiet_shutdown_read_after), \ TEST_DECL_GROUP("ssl_rw", test_wolfSSL_write_dup_err) #endif /* TESTS_API_SSL_RW_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 36a3de62ac5..03f5ed45b71 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -4230,7 +4230,8 @@ struct WOLFSSL_CTX { byte haveStaticECC:1; /* static server ECC private key */ byte partialWrite:1; /* only one msg per write call */ byte autoRetry:1; /* retry read/write on a WANT_{READ|WRITE} */ - byte quietShutdown:1; /* don't send close notify */ + byte quietShutdown:1; /* don't send close notify, unless a + * user_canceled alert owes the peer one */ byte groupMessages:1; /* group handshake messages before sending */ byte minDowngrade; /* minimum downgrade version */ byte haveEMS:1; /* have extended master secret extension */ @@ -5478,7 +5479,9 @@ struct Options { #endif #endif word16 partialWrite:1; /* only one msg per write call */ - word16 quietShutdown:1; /* don't send close notify */ + word16 quietShutdown:1; /* don't send close notify, unless a + * user_canceled alert owes the peer + * one - see sentUserCanceled */ word16 certOnly:1; /* stop once we get cert */ word16 groupMessages:1; /* group handshake messages */ word16 saveArrays:1; /* save array Memory for user get keys diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index dd35a5f9291..8d6756aaded 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3240,19 +3240,26 @@ enum { /* ssl Constants */ * it returns <= 0 AND SSL_get_error returns SSL_ERROR_ZERO_RETURN. See OpenSSL * docs for more: https://www.openssl.org/docs/man1.1.1/man3/SSL_shutdown.html */ +/* The enumerator below and the CONST_NUM_ERR_ constant that WC_NO_ERR_TRACE() + * expands to in a tracing build are both taken from this, because they have to + * agree: a constant that disagrees makes every comparison against the value + * silently false. Not for application use - the enumerator is the public + * spelling of the value. */ #ifdef WOLFSSL_ERROR_CODE_OPENSSL /* SSL_shutdown returns 0 when not done, per OpenSSL documentation. */ - WOLFSSL_SHUTDOWN_NOT_DONE = 0, + #define WOLFSSL_SHUTDOWN_NOT_DONE_VAL 0 #else - WOLFSSL_SHUTDOWN_NOT_DONE = 2, + #define WOLFSSL_SHUTDOWN_NOT_DONE_VAL 2 #endif + WOLFSSL_SHUTDOWN_NOT_DONE = WOLFSSL_SHUTDOWN_NOT_DONE_VAL, #if defined(WOLFSSL_DEBUG_TRACE_ERROR_CODES) && \ (defined(BUILDING_WOLFSSL) || \ defined(WOLFSSL_DEBUG_TRACE_ERROR_CODES_ALWAYS)) #define WOLFSSL_SHUTDOWN_NOT_DONE \ WC_ERR_TRACE(WOLFSSL_SHUTDOWN_NOT_DONE) - #define CONST_NUM_ERR_WOLFSSL_SHUTDOWN_NOT_DONE 0 + #define CONST_NUM_ERR_WOLFSSL_SHUTDOWN_NOT_DONE \ + WOLFSSL_SHUTDOWN_NOT_DONE_VAL #endif WOLFSSL_FILETYPE_ASN1 = CTC_FILETYPE_ASN1,