From 3ac56ccaafe6e0917b5c706b6fb6809af55032ed Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Thu, 20 Aug 2026 12:50:04 +1000 Subject: [PATCH] Conformance to RFC 9846: New version of TLS 1.3 specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC 9846, 5.5 CheckTLS13AEADSendLimit(): at the AEAD limit while sending early data, return TOO_MUCH_EARLY_DATA instead of calling Tls13UpdateKeys(). A KeyUpdate there would go out pre-handshake. RFC 9846, 6.1 Three alert sites in DoAlert() / DoProcessAlertRecord(): TLS 1.3 user_canceled is now exempt from teardown and session invalidation at any AlertLevel, not just warning. TLS 1.2 unchanged. RFC 9846, 4.7.3 New Tls13KeyUpdateLimitReached() helper shared by send and receive paths. At the 2^48-1 cap, DoTls13KeyUpdate() drops a peer's update_requested and continues, rather than failing the connection. App-initiated wolfSSL_update_keys() still returns BAD_STATE_E. RFC 9846, 4.3 TranslateErrorToAlert() maps BUFFER_E to decode_error as well as BUFFER_ERROR — one case label covering 27 malformed-extension sites that previously aborted with no alert sent. Tests added. --- ChangeLog.md | 50 +++ configure.ac | 2 +- doc/dox_comments/header_files/ssl.h | 22 +- src/internal.c | 72 +++- src/ssl_api_rw.c | 16 +- src/tls.c | 2 + src/tls13.c | 52 ++- tests/api/test_tls13.c | 596 ++++++++++++++++++++++++++++ tests/api/test_tls13.h | 16 + wolfssl/internal.h | 1 + 10 files changed, 800 insertions(+), 29 deletions(-) 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/internal.c b/src/internal.c index afa472608b..7b9f87b47a 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 * @@ -23961,7 +23962,7 @@ static void LogAlert(int type) /* 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 +23976,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 +24040,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 && + !(IsAtLeastTLSv1_3(ssl->version) && code == user_canceled)) { 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 +24101,16 @@ 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 && *type != user_canceled) + InvalidateSessionOnFatalAlert(ssl); + } + else if (level == alert_fatal && *type != close_notify) { InvalidateSessionOnFatalAlert(ssl); + } #endif } return level; @@ -24949,7 +24960,10 @@ 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 && + !(IsAtLeastTLSv1_3(ssl->version) && type == user_canceled)) return FATAL_ERROR; else if (ret < 0) return ret; @@ -24965,7 +24979,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 +28608,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 +28678,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 +28954,17 @@ 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)) { + break; + } + #endif if (ret != 0) { ssl->error = ret; return WOLFSSL_FATAL_ERROR; @@ -38336,7 +38375,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..ee68e74aa3 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -9122,6 +9122,602 @@ int test_tls13_KeyUpdate_sender_limit(void) 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); + + /* 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. + * + * Before the fix both DoAlert and DoProcessAlertRecord branched on the level + * byte ahead of the TLS 1.3 user_canceled exemptions, so a fatal-level + * user_canceled closed the connection and invalidated the session. */ +int test_tls13_user_canceled_fatal_level(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL *ssl_c = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + /* 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 }; + + 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 *)fatalUserCanceled, sizeof(fatalUserCanceled)), 0); + + /* The alert must be ignored: the client keeps waiting for the server + * rather than failing. Before the fix this returned FATAL_ERROR. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* Recorded but not acted on. 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); + if (EXPECT_SUCCESS() && ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.isClosed, 0); + } + + 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..58c4383973 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -107,6 +107,14 @@ 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_tls12_fatal_alert_closes_and_evicts(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); @@ -194,6 +202,14 @@ 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_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