Skip to content
Open
48 changes: 36 additions & 12 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -25169,9 +25169,9 @@ int CreateOcspResponse(WOLFSSL* ssl, OcspRequest** ocspRequest,
ret = CheckOcspRequest(SSL_CM(ssl)->ocsp_stapling, request, response,
ssl->heap);

/* Suppressing, not critical */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_REVOKED) ||
ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
/* Suppressing soft-fail responder errors. OCSP_CERT_REVOKED is an
* explicit positive assertion of revocation and must not be ignored. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret = 0;
}
Expand Down Expand Up @@ -26028,9 +26028,11 @@ int SendCertificateStatus(WOLFSSL* ssl)
ret = CheckOcspRequest(SSL_CM(ssl)->ocsp_stapling,
request, &responses[i + 1], ssl->heap);

/* Suppressing, not critical */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_REVOKED) ||
ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
/* Suppressing soft-fail responder errors.
* OCSP_CERT_REVOKED is an explicit positive
* assertion of revocation and must not be
* ignored. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret = 0;
}
Expand Down Expand Up @@ -26058,9 +26060,10 @@ int SendCertificateStatus(WOLFSSL* ssl)
ret = CheckOcspRequest(SSL_CM(ssl)->ocsp_stapling,
request, &responses[++i], ssl->heap);

/* Suppressing, not critical */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_REVOKED) ||
ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
/* Suppressing soft-fail responder errors.
* OCSP_CERT_REVOKED is an explicit positive assertion of
* revocation and must not be ignored. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret = 0;
}
Expand Down Expand Up @@ -39618,11 +39621,27 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
static int DoClientTicketCheckVersion(const WOLFSSL* ssl,
InternalTicket* it)
{
if (ssl->version.minor < it->pv.minor) {
/* DTLS minor versions decrease as the protocol version increases
* (DTLS 1.0=0xFF, DTLS 1.2=0xFD, DTLS 1.3=0xFC), so the version
* comparisons are inverted relative to TLS. */
byte greaterVersion;
byte lesserVersion;
byte belowMinDowngrade;
Comment on lines +39624 to +39629

if (ssl->options.dtls) {
greaterVersion = ssl->version.minor > it->pv.minor;
lesserVersion = ssl->version.minor < it->pv.minor;
}
else {
greaterVersion = ssl->version.minor < it->pv.minor;
lesserVersion = ssl->version.minor > it->pv.minor;
}

