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
81 changes: 76 additions & 5 deletions doc/dox_comments/header_files/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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_
Expand All @@ -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

Expand All @@ -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().

Expand All @@ -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

Expand Down
49 changes: 49 additions & 0 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -5660,6 +5668,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;
Expand Down
Loading
Loading