diff --git a/ChangeLog.md b/ChangeLog.md index 2c74b06564..1272dfb2e0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -170,6 +170,19 @@ configuration now sees neither the prototype nor the `client_cert_cb` typedef instead of failing to build; no other configuration changes. +* **Behavioral change (`wolfSSL_write_early_data` and the AEAD key usage + limit)**: RFC 9846, Section 5.5 adds that "it is not possible to perform a + KeyUpdate for early data; therefore, implementations MUST NOT exceed the + limits when sending early data". Reaching the limit mid-early-data + previously drove the ordinary rekey path, which emitted a KeyUpdate while + the client was still in `CLIENT_HELLO_COMPLETE` - before the handshake had + finished, where a conforming peer must reject it. The write now fails + instead, returning `WOLFSSL_FATAL_ERROR` with `wolfSSL_get_error()` + reporting `TOO_MUCH_EARLY_DATA`. Callers that hit it should complete the + handshake and send the remainder with `wolfSSL_write()`, which rekeys + normally. Reaching the limit needs roughly 23.7 million early data records + on one connection, so no practical caller is affected. + ## Fixes * **Fix (certificate manager left pointing at a released store)**: @@ -198,6 +211,43 @@ than withdrawing them, matching what a failure to allocate the `ecc_key` already did. Only affects builds with `WOLFSSL_BLIND_PRIVATE_KEY`. +* **Fix (fatal-level `user_canceled` closed a TLS 1.3 connection)**: RFC 9846, + Section 6.1 states that this alert "generally has AlertLevel=warning" and + that "receiving implementations SHOULD continue to read data from the peer + until a 'close_notify' is received". wolfSSL already exempted + `user_canceled` from the TLS 1.3 rule that all error alerts are fatal, but + both `DoAlert()` and `DoProcessAlertRecord()` acted on the AlertLevel byte + before reaching those exemptions, so a peer sending the alert at fatal level + tore the connection down and invalidated the session. The level byte + carries no meaning in TLS 1.3, and the alert is now ignored whichever level + the peer used. TLS 1.2 and earlier are unchanged: a fatal-level alert + remains fatal there. + +* **Fix (key update cap turned a peer's `update_requested` into a fatal + error)**: RFC 9846, Section 4.7.3 adds that a sender at the 2^48-1 key + update cap "MUST NOT send its own KeyUpdate ... and SHOULD instead ignore + the 'update_requested' flag". Responding to a peer's request went through + the ordinary send path, which refuses at the cap with `BAD_STATE_E`, and + that error propagated out and killed the connection. The request is now + dropped and the connection continues on its current keys until the Section + 5.5 data limits force it closed. An application-initiated + `wolfSSL_update_keys()` at the cap still reports `BAD_STATE_E`; the rule + applies only to responding to a peer. + +* **Fix (malformed extension aborted without sending `decode_error`)**: RFC + 9846, Section 4.3 adds that trailing data in an extension is forbidden and + that "receivers MUST abort the handshake with a 'decode_error' alert if + there is data left over after parsing the structure". The extension parsers + detect malformed structures, but around a third of them report it as the + wolfCrypt `BUFFER_E` rather than `BUFFER_ERROR`, and only `BUFFER_ERROR` was + mapped to an alert. `TranslateErrorToAlert()` returned `invalid_alert` for + `BUFFER_E`, which every caller treats as "send nothing", so the handshake + aborted correctly but silently and the peer saw only a dropped connection. + Both codes now map to `decode_error`. This affects `pre_shared_key`, + `psk_key_exchange_modes`, `early_data`, `cookie`, `post_handshake_auth` and + the certificate type extensions, and more generally any malformed handshake + message reported with `BUFFER_E`. + # wolfSSL Release 5.9.2 (Jun 23, 2026) Release 5.9.2 has been developed according to wolfSSL's development and QA diff --git a/configure.ac b/configure.ac index 3b997f801b..bb8fb55d86 100644 --- a/configure.ac +++ b/configure.ac @@ -6047,7 +6047,7 @@ fi # OLD TLS AC_ARG_ENABLE([oldtls], - [AS_HELP_STRING([--enable-oldtls],[Enable old TLS versions < 1.2 (default: disabled)])], + [AS_HELP_STRING([--enable-oldtls],[Enable old TLS versions < 1.2, which RFC 8996 and RFC 9846 forbid negotiating (default: disabled)])], [ ENABLED_OLD_TLS=$enableval ], [ ENABLED_OLD_TLS=no ] ) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index b1b2ebe50e..e80cf8a5c0 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -2584,6 +2584,11 @@ int wolfSSL_shutdown(WOLFSSL* ssl); \param ssl pointer to the SSL session, created with wolfSSL_new(). + \note RFC 9846, Section 6.1 requires this alert to be followed by a + close notify, which is why the shutdown is part of this call. On the + receiving side the alert is not itself an error: a TLS 1.3 peer keeps + reading until the close notify arrives, whatever AlertLevel was used. + _Example_ \code int ret = 0; @@ -14770,6 +14775,13 @@ int wolfSSL_require_psk(WOLFSSL* ssl); \return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3. \return WANT_WRITE if the writing is not ready. + \return BAD_STATE_E if the connection has already performed the maximum + number of key updates. RFC 9846, Section 4.7.3 caps a TLS 1.3 sender at + 2^48-1 key updates; beyond that the connection must be closed rather than + rekeyed. Note that a KeyUpdate arriving from the peer with + request_update set is ignored once this cap is reached, rather than + failing the connection, so only an application-initiated update reports + this error. \return WOLFSSL_SUCCESS if successful. _Example_ @@ -14800,7 +14812,8 @@ int wolfSSL_update_keys(WOLFSSL* ssl); is received. \param [in] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). - \param [out] required 0 when no key update response required. 1 when no key update response required. + \param [out] required 0 when no key update response is required. 1 when + a key update response from the peer is still outstanding. \return 0 on successful. \return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3. @@ -15622,7 +15635,12 @@ int wolfSSL_set_max_early_data(WOLFSSL* ssl, unsigned int sz); \return SIDE_ERROR if called with a server. \return BAD_STATE_E if invoked without a valid session or without a valid PSK cb - \return WOLFSSL_FATAL_ERROR if the connection is not made. + \return WOLFSSL_FATAL_ERROR if the connection is not made, or if the + AEAD key usage limit would be exceeded by this write, in which case + wolfSSL_get_error() reports TOO_MUCH_EARLY_DATA. A KeyUpdate cannot be + performed while sending early data (RFC 9846, Section 5.5), so no further + early data can be sent on this connection; complete the handshake with + wolfSSL_connect_TLSv13() and send the remainder with wolfSSL_write(). \return the amount of early data written in bytes if successful. _Example_ diff --git a/src/dtls.c b/src/dtls.c index d420fb88e3..5398766845 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -88,6 +88,15 @@ void DtlsResetState(WOLFSSL* ssl) ssl->keys.dtls_sequence_number_hi = 0; ssl->keys.dtls_sequence_number_lo = 0; + /* Forget any alert this object sent for the ClientHello being abandoned. + * DoClientHello() can send a fatal alert on the stateless path and then + * swallow the error (DtlsIgnoreError) so the object stays up waiting for + * the next ClientHello. A leftover alert_fatal in the history makes the + * "already sent a more specific fatal alert" guards suppress every later + * alert on this object, so a single malformed ClientHello from a spoofed + * address would mute alerts for every peer that follows. */ + XMEMSET(&ssl->alert_history.last_tx, 0, sizeof(ssl->alert_history.last_tx)); + /* Reset states */ ssl->options.serverState = NULL_STATE; ssl->options.clientState = NULL_STATE; diff --git a/src/internal.c b/src/internal.c index afa472608b..b99e6e976e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -104,7 +104,8 @@ * WOLFSSL_TLS13_NO_PEEK_HANDSHAKE_DONE: * Disable peek returning WANT_READ for tickets default: off * WOLFSSL_TLS13_IGNORE_AEAD_LIMITS: - * Ignore AEAD message limits from RFC 8446 default: off + * Ignore AEAD message limits from RFC 9846 5.5, which + * makes observing them a MUST default: off * WOLFSSL_DTLS13_SEND_MOREACK_DEFAULT: * Send more ACKs by default in DTLS 1.3 default: off * @@ -23958,10 +23959,30 @@ static void LogAlert(int type) #endif /* DEBUG_WOLFSSL */ } +/* RFC 9846 Section 6.1 exempts "user_canceled" from tearing the connection + * down whatever AlertLevel the peer used, because the level byte carries no + * meaning in TLS 1.3. + * + * The exemption needs a connection that has actually negotiated TLS 1.3. + * ssl->version holds the highest version this side offered until the peer's + * choice is known, so testing it would also exempt a pre-1.3 peer's fatal + * alert sent before version negotiation and leave the caller waiting for a + * handshake that is never coming. options.tls1_3 is only set once the version + * is settled - including by wolfSSL_set_session() for a TLS 1.3 session. + * + * ssl The SSL/TLS object. + * code Alert description received. + * returns 1 when the alert must be ignored rather than acted on. + */ +static int AlertIsExemptUserCanceled(const WOLFSSL* ssl, int code) +{ + return ssl->options.tls1_3 && (code == user_canceled); +} + /* process alert, return level */ #ifndef NO_SESSION_CACHE /* RFC 5246 Section 7.2.2: a TLS 1.2 session whose connection is terminated by a - * fatal alert MUST be invalidated so it cannot be resumed. (TLS 1.3 RFC 8446 + * fatal alert MUST be invalidated so it cannot be resumed. (TLS 1.3 RFC 9846 * Section 6.2 only requires closing the connection, but evicting here too is * sound defense-in-depth.) Evict the cached session (which also drops any * associated ticket). Acts on an established connection or an in-progress @@ -23975,7 +23996,7 @@ static void InvalidateSessionOnFatalAlert(WOLFSSL* ssl) return; /* Don't evict on an unauthenticated record: a TLS 1.3 plaintext alert * received under encryption (current record not decrypted) is rejected (or - * ignored) by DoAlert, and the teardown alert routes back here. RFC 8446 + * ignored) by DoAlert, and the teardown alert routes back here. RFC 9846 * 6.2 doesn't require TLS 1.3 eviction; TLS 1.2 alerts are plaintext so are * unaffected. */ if (IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) && @@ -24039,10 +24060,16 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type) { ssl->alert_history.last_rx.code = code; ssl->alert_history.last_rx.level = level; - if (level == alert_fatal) { + /* RFC 9846 Section 6.1: "user_canceled" only "generally" has + * AlertLevel=warning, and a receiver SHOULD keep reading until + * "close_notify" arrives. The level byte is meaningless in TLS 1.3, + * so do not let a peer that sends the alert at fatal level tear the + * connection down. */ + if (level == alert_fatal && + !AlertIsExemptUserCanceled(ssl, code)) { ssl->options.isClosed = 1; /* Don't send close_notify */ } - /* RFC 8446 Section 6.2: In TLS 1.3, all error alerts are implicitly + /* RFC 9846 Section 6.2: In TLS 1.3, all error alerts are implicitly * fatal regardless of the AlertLevel byte. */ if (IsAtLeastTLSv1_3(ssl->version) && code != close_notify && code != user_canceled) { @@ -24094,12 +24121,17 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type) } #ifndef NO_SESSION_CACHE /* Validated fatal alert: invalidate the session so it can't be resumed - * (RFC 5246 7.2.2; in TLS 1.3 all error alerts are fatal, RFC 8446 - * 6.2). */ - if (*type != close_notify && - (level == alert_fatal || - (IsAtLeastTLSv1_3(ssl->version) && *type != user_canceled))) + * (RFC 5246 7.2.2; in TLS 1.3 all error alerts are fatal, RFC 9846 + * 6.2). "close_notify" is not an error, and "user_canceled" is exempt + * in TLS 1.3 at any AlertLevel (RFC 9846 6.1). */ + if (IsAtLeastTLSv1_3(ssl->version)) { + if (*type != close_notify && + !AlertIsExemptUserCanceled(ssl, *type)) + InvalidateSessionOnFatalAlert(ssl); + } + else if (level == alert_fatal && *type != close_notify) { InvalidateSessionOnFatalAlert(ssl); + } #endif } return level; @@ -24949,7 +24981,9 @@ static int DoProcessAlertRecord(WOLFSSL* ssl) WOLFSSL_MSG("got ALERT!"); ret = DoAlert(ssl, ssl->buffers.inputBuffer.buffer, &ssl->buffers.inputBuffer.idx, &type); - if (ret == alert_fatal) + /* RFC 9846 Section 6.1: keep reading past a TLS 1.3 "user_canceled" until + * "close_notify" arrives, whatever AlertLevel the peer used. */ + if (ret == alert_fatal && !AlertIsExemptUserCanceled(ssl, type)) return FATAL_ERROR; else if (ret < 0) return ret; @@ -24965,7 +24999,7 @@ static int DoProcessAlertRecord(WOLFSSL* ssl) if (type == decrypt_error) return FATAL_ERROR; - /* RFC 8446 Section 6.2: In TLS 1.3, all error alerts MUST + /* RFC 9846 Section 6.2: In TLS 1.3, all error alerts MUST * be treated as fatal regardless of the AlertLevel byte. * Only close_notify (handled above) and user_canceled * are exempt. */ @@ -28594,7 +28628,7 @@ int IsSCR(WOLFSSL* ssl) !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) /* * Enforce limits specified in - * https://www.rfc-editor.org/rfc/rfc8446#section-5.5 + * https://www.rfc-editor.org/rfc/rfc9846#section-5.5 */ static int CheckTLS13AEADSendLimit(WOLFSSL* ssl) { @@ -28664,6 +28698,20 @@ static int CheckTLS13AEADSendLimit(WOLFSSL* ssl) if (w64GTE(seq, limit)) { /* cppcheck-suppress uninitvar * (false positive from cppcheck-2.13.0) */ +#ifdef WOLFSSL_EARLY_DATA + /* RFC 9846 Section 5.5: a KeyUpdate cannot be performed for early + * data, so a sender MUST NOT exceed the limits while sending it. + * There is no way to rekey at this point - the handshake has not + * finished, so a KeyUpdate here would be out of order - and the write + * has to fail instead. */ + if (ssl->options.side == WOLFSSL_CLIENT_END && + ssl->earlyData != no_early_data && + ssl->earlyData != done_early_data) { + WOLFSSL_MSG("AEAD limit reached while sending early data"); + WOLFSSL_ERROR_VERBOSE(TOO_MUCH_EARLY_DATA); + return TOO_MUCH_EARLY_DATA; + } +#endif return Tls13UpdateKeys(ssl); /* Need to generate new keys */ } @@ -28926,6 +28974,23 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) #if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) if (IsAtLeastTLSv1_3(ssl->version)) { ret = CheckTLS13AEADSendLimit(ssl); + #ifdef WOLFSSL_EARLY_DATA + /* Hitting the early data limit part way through a multi-record + * write must not throw away the records already emitted in this + * call: the caller is told to send the remainder over the + * completed handshake, and it can only work out the remainder + * from the returned count. Report the short write; the next call + * re-runs this check with nothing sent yet and fails properly. */ + if ((ret == WC_NO_ERR_TRACE(TOO_MUCH_EARLY_DATA)) && (sent > 0)) { + /* Every other short return here is gated on partialWrite, so + * leave the reason behind rather than returning a short count + * that looks like a complete one: wolfSSL_get_error(ssl, 0) + * reports TOO_MUCH_EARLY_DATA. The loop clears ssl->error only + * after a record goes out, so this survives to the caller. */ + ssl->error = ret; + break; + } + #endif if (ret != 0) { ssl->error = ret; return WOLFSSL_FATAL_ERROR; @@ -38336,7 +38401,14 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, int TranslateErrorToAlert(int err) { switch (err) { + /* RFC 9846 Section 4.3 requires a "decode_error" alert when an + * extension has data left over after its structure is parsed, and + * Section 6.2 defines the alert for any field out of range or + * message of incorrect length. The extension parsers report those + * as either BUFFER_ERROR or the wolfCrypt BUFFER_E; both must map + * here, or the handshake aborts silently with no alert sent. */ case WC_NO_ERR_TRACE(BUFFER_ERROR): + case WC_NO_ERR_TRACE(BUFFER_E): return decode_error; case WC_NO_ERR_TRACE(EXT_NOT_ALLOWED): case WC_NO_ERR_TRACE(PEER_KEY_ERROR): diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 5509947603..40949e28f0 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -205,7 +205,21 @@ static int wolfssl_write_dup_do_tls13_work(WOLFSSL* ssl) { /* keyUpdateRespond is cleared in SendTls13KeyUpdate. */ if (ssl->keys.keyUpdateRespond) { - ret = Tls13UpdateKeys(ssl); + /* RFC 9846 Section 4.7.3: a sender that would exceed the + * key update limit "MUST NOT send its own KeyUpdate ... + * and SHOULD instead ignore the 'update_requested' flag". + * The read side delegated this response without seeing the + * cap - it never sends KeyUpdates, so its count is not the + * one that matters - so the check belongs here, on the + * side that actually sends and owns the counter. */ + if (Tls13KeyUpdateLimitReached(ssl)) { + WOLFSSL_MSG("Key update limit reached; ignoring " + "delegated update_requested"); + ssl->keys.keyUpdateRespond = 0; + } + else { + ret = Tls13UpdateKeys(ssl); + } } } diff --git a/src/tls.c b/src/tls.c index 08e1f98c62..d40278ad39 100644 --- a/src/tls.c +++ b/src/tls.c @@ -73,6 +73,8 @@ * WOLFSSL_OLD_PRIME_CHECK: Use old DH prime checking method default: off * WOLFSSL_STATIC_DH: Enable static DH cipher suites default: off * WOLFSSL_STATIC_EPHEMERAL: Enable static ephemeral key loading default: off + * Reuses a key share across connections, which + * RFC 9846 4.3.8 forbids. Inspection/debug only. * * Post-Quantum: * WOLFSSL_HAVE_MLKEM: Enable ML-KEM (Kyber) support default: off diff --git a/src/tls13.c b/src/tls13.c index 193bbd3916..177169336b 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -13195,6 +13195,23 @@ static int SendTls13Finished(WOLFSSL* ssl) } #endif /* !NO_WOLFSSL_CLIENT || !NO_WOLFSSL_SERVER */ +/* RFC 9846 Section 4.7.3: a TLS 1.3 sender MUST NOT allow its number of key + * updates to exceed 2^48-1. DTLS 1.3 bounds the epoch instead (RFC 9147 + * Section 4.2.1), so this only covers TLS. + * + * ssl The SSL/TLS object. + * returns 1 when a further KeyUpdate would exceed the limit, 0 otherwise. + */ +int Tls13KeyUpdateLimitReached(WOLFSSL* ssl) +{ + if (ssl->options.dtls) + return 0; + + return w64GTE(ssl->keys.keyUpdateCount, + w64From32(TLS13_KEY_UPDATE_MAX_HI32, + TLS13_KEY_UPDATE_MAX_LO32)); +} + /* handle generation TLS v1.3 key_update (24) */ /* Send the TLS v1.3 KeyUpdate message. * @@ -13226,17 +13243,12 @@ int SendTls13KeyUpdate(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 */ - if (!ssl->options.dtls) { - /* RFC 9846 Section 4.7.3: a sending implementation MUST NOT allow its - * number of key updates to exceed 2^48-1. Receivers MUST NOT enforce - * this on the peer. */ - if (w64GTE(ssl->keys.keyUpdateCount, - w64From32(TLS13_KEY_UPDATE_MAX_HI32, - TLS13_KEY_UPDATE_MAX_LO32))) { - WOLFSSL_MSG("TLS 1.3 key update count at maximum; refusing " - "KeyUpdate"); - return BAD_STATE_E; - } + /* RFC 9846 Section 4.7.3: a sending implementation MUST NOT allow its + * number of key updates to exceed 2^48-1. Receivers MUST NOT enforce this + * on the peer. */ + if (Tls13KeyUpdateLimitReached(ssl)) { + WOLFSSL_MSG("TLS 1.3 key update count at maximum; refusing KeyUpdate"); + return BAD_STATE_E; } outputSz = OPAQUE8_LEN + MAX_MSG_EXTRA; @@ -13407,7 +13419,12 @@ static int DoTls13KeyUpdate(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #endif /* WOLFSSL_DTLS13 */ #if defined(HAVE_WRITE_DUP) && defined(WOLFSSL_TLS13) - /* Read side cannot write; delegate the response to the write side. */ + /* Read side cannot write; delegate the response to the write side. + * The key update cap is deliberately not checked here: the two sides + * are separate WOLFSSL objects with separate keys, and only the write + * side ever sends a KeyUpdate, so this object's keyUpdateCount is not + * the one the limit applies to. The check is applied on the write side + * in wolfssl_write_dup_do_tls13_work(). */ if (ssl->dupWrite != NULL && ssl->dupSide == READ_DUP_SIDE) { if (wc_LockMutex(&ssl->dupWrite->dupMutex) != 0) return BAD_MUTEX_E; @@ -13418,6 +13435,17 @@ static int DoTls13KeyUpdate(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif /* HAVE_WRITE_DUP && WOLFSSL_TLS13 */ + /* RFC 9846 Section 4.7.3: a sender that would exceed the key update + * limit "MUST NOT send its own KeyUpdate ... and SHOULD instead ignore + * the 'update_requested' flag". Dropping the response rather than + * failing keeps the connection alive on the current keys until the + * Section 5.5 data limits eventually force it closed. */ + if (Tls13KeyUpdateLimitReached(ssl)) { + WOLFSSL_MSG("Key update limit reached; ignoring update_requested"); + ssl->keys.keyUpdateRespond = 0; + return 0; + } + #ifndef WOLFSSL_RW_THREADED return SendTls13KeyUpdate(ssl); #else diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 2fe353732d..766ebd61d0 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -9122,6 +9122,710 @@ int test_tls13_KeyUpdate_sender_limit(void) return EXPECT_RESULT(); } +/* The other user_canceled tests inject a plaintext record while the client has + * only sent its ClientHello, so DoProcessAlertRecord() never sees the alert + * arrive over the encrypted channel with keys.decryptedCur set. That is the + * case RFC 9846 Section 6.1 is actually about: a peer cancels after the + * handshake, and the receiver "SHOULD continue to read data from the peer + * until a 'close_notify' is received". + * + * Drive it end to end: complete the handshake, have the server send + * user_canceled over the established connection, and confirm the client reads + * past it and the connection still carries data in both directions. + * + * wolfSSL_SendUserCanceled() emits the alert at warning level, which is what + * a conforming peer sends; no public API produces a fatal-level + * user_canceled, so the level-independence of the exemption is covered by + * test_tls13_user_canceled_fatal_level instead. */ +int test_tls13_user_canceled_encrypted(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + const char msg[] = "survives-user-canceled"; + char buf[sizeof(msg)]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Application data first, then the cancel. On the wire the client now has + * three encrypted records queued: data, user_canceled, close_notify. */ + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + /* Return value is deliberately not asserted: under + * WOLFSSL_ERROR_CODE_OPENSSL a shutdown still awaiting the peer's + * close_notify reports 0, which is also WOLFSSL_FAILURE. The reads + * below are the real proof that both alerts went out. */ + (void)wolfSSL_SendUserCanceled(ssl_s); + + /* The data behind the alert is delivered normally. */ + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, (int)sizeof(buf)), (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0); + + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* Records are being decrypted, so the alert really did arrive over + * the encrypted channel rather than as a plaintext record. */ + ExpectIntEQ(ssl_c->keys.decryptedCur, 1); + ExpectIntEQ(ssl_c->options.isClosed, 0); + } + + /* The next read walks past user_canceled and stops at close_notify - the + * "keep reading until close_notify" behaviour, rather than failing on the + * cancel itself. */ + ExpectIntLE(wolfSSL_read(ssl_c, buf, (int)sizeof(buf)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), WOLFSSL_ERROR_ZERO_RETURN); + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* Proof it got past the cancel: the last alert seen is the one + * behind it. */ + ExpectIntEQ(ssl_c->alert_history.last_rx.code, close_notify); + ExpectIntEQ(ssl_c->options.isClosed, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Companion to test_tls13_user_canceled_fatal_level, covering the second half + * of the DoAlert() rework: the session-invalidation exemption. + * + * That test injects the alert pre-handshake on a fresh connection, where + * InvalidateSessionOnFatalAlert() returns early ("neither handShakeDone nor + * resuming") before the reworked TLS 1.3 branch is reached - so it proves the + * isClosed half only. Here the alert lands on a connection that is resuming, + * which gets past that early return, and the cached session must survive. + * + * The probe is wolfSSL_SSL_CTX_remove_session()'s return value: 1 when it + * found the entry still cached, 0 when something already evicted it. */ +int test_tls13_user_canceled_keeps_session(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_SESSION_CACHE) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + static const unsigned char fatalUserCanceled[] = + { 0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x5a }; + char buf[64]; + + /* Full handshake so the client caches a resumable session. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Pump the NewSessionTicket through so the session is cached. */ + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* Second connection on the same contexts, resuming. test_memio_setup + * keeps a non-NULL context, so the cache entry stays reachable. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* Required for the invalidation path to be reached at all. */ + if (EXPECT_SUCCESS() && ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.resuming, 1); + } + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, + (const char *)fatalUserCanceled, sizeof(fatalUserCanceled)), 0); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* Still cached. Without the TLS 1.3 user_canceled exemption the fatal + * level would have evicted it and this would return 0. */ + ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_c, sess), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The DoAlert() rework split isClosed and session invalidation into explicit + * TLS 1.3 and pre-1.3 branches. The other new tests only drive the TLS 1.3 + * side, so a regression that stopped a TLS 1.2 fatal alert from closing the + * connection or evicting the session would go unnoticed. + * + * Over TLS 1.2 the AlertLevel byte does carry meaning, so the outcome is the + * opposite of the TLS 1.3 case: the connection closes and the session is + * evicted. + * + * wolfSSL_SSL_CTX_remove_session() returns 0 both for "the alert evicted it" + * and for "it was never cached", so asserting 0 on its own would pass + * vacuously. The flow therefore runs twice on independent contexts: pass 0 + * injects nothing and must find the session still cached (1), pass 1 injects + * the alert and must find it gone (0). The control pass is what makes the 0 + * meaningful. */ +int test_tls12_fatal_alert_closes_and_evicts(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_SESSION_CACHE) + /* Same alert as the TLS 1.3 test: fatal level, user_canceled(90). */ + static const unsigned char fatalUserCanceled[] = + { 0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x5a }; + int pass; + + for (pass = 0; pass < 2 && !EXPECT_FAIL(); pass++) { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + + 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); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* Resume, so options.resuming is set: InvalidateSessionOnFatalAlert() + * returns early on a connection that is neither done nor resuming. */ + 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(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (EXPECT_SUCCESS() && ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.resuming, 1); + } + + if (pass == 1) { + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, + (const char *)fatalUserCanceled, + sizeof(fatalUserCanceled)), 0); + /* In TLS 1.2 the fatal level is authoritative: connection dies. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntNE(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (EXPECT_SUCCESS() && ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.isClosed, 1); + } + } + + /* Pass 0 proves the session is cached at this point in the flow; + * pass 1 proves the alert removed it. */ + ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_c, sess), + (pass == 0) ? 1 : 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* RFC 9846 Section 4.7.3, write-duplicate path. The read side of a write dup + * cannot send, so a peer KeyUpdate(update_requested) is delegated to the write + * side via dupWrite->keyUpdateRespond and sent from + * wolfssl_write_dup_do_tls13_work(). That route bypassed the cap check in + * DoTls13KeyUpdate, so at the ceiling Tls13UpdateKeys() still returned + * BAD_STATE_E and the next wolfSSL_write() failed the connection - the exact + * outcome the "ignore the update_requested flag" rule forbids. + * + * The cap is checked on the write side because the two sides are separate + * WOLFSSL objects with separate keys; only the write side sends KeyUpdates, so + * only its counter is meaningful. */ +int test_tls13_KeyUpdate_limit_writedup(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_WRITE_DUP) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *ssl_w = NULL; + const char msg[] = "after-ignored-delegated-request"; + w64wrapper ceiling; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* ssl_c becomes read-only; ssl_w is the write side. */ + ExpectNotNull(ssl_w = wolfSSL_write_dup(ssl_c)); + + ceiling = w64From32(TLS13_KEY_UPDATE_MAX_HI32, TLS13_KEY_UPDATE_MAX_LO32); + + if (EXPECT_SUCCESS() && ssl_w != NULL) { + /* Write side has exhausted its key update budget, and the read side + * has delegated a response to it. */ + ssl_w->keys.keyUpdateCount = ceiling; + ssl_w->dupWrite->keyUpdateRespond = 1; + } + + /* The write settles the delegated work first. It must drop the response + * rather than fail. Before the fix this returned WOLFSSL_FATAL_ERROR with + * ssl_w->error == BAD_STATE_E. */ + ExpectIntEQ(wolfSSL_write(ssl_w, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + + if (EXPECT_SUCCESS() && ssl_w != NULL) { + ExpectTrue(w64Equal(ssl_w->keys.keyUpdateCount, ceiling)); + ExpectIntEQ(ssl_w->keys.keyUpdateRespond, 0); + ExpectIntEQ(ssl_w->dupWrite->keyUpdateRespond, 0); + } + + /* The application data still arrives, so the connection survived. */ + if (EXPECT_SUCCESS()) { + char buf[sizeof(msg)]; + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, (int)sizeof(buf)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0); + } + + 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(); +} + +/* Companion to test_tls13_early_data_AEAD_limit: crossing the limit part way + * through a multi-record early data write must report the records that did go + * out, not discard them. + * + * CheckTLS13AEADSendLimit() runs at the top of every SendData() record loop + * iteration, and a non-zero result used to return WOLFSSL_FATAL_ERROR + * immediately, throwing away the local sent counter. The caller was then told + * (by the documented behaviour) to resend the remainder over the completed + * handshake, but *outSz was 0 so it could not work out what the remainder + * was. SendData() now breaks out and returns the short count instead. + * + * Park the sequence number one below the limit and write more than one + * record: the first goes out, the second trips the limit. */ +int test_tls13_early_data_AEAD_limit_partial(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(BUILD_TLS_AES_128_GCM_SHA256) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + /* Comfortably more than one record, so the write must split. */ + static byte earlyMsg[20000]; + char buf[64]; + int written = 0; + int first = 0; + + XMEMSET(earlyMsg, 'E', sizeof(earlyMsg)); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "TLS13-AES128-GCM-SHA256"; + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, sizeof(earlyMsg) * 2), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "TLS13-AES128-GCM-SHA256"; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, sizeof(earlyMsg) * 2), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + + /* Drive the ClientHello out and leave early data in flight. */ + ExpectIntEQ(test_tls13_early_data_write_until_write_ok(ssl_c, "x", 1, + &written), 1); + + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* One below the AES-GCM limit of 2^24.5: the next record is fine, the + * one after it trips the check. */ + ssl_c->keys.sequence_number_hi = 0; + ssl_c->keys.sequence_number_lo = 0x016A09E6 - 1; + } + + /* Short write rather than a hard failure: some records made it out. */ + first = wolfSSL_write_early_data(ssl_c, earlyMsg, (int)sizeof(earlyMsg), + &written); + ExpectIntGT(first, 0); + ExpectIntLT(first, (int)sizeof(earlyMsg)); + ExpectIntEQ(written, first); + /* A short write here is not the partialWrite contract, so the reason is + * left on the object for the caller to find. */ + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), + WC_NO_ERR_TRACE(TOO_MUCH_EARLY_DATA)); + + /* Nothing more can go out: the retry fails, and reports why. */ + written = 0; + ExpectIntEQ(wolfSSL_write_early_data(ssl_c, earlyMsg, (int)sizeof(earlyMsg), + &written), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(TOO_MUCH_EARLY_DATA)); + ExpectIntEQ(written, 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* RFC 9846 Section 4.3, text with no counterpart in RFC 8446: "Unless + * otherwise specified, trailing data is forbidden. ... When processing an + * extension, receivers MUST abort the handshake with a 'decode_error' alert if + * there is data left over after parsing the structure." + * + * The extension parsers do detect malformed structures, but a third of them + * report it as the wolfCrypt BUFFER_E rather than BUFFER_ERROR, and only + * BUFFER_ERROR was mapped to an alert. TranslateErrorToAlert returned + * invalid_alert for BUFFER_E, and every caller skips SendAlert when the + * translation is invalid_alert - so the handshake aborted with nothing on the + * wire and the peer saw only a dropped connection. + * + * Inject a ServerHello whose pre_shared_key body is 3 bytes rather than the + * required 2. TLSX_PreSharedKey_Parse rejects that with BUFFER_E before it + * looks at whether the client offered the extension, so this reaches the + * mapping. The client must send decode_error. */ +int test_tls13_extension_trailing_data_alert(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && \ + (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL *ssl_c = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + /* Handshake record holding a ServerHello with supported_versions (TLS 1.3) + * and a pre_shared_key whose body is one byte too long. */ + static const unsigned char badPskSh[] = { + 0x16, 0x03, 0x03, 0x00, 0x39, /* record: handshake, len 57 */ + 0x02, 0x00, 0x00, 0x35, /* server_hello, len 53 */ + 0x03, 0x03, /* legacy_version */ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* random[32] */ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, /* legacy_session_id, empty */ + 0x13, 0x01, /* TLS_AES_128_GCM_SHA256 */ + 0x00, /* legacy_compression_method */ + 0x00, 0x0d, /* extensions, 13 bytes */ + 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04, /* supported_versions = TLS 1.3 */ + 0x00, 0x29, 0x00, 0x03, /* pre_shared_key, 3-byte body */ + 0xaa, 0xbb, 0xcc /* ...must be exactly 2 */ + }; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL, + wolfTLSv1_3_client_method, NULL), 0); + + /* Client sends ClientHello, then waits for the server response. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, + (const char *)badPskSh, sizeof(badPskSh)), 0); + + /* The handshake must fail... */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntNE(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* ...and it must say so on the wire, with decode_error. Before the fix the + * BUFFER_E from the parser mapped to invalid_alert and nothing was sent. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, decode_error); + ExpectIntEQ(h.last_tx.level, alert_fatal); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +/* RFC 9846 Section 4.7.3: "If a sending implementation receives a KeyUpdate + * with request_update set to 'update_requested', it MUST NOT send its own + * KeyUpdate if that would cause it to exceed these limits and SHOULD instead + * ignore the 'update_requested' flag." + * + * Park the client's key update count on the 2^48-1 ceiling, have the server + * request an update, and confirm the client ignores the request and keeps the + * connection usable rather than failing. Before the fix DoTls13KeyUpdate + * called SendTls13KeyUpdate unconditionally, which refuses at the ceiling with + * BAD_STATE_E and tore the connection down. + * + * An application-initiated wolfSSL_update_keys() at the ceiling must still + * report BAD_STATE_E - the "ignore" rule is specific to responding to a peer's + * update_requested. That case is covered by + * test_tls13_KeyUpdate_sender_limit above. */ +int test_tls13_KeyUpdate_limit_ignores_update_requested(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + const char msg[] = "after-ignored-request"; + char buf[sizeof(msg)]; + w64wrapper ceiling; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ceiling = w64From32(TLS13_KEY_UPDATE_MAX_HI32, TLS13_KEY_UPDATE_MAX_LO32); + + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* Client has exhausted its key update budget. */ + ssl_c->keys.keyUpdateCount = ceiling; + } + + /* Server asks the client to update. A fresh KeyUpdate from the server has + * request_update set, since it has neither an outstanding request of its + * own nor a pending response to make. */ + ExpectIntEQ(wolfSSL_update_keys(ssl_s), WOLFSSL_SUCCESS); + + /* Client processes the KeyUpdate. It must not error out. */ + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntLT(wolfSSL_read(ssl_c, buf, (int)sizeof(buf)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* The request was ignored, not answered: no KeyUpdate was sent, so the + * count is untouched and no response is left pending. */ + ExpectTrue(w64Equal(ssl_c->keys.keyUpdateCount, ceiling)); + ExpectIntEQ(ssl_c->keys.keyUpdateRespond, 0); + ExpectIntEQ(ssl_c->options.isClosed, 0); + } + + /* The connection still works in both directions. The server reads with the + * keys it already had, since the client never rekeyed its send side. */ + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), (int)sizeof(msg)); + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, (int)sizeof(buf)), (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0); + + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), (int)sizeof(msg)); + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, (int)sizeof(buf)), (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* RFC 9846 Section 5.5: "it is not possible to perform a KeyUpdate for early + * data; therefore, implementations MUST NOT exceed the limits when sending + * early data." + * + * Resume with 0-RTT, park the client's encrypt sequence number on the AES-GCM + * limit while early data is still in flight, and write again. The write must + * fail. Before the fix CheckTLS13AEADSendLimit called Tls13UpdateKeys here + * regardless, emitting a KeyUpdate in the CLIENT_HELLO_COMPLETE state - before + * the handshake had finished, where the peer must reject it. */ +int test_tls13_early_data_AEAD_limit(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(WOLFSSL_EARLY_DATA) && \ + defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(BUILD_TLS_AES_128_GCM_SHA256) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + const char earlyMsg[] = "early-data-at-limit"; + char buf[64]; + int written = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "TLS13-AES128-GCM-SHA256"; + + /* Step 1: full handshake to obtain a ticket that permits early data. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Pump the NewSessionTicket through to the client. */ + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + + /* Step 2: resume with early data. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "TLS13-AES128-GCM-SHA256"; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + + /* The first early-data write also drives the ClientHello out, leaving the + * client in CLIENT_HELLO_COMPLETE with early data still in flight. */ + ExpectIntEQ(test_tls13_early_data_write_until_write_ok(ssl_c, earlyMsg, + (int)sizeof(earlyMsg), &written), (int)sizeof(earlyMsg)); + + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* Still mid-early-data, so no KeyUpdate is possible from here. */ + ExpectIntNE(ssl_c->earlyData, no_early_data); + ExpectIntNE(ssl_c->earlyData, done_early_data); + ExpectIntEQ(ssl_c->specs.bulk_cipher_algorithm, wolfssl_aes_gcm); + + /* Park the encrypt counter on the AES-GCM limit of 2^24.5. */ + ssl_c->keys.sequence_number_hi = 0; + ssl_c->keys.sequence_number_lo = 0x016A09E6; + } + + /* Limit reached with no way to rekey: the write must fail rather than + * send a KeyUpdate. */ + ExpectIntEQ(wolfSSL_write_early_data(ssl_c, earlyMsg, + (int)sizeof(earlyMsg), &written), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(TOO_MUCH_EARLY_DATA)); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* RFC 9846 Section 6.1: "user_canceled" only "generally" carries + * AlertLevel=warning, and "Receiving implementations SHOULD continue to read + * data from the peer until a 'close_notify' is received". The level byte is + * meaningless in TLS 1.3, so an alert sent at fatal level must not tear the + * connection down. + * + * The exemption is gated on a *negotiated* TLS 1.3 connection, not on + * ssl->version, which still holds the highest version this side offered until + * the peer's choice is known. Both sides of that gate are covered here: + * + * - before negotiation the alert is acted on, so a pre-1.3 peer's fatal + * alert is not silently swallowed and the caller is not left waiting; + * - once TLS 1.3 is settled the alert is ignored whatever level was used. + * + * Before the DoAlert() rework the second case failed: both DoAlert() and + * DoProcessAlertRecord() branched on the AlertLevel byte ahead of the TLS 1.3 + * exemptions, so a fatal-level user_canceled closed the connection. */ +int test_tls13_user_canceled_fatal_level(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) + /* TLS record: content_type=alert(0x15), version=TLS1.2(0x0303), len=2, + * level=fatal(0x02), code=user_canceled(0x5a=90) */ + static const unsigned char fatalUserCanceled[] = + { 0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x5a }; + int negotiated; + + for (negotiated = 0; negotiated < 2 && !EXPECT_FAIL(); negotiated++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL *ssl_c = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL, + wolfTLSv1_3_client_method, NULL), 0); + + /* Client sends ClientHello, then waits for the server response. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + if (EXPECT_SUCCESS() && ssl_c != NULL) { + /* Nothing is negotiated yet, whatever version we offered. */ + ExpectIntEQ(ssl_c->options.tls1_3, 0); + if (negotiated) { + /* Stand in for a settled TLS 1.3 connection. Read-side keys + * are not installed yet, so the plaintext alert is still + * accepted and the exemption is what decides the outcome. */ + ssl_c->options.tls1_3 = 1; + } + } + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, + (const char *)fatalUserCanceled, sizeof(fatalUserCanceled)), 0); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + + if (negotiated) { + /* Ignored: the client keeps waiting for the server. */ + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (EXPECT_SUCCESS() && ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.isClosed, 0); + } + } + else { + /* Acted on: the version is unknown, so a fatal alert is fatal. */ + ExpectIntNE(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (EXPECT_SUCCESS() && ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.isClosed, 1); + } + } + + /* Recorded either way. RFC 9846 6.1 permits a receiver to "log or + * otherwise record" the alert. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, user_canceled); + ExpectIntEQ(h.last_rx.level, alert_fatal); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + } +#endif + return EXPECT_RESULT(); +} + #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 707f3b8771..3f75c2ce9a 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -107,6 +107,15 @@ int test_tls13_AEAD_limit_KU_aes256_gcm_sha384(void); int test_tls13_AEAD_limit_KU_aes128_ccm_sha256(void); int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); int test_tls13_KeyUpdate_sender_limit(void); +int test_tls13_KeyUpdate_limit_ignores_update_requested(void); +int test_tls13_KeyUpdate_limit_writedup(void); +int test_tls13_extension_trailing_data_alert(void); +int test_tls13_early_data_AEAD_limit(void); +int test_tls13_early_data_AEAD_limit_partial(void); +int test_tls13_user_canceled_fatal_level(void); +int test_tls13_user_canceled_keeps_session(void); +int test_tls13_user_canceled_encrypted(void); +int test_tls12_fatal_alert_closes_and_evicts(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); @@ -194,6 +203,15 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ + TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_limit_ignores_update_requested), \ + TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_limit_writedup), \ + TEST_DECL_GROUP("tls13", test_tls13_extension_trailing_data_alert), \ + TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit), \ + TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit_partial), \ + TEST_DECL_GROUP("tls13", test_tls13_user_canceled_fatal_level), \ + TEST_DECL_GROUP("tls13", test_tls13_user_canceled_keeps_session), \ + TEST_DECL_GROUP("tls13", test_tls13_user_canceled_encrypted), \ + TEST_DECL_GROUP("tls13", test_tls12_fatal_alert_closes_and_evicts), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ TEST_DECL_GROUP("tls13", test_tls13_pha_status_request) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 2f96892e06..2a7b06ae0b 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -7426,6 +7426,7 @@ WOLFSSL_LOCAL word32 MacSize(const WOLFSSL* ssl); #ifdef WOLFSSL_TLS13 WOLFSSL_LOCAL int SendTls13KeyUpdate(WOLFSSL* ssl); +WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #endif #ifdef WOLFSSL_DTLS