Conformance to RFC 9846: New version of TLS 1.3 specification

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.
pull/11215/head
Sean Parkinson 2026-08-20 12:50:04 +10:00
parent 437bca85a7
commit e8fac4e0e8
13 changed files with 1108 additions and 30 deletions

View File

@ -170,6 +170,23 @@
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`. The write stays all-or-nothing, matching
every other non-`partialWrite` return from `SendData()`. The budget also
now stops one record short, because an accepted 0-RTT exchange still owes
the server an `EndOfEarlyData`, and RFC 9846 Section 2.3 sends that under the
same early traffic keys. Callers that hit this should abandon early data on
the connection rather than resume the send from an offset. 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 +215,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

View File

@ -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 ]
)

View File

@ -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,15 @@ 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. The write is all-or-nothing as
usual: a value less than sz is never returned. Note that a failed call may
still have put records on the wire before the limit was reached, so treat
the connection as unusable for early data rather than resuming the send
from an offset.
\return the amount of early data written in bytes if successful.
_Example_

View File

@ -88,6 +88,16 @@ 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. */
ssl->alert_history.last_tx.code = -1;
ssl->alert_history.last_tx.level = -1;
/* Reset states */
ssl->options.serverState = NULL_STATE;
ssl->options.clientState = NULL_STATE;

View File

@ -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,41 @@ 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 is demonstrably running TLS 1.3, and
* neither obvious flag is enough on its own. ssl->version holds the highest
* version this side offered until the peer's choice is known, and
* options.tls1_3 is set from the session by wolfSSL_set_session() before any
* ServerHello, so a downgrade-capable client resuming a TLS 1.3 session has
* both set while the peer may still pick TLS 1.2. Exempting on either would
* swallow a pre-1.3 peer's fatal alert and leave the caller waiting for a
* handshake that is never coming.
*
* Requiring the record to have been decrypted settles it: in TLS 1.3 every
* record after the ServerHello is encrypted, so a decrypted alert can only
* have arrived on a connection whose version is already agreed - which is the
* only situation RFC 9846 Section 6.1 is describing. keys.decryptedCur is a
* property of the record in hand, not a latch: GetRecordHeader() clears it for
* each new record and only a successful decrypt sets it, so an earlier
* encrypted record cannot lend its status to a later plaintext alert.
*
* 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 && ssl->keys.decryptedCur &&
(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 +24007,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 +24071,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 +24132,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 +24992,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 +25010,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 +28639,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)
{
@ -28661,6 +28706,44 @@ static int CheckTLS13AEADSendLimit(WOLFSSL* ssl)
ssl->keys.sequence_number_lo);
}
#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 here - the handshake has not finished, so a KeyUpdate would be out
* of order - and the write has to fail instead.
*
* Stop one record early. If the server accepts the early data the client
* still owes it an EndOfEarlyData, and Section 2.3 has that message go out
* under the same 0-RTT traffic keys. Spending the last record on
* application data would leave that message to overrun the limit.
*
* seq is the number the next record will use, so it also counts the
* records already sent under this key. Refusing once seq + 1 reaches the
* limit stops application data at seq == limit - 2 and keeps the final
* slot, seq == limit - 1, free for the EndOfEarlyData. */
if (ssl->options.side == WOLFSSL_CLIENT_END &&
ssl->earlyData != no_early_data &&
ssl->earlyData != done_early_data) {
w64wrapper afterThis = seq;
w64Increment(&afterThis);
/* cppcheck-suppress uninitvar
* (false positive from cppcheck-2.13.0: every switch arm above either
* sets limit or returns) */
if (w64GTE(afterThis, limit)) {
WOLFSSL_MSG("AEAD limit reached while sending early data");
/* Deliberately not recorded here. The caller decides whether this
* is an error: a write whose last record took the final slot has
* completed and returns success, and recording it now would leave
* TOO_MUCH_EARLY_DATA in the OpenSSL-compatibility error queue for
* a call that reported no failure. SendData() records it on the
* path that does fail. */
return TOO_MUCH_EARLY_DATA;
}
return 0;
}
#endif
if (w64GTE(seq, limit)) { /* cppcheck-suppress uninitvar
* (false positive from cppcheck-2.13.0)
*/
@ -28926,6 +29009,27 @@ 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
/* This check runs at the top of every iteration, including the
* one after the last record, so it also fires when the write is
* already complete and its final record happened to reach the
* limit. Nothing is left to send, so report the success: without
* this the caller would see WOLFSSL_FATAL_ERROR for a write that
* fully landed.
*
* A write stopped part way through is a failure, not a short
* return. Short returns here are gated on partialWrite, and
* wolfSSL_get_error(ssl, ret) reports nothing for a positive ret,
* so a short count would be indistinguishable from a complete one
* to a caller following the documented contract. */
if (ret == WC_NO_ERR_TRACE(TOO_MUCH_EARLY_DATA)) {
if (sent == (word32)sz) {
break;
}
/* Failing for real, so now it is worth recording. */
WOLFSSL_ERROR_VERBOSE(ret);
}
#endif
if (ret != 0) {
ssl->error = ret;
return WOLFSSL_FATAL_ERROR;
@ -38336,7 +38440,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):

View File

@ -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);
}
}
}

View File

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

View File

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

View File

@ -2422,3 +2422,67 @@ int test_dtls13_plaintext_ack_after_handshake(void)
#endif
return EXPECT_RESULT();
}
/* DtlsResetState() runs whenever a DTLS server abandons a ClientHello on the
* stateless path - which includes the ordinary cookie exchange, since the
* object goes back to awaiting the verified ClientHello after sending the
* HelloRetryRequest.
*
* It must not leave an alert behind in the history. DoClientHello() can send a
* fatal alert there and then swallow the error (DtlsIgnoreError) so the
* listener stays up; a stale alert_fatal makes the "already sent a more
* specific fatal alert" guards suppress every later alert from that object, so
* one bad ClientHello would mute alerts for every peer that follows. The reset
* value has to be the -1 "no alert" sentinel InitSSL() uses, not zero, which
* would read back as a close_notify that was never sent.
*
* The alert is planted directly rather than provoked: the error paths that
* reach it need a malformed ClientHello that survives record and extension
* parsing far enough to fail in the cookie check, and what this guards is the
* reset itself. */
int test_dtls13_reset_clears_alert_history(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
!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;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0);
/* Client sends ClientHello 1. */
ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
if (EXPECT_SUCCESS() && ssl_s != NULL) {
/* Stand in for an alert sent while rejecting an earlier ClientHello. */
ssl_s->alert_history.last_tx.code = decode_error;
ssl_s->alert_history.last_tx.level = alert_fatal;
}
/* Server answers statelessly and resets to await the verified
* ClientHello, taking DtlsResetState() with it. */
ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
if (EXPECT_SUCCESS() && ssl_s != NULL) {
/* Back to "nothing sent", so the guards do not mute the next alert,
* and wolfSSL_get_alert_history() does not report a phantom
* close_notify (code 0) or alert_none (level 0). */
ExpectIntEQ(ssl_s->alert_history.last_tx.level, -1);
ExpectIntEQ(ssl_s->alert_history.last_tx.code, -1);
}
/* The exchange still completes normally afterwards. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

View File

@ -60,6 +60,7 @@ int test_dtls13_reuse_after_clear(void);
int test_dtls13_epoch_slot_reuse_replay(void);
int test_dtls13_epoch_slot_reuse_decrypt_epoch(void);
int test_dtls13_plaintext_ack_after_handshake(void);
int test_dtls13_reset_clears_alert_history(void);
#define TEST_DTLS13_DECLS \
TEST_DECL_GROUP("dtls13", test_dtls13_bad_epoch_ch), \
@ -90,6 +91,7 @@ int test_dtls13_plaintext_ack_after_handshake(void);
TEST_DECL_GROUP("dtls13", test_dtls13_reuse_after_clear), \
TEST_DECL_GROUP("dtls13", test_dtls13_epoch_slot_reuse_replay), \
TEST_DECL_GROUP("dtls13", test_dtls13_epoch_slot_reuse_decrypt_epoch), \
TEST_DECL_GROUP("dtls13", test_dtls13_plaintext_ack_after_handshake)
TEST_DECL_GROUP("dtls13", test_dtls13_plaintext_ack_after_handshake), \
TEST_DECL_GROUP("dtls13", test_dtls13_reset_clears_alert_history)
#endif /* TESTS_API_DTLS13_H */

View File

@ -9122,6 +9122,759 @@ 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;
WOLFSSL_SESSION *sess = 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);
}
/* The session is not invalidated either: the TLS 1.3 exemption covers
* eviction as well as teardown, and this connection has genuinely
* negotiated TLS 1.3 so it is the case RFC 9846 6.1 describes. Removing it
* now must find it still cached. */
#if !defined(NO_SESSION_CACHE) && defined(HAVE_SESSION_TICKET) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB)
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_c, sess), 1);
wolfSSL_SESSION_free(sess);
#endif
(void)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 and
* left BAD_STATE_E on the write-side object. */
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();
}
/* The AEAD limit check runs at the top of every SendData() record loop
* iteration, including the one after the last record. A write whose final
* record takes the last available slot therefore meets the check on the way
* out, with sent == sz - a complete write. It must not leave
* TOO_MUCH_EARLY_DATA behind: wolfSSL_get_error() would call a fully
* successful write a failure, and ReceiveData() refuses to read on an object
* with a stored error.
*
* Leave room for exactly one application-data record and write a single
* record's worth, so the write completes and takes the last slot the
* early-data budget has to give - the one after it stays reserved for
* EndOfEarlyData. */
int test_tls13_early_data_AEAD_limit_exact(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[] = "exactly-at-the-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";
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);
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, MAX_EARLY_DATA_SZ), 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) {
/* Two below the AES-GCM limit of 2^24.5. The record below is the last
* application-data record allowed: the final slot stays reserved for
* the EndOfEarlyData that an accepted 0-RTT exchange still owes. */
ssl_c->keys.sequence_number_hi = 0;
ssl_c->keys.sequence_number_lo = 0x016A09E6 - 2;
}
#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_ERROR_QUEUE)
/* Start from a clean queue so the check below is about this write. */
wolfSSL_ERR_clear_error();
#endif
/* Complete write: full count, and no error left on the object. */
ExpectIntEQ(wolfSSL_write_early_data(ssl_c, earlyMsg, (int)sizeof(earlyMsg),
&written), (int)sizeof(earlyMsg));
ExpectIntEQ(written, (int)sizeof(earlyMsg));
if (EXPECT_SUCCESS() && ssl_c != NULL) {
ExpectIntEQ(ssl_c->error, 0);
}
#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_ERROR_QUEUE)
/* Nothing queued either: the limit check runs on the way out of a
* completed write, and an entry left there would surface from
* ERR_get_error() as a failure the caller never had. */
ExpectIntEQ(wolfSSL_ERR_peek_error(), 0);
#endif
/* The budget really is exhausted, so the next attempt fails cleanly. */
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));
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();
}
/* Companion to test_tls13_early_data_AEAD_limit: a write that would cross the
* limit part way through must fail outright rather than report a short count.
*
* SendData()'s non-partialWrite contract is all-or-error, and
* wolfSSL_get_error(ssl, ret) reports nothing for a positive ret, so a short
* count would be indistinguishable from a complete write to a caller following
* the documented contract - it would silently drop the tail.
*
* Leave room for exactly one application-data record - the slot after it is
* reserved for EndOfEarlyData - then write more than one record's worth. The
* first record goes out and the second trips the check, which is the partial
* case: the call must still be refused outright, with nothing reported as
* sent. */
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) {
/* Two below the AES-GCM limit of 2^24.5. One record still fits - the
* slot after it is reserved for EndOfEarlyData - so the first record
* of this multi-record write goes out and the second trips the
* check, which is the partial case under test. */
ssl_c->keys.sequence_number_hi = 0;
ssl_c->keys.sequence_number_lo = 0x016A09E6 - 2;
}
/* Refused outright, reported through the conventional idiom, and nothing
* claimed as written. */
first = wolfSSL_write_early_data(ssl_c, earlyMsg, (int)sizeof(earlyMsg),
&written);
ExpectIntEQ(first, WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, first),
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 exempts "user_canceled" from tearing a TLS 1.3
* connection down whatever AlertLevel was used. That only applies once the
* connection really is running TLS 1.3.
*
* Neither obvious flag proves that before the ServerHello arrives:
* ssl->version holds the highest version offered, and options.tls1_3 is set
* straight from the session by wolfSSL_set_session(). A client resuming a TLS
* 1.3 session therefore has both set while the server may still choose TLS
* 1.2, so exempting on either would swallow that peer's fatal alert and leave
* the caller waiting for a handshake that never comes.
*
* Both shapes are driven here - a plain TLS 1.3 client, and one that has
* loaded a TLS 1.3 session so options.tls1_3 is already set - and a fatal
* user_canceled arriving before negotiation must terminate either way. The
* exemption itself is covered on a completed connection by
* test_tls13_user_canceled_encrypted. */
int test_tls13_user_canceled_fatal_level(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)
/* 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 resuming;
for (resuming = 0; resuming < 2 && !EXPECT_FAIL(); resuming++) {
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;
WOLFSSL_ALERT_HISTORY h;
char buf[64];
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);
if (resuming) {
/* A completed handshake first, so there is a TLS 1.3 session to
* load on the connection under test. */
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));
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);
}
/* 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) {
/* Loading the session already claims TLS 1.3 even though nothing
* has been negotiated - the state this test exists to guard. */
ExpectIntEQ(ssl_c->options.tls1_3, resuming ? 1 : 0);
ExpectIntEQ(ssl_c->keys.decryptedCur, 0);
}
ExpectIntEQ(test_memio_inject_message(&test_ctx, 1,
(const char *)fatalUserCanceled, sizeof(fatalUserCanceled)), 0);
/* The version is not settled, so the alert is acted on. */
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);
}
/* 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);
if (resuming) {
/* Treated as a pre-1.3 fatal alert, so the session goes too. */
ExpectIntEQ(wolfSSL_SSL_CTX_remove_session(ctx_c, sess), 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();
}
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \

View File

@ -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_early_data_AEAD_limit_exact(void);
int test_tls13_user_canceled_fatal_level(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_early_data_AEAD_limit_exact), \
TEST_DECL_GROUP("tls13", test_tls13_user_canceled_fatal_level), \
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)

View File

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