TLS 1.3: evict session from cache after accepted 0-RTT resumption#10221
Open
julek-wolfssl wants to merge 1 commit intowolfSSL:masterfrom
Open
TLS 1.3: evict session from cache after accepted 0-RTT resumption#10221julek-wolfssl wants to merge 1 commit intowolfSSL:masterfrom
julek-wolfssl wants to merge 1 commit intowolfSSL:masterfrom
Conversation
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
Contributor
There was a problem hiding this comment.
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_sessiondeclaration/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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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