Skip to content

Commit 9a19265

Browse files
authored
Merge pull request #11215 from SparkiDev/tls13_rfc9846
Conformance to RFC 9846: New version of TLS 1.3 specification
2 parents 070d311 + e8fac4e commit 9a19265

13 files changed

Lines changed: 1108 additions & 30 deletions

File tree

ChangeLog.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,23 @@
170170
configuration now sees neither the prototype nor the `client_cert_cb`
171171
typedef instead of failing to build; no other configuration changes.
172172

173+
* **Behavioral change (`wolfSSL_write_early_data` and the AEAD key usage
174+
limit)**: RFC 9846, Section 5.5 adds that "it is not possible to perform a
175+
KeyUpdate for early data; therefore, implementations MUST NOT exceed the
176+
limits when sending early data". Reaching the limit mid-early-data
177+
previously drove the ordinary rekey path, which emitted a KeyUpdate while
178+
the client was still in `CLIENT_HELLO_COMPLETE` - before the handshake had
179+
finished, where a conforming peer must reject it. The write now fails
180+
instead, returning `WOLFSSL_FATAL_ERROR` with `wolfSSL_get_error()`
181+
reporting `TOO_MUCH_EARLY_DATA`. The write stays all-or-nothing, matching
182+
every other non-`partialWrite` return from `SendData()`. The budget also
183+
now stops one record short, because an accepted 0-RTT exchange still owes
184+
the server an `EndOfEarlyData`, and RFC 9846 Section 2.3 sends that under the
185+
same early traffic keys. Callers that hit this should abandon early data on
186+
the connection rather than resume the send from an offset. Reaching the
187+
limit needs roughly 23.7 million early data records on one connection, so no
188+
practical caller is affected.
189+
173190
## Fixes
174191

175192
* **Fix (sniffer could not decrypt Encrypt-Then-MAC or X25519 sessions)**: the
@@ -266,6 +283,43 @@
266283
than withdrawing them, matching what a failure to allocate the `ecc_key`
267284
already did. Only affects builds with `WOLFSSL_BLIND_PRIVATE_KEY`.
268285

286+
* **Fix (fatal-level `user_canceled` closed a TLS 1.3 connection)**: RFC 9846,
287+
Section 6.1 states that this alert "generally has AlertLevel=warning" and
288+
that "receiving implementations SHOULD continue to read data from the peer
289+
until a 'close_notify' is received". wolfSSL already exempted
290+
`user_canceled` from the TLS 1.3 rule that all error alerts are fatal, but
291+
both `DoAlert()` and `DoProcessAlertRecord()` acted on the AlertLevel byte
292+
before reaching those exemptions, so a peer sending the alert at fatal level
293+
tore the connection down and invalidated the session. The level byte
294+
carries no meaning in TLS 1.3, and the alert is now ignored whichever level
295+
the peer used. TLS 1.2 and earlier are unchanged: a fatal-level alert
296+
remains fatal there.
297+
298+
* **Fix (key update cap turned a peer's `update_requested` into a fatal
299+
error)**: RFC 9846, Section 4.7.3 adds that a sender at the 2^48-1 key
300+
update cap "MUST NOT send its own KeyUpdate ... and SHOULD instead ignore
301+
the 'update_requested' flag". Responding to a peer's request went through
302+
the ordinary send path, which refuses at the cap with `BAD_STATE_E`, and
303+
that error propagated out and killed the connection. The request is now
304+
dropped and the connection continues on its current keys until the Section
305+
5.5 data limits force it closed. An application-initiated
306+
`wolfSSL_update_keys()` at the cap still reports `BAD_STATE_E`; the rule
307+
applies only to responding to a peer.
308+
309+
* **Fix (malformed extension aborted without sending `decode_error`)**: RFC
310+
9846, Section 4.3 adds that trailing data in an extension is forbidden and
311+
that "receivers MUST abort the handshake with a 'decode_error' alert if
312+
there is data left over after parsing the structure". The extension parsers
313+
detect malformed structures, but around a third of them report it as the
314+
wolfCrypt `BUFFER_E` rather than `BUFFER_ERROR`, and only `BUFFER_ERROR` was
315+
mapped to an alert. `TranslateErrorToAlert()` returned `invalid_alert` for
316+
`BUFFER_E`, which every caller treats as "send nothing", so the handshake
317+
aborted correctly but silently and the peer saw only a dropped connection.
318+
Both codes now map to `decode_error`. This affects `pre_shared_key`,
319+
`psk_key_exchange_modes`, `early_data`, `cookie`, `post_handshake_auth` and
320+
the certificate type extensions, and more generally any malformed handshake
321+
message reported with `BUFFER_E`.
322+
269323
# wolfSSL Release 5.9.2 (Jun 23, 2026)
270324

