Skip to content

TLS 1.3: evict session from cache after accepted 0-RTT resumption#10221

Open
julek-wolfssl wants to merge 1 commit intowolfSSL:masterfrom
julek-wolfssl:gh/10197
Open

TLS 1.3: evict session from cache after accepted 0-RTT resumption#10221
julek-wolfssl wants to merge 1 commit intowolfSSL:masterfrom
julek-wolfssl:gh/10197

Conversation

@julek-wolfssl
Copy link
Copy Markdown
Member

Per RFC 8446 section 8, a server MUST ensure that any instance of it
would accept 0-RTT for the same 0-RTT handshake at most once. Without
this, the same ClientHello could be replayed to re-accept early data on
a subsequent connection.

After the PSK is authenticated (binder verified) in DoPreSharedKeys,
call wolfSSL_SSL_CTX_remove_session on ssl->session when the client
offered 0-RTT and the session permits it. That evicts the entry from
the internal cache (under the row's write lock) and invokes the
application's ctx->rem_sess_cb so any external cache can drop its copy
too. The session's timeout is also cleared so the live reference held
by the current handshake cannot be resumed again.

The mutation is paid only when the client actually included the
early_data extension on a 0-RTT-capable session, so normal resumptions
are unaffected and the existing remove-callback counts in
test_wolfSSL_CTX_add_session_ext_{tls13,dtls13} stay correct.

wolfSSL_SSL_CTX_remove_session was previously declared and defined only
under the OpenSSL compatibility layer. Because it is now called from
the core TLS 1.3 PSK path, the declaration in wolfssl/ssl.h and the
definition in src/ssl_sess.c are moved out of that block to match the
existing !NO_SESSION_CACHE gate under which the function is meaningful.
wolfSSL_SSL_get0_session stays in the compat block.

test_tls13_early_data_0rtt_replay verifies the behaviour. It does a
full TLS 1.3 handshake with stateful tickets (SSL_OP_NO_TICKET) and
max_early_data > 0, then tries to resume the saved session twice while
offering 0-RTT each time. A minimal single-slot external session cache
is wired up via wolfSSL_CTX_sess_set_{new,get,remove}_cb to confirm
both caches are cleared. Round 0 must resume and deliver the early
data, and rem_calls must hit 1 (the fix's single eviction). Round 1
must fall back to a full handshake (session_reused == 0), deliver no
early data, and leave rem_calls at 1.

Verified against multiple configurations (incl. --enable-all
--enable-earlydata, the no-compat -DHAVE_EXT_CACHE build, and the
os-check.yml combo). Valgrind under -g2 -O0 with OPENSSL_EXTRA +
HAVE_EXT_CACHE + HAVE_EX_DATA reports no errors and no
definitely-lost bytes.

Refs #10197

Per RFC 8446 section 8, a server MUST ensure that any instance of it
would accept 0-RTT for the same 0-RTT handshake at most once. Without
this, the same ClientHello could be replayed to re-accept early data on
a subsequent connection.

After the PSK is authenticated (binder verified) in DoPreSharedKeys,
call wolfSSL_SSL_CTX_remove_session on ssl->session when the client
offered 0-RTT and the session permits it. That evicts the entry from
the internal cache (under the row's write lock) and invokes the
application's ctx->rem_sess_cb so any external cache can drop its copy
too. The session's timeout is also cleared so the live reference held
by the current handshake cannot be resumed again.

The mutation is paid only when the client actually included the
early_data extension on a 0-RTT-capable session, so normal resumptions
are unaffected and the existing remove-callback counts in
test_wolfSSL_CTX_add_session_ext_{tls13,dtls13} stay correct.

wolfSSL_SSL_CTX_remove_session was previously declared and defined only
under the OpenSSL compatibility layer. Because it is now called from
the core TLS 1.3 PSK path, the declaration in wolfssl/ssl.h and the
definition in src/ssl_sess.c are moved out of that block to match the
existing !NO_SESSION_CACHE gate under which the function is meaningful.
wolfSSL_SSL_get0_session stays in the compat block.

test_tls13_early_data_0rtt_replay verifies the behaviour. It does a
full TLS 1.3 handshake with stateful tickets (SSL_OP_NO_TICKET) and
max_early_data > 0, then tries to resume the saved session twice while
offering 0-RTT each time. A minimal single-slot external session cache
is wired up via wolfSSL_CTX_sess_set_{new,get,remove}_cb to confirm
both caches are cleared. Round 0 must resume and deliver the early
data, and rem_calls must hit 1 (the fix's single eviction). Round 1
must fall back to a full handshake (session_reused == 0), deliver no
early data, and leave rem_calls at 1.

Verified against multiple configurations (incl. --enable-all
--enable-earlydata, the no-compat -DHAVE_EXT_CACHE build, and the
os-check.yml combo). Valgrind under -g2 -O0 with OPENSSL_EXTRA +
HAVE_EXT_CACHE + HAVE_EX_DATA reports no errors and no
definitely-lost bytes.

Refs wolfSSL#10197
@julek-wolfssl julek-wolfssl self-assigned this Apr 14, 2026
Copilot AI review requested due to automatic review settings April 14, 2026 17:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR enforces TLS 1.3 0‑RTT anti-replay requirements by evicting a resumed session from caches after accepting early data, preventing the same ClientHello from being replayed to re-accept 0‑RTT.

Changes:

  • Evict 0‑RTT-capable sessions from the internal + external session caches after PSK authentication when the client offers early data.
  • Move wolfSSL_SSL_CTX_remove_session declaration/definition out of the OpenSSL-compat-only block so core TLS 1.3 code can call it.
  • Add a TLS 1.3 API test that attempts two 0‑RTT resumptions and verifies the second falls back to a full handshake and caches are cleared once.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
wolfssl/ssl.h Exposes wolfSSL_SSL_CTX_remove_session under !NO_SESSION_CACHE for core TLS usage.
src/ssl_sess.c Moves wolfSSL_SSL_CTX_remove_session implementation out of OpenSSL-compat gating while keeping cache gating.
src/tls13.c Evicts session upon successful PSK selection when 0‑RTT is offered and permitted.
tests/api/test_tls13.h Registers new TLS 1.3 0‑RTT replay test in the API test list.
tests/api/test_tls13.c Adds the 0‑RTT replay regression test with a minimal external session cache to validate eviction behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

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.

[Bug]: 0-RTT Anti-Replay Minimum Requirement Not Enforced (RFC 8446 Section 8)

2 participants