Skip to content

Commit 545376c

Browse files
authored
Merge pull request #10279 from julek-wolfssl/zd/21661
zd/21661: harden X.509 chain validation, session ticket identity binding, and peer cert restore
2 parents 27413e0 + 061311d commit 545376c

15 files changed

Lines changed: 405 additions & 85 deletions

File tree

examples/client/client.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ static int quieter = 0; /* Print fewer messages. This is helpful with overly
155155
#ifdef HAVE_SESSION_TICKET
156156

157157
#ifndef SESSION_TICKET_LEN
158-
#define SESSION_TICKET_LEN 256
158+
#define SESSION_TICKET_LEN 2048
159159
#endif
160160
static int sessionTicketCB(WOLFSSL* ssl,
161161
const unsigned char* ticket, int ticketSz,

src/internal.c

Lines changed: 148 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38542,6 +38542,11 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3854238542
if((ret=ALPN_Select(ssl)))
3854338543
goto out;
3854438544
#endif
38545+
#if defined(HAVE_SESSION_TICKET) && \
38546+
(defined(HAVE_SNI) || defined(HAVE_ALPN))
38547+
if((ret=VerifyTicketBinding(ssl)))
38548+
goto out;
38549+
#endif
3854538550

3854638551
i += totalExtSz;
3854738552
#else
@@ -39323,6 +39328,77 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3932339328
return ret;
3932439329
}
3932539330

39331+
#ifdef HAVE_SNI
39332+
/* Hash server-selected SNI; zeros dst when none. */
39333+
static int TicketSniHash(WOLFSSL* ssl, byte* dst)
39334+
{
39335+
char* name = NULL;
39336+
word16 nameLen;
39337+
39338+
nameLen = TLSX_SNI_GetRequest(ssl->extensions,
39339+
WOLFSSL_SNI_HOST_NAME,
39340+
(void**)&name, 0);
39341+
if (name != NULL && nameLen > 0) {
39342+
return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)name,
39343+
nameLen, dst, TICKET_BINDING_HASH_SZ);
39344+
}
39345+
39346+
XMEMSET(dst, 0, TICKET_BINDING_HASH_SZ);
39347+
return 0;
39348+
}
39349+
#endif
39350+
39351+
#ifdef HAVE_ALPN
39352+
/* Hash negotiated ALPN; zeros dst when none. */
39353+
static int TicketAlpnHash(WOLFSSL* ssl, byte* dst)
39354+
{
39355+
char* proto = NULL;
39356+
word16 protoLen = 0;
39357+
39358+
if (TLSX_ALPN_GetRequest(ssl->extensions, (void**)&proto,
39359+
&protoLen) == WOLFSSL_SUCCESS &&
39360+
proto != NULL && protoLen > 0) {
39361+
return wc_Hash(TICKET_BINDING_HASH_TYPE, (const byte*)proto,
39362+
protoLen, dst, TICKET_BINDING_HASH_SZ);
39363+
}
39364+
39365+
XMEMSET(dst, 0, TICKET_BINDING_HASH_SZ);
39366+
return 0;
39367+
}
39368+
#endif
39369+
39370+
#if defined(HAVE_SNI) || defined(HAVE_ALPN)
39371+
/* Server-side: verify the SNI/ALPN bindings carried on a resumed
39372+
* session match what was negotiated for the current connection.
39373+
* Must be called after extension parsing and ALPN_Select.
39374+
* Returns 0 on match, WOLFSSL_FATAL_ERROR on mismatch. */
39375+
int VerifyTicketBinding(WOLFSSL* ssl)
39376+
{
39377+
byte curHash[TICKET_BINDING_HASH_SZ];
39378+
39379+
if (!ssl->options.resuming || !ssl->options.useTicket)
39380+
return 0;
39381+
39382+
#ifdef HAVE_SNI
39383+
if (TicketSniHash(ssl, curHash) != 0 ||
39384+
XMEMCMP(curHash, ssl->session->sniHash,
39385+
TICKET_BINDING_HASH_SZ) != 0) {
39386+
WOLFSSL_MSG("Ticket SNI mismatch");
39387+
return WOLFSSL_FATAL_ERROR;
39388+
}
39389+
#endif
39390+
#ifdef HAVE_ALPN
39391+
if (TicketAlpnHash(ssl, curHash) != 0 ||
39392+
XMEMCMP(curHash, ssl->session->alpnHash,
39393+
TICKET_BINDING_HASH_SZ) != 0) {
39394+
WOLFSSL_MSG("Ticket ALPN mismatch");
39395+
return WOLFSSL_FATAL_ERROR;
39396+
}
39397+
#endif
39398+
return 0;
39399+
}
39400+
#endif
39401+
3932639402
/* create a new session ticket, 0 on success
3932739403
* Do any kind of setup in SetupTicket */
3932839404
int CreateTicket(WOLFSSL* ssl)
@@ -39421,6 +39497,18 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3942139497
it->sessionCtxSz = ssl->sessionCtxSz;
3942239498
XMEMCPY(it->sessionCtx, ssl->sessionCtx, ID_LEN);
3942339499
#endif
39500+
#ifdef HAVE_SNI
39501+
ret = TicketSniHash(ssl, it->sniHash);
39502+
if (ret != 0)
39503+
goto error;
39504+
XMEMCPY(ssl->session->sniHash, it->sniHash, TICKET_BINDING_HASH_SZ);
39505+
#endif
39506+
#ifdef HAVE_ALPN
39507+
ret = TicketAlpnHash(ssl, it->alpnHash);
39508+
if (ret != 0)
39509+
goto error;
39510+
XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ);
39511+
#endif
3942439512