271325
Release 5.9.2 has been developed according to wolfSSL's development and QA

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6069,7 +6069,7 @@ fi
60696069

60706070
# OLD TLS
60716071
AC_ARG_ENABLE([oldtls],
6072-
[AS_HELP_STRING([--enable-oldtls],[Enable old TLS versions < 1.2 (default: disabled)])],
6072+
[AS_HELP_STRING([--enable-oldtls],[Enable old TLS versions < 1.2, which RFC 8996 and RFC 9846 forbid negotiating (default: disabled)])],
60736073
[ ENABLED_OLD_TLS=$enableval ],
60746074
[ ENABLED_OLD_TLS=no ]
60756075
)

doc/dox_comments/header_files/ssl.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,6 +2584,11 @@ int wolfSSL_shutdown(WOLFSSL* ssl);
25842584

25852585
\param ssl pointer to the SSL session, created with wolfSSL_new().
25862586

2587+
\note RFC 9846, Section 6.1 requires this alert to be followed by a
2588+
close notify, which is why the shutdown is part of this call. On the
2589+
receiving side the alert is not itself an error: a TLS 1.3 peer keeps
2590+
reading until the close notify arrives, whatever AlertLevel was used.
2591+
25872592
_Example_
25882593
\code
25892594
int ret = 0;
@@ -14770,6 +14775,13 @@ int wolfSSL_require_psk(WOLFSSL* ssl);
1477014775

1477114776
\return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3.
1477214777
\return WANT_WRITE if the writing is not ready.
14778+
\return BAD_STATE_E if the connection has already performed the maximum
14779+
number of key updates. RFC 9846, Section 4.7.3 caps a TLS 1.3 sender at
14780+
2^48-1 key updates; beyond that the connection must be closed rather than
14781+
rekeyed. Note that a KeyUpdate arriving from the peer with
14782+
request_update set is ignored once this cap is reached, rather than
14783+
failing the connection, so only an application-initiated update reports
14784+
this error.
1477314785
\return WOLFSSL_SUCCESS if successful.
1477414786

1477514787
_Example_
@@ -14800,7 +14812,8 @@ int wolfSSL_update_keys(WOLFSSL* ssl);
1480014812
is received.
1480114813

1480214814
\param [in] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new().
14803-
\param [out] required 0 when no key update response required. 1 when no key update response required.
14815+
\param [out] required 0 when no key update response is required. 1 when
14816+
a key update response from the peer is still outstanding.
1480414817

1480514818
\return 0 on successful.
1480614819
\return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3.
@@ -15622,7 +15635,15 @@ int wolfSSL_set_max_early_data(WOLFSSL* ssl, unsigned int sz);
1562215635
\return SIDE_ERROR if called with a server.
1562315636
\return BAD_STATE_E if invoked without a valid session or without a valid
1562415637
PSK cb
15625-
\return WOLFSSL_FATAL_ERROR if the connection is not made.
15638+
\return WOLFSSL_FATAL_ERROR if the connection is not made, or if the
15639+
AEAD key usage limit would be exceeded by this write, in which case
15640+
wolfSSL_get_error() reports TOO_MUCH_EARLY_DATA. A KeyUpdate cannot be
15641+
performed while sending early data (RFC 9846, Section 5.5), so no further
15642+
early data can be sent on this connection. The write is all-or-nothing as
15643+
usual: a value less than sz is never returned. Note that a failed call may
15644+
still have put records on the wire before the limit was reached, so treat
15645+
the connection as unusable for early data rather than resuming the send
15646+
from an offset.
1562615647
\return the amount of early data written in bytes if successful.
1562715648

1562815649
_Example_

src/dtls.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ void DtlsResetState(WOLFSSL* ssl)
8888
ssl->keys.dtls_sequence_number_hi = 0;
8989
ssl->keys.dtls_sequence_number_lo = 0;
9090

91+
/* Forget any alert this object sent for the ClientHello being abandoned.
92+
* DoClientHello() can send a fatal alert on the stateless path and then
93+
* swallow the error (DtlsIgnoreError) so the object stays up waiting for
94+
* the next ClientHello. A leftover alert_fatal in the history makes the
95+
* "already sent a more specific fatal alert" guards suppress every later
96+
* alert on this object, so a single malformed ClientHello from a spoofed
97+
* address would mute alerts for every peer that follows. */
98+
ssl->alert_history.last_tx.code = -1;
99+
ssl->alert_history.last_tx.level = -1;
100+
91101
/* Reset states */
92102
ssl->options.serverState = NULL_STATE;
93103
ssl->options.clientState = NULL_STATE;

