Merge pull request #10822 from mattia-moffa/20260629-ticket-hint-window-check

Fix session ticket key rotation when hint exceeds key lifetime
This commit is contained in:
David Garske
2026-07-09 10:27:57 -07:00
committed by GitHub
6 changed files with 161 additions and 9 deletions
+26 -6
View File
@@ -40769,6 +40769,19 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
}
#endif
/* Returns 1 when the default ticket encryption callback is in use with a
* hint of at least half the key lifetime. */
int DefTicketHintTooLarge(WOLFSSL* ssl)
{
#if !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_WOLFSSL_SERVER)
return ssl->ctx->ticketEncCb == DefTicketEncCb &&
2 * ssl->ctx->ticketHint >= WOLFSSL_TICKET_KEY_LIFETIME;
#else
(void)ssl;
return 0;
#endif
}
/* create a new session ticket, 0 on success
* Do any kind of setup in SetupTicket */
int CreateTicket(WOLFSSL* ssl)
@@ -41785,12 +41798,19 @@ cleanup:
/* This will be set when SendHandshakeMsg returns WANT_WRITE. Create
* a new ticket only once. */
!ssl->options.buildingMsg) {
ret = SetupTicket(ssl);
if (ret != 0)
return ret;
ret = CreateTicket(ssl);
if (ret != 0)
return ret;
if (DefTicketHintTooLarge(ssl)) {
WOLFSSL_MSG("Ticket hint exceeds half the ticket key lifetime; "
"skipping ticket");
ssl->session->ticketLen = 0;
}
else {
ret = SetupTicket(ssl);
if (ret != 0)
return ret;
ret = CreateTicket(ssl);
if (ret != 0)
return ret;
}
}
length += ssl->session->ticketLen;
+6
View File
@@ -12969,6 +12969,12 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl)
WOLFSSL_START(WC_FUNC_NEW_SESSION_TICKET_SEND);
WOLFSSL_ENTER("SendTls13NewSessionTicket");
if (DefTicketHintTooLarge(ssl)) {
WOLFSSL_MSG("Ticket hint exceeds half the ticket key lifetime; "
"skipping ticket");
return 0;
}
#ifdef WOLFSSL_DTLS13
if (ssl->options.dtls)
idx = Dtls13GetRlHeaderLength(ssl, 1) + DTLS_HANDSHAKE_HEADER_SZ;
+94
View File
@@ -225,6 +225,100 @@ int test_wolfSSL_CTX_set_TicketHint_ext(void)
return EXPECT_RESULT();
}
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) \
&& defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) \
&& !defined(NO_WOLFSSL_SERVER)
/* Trivial custom ticket encryption callback: it has no key-lifetime constraint,
* so it must be able to issue a ticket for any hint. */
static int test_TicketHint_custom_encCb(WOLFSSL* ssl,
byte key_name[WOLFSSL_TICKET_NAME_SZ], byte iv[WOLFSSL_TICKET_IV_SZ],
byte mac[WOLFSSL_TICKET_MAC_SZ], int enc, byte* ticket, int inLen,
int* outLen, void* userCtx)
{
int i;
(void)ssl;
(void)userCtx;
if (enc) {
XMEMSET(key_name, 0x2A, WOLFSSL_TICKET_NAME_SZ);
XMEMSET(iv, 0x2A, WOLFSSL_TICKET_IV_SZ);
XMEMSET(mac, 0x2A, WOLFSSL_TICKET_MAC_SZ);
}
for (i = 0; i < inLen; i++)
ticket[i] = (byte)(ticket[i] ^ 0xA5);
*outLen = inLen;
return WOLFSSL_TICKET_RET_OK;
}
/* Run a handshake with the given hint (optionally with the custom callback),
* process any (post-handshake) NewSessionTicket, and return the ticket length
* the client received: -1 if the handshake failed, 0 if it completed without a
* ticket, >0 if a ticket was issued. */
static int test_TicketHint_client_ticket_len(method_provider client_meth,
method_provider server_meth, int hint, int customCb)
{
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
char buf[64];
int ret;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
if (test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
client_meth, server_meth) != 0) {
ret = -1;
goto done;
}
wolfSSL_UseSessionTicket(ssl_c);
if (customCb)
wolfSSL_CTX_set_TicketEncCb(ctx_s, test_TicketHint_custom_encCb);
wolfSSL_CTX_set_TicketHint(ctx_s, hint);
if (test_memio_do_handshake(ssl_c, ssl_s, 10, NULL) != 0) {
ret = -1;
goto done;
}
/* Drive the client to process a post-handshake NewSessionTicket, if any. */
(void)wolfSSL_read(ssl_c, buf, sizeof(buf));
ret = ssl_c->session->ticketLen;
done:
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
return ret;
}
#endif
/* The default ticket encryption callback must refuse to issue a ticket when the
* hint exceeds half the key lifetime, but a custom callback has no such limit. */
int test_wolfSSL_CTX_set_TicketHint_default_cb_limit(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) \
&& defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) \
&& !defined(NO_WOLFSSL_SERVER)
/* Default callback, hint below the limit: handshake succeeds, ticket issued. */
ExpectIntGT(test_TicketHint_client_ticket_len(wolfTLSv1_3_client_method,
wolfTLSv1_3_server_method, WOLFSSL_TICKET_KEY_LIFETIME / 2 - 1, 0), 0);
/* Default callback, hint at the limit: handshake succeeds, no ticket. */
ExpectIntEQ(test_TicketHint_client_ticket_len(wolfTLSv1_3_client_method,
wolfTLSv1_3_server_method, WOLFSSL_TICKET_KEY_LIFETIME / 2, 0), 0);
/* Custom callback: the same oversized hint still issues a ticket. */
ExpectIntGT(test_TicketHint_client_ticket_len(wolfTLSv1_3_client_method,
wolfTLSv1_3_server_method, WOLFSSL_TICKET_KEY_LIFETIME / 2, 1), 0);
#ifndef WOLFSSL_NO_TLS12
/* Same behavior on the TLS 1.2 SendTicket path. */
ExpectIntGT(test_TicketHint_client_ticket_len(wolfTLSv1_2_client_method,
wolfTLSv1_2_server_method, WOLFSSL_TICKET_KEY_LIFETIME / 2 - 1, 0), 0);
ExpectIntEQ(test_TicketHint_client_ticket_len(wolfTLSv1_2_client_method,
wolfTLSv1_2_server_method, WOLFSSL_TICKET_KEY_LIFETIME / 2, 0), 0);
ExpectIntGT(test_TicketHint_client_ticket_len(wolfTLSv1_2_client_method,
wolfTLSv1_2_server_method, WOLFSSL_TICKET_KEY_LIFETIME / 2, 1), 0);
#endif
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_tlsext_max_fragment_length_ext(void)
{
EXPECT_DECLS;
+3
View File
@@ -28,6 +28,7 @@ int test_wolfSSL_CTX_num_tickets_ext(void);
int test_wolfSSL_set1_groups_ext(void);
int test_wolfSSL_set1_groups_list_ext(void);
int test_wolfSSL_CTX_set_TicketHint_ext(void);
int test_wolfSSL_CTX_set_TicketHint_default_cb_limit(void);
int test_wolfSSL_tlsext_max_fragment_length_ext(void);
int test_wolfSSL_DisableExtendedMasterSecret_ext(void);
int test_wolfSSL_set_tlsext_host_name_ext(void);
@@ -57,6 +58,8 @@ int test_wolfSSL_CTX_set_alpn_protos_inval_ext(void);
TEST_DECL_GROUP("ssl_ext", test_wolfSSL_set1_groups_ext), \
TEST_DECL_GROUP("ssl_ext", test_wolfSSL_set1_groups_list_ext), \
TEST_DECL_GROUP("ssl_ext", test_wolfSSL_CTX_set_TicketHint_ext), \
TEST_DECL_GROUP("ssl_ext", \
test_wolfSSL_CTX_set_TicketHint_default_cb_limit), \
TEST_DECL_GROUP("ssl_ext", \
test_wolfSSL_tlsext_max_fragment_length_ext), \
TEST_DECL_GROUP("ssl_ext", \
+31 -3
View File
@@ -6529,12 +6529,38 @@ int test_tls13_short_session_ticket(void)
}
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET)
/* Custom ticket encryption callback with no key-lifetime limit, so the server
* issues a ticket for any hint (the default callback refuses a hint of at least
* half the key lifetime). */
static int test_tls13_max_lifetime_ticketEncCb(WOLFSSL* ssl,
byte key_name[WOLFSSL_TICKET_NAME_SZ], byte iv[WOLFSSL_TICKET_IV_SZ],
byte mac[WOLFSSL_TICKET_MAC_SZ], int enc, byte* ticket, int inLen,
int* outLen, void* userCtx)
{
int i;
(void)ssl;
(void)userCtx;
if (enc) {
XMEMSET(key_name, 0x2A, WOLFSSL_TICKET_NAME_SZ);
XMEMSET(iv, 0x2A, WOLFSSL_TICKET_IV_SZ);
XMEMSET(mac, 0x2A, WOLFSSL_TICKET_MAC_SZ);
}
for (i = 0; i < inLen; i++)
ticket[i] = (byte)(ticket[i] ^ 0xA5);
*outLen = inLen;
return WOLFSSL_TICKET_RET_OK;
}
#endif
/* RFC 8446 Section 4.6.1: a NewSessionTicket lifetime greater than
* MAX_LIFETIME (604800 seconds, 7 days) must be rejected. The public
* wolfSSL_CTX_set_TicketHint setter clamps the value, so write the
* out-of-range hint directly into the server CTX to force the server to
* encode an over-limit lifetime onto the wire and confirm the client's
* DoTls13NewSessionTicket bound check fires. */
* out-of-range hint directly into the server CTX. A custom encryption callback
* is installed so the default callback's key-lifetime limit does not block
* issuance, forcing the server to encode the over-limit lifetime onto the wire
* and confirming the client's DoTls13NewSessionTicket bound check fires. */
int test_tls13_new_session_ticket_max_lifetime(void)
{
EXPECT_DECLS;
@@ -6549,6 +6575,8 @@ int test_tls13_new_session_ticket_max_lifetime(void)
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
wolfSSL_CTX_set_TicketEncCb(ctx_s, test_tls13_max_lifetime_ticketEncCb);
/* Bypass the public-API clamp at 604800. */
if (EXPECT_SUCCESS()) {
ctx_s->ticketHint = MAX_LIFETIME + 1;
+1
View File
@@ -2251,6 +2251,7 @@ WOLFSSL_LOCAL void CopyDecodedName(WOLFSSL_X509_NAME* name, DecodedCert* dCert,
#endif
WOLFSSL_LOCAL int SetupTicket(WOLFSSL* ssl);
WOLFSSL_LOCAL int CreateTicket(WOLFSSL* ssl);
WOLFSSL_LOCAL int DefTicketHintTooLarge(WOLFSSL* ssl);
WOLFSSL_LOCAL int HashRaw(WOLFSSL* ssl, const byte* data, int sz);
WOLFSSL_LOCAL int HashOutput(WOLFSSL* ssl, const byte* output, int sz,
int ivSz);