From bd7db08967047e6f1b7ef5ed952b3071a1551c9d Mon Sep 17 00:00:00 2001 From: Mattia Moffa Date: Wed, 8 Jul 2026 06:17:36 +0200 Subject: [PATCH] Move ticket hint check to default callback --- src/internal.c | 7 ++++ src/ssl_api_ext.c | 11 ------ tests/api/test_ssl_ext.c | 82 ++++++++++++++++++++++++++++++++++------ tests/api/test_ssl_ext.h | 3 ++ tests/api/test_tls13.c | 34 +++++++++++++++-- wolfssl/internal.h | 4 +- 6 files changed, 114 insertions(+), 27 deletions(-) diff --git a/src/internal.c b/src/internal.c index fed9d370de..6b031c5e40 100644 --- a/src/internal.c +++ b/src/internal.c @@ -41744,6 +41744,13 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], return BUFFER_E; } + /* The two-key rotation can only honor a hint below half the key lifetime. + * Refuse to issue a ticket whose advertised lifetime it cannot cover. */ + if (enc && 2 * ctx->ticketHint >= WOLFSSL_TICKET_KEY_LIFETIME) { + WOLFSSL_MSG("Ticket hint exceeds half the ticket key lifetime"); + return WOLFSSL_TICKET_RET_FATAL; + } + /* Check we have setup the RNG, name and primary key. */ if (keyCtx->expirary[0] == 0) { #ifndef SINGLE_THREADED diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 367a0134f9..2e4a8952f1 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -1055,8 +1055,6 @@ int wolfSSL_CTX_set_TicketEncCb(WOLFSSL_CTX* ctx, SessionTicketEncCb cb) * * @param [in] ctx SSL/TLS context object. * @param [in] hint Lifetime hint in seconds. No more than 604800 (7 days). - * With the default ticket encryption callback, must also be - * less than WOLFSSL_TICKET_KEY_LIFETIME / 2. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ctx is NULL or hint is out of range. */ @@ -1070,15 +1068,6 @@ int wolfSSL_CTX_set_TicketHint(WOLFSSL_CTX* ctx, int hint) if (hint < 0 || hint > 604800) return BAD_FUNC_ARG; -#ifdef WOLFSSL_TICKET_KEY_LIFETIME - /* The default ticket encryption callback rotates two keys, each living - * WOLFSSL_TICKET_KEY_LIFETIME seconds. Its staggered rotation can only - * honor a hint below half that; a larger hint would leave no key available - * for encryption. */ - if (hint >= WOLFSSL_TICKET_KEY_LIFETIME / 2) - return BAD_FUNC_ARG; -#endif - ctx->ticketHint = hint; return WOLFSSL_SUCCESS; diff --git a/tests/api/test_ssl_ext.c b/tests/api/test_ssl_ext.c index 3dfe4f9f0b..2fad00986d 100644 --- a/tests/api/test_ssl_ext.c +++ b/tests/api/test_ssl_ext.c @@ -188,24 +188,84 @@ int test_wolfSSL_CTX_set_TicketHint_ext(void) ExpectIntEQ(wolfSSL_CTX_set_TicketHint(ctx, 604801), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectIntEQ(wolfSSL_CTX_set_TicketHint(ctx, 0), WOLFSSL_SUCCESS); -#ifdef WOLFSSL_TICKET_KEY_LIFETIME - /* The default ticket encryption callback can only honor a hint below half - * the ticket key lifetime; larger values are rejected. */ - ExpectIntEQ(wolfSSL_CTX_set_TicketHint(ctx, WOLFSSL_TICKET_KEY_LIFETIME / 2), - WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wolfSSL_CTX_set_TicketHint(ctx, - WOLFSSL_TICKET_KEY_LIFETIME / 2 - 1), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_set_TicketHint(ctx, 604800), - WC_NO_ERR_TRACE(BAD_FUNC_ARG)); -#else ExpectIntEQ(wolfSSL_CTX_set_TicketHint(ctx, 604800), WOLFSSL_SUCCESS); -#endif wolfSSL_CTX_free(ctx); #endif 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; +} + +/* Drive one TLS 1.3 handshake, returning the handshake result. */ +static int test_TicketHint_handshake(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; + int ret; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + if (test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method) != 0) { + ret = -1; + goto done; + } + if (customCb) + wolfSSL_CTX_set_TicketEncCb(ctx_s, test_TicketHint_custom_encCb); + wolfSSL_CTX_set_TicketHint(ctx_s, hint); + + ret = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); +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: a hint below the limit is honored. */ + ExpectIntEQ(test_TicketHint_handshake(WOLFSSL_TICKET_KEY_LIFETIME / 2 - 1, 0), + 0); + /* Default callback: a hint at/above the limit fails the handshake. */ + ExpectIntNE(test_TicketHint_handshake(WOLFSSL_TICKET_KEY_LIFETIME / 2, 0), 0); + /* Custom callback: the same oversized hint is fine. */ + ExpectIntEQ(test_TicketHint_handshake(WOLFSSL_TICKET_KEY_LIFETIME / 2, 1), 0); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_tlsext_max_fragment_length_ext(void) { EXPECT_DECLS; diff --git a/tests/api/test_ssl_ext.h b/tests/api/test_ssl_ext.h index ffb42f5fc8..db0de04c2e 100644 --- a/tests/api/test_ssl_ext.h +++ b/tests/api/test_ssl_ext.h @@ -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", \ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index e4d5c3821d..f3bf53e459 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -5729,12 +5729,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; @@ -5749,6 +5775,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; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index bf5441b6df..3cd37c739b 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2047,8 +2047,8 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); /* Default lifetime is 1 hour from issue of first ticket with key. */ #define WOLFSSL_TICKET_KEY_LIFETIME (60 * 60) #endif - #if WOLFSSL_TICKET_KEY_LIFETIME <= 2 * SESSION_TICKET_HINT_DEFAULT - #error "Ticket Key lifetime must be more than twice the ticket life hint." + #if WOLFSSL_TICKET_KEY_LIFETIME <= SESSION_TICKET_HINT_DEFAULT + #error "Ticket Key lifetime must be longer than ticket life hint." #endif #endif