From 1ab3f089dd2e1ae34cc47e69de2b2b15028170c4 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Wed, 4 Nov 2026 14:51:39 +0000 Subject: [PATCH 1/2] Size session ticket buffers for tickets without a peer cert With OPENSSL_ALL and KEEP_PEER_CERT the session ticket can carry the peer certificate, and every buffer derived from the ticket size was sized for the largest one (MAX_TICKET_PEER_CERT_SZ). Each WOLFSSL_SESSION had a ~2.3 KB static ticket buffer, MAX_PSK_ID_LEN followed the same size so Arrays carried two ~2.3 KB identity buffers and WOLFSSL_CTX one, and the default static session cache was about 86 KB. - WOLFSSL_SESSION.staticTicket is SESSION_TICKET_STATIC_LEN, the length of a ticket without a certificate. Longer tickets use the existing dynamic ticket buffer. Builds that do not put the cert in the ticket keep their sizes. - CreateTicket() picks the peer certificate first and sizes the ticket from it: the space given to the encrypt callback is WOLFSSL_TICKET_ENC_SZ less the certificate room the ticket does not use. A bad encrypt length now fails without leaving ticketLen past the end of the buffer, and ticketLen is cleared until the new ticket is made. - MAX_PSK_ID_LEN defaults to SESSION_TICKET_STATIC_LEN, as in builds without OPENSSL_ALL. MAX_PSK_ID_WIRE_LEN lets a server accept its own longer tickets as TLS 1.3 PSK identities; longer identities are not copied into Arrays or offered to the PSK callbacks. - Checks for "fits in the static buffer" use sizeof(staticTicket). GetSessionFromCache() records the size it allocated, wolfSSL_set_SessionTicket() reallocates based on ticketLenAlloc, and a restored persistent cache drops a ticket that was not in the static buffer instead of clamping it. Behaviour change: with OPENSSL_ALL and certificates in tickets, MAX_PSK_ID_LEN used to be the full ticket size (about 2.3 KB) and is now the size of a ticket without a certificate (a few hundred bytes), the limit builds without OPENSSL_ALL already have. A TLS 1.3 server no longer offers a longer external PSK identity to its PSK callback (a debug message says so), a longer TLS 1.2 PSK identity fails the handshake, and identity hints are cut to the new length. Define MAX_PSK_ID_LEN to keep a larger limit. On a Cortex-M33 with OPENSSL_ALL: Arrays 4976 -> 880 bytes, WOLFSSL_SESSION 2588 -> 540, WOLFSSL_CTX 2888 -> 840, static session cache 85536 -> 17952. In a TLS 1.3 client handshake the heap peak drops by 8 KB. Tests: resumption from a ticket carrying the client certificate over TLS 1.3 and TLS 1.2, including copies of the session, and an encrypt callback that reports too long an output. --- src/internal.c | 168 ++++++++++++++----------- src/ssl_api_ext.c | 5 +- src/ssl_sess.c | 18 +-- src/tls.c | 2 +- src/tls13.c | 21 +++- tests/api/test_session.c | 14 ++- tests/api/test_ssl_ext.c | 2 +- tests/api/test_tls13.c | 234 +++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 4 + tests/api/test_tls_msgtype.c | 10 +- wolfssl/internal.h | 28 ++++- 11 files changed, 404 insertions(+), 102 deletions(-) diff --git a/src/internal.c b/src/internal.c index 83eb4e289b4..2735bf01743 100644 --- a/src/internal.c +++ b/src/internal.c @@ -45204,37 +45204,114 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) InternalTicket* it; ExternalTicket* et; int encLen; + int encMax; int ret; int error; word32 itHash = 0; byte zeros[WOLFSSL_TICKET_MAC_SZ]; /* biggest cmp size */ int internalTicketSz; + word32 ticketSz; byte* mac; #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) + const byte* peerCertDer = NULL; word16 peerCertSz = 0; #endif - WOLFSSL_ASSERT_GE(sizeof(ssl->session->staticTicket), WOLFSSL_TICKET_ENC_SZ); WOLFSSL_ASSERT_SIZEOF_GE(ssl->session->staticTicket, *et); - if (ssl->session->ticket != ssl->session->staticTicket) { - /* Always use the static ticket buffer */ - XFREE(ssl->session->ticket, NULL, DYNAMIC_TYPE_SESSION_TICK); - ssl->session->ticket = ssl->session->staticTicket; - ssl->session->ticketLenAlloc = 0; +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + /* Store peer certificate in ticket for session resumption. + * Try ssl->peerCert first, then ssl->session->chain as fallback. + * Skip for DTLS to keep ticket size small for MTU constraints. */ + if (!ssl->options.dtls) { + const byte* certDer = NULL; + word32 certDerSz = 0; + + if (ssl->peerCert.derCert != NULL && + ssl->peerCert.derCert->length > 0) { + /* Use current peer certificate */ + certDer = ssl->peerCert.derCert->buffer; + certDerSz = ssl->peerCert.derCert->length; + } +#ifdef SESSION_CERTS + else if (ssl->session->chain.count > 0) { + /* Use peer certificate from session chain */ + certDer = ssl->session->chain.certs[0].buffer; + certDerSz = ssl->session->chain.certs[0].length; + } +#endif + + if (certDer != NULL && certDerSz > 0 && + certDerSz <= MAX_TICKET_PEER_CERT_SZ +#ifdef HAVE_MAX_FRAGMENT + /* We don't support fragmentation in + * SendTls13NewSessionTicket yet. */ + && (!IsAtLeastTLSv1_3(ssl->version) || + ssl->max_fragment == MAX_RECORD_SIZE) +#endif + ) { + peerCertDer = certDer; + peerCertSz = (word16)certDerSz; + } + else if (certDerSz > MAX_TICKET_PEER_CERT_SZ) { + WOLFSSL_MSG("Peer cert too large for ticket, skipping"); + } } +#endif - et = (ExternalTicket*)ssl->session->ticket; - it = (InternalTicket*)et->enc_ticket; + internalTicketSz = (int)WOLFSSL_INTERNAL_TICKET_BASE_SZ; + /* Space for the encrypt callback: WOLFSSL_TICKET_ENC_SZ less the + * certificate space this ticket does not use. */ + encMax = (int)WOLFSSL_TICKET_ENC_BASE_SZ; +#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) + internalTicketSz += peerCertSz; + encMax += peerCertSz; +#endif +#ifdef WOLFSSL_TICKET_ENC_CBC_HMAC + internalTicketSz = (internalTicketSz + 15) & (~0xf); +#endif + ticketSz = WOLFSSL_TICKET_FIXED_SZ + (word32)encMax; + + /* The static buffer holds a ticket without a peer certificate. On an + * async re-entry the size is the same and the buffer is kept. */ + if (ticketSz <= sizeof(ssl->session->staticTicket)) { + if (ssl->session->ticket != ssl->session->staticTicket) { + XFREE(ssl->session->ticket, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); + ssl->session->ticket = ssl->session->staticTicket; + ssl->session->ticketLenAlloc = 0; + } + } + else if (ssl->session->ticketLenAlloc < ticketSz) { + byte* ticket = (byte*)XMALLOC(ticketSz, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); + if (ticket == NULL) { + WOLFSSL_ERROR_VERBOSE(MEMORY_E); + return MEMORY_E; + } + if (ssl->session->ticketLenAlloc > 0) { + XFREE(ssl->session->ticket, ssl->session->heap, + DYNAMIC_TYPE_SESSION_TICK); + } + ssl->session->ticket = ticket; + ssl->session->ticketLenAlloc = (word16)ticketSz; + } + /* Set once the new ticket is made */ + ssl->session->ticketLen = 0; #ifdef WOLFSSL_ASYNC_CRYPT if (ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E)) #endif { - XMEMSET(ssl->session->ticket, 0, SESSION_TICKET_LEN); + XMEMSET(ssl->session->ticket, 0, ticketSz); } + et = (ExternalTicket*)ssl->session->ticket; + it = (InternalTicket*)et->enc_ticket; + /* build internal */ it->pv.major = ssl->version.major; it->pv.minor = ssl->version.minor; @@ -45313,51 +45390,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) - /* Store peer certificate in ticket for session resumption. - * Try ssl->peerCert first, then ssl->session->chain as fallback. - * Skip for DTLS to keep ticket size small for MTU constraints. */ - if (ssl->options.dtls) { - c16toa(0, it->peerCertLen); - peerCertSz = 0; - } - else { - const byte* certDer = NULL; - word32 certDerSz = 0; - - if (ssl->peerCert.derCert != NULL && - ssl->peerCert.derCert->length > 0) { - /* Use current peer certificate */ - certDer = ssl->peerCert.derCert->buffer; - certDerSz = ssl->peerCert.derCert->length; - } -#ifdef SESSION_CERTS - else if (ssl->session->chain.count > 0) { - /* Use peer certificate from session chain */ - certDer = ssl->session->chain.certs[0].buffer; - certDerSz = ssl->session->chain.certs[0].length; - } -#endif - - if (certDer != NULL && certDerSz > 0 && - certDerSz <= MAX_TICKET_PEER_CERT_SZ -#ifdef HAVE_MAX_FRAGMENT - /* We don't support fragmentation in - * SendTls13NewSessionTicket yet. */ - && (!IsAtLeastTLSv1_3(ssl->version) || - ssl->max_fragment == MAX_RECORD_SIZE) -#endif - ) { - c16toa((word16)certDerSz, it->peerCertLen); - XMEMCPY(it->peerCert, certDer, certDerSz); - peerCertSz = (word16)certDerSz; - } - else { - if (certDerSz > MAX_TICKET_PEER_CERT_SZ) - WOLFSSL_MSG("Peer cert too large for ticket, skipping"); - c16toa(0, it->peerCertLen); - peerCertSz = 0; - } - } + c16toa(peerCertSz, it->peerCertLen); + if (peerCertSz > 0) + XMEMCPY(it->peerCert, peerCertDer, peerCertSz); #endif #ifdef WOLFSSL_TICKET_HAVE_ID @@ -45372,19 +45407,11 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif - internalTicketSz = (int)WOLFSSL_INTERNAL_TICKET_BASE_SZ; -#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ - !defined(NO_CERT_IN_TICKET) - internalTicketSz += peerCertSz; -#endif -#ifdef WOLFSSL_TICKET_ENC_CBC_HMAC - internalTicketSz = (internalTicketSz + 15) & (~0xf); -#endif /* MAC is placed after the encrypted data */ - mac = et->enc_ticket + WOLFSSL_TICKET_ENC_SZ; + mac = et->enc_ticket + encMax; /* encrypt */ - encLen = WOLFSSL_TICKET_ENC_SZ; /* max size user can use */ + encLen = encMax; /* max size user can use */ if (ssl->ctx->ticketEncCb == NULL #if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || defined(WOLFSSL_WPAS_SMALL) || @@ -45417,10 +45444,10 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif goto error; } - if (encLen < internalTicketSz || - encLen > (int)WOLFSSL_TICKET_ENC_SZ) { + if (encLen < internalTicketSz || encLen > encMax) { WOLFSSL_MSG("Bad user ticket encrypt size"); ret = BAD_TICKET_KEY_CB_SZ; + goto error; } /* sanity checks on encrypt callback */ @@ -45459,7 +45486,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) /* set size */ c16toa((word16)encLen, et->enc_len); - if (encLen < (int)WOLFSSL_TICKET_ENC_SZ) { + if (encLen < encMax) { /* move mac up since whole enc buffer not used */ XMEMMOVE(et->enc_ticket + encLen, mac, WOLFSSL_TICKET_MAC_SZ); @@ -45471,12 +45498,11 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) error: #ifdef WOLFSSL_CHECK_MEM_ZERO /* Ticket has sensitive data in it now. */ - wc_MemZero_Add("Create Ticket internal", it, - WOLFSSL_INTERNAL_TICKET_MAX_SZ); + wc_MemZero_Add("Create Ticket internal", it, (word32)encMax); #endif - ForceZero(it, WOLFSSL_INTERNAL_TICKET_MAX_SZ); + ForceZero(it, (word32)encMax); #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(it, WOLFSSL_INTERNAL_TICKET_MAX_SZ); + wc_MemZero_Check(it, (word32)encMax); #endif WOLFSSL_ERROR_VERBOSE(ret); return ret; diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 8bbb656b512..412babadbea 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -1625,7 +1625,7 @@ int wolfSSL_set_SessionTicket(WOLFSSL* ssl, const byte* buf, if ((ret == WOLFSSL_SUCCESS) && (bufSz > 0)) { /* Ticket will fit into static ticket */ - if (bufSz <= SESSION_TICKET_LEN) { + if (bufSz <= sizeof(ssl->session->staticTicket)) { if (ssl->session->ticketLenAlloc > 0) { XFREE(ssl->session->ticket, ssl->session->heap, DYNAMIC_TYPE_SESSION_TICK); @@ -1635,7 +1635,7 @@ int wolfSSL_set_SessionTicket(WOLFSSL* ssl, const byte* buf, } else { /* Ticket requires dynamic ticket storage */ /* is dyn buffer big enough */ - if (ssl->session->ticketLen < bufSz) { + if (ssl->session->ticketLenAlloc < bufSz) { if (ssl->session->ticketLenAlloc > 0) { XFREE(ssl->session->ticket, ssl->session->heap, DYNAMIC_TYPE_SESSION_TICK); @@ -1645,6 +1645,7 @@ int wolfSSL_set_SessionTicket(WOLFSSL* ssl, const byte* buf, if (ssl->session->ticket == NULL) { ssl->session->ticket = ssl->session->staticTicket; ssl->session->ticketLenAlloc = 0; + ssl->session->ticketLen = 0; ret = MEMORY_ERROR; } else { diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 3a932c37cf4..5c8623b5d72 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -413,12 +413,13 @@ static int SessionSanityPointerSet(int row) s->type = WOLFSSL_SESSION_TYPE_CACHE; s->cacheRow = row; - /* clamp ticketLen */ + /* drop a ticket that was not in the static buffer: the row copy + * only has the static buffer's (stale) bytes for it */ #ifdef HAVE_SESSION_TICKET s->ticket = s->staticTicket; s->ticketLenAlloc = 0; - if (s->ticketLen > SESSION_TICKET_LEN) { - s->ticketLen = SESSION_TICKET_LEN; + if (s->ticketLen > sizeof(s->staticTicket)) { + s->ticketLen = 0; } #endif /* HAVE_SESSION_TICKET */ #if defined(WOLFSSL_TLS13) && (defined(HAVE_SESSION_TICKET) || \ @@ -1495,7 +1496,7 @@ static int GetSessionFromCache(WOLFSSL* ssl, WOLFSSL_SESSION* output, #ifdef HAVE_SESSION_TICKET if (tmpBufSet) { if (error == WOLFSSL_SUCCESS) { - if (output->ticketLen > SESSION_TICKET_LEN) { + if (output->ticketLen > sizeof(output->staticTicket)) { output->ticket = (byte*)XMALLOC(output->ticketLen, output->heap, DYNAMIC_TYPE_SESSION_TICK); if (output->ticket == NULL) { @@ -1504,6 +1505,9 @@ static int GetSessionFromCache(WOLFSSL* ssl, WOLFSSL_SESSION* output, output->ticketLenAlloc = 0; output->ticketLen = 0; } + else { + output->ticketLenAlloc = output->ticketLen; + } } else { output->ticket = output->staticTicket; @@ -2012,7 +2016,7 @@ int AddSessionToCache(WOLFSSL_CTX* ctx, WOLFSSL_SESSION* addSession, #ifdef HAVE_SESSION_TICKET ticLen = addSession->ticketLen; /* Alloc Memory here to avoid syscalls during lock */ - if (ticLen > SESSION_TICKET_LEN) { + if (ticLen > (int)sizeof(addSession->staticTicket)) { ticBuff = (byte*)XMALLOC((size_t)ticLen, NULL, DYNAMIC_TYPE_SESSION_TICK); if (ticBuff == NULL) { @@ -3363,7 +3367,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, if (s->ticketLenAlloc > 0) { XFREE(s->ticket, NULL, DYNAMIC_TYPE_SESSION_TICK); } - if (s->ticketLen <= SESSION_TICKET_LEN) + if (s->ticketLen <= sizeof(s->staticTicket)) s->ticket = s->staticTicket; else { s->ticket = (byte*)XMALLOC(s->ticketLen, NULL, @@ -4300,7 +4304,7 @@ static int wolfSSL_DupSessionEx(const WOLFSSL_SESSION* input, output->cacheRow = INVALID_SESSION_ROW; #endif #ifdef HAVE_SESSION_TICKET - if (input->ticketLen > SESSION_TICKET_LEN) { + if (input->ticketLen > sizeof(output->staticTicket)) { /* Need dynamic buffer */ if (ticBuff == NULL || ticLenAlloc < input->ticketLen) { /* allocate new one */ diff --git a/src/tls.c b/src/tls.c index ee221d10a63..0d4cad0c86c 100644 --- a/src/tls.c +++ b/src/tls.c @@ -12850,7 +12850,7 @@ int TLSX_PreSharedKey_Parse_ClientHello(TLSX** extensions, const byte* input, ato16(input + idx, &identityLen); idx += OPAQUE16_LEN; if (len < OPAQUE16_LEN + identityLen + OPAQUE32_LEN || - identityLen > MAX_PSK_ID_LEN) + identityLen > MAX_PSK_ID_WIRE_LEN) return BUFFER_E; /* Cache identity pointer. */ identity = input + idx; diff --git a/src/tls13.c b/src/tls13.c index d9b578a50fc..1a4e34e73c6 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6848,6 +6848,13 @@ int FindPskSuite(const WOLFSSL* ssl, PreSharedKey* psk, byte* psk_key, *found = 0; (void)suite; + /* A longer identity can only be a session ticket */ + if (psk->identityLen > MAX_PSK_ID_LEN) { + WOLFSSL_MSG("PSK identity longer than MAX_PSK_ID_LEN, not offered to " + "the PSK callback"); + return 0; + } + if (ssl->options.server_psk_tls13_cb != NULL) { *psk_keySz = ssl->options.server_psk_tls13_cb((WOLFSSL*)ssl, (char*)psk->identity, psk_key, MAX_PSK_KEY_LEN, &cipherName); @@ -7021,13 +7028,15 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, for (current = (PreSharedKey*)ext->data; current != NULL; current = current->next) { #ifndef NO_PSK - if (current->identityLen > MAX_PSK_ID_LEN) { - ret = BUFFER_ERROR; - goto cleanup; + /* A session ticket can be longer than an external PSK identity. */ + if (current->identityLen <= MAX_PSK_ID_LEN) { + XMEMCPY(ssl->arrays->client_identity, current->identity, + current->identityLen); + ssl->arrays->client_identity[current->identityLen] = '\0'; + } + else { + ssl->arrays->client_identity[0] = '\0'; } - XMEMCPY(ssl->arrays->client_identity, current->identity, - current->identityLen); - ssl->arrays->client_identity[current->identityLen] = '\0'; #endif #ifdef HAVE_SESSION_TICKET diff --git a/tests/api/test_session.c b/tests/api/test_session.c index ee2c769424c..7beb06e3955 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -2096,7 +2096,8 @@ int test_wolfSSL_SESSION_dup_peer_fail(void) !defined(NO_TLS) /* Cache a client session under id with a ticLen byte ticket of fill bytes. - * ticLen over SESSION_TICKET_LEN makes the cache allocate a ticket buffer. */ + * ticLen over SESSION_TICKET_STATIC_LEN makes the cache allocate a ticket + * buffer. */ static int test_session_at_index_add(WOLFSSL_CTX* ctx, const byte* id, word16 ticLen, byte fill, int* idx) { @@ -2150,7 +2151,7 @@ int test_wolfSSL_GetSessionAtIndex(void) WOLFSSL_SESSION* copy = NULL; WOLFSSL_SESSION* copy2 = NULL; byte id[ID_LEN]; - word16 ticLen = (word16)(SESSION_TICKET_LEN + 128); + word16 ticLen = (word16)(SESSION_TICKET_STATIC_LEN + 128); int idx = -1; XMEMSET(id, 0x5A, sizeof(id)); @@ -2217,7 +2218,7 @@ int test_wolfSSL_session_cache_short_ticket_reuse(void) XMEMSET(id, 0x5A, sizeof(id)); ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); ExpectIntEQ(test_session_at_index_add(ctx, id, - (word16)(SESSION_TICKET_LEN + 128), 0xA1, NULL), TEST_SUCCESS); + (word16)(SESSION_TICKET_STATIC_LEN + 128), 0xA1, NULL), TEST_SUCCESS); /* Same ID, short ticket in the session's static buffer. */ ExpectNotNull(sess = wolfSSL_SESSION_new()); @@ -2891,7 +2892,7 @@ static void test_set_sessions(sess_cache_t * cache_mem) * canary values. These should be sanitized on session restore. */ s->type = WOLFSSL_SESSION_TYPE_UNKNOWN; #ifdef HAVE_SESSION_TICKET - s->ticketLen = SESSION_TICKET_LEN + 1; + s->ticketLen = SESSION_TICKET_STATIC_LEN + 1; s->ticketLenAlloc = 1; #endif /* HAVE_SESSION_TICKET */ #if defined(HAVE_EXT_CACHE) || defined(HAVE_EX_DATA) @@ -2941,8 +2942,9 @@ static int test_sanity_sessions(const sess_cache_t * cache_mem, #endif /* HAVE_EX_DATA */ #ifdef HAVE_SESSION_TICKET - if (s->ticketLenAlloc != 0 || - s->ticketLen > SESSION_TICKET_LEN) { + /* The canary ticket did not fit the static buffer, so the + * restore drops it. */ + if (s->ticketLenAlloc != 0 || s->ticketLen != 0) { WOLFSSL_MSG_EX("error: %s: s = %p, id = 0x%02x%02x: " "ticketLenAlloc = %d, ticketLen = %d\n", what, s, diff --git a/tests/api/test_ssl_ext.c b/tests/api/test_ssl_ext.c index 746fd98ff62..363ac7130df 100644 --- a/tests/api/test_ssl_ext.c +++ b/tests/api/test_ssl_ext.c @@ -1098,7 +1098,7 @@ int test_wolfSSL_SessionTicket_inval_ext(void) ExpectIntEQ(wolfSSL_get_SessionTicket(ssl, out, &outSz), WOLFSSL_SUCCESS); ExpectIntEQ(outSz, 0); - /* A ticket larger than the static buffer (SESSION_TICKET_LEN) uses + /* A ticket larger than the static buffer (SESSION_TICKET_STATIC_LEN) uses * dynamic storage; growing it again frees the previous allocation, and a * later short ticket returns to the static buffer. */ ExpectIntEQ(wolfSSL_set_SessionTicket(ssl, big, 3000), WOLFSSL_SUCCESS); diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 0ada65215d3..c56462a43c0 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -12954,6 +12954,240 @@ int test_tls13_ticket_peer_cert_reverify(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SESSION_TICKET) && defined(OPENSSL_ALL) && \ + defined(KEEP_PEER_CERT) && !defined(NO_CERT_IN_TICKET) && \ + !defined(NO_RSA) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) +/* Resume with sess and check the server's peer certificate is cliCert. The + * server has no session cache, so the certificate can only come from the + * ticket. */ +static int test_ticket_peer_cert_resume_one(WOLFSSL_CTX* ctx_c, + WOLFSSL_CTX* ctx_s, WOLFSSL_SESSION* sess, WOLFSSL_X509* cliCert) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_X509 *peer = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s)); + ExpectIntEQ(wolfSSL_X509_cmp(peer, cliCert), 0); + + wolfSSL_X509_free(peer); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + return EXPECT_RESULT(); +} + +/* Full handshake with client authentication, then resume with the ticket, + * which carries the client certificate. */ +static int test_ticket_peer_cert_resume_run(method_provider method_c, + method_provider method_s) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + WOLFSSL_SESSION *copy = NULL; + WOLFSSL_X509 *cliCert = NULL; + char readBuf[64]; +#ifdef HAVE_EXT_CACHE + unsigned char* der = NULL; + const unsigned char* p = NULL; + int derSz = 0; +#endif + + ExpectNotNull(cliCert = wolfSSL_X509_load_certificate_file(cliCertFile, + CERT_FILETYPE)); + + ExpectNotNull(ctx_c = wolfSSL_CTX_new(method_c())); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_UseSessionTicket(ctx_c), WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + + ExpectNotNull(ctx_s = wolfSSL_CTX_new(method_s())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0), + WOLFSSL_SUCCESS); + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + /* Resumption can only use the ticket */ + ExpectIntEQ(wolfSSL_CTX_set_session_cache_mode(ctx_s, + WOLFSSL_SESS_CACHE_OFF), WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Take delivery of a TLS 1.3 NewSessionTicket */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* With the certificate the ticket does not fit the static buffer on + * either side. */ + if (EXPECT_SUCCESS()) { + ExpectIntGT(ssl_s->session->ticketLen, SESSION_TICKET_STATIC_LEN); + ExpectIntGE(ssl_s->session->ticketLenAlloc, + ssl_s->session->ticketLen); + } + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + if (EXPECT_SUCCESS()) { + ExpectIntGT(sess->ticketLen, SESSION_TICKET_STATIC_LEN); + ExpectIntLE(sess->ticketLen, SESSION_TICKET_LEN); + ExpectPtrNE(sess->ticket, sess->staticTicket); + } + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + + ExpectIntEQ(test_ticket_peer_cert_resume_one(ctx_c, ctx_s, sess, cliCert), + TEST_SUCCESS); + + /* A copy of the session carries its own copy of the ticket */ + ExpectNotNull(copy = wolfSSL_SESSION_dup(sess)); + if (EXPECT_SUCCESS()) { + ExpectPtrNE(copy->ticket, sess->ticket); + ExpectIntEQ(copy->ticketLen, sess->ticketLen); + } + ExpectIntEQ(test_ticket_peer_cert_resume_one(ctx_c, ctx_s, copy, cliCert), + TEST_SUCCESS); + wolfSSL_SESSION_free(copy); + copy = NULL; + +#ifdef HAVE_EXT_CACHE + /* So does a session read back from its DER encoding */ + ExpectIntGT((derSz = wolfSSL_i2d_SSL_SESSION(sess, &der)), 0); + p = der; + ExpectNotNull(copy = wolfSSL_d2i_SSL_SESSION(NULL, &p, derSz)); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(copy->ticketLen, sess->ticketLen); + ExpectPtrNE(copy->ticket, copy->staticTicket); + } + ExpectIntEQ(test_ticket_peer_cert_resume_one(ctx_c, ctx_s, copy, cliCert), + TEST_SUCCESS); + wolfSSL_SESSION_free(copy); + XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL); +#endif + + wolfSSL_SESSION_free(sess); + wolfSSL_X509_free(cliCert); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Resume from a session ticket that carries the peer certificate. The ticket + * is longer than the static ticket buffer of a session, so it is kept in a + * dynamic buffer by the server that makes it, the client that keeps it and + * every copy of the client's session. */ +int test_tls13_ticket_peer_cert_resume(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SESSION_TICKET) && defined(OPENSSL_ALL) && \ + defined(KEEP_PEER_CERT) && !defined(NO_CERT_IN_TICKET) && \ + !defined(NO_RSA) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) +#ifdef WOLFSSL_TLS13 + ExpectIntEQ(test_ticket_peer_cert_resume_run(wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), TEST_SUCCESS); +#endif +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(test_ticket_peer_cert_resume_run(wolfTLSv1_2_client_method, + wolfTLSv1_2_server_method), TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) +/* Encrypts the ticket but reports one byte more than the space it was + * given. */ +static int test_ticket_enc_long_cb(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) + return WOLFSSL_TICKET_RET_REJECT; + XMEMSET(key_name, 0x11, WOLFSSL_TICKET_NAME_SZ); + XMEMSET(iv, 0x22, WOLFSSL_TICKET_IV_SZ); + XMEMSET(mac, 0x33, WOLFSSL_TICKET_MAC_SZ); + for (i = 0; i < inLen; i++) + ticket[i] ^= 0x5a; + *outLen += 1; + return WOLFSSL_TICKET_RET_OK; +} +#endif + +/* A ticket encrypt callback that reports more output than the space it was + * given fails ticket creation, and the session is left without a ticket + * rather than with a length past the end of the ticket buffer. */ +int test_tls13_ticket_enc_too_long(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + + 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(wolfSSL_CTX_set_TicketEncCb(ctx_s, test_ticket_enc_long_cb), + WOLFSSL_SUCCESS); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(BAD_TICKET_KEY_CB_SZ)); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(ssl_s->session->ticketLen, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* Verify that a TLS 1.3 server never stores a session in its cache under the * client-chosen legacy_session_id. A second client that replays another * client's legacy_session_id in a full handshake but sends no certificate must diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index cd040650fb5..2a2738066b4 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -139,6 +139,8 @@ int test_tls13_fail_if_no_psk_no_cert_server(void); int test_tls13_fail_if_no_psk_dtls13_handshake(void); int test_tls13_fail_if_no_psk_dtls13_rejects_no_psk(void); int test_tls13_ticket_peer_cert_reverify(void); +int test_tls13_ticket_peer_cert_resume(void); +int test_tls13_ticket_enc_too_long(void); int test_tls13_legacy_session_id_peer_chain(void); int test_tls13_clear_preserves_psk_dhe(void); int test_tls13_cipher_fuzz_aes128_gcm_sha256(void); @@ -324,6 +326,8 @@ int test_tls13_AEAD_limit_KeyUpdate_want_write(void); TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_dtls13_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_dtls13_rejects_no_psk), \ TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \ + TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_resume), \ + TEST_DECL_GROUP("tls13", test_tls13_ticket_enc_too_long), \ TEST_DECL_GROUP("tls13", test_tls13_legacy_session_id_peer_chain), \ TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_gcm_sha256), \ diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 484b693006e..c3fec2ee90a 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -1679,7 +1679,7 @@ int test_tls_msgtype_sni_parse_ech_public(void) /* if (len < MIN_PSK_ID_LEN || length - idx < len) return BUFFER_E; * ... * if (len < OPAQUE16_LEN + identityLen + OPAQUE32_LEN || - * identityLen > MAX_PSK_ID_LEN) return BUFFER_E; */ + * identityLen > MAX_PSK_ID_WIRE_LEN) return BUFFER_E; */ int test_tls_msgtype_psk_ch_id_gates(void) { EXPECT_DECLS; @@ -1687,8 +1687,8 @@ int test_tls_msgtype_psk_ch_id_gates(void) defined(HAVE_TLS_EXTENSIONS) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; - byte buf[MAX_PSK_ID_LEN + 64]; - byte body[MAX_PSK_ID_LEN + 48]; + byte buf[MAX_PSK_ID_WIRE_LEN + 64]; + byte body[MAX_PSK_ID_WIRE_LEN + 48]; word16 len; Suites suites; @@ -1748,12 +1748,12 @@ int test_tls_msgtype_psk_ch_id_gates(void) ctx = NULL; /* Per-identity op0 false, op1 true: identityLen is one more than - * MAX_PSK_ID_LEN, with enough buffer supplied to hold it in full. */ + * MAX_PSK_ID_WIRE_LEN, with enough buffer supplied to hold it in full. */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); XMEMSET(&suites, 0, sizeof(suites)); { - word16 idLen = MAX_PSK_ID_LEN + 1; + word16 idLen = (word16)(MAX_PSK_ID_WIRE_LEN + 1); word16 identitiesLen = (word16)(OPAQUE16_LEN + idLen + OPAQUE32_LEN); word16 idx = 0; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 25316258723..765de1e6196 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -4097,6 +4097,10 @@ typedef struct InternalTicket { #define WOLFSSL_TICKET_ENC_SZ \ (WOLFSSL_INTERNAL_TICKET_MAX_SZ + WOLFSSL_TICKET_EXTRA_PADDING_SZ) +/* Encrypted size of a ticket that carries no peer certificate */ +#define WOLFSSL_TICKET_ENC_BASE_SZ \ + (WOLFSSL_INTERNAL_TICKET_BASE_SZ + WOLFSSL_TICKET_EXTRA_PADDING_SZ) + /* RFC 5077 defines this for session tickets. All members need to be a byte or * array of byte to avoid alignment issues */ typedef struct ExternalTicket { @@ -4116,6 +4120,12 @@ typedef struct ExternalTicket { #define SESSION_TICKET_LEN \ ((int)(WOLFSSL_TICKET_FIXED_SZ + WOLFSSL_TICKET_ENC_SZ)) +/* Length of a session ticket that carries no peer certificate. Sizes the + * static ticket buffer of WOLFSSL_SESSION; a longer ticket is kept in a + * dynamic buffer. Same as SESSION_TICKET_LEN when tickets carry no cert. */ +#define SESSION_TICKET_STATIC_LEN \ + ((int)(WOLFSSL_TICKET_FIXED_SZ + WOLFSSL_TICKET_ENC_BASE_SZ)) + typedef struct SessionTicket { word32 lifetime; #ifdef WOLFSSL_TLS13 @@ -4169,8 +4179,10 @@ WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap) #ifndef MAX_PSK_ID_LEN /* max psk identity/hint supported */ #if defined(WOLFSSL_TLS13) - #ifdef SESSION_TICKET_LEN - #define MAX_PSK_ID_LEN SESSION_TICKET_LEN + #ifdef SESSION_TICKET_STATIC_LEN + /* Not grown by a peer cert in the ticket: a server accepts its + * longer tickets up to MAX_PSK_ID_WIRE_LEN. */ + #define MAX_PSK_ID_LEN SESSION_TICKET_STATIC_LEN #else /* Previous value. Use as fallback for when tickets are disabled. */ #define MAX_PSK_ID_LEN 1536 @@ -4180,6 +4192,16 @@ WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap) #endif #endif +/* Longest identity a server accepts in a TLS 1.3 pre_shared_key extension: + * an external PSK identity or one of its session tickets. */ +#ifdef HAVE_SESSION_TICKET + #define MAX_PSK_ID_WIRE_LEN \ + ((int)MAX_PSK_ID_LEN > SESSION_TICKET_LEN ? (int)MAX_PSK_ID_LEN : \ + SESSION_TICKET_LEN) +#else + #define MAX_PSK_ID_WIRE_LEN MAX_PSK_ID_LEN +#endif + #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) int TLSX_EncryptThenMac_Respond(WOLFSSL* ssl); #endif @@ -5414,7 +5436,7 @@ struct WOLFSSL_SESSION { #endif /* WOLFSSL_EARLY_DATA */ #endif /* HAVE_SESSION_TICKET || !NO_PSK */ #ifdef HAVE_SESSION_TICKET - byte staticTicket[SESSION_TICKET_LEN]; + byte staticTicket[SESSION_TICKET_STATIC_LEN]; byte* ticket; word16 ticketLen; word16 ticketLenAlloc; /* is dynamic */ From 1ff3b1f09bcddc05a93136449997f59c882737e7 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Wed, 4 Nov 2026 15:49:52 +0000 Subject: [PATCH 2/2] Clear the ticket length when SetTicket() cannot allocate SetTicket() frees a dynamic ticket and points the session back at the static buffer before allocating the buffer for the new ticket. When that allocation failed, ticketLen kept the old ticket's length, which is longer than the static buffer, so wolfSSL_get_SessionTicket() or a session copy could read past it. Certificate-carrying tickets now use the dynamic buffer, which makes this path reachable for ordinary tickets. Test: a client holding a certificate-carrying ticket fails to allocate the buffer for a second one and is left without a ticket. --- src/internal.c | 6 ++- tests/api/test_tls13.c | 103 +++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 + 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 2735bf01743..348c2f4cd23 100644 --- a/src/internal.c +++ b/src/internal.c @@ -40684,8 +40684,12 @@ int SetTicket(WOLFSSL* ssl, const byte* ticket, word32 length) if (length > sizeof(ssl->session->staticTicket)) { byte* sessionTicket = (byte*)XMALLOC(length, ssl->heap, DYNAMIC_TYPE_SESSION_TICK); - if (sessionTicket == NULL) + if (sessionTicket == NULL) { + /* The old ticket is gone; its length is not valid for the static + * buffer */ + ssl->session->ticketLen = 0; return MEMORY_E; + } ssl->session->ticket = sessionTicket; ssl->session->ticketLenAlloc = (word16)length; } diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index c56462a43c0..4101dfa9b3d 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -13129,6 +13129,109 @@ int test_tls13_ticket_peer_cert_resume(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && defined(USE_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ + !defined(WOLFSSL_CHECK_MEM_ZERO) +static size_t ticket_alloc_fail_sz = 0; + +static void* ticket_alloc_fail_malloc(size_t size) +{ + if (ticket_alloc_fail_sz != 0 && size == ticket_alloc_fail_sz) + return NULL; + return malloc(size); +} + +static void ticket_alloc_fail_free(void* ptr) +{ + free(ptr); +} + +static void* ticket_alloc_fail_realloc(void* ptr, size_t size) +{ + return realloc(ptr, size); +} +#endif + +/* A client that holds a ticket in a dynamic buffer and fails to allocate the + * buffer for a new one is left without a ticket, not with the old length on + * the static buffer. */ +int test_tls13_ticket_set_alloc_fail(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ + !defined(NO_CERT_IN_TICKET) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) && defined(USE_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ + !defined(WOLFSSL_CHECK_MEM_ZERO) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + char readBuf[64]; + + /* With client authentication the ticket carries the client certificate + * and needs a dynamic buffer */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + 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_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (EXPECT_SUCCESS()) { + ExpectIntGT(ssl_c->session->ticketLen, + sizeof(ssl_c->session->staticTicket)); + ExpectIntGT(ssl_c->session->ticketLenAlloc, 0); + } + + /* A second ticket of the same length, whose buffer cannot be + * allocated */ + ExpectIntEQ(wolfSSL_send_SessionTicket(ssl_s), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ticket_alloc_fail_sz = ssl_c->session->ticketLen; + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(ticket_alloc_fail_malloc, + ticket_alloc_fail_free, ticket_alloc_fail_realloc), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ticket_alloc_fail_sz = 0; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), + WC_NO_ERR_TRACE(MEMORY_E)); + } + if (EXPECT_SUCCESS()) { + ExpectPtrEq(ssl_c->session->ticket, ssl_c->session->staticTicket); + ExpectIntEQ(ssl_c->session->ticketLenAlloc, 0); + ExpectIntEQ(ssl_c->session->ticketLen, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) /* Encrypts the ticket but reports one byte more than the space it was diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 2a2738066b4..3b1059dcb25 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -141,6 +141,7 @@ int test_tls13_fail_if_no_psk_dtls13_rejects_no_psk(void); int test_tls13_ticket_peer_cert_reverify(void); int test_tls13_ticket_peer_cert_resume(void); int test_tls13_ticket_enc_too_long(void); +int test_tls13_ticket_set_alloc_fail(void); int test_tls13_legacy_session_id_peer_chain(void); int test_tls13_clear_preserves_psk_dhe(void); int test_tls13_cipher_fuzz_aes128_gcm_sha256(void); @@ -328,6 +329,7 @@ int test_tls13_AEAD_limit_KeyUpdate_want_write(void); TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \ TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_resume), \ TEST_DECL_GROUP("tls13", test_tls13_ticket_enc_too_long), \ + TEST_DECL_GROUP("tls13", test_tls13_ticket_set_alloc_fail), \ TEST_DECL_GROUP("tls13", test_tls13_legacy_session_id_peer_chain), \ TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_gcm_sha256), \