From 49222dbf3b24267acfe4784f8b6f91daf4e46ede Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 24 Apr 2026 14:16:03 +0200 Subject: [PATCH 1/4] zd/21661: harden X.509 chain validation, session ticket identity binding, and peer cert restore - x509_str: require CA:TRUE unconditionally in wolfSSL_X509_verify_cert - asn: reject embedded NUL in dNSName / rfc822Name / URI SAN entries - internal: re-verify restored ticket peer cert against trust store with CRL/OCSP checks; clear stale state from session cache on verification failure - tests: update SAN NUL fixtures and add parse-time rejection coverage; add test_tls13_ticket_peer_cert_reverify for CA-removal scenario - ssl_sess: free previous session in wolfSSL_d2i_SSL_SESSION before overwrite - ticket: bind SNI and ALPN into session ticket via compile-time selected hash (TICKET_BINDING_HASH_TYPE); reject resumption on mismatch in both TLS 1.3 and TLS 1.2 paths - examples/client: increase SESSION_TICKET_LEN fallback from 256 to 2048 to support larger tickets --- examples/client/client.c | 2 +- src/internal.c | 172 +++++++++++++++++++++++++++++++------ src/ssl_sess.c | 1 + src/x509_str.c | 16 ++-- tests/api/test_asn.c | 12 ++- tests/api/test_ossl_x509.c | 5 +- tests/api/test_tls13.c | 119 ++++++++++++++++++++++++- tests/api/test_tls13.h | 4 +- tests/test-fails.conf | 15 ---- wolfcrypt/src/asn.c | 25 ++++++ wolfcrypt/src/asn_orig.c | 31 +++++++ wolfssl/internal.h | 32 +++++++ 12 files changed, 376 insertions(+), 58 deletions(-) diff --git a/examples/client/client.c b/examples/client/client.c index dfcfe5942ba..267b5309297 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -155,7 +155,7 @@ static int quieter = 0; /* Print fewer messages. This is helpful with overly #ifdef HAVE_SESSION_TICKET #ifndef SESSION_TICKET_LEN -#define SESSION_TICKET_LEN 256 +#define SESSION_TICKET_LEN 2048 #endif static int sessionTicketCB(WOLFSSL* ssl, const unsigned char* ticket, int ticketSz, diff --git a/src/internal.c b/src/internal.c index c10b89d6a67..778ea5fac9e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -39249,6 +39249,47 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) return ret; } +#ifdef HAVE_SNI + /* Hash the server-selected SNI into dst (TICKET_BINDING_HASH_SZ bytes). + * Zeros dst when no SNI is present. */ + static int TicketSniHash(WOLFSSL* ssl, byte* dst) + { + char* name = NULL; + word16 nameLen; + + nameLen = TLSX_SNI_GetRequest(ssl->extensions, + WOLFSSL_SNI_HOST_NAME, + (void**)&name, 0); + if (name != NULL && nameLen > 0) { + return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)name, + nameLen, dst, TICKET_BINDING_HASH_SZ); + } + + XMEMSET(dst, 0, TICKET_BINDING_HASH_SZ); + return 0; + } +#endif + +#ifdef HAVE_ALPN + /* Hash the negotiated ALPN protocol into dst (TICKET_BINDING_HASH_SZ + * bytes). Zeros dst when no ALPN was negotiated. */ + static int TicketAlpnHash(WOLFSSL* ssl, byte* dst) + { + char* proto = NULL; + word16 protoLen = 0; + + if (TLSX_ALPN_GetRequest(ssl->extensions, (void**)&proto, + &protoLen) == WOLFSSL_SUCCESS && + proto != NULL && protoLen > 0) { + return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)proto, + protoLen, dst, TICKET_BINDING_HASH_SZ); + } + + XMEMSET(dst, 0, TICKET_BINDING_HASH_SZ); + return 0; + } +#endif + /* create a new session ticket, 0 on success * Do any kind of setup in SetupTicket */ int CreateTicket(WOLFSSL* ssl) @@ -39347,6 +39388,18 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->sessionCtxSz = ssl->sessionCtxSz; XMEMCPY(it->sessionCtx, ssl->sessionCtx, ID_LEN); #endif +#ifdef HAVE_SNI + ret = TicketSniHash(ssl, it->sniHash); + if (ret != 0) + goto error; + XMEMCPY(ssl->session->sniHash, it->sniHash, TICKET_BINDING_HASH_SZ); +#endif +#ifdef HAVE_ALPN + ret = TicketAlpnHash(ssl, it->alpnHash); + if (ret != 0) + goto error; + XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ); +#endif #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) @@ -39701,6 +39754,28 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) XMEMCMP(psk->it->sessionCtx, ssl->sessionCtx, ssl->sessionCtxSz) != 0)) return WOLFSSL_FATAL_ERROR; +#endif +#ifdef HAVE_SNI + { + byte curHash[TICKET_BINDING_HASH_SZ]; + if (TicketSniHash((WOLFSSL*)ssl, curHash) != 0 || + XMEMCMP(curHash, psk->it->sniHash, + TICKET_BINDING_HASH_SZ) != 0) { + WOLFSSL_MSG("Ticket SNI mismatch"); + return WOLFSSL_FATAL_ERROR; + } + } +#endif +#ifdef HAVE_ALPN + { + byte curHash[TICKET_BINDING_HASH_SZ]; + if (TicketAlpnHash((WOLFSSL*)ssl, curHash) != 0 || + XMEMCMP(curHash, psk->it->alpnHash, + TICKET_BINDING_HASH_SZ) != 0) { + WOLFSSL_MSG("Ticket ALPN mismatch"); + return WOLFSSL_FATAL_ERROR; + } + } #endif return 0; } @@ -39713,36 +39788,54 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) word16 peerCertLen = 0; ato16(it->peerCertLen, &peerCertLen); - if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) { + /* Clear any peer cert state that may have been copied from the session + * cache by wolfSSL_DupSession before we got here. */ + FreeX509(&ssl->peerCert); + InitX509(&ssl->peerCert, 0, ssl->heap); #ifdef SESSION_CERTS - /* Clear existing chain and add the peer certificate */ - ssl->session->chain.count = 0; - AddSessionCertToChain(&ssl->session->chain, - it->peerCert, peerCertLen); + ssl->session->chain.count = 0; #endif - /* Also decode into ssl->peerCert for direct access */ - { - int ret; - DecodedCert* dCert; - - dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap, - DYNAMIC_TYPE_DCERT); - if (dCert != NULL) { - InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap); - ret = ParseCertRelative(dCert, CERT_TYPE, 0, NULL, NULL); - if (ret == 0) { + + if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) { + int ret; + DecodedCert* dCert; + + dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap, + DYNAMIC_TYPE_DCERT); + if (dCert != NULL) { + int verify = ssl->options.verifyPeer ? VERIFY : NO_VERIFY; + InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap); + /* Re-verify against the current trust store so that CA + * removal since ticket issue is enforced. */ + ret = ParseCertRelative(dCert, CERT_TYPE, verify, + SSL_CM(ssl), NULL); + #ifdef HAVE_OCSP + /* ParseCertRelative does not check revocation status. + * Run OCSP if the CertManager has it enabled. */ + if (ret == 0 && SSL_CM(ssl)->ocspEnabled) { + ret = CheckCertOCSP_ex(SSL_CM(ssl)->ocsp, dCert, ssl); + } + #endif + #ifdef HAVE_CRL + if (ret == 0 && SSL_CM(ssl)->crlEnabled) { + ret = CheckCertCRL(SSL_CM(ssl)->crl, dCert); + } + #endif + if (ret == 0) { + #ifdef SESSION_CERTS + AddSessionCertToChain(&ssl->session->chain, + it->peerCert, peerCertLen); + #endif + FreeX509(&ssl->peerCert); + InitX509(&ssl->peerCert, 0, ssl->heap); + ret = CopyDecodedToX509(&ssl->peerCert, dCert); + if (ret != 0) { FreeX509(&ssl->peerCert); InitX509(&ssl->peerCert, 0, ssl->heap); - ret = CopyDecodedToX509(&ssl->peerCert, dCert); - if (ret != 0) { - /* Failed to copy - clear peerCert */ - FreeX509(&ssl->peerCert); - InitX509(&ssl->peerCert, 0, ssl->heap); - } } - FreeDecodedCert(dCert); - XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT); } + FreeDecodedCert(dCert); + XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT); } } } @@ -39888,6 +39981,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) it->sessionCtxSz = sess->sessionCtxSz; XMEMCPY(it->sessionCtx, sess->sessionCtx, sess->sessionCtxSz); #endif +#ifdef HAVE_SNI + XMEMCPY(it->sniHash, sess->sniHash, TICKET_BINDING_HASH_SZ); +#endif +#ifdef HAVE_ALPN + XMEMCPY(it->alpnHash, sess->alpnHash, TICKET_BINDING_HASH_SZ); +#endif #if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ defined(SESSION_CERTS) && !defined(NO_CERT_IN_TICKET) /* Store peer certificate from session chain */ @@ -40132,6 +40231,31 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) goto cleanup; } +#ifdef HAVE_SNI + { + byte curHash[TICKET_BINDING_HASH_SZ]; + if (TicketSniHash(ssl, curHash) != 0 || + XMEMCMP(curHash, it->sniHash, + TICKET_BINDING_HASH_SZ) != 0) { + WOLFSSL_MSG("Ticket SNI mismatch"); + decryptRet = WOLFSSL_TICKET_RET_REJECT; + goto cleanup; + } + } +#endif +#ifdef HAVE_ALPN + { + byte curHash[TICKET_BINDING_HASH_SZ]; + if (TicketAlpnHash(ssl, curHash) != 0 || + XMEMCMP(curHash, it->alpnHash, + TICKET_BINDING_HASH_SZ) != 0) { + WOLFSSL_MSG("Ticket ALPN mismatch"); + decryptRet = WOLFSSL_TICKET_RET_REJECT; + goto cleanup; + } + } +#endif + DoClientTicketFinalize(ssl, it, NULL); cleanup: diff --git a/src/ssl_sess.c b/src/ssl_sess.c index ec790575057..146471350db 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -3069,6 +3069,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess, (void)idx; if (sess != NULL) { + wolfSSL_FreeSession(NULL, *sess); *sess = s; } diff --git a/src/x509_str.c b/src/x509_str.c index 294a5a2eb29..b3ec75fe472 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -705,14 +705,11 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) /* We found our issuer in the non-trusted cert list, add it * to the CM and verify the current cert against it */ - #if defined(OPENSSL_ALL) || defined(WOLFSSL_QT) - /* OpenSSL doesn't allow the cert as CA if it is not CA:TRUE for - * intermediate certs. - */ + /* RFC 5280 4.2.1.9: reject non-CA issuer. */ if (!issuer->isCa) { - /* error depth is current depth + 1 */ SetupStoreCtxError_ex(ctx, X509_V_ERR_INVALID_CA, (ctx->chain) ? (int)(ctx->chain->num + 1) : 1); + #if defined(OPENSSL_ALL) || defined(WOLFSSL_QT) if (ctx->store->verify_cb) { ret = ctx->store->verify_cb(0, ctx); if (ret != WOLFSSL_SUCCESS) { @@ -720,13 +717,14 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) goto exit; } } - else { + else + #endif + { ret = WOLFSSL_FAILURE; goto exit; } - } else - #endif - { + } + else { ret = X509StoreAddCa(ctx->store, issuer, WOLFSSL_TEMP_CA); if (ret != WOLFSSL_SUCCESS) { diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 64be65084f3..36e4ec72c1e 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -969,7 +969,7 @@ int test_DecodeAltNames_length_underflow(void) 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, /* SAN extension: correct SEQUENCE length 0x06 */ 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x08, 0x30, 0x06, 0x82, - 0x04, 0x61, 0x2a, 0x00, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x61, 0x2a, 0x62, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x92, 0x6a, 0x1e, 0x52, 0x3a, 0x1a, 0x57, 0x9f, 0xc9, 0x82, 0x9a, 0xce, 0xc8, 0xc0, 0xa9, 0x51, 0x9d, 0x2f, 0xc7, 0x72, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, @@ -1025,6 +1025,16 @@ int test_DecodeAltNames_length_underflow(void) WC_NO_ERR_TRACE(ASN_PARSE_E)); wc_FreeDecodedCert(&cert); + /* NUL in dNSName SAN must be rejected per RFC 5280 4.2.1.6. */ + XMEMCPY(bad_san_cert, good_san_cert, sizeof(good_san_cert)); + bad_san_cert[SAN_SEQ_LEN_OFFSET + 5] = 0x00; + + wc_InitDecodedCert(&cert, bad_san_cert, (word32)sizeof(bad_san_cert), + NULL); + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_FreeDecodedCert(&cert); + #endif /* !NO_CERTS && !NO_RSA && !NO_ASN */ return EXPECT_RESULT(); } diff --git a/tests/api/test_ossl_x509.c b/tests/api/test_ossl_x509.c index ce8546dc247..32854851f6a 100644 --- a/tests/api/test_ossl_x509.c +++ b/tests/api/test_ossl_x509.c @@ -1136,7 +1136,7 @@ int test_wolfSSL_X509_bad_altname(void) 0xf5, 0xe5, 0x09, 0x02, 0x01, 0x03, 0xa3, 0x61, 0x30, 0x5f, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x08, 0x30, 0x06, 0x82, - 0x04, 0x61, 0x2a, 0x00, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x61, 0x2a, 0x62, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x92, 0x6a, 0x1e, 0x52, 0x3a, 0x1a, 0x57, 0x9f, 0xc9, 0x82, 0x9a, 0xce, 0xc8, 0xc0, 0xa9, 0x51, 0x9d, 0x2f, 0xc7, 0x72, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, @@ -1175,8 +1175,7 @@ int test_wolfSSL_X509_bad_altname(void) ExpectNotNull(x509 = wolfSSL_X509_load_certificate_buffer( malformed_alt_name_cert, certSize, SSL_FILETYPE_ASN1)); - /* malformed_alt_name_cert has a malformed alternative - * name of "a*\0*". Ensure that it does not match "aaaaa" */ + /* SAN "a*b*" must not match "aaaaa" under any wildcard flag. */ ExpectIntNE(wolfSSL_X509_check_host(x509, name, nameLen, WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), 1); diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index ba12b7e3689..b32565abb7b 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -4579,10 +4579,6 @@ int test_tls13_pqc_hybrid_truncated_keyshare(void) return EXPECT_RESULT(); } -/* Test that a TLS 1.3 NewSessionTicket with a ticket shorter than ID_LEN - * (32 bytes) does not cause an unsigned integer underflow / OOB read in - * SetTicket. Uses a full memio handshake, then injects a crafted - * NewSessionTicket with a 5-byte ticket into the client's read path. */ int test_tls13_empty_record_limit(void) { EXPECT_DECLS; @@ -4754,6 +4750,11 @@ int test_tls13_empty_record_limit(void) return EXPECT_RESULT(); } +/* Test that a TLS 1.3 NewSessionTicket with a ticket shorter than ID_LEN + * (32 bytes) does not cause an unsigned integer underflow / OOB read in + * SetTicket. Uses a full memio handshake, then injects a crafted + * NewSessionTicket with a 5-byte ticket into the client's read path. */ + int test_tls13_short_session_ticket(void) { EXPECT_DECLS; @@ -5264,3 +5265,113 @@ int test_tls13_serverhello_bad_cipher_suites(void) #endif return EXPECT_RESULT(); } + +/* Verify that a peer certificate restored from a session ticket is re-verified + * against the current trust store. After CA removal, the cert must not be + * installed into ssl->peerCert even though the ticket itself decrypts fine. */ +int test_tls13_ticket_peer_cert_reverify(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(WOLFSSL_NO_TLS12) && \ + !defined(NO_RSA) + 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_X509 *peer = NULL; + char readBuf[64]; + + /* --- Step 1: mTLS handshake, obtain a session ticket --- */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Set up CTXs manually so we can configure mTLS before SSL creation */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + /* Server trusts both its own CA and the client CA for mTLS */ + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, + "certs/client-ca.pem", 0), WOLFSSL_SUCCESS); + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + + /* Create SSL objects from fully-configured CTXs */ + 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); + + /* Drain post-handshake NewSessionTicket */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* Peer cert should be available after initial handshake */ + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s)); + wolfSSL_X509_free(peer); + peer = NULL; + + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + + /* --- Step 2: remove the client CA from the server trust store --- */ + ExpectIntEQ(wolfSSL_CTX_UnloadCAs(ctx_s), WOLFSSL_SUCCESS); + /* Re-load only the server's own CA so TLS works, but not the client CA */ + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0), + WOLFSSL_SUCCESS); + + /* --- Step 3: resume with the old ticket --- */ + 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); + + /* Resumption handshake succeeds (the ticket master secret is fine) */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The session should have been resumed via PSK. */ + ExpectIntEQ(wolfSSL_session_reused(ssl_s), 1); + /* But the peer cert must NOT be restored because the issuing CA is + * no longer in the trust store. Check the peerCert directly rather + * than wolfSSL_get_peer_certificate which has a session-chain + * fallback that may see stale cache state. */ + ExpectIntEQ(ssl_s->peerCert.issuer.sz, 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index c8b42fc56c6..113eb13a08a 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -60,6 +60,7 @@ int test_tls13_cert_with_extern_psk_requires_key_share(void); int test_tls13_cert_with_extern_psk_rejects_resumption(void); int test_tls13_cert_with_extern_psk_sh_missing_key_share(void); int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void); +int test_tls13_ticket_peer_cert_reverify(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -97,6 +98,7 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void); TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_requires_key_share), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_rejects_resumption), \ TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption) + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \ + TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/tests/test-fails.conf b/tests/test-fails.conf index 955a6c67ba5..74530e3e0e6 100644 --- a/tests/test-fails.conf +++ b/tests/test-fails.conf @@ -14,21 +14,6 @@ -m -x -# server bad certificate alternate name has null --v 3 --l ECDHE-RSA-AES128-GCM-SHA256 --k ./certs/server-key.pem --c ./certs/test/server-badaltnull.pem --d - -# client bad certificate alternate name has null --v 3 --l ECDHE-RSA-AES128-GCM-SHA256 --h localhost --A ./certs/test/server-badaltnull.pem --m --x - # server nomatch common name -v 3 -l ECDHE-RSA-AES128-GCM-SHA256 diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 9a3be56616f..2d5f0c650b7 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18026,6 +18026,19 @@ static int DecodeOtherName(DecodedCert* cert, const byte* input, * @return ASN_UNKNOWN_OID_E when the OID cannot be verified. * @return MEMORY_E when dynamic memory allocation fails. */ +/* Reject IA5String SAN content that cannot legally appear in + * dNSName / rfc822Name / URI per RFC 5280 4.2.1.6. Currently just NUL. */ +static int DecodeGeneralNameCheckChars(const byte* input, int len) +{ + int i; + for (i = 0; i < len; i++) { + if (input[i] == 0) { + return ASN_PARSE_E; + } + } + return 0; +} + static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, int len, DecodedCert* cert) { @@ -18034,6 +18047,10 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, /* GeneralName choice: dnsName */ if (tag == (ASN_CONTEXT_SPECIFIC | ASN_DNS_TYPE)) { + ret = DecodeGeneralNameCheckChars(input + idx, len); + if (ret != 0) { + return ret; + } ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, ASN_DNS_TYPE, &cert->altNames); if (ret == 0) { @@ -18061,6 +18078,10 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, } /* GeneralName choice: rfc822Name */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE)) { + ret = DecodeGeneralNameCheckChars(input + idx, len); + if (ret != 0) { + return ret; + } ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, ASN_RFC822_TYPE, &cert->altEmailNames); if (ret == 0) { @@ -18069,6 +18090,10 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, } /* GeneralName choice: uniformResourceIdentifier */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_URI_TYPE)) { + ret = DecodeGeneralNameCheckChars(input + idx, len); + if (ret != 0) { + return ret; + } WOLFSSL_MSG("\tPutting URI into list but not using"); #if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_FPKI) diff --git a/wolfcrypt/src/asn_orig.c b/wolfcrypt/src/asn_orig.c index d6568aa5d11..a154f21ed04 100644 --- a/wolfcrypt/src/asn_orig.c +++ b/wolfcrypt/src/asn_orig.c @@ -3200,6 +3200,19 @@ static int DecodeConstructedOtherName(DecodedCert* cert, const byte* input, return ret; } +/* Reject IA5String SAN content that cannot legally appear in + * dNSName / rfc822Name / URI per RFC 5280 4.2.1.6. Currently just NUL. */ +static int DecodeGeneralNameCheckChars(const byte* input, int len) +{ + int i; + for (i = 0; i < len; i++) { + if (input[i] == 0) { + return ASN_PARSE_E; + } + } + return 0; +} + static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert) { word32 idx = 0; @@ -3259,6 +3272,13 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert) } length -= (int)(idx - lenStartIdx); + if ((word32)strLen + idx > sz) { + return BUFFER_E; + } + if (DecodeGeneralNameCheckChars(&input[idx], strLen) != 0) { + return ASN_PARSE_E; + } + dnsEntry = AltNameNew(cert->heap); if (dnsEntry == NULL) { WOLFSSL_MSG("\tOut of Memory"); @@ -3344,6 +3364,13 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert) } length -= (int)(idx - lenStartIdx); + if ((word32)strLen + idx > sz) { + return BUFFER_E; + } + if (DecodeGeneralNameCheckChars(&input[idx], strLen) != 0) { + return ASN_PARSE_E; + } + emailEntry = AltNameNew(cert->heap); if (emailEntry == NULL) { WOLFSSL_MSG("\tOut of Memory"); @@ -3389,6 +3416,10 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert) return BUFFER_E; } + if (DecodeGeneralNameCheckChars(&input[idx], strLen) != 0) { + return ASN_PARSE_E; + } + #if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_FPKI) /* Verify RFC 5280 Sec 4.2.1.6 rule: "The name MUST NOT be a relative URI" diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 96606ba4f97..af0c852bc96 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3498,6 +3498,24 @@ WOLFSSL_LOCAL int TLSX_AddEmptyRenegotiationInfo(TLSX** extensions, void* heap); #ifndef MAX_TICKET_PEER_CERT_SZ #define MAX_TICKET_PEER_CERT_SZ 2048 #endif +#if defined(HAVE_SNI) || defined(HAVE_ALPN) +/* Hash algorithm used for SNI/ALPN binding in session tickets. + * Pick the best available at compile time. */ +#ifndef TICKET_BINDING_HASH_TYPE + #if !defined(NO_SHA256) + #define TICKET_BINDING_HASH_TYPE WC_HASH_TYPE_SHA256 + #define TICKET_BINDING_HASH_SZ WC_SHA256_DIGEST_SIZE + #elif defined(WOLFSSL_SHA384) + #define TICKET_BINDING_HASH_TYPE WC_HASH_TYPE_SHA384 + #define TICKET_BINDING_HASH_SZ WC_SHA384_DIGEST_SIZE + #elif !defined(NO_SHA) + #define TICKET_BINDING_HASH_TYPE WC_HASH_TYPE_SHA + #define TICKET_BINDING_HASH_SZ WC_SHA_DIGEST_SIZE + #else + #error "No hash algorithm available for ticket binding" + #endif +#endif +#endif /* Our ticket format. All members need to be a byte or array of byte to * avoid alignment issues */ @@ -3520,6 +3538,14 @@ typedef struct InternalTicket { #ifdef WOLFSSL_TICKET_HAVE_ID byte id[ID_LEN]; #endif +#ifdef HAVE_SNI + byte sniHash[TICKET_BINDING_HASH_SZ]; /* digest of server name + * at ticket issue */ +#endif +#ifdef HAVE_ALPN + byte alpnHash[TICKET_BINDING_HASH_SZ]; /* digest of negotiated + * ALPN at issue */ +#endif #ifdef OPENSSL_EXTRA byte sessionCtxSz; /* sessionCtx length */ byte sessionCtx[ID_LEN]; /* app specific context id */ @@ -4761,6 +4787,12 @@ struct WOLFSSL_SESSION { byte* ticket; word16 ticketLen; word16 ticketLenAlloc; /* is dynamic */ +#ifdef HAVE_SNI + byte sniHash[TICKET_BINDING_HASH_SZ]; /* SNI at issue */ +#endif +#ifdef HAVE_ALPN + byte alpnHash[TICKET_BINDING_HASH_SZ]; /* ALPN at issue */ +#endif #endif #ifdef SESSION_CERTS From 1778081dcf523501baf05d946d3c014a52ed1d00 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 28 Apr 2026 11:27:26 +0200 Subject: [PATCH 2/4] x509_str: verify leaf signature even when verify_cb overrides INVALID_CA When verify_cb returned WOLFSSL_SUCCESS to suppress X509_V_ERR_INVALID_CA for a non-CA issuer, control skipped X509StoreVerifyCert and the leaf signature was never checked. Drop the else so signature verification runs on every issuer. --- src/x509_str.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/x509_str.c b/src/x509_str.c index b3ec75fe472..7ea0534371b 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -705,7 +705,10 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) /* We found our issuer in the non-trusted cert list, add it * to the CM and verify the current cert against it */ - /* RFC 5280 4.2.1.9: reject non-CA issuer. */ + /* RFC 5280 4.2.1.9: reject non-CA issuer. verify_cb may + * suppress the INVALID_CA error to keep building the chain, + * but the leaf signature must still be verified against the + * issuer below — never skip X509StoreVerifyCert. */ if (!issuer->isCa) { SetupStoreCtxError_ex(ctx, X509_V_ERR_INVALID_CA, (ctx->chain) ? (int)(ctx->chain->num + 1) : 1); @@ -724,26 +727,23 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) goto exit; } } - else { - ret = X509StoreAddCa(ctx->store, issuer, - WOLFSSL_TEMP_CA); - if (ret != WOLFSSL_SUCCESS) { - X509VerifyCertSetupRetry(ctx, certs, failedCerts, - &depth, origDepth); - continue; - } - added = 1; - ret = X509StoreVerifyCert(ctx); - if (ret != WOLFSSL_SUCCESS) { - if ((origDepth - depth) <= 1) - added = 0; - X509VerifyCertSetupRetry(ctx, certs, failedCerts, - &depth, origDepth); - continue; - } - /* Add it to the current chain and look at the issuer cert next */ - wolfSSL_sk_X509_push(ctx->chain, ctx->current_cert); + ret = X509StoreAddCa(ctx->store, issuer, WOLFSSL_TEMP_CA); + if (ret != WOLFSSL_SUCCESS) { + X509VerifyCertSetupRetry(ctx, certs, failedCerts, + &depth, origDepth); + continue; } + added = 1; + ret = X509StoreVerifyCert(ctx); + if (ret != WOLFSSL_SUCCESS) { + if ((origDepth - depth) <= 1) + added = 0; + X509VerifyCertSetupRetry(ctx, certs, failedCerts, + &depth, origDepth); + continue; + } + /* Add it to the current chain and look at the issuer cert next */ + wolfSSL_sk_X509_push(ctx->chain, ctx->current_cert); ctx->current_cert = issuer; } else if (ret == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { From 66074e24b4c608daccc04ad74c27d11444a149d5 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 28 Apr 2026 17:43:05 +0200 Subject: [PATCH 3/4] x509_str: align WOLFSSL_X509_V_ERR_INVALID_CA with OpenSSL value WOLFSSL_X509_V_ERR_INVALID_CA was 24 while X509_V_ERR_INVALID_CA from the OpenSSL-compat header is 79. In OPENSSL_COEXIST builds the literal X509_V_ERR_INVALID_CA macro is suppressed to avoid clashing with real OpenSSL, so referencing it from src/x509_str.c failed to compile. Move WOLFSSL_X509_V_ERR_INVALID_CA to 79 so the wolfSSL native code matches the OpenSSL value, bump WC_OSSL_V509_V_ERR_MAX to 80, and use the WOLFSSL_-prefixed constant in wolfSSL_X509_verify_cert. Extend error_test()'s missing-value table to cover the new gaps (24 and 65-78). Also skip test_tls13_ticket_peer_cert_reverify under WOLFSSL_NO_DEF_TICKET_ENC_CB, since without a ticket encryption callback the server never emits a NewSessionTicket and the test's resumption step cannot run. --- src/x509_str.c | 2 +- tests/api.c | 2 ++ tests/api/test_tls13.c | 2 +- wolfssl/ssl.h | 8 ++++---- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/x509_str.c b/src/x509_str.c index 7ea0534371b..5450080df80 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -710,7 +710,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) * but the leaf signature must still be verified against the * issuer below — never skip X509StoreVerifyCert. */ if (!issuer->isCa) { - SetupStoreCtxError_ex(ctx, X509_V_ERR_INVALID_CA, + SetupStoreCtxError_ex(ctx, WOLFSSL_X509_V_ERR_INVALID_CA, (ctx->chain) ? (int)(ctx->chain->num + 1) : 1); #if defined(OPENSSL_ALL) || defined(WOLFSSL_QT) if (ctx->store->verify_cb) { diff --git a/tests/api.c b/tests/api.c index 05a7688d7ff..740b2da2d1f 100644 --- a/tests/api.c +++ b/tests/api.c @@ -28238,9 +28238,11 @@ static int error_test(void) {11, 11}, {17, 15}, {19, 19}, + {24, 24}, {27, 26 }, {61, 30}, {63, 63}, + {78, 65}, #endif { -9, WC_SPAN1_FIRST_E + 1 }, { -300, -300 }, diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index b32565abb7b..400286d6604 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -5276,7 +5276,7 @@ int test_tls13_ticket_peer_cert_reverify(void) defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \ !defined(NO_CERT_IN_TICKET) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_RSA) + !defined(NO_RSA) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) struct test_memio_ctx test_ctx; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 236515157b4..dba3afa315d 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2684,13 +2684,13 @@ enum { WOLFSSL_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = 21, WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG = 22, WOLFSSL_X509_V_ERR_CERT_REVOKED = 23, - WOLFSSL_X509_V_ERR_INVALID_CA = 24, WOLFSSL_X509_V_ERR_PATH_LENGTH_EXCEEDED = 25, WOLFSSL_X509_V_ERR_CERT_REJECTED = 28, WOLFSSL_X509_V_ERR_SUBJECT_ISSUER_MISMATCH = 29, - WOLFSSL_X509_V_ERR_HOSTNAME_MISMATCH = 62, - WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH = 64, - WC_OSSL_V509_V_ERR_MAX = 65, + WOLFSSL_X509_V_ERR_HOSTNAME_MISMATCH = 62, + WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH = 64, + WOLFSSL_X509_V_ERR_INVALID_CA = 79, + WC_OSSL_V509_V_ERR_MAX = 80, #ifdef HAVE_OCSP /* OCSP Flags */ From db14bc7e40935f75a07489818225a652d7ac7549 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 29 Apr 2026 10:01:08 +0000 Subject: [PATCH 4/4] src/internal.c: defer ticket SNI/ALPN binding check until after parsing The early checks in DoClientTicketCheck and DoClientTicket ran before the corresponding extensions were parsed, so the computed current hash was zero while the ticket's stored hash was non-zero, rejecting valid resumptions in the nginx, haproxy, grpc and CPython integration tests. * TLS 1.3: DoTls13ClientHello processes pre_shared_key before ALPN_Select, so TLSX_ALPN_GetRequest returned WOLFSSL_ALPN_NOT_FOUND. * TLS 1.2: ClientHello extensions are parsed in wire order; clients that put SessionTicket before server_name / ALPN hit the same problem with both bindings. Consolidate the verification into a single VerifyTicketBinding() function, called once on the server after ALPN_Select (in both DoTls13ClientHello and DoClientHello). DoClientTicketFinalize copies the ticket's stored bindings onto ssl->session so the deferred check has the values to compare. The early per-call sites are removed. VerifyTicketBinding returns WOLFSSL_FATAL_ERROR on mismatch; the caller currently aborts the handshake. Behaviour on mismatch (error vs fallback to a fresh handshake) can be revisited from this single point. --- src/internal.c | 102 ++++++++++++++++++++++----------------------- src/tls13.c | 4 ++ wolfssl/internal.h | 3 ++ 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/internal.c b/src/internal.c index 778ea5fac9e..ff0757e9cb4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -38468,6 +38468,11 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if((ret=ALPN_Select(ssl))) goto out; #endif + #if defined(HAVE_SESSION_TICKET) && \ + (defined(HAVE_SNI) || defined(HAVE_ALPN)) + if((ret=VerifyTicketBinding(ssl))) + goto out; + #endif i += totalExtSz; #else @@ -39250,8 +39255,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #ifdef HAVE_SNI - /* Hash the server-selected SNI into dst (TICKET_BINDING_HASH_SZ bytes). - * Zeros dst when no SNI is present. */ + /* Hash server-selected SNI; zeros dst when none. */ static int TicketSniHash(WOLFSSL* ssl, byte* dst) { char* name = NULL; @@ -39271,8 +39275,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif #ifdef HAVE_ALPN - /* Hash the negotiated ALPN protocol into dst (TICKET_BINDING_HASH_SZ - * bytes). Zeros dst when no ALPN was negotiated. */ + /* Hash negotiated ALPN; zeros dst when none. */ static int TicketAlpnHash(WOLFSSL* ssl, byte* dst) { char* proto = NULL; @@ -39290,6 +39293,38 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif +#if defined(HAVE_SNI) || defined(HAVE_ALPN) + /* Server-side: verify the SNI/ALPN bindings carried on a resumed + * session match what was negotiated for the current connection. + * Must be called after extension parsing and ALPN_Select. + * Returns 0 on match, WOLFSSL_FATAL_ERROR on mismatch. */ + int VerifyTicketBinding(WOLFSSL* ssl) + { + byte curHash[TICKET_BINDING_HASH_SZ]; + + if (!ssl->options.resuming || !ssl->options.useTicket) + return 0; + +#ifdef HAVE_SNI + if (TicketSniHash(ssl, curHash) != 0 || + XMEMCMP(curHash, ssl->session->sniHash, + TICKET_BINDING_HASH_SZ) != 0) { + WOLFSSL_MSG("Ticket SNI mismatch"); + return WOLFSSL_FATAL_ERROR; + } +#endif +#ifdef HAVE_ALPN + if (TicketAlpnHash(ssl, curHash) != 0 || + XMEMCMP(curHash, ssl->session->alpnHash, + TICKET_BINDING_HASH_SZ) != 0) { + WOLFSSL_MSG("Ticket ALPN mismatch"); + return WOLFSSL_FATAL_ERROR; + } +#endif + return 0; + } +#endif + /* create a new session ticket, 0 on success * Do any kind of setup in SetupTicket */ int CreateTicket(WOLFSSL* ssl) @@ -39755,28 +39790,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ssl->sessionCtxSz) != 0)) return WOLFSSL_FATAL_ERROR; #endif -#ifdef HAVE_SNI - { - byte curHash[TICKET_BINDING_HASH_SZ]; - if (TicketSniHash((WOLFSSL*)ssl, curHash) != 0 || - XMEMCMP(curHash, psk->it->sniHash, - TICKET_BINDING_HASH_SZ) != 0) { - WOLFSSL_MSG("Ticket SNI mismatch"); - return WOLFSSL_FATAL_ERROR; - } - } -#endif -#ifdef HAVE_ALPN - { - byte curHash[TICKET_BINDING_HASH_SZ]; - if (TicketAlpnHash((WOLFSSL*)ssl, curHash) != 0 || - XMEMCMP(curHash, psk->it->alpnHash, - TICKET_BINDING_HASH_SZ) != 0) { - WOLFSSL_MSG("Ticket ALPN mismatch"); - return WOLFSSL_FATAL_ERROR; - } - } -#endif + /* SNI/ALPN binding is verified after ALPN_Select via + * VerifyTicketBinding(). */ return 0; } #endif /* WOLFSSL_SLT13 */ @@ -39872,6 +39887,14 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } } #endif + /* Carry the ticket bindings on the session for the deferred + * VerifyTicketBinding() check. */ +#ifdef HAVE_SNI + XMEMCPY(ssl->session->sniHash, it->sniHash, TICKET_BINDING_HASH_SZ); +#endif +#ifdef HAVE_ALPN + XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ); +#endif if (!IsAtLeastTLSv1_3(ssl->version)) { if (ssl->arrays == NULL) @@ -40231,31 +40254,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) goto cleanup; } -#ifdef HAVE_SNI - { - byte curHash[TICKET_BINDING_HASH_SZ]; - if (TicketSniHash(ssl, curHash) != 0 || - XMEMCMP(curHash, it->sniHash, - TICKET_BINDING_HASH_SZ) != 0) { - WOLFSSL_MSG("Ticket SNI mismatch"); - decryptRet = WOLFSSL_TICKET_RET_REJECT; - goto cleanup; - } - } -#endif -#ifdef HAVE_ALPN - { - byte curHash[TICKET_BINDING_HASH_SZ]; - if (TicketAlpnHash(ssl, curHash) != 0 || - XMEMCMP(curHash, it->alpnHash, - TICKET_BINDING_HASH_SZ) != 0) { - WOLFSSL_MSG("Ticket ALPN mismatch"); - decryptRet = WOLFSSL_TICKET_RET_REJECT; - goto cleanup; - } - } -#endif - + /* SNI/ALPN binding is verified after ALPN_Select via + * VerifyTicketBinding(). */ DoClientTicketFinalize(ssl, it, NULL); cleanup: diff --git a/src/tls13.c b/src/tls13.c index fba9a05cadc..2091a2563d7 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -7555,6 +7555,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, * select the ALPN protocol, if so requested */ if ((ret = ALPN_Select(ssl)) != 0) goto exit_dch; +#endif +#if defined(HAVE_SESSION_TICKET) && (defined(HAVE_SNI) || defined(HAVE_ALPN)) + if ((ret = VerifyTicketBinding(ssl)) != 0) + goto exit_dch; #endif } /* case TLS_ASYNC_BEGIN */ FALL_THROUGH; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index af0c852bc96..0572d35648d 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -6790,6 +6790,9 @@ WOLFSSL_LOCAL int DoClientTicket_ex(const WOLFSSL* ssl, PreSharedKey* psk, #endif WOLFSSL_LOCAL int DoClientTicket(WOLFSSL* ssl, const byte* input, word32 len); +#if defined(HAVE_SNI) || defined(HAVE_ALPN) +WOLFSSL_LOCAL int VerifyTicketBinding(WOLFSSL* ssl); +#endif #endif /* HAVE_SESSION_TICKET */ WOLFSSL_LOCAL int SendData(WOLFSSL* ssl, const void* data, size_t sz); #ifdef WOLFSSL_THREADED_CRYPT