3942539513
#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \
3942639514
!defined(NO_CERT_IN_TICKET)
@@ -39776,6 +39864,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3977639864
ssl->sessionCtxSz) != 0))
3977739865
return WOLFSSL_FATAL_ERROR;
3977839866
#endif
39867+
/* SNI/ALPN binding is verified after ALPN_Select via
39868+
* VerifyTicketBinding(). */
3977939869
return 0;
3978039870
}
3978139871
#endif /* WOLFSSL_SLT13 */
@@ -39787,36 +39877,54 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3978739877
word16 peerCertLen = 0;
3978839878
ato16(it->peerCertLen, &peerCertLen);
3978939879

39790-
if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) {
39880+
/* Clear any peer cert state that may have been copied from the session
39881+
* cache by wolfSSL_DupSession before we got here. */
39882+
FreeX509(&ssl->peerCert);
39883+
InitX509(&ssl->peerCert, 0, ssl->heap);
3979139884
#ifdef SESSION_CERTS
39792-
/* Clear existing chain and add the peer certificate */
39793-
ssl->session->chain.count = 0;
39794-
AddSessionCertToChain(&ssl->session->chain,
39795-
it->peerCert, peerCertLen);
39885+
ssl->session->chain.count = 0;
3979639886
#endif
39797-
/* Also decode into ssl->peerCert for direct access */
39798-
{
39799-
int ret;
39800-
DecodedCert* dCert;
39801-
39802-
dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap,
39803-
DYNAMIC_TYPE_DCERT);
39804-
if (dCert != NULL) {
39805-
InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap);
39806-
ret = ParseCertRelative(dCert, CERT_TYPE, 0, NULL, NULL);
39807-
if (ret == 0) {
39887+
39888+
if (peerCertLen > 0 && peerCertLen <= MAX_TICKET_PEER_CERT_SZ) {
39889+
int ret;
39890+
DecodedCert* dCert;
39891+
39892+
dCert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap,
39893+
DYNAMIC_TYPE_DCERT);
39894+
if (dCert != NULL) {
39895+
int verify = ssl->options.verifyPeer ? VERIFY : NO_VERIFY;
39896+
InitDecodedCert(dCert, it->peerCert, peerCertLen, ssl->heap);
39897+
/* Re-verify against the current trust store so that CA
39898+
* removal since ticket issue is enforced. */
39899+
ret = ParseCertRelative(dCert, CERT_TYPE, verify,
39900+
SSL_CM(ssl), NULL);
39901+
#ifdef HAVE_OCSP
39902+
/* ParseCertRelative does not check revocation status.
39903+
* Run OCSP if the CertManager has it enabled. */
39904+
if (ret == 0 && SSL_CM(ssl)->ocspEnabled) {
39905+
ret = CheckCertOCSP_ex(SSL_CM(ssl)->ocsp, dCert, ssl);
39906+
}
39907+
#endif
39908+
#ifdef HAVE_CRL
39909+
if (ret == 0 && SSL_CM(ssl)->crlEnabled) {
39910+
ret = CheckCertCRL(SSL_CM(ssl)->crl, dCert);
39911+
}
39912+
#endif
39913+
if (ret == 0) {
39914+
#ifdef SESSION_CERTS
39915+
AddSessionCertToChain(&ssl->session->chain,
39916+
it->peerCert, peerCertLen);
39917+
#endif
39918+
FreeX509(&ssl->peerCert);
39919+
InitX509(&ssl->peerCert, 0, ssl->heap);
39920+
ret = CopyDecodedToX509(&ssl->peerCert, dCert);
39921+
if (ret != 0) {
3980839922
FreeX509(&ssl->peerCert);
3980939923
InitX509(&ssl->peerCert, 0, ssl->heap);
39810-
ret = CopyDecodedToX509(&ssl->peerCert, dCert);
39811-
if (ret != 0) {
39812-
/* Failed to copy - clear peerCert */
39813-
FreeX509(&ssl->peerCert);
39814-
InitX509(&ssl->peerCert, 0, ssl->heap);
39815-
}
3981639924
}
39817-
FreeDecodedCert(dCert);
39818-
XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT);
3981939925
}
39926+
FreeDecodedCert(dCert);
39927+
XFREE(dCert, ssl->heap, DYNAMIC_TYPE_DCERT);
3982039928
}
3982139929
}
3982239930
}
@@ -39853,6 +39961,14 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3985339961
}
3985439962
}
3985539963
#endif
39964+
/* Carry the ticket bindings on the session for the deferred
39965+
* VerifyTicketBinding() check. */
39966+
#ifdef HAVE_SNI
39967+
XMEMCPY(ssl->session->sniHash, it->sniHash, TICKET_BINDING_HASH_SZ);
39968+
#endif
39969+
#ifdef HAVE_ALPN
39970+
XMEMCPY(ssl->session->alpnHash, it->alpnHash, TICKET_BINDING_HASH_SZ);
39971+
#endif
3985639972

