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
9 changes: 8 additions & 1 deletion src/ocsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,14 @@ int CheckOcspRequest(WOLFSSL_OCSP* ocsp, OcspRequest* ocspRequest,
urlSz = ocspRequest->urlSz;
}
else {
/* cert doesn't have extAuthInfo, assuming CERT_GOOD */
/* No AIA URL and no override. ocspCheckAll asks for strict chain
* checking, so fail closed - but only on the client verification
* instance (cm->ocsp); stapling (cm->ocsp_stapling) shares the cm
* flag and must stay best-effort. */
if (ocsp->cm->ocspCheckAll && ocsp == ocsp->cm->ocsp) {
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
6 changes: 6 additions & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -15754,6 +15754,12 @@ 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: swapping ssl->ctx switches cm-resolved settings (CA store,
* CRL, OCSP) to the new CTX but leaves ssl-cached ones (verify mode and
* callback, minDowngrade, key-size minimums, suites, version bounds)
* pinned to the original. SNI callbacks must re-apply those ssl-level
* settings explicitly; CRL/OCSP isolation requires an SSL-local store.
*/
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_dtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -2909,3 +2909,45 @@ int test_dtls13_oversized_cert_chain(void)
#endif
return EXPECT_RESULT();
}

/* DTLS counterpart to test_tls_set_session_min_downgrade. Exercises the
* inverted DTLS minor-version comparison (DTLS 1.2 minor 0xFD is "below"
* floor 0xFC = DTLS 1.3). */
int test_dtls_set_session_min_downgrade(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \
defined(WOLFSSL_DTLS13) && defined(HAVE_SESSION_TICKET)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
struct test_memio_ctx test_ctx;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));

wolfSSL_free(ssl_c); ssl_c = NULL;
wolfSSL_free(ssl_s); ssl_s = NULL;
wolfSSL_CTX_free(ctx_c); ctx_c = NULL;
wolfSSL_CTX_free(ctx_s); ctx_s = NULL;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfDTLS_client_method, wolfDTLS_server_method), 0);
ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, WOLFSSL_DTLSV1_3),
WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_FAILURE);
if (ssl_c != NULL)
ExpectIntEQ(ssl_c->options.resuming, 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();
}
4 changes: 3 additions & 1 deletion tests/api/test_dtls.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ int test_dtls_mtu_split_messages(void);
int test_dtls13_min_rtx_interval(void);
int test_dtls13_no_session_id_echo(void);
int test_dtls13_oversized_cert_chain(void);
int test_dtls_set_session_min_downgrade(void);

#define TEST_DTLS_DECLS \
TEST_DECL_GROUP("dtls", test_dtls12_basic_connection_id), \
Expand Down Expand Up @@ -87,5 +88,6 @@ int test_dtls13_oversized_cert_chain(void);
TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_stateless), \
TEST_DECL_GROUP("dtls", test_dtls13_min_rtx_interval), \
TEST_DECL_GROUP("dtls", test_dtls13_no_session_id_echo), \
TEST_DECL_GROUP("dtls", test_dtls13_oversized_cert_chain)
TEST_DECL_GROUP("dtls", test_dtls13_oversized_cert_chain), \
TEST_DECL_GROUP("dtls", test_dtls_set_session_min_downgrade)
#endif /* TESTS_API_DTLS_H */
42 changes: 42 additions & 0 deletions tests/api/test_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,48 @@ int test_tls12_etm_failed_resumption(void)
return EXPECT_RESULT();
}

/* wolfSSL_set_session() must reject a TLS 1.2 session when minDowngrade is
* set to TLS 1.3. */
int test_tls_set_session_min_downgrade(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_TLS13) && \
defined(HAVE_SESSION_TICKET)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
struct test_memio_ctx test_ctx;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));

wolfSSL_free(ssl_c); ssl_c = NULL;
wolfSSL_free(ssl_s); ssl_s = NULL;
wolfSSL_CTX_free(ctx_c); ctx_c = NULL;
wolfSSL_CTX_free(ctx_s); ctx_s = NULL;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLS_client_method, wolfTLS_server_method), 0);
ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, WOLFSSL_TLSV1_3),
WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_FAILURE);
if (ssl_c != NULL)
ExpectIntEQ(ssl_c->options.resuming, 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();
}

int test_tls_set_curves_list_ecc_fallback(void)
{
EXPECT_DECLS;
Expand Down
2 changes: 2 additions & 0 deletions tests/api/test_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ int test_tls_certreq_order(void);
int test_tls12_bad_cv_sig_alg(void);
int test_tls12_no_null_compression(void);
int test_tls12_etm_failed_resumption(void);
int test_tls_set_session_min_downgrade(void);
int test_tls_set_curves_list_ecc_fallback(void);
int test_tls12_corrupted_finished(void);
int test_tls12_peerauth_failsafe(void);
Expand All @@ -47,6 +48,7 @@ int test_tls12_peerauth_failsafe(void);
TEST_DECL_GROUP("tls", test_tls12_bad_cv_sig_alg), \
TEST_DECL_GROUP("tls", test_tls12_no_null_compression), \
TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \
TEST_DECL_GROUP("tls", test_tls_set_session_min_downgrade), \
TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \
TEST_DECL_GROUP("tls", test_tls12_corrupted_finished), \
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe)
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