src/internal.c

Lines changed: 124 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@
104104
* WOLFSSL_TLS13_NO_PEEK_HANDSHAKE_DONE:
105105
* Disable peek returning WANT_READ for tickets default: off
106106
* WOLFSSL_TLS13_IGNORE_AEAD_LIMITS:
107-
* Ignore AEAD message limits from RFC 8446 default: off
107+
* Ignore AEAD message limits from RFC 9846 5.5, which
108+
* makes observing them a MUST default: off
108109
* WOLFSSL_DTLS13_SEND_MOREACK_DEFAULT:
109110
* Send more ACKs by default in DTLS 1.3 default: off
110111
*
@@ -23994,10 +23995,41 @@ static void LogAlert(int type)
2399423995
#endif /* DEBUG_WOLFSSL */
2399523996
}
2399623997

23998+
/* RFC 9846 Section 6.1 exempts "user_canceled" from tearing the connection
23999+
* down whatever AlertLevel the peer used, because the level byte carries no
24000+
* meaning in TLS 1.3.
24001+
*
24002+
* The exemption needs a connection that is demonstrably running TLS 1.3, and
24003+
* neither obvious flag is enough on its own. ssl->version holds the highest
24004+
* version this side offered until the peer's choice is known, and
24005+
* options.tls1_3 is set from the session by wolfSSL_set_session() before any
24006+
* ServerHello, so a downgrade-capable client resuming a TLS 1.3 session has
24007+
* both set while the peer may still pick TLS 1.2. Exempting on either would
24008+
* swallow a pre-1.3 peer's fatal alert and leave the caller waiting for a
24009+
* handshake that is never coming.
24010+
*
24011+
* Requiring the record to have been decrypted settles it: in TLS 1.3 every
24012+
* record after the ServerHello is encrypted, so a decrypted alert can only
24013+
* have arrived on a connection whose version is already agreed - which is the
24014+
* only situation RFC 9846 Section 6.1 is describing. keys.decryptedCur is a
24015+
* property of the record in hand, not a latch: GetRecordHeader() clears it for
24016+
* each new record and only a successful decrypt sets it, so an earlier
24017+
* encrypted record cannot lend its status to a later plaintext alert.
24018+
*
24019+
* ssl The SSL/TLS object.
24020+
* code Alert description received.
24021+
* returns 1 when the alert must be ignored rather than acted on.
24022+
*/
24023+
static int AlertIsExemptUserCanceled(const WOLFSSL* ssl, int code)
24024+
{
24025+
return ssl->options.tls1_3 && ssl->keys.decryptedCur &&
24026+
(code == user_canceled);
24027+
}
24028+
2399724029
/* process alert, return level */
2399824030
#ifndef NO_SESSION_CACHE
2399924031
/* RFC 5246 Section 7.2.2: a TLS 1.2 session whose connection is terminated by a
24000-
* fatal alert MUST be invalidated so it cannot be resumed. (TLS 1.3 RFC 8446
24032+
* fatal alert MUST be invalidated so it cannot be resumed. (TLS 1.3 RFC 9846
2400124033
* Section 6.2 only requires closing the connection, but evicting here too is
2400224034
* sound defense-in-depth.) Evict the cached session (which also drops any
2400324035
* associated ticket). Acts on an established connection or an in-progress
@@ -24011,7 +24043,7 @@ static void InvalidateSessionOnFatalAlert(WOLFSSL* ssl)
2401124043
return;
2401224044
/* Don't evict on an unauthenticated record: a TLS 1.3 plaintext alert
2401324045
* received under encryption (current record not decrypted) is rejected (or
24014-
* ignored) by DoAlert, and the teardown alert routes back here. RFC 8446
24046+
* ignored) by DoAlert, and the teardown alert routes back here. RFC 9846
2401524047
* 6.2 doesn't require TLS 1.3 eviction; TLS 1.2 alerts are plaintext so are
2401624048
* unaffected. */
2401724049
if (IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) &&
@@ -24075,10 +24107,16 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2407524107
{
2407624108
ssl->alert_history.last_rx.code = code;
2407724109
ssl->alert_history.last_rx.level = level;
24078-
if (level == alert_fatal) {
24110+
/* RFC 9846 Section 6.1: "user_canceled" only "generally" has
24111+
* AlertLevel=warning, and a receiver SHOULD keep reading until
24112+
* "close_notify" arrives. The level byte is meaningless in TLS 1.3,
24113+
* so do not let a peer that sends the alert at fatal level tear the
24114+
* connection down. */
24115+
if (level == alert_fatal &&
24116+
!AlertIsExemptUserCanceled(ssl, code)) {
2407924117
ssl->options.isClosed = 1; /* Don't send close_notify */
2408024118
}
24081-
/* RFC 8446 Section 6.2: In TLS 1.3, all error alerts are implicitly
24119+
/* RFC 9846 Section 6.2: In TLS 1.3, all error alerts are implicitly
2408224120
* fatal regardless of the AlertLevel byte. */
2408324121
if (IsAtLeastTLSv1_3(ssl->version) &&
2408424122
code != close_notify && code != user_canceled) {
@@ -24130,12 +24168,17 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2413024168
}
2413124169
#ifndef NO_SESSION_CACHE
2413224170
/* Validated fatal alert: invalidate the session so it can't be resumed
24133-
* (RFC 5246 7.2.2; in TLS 1.3 all error alerts are fatal, RFC 8446
24134-
* 6.2). */
24135-
if (*type != close_notify &&
24136-
(level == alert_fatal ||
24137-
(IsAtLeastTLSv1_3(ssl->version) && *type != user_canceled)))
24171+
* (RFC 5246 7.2.2; in TLS 1.3 all error alerts are fatal, RFC 9846
24172+
* 6.2). "close_notify" is not an error, and "user_canceled" is exempt
24173+
* in TLS 1.3 at any AlertLevel (RFC 9846 6.1). */
24174+
if (IsAtLeastTLSv1_3(ssl->version)) {
24175+
if (*type != close_notify &&
24176+
!AlertIsExemptUserCanceled(ssl, *type))
24177+
InvalidateSessionOnFatalAlert(ssl);
24178+
}
24179+
else if (level == alert_fatal && *type != close_notify) {
2413824180
InvalidateSessionOnFatalAlert(ssl);
24181+
}
2413924182
#endif
2414024183
}
2414124184
return level;
@@ -24985,7 +25028,9 @@ static int DoProcessAlertRecord(WOLFSSL* ssl)
2498525028
WOLFSSL_MSG("got ALERT!");
2498625029
ret = DoAlert(ssl, ssl->buffers.inputBuffer.buffer,
2498725030
&ssl->buffers.inputBuffer.idx, &type);
24988-
if (ret == alert_fatal)
25031+
/* RFC 9846 Section 6.1: keep reading past a TLS 1.3 "user_canceled" until
25032+
* "close_notify" arrives, whatever AlertLevel the peer used. */
25033+
if (ret == alert_fatal && !AlertIsExemptUserCanceled(ssl, type))
2498925034
return FATAL_ERROR;
2499025035
else if (ret < 0)
2499125036
return ret;
@@ -25001,7 +25046,7 @@ static int DoProcessAlertRecord(WOLFSSL* ssl)
2500125046
if (type == decrypt_error)
2500225047
return FATAL_ERROR;
2500325048

25004-
/* RFC 8446 Section 6.2: In TLS 1.3, all error alerts MUST
25049+
/* RFC 9846 Section 6.2: In TLS 1.3, all error alerts MUST
2500525050
* be treated as fatal regardless of the AlertLevel byte.
2500625051
* Only close_notify (handled above) and user_canceled
2500725052
* are exempt. */
@@ -28634,7 +28679,7 @@ int IsSCR(WOLFSSL* ssl)
2863428679
!defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS)
2863528680
/*
2863628681
* Enforce limits specified in
28637-
* https://www.rfc-editor.org/rfc/rfc8446#section-5.5
28682+
* https://www.rfc-editor.org/rfc/rfc9846#section-5.5
2863828683
*/
2863928684
static int CheckTLS13AEADSendLimit(WOLFSSL* ssl)
2864028685
{
@@ -28701,6 +28746,44 @@ static int CheckTLS13AEADSendLimit(WOLFSSL* ssl)
2870128746
ssl->keys.sequence_number_lo);
2870228747
}
2870328748