3985739973
if (!IsAtLeastTLSv1_3(ssl->version)) {
3985839974
if (ssl->arrays == NULL)
@@ -39962,6 +40078,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3996240078
it->sessionCtxSz = sess->sessionCtxSz;
3996340079
XMEMCPY(it->sessionCtx, sess->sessionCtx, sess->sessionCtxSz);
3996440080
#endif
40081+
#ifdef HAVE_SNI
40082+
XMEMCPY(it->sniHash, sess->sniHash, TICKET_BINDING_HASH_SZ);
40083+
#endif
40084+
#ifdef HAVE_ALPN
40085+
XMEMCPY(it->alpnHash, sess->alpnHash, TICKET_BINDING_HASH_SZ);
40086+
#endif
3996540087
#if defined(OPENSSL_ALL) && defined(KEEP_PEER_CERT) && \
3996640088
defined(SESSION_CERTS) && !defined(NO_CERT_IN_TICKET)
3996740089
/* Store peer certificate from session chain */
@@ -40206,6 +40328,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
4020640328
goto cleanup;
4020740329
}
4020840330

40331+
/* SNI/ALPN binding is verified after ALPN_Select via
40332+
* VerifyTicketBinding(). */
4020940333
DoClientTicketFinalize(ssl, it, NULL);
4021040334

4021140335
cleanup:

src/ssl_sess.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,6 +3069,7 @@ WOLFSSL_SESSION* wolfSSL_d2i_SSL_SESSION(WOLFSSL_SESSION** sess,
30693069
(void)idx;
30703070

30713071
if (sess != NULL) {
3072+
wolfSSL_FreeSession(NULL, *sess);
30723073
*sess = s;
30733074
}
30743075

src/tls13.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7632,6 +7632,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
76327632
* select the ALPN protocol, if so requested */
76337633
if ((ret = ALPN_Select(ssl)) != 0)
76347634
goto exit_dch;
7635+
#endif
7636+
#if defined(HAVE_SESSION_TICKET) && (defined(HAVE_SNI) || defined(HAVE_ALPN))
7637+
if ((ret = VerifyTicketBinding(ssl)) != 0)
7638+
goto exit_dch;
76357639
#endif
76367640
} /* case TLS_ASYNC_BEGIN */
76377641
FALL_THROUGH;

src/x509_str.c

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -706,23 +706,13 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
706706
/* We found our issuer in the non-trusted cert list, add it
707707
* to the CM and verify the current cert against it */
708708
#ifndef WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE
709-
/* RFC 5280 6.1.3(k): a non-self-issued intermediate must have
710-
* basicConstraints CA:TRUE to be used as a signing authority.
711-
* Reject CA:FALSE intermediates here; the verify_cb (if any)
712-
* may override. Define WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE
713-
* to restore the legacy permissive behavior.
714-
*/
709+
/* RFC 5280 4.2.1.9: reject non-CA issuer. verify_cb may
710+
* suppress the INVALID_CA error to keep building the chain,
711+
* but the leaf signature must still be verified against the
712+
* issuer below - never skip X509StoreVerifyCert. */
715713
if (!issuer->isCa) {
716-
/* error depth is current depth + 1. The compat alias
717-
* X509_V_ERR_INVALID_CA (= 79) lives in wolfssl/openssl/x509.h
718-
* which is not always pulled into this translation unit
719-
* (e.g. some linuxkm build chains). Define a local fallback
720-
* so callers reading X509_STORE_CTX_get_error() see the
721-
* OpenSSL-compatible value. */
722-
#ifndef X509_V_ERR_INVALID_CA
723-
#define X509_V_ERR_INVALID_CA 79
724-
#endif
725-
SetupStoreCtxError_ex(ctx, X509_V_ERR_INVALID_CA,
714+
/* error depth is current depth + 1 */
715+
SetupStoreCtxError_ex(ctx, WOLFSSL_X509_V_ERR_INVALID_CA,
726716
(ctx->chain) ? (int)(ctx->chain->num + 1) : 1);
727717
#if defined(OPENSSL_ALL) || defined(WOLFSSL_QT)
728718
if (ctx->store->verify_cb) {
@@ -738,28 +728,25 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
738728
ret = WOLFSSL_FAILURE;
739729
goto exit;
740730
}
741-
} else
731+
}
742732
#endif
743-
{
744-
ret = X509StoreAddCa(ctx->store, issuer,
745-
WOLFSSL_TEMP_CA);
746-
if (ret != WOLFSSL_SUCCESS) {
747-
X509VerifyCertSetupRetry(ctx, certs, failedCerts,
748-
&depth, origDepth);
749-
continue;
750-
}
751-
added = 1;
752-
ret = X509StoreVerifyCert(ctx);
753-
if (ret != WOLFSSL_SUCCESS) {
754-
if ((origDepth - depth) <= 1)
755-
added = 0;
756-
X509VerifyCertSetupRetry(ctx, certs, failedCerts,
757-
&depth, origDepth);
758-
continue;
759-
}
760-
/* Add it to the current chain and look at the issuer cert next */
761-
wolfSSL_sk_X509_push(ctx->chain, ctx->current_cert);
733+
ret = X509StoreAddCa(ctx->store, issuer, WOLFSSL_TEMP_CA);
734+
if (ret != WOLFSSL_SUCCESS) {
735+
X509VerifyCertSetupRetry(ctx, certs, failedCerts,
736+
&depth, origDepth);
737+
continue;
762738
}
739+
added = 1;
740+
ret = X509StoreVerifyCert(ctx);
741+
if (ret != WOLFSSL_SUCCESS) {
742+
if ((origDepth - depth) <= 1)
743+
added = 0;
744+
X509VerifyCertSetupRetry(ctx, certs, failedCerts,
745+
&depth, origDepth);
746+
continue;
747+
}
748+
/* Add it to the current chain and look at the issuer cert next */
749+
wolfSSL_sk_X509_push(ctx->chain, ctx->current_cert);
763750
ctx->current_cert = issuer;
764751
}
765752
else if (ret == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) {

tests/api.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30232,9 +30232,11 @@ static int error_test(void)
3023230232
{11, 11},
3023330233
{17, 15},
3023430234
{19, 19},
30235+
{24, 24},
3023530236
{27, 26 },
3023630237
{61, 30},
3023730238
{63, 63},
30239+
{78, 65},
3023830240
#endif
3023930241
{ -9, WC_SPAN1_FIRST_E + 1 },
3024030242
{ -300, -300 },

tests/api/test_asn.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ int test_DecodeAltNames_length_underflow(void)
969969
0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00,
970970
/* SAN extension: correct SEQUENCE length 0x06 */
971971
0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x08, 0x30, 0x06, 0x82,
972-
0x04, 0x61, 0x2a, 0x00, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
972+
0x04, 0x61, 0x2a, 0x62, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
973973
0x04, 0x16, 0x04, 0x14, 0x92, 0x6a, 0x1e, 0x52, 0x3a, 0x1a, 0x57, 0x9f,
974974
0xc9, 0x82, 0x9a, 0xce, 0xc8, 0xc0, 0xa9, 0x51, 0x9d, 0x2f, 0xc7, 0x72,
975975
0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
@@ -1025,6 +1025,16 @@ int test_DecodeAltNames_length_underflow(void)
10251025
WC_NO_ERR_TRACE(ASN_PARSE_E));
10261026
wc_FreeDecodedCert(&cert);
10271027

