Skip to content

TLS 1.3: gate 0-RTT on a session cache hit - #11295

Open
julek-wolfssl wants to merge 3 commits into
wolfSSL:masterfrom
julek-wolfssl:tls13-0rtt-single-use-bound
Open

julek-wolfssl wants to merge 3 commits into
wolfSSL:masterfrom
julek-wolfssl:tls13-0rtt-single-use-bound

Conversation

@julek-wolfssl

Copy link
Copy Markdown
Member

RFC 8446 Section 8 requires that a server accept 0-RTT for the same handshake at most once, so replay cannot be used for timing measurements or to exhaust rate limits. This bound did not hold for self-encrypted tickets.

  • CheckPreSharedKeys() gated 0-RTT on wolfSSL_SSL_CTX_remove_session() reporting removal, but that return value cannot carry the answer: it reports success whenever it invokes ctx->rem_sess_cb, which returns void, so any context with a remove callback registered passed the gate every time.
  • SendTls13NewSessionTicket() generated a fresh session ID only for a ticket sent as an ID, so a reissued self-encrypted ticket reused the ID and AddSession() restored the entry that accepting the earlier ticket's early data had removed.

0-RTT is now gated on the ticket's session still being present in the session cache, which DoClientTicketFinalize() already looks up and which an external cache reports via wolfSSL_CTX_sess_set_get_cb(). The accepted connection evicts the entry, so a later use of the same ticket misses (eviction, and thus the bound, is best effort).

Every ticket now carries its own session ID, so a reissued ticket no longer restores a consumed entry. As a result, a server issuing many tickets over the same session will use more cache entries than before.

A ticket sent as an ID was unaffected by the original bug, since it cannot resume at all without a cache hit.

Copilot AI lite review requested due to automatic review settings August 26, 2026 21:56
@julek-wolfssl julek-wolfssl self-assigned this Aug 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens TLS 1.3 0-RTT anti-replay behavior (RFC 8446 §8) by ensuring early data is accepted only when the ticket’s corresponding session is still present in the session cache, and by preventing reissued self-encrypted tickets from reusing (and thereby resurrecting) the same cache entry.

Changes:

  • Gate 0-RTT acceptance on a recorded session-cache hit (ticketCacheHit) and evict the cache entry upon accepting early data.
  • Generate a fresh per-ticket session ID for TLS 1.3 tickets (including self-encrypted tickets when WOLFSSL_TICKET_HAVE_ID is enabled) to avoid restoring consumed entries.
  • Extend/adjust tests to cover external-cache replay behavior, remove-callback replay behavior, and QUIC early-data ticket chaining.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
wolfssl/internal.h Adds ticketCacheHit option bit to track whether the ticket session was found in cache.
src/internal.c Sets ticketCacheHit based on whether ticket finalization had a cache-backed session (or session lookup succeeded).
src/tls13.c Uses ticketCacheHit to gate 0-RTT and evicts the session entry on acceptance; ensures new per-ticket IDs are generated appropriately.
src/ssl.c Clears ticketCacheHit in wolfSSL_clear() to avoid cross-handshake state leakage.
tests/quic.c Updates QUIC early-data test to resume using the newly issued ticket after consuming one for 0-RTT.
tests/api/test_tls13.h Registers new TLS 1.3 replay-related tests.
tests/api/test_tls13.c Adds replay-round helper + new tests covering external-cache replay and remove-callback replay cases.
tests/api/test_session.c Adjusts session removal expectations due to new per-ticket session IDs increasing cache entries.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@wolfSSL-Bot

Copy link
Copy Markdown

Can one of the admins verify this patch?

@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

2 similar comments
@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

@julek-wolfssl
julek-wolfssl force-pushed the tls13-0rtt-single-use-bound branch from ef3c453 to e85d320 Compare September 2, 2026 17:37
@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

1 similar comment
@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #11295

Scan targets checked: wolfcrypt-rs-bugs, wolfssl-bugs, wolfssl-src

Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Reported findings require changes before merge.

Comment thread tests/quic.c
Comment thread src/tls13.c
Comment thread src/tls13.c Outdated
@philljj
philljj self-requested a review September 7, 2026 20:28
@philljj philljj assigned julek-wolfssl and unassigned wolfSSL-Bot Sep 8, 2026
@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

@julek-wolfssl
julek-wolfssl force-pushed the tls13-0rtt-single-use-bound branch from 5f2c003 to 10ec03c Compare September 10, 2026 14:16
RFC 8446 Section 8 asks that one server instance accept 0-RTT for the
same handshake at most once, so repeated replay cannot be used for
timing measurements or to exhaust rate limits. That bound was not held
for self-encrypted tickets. A ticket sent as an ID was unaffected: it
cannot resume at all without a cache hit.

CheckPreSharedKeys() gated 0-RTT on wolfSSL_SSL_CTX_remove_session()
reporting that it removed the session. That return value cannot carry
the answer: it reports success whenever it invokes ctx->rem_sess_cb,
which returns void, so any context with a remove callback registered
passed the gate every time. Separately, SendTls13NewSessionTicket()
generated a fresh session ID only for a ticket sent as an ID, so a
reissued self-encrypted ticket reused the ID and AddSession() restored
the entry that accepting the earlier ticket's early data had removed.

0-RTT is now gated on the ticket's session still being in the session
cache, which DoClientTicketFinalize() already looks up and which an
external cache reports through wolfSSL_CTX_sess_set_get_cb(). The
accepted connection evicts the entry, so the next use of the same
ticket misses. Eviction is best effort, so the bound is too.

Every ticket now carries its own session ID, so a reissued one does not
restore a consumed entry. A server issuing many tickets over the same
session therefore uses more cache entries than before.
Generating a new session ID for every NewSessionTicket detached
server-side session state from the cache on resumption: wolfSSL keys
WOLFSSL_SESSION ex_data on the session ID, so an application that marks
a session at handshake time (stunnel's redirect authentication marker)
lost the mark on every resume. stunnel test 183 caught this.

Only a session that can carry early data needs its own cache entry per
ticket to back the RFC 8446 Sect. 8 single-use bound, so gate the new ID
on maxEarlyDataSz.

Also drop an unused array in test_tls13_0rtt_remove_cb_replay that
clang-tidy flagged as a dead store.
…ello

Reset ticketCacheHit at the top of DoPreSharedKeys() next to
ticketPredatesCtx. It was only cleared by wolfSSL_clear(), so the second
ClientHello of an HRR exchange gated 0-RTT on the first hello's stale
value.

Only accept early data when wolfSSL_SSL_CTX_remove_session() reports an
eviction. With HAVE_EXT_CACHE, WOLFSSL_SESS_CACHE_NO_INTERNAL and no
rem_sess_cb it removes nothing, so one ticket's 0-RTT was accepted an
unlimited number of times.

Clear session after freeing it in test_quic_early_data. Expect() skips
its body once an earlier expectation failed, leaving the freed pointer
to be freed again during cleanup.
@julek-wolfssl
julek-wolfssl force-pushed the tls13-0rtt-single-use-bound branch from 10ec03c to 569ae5f Compare September 15, 2026 10:32
@julek-wolfssl

Copy link
Copy Markdown
Member Author

retest this please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants