From 22ccbb44f49a8f0f905d9c94331db6f42ef00f37 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 30 Apr 2026 22:28:40 +0200 Subject: [PATCH 1/9] Stop suppressing OCSP_CERT_REVOKED in server stapling path Server-side OCSP stapling was unconditionally folding OCSP_CERT_REVOKED, OCSP_CERT_UNKNOWN, and OCSP_LOOKUP_FAIL into a success result so a stapling failure would not break the handshake. OCSP_CERT_REVOKED, however, is an explicit positive assertion of revocation by the responder and must not be ignored: silently suppressing it lets a server keep advertising a revoked certificate to clients that rely on stapling for revocation status. Drop OCSP_CERT_REVOKED from the suppression list in CreateOcspResponse, the CSR2_OCSP_MULTI handler in SendCertificateStatus, and ProcessChainOCSPRequest. Continue suppressing OCSP_CERT_UNKNOWN and OCSP_LOOKUP_FAIL, which are true soft-fail responder conditions where the responder cannot answer. F-1820 --- src/internal.c | 21 ++++++++++++--------- src/tls.c | 7 ++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/internal.c b/src/internal.c index 6dc0cbe2d1f..0760a562638 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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; } @@ -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; } @@ -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; } diff --git a/src/tls.c b/src/tls.c index 7f7e0b0d1bf..aff86f2c638 100644 --- a/src/tls.c +++ b/src/tls.c @@ -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; } From 05219c7f64133a13d644d3c075de530405741479 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 30 Apr 2026 22:30:01 +0200 Subject: [PATCH 2/9] Make DoClientTicketCheckVersion DTLS-aware DTLS minor versions decrease as the protocol version increases (DTLS 1.0=0xFF, DTLS 1.2=0xFD, DTLS 1.3=0xFC), but the ticket version comparisons in DoClientTicketCheckVersion used the TLS direction unconditionally. As a result a DTLS server resuming a session ticket from a different DTLS version could land on the wrong branch: a ticket from a newer DTLS version would be treated as a downgrade instead of being rejected, and a ticket from an older DTLS version would be flagged as 'greater version' and refused outright. The minDowngrade check at the bottom had the same inversion bug. Branch on ssl->options.dtls so the greater-version, lesser-version, and minDowngrade comparisons all use the right direction for the active protocol family. TLS behavior is unchanged. F-1828 --- src/internal.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index 0760a562638..5b6a7223da0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -39621,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; + + 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"); @@ -39639,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; } From 68e78acb69064b56f599288e47d5f0e3579122f5 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 30 Apr 2026 22:31:18 +0200 Subject: [PATCH 3/9] Validate minDowngrade in wolfSSL_SetSession before reusing version When resuming a session wolfSSL_SetSession unconditionally overwrote ssl->version with the version stored in the cached session, even if that version was below the WOLFSSL's configured minDowngrade. The overwritten version then fed straight into SendClientHello, so a client configured to require TLS 1.2 or higher could still emit a ClientHello advertising e.g. TLS 1.0 when resuming an old cached session. The ServerHello path catches the actual downgrade, but the ClientHello version is already a protocol-conformance issue and can confuse middleboxes. Reject the session if its stored minor version is below ssl->options.minDowngrade. The check is DTLS-aware: DTLS minor versions decrease as the protocol version increases, so the direction of the comparison is flipped for DTLS. F-2105 --- src/ssl_sess.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index ec790575057..6559b24d8a7 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -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. */ + 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; From d6b879c8a559b840d18235c1447fcc2fdb8fbe95 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 30 Apr 2026 22:37:37 +0200 Subject: [PATCH 4/9] Test TLS 1.3 NewSessionTicket MAX_LIFETIME bound check DoTls13NewSessionTicket rejects a ticket lifetime greater than MAX_LIFETIME (RFC 8446 Section 4.6.1, 7 days), but no test exercised the rejection: every server in the suite stays well within the limit, so a mutation deleting that bound check would go unnoticed. Add a manual memio test that pokes ctx_s->ticketHint to MAX_LIFETIME + 1 (the public setter clamps to 604800), runs a full TLS 1.3 handshake, and reads the post-handshake NewSessionTicket on the client. The test confirms the over-limit lifetime surfaces from wolfSSL_read as SERVER_HINT_ERROR. F-2121 --- tests/api/test_tls13.c | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 2 files changed, 44 insertions(+) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index ba12b7e3689..9ac35d943ee 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -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 diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index c8b42fc56c6..cf6f6177c76 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -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); @@ -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), \ From f24e41a8b5f5bfe55a6abec1a570168789def646 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 30 Apr 2026 22:38:50 +0200 Subject: [PATCH 5/9] Document SNI per-host policy gap in wolfSSL_set_SSL_CTX wolfSSL_set_SSL_CTX is the OpenSSL-compatible entry point that an SNI callback uses to swap in the per-vhost certificate during the handshake. By design it only copies the certificate chain and private key from the new CTX. Verification settings, the trusted CA store, CRL/OCSP configuration, minimum key-size requirements, and cipher/version policy stay attached to the original CTX. For multi-tenant servers where each virtual host has its own security policy, that means one host's verification rules silently apply to a connection meant for another. Expand the leading comment with an explicit SECURITY WARNING that lists the settings which are NOT inherited and points at the WOLFSSL*-level setters callers must use inside the SNI callback when virtual hosts have different policies. The behavior of the function is unchanged. F-2902 --- src/ssl.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index e5ca3ca9c26..421f1171f44 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -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) From 5f9bb4e32c5a3a4c46c8b8f8dbf71d67771bd6b6 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 30 Apr 2026 22:39:55 +0200 Subject: [PATCH 6/9] Fail closed in CheckOcspRequest when ocspCheckAll and no URL CheckOcspRequest used to return CERT_GOOD whenever a certificate lacked an AIA extension and no override URL was configured, with the rationale 'Cert has no OCSP URL, assuming CERT_GOOD'. That is a fail-open soft-fail: an operator who turned on WOLFSSL_OCSP_CHECKALL expecting every certificate in the chain to be revocation-checked would still silently accept a certificate that omits its OCSP responder URL, letting a misconfigured (or attacker-controlled) issuer bypass revocation for non-stapled flows. Gate the fail-open path on cm->ocspCheckAll. When the caller has asked for full-chain OCSP checking, return OCSP_NEED_URL so the chain is refused. The legacy behavior is preserved when ocspCheckAll is not set, keeping the soft-fail default for plain WOLFSSL_OCSP_ENABLE users. F-3227 --- src/ocsp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ocsp.c b/src/ocsp.c index 8c779c62476..6fb74a62f3b 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -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; } From 757687d1f348cc97bd065ddce2baa1d484770eac Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 5 May 2026 16:38:30 +0200 Subject: [PATCH 7/9] fixup! Fail closed in CheckOcspRequest when ocspCheckAll and no URL --- src/ocsp.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/ocsp.c b/src/ocsp.c index 6fb74a62f3b..f7e75ecb232 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -544,13 +544,11 @@ int CheckOcspRequest(WOLFSSL_OCSP* ocsp, OcspRequest* ocspRequest, urlSz = ocspRequest->urlSz; } else { - /* 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) { + /* 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; } From 0244864a459059b9e236c0a5d7439b4b04f2b17c Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 5 May 2026 16:38:30 +0200 Subject: [PATCH 8/9] fixup! Document SNI per-host policy gap in wolfSSL_set_SSL_CTX --- src/ssl.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 421f1171f44..2002f1061be 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -15755,25 +15755,11 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) * - 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. + * 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) From 42e40d322f13555899965de66d79e4682e5ac2f2 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 5 May 2026 16:38:30 +0200 Subject: [PATCH 9/9] fixup! Validate minDowngrade in wolfSSL_SetSession before reusing version --- tests/api/test_dtls.c | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_dtls.h | 4 +++- tests/api/test_tls.c | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 2 ++ 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index c6e8f7cc286..9fcca98f491 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -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(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 3a1e083a60e..e10ff55ad10 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -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), \ @@ -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 */ diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index aedae4f7031..2de15544b0b 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -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; diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index c0f74f21505..3ca34689e3e 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -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); @@ -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)