1028+
/* NUL in dNSName SAN must be rejected per RFC 5280 4.2.1.6. */
1029+
XMEMCPY(bad_san_cert, good_san_cert, sizeof(good_san_cert));
1030+
bad_san_cert[SAN_SEQ_LEN_OFFSET + 5] = 0x00;
1031+
1032+
wc_InitDecodedCert(&cert, bad_san_cert, (word32)sizeof(bad_san_cert),
1033+
NULL);
1034+
ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL),
1035+
WC_NO_ERR_TRACE(ASN_PARSE_E));
1036+
wc_FreeDecodedCert(&cert);
1037+
10281038
#endif /* !NO_CERTS && !NO_RSA && !NO_ASN */
10291039
return EXPECT_RESULT();
10301040
}

tests/api/test_ossl_x509.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ int test_wolfSSL_X509_bad_altname(void)
11441144
0xf5, 0xe5, 0x09, 0x02, 0x01, 0x03, 0xa3, 0x61, 0x30, 0x5f, 0x30, 0x0c,
11451145
0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00,
11461146
0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x08, 0x30, 0x06, 0x82,
1147-
0x04, 0x61, 0x2a, 0x00, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
1147+
0x04, 0x61, 0x2a, 0x62, 0x2a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
11481148
0x04, 0x16, 0x04, 0x14, 0x92, 0x6a, 0x1e, 0x52, 0x3a, 0x1a, 0x57, 0x9f,
11491149
0xc9, 0x82, 0x9a, 0xce, 0xc8, 0xc0, 0xa9, 0x51, 0x9d, 0x2f, 0xc7, 0x72,
11501150
0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
@@ -1183,8 +1183,7 @@ int test_wolfSSL_X509_bad_altname(void)
11831183
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_buffer(
11841184
malformed_alt_name_cert, certSize, SSL_FILETYPE_ASN1));
11851185

1186-
/* malformed_alt_name_cert has a malformed alternative
1187-
* name of "a*\0*". Ensure that it does not match "aaaaa" */
1186+
/* SAN "a*b*" must not match "aaaaa" under any wildcard flag. */
11881187
ExpectIntNE(wolfSSL_X509_check_host(x509, name, nameLen,
11891188
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), 1);
11901189

0 commit comments

Comments
 (0)