diff --git a/src/internal.c b/src/internal.c index 83eb4e289b4..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; } @@ -45204,37 +45208,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 +45394,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 +45411,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 +45448,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 +45490,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 +45502,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..4101dfa9b3d 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -12954,6 +12954,343 @@ 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) && \ + 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 + * 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..3b1059dcb25 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -139,6 +139,9 @@ 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_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); @@ -324,6 +327,9 @@ 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_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), \ 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 */