diff --git a/src/internal.c b/src/internal.c index f2a977e26f..e0462a674d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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; diff --git a/src/tls13.c b/src/tls13.c index a20b6ec9e2..70dc99e568 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -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; diff --git a/tests/api/test_ssl_ext.c b/tests/api/test_ssl_ext.c index 4e24edd64b..8dacf484f4 100644 --- a/tests/api/test_ssl_ext.c +++ b/tests/api/test_ssl_ext.c @@ -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; 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 a691c90fc8..de7ff8bb7c 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -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; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 786dc52c34..cf26e8ca1c 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -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);