Skip to content

Fix TLS 1.2 session ticket handling when the server declines the ticket - #12064

Open
Frauschi wants to merge 2 commits into
wolfSSL:masterfrom
Frauschi:tls12_ticket_session_id
Open

Frauschi wants to merge 2 commits into
wolfSSL:masterfrom
Frauschi:tls12_ticket_session_id

Conversation

@Frauschi

@Frauschi Frauschi commented Nov 9, 2026

Copy link
Copy Markdown
Member

Fix TLS 1.2 session ticket handling when a server declines the ticket, on both the client and the server side (RFC 5077 Section 3.4).

Client:

  • A declining server that still sends a session ID no longer makes the handshake fail with OUT_OF_ORDER_E; the client falls back to a full handshake. This hit any wolfSSL server with its session cache on, for example after a restart with a new ticket key.
  • After the fallback the client drops the declined ticket and its made-up session ID, so the next connection resumes by the server's session ID instead of offering a stale ticket.

Server:

  • A ClientHello carrying a TLS 1.2 ticket no longer resumes by its session ID when the ticket is not used: failed decryption, wolfSSL_NoTicketTLSv12(), or no ticket callback.
  • With WOLFSSL_DTLS_NO_HVR_ON_RESUME, the stateless ClientHello check uses the session ID only when there is no ticket, so a declined ticket no longer skips the cookie exchange.

Tests:

  • test_tls12_resume_ticket_decline_fallback now keeps the server cache on and resumes a third connection by session ID.
  • New test_tls12_ticket_declined_no_id_resumption covers the three server cases, plus DTLS 1.2 with WOLFSSL_DTLS_NO_HVR_ON_RESUME.
  • Each test fails without its fix.

A TLS 1.2 client sends its ticket with an empty session ID and learns
from the server's next handshake message whether the ticket was
accepted, as RFC 5077 Section 3.4 allows. When a Certificate or
ServerKeyExchange follows, the client falls back to a full handshake,
but only if the ServerHello carried no session ID: a session ID was
taken as a promise to resume, and the handshake failed with
OUT_OF_ORDER_E.

A server that declines the ticket may still send a session ID, to offer
stateful resumption (RFC 5077 Section 3.4), and a client that sent an
empty session ID ignores it. Any server that declined or ignored the
ticket and sent a session ID made the client fail. That includes a
server without ticket support, and a wolfSSL server with its session
cache on, for example after a restart with a new ticket key. A server
that declines with an empty session ID, as OpenSSL does, was not
affected.

Keep the fatal error for a session ID the client sent and the server
echoed, and ignore the ServerHello session ID when the client offered a
ticket.

After the fallback the client also kept the declined ticket next to the
new session. Its next connection offered that ticket, so a server that
could still decrypt it resumed with the ticket's master secret while
the client used the new one, and the handshake failed. The fallback now
drops the ticket, as a failed Finished check already does, and the
client resumes by the new session ID instead.

The fallback also drops the session ID the client made up for the
ticket. With that ID left in place, SetupSession() stored the new
session under it, and the next connection offered it in place of the
server's session ID.

test_tls12_resume_ticket_decline_fallback turned the server's session
cache off to avoid the first failure. It now keeps the cache on, checks
that the declining server sent a session ID, and resumes a third
connection by that session ID. It fails without this change.

A second run gets its first ticket with an empty session ID, so the
client makes up an ID, and frees every other reference to the session
before the fallback, so the made-up ID survives to SetupSession(). It
fails without the new reset.
Copilot AI balanced review requested due to automatic review settings November 9, 2026 09:03
@Frauschi Frauschi self-assigned this Nov 9, 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.

🟢 Approval recommended

No active review issues are recorded for the TLS 1.2 session-ticket rejection changes.

No open issues 🎉

What changed in this PR

Fixes TLS 1.2 session-ticket rejection handling so clients can complete a full handshake and servers do not fall back to session-ID resumption when a ticket is declined, consistent with RFC 5077.