28749+
#ifdef WOLFSSL_EARLY_DATA
28750+
/* RFC 9846 Section 5.5: a KeyUpdate cannot be performed for early data, so
28751+
* a sender MUST NOT exceed the limits while sending it. There is no way to
28752+
* rekey here - the handshake has not finished, so a KeyUpdate would be out
28753+
* of order - and the write has to fail instead.
28754+
*
28755+
* Stop one record early. If the server accepts the early data the client
28756+
* still owes it an EndOfEarlyData, and Section 2.3 has that message go out
28757+
* under the same 0-RTT traffic keys. Spending the last record on
28758+
* application data would leave that message to overrun the limit.
28759+
*
28760+
* seq is the number the next record will use, so it also counts the
28761+
* records already sent under this key. Refusing once seq + 1 reaches the
28762+
* limit stops application data at seq == limit - 2 and keeps the final
28763+
* slot, seq == limit - 1, free for the EndOfEarlyData. */
28764+
if (ssl->options.side == WOLFSSL_CLIENT_END &&
28765+
ssl->earlyData != no_early_data &&
28766+
ssl->earlyData != done_early_data) {
28767+
w64wrapper afterThis = seq;
28768+
28769+
w64Increment(&afterThis);
28770+
/* cppcheck-suppress uninitvar
28771+
* (false positive from cppcheck-2.13.0: every switch arm above either
28772+
* sets limit or returns) */
28773+
if (w64GTE(afterThis, limit)) {
28774+
WOLFSSL_MSG("AEAD limit reached while sending early data");
28775+
/* Deliberately not recorded here. The caller decides whether this
28776+
* is an error: a write whose last record took the final slot has
28777+
* completed and returns success, and recording it now would leave
28778+
* TOO_MUCH_EARLY_DATA in the OpenSSL-compatibility error queue for
28779+
* a call that reported no failure. SendData() records it on the
28780+
* path that does fail. */
28781+
return TOO_MUCH_EARLY_DATA;
28782+
}
28783+
return 0;
28784+
}
28785+
#endif
28786+
2870428787
if (w64GTE(seq, limit)) { /* cppcheck-suppress uninitvar
2870528788
* (false positive from cppcheck-2.13.0)
2870628789
*/
@@ -28966,6 +29049,27 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz)
2896629049
#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS)
2896729050
if (IsAtLeastTLSv1_3(ssl->version)) {
2896829051
ret = CheckTLS13AEADSendLimit(ssl);
29052+
#ifdef WOLFSSL_EARLY_DATA
29053+
/* This check runs at the top of every iteration, including the
29054+
* one after the last record, so it also fires when the write is
29055+
* already complete and its final record happened to reach the
29056+
* limit. Nothing is left to send, so report the success: without
29057+
* this the caller would see WOLFSSL_FATAL_ERROR for a write that
29058+
* fully landed.
29059+
*
29060+
* A write stopped part way through is a failure, not a short
29061+
* return. Short returns here are gated on partialWrite, and
29062+
* wolfSSL_get_error(ssl, ret) reports nothing for a positive ret,
29063+
* so a short count would be indistinguishable from a complete one
29064+
* to a caller following the documented contract. */
29065+
if (ret == WC_NO_ERR_TRACE(TOO_MUCH_EARLY_DATA)) {
29066+
if (sent == (word32)sz) {
29067+
break;
29068+
}
29069+
/* Failing for real, so now it is worth recording. */
29070+
WOLFSSL_ERROR_VERBOSE(ret);
29071+
}
29072+
#endif
2896929073
if (ret != 0) {
2897029074
ssl->error = ret;
2897129075
return WOLFSSL_FATAL_ERROR;
@@ -38414,7 +38518,14 @@ const byte* MaskCurve25519PeerKey(const byte* pub, word32 pubSz,
3841438518
int TranslateErrorToAlert(int err)
3841538519
{
3841638520
switch (err) {
38521+
/* RFC 9846 Section 4.3 requires a "decode_error" alert when an
38522+
* extension has data left over after its structure is parsed, and
38523+
* Section 6.2 defines the alert for any field out of range or
38524+
* message of incorrect length. The extension parsers report those
38525+
* as either BUFFER_ERROR or the wolfCrypt BUFFER_E; both must map
38526+
* here, or the handshake aborts silently with no alert sent. */
3841738527
case WC_NO_ERR_TRACE(BUFFER_ERROR):
38528+
case WC_NO_ERR_TRACE(BUFFER_E):
3841838529
return decode_error;
3841938530
case WC_NO_ERR_TRACE(EXT_NOT_ALLOWED):
3842038531
case WC_NO_ERR_TRACE(PEER_KEY_ERROR):

0 commit comments

Comments
 (0)