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;
  verify leaf signature even when verify_cb overrides INVALID_CA
- x509_str: align WOLFSSL_X509_V_ERR_INVALID_CA with OpenSSL value (79)
  so OPENSSL_COEXIST builds compile; bump WC_OSSL_V509_V_ERR_MAX to 80
  and extend error_test() missing-value table for the new gaps
- 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
- 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
- ticket: defer SNI/ALPN binding check until after extensions are parsed
  by consolidating into VerifyTicketBinding(), called once after
  ALPN_Select in DoTls13ClientHello and DoClientHello; the early
  per-call sites ran before extensions were parsed and rejected valid
  resumptions in nginx, haproxy, grpc, and CPython integration tests
- ssl_sess: free previous session in wolfSSL_d2i_SSL_SESSION before
  overwrite
- examples/client: increase SESSION_TICKET_LEN fallback from 256 to 2048
  to support larger tickets
- tests: update SAN NUL fixtures and add parse-time rejection coverage;
  add test_tls13_ticket_peer_cert_reverify for CA-removal scenario; skip
  it under WOLFSSL_NO_DEF_TICKET_ENC_CB
This commit is contained in:
Juliusz Sosinowicz
2026-05-06 13:26:20 +02:00
parent 50da0c0a26
commit 061311d6ca
15 changed files with 405 additions and 85 deletions
+1 -1
View File
@@ -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,
+147 -23
View File
@@ -38542,6 +38542,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
@@ -39323,6 +39328,77 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
return ret;
}
#ifdef HAVE_SNI
/* Hash server-selected SNI; zeros dst when none. */
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 negotiated ALPN; zeros dst when none. */
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
#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)
@@ -39421,6 +39497,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)
@@ -39776,6 +39864,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
ssl->sessionCtxSz) != 0))
return WOLFSSL_FATAL_ERROR;
#endif
/* SNI/ALPN binding is verified after ALPN_Select via
* VerifyTicketBinding(). */
return 0;
}
#endif /* WOLFSSL_SLT13 */
@@ -39787,36 +39877,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);
}
}
}
@@ -39853,6 +39961,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)
@@ -39962,6 +40078,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 */
@@ -40206,6 +40328,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
goto cleanup;
}
/* SNI/ALPN binding is verified after ALPN_Select via
* VerifyTicketBinding(). */
DoClientTicketFinalize(ssl, it, NULL);
cleanup:
+1
View File
@@ -3069,6 +3069,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess,
(void)idx;
if (sess != NULL) {
wolfSSL_FreeSession(NULL, *sess);
*sess = s;
}
+4
View File
@@ -7632,6 +7632,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;
+24 -37
View File
@@ -706,23 +706,13 @@ 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 */
#ifndef WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE
/* RFC 5280 6.1.3(k): a non-self-issued intermediate must have
* basicConstraints CA:TRUE to be used as a signing authority.
* Reject CA:FALSE intermediates here; the verify_cb (if any)
* may override. Define WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE
* to restore the legacy permissive behavior.
*/
/* 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) {
/* error depth is current depth + 1. The compat alias
* X509_V_ERR_INVALID_CA (= 79) lives in wolfssl/openssl/x509.h
* which is not always pulled into this translation unit
* (e.g. some linuxkm build chains). Define a local fallback
* so callers reading X509_STORE_CTX_get_error() see the
* OpenSSL-compatible value. */
#ifndef X509_V_ERR_INVALID_CA
#define X509_V_ERR_INVALID_CA 79
#endif
SetupStoreCtxError_ex(ctx, X509_V_ERR_INVALID_CA,
/* error depth is current depth + 1 */
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) {
@@ -738,28 +728,25 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
ret = WOLFSSL_FAILURE;
goto exit;
}
} else
#endif
{
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);
}
#endif
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)) {
+2
View File
@@ -30232,9 +30232,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 },
+11 -1
View File
@@ -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();
}
+2 -3
View File
@@ -1144,7 +1144,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,
@@ -1183,8 +1183,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);
+115
View File
@@ -5222,6 +5222,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;
@@ -5732,3 +5737,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) && !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;
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();
}
+3 -1
View File
@@ -66,6 +66,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), \
@@ -109,6 +110,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 */
-15
View File
@@ -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
+25
View File
@@ -18338,6 +18338,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)
{
@@ -18346,6 +18359,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) {
@@ -18373,6 +18390,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) {
@@ -18381,6 +18402,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)
+31
View File
@@ -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"
+35
View File
@@ -3508,6 +3508,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 */
@@ -3530,6 +3548,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 */
@@ -4771,6 +4797,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
@@ -6777,6 +6809,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
+4 -4
View File
@@ -2701,13 +2701,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 */