Changes:

  • Allows client fallback when the server supplies a new session ID, clearing the declined ticket and generated ID.
  • Prevents session-ID resumption alongside an unused ticket, including during DTLS cookie checks.
  • Expands regression tests for ticket rejection and subsequent session-ID resumption.
File Description
tests/​api/​test_tls.h Declares and registers the new server-side regression test.
tests/​api/​test_tls.c Adds rejection scenarios and extends client fallback tests through a third connection.
src/​tls.c Disables stateful resumption when a nonempty TLS 1.2 ticket is received.
src/​internal.c Permits full-handshake fallback and clears declined ticket state.
src/​dtls.c Prevents session-ID fallback from bypassing the DTLS cookie exchange when a ticket is present.

Review effort: Balanced

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +128 B (+0.0%, 801,316 B / 1,048,576 B, total: 76% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +128 B (+0.0%, 343,824 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +64 B (+0.0%, 313,744 B / 1,048,576 B, total: 30% used)

zephyr-frdm_rw612

  • FLASH: log_const_area -8 B, rodata -8 B, text +1,664 B (+0.6%, 284,112 B / 67,108,864 B, total: 0% used)

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

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

Scan targets checked: wolfssl-src, wolfssl-bugs
Not reviewed (no scan target covers them): tests/api/test_tls.h
Coverage: 4 of 4 in-scope changed file(s) opened by the reviewer

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

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

Review tier: Max

Comment thread src/dtls.c Resolved
@Frauschi

Frauschi commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

@claude review

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread src/dtls.c Resolved
RFC 5077 Section 3.4 says a server that gets a ticket must not use the
ClientHello session ID for stateful resumption. A wolfSSL server that
did not use the ticket kept the resumption attempt the session ID had
started, and looked the ID up in its session cache. That happened when
the ticket failed to decrypt, when TLS 1.2 tickets were turned off with
wolfSSL_NoTicketTLSv12(), and when no ticket callback was set. A client
that sends a session ID next to its ticket, as OpenSSL does, could then
be resumed from the cache. End the resumption attempt whenever a TLS
1.2 ClientHello carries a ticket; an accepted ticket starts it again.

With WOLFSSL_DTLS_NO_HVR_ON_RESUME the stateless ClientHello check made
the same choice. A ticket that failed to decrypt fell back to the
session ID, so the server skipped the cookie exchange and then ran a
full handshake. It also resumed on a ticket that decrypted while
wolfSSL_NoTicketTLSv12() was set, which the server then ignores. The
check now looks at the session ID only when the ClientHello has no
ticket, and treats a TLS 1.2 ticket as declined under
wolfSSL_NoTicketTLSv12(). An empty ticket counts as a ticket when the
server would answer it with a new one, because the server then does
not resume by the session ID either.

test_tls12_ticket_declined_no_id_resumption puts a session ID the
server has cached into a ClientHello carrying a ticket the server
cannot use, for each of the three cases. It checks that the server does
not resume. The wolfSSL_NoTicketTLSv12() case uses a ticket the server
can decrypt. With WOLFSSL_DTLS_NO_HVR_ON_RESUME, the only build where
the stateless check can resume, DTLS 1.2 runs of the three cases and of
a cached session ID with an empty ticket also check that the server
asks for a cookie. Each case fails without this change.
@Frauschi
Frauschi force-pushed the tls12_ticket_session_id branch from 6195b8b to 7ea4866 Compare November 9, 2026 10:40

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

Scan targets checked: wolfssl-src, wolfssl-bugs
Not reviewed (no scan target covers them): tests/api/test_tls.h
Coverage: 1 of 2 in-scope changed file(s) opened by the reviewer; not opened: tests/api/test_tls.c

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Lite

@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed their stale review November 9, 2026 10:49

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

@Frauschi Frauschi assigned wolfSSL-Bot and unassigned Frauschi Nov 9, 2026
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.

4 participants