if (greaterVersion) {
WOLFSSL_MSG("Ticket has greater version");
return VERSION_ERROR;
}
else if (ssl->version.minor > it->pv.minor) {
else if (lesserVersion) {
if (IsAtLeastTLSv1_3(it->pv) != IsAtLeastTLSv1_3(ssl->version)) {
WOLFSSL_MSG("Tickets cannot be shared between "
"TLS 1.3 and TLS 1.2 and lower");
Expand All @@ -39636,7 +39655,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)

WOLFSSL_MSG("Downgrading protocol due to ticket");

if (it->pv.minor < ssl->options.minDowngrade) {
if (ssl->options.dtls)
belowMinDowngrade = it->pv.minor > ssl->options.minDowngrade;
else
belowMinDowngrade = it->pv.minor < ssl->options.minDowngrade;

if (belowMinDowngrade) {
WOLFSSL_MSG("Ticket has lesser version than allowed");
return VERSION_ERROR;
}
Expand Down
11 changes: 10 additions & 1 deletion src/ocsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,16 @@ int CheckOcspRequest(WOLFSSL_OCSP* ocsp, OcspRequest* ocspRequest,
urlSz = ocspRequest->urlSz;
}
else {
/* cert doesn't have extAuthInfo, assuming CERT_GOOD */
/* No AIA OCSP responder URL embedded in the cert and no override
* configured. The legacy behavior is to fail-open and return CERT_GOOD,
* but a caller that asked for ocspCheckAll requested strict checking
* of the entire chain - silently accepting a cert that omits its AIA
* extension would let a non-stapled flow bypass revocation entirely.
* Surface OCSP_NEED_URL so the caller can refuse the chain. */
if (ocsp->cm->ocspCheckAll) {
WOLFSSL_MSG("Cert has no OCSP URL and ocspCheckAll is set");
return OCSP_NEED_URL;
}
WOLFSSL_MSG("Cert has no OCSP URL, assuming CERT_GOOD");
return 0;
}
Expand Down
20 changes: 20 additions & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -15754,6 +15754,26 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
* - changing the server certificate(s)
* - changing the server id for session handling
* and everything else in WOLFSSL* needs to remain untouched.
*
* SECURITY WARNING (multi-tenant SNI virtual hosting):
* Per-host security policy is NOT copied from the new CTX. In particular
* the following settings are inherited from the original CTX and will
* apply to the connection regardless of which virtual host the SNI
* callback selects:
* - peer-verification mode (SSL_VERIFY_PEER, verifyNone, failNoCert)
* - the trusted CA store and any verify callback
* - CRL and OCSP configuration (including OCSP must-staple policy)
* - minimum key-size requirements
* - cipher-suite preferences and protocol-version bounds
*
* Callers that need different verification policies per virtual host
* (e.g. one host requires mTLS while another does not, or hosts trust
* different CA bundles) MUST propagate those settings onto the WOLFSSL*
* manually inside the SNI callback (e.g. via wolfSSL_set_verify,
* wolfSSL_UseCRL, wolfSSL_UseOCSP, wolfSSL_set_cipher_list, ...).
* Failing to do so silently keeps the original CTX's policy and can
* result in client certificates being skipped for a host that requires
* them, or peer certificates from the wrong CA bundle being accepted.
*/
WOLFSSL_ENTER("wolfSSL_set_SSL_CTX");
if (ssl == NULL || ctx == NULL)
Expand Down
17 changes: 17 additions & 0 deletions src/ssl_sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,23 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session)
ssl->options.haveEMS = (ssl->session->haveEMS) ? 1 : 0;

if (ssl->session->version.major != 0) {
/* Reject sessions whose protocol version is below the configured
* minimum so a stale cached session cannot make the client send a
* ClientHello advertising a version it isn't allowed to negotiate.
* DTLS minor versions are inverted: a higher minor means an older
* protocol, so the comparison flips. */
Comment on lines +1609 to +1613
byte belowMinDowngrade;
if (ssl->options.dtls)
belowMinDowngrade = ssl->session->version.minor >
ssl->options.minDowngrade;
else
belowMinDowngrade = ssl->session->version.minor <
ssl->options.minDowngrade;
if (belowMinDowngrade) {
WOLFSSL_MSG("Session version below configured minDowngrade");
ssl->options.resuming = 0;
return WOLFSSL_FAILURE;
}
ssl->version = ssl->session->version;
if (IsAtLeastTLSv1_3(ssl->version))
ssl->options.tls1_3 = 1;
Expand Down
7 changes: 4 additions & 3 deletions src/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -3658,9 +3658,10 @@ int ProcessChainOCSPRequest(WOLFSSL* ssl)
request->ssl = ssl;
ret = CheckOcspRequest(SSL_CM(ssl)->ocsp_stapling,
request, &csr->responses[i], ssl->heap);
/* Suppressing, not critical */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_REVOKED) ||
ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
/* Suppressing soft-fail responder errors. OCSP_CERT_REVOKED
* is an explicit positive assertion of revocation and must
* not be ignored. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret = 0;
}
Expand Down
42 changes: 42 additions & 0 deletions tests/api/test_tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -4815,6 +4815,48 @@ int test_tls13_short_session_ticket(void)
}


/* RFC 8446 Section 4.6.1: a NewSessionTicket lifetime greater than
* MAX_LIFETIME (604800 seconds, 7 days) must be rejected. The public
* wolfSSL_CTX_set_TicketHint setter clamps the value, so write the
* out-of-range hint directly into the server CTX to force the server to
* encode an over-limit lifetime onto the wire and confirm the client's
* DoTls13NewSessionTicket bound check fires. */
int test_tls13_new_session_ticket_max_lifetime(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
char buf[64];

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);

/* Bypass the public-API clamp at 604800. */
if (EXPECT_SUCCESS()) {
ctx_s->ticketHint = MAX_LIFETIME + 1;
}

ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

/* Reading the post-handshake NewSessionTicket should surface the
* over-limit lifetime as SERVER_HINT_ERROR. */
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WC_NO_ERR_TRACE(SERVER_HINT_ERROR));

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}


/* Test that a corrupted TLS 1.3 Finished verify_data is properly rejected
* with VERIFY_FINISHED_ERROR. We run the handshake step-by-step and corrupt
* the server's client_write_MAC_secret before it processes the client's
Expand Down
2 changes: 2 additions & 0 deletions tests/api/test_tls13.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ int test_tls13_derive_keys_no_key(void);
int test_tls13_pqc_hybrid_truncated_keyshare(void);
int test_tls13_empty_record_limit(void);
int test_tls13_short_session_ticket(void);
int test_tls13_new_session_ticket_max_lifetime(void);
int test_tls13_early_data_0rtt_replay(void);
int test_tls13_corrupted_finished(void);
int test_tls13_peerauth_failsafe(void);
Expand Down Expand Up @@ -84,6 +85,7 @@ int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \
TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \
TEST_DECL_GROUP("tls13", test_tls13_short_session_ticket), \
TEST_DECL_GROUP("tls13", test_tls13_new_session_ticket_max_lifetime), \
TEST_DECL_GROUP("tls13", test_tls13_early_data_0rtt_replay), \
TEST_DECL_GROUP("tls13", test_tls13_unknown_ext_rejected), \
TEST_DECL_GROUP("tls13", test_tls13_corrupted_finished), \
Expand Down
Loading