mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 13:20:52 +02:00
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:
@@ -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
@@ -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:
|
||||
|
||||
@@ -3069,6 +3069,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess,
|
||||
(void)idx;
|
||||
|
||||
if (sess != NULL) {
|
||||
wolfSSL_FreeSession(NULL, *sess);
|
||||
*sess = s;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)) {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user