Only send TLS 1.3 NewSessionTicket when the client advertised PSK modes

RFC 9846 Section 4.3.9 says psk_key_exchange_modes restricts the PSKs the
server may supply through NewSessionTicket, and Section 4.7.1 makes sending
a ticket conditional on the ClientHello carrying a suitable extension. The
send paths only checked that tickets were enabled.

Record the modes from the ClientHello in Options - the extension object is
freed with the rest of the handshake state, so wolfSSL_send_SessionTicket()
cannot look it up. The automatic path skips the ticket; the explicit API
returns MISSING_HANDSHAKE_DATA or PSK_KEY_ERROR.

Define WOLFSSL_TLS13_TICKET_NO_PSK_MODES for the old behaviour.

Fixes https://github.com/wolfSSL/wolfssl/issues/11126
pull/11178/head
Juliusz Sosinowicz 2026-08-14 16:00:07 +00:00
parent 252c3b1aa3
commit 456b7e6a27
5 changed files with 233 additions and 2 deletions

View File

@ -13060,8 +13060,16 @@ static int TLSX_PskKeModes_Parse(WOLFSSL* ssl, const byte* input, word16 length,
byte modes;
ret = TLSX_PskKeyModes_Parse_Modes(input, length, msgType, &modes);
if (ret == 0)
if (ret == 0) {
#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_SERVER)
/* Keep the advertised modes for the NewSessionTicket decision. The
* extension object is dropped with the rest of the handshake state
* once the handshake is done. */
ssl->options.pskKeModes = modes;
ssl->options.pskKeModesRecvd = 1;
#endif
ret = TLSX_PskKeyModes_Use(ssl, modes);
}
if (ret != 0) {
WOLFSSL_ERROR_VERBOSE(ret);

View File

@ -74,6 +74,11 @@
* WOLFSSL_TICKET_HAVE_ID: Session tickets include ID default: off
* Forced on when WOLFSSL_EARLY_DATA is set.
* WOLFSSL_TICKET_NONCE_MALLOC: Dynamically allocate ticket nonce default: off
* WOLFSSL_TLS13_TICKET_NO_PSK_MODES: Send NewSessionTicket even default: off
* when the ClientHello advertised no usable
* psk_key_exchange_modes. Restores the pre-check
* behaviour; RFC 9846 Sections 4.3.9 and 4.7.1 say
* the server should not send such a ticket.
*
* TLS 1.3 Key Exchange:
* HAVE_KEYING_MATERIAL: Export keying material (RFC 8446 7.5) default: off
@ -14596,6 +14601,48 @@ restore:
}
#endif
/* Check the client advertised a PSK key exchange mode a resumption ticket can
* be used with.
*
* RFC 9846 Section 4.3.9: psk_key_exchange_modes restricts both the PSKs
* offered in the ClientHello and those the server might supply through
* NewSessionTicket, and servers should not send tickets that are incompatible
* with the advertised modes. RFC 9846 Section 4.7.1 makes sending a ticket
* conditional on the client's hello carrying a suitable extension.
*
* ssl The SSL/TLS object.
* returns 0 when a ticket may be sent, MISSING_HANDSHAKE_DATA when the
* extension was not received and PSK_KEY_ERROR when none of the
* advertised modes is usable.
*/
static int CheckTls13TicketPskModes(WOLFSSL* ssl)
{
#ifndef WOLFSSL_TLS13_TICKET_NO_PSK_MODES
if (!ssl->options.pskKeModesRecvd) {
WOLFSSL_MSG("No psk_key_exchange_modes in ClientHello");
return MISSING_HANDSHAKE_DATA;
}
if ((ssl->options.pskKeModes & (1 << PSK_KE)) != 0
#ifdef HAVE_SUPPORTED_CURVES
&& !ssl->options.onlyPskDheKe
#endif
) {
return 0;
}
if ((ssl->options.pskKeModes & (1 << PSK_DHE_KE)) != 0 &&
!ssl->options.noPskDheKe) {
return 0;
}
WOLFSSL_MSG("No usable psk_key_exchange_modes advertised by client");
return PSK_KEY_ERROR;
#else
(void)ssl;
return 0;
#endif
}
/* Send New Session Ticket handshake message.
* Message contains the information required to perform resumption.
*
@ -14620,6 +14667,12 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl)
return 0;
}
if (CheckTls13TicketPskModes(ssl) != 0) {
WOLFSSL_MSG("Client advertised no usable PSK key exchange mode; "
"skipping ticket");
return 0;
}
#ifdef WOLFSSL_DTLS13
if (ssl->options.dtls)
idx = Dtls13GetRlHeaderLength(ssl, 1) + DTLS_HANDSHAKE_HEADER_SZ;
@ -18064,17 +18117,27 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
* returns BAD_FUNC_ARG when ssl is NULL, or not using TLS v1.3,
* SIDE_ERROR when not a server,
* NOT_READY_ERROR when handshake not complete,
* MISSING_HANDSHAKE_DATA when the ClientHello had no
* psk_key_exchange_modes extension,
* PSK_KEY_ERROR when no advertised PSK key exchange mode is usable,
* WOLFSSL_FATAL_ERROR when creating or sending message fails, and
* WOLFSSL_SUCCESS on success.
*/
int wolfSSL_send_SessionTicket(WOLFSSL* ssl)
{
int ret;
if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version))
return BAD_FUNC_ARG;
if (ssl->options.side == WOLFSSL_CLIENT_END)
return SIDE_ERROR;
if (ssl->options.handShakeState != HANDSHAKE_DONE)
return NOT_READY_ERROR;
ret = CheckTls13TicketPskModes(ssl);
if (ret != 0) {
WOLFSSL_ERROR_VERBOSE(ret);
return ret;
}
if ((ssl->error = SendTls13NewSessionTicket(ssl)) != 0) {
WOLFSSL_ERROR(ssl->error);

View File

@ -11612,3 +11612,153 @@ int test_tls13_cryptocb_async(void)
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
!defined(WOLFSSL_TLS13_TICKET_NO_PSK_MODES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
/* Drive a TLS 1.3 handshake up to, but not including, the server's final
* wolfSSL_accept() - the call that runs the NewSessionTicket loop. */
static int test_tls13_handshake_to_ticket(WOLFSSL* ssl_c, WOLFSSL* ssl_s)
{
EXPECT_DECLS;
/* ClientHello. */
ExpectIntEQ(wolfSSL_connect(ssl_c), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
/* Server flight. */
ExpectIntEQ(wolfSSL_accept(ssl_s), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
/* Client Finished. */
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
return EXPECT_RESULT();
}
#endif
/* RFC 9846 Section 4.3.9 and Section 4.7.1: a NewSessionTicket creates a
* resumption PSK, so the server may only send one when the ClientHello
* advertised a psk_key_exchange_modes mode it can be used with. */
int test_tls13_ticket_psk_modes(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
!defined(WOLFSSL_TLS13_TICKET_NO_PSK_MODES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
/* A ClientHello without psk_key_exchange_modes gets no ticket. */
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_tls13_handshake_to_ticket(ssl_c, ssl_s), TEST_SUCCESS);
ExpectIntEQ(ssl_s->options.pskKeModesRecvd, 1);
if (EXPECT_SUCCESS()) {
ssl_s->options.pskKeModesRecvd = 0;
}
ExpectIntEQ(test_ctx.c_len, 0);
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(test_ctx.c_len, 0);
wolfSSL_free(ssl_c);
ssl_c = NULL;
wolfSSL_free(ssl_s);
ssl_s = NULL;
wolfSSL_CTX_free(ctx_c);
ctx_c = NULL;
wolfSSL_CTX_free(ctx_s);
ctx_s = NULL;
/* Control: the wolfSSL client advertises the modes, so a ticket is sent. */
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_tls13_handshake_to_ticket(ssl_c, ssl_s), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_len, 0);
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntGT(test_ctx.c_len, 0);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* wolfSSL_send_SessionTicket() applies the same RFC 9846 precondition as the
* automatic ticket path, and reports why it will not send. */
int test_tls13_send_session_ticket_psk_modes(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \
!defined(WOLFSSL_TLS13_TICKET_NO_PSK_MODES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
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);
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS);
if (EXPECT_SUCCESS()) {
/* No psk_key_exchange_modes extension in the ClientHello. */
ssl_s->options.pskKeModesRecvd = 0;
}
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s),
WC_NO_ERR_TRACE(MISSING_HANDSHAKE_DATA));
if (EXPECT_SUCCESS()) {
/* Extension present but carrying only unrecognized modes. */
ssl_s->options.pskKeModesRecvd = 1;
ssl_s->options.pskKeModes = 0;
}
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s),
WC_NO_ERR_TRACE(PSK_KEY_ERROR));
if (EXPECT_SUCCESS()) {
/* psk_dhe_ke only, but the server refuses (EC)DHE with PSK. */
ssl_s->options.pskKeModes = 1 << PSK_DHE_KE;
ssl_s->options.noPskDheKe = 1;
}
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s),
WC_NO_ERR_TRACE(PSK_KEY_ERROR));
if (EXPECT_SUCCESS()) {
ssl_s->options.noPskDheKe = 0;
}
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS);
#ifdef HAVE_SUPPORTED_CURVES
if (EXPECT_SUCCESS()) {
/* psk_ke only, but the server requires (EC)DHE with PSK. */
ssl_s->options.pskKeModes = 1 << PSK_KE;
ssl_s->options.onlyPskDheKe = 1;
}
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s),
WC_NO_ERR_TRACE(PSK_KEY_ERROR));
if (EXPECT_SUCCESS()) {
ssl_s->options.onlyPskDheKe = 0;
}
ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS);
#endif
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

View File

@ -133,6 +133,8 @@ int test_tls13_pha_status_request(void);
int test_tls13_x25519_keyshare_masks_reserved_bit(void);
int test_tls13_is_init_finished_want_write(void);
int test_tls13_cryptocb_async(void);
int test_tls13_ticket_psk_modes(void);
int test_tls13_send_session_ticket_psk_modes(void);
#define TEST_TLS13_DECLS \
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@ -243,6 +245,8 @@ int test_tls13_cryptocb_async(void);
TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \
TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit), \
TEST_DECL_GROUP("tls13", test_tls13_is_init_finished_want_write), \
TEST_DECL_GROUP("tls13", test_tls13_cryptocb_async)
TEST_DECL_GROUP("tls13", test_tls13_cryptocb_async), \
TEST_DECL_GROUP("tls13", test_tls13_ticket_psk_modes), \
TEST_DECL_GROUP("tls13", test_tls13_send_session_ticket_psk_modes)
#endif /* WOLFCRYPT_TEST_TLS13_H */

View File

@ -5452,6 +5452,9 @@ struct Options {
#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_TLS13)
unsigned int maxTicketTls13; /* maximum number of tickets to send */
unsigned int ticketsSent; /* keep track of the total sent */
#ifndef NO_WOLFSSL_SERVER
byte pskKeModes; /* modes client advertised in CH */
#endif
#endif
/* on/off or small bit flags, optimize layout */
@ -5534,6 +5537,9 @@ struct Options {
#ifdef WOLFSSL_EARLY_DATA
word16 ticketPredatesCtx:1; /* PSK ticket minted before ctx */
#endif
#ifndef NO_WOLFSSL_SERVER
word16 pskKeModesRecvd:1; /* CH had psk_key_exchange_modes */
#endif
#endif
#endif
#ifdef WOLFSSL_DTLS