From f74dcb77f0d0f2e5bb74f5a15773d5c82e6aae35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 21 Oct 2026 14:45:42 +0200 Subject: [PATCH 01/17] Offer the RFC 7027 brainpool groups only when TLS 1.2 or older is reachable A TLS 1.3 client also lists the RFC 7027 brainpool codepoints when it may fall back to an earlier version. The check compared minDowngrade against both TLSv1_2_MINOR and DTLSv1_2_MINOR with <=, which is true for every TLS minor and for DTLS 1.3, whose minor (0xFC) is below DTLS 1.2's. A client with TLS 1.3 or DTLS 1.3 as its minimum version therefore still offered the pre-1.3 codepoints. Add TLSX_PreTls13Reachable(), which answers whether supported_versions would also offer a version before 1.3, the same way TLSX_SupportedVersions_GetSize() decides it: downgrade allowed, and TLS 1.2, TLS 1.1 (DTLS 1.0) or, with WOLFSSL_ALLOW_TLSV10, TLS 1.0 at or above the minimum version and not masked off with WOLFSSL_OP_NO_TLSv1_2, _1_1 or NO_TLSv1. A build with neither TLS 1.2 nor old TLS never offers them. DTLS minors count down. TLSX_PopulateSupportedGroups() also builds the list a TLS 1.3 server sends in EncryptedExtensions when the client lacks its first choice. The version is settled by then, so the helper answers 0 on the server side. Adds a test for the P256, P384 and P512 brainpool codepoints a TLS, TLS 1.2-only, TLS 1.3-only and DTLS client offers with a minimum version set on the SSL or the CTX, with versions masked off down to TLS 1.0 and DTLS 1.0, and in the list a TLS 1.3 server sends back. --- src/tls.c | 61 +++++++++- tests/api.c | 1 + tests/api/test_tls_ext.c | 232 +++++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 2 + 4 files changed, 293 insertions(+), 3 deletions(-) diff --git a/src/tls.c b/src/tls.c index a9277c3220d..7a712e05440 100644 --- a/src/tls.c +++ b/src/tls.c @@ -169,6 +169,9 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap); #ifdef HAVE_SUPPORTED_CURVES static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions); +#if defined(HAVE_ECC) && defined(HAVE_ECC_BRAINPOOL) +static int TLSX_PreTls13Reachable(const WOLFSSL* ssl); +#endif #endif #if defined(HAVE_ECC) && !defined(NO_ECC_SECP) @@ -5280,15 +5283,15 @@ static int TLSX_DefaultGroupAllowed(const WOLFSSL* ssl, word16 name) case WOLFSSL_ECC_BRAINPOOLP384R1TLS13: case WOLFSSL_ECC_BRAINPOOLP512R1TLS13: return IsAtLeastTLSv1_3(ssl->version); + #if defined(HAVE_ECC) && defined(HAVE_ECC_BRAINPOOL) /* RFC 7027 codepoints, also offered by TLS 1.3 when it may fall back * to TLS 1.2. */ case WOLFSSL_ECC_BRAINPOOLP256R1: case WOLFSSL_ECC_BRAINPOOLP384R1: case WOLFSSL_ECC_BRAINPOOLP512R1: return !IsAtLeastTLSv1_3(ssl->version) || - (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)); + TLSX_PreTls13Reachable(ssl); + #endif default: return 1; } @@ -16679,6 +16682,58 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, #ifdef HAVE_SUPPORTED_CURVES +#if defined(HAVE_ECC) && defined(HAVE_ECC_BRAINPOOL) +/* Returns 1 when a client's supported_versions would also offer a version + * before 1.3. */ +static int TLSX_PreTls13Reachable(const WOLFSSL* ssl) +{ + int ret = 0; +#if defined(WOLFSSL_TLS13) && \ + (!defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)) + byte isDtls = (byte)ssl->options.dtls; + + if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->options.downgrade) { + #ifndef WOLFSSL_NO_TLS12 + if (versionIsLessEqual(isDtls, ssl->options.minDowngrade, + (byte)(isDtls ? DTLSv1_2_MINOR : TLSv1_2_MINOR)) + #if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + && (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == 0 + #endif + ) { + ret = 1; + } + #endif + #ifndef NO_OLD_TLS + if (versionIsLessEqual(isDtls, ssl->options.minDowngrade, + (byte)(isDtls ? DTLS_MINOR : TLSv1_1_MINOR)) + #if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + && (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_1) == 0 + #endif + ) { + ret = 1; + } + #ifdef WOLFSSL_ALLOW_TLSV10 + if (!isDtls && (ssl->options.minDowngrade <= TLSv1_MINOR) + #if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + && (ssl->options.mask & WOLFSSL_OP_NO_TLSv1) == 0 + #endif + ) { + ret = 1; + } + #endif + #endif + } +#else + (void)ssl; +#endif + + return ret; +} +#endif + /* Populates the default supported groups / curves */ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) { diff --git a/tests/api.c b/tests/api.c index 403545cd342..c21e8b74f89 100644 --- a/tests/api.c +++ b/tests/api.c @@ -48259,6 +48259,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_QUIC_TP_non_quic), TEST_DECL(test_TLSX_ALPN_server_response_count), TEST_DECL(test_TLSX_SupportedCurve_empty_or_unsupported), + TEST_DECL(test_TLSX_SupportedCurve_brainpool_downgrade), TEST_DECL(test_TLSX_SignatureAlgorithmsCert_parse), TEST_DECL(test_TLSX_PointFormat_list), TEST_DECL(test_TLSX_MaxFragment_value), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 5a02b320f1d..42cffd04a6c 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3610,6 +3610,238 @@ int test_TLSX_SupportedCurve_empty_or_unsupported(void) return EXPECT_RESULT(); } +#if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_BRAINPOOL) && ECC_MIN_KEY_SZ <= 256 && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) +static const word16 test_bp_tls13[] = { + WOLFSSL_ECC_BRAINPOOLP256R1TLS13, +#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + WOLFSSL_ECC_BRAINPOOLP384R1TLS13, +#endif +#if (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 512 + WOLFSSL_ECC_BRAINPOOLP512R1TLS13, +#endif +}; +static const word16 test_bp_tls12[] = { + WOLFSSL_ECC_BRAINPOOLP256R1, +#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + WOLFSSL_ECC_BRAINPOOLP384R1, +#endif +#if (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 512 + WOLFSSL_ECC_BRAINPOOLP512R1, +#endif +}; +#define TEST_BP_CNT \ + ((int)(sizeof(test_bp_tls13) / sizeof(test_bp_tls13[0]))) + +static void test_brainpool_count(TLSX* ext, int* tls13Cnt, int* tls12Cnt) +{ + SupportedCurves* curves = NULL; + int i; + int j; + + *tls13Cnt = 0; + *tls12Cnt = 0; + if (ext != NULL) + curves = (SupportedCurves*)ext->data; + for (j = 0; curves != NULL && j < curves->count; j++) { + for (i = 0; i < TEST_BP_CNT; i++) { + if (curves->name[j] == test_bp_tls13[i]) + (*tls13Cnt)++; + else if (curves->name[j] == test_bp_tls12[i]) + (*tls12Cnt)++; + } + } +} + +/* Counts the brainpool codepoints of each kind in a client's supported_groups + * list. */ +static int test_brainpool_offer(method_provider method, int ctxMinVersion, + int minVersion, long opts, int* tls13Cnt, int* tls12Cnt) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL* ssl_c = NULL; + TLSX* ext = NULL; + + *tls13Cnt = 0; + *tls12Cnt = 0; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, NULL, NULL, + method, NULL), 0); + if (ctxMinVersion != 0) { + ExpectIntEQ(wolfSSL_CTX_SetMinVersion(ctx_c, ctxMinVersion), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL, + method, NULL), 0); + if (minVersion != 0) { + ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, minVersion), + WOLFSSL_SUCCESS); + } + if ((opts != 0) && (ssl_c != NULL)) + (void)wolfSSL_set_options(ssl_c, opts); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + if (ssl_c != NULL) + ext = TLSX_Find(ssl_c->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(ext); + test_brainpool_count(ext, tls13Cnt, tls12Cnt); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + return EXPECT_RESULT(); +} + +#if !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) +/* Counts the brainpool codepoints of each kind in the supported_groups list a + * TLS 1.3 server sends when the client lacks the server's first group. */ +static int test_brainpool_server_offer(int* tls13Cnt, int* tls12Cnt) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + TLSX* ext = NULL; + + *tls13Cnt = 0; + *tls12Cnt = 0; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfSSLv23_server_method), 0); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl_c, + WOLFSSL_ECC_BRAINPOOLP256R1TLS13), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseKeyShare(ssl_c, WOLFSSL_ECC_BRAINPOOLP256R1TLS13), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_version(ssl_s), TLS1_3_VERSION); + + if (ssl_s != NULL) + ext = TLSX_Find(ssl_s->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(ext); + if (ext != NULL) + ExpectIntEQ(ext->resp, 1); + test_brainpool_count(ext, tls13Cnt, tls12Cnt); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif +#endif + +/* The RFC 7027 brainpool codepoints are offered only when TLS 1.2 or older can + * still be negotiated; TLS 1.3 uses the RFC 8734 ones. */ +int test_TLSX_SupportedCurve_brainpool_downgrade(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_BRAINPOOL) && ECC_MIN_KEY_SZ <= 256 && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) + int tls13Cnt = 0; + int tls12Cnt = 0; + + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, 0, 0, 0, + &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); + + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, 0, + WOLFSSL_TLSV1_2, 0, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); + + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, 0, + WOLFSSL_TLSV1_3, 0, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); + + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, + WOLFSSL_TLSV1_3, 0, 0, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); + + ExpectIntEQ(test_brainpool_offer(wolfTLSv1_3_client_method, 0, 0, 0, + &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); + + ExpectIntEQ(test_brainpool_offer(wolfTLSv1_2_client_method, 0, 0, 0, + &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, 0); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); + +#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, 0, 0, + WOLFSSL_OP_NO_TLSv1 | WOLFSSL_OP_NO_TLSv1_1 | + WOLFSSL_OP_NO_TLSv1_2, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); +#ifndef NO_OLD_TLS + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, 0, + WOLFSSL_TLSV1_1, WOLFSSL_OP_NO_TLSv1_2, &tls13Cnt, &tls12Cnt), + TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); +#ifdef WOLFSSL_ALLOW_TLSV10 + ExpectIntEQ(test_brainpool_offer(wolfSSLv23_client_method, 0, + WOLFSSL_TLSV1, WOLFSSL_OP_NO_TLSv1_1 | WOLFSSL_OP_NO_TLSv1_2, + &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); +#endif +#ifdef WOLFSSL_DTLS13 + ExpectIntEQ(test_brainpool_offer(wolfDTLS_client_method, 0, + WOLFSSL_DTLSV1, WOLFSSL_OP_NO_TLSv1_2, &tls13Cnt, &tls12Cnt), + TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); +#endif +#endif +#endif + +#ifdef WOLFSSL_DTLS + ExpectIntEQ(test_brainpool_offer(wolfDTLS_client_method, 0, + WOLFSSL_DTLSV1_2, 0, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); +#ifdef WOLFSSL_DTLS13 + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); +#else + ExpectIntEQ(tls13Cnt, 0); +#endif + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); +#endif + +#ifdef WOLFSSL_DTLS13 + ExpectIntEQ(test_brainpool_offer(wolfDTLS_client_method, 0, + WOLFSSL_DTLSV1_3, 0, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); +#endif + +#if !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) + ExpectIntEQ(test_brainpool_server_offer(&tls13Cnt, &tls12Cnt), + TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); +#endif +#endif + return EXPECT_RESULT(); +} + /* The signature_algorithms_cert list is allocated only when a peer sends the * extension: first list, replacement, truncation, and release. */ int test_TLSX_SignatureAlgorithmsCert_parse(void) diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 7e943bdf2c2..b34f7368269 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -69,6 +69,7 @@ int test_TLSX_TCA_tls13_msg_type_validation(void); int test_TLSX_QUIC_TP_non_quic(void); int test_TLSX_ALPN_server_response_count(void); int test_TLSX_SupportedCurve_empty_or_unsupported(void); +int test_TLSX_SupportedCurve_brainpool_downgrade(void); int test_TLSX_SignatureAlgorithmsCert_parse(void); int test_TLSX_PointFormat_list(void); int test_TLSX_MaxFragment_value(void); @@ -112,6 +113,7 @@ int test_wolfSSL_custom_ext_add_null(void); TEST_DECL_GROUP("tls", test_TLSX_SRTP_msg_type_validation), \ TEST_DECL_GROUP("tls", test_TLSX_ALPN_server_response_count), \ TEST_DECL_GROUP("tls", test_TLSX_SupportedCurve_empty_or_unsupported), \ + TEST_DECL_GROUP("tls", test_TLSX_SupportedCurve_brainpool_downgrade), \ TEST_DECL_GROUP("tls", test_TLSX_PointFormat_uncompressed_required) #endif /* TESTS_API_TEST_TLS_EXT_H */ From 30f6d7f77531d0a8f07f0e05f4d6032f69d0af65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 21 Oct 2026 21:34:30 +0200 Subject: [PATCH 02/17] Downgrade a DTLS 1.3 SSL when TLS 1.3 is masked off wolfSSL_set_options() and SetSSL_CTX_CheckVersion() move an SSL from TLS 1.3 to TLS 1.2 when WOLFSSL_OP_NO_TLSv1_3 is set, but compare the version against TLSv1_3_MINOR only. A DTLS 1.3 SSL (minor 0xFC) kept its version, so its ClientHello offered only older DTLS versions in supported_versions while still carrying a TLS 1.3 key_share and the TLS 1.3-only groups (ML-KEM hybrids, RFC 8734 brainpool). Add Dtls13DowngradeMinor(), which picks the newest older DTLS version that the version mask and minimum still allow and that is built: DTLS 1.2, or DTLS 1.0 when old TLS is built. The later TLS mask steps only match TLS minors, so DTLS needs its own choice. wolfSSL_set_options() moves to it when there is one and the SSL may downgrade, so wolfSSL_require_psk() and the DTLS 1.3-only methods keep DTLS 1.3. It also steps down again when a later call masks DTLS 1.2 on an SSL it already moved there, so separate calls end where one combined call does. SetSSL_CTX_CheckVersion() returns VERSION_ERROR when the method cannot downgrade or no DTLS version is left, as it does for TLS. Adds test_dtls13_no_tls13_option for the option on the client or server SSL or CTX, with DTLS 1.2 also masked off in the same or a separate call or below the minimum, with no DTLS version left, in builds without TLS 1.2, for a DTLS 1.3-only method and after wolfSSL_require_psk(), and a DTLS case to the brainpool supported_groups test. --- src/internal.c | 42 ++++++++++++ src/ssl.c | 12 ++++ tests/api/test_dtls.c | 141 +++++++++++++++++++++++++++++++++++++++ tests/api/test_dtls.h | 4 +- tests/api/test_tls_ext.c | 7 ++ wolfssl/internal.h | 2 + 6 files changed, 207 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 34fc49a735b..9fe8d905fdd 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8210,6 +8210,29 @@ int InitSSL_Suites(WOLFSSL* ssl) writeDup flag indicating this is a write dup only WOLFSSL_SUCCESS return value on success */ +#ifdef WOLFSSL_DTLS13 +/* Returns the newest DTLS minor before 1.3 that the version mask and + * minDowngrade allow, or 0. DTLS minors count down. */ +byte Dtls13DowngradeMinor(const WOLFSSL* ssl, byte minDowngrade) +{ +#ifndef WOLFSSL_NO_TLS12 + if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == 0 && + minDowngrade >= DTLSv1_2_MINOR) { + return DTLSv1_2_MINOR; + } +#endif +#ifndef NO_OLD_TLS + if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_1) == 0 && + minDowngrade >= DTLS_MINOR) { + return DTLS_MINOR; + } +#endif + (void)ssl; + (void)minDowngrade; + return 0; +} +#endif + #ifdef OPENSSL_EXTRA /* Apply the protocol version mask and downgrade rules. Returns VERSION_ERROR * when the requested options are inconsistent, 0 otherwise. */ @@ -8228,6 +8251,25 @@ static int SetSSL_CTX_CheckVersion(WOLFSSL* ssl, WOLFSSL_CTX* ctx) ssl->version.minor = TLSv1_2_MINOR; } #endif + #ifdef WOLFSSL_DTLS13 + if (ssl->version.major == DTLS_MAJOR && + ssl->version.minor == DTLSv1_3_MINOR && + (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_3) == + WOLFSSL_OP_NO_TLSv1_3) { + byte minor = 0; + + if (ctx->method->downgrade) + minor = Dtls13DowngradeMinor(ssl, ctx->minDowngrade); + if (minor == 0) { + WOLFSSL_MSG("\tInconsistent protocol options. DTLS 1.3 set but " + "not allowed and no lower DTLS version allowed."); + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); + return VERSION_ERROR; + } + WOLFSSL_MSG("\tOption set to not allow DTLSv1.3, Downgrading"); + ssl->version.minor = minor; + } + #endif if (ssl->version.minor == TLSv1_2_MINOR && (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2) { if (!ctx->method->downgrade) { diff --git a/src/ssl.c b/src/ssl.c index e3890aeeba9..e54450c773c 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7616,6 +7616,18 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) WOLFSSL_MSG("Disabling TLS 1.3"); if (ssl->version.minor == TLSv1_3_MINOR) ssl->version.minor = TLSv1_2_MINOR; + #ifdef WOLFSSL_DTLS13 + else if (ssl->version.major == DTLS_MAJOR && ssl->options.downgrade && + (ssl->version.minor == DTLSv1_3_MINOR || + (ssl->version.minor == DTLSv1_2_MINOR && + (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == + WOLFSSL_OP_NO_TLSv1_2))) { + byte minor = Dtls13DowngradeMinor(ssl, + ssl->options.minDowngrade); + if (minor != 0) + ssl->version.minor = minor; + } + #endif } if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2) { diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 24aa2593ba2..a3e70e07a89 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16532,3 +16532,144 @@ int test_dtls_set_rfd_wfd(void) #endif return EXPECT_RESULT(); } + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(OPENSSL_EXTRA) +/* Sets opts, then opts2 in a second call, and minVer when not 0, on the client + * or server SSL or CTX. An expVersion of 0 means no DTLS version is left. */ +static int test_dtls13_mask_case(int onServer, int onCtx, long opts, + long opts2, int minVer, int expVersion) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx; + WOLFSSL* ssl; + int ret; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ctx = onServer ? ctx_s : ctx_c; + if (onCtx && minVer != 0) + ExpectIntEQ(wolfSSL_CTX_SetMinVersion(ctx, minVer), WOLFSSL_SUCCESS); + if (onCtx) { + (void)wolfSSL_CTX_set_options(ctx, opts); + (void)wolfSSL_CTX_set_options(ctx, opts2); + } + ret = test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method); + if (onCtx && expVersion == 0) { + ExpectIntNE(ret, 0); + ExpectNull(onServer ? ssl_s : ssl_c); + } + else { + ExpectIntEQ(ret, 0); + ssl = onServer ? ssl_s : ssl_c; + if (!onCtx && minVer != 0) + ExpectIntEQ(wolfSSL_SetMinVersion(ssl, minVer), WOLFSSL_SUCCESS); + if (!onCtx && ssl != NULL) { + (void)wolfSSL_set_options(ssl, opts); + (void)wolfSSL_set_options(ssl, opts2); + } + ExpectIntEQ(wolfSSL_version(ssl), + expVersion != 0 ? expVersion : DTLS1_3_VERSION); + + ret = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); + if (expVersion != 0) { + ExpectIntEQ(ret, 0); + ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); + ExpectIntEQ(wolfSSL_version(ssl_s), expVersion); + } + else { + ExpectIntNE(ret, 0); + } + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* WOLFSSL_OP_NO_TLSv1_3 moves a DTLS 1.3 client or server to the newest older + * DTLS version the mask and minimum allow, or is refused when none is left. + * + * @return TEST_SUCCESS on success. + */ +int test_dtls13_no_tls13_option(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(OPENSSL_EXTRA) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; +#ifndef WOLFSSL_NO_TLS12 + int onServer; +#endif + int onCtx; +#ifndef NO_OLD_TLS + int dtls10 = DTLS1_VERSION; +#else + int dtls10 = 0; +#endif + +#ifndef WOLFSSL_NO_TLS12 + for (onServer = 0; onServer <= 1; onServer++) { + for (onCtx = 0; onCtx <= 1; onCtx++) { + ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, + WOLFSSL_OP_NO_TLSv1_3, 0, 0, DTLS1_2_VERSION), TEST_SUCCESS); + } + } +#else + ExpectIntEQ(test_dtls13_mask_case(0, 0, WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(0, 1, WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(1, 1, WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0), + TEST_SUCCESS); +#endif + for (onCtx = 0; onCtx <= 1; onCtx++) { + ExpectIntEQ(test_dtls13_mask_case(0, onCtx, + WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2, 0, 0, dtls10), + TEST_SUCCESS); +#ifndef NO_OLD_TLS + ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_2, 0, DTLS1_VERSION), TEST_SUCCESS); +#endif + ExpectIntEQ(test_dtls13_mask_case(0, onCtx, + WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1, 0, 0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, 0, + WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); + } + + /* A DTLS 1.3-only method cannot move down. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); + wolfSSL_free(ssl); + ssl = NULL; + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectNull(wolfSSL_new(ctx)); + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_2); + ExpectNull(wolfSSL_new(ctx)); + wolfSSL_CTX_free(ctx); + ctx = NULL; + +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_require_psk(ssl), 0); + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 99c342c31bf..f29bf48e131 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -181,6 +181,7 @@ int test_dtls12_cookie_secret_generate_fail(void); int test_dtls13_hrr_cookie_secret_generate_fail(void); int test_dtls_max_fragment_write(void); int test_dtls_set_rfd_wfd(void); +int test_dtls13_no_tls13_option(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_import_dh_key_sz(void); int test_wolfSSL_dtls_export_peers(void); @@ -404,5 +405,6 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write), \ - TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd) + TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd), \ + TEST_DECL_GROUP("dtls", test_dtls13_no_tls13_option) #endif /* TESTS_API_DTLS_H */ diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 42cffd04a6c..de43dab6121 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3830,6 +3830,13 @@ int test_TLSX_SupportedCurve_brainpool_downgrade(void) WOLFSSL_DTLSV1_3, 0, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); ExpectIntEQ(tls13Cnt, TEST_BP_CNT); ExpectIntEQ(tls12Cnt, 0); +#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + ExpectIntEQ(test_brainpool_offer(wolfDTLS_client_method, 0, 0, + WOLFSSL_OP_NO_TLSv1_3, &tls13Cnt, &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, 0); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); +#endif #endif #if !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index c96be637e07..6fb51e6ee09 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8476,6 +8476,8 @@ WOLFSSL_LOCAL int DtlsNoCookieChGood(WOLFSSL* ssl); #endif #ifdef WOLFSSL_DTLS13 +WOLFSSL_LOCAL byte Dtls13DowngradeMinor(const WOLFSSL* ssl, + byte minDowngrade); #ifdef WOLFSSL_API_PREFIX_MAP #define Dtls13GetEpoch wolfSSL_Dtls13GetEpoch #define Dtls13NewEpoch wolfSSL_Dtls13NewEpoch From 33a8843ba1ef3c6605130013035b88b5c7ad3f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 21 Oct 2026 21:34:31 +0200 Subject: [PATCH 03/17] Keep the RFC 7027 brainpool groups out of the ECH inner ClientHello The inner ClientHello offers only TLS 1.3, as downgrade is cleared while it is written, but it referenced the outer supported_groups through ech_outer_extensions and so still carried the pre-1.3 brainpool codepoints. When ECH is in use and a pre-1.3 version is reachable, the client now gives the outer ClientHello its own default groups list in ech->extensions, the way the public-name SNI is handled, and builds the inner list without the pre-1.3 groups. An extension with an outer-only copy is no longer referenced from ech_outer_extensions. A server that negotiates TLS 1.2 or older answers the outer ClientHello, so DoServerHello() now swaps the outer extensions back in, as the TLS 1.3 rejection paths already do, unless a HelloRetryRequest accepted ECH. Without that the client kept the inner list and could not use a brainpool client certificate in the TLS 1.2 handshake. A second ClientHello after a HelloRetryRequest keeps the lists the first one sent, as RFC 8446 requires: the split is skipped then, and the default groups are no longer added again to an existing supported_groups extension, which would have put the pre-1.3 groups back into the inner list. Adds ECH cases to the brainpool test: the inner list as the server parsed it, the client's list after a TLS 1.2 ServerHello, and, with ECH accepted and rejected, that the ClientHello after a HelloRetryRequest repeats the first one's supported_groups and its position. supported_versions is left out of the order check, as it already moves in every second ClientHello. --- src/internal.c | 7 ++ src/tls.c | 54 ++++++-- tests/api/test_tls_ext.c | 260 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index 9fe8d905fdd..d6f43cb6ec5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -37698,6 +37698,13 @@ static int IsTls13CipherSuite(byte first, byte second) } #endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + /* A server negotiating TLS 1.2 or older answers the outer hello. */ + ret = TLSX_EchReplaceExtensions(ssl, ssl->options.echAccepted); + if (ret != 0) + return ret; +#endif + /* random */ XMEMCPY(ssl->arrays->serverRandom, input + i, RAN_LEN); i += RAN_LEN; diff --git a/src/tls.c b/src/tls.c index 7a712e05440..e5a018e50a5 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16752,6 +16752,39 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) return WOLFSSL_SUCCESS; } +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_BRAINPOOL) +/* Gives the ECH outer ClientHello the full default groups list and the inner + * one, which offers only TLS 1.3, the list without the pre-1.3 groups. */ +static int TLSX_EchSplitSupportedGroups(WOLFSSL* ssl) +{ + int ret; + byte downgrade; + TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH); + WOLFSSL_ECH* ech; + + /* After a HelloRetryRequest the lists stay as the first hello sent them. */ + if (echX == NULL || echX->data == NULL || ssl->options.userCurves || + ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE || + TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS) == NULL || + !TLSX_PreTls13Reachable(ssl)) { + return 0; + } + + ech = (WOLFSSL_ECH*)echX->data; + ret = TLSX_PopulateSupportedGroups(ssl, &ech->extensions); + if (ret == WOLFSSL_SUCCESS) { + TLSX_Remove(&ssl->extensions, TLSX_SUPPORTED_GROUPS, ssl->heap); + downgrade = ssl->options.downgrade; + ssl->options.downgrade = 0; + ret = TLSX_PopulateSupportedGroups(ssl, &ssl->extensions); + ssl->options.downgrade = downgrade; + } + + return (ret == WOLFSSL_SUCCESS) ? 0 : ret; +} +#endif + #endif /* HAVE_SUPPORTED_CURVES */ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) @@ -16791,7 +16824,8 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #if defined(HAVE_SUPPORTED_CURVES) if (!ssl->options.userCurves && !ssl->ctx->userCurves) { if (TLSX_Find(ssl->ctx->extensions, - TLSX_SUPPORTED_GROUPS) == NULL) { + TLSX_SUPPORTED_GROUPS) == NULL && + TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS) == NULL) { ret = TLSX_PopulateSupportedGroups(ssl, &ssl->extensions); if (ret != WOLFSSL_SUCCESS) return ret; @@ -17218,6 +17252,11 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) else if (ssl->echConfigs != NULL) { ret = ECH_USE(ssl->echConfigs, &(ssl->extensions), ssl->heap, ssl->rng); + #if defined(HAVE_SUPPORTED_CURVES) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_BRAINPOOL) + if (ret == 0) + ret = TLSX_EchSplitSupportedGroups(ssl); + #endif } } #endif @@ -17434,9 +17473,9 @@ static int TLSX_ECH_IsEncodable(word16 type) * 2-byte extension_type + 2-byte extension_data length + * 1-byte list length + 2*count bytes of extension types */ -static int TLSX_ECH_BuildOuterExtensions(WOLFSSL* ssl, const byte* semaphore, - byte msgType, byte* output, word16* pOffset, word16* outCount, - byte* encodeMask) +static int TLSX_ECH_BuildOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, + const byte* semaphore, byte msgType, byte* output, word16* pOffset, + word16* outCount, byte* encodeMask) { TLSX* list; TLSX* extension; @@ -17473,7 +17512,8 @@ static int TLSX_ECH_BuildOuterExtensions(WOLFSSL* ssl, const byte* semaphore, if (!IS_OFF(seen, semIdx)) continue; TURN_ON(seen, semIdx); - if (!TLSX_ECH_IsEncodable(type)) + if (!TLSX_ECH_IsEncodable(type) || + TLSX_Find(ech->extensions, (TLSX_Type)type) != NULL) continue; if (typesStart != NULL) @@ -17530,7 +17570,7 @@ static int TLSX_GetSizeWithEch(WOLFSSL* ssl, byte* semaphore, byte msgType, * `semaphore` is in/out so encodable extensions will later be ignored */ if (ret == 0 && ech != NULL && ech->type == ECH_TYPE_INNER && ech->writeEncoded) { - ret = TLSX_ECH_BuildOuterExtensions(ssl, semaphore, msgType, + ret = TLSX_ECH_BuildOuterExtensions(ssl, ech, semaphore, msgType, NULL, pLength, &count, semaphore); } if (ret == 0 && ssl->extensions) @@ -18089,7 +18129,7 @@ static int TLSX_WriteWithEch(WOLFSSL* ssl, byte* output, byte* semaphore, XMEMSET(encodeMask, 0, SEMAPHORE_SIZE); - ret = TLSX_ECH_BuildOuterExtensions(ssl, semaphore, msgType, + ret = TLSX_ECH_BuildOuterExtensions(ssl, ech, semaphore, msgType, ech->writeEncoded ? output : NULL, ech->writeEncoded ? pOffset : NULL, &count, mask); diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index de43dab6121..ec8f2cbb2fb 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3739,6 +3739,243 @@ static int test_brainpool_server_offer(int* tls13Cnt, int* tls12Cnt) return EXPECT_RESULT(); } #endif + +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) && !defined(NO_WOLFSSL_SERVER) +/* Counts the brainpool codepoints in the ECH inner ClientHello as the server + * parsed it, or in the list the client uses after a TLS 1.2 ServerHello. */ +static int test_brainpool_ech_offer(method_provider serverMethod, + int* tls13Cnt, int* tls12Cnt) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + TLSX* ext = NULL; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + int tls12Server = (serverMethod == wolfTLSv1_2_server_method); + + *tls13Cnt = 0; + *tls12Cnt = 0; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, serverMethod), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + if (tls12Server) { + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_2_VERSION); + if (ssl_c != NULL) + ext = TLSX_Find(ssl_c->extensions, TLSX_SUPPORTED_GROUPS); + } + else { + ExpectIntEQ(wolfSSL_GetEchStatus(ssl_s), + WOLFSSL_ECH_STATUS_ACCEPTED); + if (ssl_s != NULL) + ext = TLSX_Find(ssl_s->extensions, TLSX_SUPPORTED_GROUPS); + if (ext != NULL) + ExpectIntEQ(ext->resp, 0); + } + ExpectNotNull(ext); + test_brainpool_count(ext, tls13Cnt, tls12Cnt); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#define TEST_BP_MAX_EXTS 64 + +/* Lists a ClientHello's extension types, less those allowed to change after a + * HelloRetryRequest and supported_versions, and finds supported_groups. */ +static int test_bp_parse_ch(const byte* buf, int len, word16* types, + int* typeCnt, const byte** groups, word16* groupsLen) +{ + int idx = 0; + int end; + word16 recLen; + word16 extLen; + word16 type; + + *typeCnt = 0; + *groups = NULL; + *groupsLen = 0; + while (idx + RECORD_HEADER_SZ <= len && buf[idx] != handshake) { + ato16(buf + idx + 3, &recLen); + idx += RECORD_HEADER_SZ + recLen; + } + if (idx + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ > len || + buf[idx + RECORD_HEADER_SZ] != client_hello) { + return -1; + } + ato16(buf + idx + 3, &recLen); + end = idx + RECORD_HEADER_SZ + recLen; + if (end > len) + return -1; + idx += RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + VERSION_SZ + RAN_LEN; + if (idx >= end) + return -1; + idx += 1 + buf[idx]; + if (idx + OPAQUE16_LEN > end) + return -1; + ato16(buf + idx, &extLen); + idx += OPAQUE16_LEN + extLen; + if (idx >= end) + return -1; + idx += 1 + buf[idx]; + idx += OPAQUE16_LEN; + while (idx + 2 * OPAQUE16_LEN <= end && *typeCnt < TEST_BP_MAX_EXTS) { + ato16(buf + idx, &type); + ato16(buf + idx + OPAQUE16_LEN, &extLen); + if (idx + 2 * OPAQUE16_LEN + extLen > end) + return -1; + if (type == TLSXT_SUPPORTED_GROUPS) { + *groups = buf + idx + 2 * OPAQUE16_LEN; + *groupsLen = extLen; + } + if (type != TLSXT_KEY_SHARE && type != TLSXT_EARLY_DATA && + type != TLSXT_COOKIE && type != TLSXT_PRE_SHARED_KEY && + type != TLSXT_ECH && type != TLSXT_SUPPORTED_VERSIONS) { + types[(*typeCnt)++] = type; + } + idx += 2 * OPAQUE16_LEN + extLen; + } + return (*groups != NULL && idx == end) ? 0 : -1; +} + +/* Compares the ClientHellos before and after a HelloRetryRequest with ECH and + * counts the brainpool codepoints in the second one. */ +static int test_brainpool_ech_hrr(int accept, int* tls13Cnt, int* tls12Cnt) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL_CTX* ctx_o = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + byte ch1[TEST_MEMIO_BUF_SZ]; + int ch1Len = 0; + word16 types1[TEST_BP_MAX_EXTS]; + word16 types2[TEST_BP_MAX_EXTS]; + int cnt1 = 0; + int cnt2 = 0; + const byte* groups1 = NULL; + const byte* groups2 = NULL; + word16 groupsLen1 = 0; + word16 groupsLen2 = 0; + word16 name; + int innerTls13Cnt = 0; + int innerTls12Cnt = 0; + int i; + int j; + + *tls13Cnt = 0; + *tls12Cnt = 0; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfSSLv23_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + if (accept) { + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + } + else { + /* Keys the server does not hold, so it rejects ECH. */ + ExpectNotNull(ctx_o = wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_o, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_o, configs, &configsLen), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (EXPECT_SUCCESS()) { + ch1Len = test_ctx.s_len; + XMEMCPY(ch1, test_ctx.s_buff, (size_t)ch1Len); + } + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->options.serverState, + SERVER_HELLO_RETRY_REQUEST_COMPLETE); + } + + ExpectIntEQ(test_bp_parse_ch(ch1, ch1Len, types1, &cnt1, &groups1, + &groupsLen1), 0); + ExpectIntEQ(test_bp_parse_ch(test_ctx.s_buff, test_ctx.s_len, types2, + &cnt2, &groups2, &groupsLen2), 0); + ExpectIntEQ(cnt1, cnt2); + if (EXPECT_SUCCESS()) + ExpectIntEQ(XMEMCMP(types1, types2, (size_t)cnt1 * sizeof(word16)), 0); + ExpectIntEQ(groupsLen1, groupsLen2); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(XMEMCMP(groups1, groups2, groupsLen1), 0); + for (i = OPAQUE16_LEN; i + OPAQUE16_LEN <= groupsLen2; + i += OPAQUE16_LEN) { + ato16(groups2 + i, &name); + for (j = 0; j < TEST_BP_CNT; j++) { + if (name == test_bp_tls13[j]) + (*tls13Cnt)++; + else if (name == test_bp_tls12[j]) + (*tls12Cnt)++; + } + } + } + + if (accept) { + if (ssl_c != NULL) { + test_brainpool_count(TLSX_Find(ssl_c->extensions, + TLSX_SUPPORTED_GROUPS), &innerTls13Cnt, &innerTls12Cnt); + } + ExpectIntEQ(innerTls13Cnt, TEST_BP_CNT); + ExpectIntEQ(innerTls12Cnt, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_GetEchStatus(ssl_c), WOLFSSL_ECH_STATUS_ACCEPTED); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_o); + return EXPECT_RESULT(); +} +#endif #endif /* The RFC 7027 brainpool codepoints are offered only when TLS 1.2 or older can @@ -3845,6 +4082,29 @@ int test_TLSX_SupportedCurve_brainpool_downgrade(void) ExpectIntEQ(tls13Cnt, TEST_BP_CNT); ExpectIntEQ(tls12Cnt, 0); #endif + +#if defined(HAVE_ECH) && defined(HAVE_SNI) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) && !defined(NO_WOLFSSL_SERVER) + ExpectIntEQ(test_brainpool_ech_offer(wolfSSLv23_server_method, &tls13Cnt, + &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, 0); + + ExpectIntEQ(test_brainpool_ech_offer(wolfTLSv1_2_server_method, &tls13Cnt, + &tls12Cnt), TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); + + ExpectIntEQ(test_brainpool_ech_hrr(1, &tls13Cnt, &tls12Cnt), + TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); + + ExpectIntEQ(test_brainpool_ech_hrr(0, &tls13Cnt, &tls12Cnt), + TEST_SUCCESS); + ExpectIntEQ(tls13Cnt, TEST_BP_CNT); + ExpectIntEQ(tls12Cnt, TEST_BP_CNT); +#endif #endif return EXPECT_RESULT(); } From deec546253ca3e45f7919f7a2f2a1c2546c66e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 20:04:40 +0200 Subject: [PATCH 04/17] Address review on DTLS 1.3 downgrade and ECH outer hello wolfSSL_set_options(): when a later call masks DTLS 1.2 on an SSL that an earlier WOLFSSL_OP_NO_TLSv1_3 moved there and no older DTLS version is left, put the SSL back on DTLS 1.3 so the handshake fails, as it does after one combined call. OpenSSL 4.1 checks the accumulated mask when the handshake starts, so the order of the calls does not matter there either. The DTLS branch now leaves the version alone once the handshake has started. Add SSL_OP_NO_DTLSv1, SSL_OP_NO_DTLSv1_2 and SSL_OP_NO_DTLSv1_3. OpenSSL 4.1, the first release with DTLS 1.3, defines SSL_OP_NO_DTLSv1_3 as the SSL_OP_NO_TLSv1_3 bit. SSL_OP_NO_DTLSv1 maps to WOLFSSL_OP_NO_TLSv1_1, the option wolfSSL masks DTLS 1.0 with. TLSX_EchSplitSupportedGroups() removed the supported_groups extension and pushed the inner list back at the head of ssl->extensions, so a real ECH outer ClientHello wrote supported_groups first and could be told apart from a GREASE ECH one by its extension order. It now builds the inner list in a temporary list and swaps only the data into the existing extension. DoServerHello(): a ServerHello for TLS 1.1 or older after a HelloRetryRequest that accepted ECH now fails with illegal_parameter, as a TLS 1.3 ServerHello that rejects ECH after an accepting HelloRetryRequest does (RFC 9849 section 6.1.5). Otherwise the outer extensions are swapped back in. A TLS 1.2 ServerHello there already fails the HelloRetryRequest cipher suite check in DoTls13ServerHello(). Tests: separate set_options calls that leave no DTLS version, set_options after the handshake, the SSL_OP_NO_DTLS names, the extension order of a real ECH outer ClientHello against a GREASE ECH one, and test_tls13_ech_hrr_tls11_server_hello. --- src/internal.c | 9 ++++- src/ssl.c | 5 ++- src/tls.c | 23 ++++++++++-- tests/api/test_dtls.c | 9 +++++ tests/api/test_tls13.c | 80 ++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 + tests/api/test_tls_ext.c | 64 ++++++++++++++++++++++++++++++++ wolfssl/ssl.h | 5 +++ 8 files changed, 190 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index d6f43cb6ec5..b9856bc2f5a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -37700,7 +37700,14 @@ static int IsTls13CipherSuite(byte first, byte second) #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) /* A server negotiating TLS 1.2 or older answers the outer hello. */ - ret = TLSX_EchReplaceExtensions(ssl, ssl->options.echAccepted); + if (ssl->options.echAccepted) { + WOLFSSL_MSG("ECH accepted in HelloRetryRequest, ServerHello is " + "not TLS 1.3"); + SendAlert(ssl, alert_fatal, illegal_parameter); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + return INVALID_PARAMETER; + } + ret = TLSX_EchReplaceExtensions(ssl, 0); if (ret != 0) return ret; #endif diff --git a/src/ssl.c b/src/ssl.c index e54450c773c..ab921f09e27 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7618,14 +7618,15 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) ssl->version.minor = TLSv1_2_MINOR; #ifdef WOLFSSL_DTLS13 else if (ssl->version.major == DTLS_MAJOR && ssl->options.downgrade && + ssl->options.handShakeState == NULL_STATE && (ssl->version.minor == DTLSv1_3_MINOR || (ssl->version.minor == DTLSv1_2_MINOR && (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2))) { byte minor = Dtls13DowngradeMinor(ssl, ssl->options.minDowngrade); - if (minor != 0) - ssl->version.minor = minor; + /* With no older version left, DTLS 1.3 fails the handshake. */ + ssl->version.minor = (minor != 0) ? minor : DTLSv1_3_MINOR; } #endif } diff --git a/src/tls.c b/src/tls.c index e5a018e50a5..d14deb26087 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16761,12 +16761,16 @@ static int TLSX_EchSplitSupportedGroups(WOLFSSL* ssl) int ret; byte downgrade; TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH); + TLSX* groupsX = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + TLSX* innerList = NULL; + TLSX* innerX = NULL; + void* data; WOLFSSL_ECH* ech; /* After a HelloRetryRequest the lists stay as the first hello sent them. */ - if (echX == NULL || echX->data == NULL || ssl->options.userCurves || + if (echX == NULL || echX->data == NULL || groupsX == NULL || + ssl->options.userCurves || ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE || - TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS) == NULL || !TLSX_PreTls13Reachable(ssl)) { return 0; } @@ -16774,12 +16778,23 @@ static int TLSX_EchSplitSupportedGroups(WOLFSSL* ssl) ech = (WOLFSSL_ECH*)echX->data; ret = TLSX_PopulateSupportedGroups(ssl, &ech->extensions); if (ret == WOLFSSL_SUCCESS) { - TLSX_Remove(&ssl->extensions, TLSX_SUPPORTED_GROUPS, ssl->heap); downgrade = ssl->options.downgrade; ssl->options.downgrade = 0; - ret = TLSX_PopulateSupportedGroups(ssl, &ssl->extensions); + ret = TLSX_PopulateSupportedGroups(ssl, &innerList); ssl->options.downgrade = downgrade; } + if (ret == WOLFSSL_SUCCESS) { + innerX = TLSX_Find(innerList, TLSX_SUPPORTED_GROUPS); + if (innerX == NULL) + ret = BAD_STATE_E; + } + if (ret == WOLFSSL_SUCCESS) { + /* Swap only the data so the outer hello keeps the GREASE ECH order. */ + data = groupsX->data; + groupsX->data = innerX->data; + innerX->data = data; + } + TLSX_FreeAll(innerList, ssl->heap); return (ret == WOLFSSL_SUCCESS) ? 0 : ret; } diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index a3e70e07a89..6299a934ca9 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16581,6 +16581,9 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, ExpectIntEQ(ret, 0); ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); ExpectIntEQ(wolfSSL_version(ssl_s), expVersion); + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1); + ExpectIntEQ(wolfSSL_version(ssl), expVersion); } else { ExpectIntNE(ret, 0); @@ -16643,9 +16646,15 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_1, 0, 0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_1, 0, 0), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, 0, WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); } +#ifndef OPENSSL_COEXIST + ExpectIntEQ(test_dtls13_mask_case(0, 0, SSL_OP_NO_DTLSv1_3, + SSL_OP_NO_DTLSv1_2 | SSL_OP_NO_DTLSv1, 0, 0), TEST_SUCCESS); +#endif /* A DTLS 1.3-only method cannot move down. */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 5a78ed63d7e..3a5c73a043b 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8454,6 +8454,86 @@ int test_tls13_ech_hrr_coalesced_server_hello(void) return EXPECT_RESULT(); } +/* A TLS 1.1 ServerHello after a HelloRetryRequest that accepted ECH is + * rejected, like a TLS 1.3 one that rejects ECH (RFC 9849 6.1.5). */ +int test_tls13_ech_hrr_tls11_server_hello(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && defined(HAVE_SNI) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) && \ + !defined(NO_OLD_TLS) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL_CTX* ctx_c11 = NULL; + WOLFSSL_CTX* ctx_s11 = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL* ssl_c11 = NULL; + WOLFSSL* ssl_s11 = NULL; + struct test_memio_ctx test_ctx; + struct test_memio_ctx test_ctx11; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&test_ctx11, 0, sizeof(test_ctx11)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfSSLv23_server_method), 0); + ExpectIntEQ(test_memio_setup(&test_ctx11, &ctx_c11, &ctx_s11, &ssl_c11, + &ssl_s11, wolfSSLv23_client_method, wolfTLSv1_1_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, ECH_TEST_PUB_NAME, 0, 0, + 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, + ECH_TEST_PRIV_NAME, (word16)XSTRLEN(ECH_TEST_PRIV_NAME)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, WOLFSSL_TLSV1_1), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_memio_msg_is_hello_retry_request(&test_ctx), 1); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.echAccepted, 1); + + /* The second ClientHello goes to a TLS 1.1 server. */ + ExpectIntEQ(test_memio_inject_message(&test_ctx11, 0, + (const char*)test_ctx.s_buff, test_ctx.s_len), 0); + ExpectIntEQ(wolfSSL_accept(ssl_s11), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s11, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, + (const char*)test_ctx11.c_buff, test_ctx11.c_len), 0); + + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WC_NO_ERR_TRACE(INVALID_PARAMETER)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c11); + wolfSSL_free(ssl_s11); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c11); + wolfSSL_CTX_free(ctx_s11); +#endif + return EXPECT_RESULT(); +} + /* The same offsets against a ServerHello reassembled from two records, where * input points at the message body: ECH acceptance used to read 4 bytes past * ssl->pendingMsg and report a rejection the server never sent. */ diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 9bb51900267..f3ec056d943 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -58,6 +58,7 @@ int test_tls13_duplicate_extension(void); int test_tls13_duplicate_ech_extension(void); int test_tls13_ech_hrr_coalesced_server_hello(void); int test_tls13_ech_fragmented_server_hello(void); +int test_tls13_ech_hrr_tls11_server_hello(void); int test_key_share_mismatch(void); int test_key_share_mismatch_psk_dhe(void); int test_tls13_middlebox_compat_empty_session_id(void); @@ -250,6 +251,7 @@ int test_tls13_exporter_suite_decision_coverage(void); TEST_DECL_GROUP("tls13", test_tls13_duplicate_ech_extension), \ TEST_DECL_GROUP("tls13", test_tls13_ech_hrr_coalesced_server_hello), \ TEST_DECL_GROUP("tls13", test_tls13_ech_fragmented_server_hello), \ + TEST_DECL_GROUP("tls13", test_tls13_ech_hrr_tls11_server_hello), \ TEST_DECL_GROUP("tls13", test_key_share_mismatch), \ TEST_DECL_GROUP("tls13", test_key_share_mismatch_psk_dhe), \ TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_empty_session_id), \ diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index ec8f2cbb2fb..b83f2715fed 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3975,6 +3975,68 @@ static int test_brainpool_ech_hrr(int accept, int* tls13Cnt, int* tls12Cnt) wolfSSL_CTX_free(ctx_o); return EXPECT_RESULT(); } + +/* Lists the extension types of the first ClientHello with real ECH, or with + * GREASE ECH when real is 0. */ +static int test_bp_ech_ch_types(int real, word16* types, int* typeCnt) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + static const char pubName[] = "ech-public-name.com"; + static const char privName[] = "ech-private-name.com"; + byte configs[512]; + word32 configsLen = (word32)sizeof(configs); + const byte* groups = NULL; + word16 groupsLen = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfSSLv23_server_method), 0); + if (real) { + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx_s, pubName, 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx_s, configs, &configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, privName, + (word16)XSTRLEN(privName)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_bp_parse_ch(test_ctx.s_buff, test_ctx.s_len, types, + typeCnt, &groups, &groupsLen), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A real ECH outer ClientHello orders its extensions like a GREASE one. */ +static int test_brainpool_ech_order(void) +{ + EXPECT_DECLS; + word16 realTypes[TEST_BP_MAX_EXTS]; + word16 greaseTypes[TEST_BP_MAX_EXTS]; + int realCnt = 0; + int greaseCnt = 0; + + ExpectIntEQ(test_bp_ech_ch_types(1, realTypes, &realCnt), TEST_SUCCESS); + ExpectIntEQ(test_bp_ech_ch_types(0, greaseTypes, &greaseCnt), + TEST_SUCCESS); + ExpectIntEQ(realCnt, greaseCnt); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(XMEMCMP(realTypes, greaseTypes, + (size_t)realCnt * sizeof(word16)), 0); + } + return EXPECT_RESULT(); +} #endif #endif @@ -4104,6 +4166,8 @@ int test_TLSX_SupportedCurve_brainpool_downgrade(void) TEST_SUCCESS); ExpectIntEQ(tls13Cnt, TEST_BP_CNT); ExpectIntEQ(tls12Cnt, TEST_BP_CNT); + + ExpectIntEQ(test_brainpool_ech_order(), TEST_SUCCESS); #endif #endif return EXPECT_RESULT(); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ad14fcf5ab4..0bd4ec2094f 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3074,6 +3074,11 @@ enum { #define SSL_OP_NO_TLSv1_3 WOLFSSL_OP_NO_TLSv1_3 #endif +/* DTLS 1.0 is masked by the TLS 1.1 option. */ +#define SSL_OP_NO_DTLSv1 WOLFSSL_OP_NO_TLSv1_1 +#define SSL_OP_NO_DTLSv1_2 WOLFSSL_OP_NO_TLSv1_2 +#define SSL_OP_NO_DTLSv1_3 WOLFSSL_OP_NO_TLSv1_3 + #ifdef HAVE_SESSION_TICKET #define SSL_OP_NO_TICKET WOLFSSL_OP_NO_TICKET #endif From ee8700e6dc2984eebad06d0007a7e5dc4e748427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 20:22:06 +0200 Subject: [PATCH 05/17] Check the DTLS version mask when the handshake starts wolfSSL_set_options() moved a DTLS SSL to an older version as each option call came in, so the result depended on the order of the calls: three separate calls left a masked DTLS 1.0, a server put back on DTLS 1.3 when nothing was left negotiated it anyway because the server never checks its own mask, and a call made during the handshake still changed the version. Do it the way OpenSSL 4.1 does instead and look at the accumulated mask once, when wolfSSL_connect() or wolfSSL_accept() starts a handshake. DtlsCheckVersionMask() moves a DTLS SSL that may downgrade to the newest built version that the mask and minimum allow, or fails with VERSION_ERROR when none is left. wolfSSL_set_options() no longer changes the DTLS version. Methods that cannot downgrade keep their version, as OpenSSL's fixed-version methods ignore the mask too. A DTLS server that steps down to the client's version in DoClientHello() now checks that version against the mask as well, the way the TLS path already walks it, and refuses with protocol_version when no allowed version is left. Before, a DTLS server negotiated a masked DTLS 1.2 or 1.0 with a client that offered only that version. Dtls13DowngradeMinor() becomes DtlsAllowedMinor(), which also takes the newest version to start from and is built with DTLS 1.2 alone. The ECH check in DoServerHello() now applies only when this handshake had a HelloRetryRequest. wolfSSL_clear() keeps echAccepted, so a reused SSL whose last handshake accepted ECH failed a TLS 1.2 or older fallback. An empty inner groups list no longer fails the ECH split. SSL_OP_NO_DTLSv1_3 is defined only when DTLS 1.3 is built, like SSL_OP_NO_TLSv1_3. test_dtls13_no_tls13_option now runs every mask case on the client and the server, through SSL and CTX, with up to three separate calls, and checks the version after the handshake. It adds a DTLS 1.2-only client against a server that masks DTLS 1.2, and checks that a DTLS 1.3-only method and wolfSSL_require_psk() keep DTLS 1.3 through wolfSSL_connect(). test_tls13_ech_hrr_tls11_server_hello adds a TLS 1.1 fallback with a stale echAccepted. --- src/internal.c | 66 ++++++++++++++++++++++---- src/ssl.c | 13 ------ src/ssl_api_hs.c | 18 ++++++++ src/tls.c | 7 +-- tests/api/test_dtls.c | 102 +++++++++++++++++++++++++++-------------- tests/api/test_tls13.c | 23 ++++++++++ wolfssl/internal.h | 5 +- wolfssl/ssl.h | 2 + 8 files changed, 174 insertions(+), 62 deletions(-) diff --git a/src/internal.c b/src/internal.c index b9856bc2f5a..6a103f66ef0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8210,13 +8210,20 @@ int InitSSL_Suites(WOLFSSL* ssl) writeDup flag indicating this is a write dup only WOLFSSL_SUCCESS return value on success */ -#ifdef WOLFSSL_DTLS13 -/* Returns the newest DTLS minor before 1.3 that the version mask and - * minDowngrade allow, or 0. DTLS minors count down. */ -byte Dtls13DowngradeMinor(const WOLFSSL* ssl, byte minDowngrade) +#ifdef WOLFSSL_DTLS +/* Returns the newest built DTLS minor, no newer than maxMinor, that the version + * mask and minDowngrade allow, or 0. DTLS minors count down. */ +byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) { +#ifdef WOLFSSL_DTLS13 + if (maxMinor <= DTLSv1_3_MINOR && + (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_3) == 0) { + return DTLSv1_3_MINOR; + } +#endif #ifndef WOLFSSL_NO_TLS12 - if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == 0 && + if (maxMinor <= DTLSv1_2_MINOR && + (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == 0 && minDowngrade >= DTLSv1_2_MINOR) { return DTLSv1_2_MINOR; } @@ -8228,9 +8235,31 @@ byte Dtls13DowngradeMinor(const WOLFSSL* ssl, byte minDowngrade) } #endif (void)ssl; + (void)maxMinor; (void)minDowngrade; return 0; } + +/* Moves a DTLS SSL that may downgrade to the newest version its mask allows. + * Returns VERSION_ERROR when none is left. */ +int DtlsCheckVersionMask(WOLFSSL* ssl) +{ + byte minor; + + if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || + ssl->options.mask == 0) { + return 0; + } + minor = DtlsAllowedMinor(ssl, ssl->version.minor, + ssl->options.minDowngrade); + if (minor == 0) { + WOLFSSL_MSG("No DTLS version left that the options allow"); + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); + return VERSION_ERROR; + } + ssl->version.minor = minor; + return 0; +} #endif #ifdef OPENSSL_EXTRA @@ -8259,7 +8288,7 @@ static int SetSSL_CTX_CheckVersion(WOLFSSL* ssl, WOLFSSL_CTX* ctx) byte minor = 0; if (ctx->method->downgrade) - minor = Dtls13DowngradeMinor(ssl, ctx->minDowngrade); + minor = DtlsAllowedMinor(ssl, DTLSv1_2_MINOR, ctx->minDowngrade); if (minor == 0) { WOLFSSL_MSG("\tInconsistent protocol options. DTLS 1.3 set but " "not allowed and no lower DTLS version allowed."); @@ -37699,14 +37728,15 @@ static int IsTls13CipherSuite(byte first, byte second) #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) - /* A server negotiating TLS 1.2 or older answers the outer hello. */ - if (ssl->options.echAccepted) { + if (ssl->options.echAccepted && ssl->options.serverState == + SERVER_HELLO_RETRY_REQUEST_COMPLETE) { WOLFSSL_MSG("ECH accepted in HelloRetryRequest, ServerHello is " "not TLS 1.3"); SendAlert(ssl, alert_fatal, illegal_parameter); WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); return INVALID_PARAMETER; } + /* A server negotiating TLS 1.2 or older answers the outer hello. */ ret = TLSX_EchReplaceExtensions(ssl, 0); if (ret != 0) return ret; @@ -44427,6 +44457,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (lesserVersion) { byte belowMinDowngrade; + #ifdef WOLFSSL_DTLS + byte dtlsMinor; + #endif word16 haveRSA = 0; word16 havePSK = 0; int keySz = 0; @@ -44500,6 +44533,23 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ssl->options.tls1_3 = 0; ssl->version.minor = DTLS_MINOR; } + #ifdef WOLFSSL_DTLS + dtlsMinor = DtlsAllowedMinor(ssl, ssl->version.minor, + ssl->options.minDowngrade); + if (dtlsMinor == 0) { + WOLFSSL_MSG("\tNo DTLS version left that the options " + "allow"); + ret = VERSION_ERROR; + /* propagate socket errors to avoid re-calling send alert */ + if (SendAlert(ssl, alert_fatal, + wolfssl_alert_protocol_version) + == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + ret = SOCKET_ERROR_E; + } + goto out; + } + ssl->version.minor = dtlsMinor; + #endif } #ifndef NO_RSA haveRSA = 1; diff --git a/src/ssl.c b/src/ssl.c index ab921f09e27..e3890aeeba9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7616,19 +7616,6 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) WOLFSSL_MSG("Disabling TLS 1.3"); if (ssl->version.minor == TLSv1_3_MINOR) ssl->version.minor = TLSv1_2_MINOR; - #ifdef WOLFSSL_DTLS13 - else if (ssl->version.major == DTLS_MAJOR && ssl->options.downgrade && - ssl->options.handShakeState == NULL_STATE && - (ssl->version.minor == DTLSv1_3_MINOR || - (ssl->version.minor == DTLSv1_2_MINOR && - (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == - WOLFSSL_OP_NO_TLSv1_2))) { - byte minor = Dtls13DowngradeMinor(ssl, - ssl->options.minDowngrade); - /* With no older version left, DTLS 1.3 fails the handshake. */ - ssl->version.minor = (minor != 0) ? minor : DTLSv1_3_MINOR; - } - #endif } if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2) { diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index 257951af4a1..8cf42116f0b 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -394,6 +394,15 @@ int wolfSSL_connect(WOLFSSL* ssl) #endif #endif /* OPENSSL_EXTRA || WOLFSSL_EITHER_SIDE */ + #ifdef WOLFSSL_DTLS + if (ssl->options.connectState == CONNECT_BEGIN && + (ret = DtlsCheckVersionMask(ssl)) != 0) { + ssl->error = ret; + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + #endif + #if defined(WOLFSSL_NO_TLS12) && defined(NO_OLD_TLS) && \ defined(WOLFSSL_TLS13) return wolfSSL_connect_TLSv13(ssl); @@ -882,6 +891,15 @@ int wolfSSL_accept(WOLFSSL* ssl) } #endif /* OPENSSL_EXTRA || WOLFSSL_EITHER_SIDE */ + #ifdef WOLFSSL_DTLS + if (ssl->options.acceptState == ACCEPT_BEGIN && + (ret = DtlsCheckVersionMask(ssl)) != 0) { + ssl->error = ret; + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + #endif + #if defined(WOLFSSL_NO_TLS12) && defined(NO_OLD_TLS) && \ defined(WOLFSSL_TLS13) return wolfSSL_accept_TLSv13(ssl); diff --git a/src/tls.c b/src/tls.c index d14deb26087..e7c96614d61 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16783,12 +16783,9 @@ static int TLSX_EchSplitSupportedGroups(WOLFSSL* ssl) ret = TLSX_PopulateSupportedGroups(ssl, &innerList); ssl->options.downgrade = downgrade; } - if (ret == WOLFSSL_SUCCESS) { + if (ret == WOLFSSL_SUCCESS) innerX = TLSX_Find(innerList, TLSX_SUPPORTED_GROUPS); - if (innerX == NULL) - ret = BAD_STATE_E; - } - if (ret == WOLFSSL_SUCCESS) { + if (innerX != NULL) { /* Swap only the data so the outer hello keeps the GREASE ECH order. */ data = groupsX->data; groupsX->data = innerX->data; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 6299a934ca9..e9891411ca2 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16535,10 +16535,10 @@ int test_dtls_set_rfd_wfd(void) #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ defined(WOLFSSL_DTLS13) && defined(OPENSSL_EXTRA) -/* Sets opts, then opts2 in a second call, and minVer when not 0, on the client - * or server SSL or CTX. An expVersion of 0 means no DTLS version is left. */ +/* Sets opts, opts2 and opts3 in separate calls, and minVer when not 0, on the + * client or server SSL or CTX. An expVersion of 0 means no version is left. */ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, - long opts2, int minVer, int expVersion) + long opts2, long opts3, int minVer, int expVersion) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -16557,6 +16557,7 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, if (onCtx) { (void)wolfSSL_CTX_set_options(ctx, opts); (void)wolfSSL_CTX_set_options(ctx, opts2); + (void)wolfSSL_CTX_set_options(ctx, opts3); } ret = test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLS_client_method, wolfDTLS_server_method); @@ -16567,14 +16568,15 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, else { ExpectIntEQ(ret, 0); ssl = onServer ? ssl_s : ssl_c; + if (onCtx) + ExpectIntEQ(wolfSSL_version(ssl), expVersion); if (!onCtx && minVer != 0) ExpectIntEQ(wolfSSL_SetMinVersion(ssl, minVer), WOLFSSL_SUCCESS); if (!onCtx && ssl != NULL) { (void)wolfSSL_set_options(ssl, opts); (void)wolfSSL_set_options(ssl, opts2); + (void)wolfSSL_set_options(ssl, opts3); } - ExpectIntEQ(wolfSSL_version(ssl), - expVersion != 0 ? expVersion : DTLS1_3_VERSION); ret = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); if (expVersion != 0) { @@ -16587,6 +16589,8 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, } else { ExpectIntNE(ret, 0); + ExpectIntEQ(wolfSSL_get_error(ssl, ret), + WC_NO_ERR_TRACE(VERSION_ERROR)); } } @@ -16596,6 +16600,34 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, wolfSSL_CTX_free(ctx_s); return EXPECT_RESULT(); } + +#ifndef WOLFSSL_NO_TLS12 +/* A DTLS server whose mask drops DTLS 1.2 refuses a DTLS 1.2-only client. */ +static int test_dtls13_mask_server_walk(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, wolfDTLS_server_method), 0); + if (ssl_s != NULL) { + (void)wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1); + } + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif #endif /* WOLFSSL_OP_NO_TLSv1_3 moves a DTLS 1.3 client or server to the newest older @@ -16610,56 +16642,57 @@ int test_dtls13_no_tls13_option(void) defined(WOLFSSL_DTLS13) && defined(OPENSSL_EXTRA) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; -#ifndef WOLFSSL_NO_TLS12 int onServer; -#endif int onCtx; #ifndef NO_OLD_TLS int dtls10 = DTLS1_VERSION; #else int dtls10 = 0; #endif - #ifndef WOLFSSL_NO_TLS12 + int dtls12 = DTLS1_2_VERSION; +#else + int dtls12 = 0; +#endif + for (onServer = 0; onServer <= 1; onServer++) { for (onCtx = 0; onCtx <= 1; onCtx++) { ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, - WOLFSSL_OP_NO_TLSv1_3, 0, 0, DTLS1_2_VERSION), TEST_SUCCESS); + WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0, dtls12), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, + WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2, 0, 0, 0, + dtls10), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, + WOLFSSL_OP_NO_TLSv1_3, WOLFSSL_OP_NO_TLSv1_2, 0, 0, dtls10), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, + WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1, 0, 0, 0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, + WOLFSSL_OP_NO_TLSv1_3, WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1, 0, 0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_case(onServer, onCtx, + WOLFSSL_OP_NO_TLSv1_3, WOLFSSL_OP_NO_TLSv1_2, + WOLFSSL_OP_NO_TLSv1_1, 0, 0), TEST_SUCCESS); } + ExpectIntEQ(test_dtls13_mask_case(onServer, 0, WOLFSSL_OP_NO_TLSv1_3, + 0, 0, WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); } -#else - ExpectIntEQ(test_dtls13_mask_case(0, 0, WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0), - TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_case(0, 1, WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0), - TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_case(1, 1, WOLFSSL_OP_NO_TLSv1_3, 0, 0, 0), - TEST_SUCCESS); -#endif - for (onCtx = 0; onCtx <= 1; onCtx++) { - ExpectIntEQ(test_dtls13_mask_case(0, onCtx, - WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2, 0, 0, dtls10), - TEST_SUCCESS); -#ifndef NO_OLD_TLS - ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, - WOLFSSL_OP_NO_TLSv1_2, 0, DTLS1_VERSION), TEST_SUCCESS); -#endif - ExpectIntEQ(test_dtls13_mask_case(0, onCtx, - WOLFSSL_OP_NO_TLSv1_3 | WOLFSSL_OP_NO_TLSv1_2 | - WOLFSSL_OP_NO_TLSv1_1, 0, 0, 0), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, - WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_1, 0, 0), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_case(0, onCtx, WOLFSSL_OP_NO_TLSv1_3, 0, - WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); - } + ExpectIntEQ(test_dtls13_mask_case(0, 1, WOLFSSL_OP_NO_TLSv1_3, 0, 0, + WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); #ifndef OPENSSL_COEXIST ExpectIntEQ(test_dtls13_mask_case(0, 0, SSL_OP_NO_DTLSv1_3, - SSL_OP_NO_DTLSv1_2 | SSL_OP_NO_DTLSv1, 0, 0), TEST_SUCCESS); + SSL_OP_NO_DTLSv1_2, SSL_OP_NO_DTLSv1, 0, 0), TEST_SUCCESS); +#endif +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(test_dtls13_mask_server_walk(), TEST_SUCCESS); #endif /* A DTLS 1.3-only method cannot move down. */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + (void)wolfSSL_connect(ssl); ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); wolfSSL_free(ssl); ssl = NULL; @@ -16675,6 +16708,7 @@ int test_dtls13_no_tls13_option(void) ExpectNotNull(ssl = wolfSSL_new(ctx)); ExpectIntEQ(wolfSSL_require_psk(ssl), 0); (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + (void)wolfSSL_connect(ssl); ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 3a5c73a043b..539104e1db6 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8530,6 +8530,29 @@ int test_tls13_ech_hrr_tls11_server_hello(void) wolfSSL_CTX_free(ctx_s); wolfSSL_CTX_free(ctx_c11); wolfSSL_CTX_free(ctx_s11); + ssl_c = NULL; + ssl_s = NULL; + ctx_c = NULL; + ctx_s = NULL; + + /* Without a HelloRetryRequest an echAccepted left from an earlier + * connection does not stop the TLS 1.1 fallback. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfTLSv1_1_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl_c, configs, configsLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, WOLFSSL_TLSV1_1), WOLFSSL_SUCCESS); + if (ssl_c != NULL) + ssl_c->options.echAccepted = 1; + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_1_VERSION); + + 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/wolfssl/internal.h b/wolfssl/internal.h index 6fb51e6ee09..5987a7b53f5 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8470,14 +8470,15 @@ WOLFSSL_LOCAL int DtlsUpdateWindow(WOLFSSL* ssl); WOLFSSL_LOCAL void DtlsResetState(WOLFSSL *ssl); WOLFSSL_LOCAL int DtlsIgnoreError(int err); WOLFSSL_LOCAL void DtlsSetSeqNumForReply(WOLFSSL* ssl); +WOLFSSL_LOCAL byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, + byte minDowngrade); +WOLFSSL_LOCAL int DtlsCheckVersionMask(WOLFSSL* ssl); #ifndef NO_WOLFSSL_SERVER WOLFSSL_LOCAL int DtlsNoCookieChGood(WOLFSSL* ssl); #endif #endif #ifdef WOLFSSL_DTLS13 -WOLFSSL_LOCAL byte Dtls13DowngradeMinor(const WOLFSSL* ssl, - byte minDowngrade); #ifdef WOLFSSL_API_PREFIX_MAP #define Dtls13GetEpoch wolfSSL_Dtls13GetEpoch #define Dtls13NewEpoch wolfSSL_Dtls13NewEpoch diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 0bd4ec2094f..62dd4ce6dec 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3077,7 +3077,9 @@ enum { /* DTLS 1.0 is masked by the TLS 1.1 option. */ #define SSL_OP_NO_DTLSv1 WOLFSSL_OP_NO_TLSv1_1 #define SSL_OP_NO_DTLSv1_2 WOLFSSL_OP_NO_TLSv1_2 +#ifdef WOLFSSL_DTLS13 #define SSL_OP_NO_DTLSv1_3 WOLFSSL_OP_NO_TLSv1_3 +#endif #ifdef HAVE_SESSION_TICKET #define SSL_OP_NO_TICKET WOLFSSL_OP_NO_TICKET From 2111f43feb5a2c669204f92195de7add2d8d71ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 20:42:07 +0200 Subject: [PATCH 06/17] Rebuild suites and cover all entry points in the DTLS mask check DtlsCheckVersionMask() lowered the version without rebuilding the cipher suites, so a client or server moved to DTLS 1.2 or 1.0 kept the list built for DTLS 1.3 and could negotiate a TLS 1.3 or TLS 1.2-only suite at DTLS 1.0. The suite rebuild at the end of wolfSSL_set_options() moves into ResetSuitesForVersion(), unchanged, and DtlsCheckVersionMask() calls it after a move. wolfSSL_negotiate(), which wolfSSL_read() and wolfSSL_write() use, and callers of wolfSSL_connect_TLSv13() or wolfSSL_accept_TLSv13() went straight to the TLS 1.3 state machines and skipped the check. Both now run it at the start of the handshake and hand over to wolfSSL_connect() or wolfSSL_accept() when the version drops below DTLS 1.3. The check keeps the current version whenever the mask allows it and only applies the minimum when it has to step down, so a DTLS context left with a TLS-numbered minimum by wolfSSL_CTX_set_min_proto_version(ctx, 0) no longer fails every handshake once any option bit is set. It skips SSLs that already completed a handshake, so a renegotiation cannot change the version. A DTLS client now also checks the version the server chose against its own mask in CheckVersion(), as the TLS client does. Before, a client that masked DTLS 1.2 still completed a DTLS 1.2 handshake with a server that only spoke DTLS 1.2. Tests: no TLS 1.3 suite is left after a move, a handshake driven by wolfSSL_negotiate() on a masked server, a client masking DTLS 1.2 against a DTLS 1.2-only server, and builds without TLS 1.2 but with old TLS expect DTLS 1.0 instead of no version. --- src/internal.c | 31 ++++------ src/ssl.c | 135 ++++++++++++++++++++++++++---------------- src/tls13.c | 28 +++++++++ tests/api/test_dtls.c | 77 +++++++++++++++++++++++- 4 files changed, 199 insertions(+), 72 deletions(-) diff --git a/src/internal.c b/src/internal.c index 6a103f66ef0..ed2cb87c9c8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8239,27 +8239,6 @@ byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) (void)minDowngrade; return 0; } - -/* Moves a DTLS SSL that may downgrade to the newest version its mask allows. - * Returns VERSION_ERROR when none is left. */ -int DtlsCheckVersionMask(WOLFSSL* ssl) -{ - byte minor; - - if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || - ssl->options.mask == 0) { - return 0; - } - minor = DtlsAllowedMinor(ssl, ssl->version.minor, - ssl->options.minDowngrade); - if (minor == 0) { - WOLFSSL_MSG("No DTLS version left that the options allow"); - WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); - return VERSION_ERROR; - } - ssl->version.minor = minor; - return 0; -} #endif #ifdef OPENSSL_EXTRA @@ -37629,6 +37608,16 @@ static int IsTls13CipherSuite(byte first, byte second) } } + #ifdef WOLFSSL_DTLS + if (ssl->options.dtls && ssl->options.downgrade && + DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) != + ssl->version.minor) { + WOLFSSL_MSG("\tOption set to not allow this DTLS version"); + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); + return VERSION_ERROR; + } + #endif + /* check if option is set to not allow the current version * set from either wolfSSL_set_options or wolfSSL_CTX_set_options */ if (!ssl->options.dtls && ssl->options.downgrade && diff --git a/src/ssl.c b/src/ssl.c index e3890aeeba9..29b561e277e 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7598,60 +7598,13 @@ static int FindHashSig(const Suites* suites, byte first, byte second) return MATCH_SUITE_ERROR; } -long wolfSSL_set_options(WOLFSSL* ssl, long op) +/* Rebuilds the cipher suites for the current protocol version. */ +static int ResetSuitesForVersion(WOLFSSL* ssl) { word16 haveRSA = 1; word16 havePSK = 0; int keySz = 0; - WOLFSSL_ENTER("wolfSSL_set_options"); - - if (ssl == NULL) { - return 0; - } - - ssl->options.mask = (unsigned long)wolf_set_options((long)ssl->options.mask, op); - - if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_3) == WOLFSSL_OP_NO_TLSv1_3) { - WOLFSSL_MSG("Disabling TLS 1.3"); - if (ssl->version.minor == TLSv1_3_MINOR) - ssl->version.minor = TLSv1_2_MINOR; - } - - if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2) { - WOLFSSL_MSG("Disabling TLS 1.2"); - if (ssl->version.minor == TLSv1_2_MINOR) - ssl->version.minor = TLSv1_1_MINOR; - } - - if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_1) == WOLFSSL_OP_NO_TLSv1_1) { - WOLFSSL_MSG("Disabling TLS 1.1"); - if (ssl->version.minor == TLSv1_1_MINOR) - ssl->version.minor = TLSv1_MINOR; - } - - if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1) == WOLFSSL_OP_NO_TLSv1) { - WOLFSSL_MSG("Disabling TLS 1.0"); - if (ssl->version.minor == TLSv1_MINOR) - ssl->version.minor = SSLv3_MINOR; - } - - if ((ssl->options.mask & WOLFSSL_OP_NO_COMPRESSION) - == WOLFSSL_OP_NO_COMPRESSION) { - #ifdef HAVE_LIBZ - ssl->options.usingCompression = 0; - #endif - } - -#if defined(HAVE_SESSION_TICKET) && (defined(OPENSSL_EXTRA) \ - || defined(HAVE_WEBSERVER) || defined(WOLFSSL_WPAS_SMALL)) - if ((ssl->options.mask & WOLFSSL_OP_NO_TICKET) == WOLFSSL_OP_NO_TICKET) { - ssl->options.noTicketTls12 = 1; - } -#endif - - - /* in the case of a version change the cipher suites should be reset */ #ifndef NO_PSK havePSK = ssl->options.havePSK; #endif @@ -7664,7 +7617,7 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) if (ssl->options.side != WOLFSSL_NEITHER_END) { if (AllocateSuites(ssl) != 0) - return 0; + return MEMORY_E; if (!ssl->suites->setSuites) { /* Client side won't set DH params, so it needs haveDH set to TRUE. */ if (ssl->options.side == WOLFSSL_CLIENT_END) @@ -7726,10 +7679,92 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) } } + return 0; +} + +long wolfSSL_set_options(WOLFSSL* ssl, long op) +{ + WOLFSSL_ENTER("wolfSSL_set_options"); + + if (ssl == NULL) { + return 0; + } + + ssl->options.mask = (unsigned long)wolf_set_options((long)ssl->options.mask, op); + + if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_3) == WOLFSSL_OP_NO_TLSv1_3) { + WOLFSSL_MSG("Disabling TLS 1.3"); + if (ssl->version.minor == TLSv1_3_MINOR) + ssl->version.minor = TLSv1_2_MINOR; + } + + if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2) { + WOLFSSL_MSG("Disabling TLS 1.2"); + if (ssl->version.minor == TLSv1_2_MINOR) + ssl->version.minor = TLSv1_1_MINOR; + } + + if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_1) == WOLFSSL_OP_NO_TLSv1_1) { + WOLFSSL_MSG("Disabling TLS 1.1"); + if (ssl->version.minor == TLSv1_1_MINOR) + ssl->version.minor = TLSv1_MINOR; + } + + if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1) == WOLFSSL_OP_NO_TLSv1) { + WOLFSSL_MSG("Disabling TLS 1.0"); + if (ssl->version.minor == TLSv1_MINOR) + ssl->version.minor = SSLv3_MINOR; + } + + if ((ssl->options.mask & WOLFSSL_OP_NO_COMPRESSION) + == WOLFSSL_OP_NO_COMPRESSION) { + #ifdef HAVE_LIBZ + ssl->options.usingCompression = 0; + #endif + } + +#if defined(HAVE_SESSION_TICKET) && (defined(OPENSSL_EXTRA) \ + || defined(HAVE_WEBSERVER) || defined(WOLFSSL_WPAS_SMALL)) + if ((ssl->options.mask & WOLFSSL_OP_NO_TICKET) == WOLFSSL_OP_NO_TICKET) { + ssl->options.noTicketTls12 = 1; + } +#endif + + + /* in the case of a version change the cipher suites should be reset */ + if (ResetSuitesForVersion(ssl) != 0) + return 0; + return (long)ssl->options.mask; } +#ifdef WOLFSSL_DTLS +/* Moves a DTLS SSL that may downgrade, before its first handshake, to the + * newest version its mask allows. Returns VERSION_ERROR when none is left. */ +int DtlsCheckVersionMask(WOLFSSL* ssl) +{ + byte minor; + + /* A minimum of DTLS_MINOR tests the mask alone. */ + if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || + ssl->options.handShakeDone || + DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) == + ssl->version.minor) { + return 0; + } + minor = DtlsAllowedMinor(ssl, ssl->version.minor, + ssl->options.minDowngrade); + if (minor == 0) { + WOLFSSL_MSG("No DTLS version left that the options allow"); + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); + return VERSION_ERROR; + } + ssl->version.minor = minor; + return ResetSuitesForVersion(ssl); +} +#endif + long wolfSSL_get_options(const WOLFSSL* ssl) { WOLFSSL_ENTER("wolfSSL_get_options"); diff --git a/src/tls13.c b/src/tls13.c index 869d0665b8c..fa25a7c6f9f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16783,6 +16783,20 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) return WOLFSSL_FATAL_ERROR; } +#ifdef WOLFSSL_DTLS + if (ssl->options.connectState == CONNECT_BEGIN) { + byte minor = ssl->version.minor; + + if ((ret = DtlsCheckVersionMask(ssl)) != 0) { + ssl->error = ret; + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + if (ssl->version.minor != minor) + return wolfSSL_connect(ssl); + } +#endif + /* make sure this wolfSSL object has arrays and rng setup. Protects * case where the WOLFSSL object is reused via wolfSSL_clear() */ if ((ret = ReinitSSL(ssl, ssl->ctx, 0)) != 0) { @@ -18071,6 +18085,20 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) return WOLFSSL_FATAL_ERROR; } +#ifdef WOLFSSL_DTLS + if (ssl->options.acceptState == TLS13_ACCEPT_BEGIN) { + byte minor = ssl->version.minor; + + if ((ret = DtlsCheckVersionMask(ssl)) != 0) { + ssl->error = ret; + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + if (ssl->version.minor != minor) + return wolfSSL_accept(ssl); + } +#endif + /* make sure this wolfSSL object has arrays and rng setup. Protects * case where the WOLFSSL object is reused via wolfSSL_clear() */ if ((ret = ReinitSSL(ssl, ssl->ctx, 0)) != 0) { diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index e9891411ca2..91b022d5cf8 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16535,6 +16535,18 @@ int test_dtls_set_rfd_wfd(void) #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ defined(WOLFSSL_DTLS13) && defined(OPENSSL_EXTRA) +static int test_dtls13_has_tls13_suite(WOLFSSL* ssl) +{ + const Suites* suites = WOLFSSL_SUITES(ssl); + word16 i; + + for (i = 0; suites != NULL && i < suites->suiteSz; i += SUITE_LEN) { + if (suites->suites[i] == TLS13_BYTE) + return 1; + } + return 0; +} + /* Sets opts, opts2 and opts3 in separate calls, and minVer when not 0, on the * client or server SSL or CTX. An expVersion of 0 means no version is left. */ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, @@ -16583,6 +16595,8 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, ExpectIntEQ(ret, 0); ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); ExpectIntEQ(wolfSSL_version(ssl_s), expVersion); + if (expVersion != DTLS1_3_VERSION && ssl != NULL) + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl), 0); (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_1); ExpectIntEQ(wolfSSL_version(ssl), expVersion); @@ -16627,6 +16641,65 @@ static int test_dtls13_mask_server_walk(void) wolfSSL_CTX_free(ctx_s); return EXPECT_RESULT(); } + +/* A DTLS client whose mask drops DTLS 1.2 refuses a DTLS 1.2-only server. */ +static int test_dtls13_mask_client_check(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLSv1_2_server_method), 0); + if (ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_2); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* wolfSSL_negotiate() applies the mask too, on the server here. */ +static int test_dtls13_mask_negotiate(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int retC = WOLFSSL_FATAL_ERROR; + int retS = WOLFSSL_FATAL_ERROR; + int i; + + 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); + if (ssl_s != NULL) + (void)wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_TLSv1_3); + for (i = 0; EXPECT_SUCCESS() && i < 10 && + (retC != WOLFSSL_SUCCESS || retS != WOLFSSL_SUCCESS); i++) { + if (retC != WOLFSSL_SUCCESS) + retC = wolfSSL_negotiate(ssl_c); + if (retS != WOLFSSL_SUCCESS) + retS = wolfSSL_negotiate(ssl_s); + } + ExpectIntEQ(retC, WOLFSSL_SUCCESS); + ExpectIntEQ(retS, WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} #endif #endif @@ -16652,7 +16725,7 @@ int test_dtls13_no_tls13_option(void) #ifndef WOLFSSL_NO_TLS12 int dtls12 = DTLS1_2_VERSION; #else - int dtls12 = 0; + int dtls12 = dtls10; #endif for (onServer = 0; onServer <= 1; onServer++) { @@ -16686,6 +16759,8 @@ int test_dtls13_no_tls13_option(void) #endif #ifndef WOLFSSL_NO_TLS12 ExpectIntEQ(test_dtls13_mask_server_walk(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); #endif /* A DTLS 1.3-only method cannot move down. */ From 772f77659fee6325683bdb57a350f55515ce8dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 21:04:56 +0200 Subject: [PATCH 07/17] Apply the DTLS version mask in set_options and on every ClientHello DtlsCheckVersionMask() lowered the version but left options.tls1_3 and a resumption of a DTLS 1.3 session in place. After wolfSSL_set_session() with such a session, wolfSSL_connect() still went into the TLS 1.3 state machine, where SendTls13ClientHello() compares the session version in TLS order and put the SSL back on DTLS 1.3 to build the ClientHello. A move now clears tls1_3 and drops a resumption whose version is no longer the SSL's. wolfSSL_set_options() calls DtlsCheckVersionMask() as well, so a masked SSL reports and acts as the older version before the handshake, as it does when the mask comes from the CTX, and calls such as wolfSSL_write_early_data() that check the version before the handshake see the older one. The check at the start of the handshake stays and catches a session or option set later. Neither moves the version once the handshake has begun. The DTLS server mask check in DoClientHello() ran only when the server stepped down to the legacy ClientHello version. A client whose supported_versions offered DTLS 1.2 and nothing newer was negotiated at the masked DTLS 1.2. The check now runs for every DTLS ClientHello that may downgrade and rebuilds the suites through ResetSuitesForVersion() when it moves. The client's check in CheckVersion() skips a renegotiation, as the handshake-start check does, so a mask set after the handshake does not break it. The ECH HelloRetryRequest rejection in DoServerHello() returns SOCKET_ERROR_E when the alert cannot be sent, like its neighbors. Tests: the version right after wolfSSL_set_options(), a DTLS 1.3 session set before or after the mask, and a ClientHello whose supported_versions offers only DTLS 1.2 against a server that allows only DTLS 1.3. --- src/internal.c | 52 +++++++++++--------- src/ssl.c | 14 +++++- tests/api/test_dtls.c | 107 ++++++++++++++++++++++++++++++++++++++++++ wolfssl/internal.h | 1 + 4 files changed, 152 insertions(+), 22 deletions(-) diff --git a/src/internal.c b/src/internal.c index ed2cb87c9c8..6525b3ed6b9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -37610,6 +37610,7 @@ static int IsTls13CipherSuite(byte first, byte second) #ifdef WOLFSSL_DTLS if (ssl->options.dtls && ssl->options.downgrade && + !ssl->options.handShakeDone && DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) != ssl->version.minor) { WOLFSSL_MSG("\tOption set to not allow this DTLS version"); @@ -37721,8 +37722,11 @@ static int IsTls13CipherSuite(byte first, byte second) SERVER_HELLO_RETRY_REQUEST_COMPLETE) { WOLFSSL_MSG("ECH accepted in HelloRetryRequest, ServerHello is " "not TLS 1.3"); - SendAlert(ssl, alert_fatal, illegal_parameter); WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + if (SendAlert(ssl, alert_fatal, illegal_parameter) == + WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + return SOCKET_ERROR_E; + } return INVALID_PARAMETER; } /* A server negotiating TLS 1.2 or older answers the outer hello. */ @@ -44446,9 +44450,6 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (lesserVersion) { byte belowMinDowngrade; - #ifdef WOLFSSL_DTLS - byte dtlsMinor; - #endif word16 haveRSA = 0; word16 havePSK = 0; int keySz = 0; @@ -44522,23 +44523,6 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) ssl->options.tls1_3 = 0; ssl->version.minor = DTLS_MINOR; } - #ifdef WOLFSSL_DTLS - dtlsMinor = DtlsAllowedMinor(ssl, ssl->version.minor, - ssl->options.minDowngrade); - if (dtlsMinor == 0) { - WOLFSSL_MSG("\tNo DTLS version left that the options " - "allow"); - ret = VERSION_ERROR; - /* propagate socket errors to avoid re-calling send alert */ - if (SendAlert(ssl, alert_fatal, - wolfssl_alert_protocol_version) - == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { - ret = SOCKET_ERROR_E; - } - goto out; - } - ssl->version.minor = dtlsMinor; - #endif } #ifndef NO_RSA haveRSA = 1; @@ -44559,6 +44543,32 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) TRUE, TRUE, TRUE, TRUE, ssl->options.side); } + #ifdef WOLFSSL_DTLS + if (ssl->options.dtls && ssl->options.downgrade && + DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) != + ssl->version.minor) { + byte dtlsMinor = DtlsAllowedMinor(ssl, ssl->version.minor, + ssl->options.minDowngrade); + + if (dtlsMinor == 0) { + WOLFSSL_MSG("\tNo DTLS version left that the options allow"); + ret = VERSION_ERROR; + /* propagate socket errors to avoid re-calling send alert */ + if (SendAlert(ssl, alert_fatal, + wolfssl_alert_protocol_version) + == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + ret = SOCKET_ERROR_E; + } + goto out; + } + ssl->options.tls1_3 = 0; + ssl->version.minor = dtlsMinor; + ret = ResetSuitesForVersion(ssl); + if (ret != 0) + goto out; + } + #endif + /* Version the record layer is on before the mask walk below steps it * down. It is one the client offered (or the server's own maximum * when the client offered more), so the two alert exits inside that diff --git a/src/ssl.c b/src/ssl.c index 29b561e277e..1e90e79e3de 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7599,7 +7599,7 @@ static int FindHashSig(const Suites* suites, byte first, byte second) } /* Rebuilds the cipher suites for the current protocol version. */ -static int ResetSuitesForVersion(WOLFSSL* ssl) +int ResetSuitesForVersion(WOLFSSL* ssl) { word16 haveRSA = 1; word16 havePSK = 0; @@ -7730,6 +7730,11 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) } #endif +#ifdef WOLFSSL_DTLS + /* The handshake start checks again and fails when no version is left. */ + (void)DtlsCheckVersionMask(ssl); +#endif + /* in the case of a version change the cipher suites should be reset */ if (ResetSuitesForVersion(ssl) != 0) @@ -7749,6 +7754,8 @@ int DtlsCheckVersionMask(WOLFSSL* ssl) /* A minimum of DTLS_MINOR tests the mask alone. */ if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || ssl->options.handShakeDone || + ssl->options.connectState != CONNECT_BEGIN || + ssl->options.acceptState != ACCEPT_BEGIN || DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) == ssl->version.minor) { return 0; @@ -7761,6 +7768,11 @@ int DtlsCheckVersionMask(WOLFSSL* ssl) return VERSION_ERROR; } ssl->version.minor = minor; + ssl->options.tls1_3 = 0; + if (ssl->options.resuming && ssl->session != NULL && + ssl->session->version.minor != minor) { + ssl->options.resuming = 0; + } return ResetSuitesForVersion(ssl); } #endif diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 91b022d5cf8..6ea397cdd3b 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16588,6 +16588,8 @@ static int test_dtls13_mask_case(int onServer, int onCtx, long opts, (void)wolfSSL_set_options(ssl, opts); (void)wolfSSL_set_options(ssl, opts2); (void)wolfSSL_set_options(ssl, opts3); + if (expVersion != 0) + ExpectIntEQ(wolfSSL_version(ssl), expVersion); } ret = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); @@ -16666,6 +16668,106 @@ static int test_dtls13_mask_client_check(void) return EXPECT_RESULT(); } +/* A server whose mask leaves only DTLS 1.3 refuses a ClientHello whose + * supported_versions offers DTLS 1.2 and nothing newer. */ +static int test_dtls13_mask_server_sv(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int patched = 0; + int i; + + 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); + if (ssl_s != NULL) { + (void)wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1); + } +#ifdef WOLFSSL_SEND_HRR_COOKIE + ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + /* supported_versions: type, length, list length, then DTLS 1.3 first. */ + for (i = 0; EXPECT_SUCCESS() && i + 7 <= test_ctx.s_len; i++) { + if (test_ctx.s_buff[i] == 0x00 && test_ctx.s_buff[i + 1] == 0x2b && + test_ctx.s_buff[i + 5] == DTLS_MAJOR && + test_ctx.s_buff[i + 6] == DTLSv1_3_MINOR) { + test_ctx.s_buff[i + 6] = DTLSv1_2_MINOR; + patched = 1; + break; + } + } + ExpectIntEQ(patched, 1); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#ifdef HAVE_SESSION_TICKET +/* A DTLS 1.3 session set before or after the mask is not resumed. */ +static int test_dtls13_mask_session(int sessionFirst) +{ + EXPECT_DECLS; + 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; + char buf[64]; + + 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(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_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); + if (sessionFirst) + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + if (ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + if (!sessionFirst) + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.tls1_3, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + /* wolfSSL_negotiate() applies the mask too, on the server here. */ static int test_dtls13_mask_negotiate(void) { @@ -16761,6 +16863,11 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_server_walk(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); +#ifdef HAVE_SESSION_TICKET + ExpectIntEQ(test_dtls13_mask_session(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_session(1), TEST_SUCCESS); +#endif + ExpectIntEQ(test_dtls13_mask_server_sv(), TEST_SUCCESS); #endif /* A DTLS 1.3-only method cannot move down. */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 5987a7b53f5..692534218d0 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2376,6 +2376,7 @@ WOLFSSL_LOCAL void InitSSL_Method(WOLFSSL_METHOD* method, ProtocolVersion pv); WOLFSSL_LOCAL void InitSSL_CTX_Suites(const WOLFSSL_CTX* ctx, Suites* suites); WOLFSSL_LOCAL int InitSSL_Suites(WOLFSSL* ssl); +WOLFSSL_LOCAL int ResetSuitesForVersion(WOLFSSL* ssl); WOLFSSL_LOCAL int InitSSL_Side(WOLFSSL* ssl, word16 side); From 801e5da61f6312c3bd08f1b21525fae1f5e02ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 21:39:02 +0200 Subject: [PATCH 08/17] Start the DTLS mask walk from the method version when resuming wolfSSL_set_session() lowers the SSL to the session's version, so with a DTLS 1.2 session and DTLS 1.2 masked the walk started at DTLS 1.2 and a client that allows DTLS 1.3 fell to DTLS 1.0 or failed with VERSION_ERROR. A resumption now walks from the method's version, and the resumption is dropped as before when its version is no longer the SSL's. DtlsMaskVersion() only moves the version and DtlsCheckVersionMask() adds the suite rebuild, so wolfSSL_set_options() rebuilds the suites once. wolfSSL_connect() and wolfSSL_accept() check the mask only for the side they serve, so a call on the wrong side fails with SIDE_ERROR without changing the SSL first. The server check in DoClientHello() skips a renegotiation, as the client's does. Drop SSL_OP_NO_DTLSv1. OpenSSL defines it as the SSL_OP_NO_TLSv1 bit, while wolfSSL masks DTLS 1.0 with WOLFSSL_OP_NO_TLSv1_1, so the alias would have behaved differently from OpenSSL. SSL_OP_NO_DTLSv1_2 and SSL_OP_NO_DTLSv1_3 use the same bits as in OpenSSL and stay. Adds a DTLS 1.2 session with DTLS 1.2 masked to the session cases. --- src/internal.c | 1 + src/ssl.c | 83 +++++++++++++++++++++++++------------------ src/ssl_api_hs.c | 4 +-- tests/api/test_dtls.c | 30 ++++++++++------ wolfssl/ssl.h | 2 -- 5 files changed, 72 insertions(+), 48 deletions(-) diff --git a/src/internal.c b/src/internal.c index 6525b3ed6b9..3356f3469c3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -44545,6 +44545,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS if (ssl->options.dtls && ssl->options.downgrade && + !ssl->options.handShakeDone && DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) != ssl->version.minor) { byte dtlsMinor = DtlsAllowedMinor(ssl, ssl->version.minor, diff --git a/src/ssl.c b/src/ssl.c index 1e90e79e3de..396bdfe34c0 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7682,6 +7682,54 @@ int ResetSuitesForVersion(WOLFSSL* ssl) return 0; } +#ifdef WOLFSSL_DTLS +/* Moves a DTLS SSL that may downgrade, before its first handshake, to the + * newest version its mask allows. Returns 1 when it moved, 0 when it did not + * and VERSION_ERROR when no version is left. */ +static int DtlsMaskVersion(WOLFSSL* ssl) +{ + byte maxMinor = ssl->version.minor; + byte minor; + + /* A minimum of DTLS_MINOR tests the mask alone. */ + if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || + ssl->options.handShakeDone || + ssl->options.connectState != CONNECT_BEGIN || + ssl->options.acceptState != ACCEPT_BEGIN || + DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) == + ssl->version.minor) { + return 0; + } + /* A resumed session lowered the version to its own. */ + if (ssl->options.resuming) + maxMinor = ssl->ctx->method->version.minor; + minor = DtlsAllowedMinor(ssl, maxMinor, ssl->options.minDowngrade); + if (minor == 0) { + WOLFSSL_MSG("No DTLS version left that the options allow"); + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); + return VERSION_ERROR; + } + ssl->version.minor = minor; + ssl->options.tls1_3 = 0; + if (ssl->options.resuming && ssl->session != NULL && + ssl->session->version.minor != minor) { + ssl->options.resuming = 0; + } + return 1; +} + +/* Applies the DTLS version mask at the start of a handshake. Returns + * VERSION_ERROR when no version is left. */ +int DtlsCheckVersionMask(WOLFSSL* ssl) +{ + int ret = DtlsMaskVersion(ssl); + + if (ret == 1) + ret = ResetSuitesForVersion(ssl); + return ret; +} +#endif + long wolfSSL_set_options(WOLFSSL* ssl, long op) { WOLFSSL_ENTER("wolfSSL_set_options"); @@ -7731,8 +7779,7 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) #endif #ifdef WOLFSSL_DTLS - /* The handshake start checks again and fails when no version is left. */ - (void)DtlsCheckVersionMask(ssl); + (void)DtlsMaskVersion(ssl); #endif @@ -7744,38 +7791,6 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) } -#ifdef WOLFSSL_DTLS -/* Moves a DTLS SSL that may downgrade, before its first handshake, to the - * newest version its mask allows. Returns VERSION_ERROR when none is left. */ -int DtlsCheckVersionMask(WOLFSSL* ssl) -{ - byte minor; - - /* A minimum of DTLS_MINOR tests the mask alone. */ - if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || - ssl->options.handShakeDone || - ssl->options.connectState != CONNECT_BEGIN || - ssl->options.acceptState != ACCEPT_BEGIN || - DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) == - ssl->version.minor) { - return 0; - } - minor = DtlsAllowedMinor(ssl, ssl->version.minor, - ssl->options.minDowngrade); - if (minor == 0) { - WOLFSSL_MSG("No DTLS version left that the options allow"); - WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); - return VERSION_ERROR; - } - ssl->version.minor = minor; - ssl->options.tls1_3 = 0; - if (ssl->options.resuming && ssl->session != NULL && - ssl->session->version.minor != minor) { - ssl->options.resuming = 0; - } - return ResetSuitesForVersion(ssl); -} -#endif long wolfSSL_get_options(const WOLFSSL* ssl) { diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index 8cf42116f0b..c02ea93308b 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -395,7 +395,7 @@ int wolfSSL_connect(WOLFSSL* ssl) #endif /* OPENSSL_EXTRA || WOLFSSL_EITHER_SIDE */ #ifdef WOLFSSL_DTLS - if (ssl->options.connectState == CONNECT_BEGIN && + if (ssl->options.side == WOLFSSL_CLIENT_END && (ret = DtlsCheckVersionMask(ssl)) != 0) { ssl->error = ret; WOLFSSL_ERROR(ssl->error); @@ -892,7 +892,7 @@ int wolfSSL_accept(WOLFSSL* ssl) #endif /* OPENSSL_EXTRA || WOLFSSL_EITHER_SIDE */ #ifdef WOLFSSL_DTLS - if (ssl->options.acceptState == ACCEPT_BEGIN && + if (ssl->options.side == WOLFSSL_SERVER_END && (ret = DtlsCheckVersionMask(ssl)) != 0) { ssl->error = ret; WOLFSSL_ERROR(ssl->error); diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 6ea397cdd3b..1584be2f2cc 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16715,8 +16715,10 @@ static int test_dtls13_mask_server_sv(void) } #ifdef HAVE_SESSION_TICKET -/* A DTLS 1.3 session set before or after the mask is not resumed. */ -static int test_dtls13_mask_session(int sessionFirst) +/* A session made with firstOpts and set before or after secondOpts, which + * mask its version, is not resumed and expVersion is negotiated instead. */ +static int test_dtls13_mask_session(int sessionFirst, long firstOpts, + long secondOpts, int expVersion) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -16728,8 +16730,10 @@ static int test_dtls13_mask_session(int sessionFirst) 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); + if (ssl_c != NULL && firstOpts != 0) + (void)wolfSSL_set_options(ssl_c, firstOpts); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntNE(wolfSSL_version(ssl_c), expVersion); ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), WOLFSSL_ERROR_WANT_READ); @@ -16745,18 +16749,18 @@ static int test_dtls13_mask_session(int sessionFirst) if (sessionFirst) ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); if (ssl_c != NULL) - (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + (void)wolfSSL_set_options(ssl_c, secondOpts); if (!sessionFirst) ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), WOLFSSL_ERROR_WANT_READ); - ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); if (ssl_c != NULL) ExpectIntEQ(ssl_c->options.tls1_3, 0); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); - ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); + ExpectIntEQ(wolfSSL_version(ssl_s), expVersion); ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0); wolfSSL_SESSION_free(sess); @@ -16857,15 +16861,21 @@ int test_dtls13_no_tls13_option(void) WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); #ifndef OPENSSL_COEXIST ExpectIntEQ(test_dtls13_mask_case(0, 0, SSL_OP_NO_DTLSv1_3, - SSL_OP_NO_DTLSv1_2, SSL_OP_NO_DTLSv1, 0, 0), TEST_SUCCESS); + SSL_OP_NO_DTLSv1_2, WOLFSSL_OP_NO_TLSv1_1, 0, 0), TEST_SUCCESS); #endif #ifndef WOLFSSL_NO_TLS12 ExpectIntEQ(test_dtls13_mask_server_walk(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); #ifdef HAVE_SESSION_TICKET - ExpectIntEQ(test_dtls13_mask_session(0), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_session(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_session(0, 0, WOLFSSL_OP_NO_TLSv1_3, + DTLS1_2_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_session(1, 0, WOLFSSL_OP_NO_TLSv1_3, + DTLS1_2_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_session(0, WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_2, DTLS1_3_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_session(1, WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_2, DTLS1_3_VERSION), TEST_SUCCESS); #endif ExpectIntEQ(test_dtls13_mask_server_sv(), TEST_SUCCESS); #endif diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 62dd4ce6dec..41204285188 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3074,8 +3074,6 @@ enum { #define SSL_OP_NO_TLSv1_3 WOLFSSL_OP_NO_TLSv1_3 #endif -/* DTLS 1.0 is masked by the TLS 1.1 option. */ -#define SSL_OP_NO_DTLSv1 WOLFSSL_OP_NO_TLSv1_1 #define SSL_OP_NO_DTLSv1_2 WOLFSSL_OP_NO_TLSv1_2 #ifdef WOLFSSL_DTLS13 #define SSL_OP_NO_DTLSv1_3 WOLFSSL_OP_NO_TLSv1_3 From 353b23d24bdb94a4f29fc7f24d3854e407cb8bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 21:56:47 +0200 Subject: [PATCH 09/17] Pick only listed DTLS versions and share the server mask check The DTLS server stepped down from the version supported_versions picked to the next one its mask allowed, so a server masking DTLS 1.2 answered a client that listed only DTLS 1.2 with DTLS 1.0. TLSX_SupportedVersions_Parse() now skips DTLS versions the mask does not allow on an SSL that may downgrade, so it picks the newest listed version that is allowed or refuses with protocol_version, as OpenSSL does. The step-down in DoClientHello() remains for a ClientHello without supported_versions, and now calls DtlsCheckVersionMask() instead of repeating it. The ECH rejection in DoServerHello() detects the HelloRetryRequest through msgsReceived.got_hello_retry_request. DoTls13ServerHello() sets serverState to SERVER_HELLO_COMPLETE before it calls DoServerHello() for a TLS 1.2 ServerHello that carries extensions, so the serverState test missed it. wolfSSL_connect_TLSv13() resets earlyData when the mask moves the SSL below DTLS 1.3 and it hands over to wolfSSL_connect(). wolfSSL_write_early_data() had set it, and nothing on the DTLS 1.2 path cleared it, so every write after the handshake failed. When the mask drops a resumption, haveEMS goes back to the default wolfSSL_clear() uses instead of keeping the session's, so the full handshake offers extended_master_secret again. Tests: a server masking DTLS 1.2 against a client listing only DTLS 1.2, early data written with a DTLS 1.3 session after the mask, and a dropped resumption of a session without EMS. --- src/internal.c | 34 ++++------- src/ssl.c | 6 +- src/tls.c | 6 ++ src/tls13.c | 6 +- tests/api/test_dtls.c | 127 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 144 insertions(+), 35 deletions(-) diff --git a/src/internal.c b/src/internal.c index 3356f3469c3..b7641549ed0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -37718,8 +37718,8 @@ static int IsTls13CipherSuite(byte first, byte second) #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) - if (ssl->options.echAccepted && ssl->options.serverState == - SERVER_HELLO_RETRY_REQUEST_COMPLETE) { + if (ssl->options.echAccepted && + ssl->msgsReceived.got_hello_retry_request) { WOLFSSL_MSG("ECH accepted in HelloRetryRequest, ServerHello is " "not TLS 1.3"); WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); @@ -44544,30 +44544,16 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #ifdef WOLFSSL_DTLS - if (ssl->options.dtls && ssl->options.downgrade && - !ssl->options.handShakeDone && - DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) != - ssl->version.minor) { - byte dtlsMinor = DtlsAllowedMinor(ssl, ssl->version.minor, - ssl->options.minDowngrade); - - if (dtlsMinor == 0) { - WOLFSSL_MSG("\tNo DTLS version left that the options allow"); - ret = VERSION_ERROR; - /* propagate socket errors to avoid re-calling send alert */ - if (SendAlert(ssl, alert_fatal, - wolfssl_alert_protocol_version) - == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { - ret = SOCKET_ERROR_E; - } - goto out; + ret = DtlsCheckVersionMask(ssl); + if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) { + /* propagate socket errors to avoid re-calling send alert */ + if (SendAlert(ssl, alert_fatal, wolfssl_alert_protocol_version) + == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + ret = SOCKET_ERROR_E; } - ssl->options.tls1_3 = 0; - ssl->version.minor = dtlsMinor; - ret = ResetSuitesForVersion(ssl); - if (ret != 0) - goto out; } + if (ret != 0) + goto out; #endif /* Version the record layer is on before the mask walk below steps it diff --git a/src/ssl.c b/src/ssl.c index 396bdfe34c0..a93411ed941 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7684,8 +7684,7 @@ int ResetSuitesForVersion(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS /* Moves a DTLS SSL that may downgrade, before its first handshake, to the - * newest version its mask allows. Returns 1 when it moved, 0 when it did not - * and VERSION_ERROR when no version is left. */ + * newest version its mask allows. Returns 1 when it moved, or VERSION_ERROR. */ static int DtlsMaskVersion(WOLFSSL* ssl) { byte maxMinor = ssl->version.minor; @@ -7714,6 +7713,9 @@ static int DtlsMaskVersion(WOLFSSL* ssl) if (ssl->options.resuming && ssl->session != NULL && ssl->session->version.minor != minor) { ssl->options.resuming = 0; + #ifdef HAVE_EXTENDED_MASTER + ssl->options.haveEMS = !ssl->options.disableEMS; + #endif } return 1; } diff --git a/src/tls.c b/src/tls.c index e7c96614d61..a2752c1d1d3 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7968,6 +7968,12 @@ int TLSX_SupportedVersions_Parse(const WOLFSSL* ssl, const byte* input, if (versionIsLesser(isDtls, minor, ssl->options.minDowngrade)) continue; } + #ifdef WOLFSSL_DTLS + if (isDtls && ssl->options.downgrade && + DtlsAllowedMinor(ssl, minor, DTLS_MINOR) != minor) { + continue; + } + #endif if (versionIsGreater(isDtls, minor, clientGreatestMinor)) clientGreatestMinor = minor; diff --git a/src/tls13.c b/src/tls13.c index fa25a7c6f9f..79d830ba7b3 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16792,8 +16792,12 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; } - if (ssl->version.minor != minor) + if (ssl->version.minor != minor) { + #ifdef WOLFSSL_EARLY_DATA + ssl->earlyData = no_early_data; + #endif return wolfSSL_connect(ssl); + } } #endif diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 1584be2f2cc..6e93a9d17a2 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16668,9 +16668,9 @@ static int test_dtls13_mask_client_check(void) return EXPECT_RESULT(); } -/* A server whose mask leaves only DTLS 1.3 refuses a ClientHello whose - * supported_versions offers DTLS 1.2 and nothing newer. */ -static int test_dtls13_mask_server_sv(void) +/* A server refuses a ClientHello whose supported_versions offers DTLS 1.2 + * and nothing newer when its mask allows none of the listed versions. */ +static int test_dtls13_mask_server_sv(long clientOpts, long serverOpts) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -16682,10 +16682,10 @@ static int test_dtls13_mask_server_sv(void) 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); - if (ssl_s != NULL) { - (void)wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_TLSv1_2 | - WOLFSSL_OP_NO_TLSv1_1); - } + if (ssl_c != NULL && clientOpts != 0) + (void)wolfSSL_set_options(ssl_c, clientOpts); + if (ssl_s != NULL) + (void)wolfSSL_set_options(ssl_s, serverOpts); #ifdef WOLFSSL_SEND_HRR_COOKIE ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); #endif @@ -16772,6 +16772,105 @@ static int test_dtls13_mask_session(int sessionFirst, long firstOpts, } #endif +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_EARLY_DATA) +/* Early data written with a DTLS 1.3 session after the mask is not left + * pending once the handshake falls back to DTLS 1.2. */ +static int test_dtls13_mask_early_data(void) +{ + EXPECT_DECLS; + 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; + char buf[64]; + int outSz = 0; + + 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_CTX_set_max_early_data(ctx_s, sizeof(buf)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_early_data(ssl_s, sizeof(buf)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_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); + if (ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_write_early_data(ssl_c, "early", 5, &outSz), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_write(ssl_c, "data", 4), 4); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +#if defined(HAVE_SESSION_TICKET) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_OLD_TLS) +/* A dropped resumption of a session without EMS still offers EMS. */ +static int test_dtls13_mask_session_ems(void) +{ + EXPECT_DECLS; + 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_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_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + if (ssl_c != NULL) { + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3 | + WOLFSSL_OP_NO_TLSv1_2); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_VERSION); + ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.haveEMS, 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + /* wolfSSL_negotiate() applies the mask too, on the server here. */ static int test_dtls13_mask_negotiate(void) { @@ -16877,7 +16976,19 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_session(1, WOLFSSL_OP_NO_TLSv1_3, WOLFSSL_OP_NO_TLSv1_2, DTLS1_3_VERSION), TEST_SUCCESS); #endif - ExpectIntEQ(test_dtls13_mask_server_sv(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_server_sv(0, WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_1), TEST_SUCCESS); +#ifndef NO_OLD_TLS + ExpectIntEQ(test_dtls13_mask_server_sv(WOLFSSL_OP_NO_TLSv1_1, + WOLFSSL_OP_NO_TLSv1_2), TEST_SUCCESS); +#endif +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_EARLY_DATA) + ExpectIntEQ(test_dtls13_mask_early_data(), TEST_SUCCESS); +#endif +#if defined(HAVE_SESSION_TICKET) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_OLD_TLS) + ExpectIntEQ(test_dtls13_mask_session_ems(), TEST_SUCCESS); +#endif #endif /* A DTLS 1.3-only method cannot move down. */ From 873e2d3fca678ebd70029133f7df3a681098709c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 22:24:12 +0200 Subject: [PATCH 10/17] Apply the DTLS mask when a session is set, not mid-ClientHello wolfSSL_set_session() puts the SSL on the session's version, so with the mask already set, a DTLS 1.3 session took a masked SSL back to DTLS 1.3 until the handshake started. wolfSSL_write_early_data() then started a DTLS 1.3 handshake, the connect path moved it to DTLS 1.2, and every non-blocking retry returned BAD_FUNC_ARG while the error still read WANT_READ. SetSession() now applies the mask too, so the version is right before any early data is written. wolfSSL_set_options() no longer moves the version once a ClientHello has arrived. A server callback that sets options while the ClientHello is parsed, such as an SNI callback re-applying per-host options, moved an SSL that ClientHello had already settled on DTLS 1.3. Only a client resumption changes the walk and resets haveEMS. A server reused through wolfSSL_clear() keeps a stale resuming flag, and the reset would have forced EMS on before the ClientHello was read. DtlsAllowedMinor() reads the mask only under OPENSSL_EXTRA, HAVE_WEBSERVER or WOLFSSL_WPAS_SMALL, the builds where supported_versions honours it, so other builds do not offer a version and then refuse it. Tests: early data after a DTLS 1.3 session set behind the mask is refused up front with nothing left pending, and an SNI callback that masks DTLS 1.3 leaves the handshake on DTLS 1.3. --- src/internal.c | 14 ++++++-- src/ssl.c | 11 +++--- src/ssl_sess.c | 3 ++ tests/api/test_dtls.c | 80 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/internal.c b/src/internal.c index b7641549ed0..26a82fbe9cd 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8215,26 +8215,34 @@ int InitSSL_Suites(WOLFSSL* ssl) * mask and minDowngrade allow, or 0. DTLS minors count down. */ byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) { +#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + unsigned long mask = ssl->options.mask; +#else + unsigned long mask = 0; +#endif + #ifdef WOLFSSL_DTLS13 if (maxMinor <= DTLSv1_3_MINOR && - (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_3) == 0) { + (mask & WOLFSSL_OP_NO_TLSv1_3) == 0) { return DTLSv1_3_MINOR; } #endif #ifndef WOLFSSL_NO_TLS12 if (maxMinor <= DTLSv1_2_MINOR && - (ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == 0 && + (mask & WOLFSSL_OP_NO_TLSv1_2) == 0 && minDowngrade >= DTLSv1_2_MINOR) { return DTLSv1_2_MINOR; } #endif #ifndef NO_OLD_TLS - if ((ssl->options.mask & WOLFSSL_OP_NO_TLSv1_1) == 0 && + if ((mask & WOLFSSL_OP_NO_TLSv1_1) == 0 && minDowngrade >= DTLS_MINOR) { return DTLS_MINOR; } #endif (void)ssl; + (void)mask; (void)maxMinor; (void)minDowngrade; return 0; diff --git a/src/ssl.c b/src/ssl.c index a93411ed941..0ecda5e9842 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7688,6 +7688,8 @@ int ResetSuitesForVersion(WOLFSSL* ssl) static int DtlsMaskVersion(WOLFSSL* ssl) { byte maxMinor = ssl->version.minor; + byte resuming = (byte)(ssl->options.resuming && + ssl->options.side != WOLFSSL_SERVER_END); byte minor; /* A minimum of DTLS_MINOR tests the mask alone. */ @@ -7700,7 +7702,7 @@ static int DtlsMaskVersion(WOLFSSL* ssl) return 0; } /* A resumed session lowered the version to its own. */ - if (ssl->options.resuming) + if (resuming) maxMinor = ssl->ctx->method->version.minor; minor = DtlsAllowedMinor(ssl, maxMinor, ssl->options.minDowngrade); if (minor == 0) { @@ -7710,7 +7712,7 @@ static int DtlsMaskVersion(WOLFSSL* ssl) } ssl->version.minor = minor; ssl->options.tls1_3 = 0; - if (ssl->options.resuming && ssl->session != NULL && + if (resuming && ssl->session != NULL && ssl->session->version.minor != minor) { ssl->options.resuming = 0; #ifdef HAVE_EXTENDED_MASTER @@ -7781,10 +7783,10 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) #endif #ifdef WOLFSSL_DTLS - (void)DtlsMaskVersion(ssl); + if (!ssl->msgsReceived.got_client_hello) + (void)DtlsMaskVersion(ssl); #endif - /* in the case of a version change the cipher suites should be reset */ if (ResetSuitesForVersion(ssl) != 0) return 0; @@ -7793,7 +7795,6 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) } - long wolfSSL_get_options(const WOLFSSL* ssl) { WOLFSSL_ENTER("wolfSSL_get_options"); diff --git a/src/ssl_sess.c b/src/ssl_sess.c index c1ef5472e89..f09e6d37775 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1749,6 +1749,9 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) ssl->peerVerifyRet = (unsigned long)ssl->session->peerVerifyRet; #endif +#ifdef WOLFSSL_DTLS + (void)DtlsCheckVersionMask(ssl); +#endif return WOLFSSL_SUCCESS; } diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 6e93a9d17a2..c1755ce8406 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16717,8 +16717,8 @@ static int test_dtls13_mask_server_sv(long clientOpts, long serverOpts) #ifdef HAVE_SESSION_TICKET /* A session made with firstOpts and set before or after secondOpts, which * mask its version, is not resumed and expVersion is negotiated instead. */ -static int test_dtls13_mask_session(int sessionFirst, long firstOpts, - long secondOpts, int expVersion) +static int test_dtls13_mask_session_method(int sessionFirst, long firstOpts, + long secondOpts, int expVersion, method_provider method) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -16743,9 +16743,11 @@ static int test_dtls13_mask_session(int sessionFirst, long firstOpts, wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = 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); + method, wolfDTLS_server_method), 0); if (sessionFirst) ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); if (ssl_c != NULL) @@ -16756,8 +16758,10 @@ static int test_dtls13_mask_session(int sessionFirst, long firstOpts, ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), WOLFSSL_ERROR_WANT_READ); ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); - if (ssl_c != NULL) + if (ssl_c != NULL) { ExpectIntEQ(ssl_c->options.tls1_3, 0); + ExpectIntEQ(ssl_c->options.resuming, 0); + } ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); ExpectIntEQ(wolfSSL_version(ssl_s), expVersion); @@ -16770,11 +16774,27 @@ static int test_dtls13_mask_session(int sessionFirst, long firstOpts, wolfSSL_CTX_free(ctx_s); return EXPECT_RESULT(); } + +/* Runs test_dtls13_mask_session_method() with a client and, when built, an + * either-side object that becomes the client. */ +static int test_dtls13_mask_session(int sessionFirst, long firstOpts, + long secondOpts, int expVersion) +{ + EXPECT_DECLS; + + ExpectIntEQ(test_dtls13_mask_session_method(sessionFirst, firstOpts, + secondOpts, expVersion, wolfDTLS_client_method), TEST_SUCCESS); +#ifdef WOLFSSL_EITHER_SIDE + ExpectIntEQ(test_dtls13_mask_session_method(sessionFirst, firstOpts, + secondOpts, expVersion, wolfDTLS_method), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} #endif #if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_EARLY_DATA) -/* Early data written with a DTLS 1.3 session after the mask is not left - * pending once the handshake falls back to DTLS 1.2. */ +/* A DTLS 1.3 session set after the mask moves the SSL to DTLS 1.2, so early + * data is refused up front and nothing is left pending. */ static int test_dtls13_mask_early_data(void) { EXPECT_DECLS; @@ -16808,8 +16828,12 @@ static int test_dtls13_mask_early_data(void) if (ssl_c != NULL) (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); ExpectIntEQ(wolfSSL_write_early_data(ssl_c, "early", 5, &outSz), - WOLFSSL_FATAL_ERROR); + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(outSz, 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->earlyData, no_early_data); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); ExpectIntEQ(wolfSSL_write(ssl_c, "data", 4), 4); @@ -16871,6 +16895,45 @@ static int test_dtls13_mask_session_ems(void) } #endif +#ifdef HAVE_SNI +static int test_dtls13_mask_sni_cb(WOLFSSL* ssl, int* ret, void* arg) +{ + (void)ret; + (void)arg; + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + return 0; +} + +/* An option set while the server parses the ClientHello does not move the + * version that ClientHello already settled. */ +static int test_dtls13_mask_in_client_hello(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char host[] = "example.com"; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + wolfSSL_CTX_set_servername_callback(ctx_s, test_dtls13_mask_sni_cb); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + /* wolfSSL_negotiate() applies the mask too, on the server here. */ static int test_dtls13_mask_negotiate(void) { @@ -16966,6 +17029,9 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_server_walk(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); +#ifdef HAVE_SNI + ExpectIntEQ(test_dtls13_mask_in_client_hello(), TEST_SUCCESS); +#endif #ifdef HAVE_SESSION_TICKET ExpectIntEQ(test_dtls13_mask_session(0, 0, WOLFSSL_OP_NO_TLSv1_3, DTLS1_2_VERSION), TEST_SUCCESS); From d328c1b443a64deaf7b4e34e7ef1467d47542605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 23 Oct 2026 14:46:48 +0200 Subject: [PATCH 11/17] Add SSL_OP_NO_DTLSv1 and apply DTLS min and max proto versions wolfSSL_set_max_proto_version() ignored DTLS1_VERSION and DTLS1_2_VERSION on a DTLS SSL, so capping a DTLS 1.3 SSL at DTLS 1.2 did nothing. A TLS version such as TLS1_2_VERSION set the mask bit but left the SSL on DTLS 1.3 until the handshake started, so wolfSSL_write_early_data() still accepted data. The DTLS versions now set the matching mask bits, and the SSL moves to the newest allowed version straight away, as wolfSSL_set_options() does. A call that fails because no version is left restores the mask it found. DTLS1_VERSION needs old TLS, as it already did for the minimum. DTLS1_3_VERSION is accepted by the SSL and CTX min and max setters and is in protoVerTbl, so a maximum of 0 walks to DTLS 1.3. Before, the walk stopped at DTLS1_2_VERSION and capped a DTLS CTX at DTLS 1.2. On a fixed-version DTLS method below DTLS 1.3, DTLS1_3_VERSION fails rather than turning the method into DTLS 1.3, so a maximum of 0 walks on to DTLS1_2_VERSION as before. A TLS-numbered minimum on a DTLS SSL or CTX, from a min proto version of 0, a TLS version, or wolfSSL_SetMinVersion(), was compared as a DTLS minor by supported_versions, so a DTLS 1.3 client sent an empty list and the server answered decode_error. The min setters now store the DTLS version that shares the mask bit. A DTLS version is refused as the minimum of a TLS SSL or CTX, through the proto setters or wolfSSL_SetMinVersion(), and as the maximum of a TLS SSL. The CTX max setter refuses a version of the other kind before it sets any mask bit; it used to set them first, so a failed TLS1_2_VERSION maximum on a DTLS CTX still masked DTLS 1.3. GetMinProtoVersion() reports DTLS minimums, so wolfSSL_CTX_set_max_proto_version() no longer drops one when it restores the minimum, and a DTLS CTX minimum no longer clears the CTX's version options, which compared TLS table positions with DTLS ones. Without DTLS 1.3 built, a TLS 1.3 minimum on a DTLS SSL or CTX is refused, since no version could meet it, and GetMinProtoVersion() reports DTLS 1.3 only when it is built. DtlsMaskVersion() kept the current version when the mask allowed it, without checking the minimum, so WOLFSSL_OP_NO_TLSv1_3 followed by wolfSSL_SetMinVersion(ssl, WOLFSSL_DTLSV1_3) ran a DTLS 1.2 handshake. It now checks both and fails with VERSION_ERROR. Add SSL_OP_NO_DTLSv1. It is WOLFSSL_OP_NO_TLSv1_1, which has OpenSSL's SSL_OP_NO_DTLSv1 value and is the bit wolfSSL masks DTLS 1.0 with; on a TLS object it masks TLS 1.1 rather than TLS 1.0. The wolfSSL_CTX_set_options() table lists the three DTLS options. Tests: a minimum set after the mask, through either setter, on the client and the server; the SSL max proto version at DTLS 1.0, 1.2, 1.3, a TLS version and 0; a CTX minimum and maximum of 0; a CTX maximum of 0 after a DTLS 1.2 maximum; a fixed DTLS 1.2 CTX under DTLS 1.0, DTLS 1.3 and 0 maximums; TLS 1.2, TLS 1.1 and TLS 1.3 minimums on a DTLS SSL and CTX; a TLS maximum on a DTLS CTX; failed maximums leaving the SSL unchanged; a TLS 1.1 maximum; the CTX DTLS 1.3 minimum surviving a later maximum and keeping a masked DTLS 1.3 masked; and DTLS versions refused on TLS objects. --- doc/dox_comments/header_files/ssl.h | 3 + src/internal.c | 32 ++- src/ssl.c | 144 ++++++++++++- tests/api/test_dtls.c | 306 +++++++++++++++++++++++++++- wolfssl/internal.h | 1 + wolfssl/ssl.h | 1 + 6 files changed, 468 insertions(+), 19 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 4a1d0e0f51a..f9fffb8d8d6 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -7660,6 +7660,9 @@ long wolfSSL_CTX_set_tlsext_opaque_prf_input_callback_arg( | SSL_OP_NO_TLSv1_1 | Disable TLS 1.1 | | SSL_OP_NO_TLSv1_2 | Disable TLS 1.2 | | SSL_OP_NO_TLSv1_3 | Disable TLS 1.3 (requires WOLFSSL_TLS13) | + | SSL_OP_NO_DTLSv1 | Disable DTLS 1.0; same bit as SSL_OP_NO_TLSv1_1, so it disables TLS 1.1 on a TLS object | + | SSL_OP_NO_DTLSv1_2 | Disable DTLS 1.2; same bit as SSL_OP_NO_TLSv1_2 | + | SSL_OP_NO_DTLSv1_3 | Disable DTLS 1.3 (requires WOLFSSL_DTLS13); same bit as SSL_OP_NO_TLSv1_3 | | SSL_OP_NO_COMPRESSION | Disable record-layer compression (no-op unless HAVE_LIBZ) | | SSL_OP_NO_TICKET | Disable RFC 5077 session tickets (TLS 1.2 only; TLS 1.3 ignores this flag); requires HAVE_SESSION_TICKET and (OPENSSL_EXTRA or HAVE_WEBSERVER or WOLFSSL_WPAS_SMALL) | | SSL_OP_NO_RENEGOTIATION | Reject peer-initiated renegotiation | diff --git a/src/internal.c b/src/internal.c index 26a82fbe9cd..22a433bf06d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8200,17 +8200,19 @@ int InitSSL_Suites(WOLFSSL* ssl) return WOLFSSL_SUCCESS; } -/* This function inherits a WOLFSSL_CTX's fields into an SSL object. - It is used during initialization and to switch an ssl's CTX with - wolfSSL_Set_SSL_CTX. Requires ssl->suites alloc and ssl-arrays with PSK - unless writeDup is on. - - ssl object to initialize - ctx parent factory - writeDup flag indicating this is a write dup only - - WOLFSSL_SUCCESS return value on success */ #ifdef WOLFSSL_DTLS +/* Maps a TLS minimum to the DTLS version that shares its mask bit. */ +byte DtlsMinDowngrade(byte minDowngrade) +{ + if (minDowngrade == TLSv1_3_MINOR) + return DTLSv1_3_MINOR; + if (minDowngrade == TLSv1_2_MINOR) + return DTLSv1_2_MINOR; + if (minDowngrade < DTLSv1_3_MINOR) + return DTLS_MINOR; + return minDowngrade; +} + /* Returns the newest built DTLS minor, no newer than maxMinor, that the version * mask and minDowngrade allow, or 0. DTLS minors count down. */ byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) @@ -8249,6 +8251,16 @@ byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) } #endif +/* This function inherits a WOLFSSL_CTX's fields into an SSL object. + It is used during initialization and to switch an ssl's CTX with + wolfSSL_Set_SSL_CTX. Requires ssl->suites alloc and ssl-arrays with PSK + unless writeDup is on. + + ssl object to initialize + ctx parent factory + writeDup flag indicating this is a write dup only + + WOLFSSL_SUCCESS return value on success */ #ifdef OPENSSL_EXTRA /* Apply the protocol version mask and downgrade rules. Returns VERSION_ERROR * when the requested options are inconsistent, 0 otherwise. */ diff --git a/src/ssl.c b/src/ssl.c index 0ecda5e9842..4c7d1837da6 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -2165,9 +2165,18 @@ int wolfSSL_clear_group_messages(WOLFSSL* ssl) } /* make minVersion the internal equivalent SSL version */ -static int SetMinVersionHelper(byte* minVersion, int version) +static int SetMinVersionHelper(byte* minVersion, int version, byte major) { (void)minVersion; + (void)major; + +#ifdef WOLFSSL_DTLS + if ((version == WOLFSSL_DTLSV1 || version == WOLFSSL_DTLSV1_2 || + version == WOLFSSL_DTLSV1_3) && major != DTLS_MAJOR) { + WOLFSSL_MSG("Mismatched protocol version"); + return BAD_FUNC_ARG; + } +#endif switch (version) { #if defined(WOLFSSL_ALLOW_SSLV3) && !defined(NO_OLD_TLS) @@ -2196,6 +2205,10 @@ static int SetMinVersionHelper(byte* minVersion, int version) #endif #ifdef WOLFSSL_TLS13 case WOLFSSL_TLSV1_3: + #if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_DTLS13) + if (major == DTLS_MAJOR) + return BAD_FUNC_ARG; + #endif *minVersion = TLSv1_3_MINOR; break; #endif @@ -2218,6 +2231,10 @@ static int SetMinVersionHelper(byte* minVersion, int version) WOLFSSL_MSG("Bad function argument"); return BAD_FUNC_ARG; } +#ifdef WOLFSSL_DTLS + if (major == DTLS_MAJOR) + *minVersion = DtlsMinDowngrade(*minVersion); +#endif return WOLFSSL_SUCCESS; } @@ -2242,7 +2259,8 @@ int wolfSSL_CTX_SetMinVersion(WOLFSSL_CTX* ctx, int version) } #endif /* WOLFSSL_SYS_CRYPTO_POLICY */ - ret = SetMinVersionHelper(&ctx->minDowngrade, version); + ret = SetMinVersionHelper(&ctx->minDowngrade, version, + ctx->method->version.major); if (ret == WOLFSSL_SUCCESS) ctx->minVersionSet = 1; @@ -2285,7 +2303,8 @@ int wolfSSL_SetMinVersion(WOLFSSL* ssl, int version) } #endif /* WOLFSSL_SYS_CRYPTO_POLICY */ - ret = SetMinVersionHelper(&ssl->options.minDowngrade, version); + ret = SetMinVersionHelper(&ssl->options.minDowngrade, version, + ssl->version.major); if (ret == WOLFSSL_SUCCESS) { ssl->options.minVersionSet = 1; RestoreDowngrade(ssl); @@ -5054,7 +5073,10 @@ static const int protoVerTbl[] = { TLS1_2_VERSION, TLS1_3_VERSION, DTLS1_VERSION, - DTLS1_2_VERSION + DTLS1_2_VERSION, +#ifdef WOLFSSL_DTLS13 + DTLS1_3_VERSION, +#endif }; /* number of protocol versions listed in protoVerTbl */ #define NUMBER_OF_PROTOCOLS (sizeof(protoVerTbl)/sizeof(int)) @@ -5078,6 +5100,7 @@ static const int protoVerTbl[] = { * * TLS1_3_VERSION * * DTLS1_VERSION * * DTLS1_2_VERSION + * * DTLS1_3_VERSION * @return WOLFSSL_SUCCESS on valid settings and WOLFSSL_FAILURE when no * protocol versions are left enabled. */ @@ -5126,11 +5149,20 @@ static int Set_CTX_min_proto_version(WOLFSSL_CTX* ctx, int version) case DTLS1_2_VERSION: ctx->minDowngrade = DTLSv1_2_MINOR; break; + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + ctx->minDowngrade = DTLSv1_3_MINOR; + break; + #endif #endif default: WOLFSSL_MSG("Unrecognized protocol version or not compiled in"); return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if (ctx->method->version.major == DTLS_MAJOR) + ctx->minDowngrade = DtlsMinDowngrade(ctx->minDowngrade); +#endif switch (version) { #ifndef NO_TLS @@ -5154,6 +5186,9 @@ static int Set_CTX_min_proto_version(WOLFSSL_CTX* ctx, int version) #ifdef WOLFSSL_DTLS case DTLS1_VERSION: case DTLS1_2_VERSION: + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + #endif break; #endif default: @@ -5179,6 +5214,13 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) if (ctx == NULL) { return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if ((version >> 8) == DTLS_MAJOR && + ctx->method->version.major != DTLS_MAJOR) { + WOLFSSL_MSG("Mismatched protocol version"); + return WOLFSSL_FAILURE; + } +#endif if (version != 0) { proto = version; @@ -5210,7 +5252,7 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) break; } } - if (idx < i) { + if (idx < i && ctx->method->version.major != DTLS_MAJOR) { wolfSSL_CTX_clear_options(ctx, WOLFSSL_OP_NO_TLSv1 | WOLFSSL_OP_NO_TLSv1_1 | WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_3); @@ -5244,6 +5286,7 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) * * TLS1_3_VERSION * * DTLS1_VERSION * * DTLS1_2_VERSION + * * DTLS1_3_VERSION * @return WOLFSSL_SUCCESS on valid settings and WOLFSSL_FAILURE when no * protocol versions are left enabled. */ @@ -5256,6 +5299,13 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) WOLFSSL_MSG("Bad parameter"); return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if (((ver >> 8) == DTLS_MAJOR) != + (ctx->method->version.major == DTLS_MAJOR)) { + WOLFSSL_MSG("Mismatched protocol version"); + return WOLFSSL_FAILURE; + } +#endif switch (ver) { #ifndef NO_TLS @@ -5281,8 +5331,13 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) break; #endif #ifdef WOLFSSL_DTLS + #ifndef NO_OLD_TLS case DTLS1_VERSION: + #endif case DTLS1_2_VERSION: + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + #endif break; #endif default: @@ -5309,6 +5364,9 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) #ifdef WOLFSSL_DTLS case DTLS1_VERSION: case DTLS1_2_VERSION: + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + #endif if (ctx->method->version.major != DTLS_MAJOR) { WOLFSSL_MSG("Mismatched protocol version"); return WOLFSSL_FAILURE; @@ -5342,6 +5400,16 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) case DTLS1_2_VERSION: ctx->method->version.minor = DTLSv1_2_MINOR; break; + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + if (!ctx->method->downgrade && + ctx->method->version.minor != DTLSv1_3_MINOR) { + WOLFSSL_MSG("Fixed-version DTLS method below DTLS 1.3"); + return WOLFSSL_FAILURE; + } + ctx->method->version.minor = DTLSv1_3_MINOR; + break; + #endif #endif default: WOLFSSL_MSG("Unrecognized protocol version or not compiled in"); @@ -5402,6 +5470,12 @@ static int Set_SSL_min_proto_version(WOLFSSL* ssl, int ver) if (ssl == NULL) { return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if ((ver >> 8) == DTLS_MAJOR && ssl->version.major != DTLS_MAJOR) { + WOLFSSL_MSG("Mismatched protocol version"); + return WOLFSSL_FAILURE; + } +#endif switch (ver) { #ifndef NO_TLS @@ -5440,11 +5514,20 @@ static int Set_SSL_min_proto_version(WOLFSSL* ssl, int ver) case DTLS1_2_VERSION: ssl->options.minDowngrade = DTLSv1_2_MINOR; break; + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + ssl->options.minDowngrade = DTLSv1_3_MINOR; + break; + #endif #endif default: WOLFSSL_MSG("Unrecognized protocol version or not compiled in"); return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if (ssl->version.major == DTLS_MAJOR) + ssl->options.minDowngrade = DtlsMinDowngrade(ssl->options.minDowngrade); +#endif switch (ver) { #ifndef NO_TLS @@ -5468,6 +5551,9 @@ static int Set_SSL_min_proto_version(WOLFSSL* ssl, int ver) #ifdef WOLFSSL_DTLS case DTLS1_VERSION: case DTLS1_2_VERSION: + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + #endif break; #endif default: @@ -5511,6 +5597,10 @@ int wolfSSL_set_min_proto_version(WOLFSSL* ssl, int version) static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) { +#ifdef WOLFSSL_DTLS + unsigned long mask; + int ret; +#endif WOLFSSL_ENTER("Set_SSL_max_proto_version"); @@ -5518,6 +5608,13 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) WOLFSSL_MSG("Bad parameter"); return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if ((ver >> 8) == DTLS_MAJOR && ssl->version.major != DTLS_MAJOR) { + WOLFSSL_MSG("Mismatched protocol version"); + return WOLFSSL_FAILURE; + } + mask = ssl->options.mask; +#endif switch (ver) { case SSL2_VERSION: @@ -5541,15 +5638,32 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) break; #endif #ifdef WOLFSSL_DTLS + #ifndef NO_OLD_TLS case DTLS1_VERSION: + ssl->options.mask |= WOLFSSL_OP_NO_TLSv1_2; + FALL_THROUGH; + #endif case DTLS1_2_VERSION: + ssl->options.mask |= WOLFSSL_OP_NO_TLSv1_3; break; + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + break; + #endif #endif default: WOLFSSL_MSG("Unrecognized protocol version or not compiled in"); return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if (!ssl->msgsReceived.got_client_hello && + (ret = DtlsCheckVersionMask(ssl)) != 0) { + if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) + ssl->options.mask = mask; + return WOLFSSL_FAILURE; + } +#endif return CheckSslMethodVersion(ssl->version.major, ssl->options.mask); } @@ -5608,6 +5722,21 @@ static int GetMinProtoVersion(int minDowngrade) case TLSv1_3_MINOR: ret = TLS1_3_VERSION; break; +#endif +#ifdef WOLFSSL_DTLS + #ifndef NO_OLD_TLS + case DTLS_MINOR: + ret = DTLS1_VERSION; + break; + #endif + case DTLSv1_2_MINOR: + ret = DTLS1_2_VERSION; + break; + #ifdef WOLFSSL_DTLS13 + case DTLSv1_3_MINOR: + ret = DTLS1_3_VERSION; + break; + #endif #endif default: ret = 0; @@ -7692,13 +7821,12 @@ static int DtlsMaskVersion(WOLFSSL* ssl) ssl->options.side != WOLFSSL_SERVER_END); byte minor; - /* A minimum of DTLS_MINOR tests the mask alone. */ if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || ssl->options.handShakeDone || ssl->options.connectState != CONNECT_BEGIN || ssl->options.acceptState != ACCEPT_BEGIN || - DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) == - ssl->version.minor) { + DtlsAllowedMinor(ssl, ssl->version.minor, + ssl->options.minDowngrade) == ssl->version.minor) { return 0; } /* A resumed session lowered the version to its own. */ diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index c1755ce8406..6fe29eafe1b 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16934,6 +16934,238 @@ static int test_dtls13_mask_in_client_hello(void) } #endif +/* A CTX maximum of 0 lifts an earlier DTLS maximum. */ +static int test_dtls13_ctx_max_proto_reset(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A fixed DTLS 1.2 method is not raised to DTLS 1.3, and a maximum of 0 takes + * it back to DTLS 1.2 after a lower maximum. */ +static int test_dtls13_ctx_max_proto_fixed(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_2_client_method, wolfDTLS_server_method), 0); +#ifndef NO_OLD_TLS + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_VERSION), + WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A TLS minimum on a DTLS SSL or CTX still offers DTLS 1.3, and a maximum that + * leaves no version fails without changing the SSL. */ +static int test_dtls13_min_max_tls_version(int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + long opts = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (mode == 2) { + ExpectIntEQ(wolfSSL_CTX_SetMinVersion(ctx_c, WOLFSSL_TLSV1_2), + WOLFSSL_SUCCESS); + } + else if (mode == 5) { + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx_c, TLS1_2_VERSION), + WOLFSSL_SUCCESS); + } + else if (mode == 6) { + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, TLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx_c) & WOLFSSL_OP_NO_TLSv1_3, 0); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (mode == 1) { + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_FAILURE); + } + else if (mode == 0) { + ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, WOLFSSL_TLSV1_2), + WOLFSSL_SUCCESS); + } + else if (mode == 3) { + opts = wolfSSL_get_options(ssl_c); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, TLS1_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl_c), opts); + } +#ifndef NO_OLD_TLS + else if (mode == 4) { + ExpectIntEQ(wolfSSL_SetMinVersion(ssl_c, WOLFSSL_TLSV1_1), + WOLFSSL_SUCCESS); + } +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A TLS 1.3 CTX minimum on a DTLS CTX leaves only DTLS 1.3. */ +static int test_dtls13_ctx_min_tls13(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx_c, TLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_min_proto_version(ctx_c), DTLS1_3_VERSION); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntNE(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A minimum set after the mask moved the SSL to DTLS 1.2 still applies. */ +static int test_dtls13_mask_min_after(int onServer, int useProto) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL* ssl; + + 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); + ssl = onServer ? ssl_s : ssl_c; + if (ssl != NULL) + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_2_VERSION); + if (useProto) { + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + } + else { + ExpectIntEQ(wolfSSL_SetMinVersion(ssl, WOLFSSL_DTLSV1_3), + WOLFSSL_SUCCESS); + } + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* wolfSSL_set_max_proto_version() caps a DTLS SSL straight away. */ +static int test_dtls13_mask_max_proto(int onServer, int maxVer, + int expVersion) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL* ssl; + + 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); + ssl = onServer ? ssl_s : ssl_c; + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, maxVer), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl), expVersion); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), expVersion); + ExpectIntEQ(wolfSSL_version(ssl_s), expVersion); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A CTX minimum of 0 keeps DTLS 1.3 in the client's supported_versions. */ +static int test_dtls13_ctx_min_proto_zero(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + /* wolfSSL_negotiate() applies the mask too, on the server here. */ static int test_dtls13_mask_negotiate(void) { @@ -17023,12 +17255,43 @@ int test_dtls13_no_tls13_option(void) WOLFSSL_DTLSV1_3, 0), TEST_SUCCESS); #ifndef OPENSSL_COEXIST ExpectIntEQ(test_dtls13_mask_case(0, 0, SSL_OP_NO_DTLSv1_3, - SSL_OP_NO_DTLSv1_2, WOLFSSL_OP_NO_TLSv1_1, 0, 0), TEST_SUCCESS); + SSL_OP_NO_DTLSv1_2, SSL_OP_NO_DTLSv1, 0, 0), TEST_SUCCESS); #endif #ifndef WOLFSSL_NO_TLS12 ExpectIntEQ(test_dtls13_mask_server_walk(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_max_proto_reset(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_min_max_tls_version(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_min_max_tls_version(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_min_max_tls_version(2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_min_max_tls_version(3), TEST_SUCCESS); +#ifndef NO_OLD_TLS + ExpectIntEQ(test_dtls13_min_max_tls_version(4), TEST_SUCCESS); +#endif + ExpectIntEQ(test_dtls13_min_max_tls_version(5), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_min_max_tls_version(6), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_min_tls13(), TEST_SUCCESS); + for (onServer = 0; onServer <= 1; onServer++) { + ExpectIntEQ(test_dtls13_mask_min_after(onServer, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_min_after(onServer, 1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_2_VERSION, + DTLS1_2_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_proto(onServer, TLS1_2_VERSION, + DTLS1_2_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_3_VERSION, + DTLS1_3_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_proto(onServer, 0, DTLS1_3_VERSION), + TEST_SUCCESS); + #ifndef NO_OLD_TLS + ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_VERSION, + DTLS1_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_proto(onServer, TLS1_1_VERSION, + DTLS1_VERSION), TEST_SUCCESS); + #endif + } + ExpectIntEQ(test_dtls13_ctx_min_proto_zero(), TEST_SUCCESS); #ifdef HAVE_SNI ExpectIntEQ(test_dtls13_mask_in_client_hello(), TEST_SUCCESS); #endif @@ -17072,6 +17335,47 @@ int test_dtls13_no_tls13_option(void) wolfSSL_CTX_free(ctx); ctx = NULL; + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_min_proto_version(ctx), DTLS1_3_VERSION); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); + wolfSSL_free(ssl); + ssl = NULL; + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx) & WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_3); + ExpectNull(wolfSSL_new(ctx)); +#ifdef NO_OLD_TLS + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx, DTLS1_VERSION), + WOLFSSL_FAILURE); +#endif + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* DTLS versions are refused as a minimum or maximum on a TLS object. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_method())); + ExpectIntEQ(wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_DTLSV1_2), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_SetMinVersion(ssl, WOLFSSL_DTLSV1_2), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 692534218d0..b1d91589010 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8471,6 +8471,7 @@ WOLFSSL_LOCAL int DtlsUpdateWindow(WOLFSSL* ssl); WOLFSSL_LOCAL void DtlsResetState(WOLFSSL *ssl); WOLFSSL_LOCAL int DtlsIgnoreError(int err); WOLFSSL_LOCAL void DtlsSetSeqNumForReply(WOLFSSL* ssl); +WOLFSSL_LOCAL byte DtlsMinDowngrade(byte minDowngrade); WOLFSSL_LOCAL byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade); WOLFSSL_LOCAL int DtlsCheckVersionMask(WOLFSSL* ssl); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 41204285188..77fe472f429 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3074,6 +3074,7 @@ enum { #define SSL_OP_NO_TLSv1_3 WOLFSSL_OP_NO_TLSv1_3 #endif +#define SSL_OP_NO_DTLSv1 WOLFSSL_OP_NO_TLSv1_1 #define SSL_OP_NO_DTLSv1_2 WOLFSSL_OP_NO_TLSv1_2 #ifdef WOLFSSL_DTLS13 #define SSL_OP_NO_DTLSv1_3 WOLFSSL_OP_NO_TLSv1_3 From 85064c26018a7f159a7381494d99189024cfb2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 23 Oct 2026 14:46:48 +0200 Subject: [PATCH 12/17] Hand pre-1.3 DTLS connections to the legacy handshake wolfSSL_connect_TLSv13() and wolfSSL_accept_TLSv13() handed off to wolfSSL_connect() and wolfSSL_accept() only when their own mask check moved the version. wolfSSL_set_options(), wolfSSL_set_max_proto_version() and CTX setup now move a DTLS SSL to DTLS 1.2 before either entry point runs, so such an SSL went into the TLS 1.3 state machine. With ECH built in, SendTls13ClientHello() failed, since no ECH extension is added below TLS 1.3. Both entry points now hand any DTLS SSL below 1.3 to the legacy handshake, on every call. That also covers re-entry with non-blocking I/O after the hand-off, which used to run the TLS 1.3 state machine on the legacy connect or accept state. The hand-off clears tls1_3, since wolfSSL_connect() and wolfSSL_accept() route on that flag: setting a DTLS 1.3 session and then a DTLS 1.2 one left it set on a DTLS 1.2 SSL, and the two entry points called each other until the stack overflowed. The check only runs in builds with a pre-1.3 handshake. Tests: both entry points, driven directly over memio, complete a DTLS 1.2 handshake after SSL-level and CTX-level masking, and a client given a DTLS 1.3 session and then a DTLS 1.2 one completes a DTLS 1.2 handshake. --- src/tls13.c | 45 +++++++++--------- tests/api/test_dtls.c | 106 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 22 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 79d830ba7b3..00aa342321f 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16784,21 +16784,21 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) } #ifdef WOLFSSL_DTLS - if (ssl->options.connectState == CONNECT_BEGIN) { - byte minor = ssl->version.minor; - - if ((ret = DtlsCheckVersionMask(ssl)) != 0) { - ssl->error = ret; - WOLFSSL_ERROR(ssl->error); - return WOLFSSL_FATAL_ERROR; - } - if (ssl->version.minor != minor) { + if (ssl->options.connectState == CONNECT_BEGIN && + (ret = DtlsCheckVersionMask(ssl)) != 0) { + ssl->error = ret; + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + #if !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS) + if (ssl->version.major == DTLS_MAJOR && !IsAtLeastTLSv1_3(ssl->version)) { + ssl->options.tls1_3 = 0; #ifdef WOLFSSL_EARLY_DATA - ssl->earlyData = no_early_data; + ssl->earlyData = no_early_data; #endif - return wolfSSL_connect(ssl); - } + return wolfSSL_connect(ssl); } + #endif #endif /* make sure this wolfSSL object has arrays and rng setup. Protects @@ -18090,17 +18090,18 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) } #ifdef WOLFSSL_DTLS - if (ssl->options.acceptState == TLS13_ACCEPT_BEGIN) { - byte minor = ssl->version.minor; - - if ((ret = DtlsCheckVersionMask(ssl)) != 0) { - ssl->error = ret; - WOLFSSL_ERROR(ssl->error); - return WOLFSSL_FATAL_ERROR; - } - if (ssl->version.minor != minor) - return wolfSSL_accept(ssl); + if (ssl->options.acceptState == TLS13_ACCEPT_BEGIN && + (ret = DtlsCheckVersionMask(ssl)) != 0) { + ssl->error = ret; + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; } + #if !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS) + if (ssl->version.major == DTLS_MAJOR && !IsAtLeastTLSv1_3(ssl->version)) { + ssl->options.tls1_3 = 0; + return wolfSSL_accept(ssl); + } + #endif #endif /* make sure this wolfSSL object has arrays and rng setup. Protects diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 6fe29eafe1b..5e61f44f513 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -17166,6 +17166,107 @@ static int test_dtls13_ctx_min_proto_zero(void) return EXPECT_RESULT(); } +/* The TLS 1.3 entry points hand an SSL already at DTLS 1.2 to the DTLS 1.2 + * handshake, whether the SSL or the CTX mask moved it. */ +static int test_dtls13_mask_tls13_entry(int onCtx) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int retC = WOLFSSL_FATAL_ERROR; + int retS = WOLFSSL_FATAL_ERROR; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (onCtx) { + (void)wolfSSL_CTX_set_options(ctx_c, WOLFSSL_OP_NO_TLSv1_3); + (void)wolfSSL_CTX_set_options(ctx_s, WOLFSSL_OP_NO_TLSv1_3); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (!onCtx && ssl_c != NULL && ssl_s != NULL) { + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + (void)wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_TLSv1_3); + } + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + for (i = 0; EXPECT_SUCCESS() && i < 10 && + (retC != WOLFSSL_SUCCESS || retS != WOLFSSL_SUCCESS); i++) { + if (retC != WOLFSSL_SUCCESS) { + retC = wolfSSL_connect_TLSv13(ssl_c); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.tls1_3, 0); + } + if (retS != WOLFSSL_SUCCESS) + retS = wolfSSL_accept_TLSv13(ssl_s); + } + ExpectIntEQ(retC, WOLFSSL_SUCCESS); + ExpectIntEQ(retS, WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#ifdef HAVE_SESSION_TICKET +/* Setting a DTLS 1.3 session and then a DTLS 1.2 one leaves tls1_3 set on a + * DTLS 1.2 SSL; the hand-off between the entry points must not loop. */ +static int test_dtls13_stale_tls13_session(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess13 = NULL, *sess12 = NULL; + struct test_memio_ctx test_ctx; + char buf[8]; + + 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(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectNotNull(sess13 = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = 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, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess12 = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_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, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess13), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess12), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + + wolfSSL_SESSION_free(sess13); + wolfSSL_SESSION_free(sess12); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + /* wolfSSL_negotiate() applies the mask too, on the server here. */ static int test_dtls13_mask_negotiate(void) { @@ -17291,6 +17392,11 @@ int test_dtls13_no_tls13_option(void) DTLS1_VERSION), TEST_SUCCESS); #endif } + for (onCtx = 0; onCtx <= 1; onCtx++) + ExpectIntEQ(test_dtls13_mask_tls13_entry(onCtx), TEST_SUCCESS); +#ifdef HAVE_SESSION_TICKET + ExpectIntEQ(test_dtls13_stale_tls13_session(), TEST_SUCCESS); +#endif ExpectIntEQ(test_dtls13_ctx_min_proto_zero(), TEST_SUCCESS); #ifdef HAVE_SNI ExpectIntEQ(test_dtls13_mask_in_client_hello(), TEST_SUCCESS); From 39fcfac5e2f841b7468e54e6f28df0eea8287415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 27 Oct 2026 18:22:38 +0100 Subject: [PATCH 13/17] Let DTLS max proto and options lift an earlier cap wolfSSL_set_max_proto_version() and wolfSSL_clear_options() only moved a DTLS SSL down. After a maximum of DTLS 1.2, a maximum of DTLS 1.3 or 0 returned success and left the SSL on DTLS 1.2, and clearing SSL_OP_NO_DTLSv1_3 did not bring DTLS 1.3 back. When a maximum loosens the mask, or wolfSSL_clear_options() clears a DTLS version bit, the version is now walked down from the CTX method version, so the SSL moves back up. A lift never moves an allowed version down, and keeps an allowed version when the walk finds none, so a CTX maximum lowered after wolfSSL_new() does not reach the SSL that way. A resuming client keeps its session's version while the mask allows it. wolfSSL_set_options() and the checks at handshake start still only step down. wolfSSL_SetSession() now fails when the suites cannot be rebuilt for the version the mask moved it to; a session with no version left is still reported when the handshake starts. The DTLS maximum shares the WOLFSSL_OP_NO_TLSv1_2/_3 bits with SSL_OP_NO_DTLSv1_2/_3. The new options field dtlsMaxMask records the bits the maximum added itself, and a later maximum clears only those, so a version the application or the CTX masked off stays off. wolfSSL_set_options() and wolfSSL_clear_options() take a bit out of the record. The mask no longer moves the version while the first handshake is under way. DtlsHandshakeStarted() reports that from handShakeDone, the connect and accept states, got_client_hello, buffered output and, with async crypto, an async state other than TLS_ASYNC_BEGIN or handler args kept in ssl->async, since DoTls13ClientHello() clears got_client_hello when it pends and can pend before it leaves TLS_ASYNC_BEGIN. Before, an async accept that re-entered could switch to DTLS 1.2 partway through a DTLS 1.3 ClientHello, and a client could have its version raised between WANT_WRITE and the retry that sent the ClientHello. In that window a DTLS wolfSSL_set_max_proto_version() that would change the mask fails without changing anything, since it could no longer take effect, and one that changes nothing succeeds. A maximum of 0 on a DTLS SSL means the newest DTLS version, so it fails there too instead of settling on the current cap. The maximum also refuses a change after the handshake: a lift there would make a later DTLS 1.2 renegotiation treat the server's downgrade sentinel as an attack. wolfSSL_clear() lets it apply again, and now resets the async handler state in builds without TLS 1.3 too, where a stale one would leave the SSL looking started. wolfSSL_clear_options() reports a failed suite rebuild but not VERSION_ERROR, which the handshake start reports, as wolfSSL_set_options() does. wolfSSL_CTX_set_max_proto_version() raises a fixed-version DTLS method to DTLS 1.3, as it does fixed TLS methods. A DTLS 1.3 method lowered to DTLS 1.2 could not be restored before. A wolfDTLSv1_2_method() CTX given a maximum of 0 now becomes DTLS 1.3-only, as a wolfTLSv1_2_method() CTX becomes TLS 1.3-only. wolfSSL_CTX_set_max_proto_version() with DTLS1_2_VERSION or DTLS1_VERSION also sets WOLFSSL_OP_NO_TLSv1_3 and WOLFSSL_OP_NO_TLSv1_2, as the TLS versions do (WOLFSSL_OP_NO_TLSv1_3 only when DTLS 1.3 is built, at both levels, so a maximum of 0 adds no bit that means nothing), so an SSL keeps the CTX maximum it was created with when the CTX maximum is raised later. Before, such an SSL stayed on DTLS 1.2 while TLSv1_3_Capable() read DTLS 1.3 from the shared method, and it rejected the server's downgrade sentinel. wolfSSL_CTX_get_max_proto_version() returns DTLS versions for a DTLS CTX, so its result can be passed back to the setter. It honors the CTX minimum, and for a fixed-version DTLS method it returns that version, or 0 when the mask or the minimum excludes it. It shares the version walk with DtlsAllowedMinor() through the new DtlsMaskAllowedMinor(), which takes the mask directly. wolfSSL_CTX_set_min_proto_version() and wolfSSL_CTX_set_max_proto_version() fail on a DTLS CTX when the mask and the minimum leave no DTLS version, or, for a fixed-version DTLS method, leave out the method's own version, as the TLS setters do. Before, they returned success and every later wolfSSL_new() failed with VERSION_ERROR. A rejected DTLS call restores the mask, the minimum, the method version and the min/max flags, so the CTX keeps working. wolfSSL_set_max_proto_version() refuses a TLS version on a DTLS SSL, like the CTX setter and OpenSSL. On a fixed-version DTLS SSL it also refuses a maximum below the SSL's version, judged by the bits the maximum adds, so an option the application set does not make every later maximum fail. Before, the bit was set and the SSL kept its version, so the ClientHello went out with an empty supported_versions list. DtlsCheckVersionMask() takes a mode (handshake start, lift, or the server's step-down inside DoClientHello()), so every caller goes through the same checks, and the DTLS mask helpers move above the OPENSSL_EXTRA block so the setters can use them. DtlsMaskVersion() no longer records VERSION_ERROR itself, so the option setters that ignore it leave nothing on the error queue; DoClientHello() records it where it sends the alert. When DtlsCheckVersionMask() lifts a server SSL to DTLS 1.3, it runs InitSSL_DtlsServer() again, since the DTLS 1.3 cookie secret and ClientHello fragmentation are only set up for the version the SSL was created with. Without them every DTLS 1.3 handshake of a server created under SSL_OP_NO_DTLSv1_3 and lifted later fails with COOKIE_ERROR. InitSSL_DtlsServer() only fills in what is missing. Tests: a server created under a CTX SSL_OP_NO_DTLSv1_3 and lifted, with a plain and a fragmented post-quantum ClientHello, a maximum of 0 after a handshake and a lower one after wolfSSL_clear(), a DTLS 1.2 cap lifted by a maximum, by a maximum of 0 and by clearing the option, a maximum lifted by clearing the option, a DTLS 1.0 cap lifted to DTLS 1.2, a lift under a CTX maximum lowered to DTLS 1.0 that keeps DTLS 1.2, the CTX maximum of DTLS 1.0, fixed DTLS 1.3 and DTLS 1.2 CTXs raised to DTLS 1.3, a TLS 1.2 maximum refused on a DTLS SSL, CTX and user options that survive later maximums, a maximum and option calls while the first ClientHello waits on WANT_WRITE, a maximum after the ClientHello was sent, maximums after the handshake and after wolfSSL_clear(), maximums that repeat, lift or tighten from a server SNI callback, a maximum of 0 mid-handshake, the CTX maximum read back and set again, a lower maximum refused and an equal one or 0 accepted on a fixed DTLS 1.3 SSL with SSL_OP_NO_DTLSv1_3 set and on a fixed DTLS 1.2 SSL, a DTLS 1.3 CTX minimum refused under SSL_OP_NO_DTLSv1_3 and a DTLS 1.2 CTX maximum refused under a DTLS 1.3 minimum, both leaving a working CTX, a minimum refused on a fixed DTLS 1.3 method that masks its own version, the CTX maximum of a fixed DTLS 1.3 method that masks its own version, a CTX maximum raised after wolfSSL_new(), option and maximum calls after the CTX maximum was lowered, the return value of wolfSSL_clear_options() when no version is left, the exact error for a DTLS 1.3-only client against a DTLS 1.2 server, and a TLS 1.3 minimum refused on a DTLS object without DTLS 1.3. --- src/internal.c | 31 +-- src/ssl.c | 330 ++++++++++++++++++------ src/ssl_api_hs.c | 4 +- src/ssl_sess.c | 5 +- src/tls13.c | 4 +- tests/api/test_dtls.c | 574 ++++++++++++++++++++++++++++++++++++++++-- tests/api/test_dtls.h | 6 +- wolfssl/internal.h | 15 +- 8 files changed, 856 insertions(+), 113 deletions(-) diff --git a/src/internal.c b/src/internal.c index 22a433bf06d..bcd4e611488 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2818,10 +2818,6 @@ void InitSSL_Method(WOLFSSL_METHOD* method, ProtocolVersion pv) method->downgrade = 0; } -#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) -static int InitSSL_DtlsServer(WOLFSSL* ssl); -#endif /* WOLFSSL_DTLS && !NO_WOLFSSL_SERVER */ - #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) || \ defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) int InitSSL_Side(WOLFSSL* ssl, word16 side) @@ -8215,15 +8211,8 @@ byte DtlsMinDowngrade(byte minDowngrade) /* Returns the newest built DTLS minor, no newer than maxMinor, that the version * mask and minDowngrade allow, or 0. DTLS minors count down. */ -byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) +byte DtlsMaskAllowedMinor(unsigned long mask, byte maxMinor, byte minDowngrade) { -#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ - defined(WOLFSSL_WPAS_SMALL) - unsigned long mask = ssl->options.mask; -#else - unsigned long mask = 0; -#endif - #ifdef WOLFSSL_DTLS13 if (maxMinor <= DTLSv1_3_MINOR && (mask & WOLFSSL_OP_NO_TLSv1_3) == 0) { @@ -8243,12 +8232,23 @@ byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) return DTLS_MINOR; } #endif - (void)ssl; (void)mask; (void)maxMinor; (void)minDowngrade; return 0; } + +/* DtlsMaskAllowedMinor() with the SSL's own version mask. */ +byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade) +{ +#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \ + defined(WOLFSSL_WPAS_SMALL) + return DtlsMaskAllowedMinor(ssl->options.mask, maxMinor, minDowngrade); +#else + (void)ssl; + return DtlsMaskAllowedMinor(0, maxMinor, minDowngrade); +#endif +} #endif /* This function inherits a WOLFSSL_CTX's fields into an SSL object. @@ -9285,7 +9285,7 @@ static void InitSSL_Tls13Options(WOLFSSL* ssl, WOLFSSL_CTX* ctx) * @return 0 on success, and when there is nothing to do. * @return A negative error code when a secret cannot be generated. */ -static int InitSSL_DtlsServer(WOLFSSL* ssl) +int InitSSL_DtlsServer(WOLFSSL* ssl) { int ret; @@ -44564,8 +44564,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #ifdef WOLFSSL_DTLS - ret = DtlsCheckVersionMask(ssl); + ret = DtlsCheckVersionMask(ssl, DTLS_MASK_IN_HELLO); if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) { + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); /* propagate socket errors to avoid re-calling send alert */ if (SendAlert(ssl, alert_fatal, wolfssl_alert_protocol_version) == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { diff --git a/src/ssl.c b/src/ssl.c index 4c7d1837da6..498b9ddc6ab 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -4901,6 +4901,95 @@ size_t wolfSSL_get_server_random(const WOLFSSL *ssl, unsigned char *out, #endif /* !NO_WOLFSSL_SERVER */ #endif /* OPENSSL_EXTRA || WOLFSSL_WPAS_SMALL || HAVE_SECRET_CALLBACK */ +#ifdef WOLFSSL_DTLS +/* Returns 1 once the first handshake has started, including a first + * ClientHello that is still being built, sent or parsed. */ +static int DtlsHandshakeStarted(const WOLFSSL* ssl) +{ + int ret; + + ret = ssl->options.handShakeDone || + ssl->options.connectState != CONNECT_BEGIN || + ssl->options.acceptState != ACCEPT_BEGIN || + ssl->msgsReceived.got_client_hello || + ssl->buffers.outputBuffer.length > 0; +#ifdef WOLFSSL_ASYNC_CRYPT + if (ssl->options.asyncState != TLS_ASYNC_BEGIN || + (ssl->async != NULL && ssl->async->freeArgs != NULL)) + ret = 1; +#endif + return ret; +} + +/* Steps a DTLS SSL that may downgrade to a version its mask allows, or up to + * the method version for a lift. Returns 1 if it moved, 0, or VERSION_ERROR. */ +static int DtlsMaskVersion(WOLFSSL* ssl, enum DtlsMaskMode mode) +{ + byte maxMinor = ssl->version.minor; + byte resuming = (byte)(ssl->options.resuming && + ssl->options.side != WOLFSSL_SERVER_END); + byte allowed; + byte minor; + + if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || + ssl->options.handShakeDone) { + return 0; + } + if (mode == DTLS_MASK_IN_HELLO) { + if (ssl->options.connectState != CONNECT_BEGIN || + ssl->options.acceptState != ACCEPT_BEGIN) { + return 0; + } + } + else if (DtlsHandshakeStarted(ssl)) { + return 0; + } + allowed = (byte)(DtlsAllowedMinor(ssl, ssl->version.minor, + ssl->options.minDowngrade) == ssl->version.minor); + if (allowed && (mode != DTLS_MASK_LIFT || resuming)) + return 0; + /* Lifts walk from the method; a resumed session lowered the version. */ + if (mode == DTLS_MASK_LIFT || resuming) + maxMinor = ssl->ctx->method->version.minor; + minor = DtlsAllowedMinor(ssl, maxMinor, ssl->options.minDowngrade); + if (minor == 0 && !allowed) { + WOLFSSL_MSG("No DTLS version left that the options allow"); + return VERSION_ERROR; + } + /* A lift never moves an allowed version down; DTLS minors count down. */ + if (minor == ssl->version.minor || + (allowed && (minor == 0 || minor > ssl->version.minor))) + return 0; + ssl->version.minor = minor; + ssl->options.tls1_3 = 0; + if (resuming && ssl->session != NULL && + ssl->session->version.minor != minor) { + ssl->options.resuming = 0; + #ifdef HAVE_EXTENDED_MASTER + ssl->options.haveEMS = !ssl->options.disableEMS; + #endif + } + return 1; +} + +/* Applies the DTLS version mask and rebuilds the suites when the version + * moved. Returns VERSION_ERROR when no version is left. */ +int DtlsCheckVersionMask(WOLFSSL* ssl, enum DtlsMaskMode mode) +{ + int ret = DtlsMaskVersion(ssl, mode); + + if (ret == 1) { + ret = ResetSuitesForVersion(ssl); + #ifndef NO_WOLFSSL_SERVER + if (ret == 0 && ssl->options.side == WOLFSSL_SERVER_END && + IsAtLeastTLSv1_3(ssl->version)) + ret = InitSSL_DtlsServer(ssl); + #endif + } + return ret; +} +#endif + #ifdef OPENSSL_EXTRA #if !defined(NO_WOLFSSL_SERVER) /* Used to get the peer ephemeral public key sent during the connection @@ -5060,6 +5149,61 @@ static int CheckSslMethodVersion(byte major, unsigned long options) return WOLFSSL_SUCCESS; } +#ifdef WOLFSSL_DTLS +/* Returns 0 when the CTX mask and minimum leave no DTLS version. */ +static int CtxDtlsVersionLeft(const WOLFSSL_CTX* ctx) +{ + byte minor; + + if (ctx->method->version.major != DTLS_MAJOR) + return 1; + minor = DtlsMaskAllowedMinor(ctx->mask, ctx->method->version.minor, + ctx->minDowngrade); + if (minor == 0 || + (!ctx->method->downgrade && minor != ctx->method->version.minor)) { + WOLFSSL_MSG("All compiled in DTLS versions disabled"); + return 0; + } + return 1; +} +#endif + +#ifdef WOLFSSL_DTLS +/* The CTX version settings a rejected DTLS minimum or maximum restores. */ +typedef struct CtxVersionState { + unsigned long mask; + ProtocolVersion version; + byte minDowngrade; + byte minVersionSet; + word16 minProto:1; + word16 maxProto:1; +} CtxVersionState; + +static void CtxVersionSave(const WOLFSSL_CTX* ctx, CtxVersionState* st) +{ + st->mask = ctx->mask; + st->version = ctx->method->version; + st->minDowngrade = ctx->minDowngrade; + st->minVersionSet = ctx->minVersionSet; + st->minProto = ctx->minProto; + st->maxProto = ctx->maxProto; +} + +static int CtxVersionRestore(WOLFSSL_CTX* ctx, const CtxVersionState* st, + int ret) +{ + if (ret != WOLFSSL_SUCCESS && ctx->method->version.major == DTLS_MAJOR) { + ctx->mask = st->mask; + ctx->method->version = st->version; + ctx->minDowngrade = st->minDowngrade; + ctx->minVersionSet = st->minVersionSet; + ctx->minProto = st->minProto; + ctx->maxProto = st->maxProto; + } + return ret; +} +#endif + /** * protoVerTbl holds (D)TLS version numbers in ascending order. * Except DTLS versions, the newer version is located in the latter part of @@ -5196,6 +5340,10 @@ static int Set_CTX_min_proto_version(WOLFSSL_CTX* ctx, int version) return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if (!CtxDtlsVersionLeft(ctx)) + return WOLFSSL_FAILURE; +#endif return CheckSslMethodVersion(ctx->method->version.major, ctx->mask); } @@ -5208,6 +5356,9 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) int maxProto = 0; int i; int idx = 0; +#ifdef WOLFSSL_DTLS + CtxVersionState st; +#endif WOLFSSL_ENTER("wolfSSL_CTX_set_min_proto_version"); @@ -5220,6 +5371,7 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) WOLFSSL_MSG("Mismatched protocol version"); return WOLFSSL_FAILURE; } + CtxVersionSave(ctx, &st); #endif if (version != 0) { @@ -5264,6 +5416,9 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) ctx->minVersionSet = (version != 0); } +#ifdef WOLFSSL_DTLS + ret = CtxVersionRestore(ctx, &st, ret); +#endif return ret; } @@ -5333,12 +5488,18 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) #ifdef WOLFSSL_DTLS #ifndef NO_OLD_TLS case DTLS1_VERSION: + wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_2); + FALL_THROUGH; #endif case DTLS1_2_VERSION: #ifdef WOLFSSL_DTLS13 - case DTLS1_3_VERSION: + wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); #endif break; + #ifdef WOLFSSL_DTLS13 + case DTLS1_3_VERSION: + break; + #endif #endif default: WOLFSSL_MSG("Unrecognized protocol version or not compiled in"); @@ -5402,11 +5563,6 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) break; #ifdef WOLFSSL_DTLS13 case DTLS1_3_VERSION: - if (!ctx->method->downgrade && - ctx->method->version.minor != DTLSv1_3_MINOR) { - WOLFSSL_MSG("Fixed-version DTLS method below DTLS 1.3"); - return WOLFSSL_FAILURE; - } ctx->method->version.minor = DTLSv1_3_MINOR; break; #endif @@ -5415,6 +5571,10 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) WOLFSSL_MSG("Unrecognized protocol version or not compiled in"); return WOLFSSL_FAILURE; } + #ifdef WOLFSSL_DTLS + if (!CtxDtlsVersionLeft(ctx)) + ret = WOLFSSL_FAILURE; + #endif } return ret; } @@ -5428,12 +5588,18 @@ int wolfSSL_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); int minProto; byte minVersionSet; +#ifdef WOLFSSL_DTLS + CtxVersionState st; +#endif WOLFSSL_ENTER("wolfSSL_CTX_set_max_proto_version"); if (ctx == NULL) { return ret; } +#ifdef WOLFSSL_DTLS + CtxVersionSave(ctx, &st); +#endif /* clear out flags and reset min protocol version */ minProto = wolfSSL_CTX_get_min_proto_version(ctx); @@ -5445,7 +5611,11 @@ int wolfSSL_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) ctx->minVersionSet = minVersionSet; /* restoring, not setting, a minimum */ if (version != 0) { ctx->maxProto = 0; /* turn max proto flag off */ - return Set_CTX_max_proto_version(ctx, version); + ret = Set_CTX_max_proto_version(ctx, version); + #ifdef WOLFSSL_DTLS + ret = CtxVersionRestore(ctx, &st, ret); + #endif + return ret; } /* when 0 is specified as version, try to find out the min version from @@ -5459,6 +5629,9 @@ int wolfSSL_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) } } +#ifdef WOLFSSL_DTLS + ret = CtxVersionRestore(ctx, &st, ret); +#endif return ret; } @@ -5599,6 +5772,9 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) { #ifdef WOLFSSL_DTLS unsigned long mask; + unsigned long maxMask; + unsigned long want = 0; + unsigned long cleared; int ret; #endif @@ -5609,11 +5785,12 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) return WOLFSSL_FAILURE; } #ifdef WOLFSSL_DTLS - if ((ver >> 8) == DTLS_MAJOR && ssl->version.major != DTLS_MAJOR) { + if (((ver >> 8) == DTLS_MAJOR) != (ssl->version.major == DTLS_MAJOR)) { WOLFSSL_MSG("Mismatched protocol version"); return WOLFSSL_FAILURE; } mask = ssl->options.mask; + maxMask = ssl->options.dtlsMaxMask; #endif switch (ver) { @@ -5640,11 +5817,13 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) #ifdef WOLFSSL_DTLS #ifndef NO_OLD_TLS case DTLS1_VERSION: - ssl->options.mask |= WOLFSSL_OP_NO_TLSv1_2; + want = WOLFSSL_OP_NO_TLSv1_2; FALL_THROUGH; #endif case DTLS1_2_VERSION: - ssl->options.mask |= WOLFSSL_OP_NO_TLSv1_3; + #ifdef WOLFSSL_DTLS13 + want |= WOLFSSL_OP_NO_TLSv1_3; + #endif break; #ifdef WOLFSSL_DTLS13 case DTLS1_3_VERSION: @@ -5657,11 +5836,29 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) } #ifdef WOLFSSL_DTLS - if (!ssl->msgsReceived.got_client_hello && - (ret = DtlsCheckVersionMask(ssl)) != 0) { - if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) - ssl->options.mask = mask; - return WOLFSSL_FAILURE; + if (ssl->version.major == DTLS_MAJOR) { + /* Clear only the bits the previous maximum set. */ + cleared = mask & ~(maxMask & ~want); + if ((cleared | want) != mask && DtlsHandshakeStarted(ssl)) { + WOLFSSL_MSG("DTLS handshake already started"); + return WOLFSSL_FAILURE; + } + if (!ssl->options.downgrade && DtlsMaskAllowedMinor(want, + ssl->version.minor, DTLS_MINOR) != ssl->version.minor) { + WOLFSSL_MSG("Maximum below the fixed DTLS version"); + return WOLFSSL_FAILURE; + } + ssl->options.dtlsMaxMask = want & (maxMask | ~mask); + ssl->options.mask = cleared | want; + ret = DtlsCheckVersionMask(ssl, cleared != mask ? DTLS_MASK_LIFT : + DTLS_MASK_START); + if (ret != 0) { + if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) { + ssl->options.mask = mask; + ssl->options.dtlsMaxMask = maxMask; + } + return WOLFSSL_FAILURE; + } } #endif return CheckSslMethodVersion(ssl->version.major, ssl->options.mask); @@ -5677,6 +5874,15 @@ int wolfSSL_set_max_proto_version(WOLFSSL* ssl, int version) if (ssl == NULL) { return WOLFSSL_FAILURE; } +#ifdef WOLFSSL_DTLS + if (version == 0 && ssl->version.major == DTLS_MAJOR) { + #ifdef WOLFSSL_DTLS13 + version = DTLS1_3_VERSION; + #else + version = DTLS1_2_VERSION; + #endif + } +#endif if (version != 0) { return Set_SSL_max_proto_version(ssl, version); } @@ -5807,6 +6013,9 @@ int wolfSSL_CTX_get_max_proto_version(WOLFSSL_CTX* ctx) { int ret = 0; long options = 0; /* default to nothing set */ +#ifdef WOLFSSL_DTLS + byte minor; +#endif WOLFSSL_ENTER("wolfSSL_CTX_get_max_proto_version"); @@ -5817,6 +6026,16 @@ int wolfSSL_CTX_get_max_proto_version(WOLFSSL_CTX* ctx) if ((ctx != NULL) && ctx->maxProto) { ret = 0; } +#ifdef WOLFSSL_DTLS + else if ((ctx != NULL) && (ctx->method->version.major == DTLS_MAJOR)) { + minor = DtlsMaskAllowedMinor((unsigned long)options, + ctx->method->version.minor, ctx->minDowngrade); + if (!ctx->method->downgrade && minor != ctx->method->version.minor) + minor = 0; + ret = (minor != 0) ? ((DTLS_MAJOR << 8) | minor) : + WOLFSSL_FATAL_ERROR; + } +#endif else { ret = GetMaxProtoVersion(options); } @@ -6050,6 +6269,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #ifdef WOLFSSL_DTLS ssl->options.dtlsStateful = 0; #endif + #ifdef WOLFSSL_ASYNC_CRYPT + ssl->options.asyncState = TLS_ASYNC_BEGIN; + #endif #ifdef WOLFSSL_TLS13 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) ssl->options.pskNegotiated = 0; @@ -6070,10 +6292,8 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #endif #ifdef WOLFSSL_ASYNC_CRYPT ssl->options.buildArgs13Set = 0; - /* An abandoned handshake can leave a mid-flight handler resume - * state and a queued key-schedule event behind; a reused object - * must start fresh. */ - ssl->options.asyncState = TLS_ASYNC_BEGIN; + /* An abandoned handshake can leave a queued key-schedule event + * behind; a reused object must start fresh. */ if (ssl->asyncDev == &ssl->kdfAsyncDev) { if (ssl->kdfAsyncDev.event.state == WOLF_EVENT_STATE_PENDING && ssl->ctx != NULL) { @@ -7811,57 +8031,6 @@ int ResetSuitesForVersion(WOLFSSL* ssl) return 0; } -#ifdef WOLFSSL_DTLS -/* Moves a DTLS SSL that may downgrade, before its first handshake, to the - * newest version its mask allows. Returns 1 when it moved, or VERSION_ERROR. */ -static int DtlsMaskVersion(WOLFSSL* ssl) -{ - byte maxMinor = ssl->version.minor; - byte resuming = (byte)(ssl->options.resuming && - ssl->options.side != WOLFSSL_SERVER_END); - byte minor; - - if (ssl->version.major != DTLS_MAJOR || !ssl->options.downgrade || - ssl->options.handShakeDone || - ssl->options.connectState != CONNECT_BEGIN || - ssl->options.acceptState != ACCEPT_BEGIN || - DtlsAllowedMinor(ssl, ssl->version.minor, - ssl->options.minDowngrade) == ssl->version.minor) { - return 0; - } - /* A resumed session lowered the version to its own. */ - if (resuming) - maxMinor = ssl->ctx->method->version.minor; - minor = DtlsAllowedMinor(ssl, maxMinor, ssl->options.minDowngrade); - if (minor == 0) { - WOLFSSL_MSG("No DTLS version left that the options allow"); - WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); - return VERSION_ERROR; - } - ssl->version.minor = minor; - ssl->options.tls1_3 = 0; - if (resuming && ssl->session != NULL && - ssl->session->version.minor != minor) { - ssl->options.resuming = 0; - #ifdef HAVE_EXTENDED_MASTER - ssl->options.haveEMS = !ssl->options.disableEMS; - #endif - } - return 1; -} - -/* Applies the DTLS version mask at the start of a handshake. Returns - * VERSION_ERROR when no version is left. */ -int DtlsCheckVersionMask(WOLFSSL* ssl) -{ - int ret = DtlsMaskVersion(ssl); - - if (ret == 1) - ret = ResetSuitesForVersion(ssl); - return ret; -} -#endif - long wolfSSL_set_options(WOLFSSL* ssl, long op) { WOLFSSL_ENTER("wolfSSL_set_options"); @@ -7911,8 +8080,8 @@ long wolfSSL_set_options(WOLFSSL* ssl, long op) #endif #ifdef WOLFSSL_DTLS - if (!ssl->msgsReceived.got_client_hello) - (void)DtlsMaskVersion(ssl); + ssl->options.dtlsMaxMask &= (unsigned long)~op; + (void)DtlsMaskVersion(ssl, DTLS_MASK_START); #endif /* in the case of a version change the cipher suites should be reset */ @@ -7980,10 +8149,27 @@ int wolfSSL_SSL_renegotiate_pending(WOLFSSL *s) long wolfSSL_clear_options(WOLFSSL* ssl, long opt) { +#ifdef WOLFSSL_DTLS + unsigned long lifted; + int ret; +#endif + WOLFSSL_ENTER("wolfSSL_clear_options"); if(ssl == NULL) return WOLFSSL_FAILURE; +#ifdef WOLFSSL_DTLS + lifted = ssl->options.mask & (unsigned long)opt & + (WOLFSSL_OP_NO_TLSv1_1 | WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_3); + ssl->options.dtlsMaxMask &= (unsigned long)~opt; +#endif ssl->options.mask &= (unsigned long)~opt; +#ifdef WOLFSSL_DTLS + if (lifted != 0) { + ret = DtlsCheckVersionMask(ssl, DTLS_MASK_LIFT); + if (ret != 0 && ret != WC_NO_ERR_TRACE(VERSION_ERROR)) + return 0; + } +#endif return (long)ssl->options.mask; } diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index c02ea93308b..0fb0cacfda1 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -396,7 +396,7 @@ int wolfSSL_connect(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS if (ssl->options.side == WOLFSSL_CLIENT_END && - (ret = DtlsCheckVersionMask(ssl)) != 0) { + (ret = DtlsCheckVersionMask(ssl, DTLS_MASK_START)) != 0) { ssl->error = ret; WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; @@ -893,7 +893,7 @@ int wolfSSL_accept(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS if (ssl->options.side == WOLFSSL_SERVER_END && - (ret = DtlsCheckVersionMask(ssl)) != 0) { + (ret = DtlsCheckVersionMask(ssl, DTLS_MASK_START)) != 0) { ssl->error = ret; WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; diff --git a/src/ssl_sess.c b/src/ssl_sess.c index f09e6d37775..faaf5afbe29 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1750,7 +1750,10 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) ssl->peerVerifyRet = (unsigned long)ssl->session->peerVerifyRet; #endif #ifdef WOLFSSL_DTLS - (void)DtlsCheckVersionMask(ssl); + /* No version left is reported when the handshake starts. */ + ret = DtlsCheckVersionMask(ssl, DTLS_MASK_START); + if (ret < 0 && ret != WC_NO_ERR_TRACE(VERSION_ERROR)) + return WOLFSSL_FAILURE; #endif return WOLFSSL_SUCCESS; diff --git a/src/tls13.c b/src/tls13.c index 00aa342321f..5551cabfe55 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16785,7 +16785,7 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS if (ssl->options.connectState == CONNECT_BEGIN && - (ret = DtlsCheckVersionMask(ssl)) != 0) { + (ret = DtlsCheckVersionMask(ssl, DTLS_MASK_START)) != 0) { ssl->error = ret; WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; @@ -18091,7 +18091,7 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS if (ssl->options.acceptState == TLS13_ACCEPT_BEGIN && - (ret = DtlsCheckVersionMask(ssl)) != 0) { + (ret = DtlsCheckVersionMask(ssl, DTLS_MASK_START)) != 0) { ssl->error = ret; WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 5e61f44f513..dea599ec732 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16932,6 +16932,53 @@ static int test_dtls13_mask_in_client_hello(void) wolfSSL_CTX_free(ctx_s); return EXPECT_RESULT(); } + +static int test_dtls13_mask_max_sni_cb(WOLFSSL* ssl, int* ret, void* arg) +{ + int* res = (int*)arg; + + (void)ret; + res[1] = wolfSSL_set_max_proto_version(ssl, res[0]); + return 0; +} + +/* While the server parses the ClientHello, a maximum that changes nothing + * succeeds and one that lifts or tightens the cap fails. */ +static int test_dtls13_mask_max_in_client_hello(int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char host[] = "example.com"; + int res[2]; + + res[0] = (mode == 2) ? DTLS1_2_VERSION : DTLS1_3_VERSION; + res[1] = -2; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + wolfSSL_CTX_set_servername_callback(ctx_s, test_dtls13_mask_max_sni_cb); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s, res), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (mode == 1) { + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_s, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(res[1], (mode == 0) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_version(ssl_s), + (mode == 1) ? DTLS1_2_VERSION : DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} #endif /* A CTX maximum of 0 lifts an earlier DTLS maximum. */ @@ -16947,6 +16994,9 @@ static int test_dtls13_ctx_max_proto_reset(void) wolfDTLS_client_method, wolfDTLS_server_method), 0); ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, + wolfSSL_CTX_get_max_proto_version(ctx_c)), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLS_client_method, wolfDTLS_server_method), 0); @@ -16960,9 +17010,9 @@ static int test_dtls13_ctx_max_proto_reset(void) return EXPECT_RESULT(); } -/* A fixed DTLS 1.2 method is not raised to DTLS 1.3, and a maximum of 0 takes - * it back to DTLS 1.2 after a lower maximum. */ -static int test_dtls13_ctx_max_proto_fixed(void) +/* A CTX maximum moves a fixed DTLS method down and back up, as it does a fixed + * TLS method. */ +static int test_dtls13_ctx_max_proto_fixed(method_provider method) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -16971,19 +17021,19 @@ static int test_dtls13_ctx_max_proto_fixed(void) XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, - wolfDTLSv1_2_client_method, wolfDTLS_server_method), 0); -#ifndef NO_OLD_TLS - ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_VERSION), + method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), WOLFSSL_SUCCESS); -#endif ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_3_VERSION), - WOLFSSL_FAILURE); + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, - wolfDTLSv1_2_client_method, wolfDTLS_server_method), 0); - ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); @@ -17034,6 +17084,8 @@ static int test_dtls13_min_max_tls_version(int mode) opts = wolfSSL_get_options(ssl_c); ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, TLS1_VERSION), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, TLS1_2_VERSION), + WOLFSSL_FAILURE); ExpectIntEQ(wolfSSL_get_options(ssl_c), opts); } #ifndef NO_OLD_TLS @@ -17069,7 +17121,9 @@ static int test_dtls13_ctx_min_tls13(void) ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLS_client_method, wolfDTLSv1_2_server_method), 0); ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntNE(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); @@ -17141,6 +17195,348 @@ static int test_dtls13_mask_max_proto(int onServer, int maxVer, return EXPECT_RESULT(); } +/* A maximum or an option that capped a DTLS SSL can be lifted again. */ +static int test_dtls13_mask_lift(int onServer, int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL* ssl; + int capVer = (mode == 3) ? DTLS1_VERSION : DTLS1_2_VERSION; + int liftVer = (mode == 3) ? DTLS1_2_VERSION : DTLS1_3_VERSION; + + 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); + ssl = onServer ? ssl_s : ssl_c; + if (mode == 2) { + if (ssl != NULL) + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + } + else { + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, capVer), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_version(ssl), capVer); + if (mode == 2 || mode == 4) { + if (ssl != NULL) + (void)wolfSSL_clear_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + } + else { + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, + mode == 1 ? 0 : liftVer), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_version(ssl), liftVer); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), liftVer); + ExpectIntEQ(wolfSSL_version(ssl_s), liftVer); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A DTLS maximum does not clear a version the CTX or the user masked off, + * before or after an earlier maximum. */ +static int test_dtls13_mask_max_keeps_options(int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (mode == 0) + (void)wolfSSL_CTX_set_options(ctx_c, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + if (mode == 1 && ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + if (mode == 2 && ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_options(ssl_c) & WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A maximum set while the client's first ClientHello is still buffered fails, + * and option calls leave the version that ClientHello was built for. */ +static int test_dtls13_mask_lift_want_write(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + test_ctx.c_force_want_write = 1; + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_WRITE); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + if (ssl_c != NULL) + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_2); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + if (ssl_c != NULL) + (void)wolfSSL_clear_options(ssl_c, WOLFSSL_OP_NO_TLSv1_2); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + test_ctx.c_force_want_write = 0; + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* Option and maximum calls keep the version an SSL got from its CTX after the + * CTX maximum is lowered, also when they lift a mask bit. */ +static int test_dtls13_mask_option_keeps_version(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_1); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + (void)wolfSSL_clear_options(ssl_c, WOLFSSL_OP_NO_TLSv1_1); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_COMPRESSION); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + (void)wolfSSL_clear_options(ssl_c, WOLFSSL_OP_NO_COMPRESSION); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* An SSL keeps the CTX maximum it was created with when the CTX maximum is + * raised later, also when an unrelated version option is cleared. */ +static int test_dtls13_mask_ctx_raise(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_1); + (void)wolfSSL_clear_options(ssl_c, WOLFSSL_OP_NO_TLSv1_1); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A maximum that changes the mask fails once the first ClientHello is sent and + * after the handshake, a repeated one succeeds, and wolfSSL_clear() resets. */ +static int test_dtls13_mask_max_after_hello(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, 0), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl_c) & WOLFSSL_OP_NO_TLSv1_3, + WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_s, DTLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#ifndef NO_OLD_TLS +/* A CTX maximum of DTLS 1.0 masks the newer versions, and an SSL lift that + * finds nothing below the lowered method keeps its allowed version. */ +static int test_dtls13_ctx_max_dtls10(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx, DTLS1_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx) & (WOLFSSL_OP_NO_TLSv1_2 | + WOLFSSL_OP_NO_TLSv1_3), WOLFSSL_OP_NO_TLSv1_2 | WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx), DTLS1_VERSION); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_options(ssl) & WOLFSSL_OP_NO_TLSv1_3, 0); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_2_VERSION); + wolfSSL_free(ssl); + ssl = NULL; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_VERSION); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +#if defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_768) && \ + (!defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) || \ + (defined(WOLFSSL_PQC_HYBRIDS) && defined(HAVE_CURVE25519))) + #define TEST_DTLS13_LIFT_PQ +#endif + +/* A server created below DTLS 1.3 and lifted to it later completes a DTLS 1.3 + * handshake, also with a fragmented post-quantum ClientHello. */ +static int test_dtls13_mask_lift_new_server(int pq) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; +#ifdef TEST_DTLS13_LIFT_PQ +#ifndef WOLFSSL_TLS_NO_MLKEM_STANDALONE + int group = WOLFSSL_ML_KEM_768; +#else + int group = WOLFSSL_X25519MLKEM768; +#endif +#endif + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + (void)wolfSSL_CTX_set_options(ctx_s, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_2_VERSION); + (void)wolfSSL_clear_options(ssl_s, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); +#ifdef TEST_DTLS13_LIFT_PQ + if (pq) { + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseKeyShare(ssl_c, group), WOLFSSL_SUCCESS); + } +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); + + (void)pq; + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* wolfSSL_clear_options() returns the mask when it still leaves no version, + * and the handshake reports VERSION_ERROR. */ +static int test_dtls13_mask_clear_no_version(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + if (ssl_c != NULL) { + (void)wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_TLSv1_3 | + WOLFSSL_OP_NO_TLSv1_1); + } + ExpectIntEQ(wolfSSL_clear_options(ssl_c, WOLFSSL_OP_NO_TLSv1_1) & + WOLFSSL_OP_NO_TLSv1_3, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + /* A CTX minimum of 0 keeps DTLS 1.3 in the client's supported_versions. */ static int test_dtls13_ctx_min_proto_zero(void) { @@ -17304,6 +17700,65 @@ static int test_dtls13_mask_negotiate(void) #endif #endif +/* wolfSSL_clear() lets a DTLS maximum change the mask again after a handshake, + * also in async builds without TLS 1.3. */ +int test_dtls_max_proto_after_clear(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + defined(OPENSSL_EXTRA) && !defined(WOLFSSL_NO_TLS12) && \ + (defined(WOLFSSL_DTLS13) || !defined(NO_OLD_TLS)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; +#ifdef WOLFSSL_DTLS13 + int ver = DTLS1_2_VERSION; +#else + int ver = DTLS1_VERSION; +#endif + + 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_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx_c) & WOLFSSL_OP_NO_TLSv1_3, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_options(ssl_c) & WOLFSSL_OP_NO_TLSv1_3, 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, ver), WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, ver), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), ver); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A DTLS object refuses a TLS 1.3 minimum when DTLS 1.3 is not built. */ +int test_dtls_min_tls13_without_dtls13(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntNE(wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_3), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntNE(wolfSSL_SetMinVersion(ssl, WOLFSSL_TLSV1_3), WOLFSSL_SUCCESS); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* WOLFSSL_OP_NO_TLSv1_3 moves a DTLS 1.3 client or server to the newest older * DTLS version the mask and minimum allow, or is refused when none is left. * @@ -17363,7 +17818,10 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_ctx_max_proto_reset(), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(wolfDTLSv1_3_client_method), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(wolfDTLSv1_2_client_method), + TEST_SUCCESS); ExpectIntEQ(test_dtls13_min_max_tls_version(0), TEST_SUCCESS); ExpectIntEQ(test_dtls13_min_max_tls_version(1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_min_max_tls_version(2), TEST_SUCCESS); @@ -17379,27 +17837,46 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_min_after(onServer, 1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_2_VERSION, DTLS1_2_VERSION), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_max_proto(onServer, TLS1_2_VERSION, - DTLS1_2_VERSION), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_3_VERSION, DTLS1_3_VERSION), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_proto(onServer, 0, DTLS1_3_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift(onServer, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift(onServer, 1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift(onServer, 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift(onServer, 4), TEST_SUCCESS); #ifndef NO_OLD_TLS ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_VERSION, DTLS1_VERSION), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_mask_max_proto(onServer, TLS1_1_VERSION, - DTLS1_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift(onServer, 3), TEST_SUCCESS); #endif } for (onCtx = 0; onCtx <= 1; onCtx++) ExpectIntEQ(test_dtls13_mask_tls13_entry(onCtx), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_keeps_options(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_keeps_options(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_keeps_options(2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift_want_write(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_option_keeps_version(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_ctx_raise(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_after_hello(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_clear_no_version(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_lift_new_server(0), TEST_SUCCESS); +#ifdef TEST_DTLS13_LIFT_PQ + ExpectIntEQ(test_dtls13_mask_lift_new_server(1), TEST_SUCCESS); +#endif +#ifndef NO_OLD_TLS + ExpectIntEQ(test_dtls13_ctx_max_dtls10(), TEST_SUCCESS); +#endif #ifdef HAVE_SESSION_TICKET ExpectIntEQ(test_dtls13_stale_tls13_session(), TEST_SUCCESS); #endif ExpectIntEQ(test_dtls13_ctx_min_proto_zero(), TEST_SUCCESS); #ifdef HAVE_SNI ExpectIntEQ(test_dtls13_mask_in_client_hello(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_in_client_hello(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_in_client_hello(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_max_in_client_hello(2), TEST_SUCCESS); #endif #ifdef HAVE_SESSION_TICKET ExpectIntEQ(test_dtls13_mask_session(0, 0, WOLFSSL_OP_NO_TLSv1_3, @@ -17426,21 +17903,48 @@ int test_dtls13_no_tls13_option(void) #endif #endif - /* A DTLS 1.3-only method cannot move down. */ + /* A DTLS 1.3-only method cannot move down or take a lower maximum. */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl) & WOLFSSL_OP_NO_TLSv1_3, 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, 0), WOLFSSL_SUCCESS); (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, 0), WOLFSSL_SUCCESS); (void)wolfSSL_connect(ssl); ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); wolfSSL_free(ssl); ssl = NULL; (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx), 0); ExpectNull(wolfSSL_new(ctx)); (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_2); ExpectNull(wolfSSL_new(ctx)); wolfSSL_CTX_free(ctx); ctx = NULL; +#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl) & WOLFSSL_OP_NO_TLSv1_2, 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_2_VERSION); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), WOLFSSL_SUCCESS); @@ -17452,15 +17956,47 @@ int test_dtls13_no_tls13_option(void) wolfSSL_free(ssl); ssl = NULL; (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx), 0); ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), - WOLFSSL_SUCCESS); + WOLFSSL_FAILURE); ExpectIntEQ(wolfSSL_CTX_get_options(ctx) & WOLFSSL_OP_NO_TLSv1_3, WOLFSSL_OP_NO_TLSv1_3); ExpectNull(wolfSSL_new(ctx)); + (void)wolfSSL_CTX_clear_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx, DTLS1_2_VERSION), + WOLFSSL_FAILURE); #ifdef NO_OLD_TLS ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx, DTLS1_VERSION), WOLFSSL_FAILURE); #endif + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_3_VERSION); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + +#ifndef WOLFSSL_NO_TLS12 + /* A rejected CTX minimum leaves the CTX as it was. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_version(ssl), DTLS1_2_VERSION); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif + + /* A fixed DTLS 1.3 method has no version left once it is masked off. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx, DTLS1_2_VERSION), + WOLFSSL_FAILURE); wolfSSL_CTX_free(ctx); ctx = NULL; diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index f29bf48e131..3cfac3cbaa0 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -182,6 +182,8 @@ int test_dtls13_hrr_cookie_secret_generate_fail(void); int test_dtls_max_fragment_write(void); int test_dtls_set_rfd_wfd(void); int test_dtls13_no_tls13_option(void); +int test_dtls_max_proto_after_clear(void); +int test_dtls_min_tls13_without_dtls13(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_import_dh_key_sz(void); int test_wolfSSL_dtls_export_peers(void); @@ -406,5 +408,7 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write), \ TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd), \ - TEST_DECL_GROUP("dtls", test_dtls13_no_tls13_option) + TEST_DECL_GROUP("dtls", test_dtls13_no_tls13_option), \ + TEST_DECL_GROUP("dtls", test_dtls_max_proto_after_clear), \ + TEST_DECL_GROUP("dtls", test_dtls_min_tls13_without_dtls13) #endif /* TESTS_API_DTLS_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index b1d91589010..aa7f51e94e3 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5863,6 +5863,9 @@ struct Options { void* psk_ctx; #endif /* NO_PSK */ unsigned long mask; /* store SSL_OP_ flags */ +#ifdef WOLFSSL_DTLS + unsigned long dtlsMaxMask; /* mask bits the DTLS maximum added */ +#endif #if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || defined(WOLFSSL_WPAS_SMALL) word16 minProto:1; /* sets min to min available */ word16 maxProto:1; /* sets max to max available */ @@ -8472,9 +8475,19 @@ WOLFSSL_LOCAL void DtlsResetState(WOLFSSL *ssl); WOLFSSL_LOCAL int DtlsIgnoreError(int err); WOLFSSL_LOCAL void DtlsSetSeqNumForReply(WOLFSSL* ssl); WOLFSSL_LOCAL byte DtlsMinDowngrade(byte minDowngrade); +#ifndef NO_WOLFSSL_SERVER +WOLFSSL_LOCAL int InitSSL_DtlsServer(WOLFSSL* ssl); +#endif +WOLFSSL_LOCAL byte DtlsMaskAllowedMinor(unsigned long mask, byte maxMinor, + byte minDowngrade); WOLFSSL_LOCAL byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade); -WOLFSSL_LOCAL int DtlsCheckVersionMask(WOLFSSL* ssl); +enum DtlsMaskMode { + DTLS_MASK_START, + DTLS_MASK_LIFT, + DTLS_MASK_IN_HELLO +}; +WOLFSSL_LOCAL int DtlsCheckVersionMask(WOLFSSL* ssl, enum DtlsMaskMode mode); #ifndef NO_WOLFSSL_SERVER WOLFSSL_LOCAL int DtlsNoCookieChGood(WOLFSSL* ssl); #endif From e113d17ca21e6dde6fea37240abc7509d327f249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 3 Nov 2026 13:14:56 +0100 Subject: [PATCH 14/17] Filter suites and sigalgs for the version from an unfiltered copy ResetSuitesForVersion() removed the suites and sigalgs that the new version lacks from a user-set list in place. Once a DTLS maximum or option could be lifted again, that loss became permanent: capping a DTLS 1.3 SSL at DTLS 1.2 dropped the TLS 1.3 suites and the TLS 1.3-only brainpool sigalgs, and lifting the cap left a DTLS 1.3 SSL without them. Without a user list, the suites were rebuilt for the version but InitSuites() keeps existing sigalgs, so a capped SSL still offered the TLS 1.3-only brainpool and SLH-DSA sigalgs in its DTLS 1.2 ClientHello or CertificateRequest. The new FilterSuitesForVersion() filters ssl->suites for its version. The first time it has to remove something, it keeps the unfiltered list in suitesUser, and every later call filters that copy, so a lift restores what a cap removed. It filters the suites of a user-set list and the sigalgs of any list, user-set or default. ResetSuitesForVersion() rebuilds the default suites and then calls it, also on an SSL with no side yet once a copy exists, and so do wolfSSL_SetVersion() and InitSSL_Suites(), which an either-side SSL runs when it takes a side. If the copy cannot be allocated, the list is still filtered in place. At the newest version nothing can be removed, so the filter only copies back from the saved list there. A suite or sigalg is removed only when the newest version's defaults have it and this version's defaults lack it: TLS 1.3 suites below 1.3, TLS 1.2 suites below 1.2, the TLS 1.3-only brainpool and SLH-DSA sigalgs, and RSA-PSS before TLS 1.2. Anything else the user set, such as the multicast WDM suite or ECDSA with SHA-224, DSA or SHA-1 sigalgs, is kept, as before. If the filter would leave no sigalg, the list is kept as it is, since an empty list means the defaults. wolfSSL_set_cipher_list(), wolfSSL_set_cipher_list_bytes() and wolfSSL_set1_sigalgs_list() write into the copy while it exists and always filter afterwards, also when they fail, so the result depends only on the version and not on whether the list was set before or after a cap or whether a copy was made. A TLS 1.2 SSL given a list with TLS 1.3 suites now drops them, as a DTLS 1.2 SSL already did. A sigalgs change made while capped keeps the user's suites. SetSuitesHashSigAlgo() now parses into a local buffer, so a sigalgs list that fails to parse leaves the previous one in place, on the CTX as well as on the SSL. On an SSL whose list is still the default, the setters first rebuild the defaults for the newest version the SSL can reach and its current key, so with OPENSSL_EXTRA a TLS 1.2-only cipher list keeps the default TLS 1.3 suites, as OpenSSL does. SetCipherList_ex() builds the list for that newest version too, the newer of the SSL's and the CTX method's, for TLS and DTLS alike, so a keyword list such as HIGH set while capped still has its TLS 1.3 suites for a lift, and one set after the CTX maximum was lowered keeps them on an SSL still at DTLS 1.3. The filter also built its table with the server's key flags. With an ECC key, haveStaticECC cleared haveRSA and the RSA suites were removed; without one, the static ECDH suites were. It now builds the table for the client side with every key type, so only the version decides what is removed. VerifyServerSuite() still skips a suite the server's key cannot use when it matches suites. test_dtls13_mask_lift_cipher_list caps and lifts a user-set list on the client and on a server with an ECC key: the cap removes only the TLS 1.3 suite, and the lift restores the list with a sigalgs change (including ECDSA with SHA-224), a TLS 1.2 cipher list, a list set by bytes or an unrelated option made while capped. test_dtls13_mask_list_after_cap sets the default list, a named list, a TLS 1.2-only list or HIGH after the cap, and a cipher list and a byte list that fail to parse, and checks that no TLS 1.3 suite or brainpool sigalg is left until the lift. Further tests cover a DTLS and a TLS either-side SSL capped before it becomes the client, a list set on a side-less SSL after wolfSSL_SetVersion(), HIGH set after the CTX maximum was lowered, SHA-1 sigalgs kept where they are built, wolfSSL_SetVersion() restoring the TLS 1.3 suites a TLS mask removed, also from a TLS 1.2 list set under the mask, a capped list kept across wolfSSL_clear() and restored by a lift, a CTX sigalgs list kept when a new one fails to parse, sigalgs set on a default list across a cap, a failed set and a lift, a brainpool-only sigalgs list kept under a cap, a capped TLS server merging with defaults for a key loaded later, a TLS 1.2 SSL given a TLS 1.3 suite, and the multicast suite kept under a cap. --- src/internal.c | 32 ++- src/ssl.c | 302 ++++++++++++++++------- src/ssl_api_ext.c | 3 +- tests/api.c | 11 + tests/api/test_dtls.c | 557 ++++++++++++++++++++++++++++++++++++++++++ wolfssl/internal.h | 7 + 6 files changed, 818 insertions(+), 94 deletions(-) diff --git a/src/internal.c b/src/internal.c index bcd4e611488..6a3d8869555 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8046,7 +8046,7 @@ int wolfSSL_CTX_IsPrivatePkSet(WOLFSSL_CTX* ctx) } #endif /* HAVE_PK_CALLBACKS */ -static void InitSuites_EitherSide(Suites* suites, ProtocolVersion pv, int keySz, +void InitSuites_EitherSide(Suites* suites, ProtocolVersion pv, int keySz, word16 haveRSA, word16 havePSK, word16 haveDH, word16 haveECDSAsig, word16 haveECC, word16 haveStaticECC, word16 haveAnon, @@ -8090,6 +8090,20 @@ void InitSSL_CTX_Suites(const WOLFSSL_CTX* ctx, Suites* suites) haveAnon, ctx->method->side); } +/* Returns the newest version a downgrade SSL can be moved back up to. DTLS + * minors count down. */ +ProtocolVersion SslNewestVersion(const WOLFSSL* ssl) +{ + ProtocolVersion pv = ssl->version; + byte methodMinor = ssl->ctx->method->version.minor; + + if (ssl->options.downgrade && (ssl->options.dtls ? + methodMinor < pv.minor : methodMinor > pv.minor)) { + pv = ssl->ctx->method->version; + } + return pv; +} + int InitSSL_Suites(WOLFSSL* ssl) { int keySz = 0; @@ -8148,6 +8162,8 @@ int InitSSL_Suites(WOLFSSL* ssl) havePSK, ssl->options.haveDH, ssl->options.haveECDSAsig, ssl->options.haveECC, ssl->options.haveStaticECC, ssl->options.useAnon, ssl->options.side); + if (FilterSuitesForVersion(ssl) != 0) + return MEMORY_E; } #if !defined(NO_CERTS) && !defined(WOLFSSL_SESSION_EXPORT) @@ -10505,6 +10521,8 @@ void FreeSuites(WOLFSSL* ssl) #endif XFREE(ssl->suites, ssl->heap, DYNAMIC_TYPE_SUITES); ssl->suites = NULL; + XFREE(ssl->suitesUser, ssl->heap, DYNAMIC_TYPE_SUITES); + ssl->suitesUser = NULL; } @@ -34942,7 +34960,8 @@ int SetCipherList_ex(const WOLFSSL_CTX* ctx, const WOLFSSL* ssl, side = ctx->method->side; } else if (ssl != NULL) { - version = ssl->version; + /* A later lift needs the suites of the newer version in the list. */ + version = SslNewestVersion(ssl); #ifndef NO_CERTS privateKeySz = ssl->buffers.keySz; #endif @@ -35224,9 +35243,7 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list) const char* s = list; byte sig_alg = 0; byte mac_alg = no_mac; - - /* Setting is destructive on error. */ - suites->hashSigAlgoSz = 0; + byte hashSigAlgo[WOLFSSL_MAX_SIGALGO]; do { if (*list == '+') { @@ -35266,7 +35283,7 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list) break; } } - AddSuiteHashSigAlgo(suites->hashSigAlgo, mac_alg, sig_alg, 0, &idx); + AddSuiteHashSigAlgo(hashSigAlgo, mac_alg, sig_alg, 0, &idx); sig_alg = 0; mac_alg = no_mac; s = list + 1; @@ -35279,7 +35296,8 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list) if (s != list && (sig_alg != 0 || mac_alg != 0)) { ret = 0; } - else { + if (ret == 1) { + XMEMCPY(suites->hashSigAlgo, hashSigAlgo, idx); suites->hashSigAlgoSz = idx; } diff --git a/src/ssl.c b/src/ssl.c index 498b9ddc6ab..ababf485ac9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -2427,6 +2427,8 @@ int wolfSSL_SetVersion(WOLFSSL* ssl, int version) ssl->options.haveDH, ssl->options.haveECDSAsig, ssl->options.haveECC, TRUE, ssl->options.haveStaticECC, ssl->options.useAnon, TRUE, TRUE, TRUE, TRUE, ssl->options.side); + if (FilterSuitesForVersion(ssl) != 0) + return WOLFSSL_FAILURE; return WOLFSSL_SUCCESS; } #endif /* !leanpsk */ @@ -3353,8 +3355,53 @@ int wolfSSL_CTX_set_cipher_list_bytes(WOLFSSL_CTX* ctx, const byte* list, } #endif /* OPENSSL_EXTRA || WOLFSSL_SET_CIPHER_BYTES */ +/* Builds the default suites for pv into suites, keeping existing sigalgs. */ +static void InitSslDefaultSuites(WOLFSSL* ssl, Suites* suites, + ProtocolVersion pv) +{ + word16 haveRSA = 1; + word16 havePSK = 0; + int keySz = 0; + +#ifndef NO_PSK + havePSK = ssl->options.havePSK; +#endif +#ifdef NO_RSA + haveRSA = 0; +#endif +#ifndef NO_CERTS + keySz = ssl->buffers.keySz; +#endif + InitSuites_EitherSide(suites, pv, keySz, haveRSA, havePSK, + ssl->options.haveDH, ssl->options.haveECDSAsig, ssl->options.haveECC, + ssl->options.haveStaticECC, ssl->options.useAnon, ssl->options.side); +} + +/* Returns the unfiltered list an SSL setter changes. A default list is first + * rebuilt for the newest version, so a merge finds its TLS 1.3 suites. */ +static Suites* SslSuitesToSet(WOLFSSL* ssl) +{ + Suites* suites = (ssl->suitesUser != NULL) ? ssl->suitesUser : + ssl->suites; + + if (!suites->setSuites && ssl->options.side != WOLFSSL_NEITHER_END) + InitSslDefaultSuites(ssl, suites, SslNewestVersion(ssl)); + return suites; +} + +/* Filters the list an SSL setter changed for the SSL's version. */ +static int SslSuitesSetDone(WOLFSSL* ssl, int ret) +{ + if (ResetSuitesForVersion(ssl) != 0) + ret = WOLFSSL_FAILURE; + return ret; +} + int wolfSSL_set_cipher_list(WOLFSSL* ssl, const char* list) { + int ret; + Suites* suites; + WOLFSSL_ENTER("wolfSSL_set_cipher_list"); if (ssl == NULL || ssl->ctx == NULL) { @@ -3364,19 +3411,24 @@ int wolfSSL_set_cipher_list(WOLFSSL* ssl, const char* list) if (AllocateSuites(ssl) != 0) return WOLFSSL_FAILURE; + suites = SslSuitesToSet(ssl); #ifdef OPENSSL_EXTRA - return wolfSSL_parse_cipher_list(NULL, ssl, ssl->suites, list); + ret = wolfSSL_parse_cipher_list(NULL, ssl, suites, list); #else - return (SetCipherList_ex(NULL, ssl, ssl->suites, list)) ? + ret = (SetCipherList_ex(NULL, ssl, suites, list)) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; #endif + return SslSuitesSetDone(ssl, ret); } #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_SET_CIPHER_BYTES) int wolfSSL_set_cipher_list_bytes(WOLFSSL* ssl, const byte* list, const int listSz) { + int ret; + Suites* suites; + WOLFSSL_ENTER("wolfSSL_set_cipher_list_bytes"); if (ssl == NULL || ssl->ctx == NULL) { @@ -3386,9 +3438,11 @@ int wolfSSL_set_cipher_list_bytes(WOLFSSL* ssl, const byte* list, if (AllocateSuites(ssl) != 0) return WOLFSSL_FAILURE; - return (SetCipherListFromBytes(ssl->ctx, ssl->suites, list, listSz)) + suites = SslSuitesToSet(ssl); + ret = (SetCipherListFromBytes(ssl->ctx, suites, list, listSz)) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; + return SslSuitesSetDone(ssl, ret); } #endif /* OPENSSL_EXTRA || WOLFSSL_SET_CIPHER_BYTES */ @@ -7929,106 +7983,182 @@ static long wolf_set_options(long old_op, long op) return old_op | op; } -static int FindHashSig(const Suites* suites, byte first, byte second) +/* Returns the newest minor version built for a major version. */ +static byte NewestMinor(byte major) +{ +#ifdef WOLFSSL_DTLS + if (major == DTLS_MAJOR) { + #ifdef WOLFSSL_DTLS13 + return DTLSv1_3_MINOR; + #else + return DTLSv1_2_MINOR; + #endif + } +#endif + (void)major; +#ifdef WOLFSSL_TLS13 + return TLSv1_3_MINOR; +#else + return TLSv1_2_MINOR; +#endif +} + +/* Default suites and sigalgs of a version and of the newest version. */ +typedef struct SuitesFilter { + Suites allowed; + Suites newest; +} SuitesFilter; + +static void InitSuitesFilter(SuitesFilter* f, ProtocolVersion pv) +{ + ProtocolVersion newest = pv; + + XMEMSET(f, 0, sizeof(SuitesFilter)); + newest.minor = NewestMinor(pv.major); + /* Client side so the server's key type removes nothing, and + * SUITES_NULL_EXPLICIT keeps a configured integrity-only suite. */ + InitSuites(&f->allowed, pv, 0, 1, 1, 1, 1, 1, 1, 1, 1, + SUITES_NULL_EXPLICIT, 1, 1, 1, WOLFSSL_CLIENT_END); + InitSuites(&f->newest, newest, 0, 1, 1, 1, 1, 1, 1, 1, 1, + SUITES_NULL_EXPLICIT, 1, 1, 1, WOLFSSL_CLIENT_END); +} + +static int HashSigIn(const Suites* suites, const byte* sigAlg) { word16 i; - if (suites == NULL || suites->hashSigAlgoSz == 0) { - WOLFSSL_MSG("Suites pointer error or suiteSz 0"); - return SUITES_ERROR; + for (i = 0; i + 1 < suites->hashSigAlgoSz; i += 2) { + if (suites->hashSigAlgo[i] == sigAlg[0] && + suites->hashSigAlgo[i+1] == sigAlg[1]) + return 1; } + return 0; +} - for (i = 0; i < suites->hashSigAlgoSz-1; i += 2) { - if (suites->hashSigAlgo[i] == first && - suites->hashSigAlgo[i+1] == second ) - return i; - } +static int SuiteKept(const SuitesFilter* f, const byte* suite) +{ + return f == NULL || FindSuite(&f->allowed, suite[0], suite[1]) >= 0 || + FindSuite(&f->newest, suite[0], suite[1]) < 0; +} - return MATCH_SUITE_ERROR; +static int SigAlgKept(const SuitesFilter* f, const byte* sigAlg) +{ + return f == NULL || HashSigIn(&f->allowed, sigAlg) || + !HashSigIn(&f->newest, sigAlg); } -/* Rebuilds the cipher suites for the current protocol version. */ -int ResetSuitesForVersion(WOLFSSL* ssl) +/* Copies the entries of src that f keeps into out, the suites only when they + * are user-set, and returns how many it left out. A NULL out only counts. */ +static int FilterSuites(Suites* out, const Suites* src, const SuitesFilter* f, + int setSuites) { - word16 haveRSA = 1; - word16 havePSK = 0; - int keySz = 0; + word16 in; + word16 sz = 0; + int dropped = 0; -#ifndef NO_PSK - havePSK = ssl->options.havePSK; -#endif -#ifdef NO_RSA - haveRSA = 0; -#endif -#ifndef NO_CERTS - keySz = ssl->buffers.keySz; + if (setSuites) { + for (in = 0; in < src->suiteSz; in += SUITE_LEN) { + if (!SuiteKept(f, &src->suites[in])) { + dropped++; + } + else { + if (out != NULL) { + out->suites[sz] = src->suites[in]; + out->suites[sz+1] = src->suites[in+1]; + } + sz += SUITE_LEN; + } + } + if (out != NULL) + out->suiteSz = sz; + } + sz = 0; + for (in = 0; in < src->hashSigAlgoSz; in += 2) { + if (SigAlgKept(f, &src->hashSigAlgo[in])) { + if (out != NULL) { + out->hashSigAlgo[sz] = src->hashSigAlgo[in]; + out->hashSigAlgo[sz+1] = src->hashSigAlgo[in+1]; + } + sz += 2; + } + } + /* An empty list means the defaults, so keep the list as it is instead. */ + if (sz == 0) { + sz = src->hashSigAlgoSz; + if (out != NULL && out != src) + XMEMCPY(out->hashSigAlgo, src->hashSigAlgo, sz); + } + else { + dropped += (src->hashSigAlgoSz - sz) / 2; + } + if (out != NULL) + out->hashSigAlgoSz = sz; + return dropped; +} + +/* Filters ssl->suites for its version from the unfiltered copy in suitesUser, + * saving the copy the first time an entry has to go. */ +int FilterSuitesForVersion(WOLFSSL* ssl) +{ + int ret = 0; + int setSuites; +#ifdef WOLFSSL_SMALL_STACK + SuitesFilter* f; +#else + SuitesFilter f[1]; #endif - if (ssl->options.side != WOLFSSL_NEITHER_END) { - if (AllocateSuites(ssl) != 0) - return MEMORY_E; - if (!ssl->suites->setSuites) { - /* Client side won't set DH params, so it needs haveDH set to TRUE. */ - if (ssl->options.side == WOLFSSL_CLIENT_END) - InitSuites(ssl->suites, ssl->version, keySz, haveRSA, - havePSK, TRUE, ssl->options.haveECDSAsig, - ssl->options.haveECC, TRUE, ssl->options.haveStaticECC, - ssl->options.useAnon, - TRUE, TRUE, TRUE, TRUE, ssl->options.side); - else - InitSuites(ssl->suites, ssl->version, keySz, haveRSA, - havePSK, ssl->options.haveDH, ssl->options.haveECDSAsig, - ssl->options.haveECC, TRUE, ssl->options.haveStaticECC, - ssl->options.useAnon, - TRUE, TRUE, TRUE, TRUE, ssl->options.side); + if (ssl->suites == NULL) + return 0; + setSuites = ssl->suites->setSuites; + if (ssl->version.minor == NewestMinor(ssl->version.major)) { + if (ssl->suitesUser != NULL) + (void)FilterSuites(ssl->suites, ssl->suitesUser, NULL, setSuites); + return 0; + } +#ifdef WOLFSSL_SMALL_STACK + f = (SuitesFilter*)XMALLOC(sizeof(SuitesFilter), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (f == NULL) + return MEMORY_E; +#endif + InitSuitesFilter(f, ssl->version); + if (ssl->suitesUser == NULL && + FilterSuites(NULL, ssl->suites, f, setSuites) > 0) { + ssl->suitesUser = (Suites*)XMALLOC(sizeof(Suites), ssl->heap, + DYNAMIC_TYPE_SUITES); + if (ssl->suitesUser == NULL) { + (void)FilterSuites(ssl->suites, ssl->suites, f, setSuites); + ret = MEMORY_E; } else { - /* Only preserve overlapping suites */ - Suites tmpSuites; - word16 in, out; - word16 haveECDSAsig, haveStaticECC; -#ifdef NO_RSA - haveECDSAsig = 1; - haveStaticECC = 1; -#else - haveECDSAsig = 0; - haveStaticECC = ssl->options.haveStaticECC; -#endif - XMEMSET(&tmpSuites, 0, sizeof(Suites)); - /* Get all possible ciphers and sigalgs for the version. Following - * options limit the allowed ciphers so let's try to get as many as - * possible. - * - haveStaticECC turns off haveRSA - * - haveECDSAsig turns off haveRSAsig - * - SUITES_NULL_EXPLICIT includes the integrity-only suites, so - * an explicitly configured one is preserved */ - InitSuites(&tmpSuites, ssl->version, 0, 1, 1, 1, haveECDSAsig, 1, 1, - haveStaticECC, 1, SUITES_NULL_EXPLICIT, 1, 1, 1, - ssl->options.side); - for (in = 0, out = 0; in < ssl->suites->suiteSz; in += SUITE_LEN) { - if (FindSuite(&tmpSuites, ssl->suites->suites[in], - ssl->suites->suites[in+1]) >= 0) { - ssl->suites->suites[out] = ssl->suites->suites[in]; - ssl->suites->suites[out+1] = ssl->suites->suites[in+1]; - out += SUITE_LEN; - } - } - ssl->suites->suiteSz = out; - for (in = 0, out = 0; in < ssl->suites->hashSigAlgoSz; in += 2) { - if (FindHashSig(&tmpSuites, ssl->suites->hashSigAlgo[in], - ssl->suites->hashSigAlgo[in+1]) >= 0) { - ssl->suites->hashSigAlgo[out] = - ssl->suites->hashSigAlgo[in]; - ssl->suites->hashSigAlgo[out+1] = - ssl->suites->hashSigAlgo[in+1]; - out += 2; - } - } - ssl->suites->hashSigAlgoSz = out; + XMEMCPY(ssl->suitesUser, ssl->suites, sizeof(Suites)); } } + if (ret == 0 && ssl->suitesUser != NULL) + (void)FilterSuites(ssl->suites, ssl->suitesUser, f, setSuites); +#ifdef WOLFSSL_SMALL_STACK + XFREE(f, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return ret; +} - return 0; +/* Rebuilds the default cipher suites for the current protocol version and + * filters the list with FilterSuitesForVersion(). */ +int ResetSuitesForVersion(WOLFSSL* ssl) +{ + if (ssl->options.side == WOLFSSL_NEITHER_END) { + if (ssl->suitesUser == NULL || ssl->suites == NULL) + return 0; + } + else if (AllocateSuites(ssl) != 0) { + return MEMORY_E; + } + if (ssl->suitesUser != NULL) + ssl->suites->setSuites = ssl->suitesUser->setSuites; + if (!ssl->suites->setSuites && ssl->options.side != WOLFSSL_NEITHER_END) + InitSslDefaultSuites(ssl, ssl->suites, ssl->version); + return FilterSuitesForVersion(ssl); } long wolfSSL_set_options(WOLFSSL* ssl, long op) diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 412babadbea..cdc80c4afaf 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -2139,7 +2139,8 @@ int wolfSSL_set1_sigalgs_list(WOLFSSL* ssl, const char* list) ret = WOLFSSL_FAILURE; } else { - ret = SetSuitesHashSigAlgo(ssl->suites, list); + ret = SetSuitesHashSigAlgo(SslSuitesToSet(ssl), list); + ret = SslSuitesSetDone(ssl, ret); } return ret; diff --git a/tests/api.c b/tests/api.c index c21e8b74f89..2165ddee5e0 100644 --- a/tests/api.c +++ b/tests/api.c @@ -21396,6 +21396,17 @@ static int test_wolfSSL_set1_sigalgs_list(void) ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl, "RSA+SHA256+RSA"), WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + ExpectIntEQ(wolfSSL_CTX_set1_sigalgs_list(ctx, "RSA+SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl, "RSA+SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set1_sigalgs_list(ctx, "RSA+BOGUS"), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl, "RSA+BOGUS"), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + ExpectIntEQ(ctx == NULL ? 0 : ctx->suites->hashSigAlgoSz, 2); + ExpectIntEQ(ssl == NULL ? 0 : WOLFSSL_SUITES(ssl)->hashSigAlgoSz, 2); + { const char entry[] = "RSA+SHA256"; const int entryLen = (int)sizeof(entry) - 1; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index dea599ec732..eca490d026e 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -17239,6 +17239,515 @@ static int test_dtls13_mask_lift(int onServer, int mode) return EXPECT_RESULT(); } +#if defined(BUILD_TLS_AES_128_GCM_SHA256) && defined(HAVE_ECC) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) +#ifdef WOLFSSL_SHA224 + #define TEST_DTLS13_SA_SHA224 ":ECDSA+SHA224" +#else + #define TEST_DTLS13_SA_SHA224 "" +#endif +#if !defined(NO_SHA) && \ + (!defined(NO_OLD_TLS) || defined(WOLFSSL_ALLOW_TLS_SHA1)) + #define TEST_DTLS13_SA_SHA1 ":ECDSA+SHA1" +#else + #define TEST_DTLS13_SA_SHA1 "" +#endif + +static int test_dtls13_has_suite(const WOLFSSL* ssl, byte first, byte second) +{ + word16 i; + + for (i = 0; ssl != NULL && ssl->suites != NULL && + i < ssl->suites->suiteSz; i += SUITE_LEN) { + if (ssl->suites->suites[i] == first && + ssl->suites->suites[i+1] == second) + return 1; + } + return 0; +} + +static int test_dtls13_has_sigalg(WOLFSSL* ssl, byte first, byte second) +{ + const Suites* suites = (ssl != NULL) ? WOLFSSL_SUITES(ssl) : NULL; + word16 i; + + for (i = 0; suites != NULL && i + 1 < suites->hashSigAlgoSz; i += 2) { + if (suites->hashSigAlgo[i] == first && + suites->hashSigAlgo[i+1] == second) + return 1; + } + return 0; +} + +static int test_dtls13_suites_eq(const WOLFSSL* ssl, const Suites* exp) +{ + return ssl != NULL && ssl->suites != NULL && + ssl->suites->suiteSz == exp->suiteSz && + ssl->suites->hashSigAlgoSz == exp->hashSigAlgoSz && + XMEMCMP(ssl->suites->suites, exp->suites, exp->suiteSz) == 0 && + XMEMCMP(ssl->suites->hashSigAlgo, exp->hashSigAlgo, + exp->hashSigAlgoSz) == 0; +} + +/* Lifting a cap restores a user list with what modes 1-4 change while capped: + * sigalgs, the TLS 1.2 suites, the whole list by bytes, an unrelated option. */ +static int test_dtls13_mask_lift_list_mode(int onServer, int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL* ssl; + Suites exp; + static const byte tls13Ecdsa[] = { + TLS13_BYTE, TLS_AES_128_GCM_SHA256, + ECC_BYTE, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + }; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&exp, 0, sizeof(exp)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_s, eccCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caEccCertFile, NULL), + WOLFSSL_SUCCESS); + ssl = onServer ? ssl_s : ssl_c; + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, + "TLS13-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); +#ifdef HAVE_ECC_BRAINPOOL + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl, + "ECDSA+SHA256:ECDSA-BRAINPOOL+SHA256"), WOLFSSL_SUCCESS); +#endif + if (ssl != NULL && ssl->suites != NULL) + XMEMCPY(&exp, ssl->suites, sizeof(exp)); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + if (ssl != NULL && ssl->suites != NULL) + ExpectIntEQ(ssl->suites->suiteSz, exp.suiteSz - SUITE_LEN); + if (mode == 1) { + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl, "ECDSA+SHA256" + TEST_DTLS13_SA_SHA224 TEST_DTLS13_SA_SHA1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_sigalg(ssl, sha224_mac, ecc_dsa_sa_algo), + TEST_DTLS13_SA_SHA224[0] != '\0'); + ExpectIntEQ(test_dtls13_has_sigalg(ssl, sha_mac, ecc_dsa_sa_algo), + TEST_DTLS13_SA_SHA1[0] != '\0'); + if (ssl != NULL && ssl->suites != NULL) { + exp.hashSigAlgoSz = ssl->suites->hashSigAlgoSz; + XMEMCPY(exp.hashSigAlgo, ssl->suites->hashSigAlgo, + exp.hashSigAlgoSz); + } + } + else if (mode == 2) { + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, + "ECDHE-ECDSA-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); + } + else if (mode == 3) { + ExpectIntEQ(wolfSSL_set_cipher_list_bytes(ssl, tls13Ecdsa, + (int)sizeof(tls13Ecdsa)), WOLFSSL_SUCCESS); + } + else if (mode == 4) { + if (ssl != NULL) + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_COMPRESSION); + } + if (mode == 2 || mode == 3) { + ExpectTrue(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)); + ExpectFalse(test_dtls13_has_suite(ssl, TLS13_BYTE, + TLS_AES_128_GCM_SHA256)); + } + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + if (mode == 2 || mode == 3) { + ExpectTrue(test_dtls13_has_suite(ssl, TLS13_BYTE, + TLS_AES_128_GCM_SHA256)); + ExpectTrue(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)); + ExpectFalse(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)); + } + else { + ExpectTrue(test_dtls13_suites_eq(ssl, &exp)); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectStrEQ(wolfSSL_get_cipher_name(ssl_c), "TLS13-AES128-GCM-SHA256"); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +static int test_dtls13_mask_lift_cipher_list(int onServer) +{ + EXPECT_DECLS; + int mode; + + for (mode = 0; mode <= 4; mode++) { + ExpectIntEQ(test_dtls13_mask_lift_list_mode(onServer, mode), + TEST_SUCCESS); + } + return EXPECT_RESULT(); +} + +/* A list set while capped, by name or keyword, or the default list, is filtered + * for the cap and gets its TLS 1.3 suites and sigalgs back on a lift. */ +static int test_dtls13_mask_list_after_cap(int onServer, const char* list) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL* ssl; + const byte oddList[] = { TLS13_BYTE, TLS_AES_128_GCM_SHA256, ECC_BYTE }; + + 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_use_certificate_file(ssl_s, eccCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caEccCertFile, NULL), + WOLFSSL_SUCCESS); + ssl = onServer ? ssl_s : ssl_c; + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + if (list != NULL) { + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, list), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "BOGUS-CIPHER"), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_cipher_list_bytes(ssl, oddList, sizeof(oddList)), + WOLFSSL_FAILURE); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl), 0); +#ifdef HAVE_ECC_BRAINPOOL + ExpectIntEQ(test_dtls13_has_sigalg(ssl, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), 0); +#endif + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl), 1); + if (list != NULL && XSTRCMP(list, "HIGH") != 0) { + ExpectIntEQ(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), 0); + } +#ifdef HAVE_ECC_BRAINPOOL + /* A named list gets TLS 1.3 sigalgs only when it names a TLS 1.3 suite. */ + ExpectIntEQ(test_dtls13_has_sigalg(ssl, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), + list == NULL || XSTRSTR(list, "TLS13") != NULL || + XSTRCMP(list, "HIGH") == 0); +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_EITHER_SIDE) && defined(HAVE_ECC_BRAINPOOL) +/* An either-side SSL capped before it picks a side drops the TLS 1.3 sigalgs + * once it becomes the client. */ +static int test_dtls13_mask_either_side_sigalgs(int dtls) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(dtls ? wolfDTLS_method() : + wolfSSLv23_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + if (ssl != NULL) + wolfSSL_set_connect_state(ssl); + ExpectIntEQ(wolfSSL_version(ssl), dtls ? DTLS1_2_VERSION : + TLS1_2_VERSION); + ExpectIntEQ(test_dtls13_has_sigalg(ssl, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} + +/* A list set on an SSL with no side, after wolfSSL_SetVersion() filtered its + * suites, is the one it uses once it becomes the client. */ +static int test_dtls13_either_side_list(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_SetVersion(ssl, WOLFSSL_TLSV1_2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ECDHE-ECDSA-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + if (ssl != NULL) + wolfSSL_set_connect_state(ssl); + ExpectIntEQ(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), 1); + ExpectIntEQ(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +#ifdef HAVE_ECC_BRAINPOOL +/* Sigalgs set on a default list survive a cap, a failed set and a lift, and a + * list the cap would empty is kept as it is. */ +static int test_dtls13_mask_default_list_sigalgs(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_s, eccCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caEccCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_c, + "ECDSA+SHA256:ECDSA-BRAINPOOL+SHA256"), WOLFSSL_SUCCESS); + if (ssl_c != NULL && ssl_c->suites != NULL) + ExpectIntEQ(ssl_c->suites->hashSigAlgoSz, 4); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_sigalg(ssl_c, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), 0); + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_c, "NONE+SHA256"), + WOLFSSL_FAILURE); + ExpectIntEQ(test_dtls13_has_sigalg(ssl_c, sha256_mac, ecc_dsa_sa_algo), + 1); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_sigalg(ssl_c, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), 1); + if (ssl_c != NULL && ssl_c->suites != NULL) + ExpectIntEQ(ssl_c->suites->hashSigAlgoSz, 4); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_c, "ECDSA-BRAINPOOL+SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_sigalg(ssl_c, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) +/* A TLS 1.3 list set on a capped TLS server merges with defaults built for the + * ECC key the SSL loaded after wolfSSL_new(). */ +static int test_dtls13_tls_default_copy_key(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + if (ctx != NULL) + (void)wolfSSL_CTX_set_options(ctx, WOLFSSL_OP_NO_TLSv1_3); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_use_certificate_file(ssl, eccCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl, eccKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "TLS13-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), 1); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif +#endif + +#ifdef BUILD_WDM_WITH_NULL_SHA256 +/* The multicast suite, which no version lists by default, is not filtered. */ +static int test_dtls13_mask_mcast_suite(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "WDM-NULL-SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_suite(ssl, CIPHER_BYTE, WDM_WITH_NULL_SHA256), + 1); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* A cipher list set after the CTX maximum was lowered keeps its TLS 1.3 suites + * on an SSL still at DTLS 1.3. */ +static int test_dtls13_ctx_lowered_cipher_list(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, "HIGH"), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl_c), 1); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +#ifndef NO_WOLFSSL_CLIENT +/* wolfSSL_SetVersion() restores the suites a TLS mask filtered out, also from + * a TLS 1.2 list set under the mask. */ +static int test_dtls13_tls_set_version_suites(int listAfterMask) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (!listAfterMask) { + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, + "TLS13-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + } + if (ssl != NULL) + (void)wolfSSL_set_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + if (listAfterMask) { + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, + "ECDHE-ECDSA-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl), 0); + if (ssl != NULL) + (void)wolfSSL_clear_options(ssl, WOLFSSL_OP_NO_TLSv1_3); + ExpectIntEQ(wolfSSL_SetVersion(ssl, WOLFSSL_TLSV1_3), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl), 1); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + +/* A capped user list survives wolfSSL_clear() after a handshake and comes back + * on a lift. */ +static int test_dtls13_mask_clear_list(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = 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, + wolfDTLS_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_s, eccCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caEccCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, + "TLS13-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_2_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_2_VERSION); + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl_c), 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl_c), 1); + ExpectIntEQ(test_dtls13_has_suite(ssl_c, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), 1); + ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} + +/* A TLS 1.2 SSL drops the TLS 1.3 suites and sigalgs of a list it is given. */ +static int test_dtls13_tls12_cipher_list(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, + "TLS13-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_has_tls13_suite(ssl), 0); + ExpectIntEQ(test_dtls13_has_suite(ssl, ECC_BYTE, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), 1); +#ifdef HAVE_ECC_BRAINPOOL + ExpectIntEQ(test_dtls13_has_sigalg(ssl, NEW_SA_MAJOR, + ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR), 0); +#endif + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); +} +#endif + /* A DTLS maximum does not clear a version the CTX or the user masked off, * before or after an earlier maximum. */ static int test_dtls13_mask_max_keeps_options(int mode) @@ -17845,6 +18354,21 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_lift(onServer, 1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_lift(onServer, 2), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_lift(onServer, 4), TEST_SUCCESS); + #if defined(BUILD_TLS_AES_128_GCM_SHA256) && defined(HAVE_ECC) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + ExpectIntEQ(test_dtls13_mask_lift_cipher_list(onServer), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_list_after_cap(onServer, NULL), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_list_after_cap(onServer, + "TLS13-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_list_after_cap(onServer, "HIGH"), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_list_after_cap(onServer, + "ECDHE-ECDSA-AES128-GCM-SHA256"), TEST_SUCCESS); + #endif #ifndef NO_OLD_TLS ExpectIntEQ(test_dtls13_mask_max_proto(onServer, DTLS1_VERSION, DTLS1_VERSION), TEST_SUCCESS); @@ -17853,6 +18377,39 @@ int test_dtls13_no_tls13_option(void) } for (onCtx = 0; onCtx <= 1; onCtx++) ExpectIntEQ(test_dtls13_mask_tls13_entry(onCtx), TEST_SUCCESS); +#if defined(BUILD_TLS_AES_128_GCM_SHA256) && defined(HAVE_ECC) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) +#ifndef NO_WOLFSSL_CLIENT + ExpectIntEQ(test_dtls13_tls_set_version_suites(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_tls_set_version_suites(1), TEST_SUCCESS); +#endif + ExpectIntEQ(test_dtls13_mask_clear_list(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_tls12_cipher_list(), TEST_SUCCESS); +#endif +#if defined(BUILD_TLS_AES_128_GCM_SHA256) && defined(HAVE_ECC) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) && \ + defined(WOLFSSL_EITHER_SIDE) && defined(HAVE_ECC_BRAINPOOL) + ExpectIntEQ(test_dtls13_mask_either_side_sigalgs(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_either_side_sigalgs(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_either_side_list(), TEST_SUCCESS); +#endif +#if defined(BUILD_TLS_AES_128_GCM_SHA256) && defined(HAVE_ECC) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + ExpectIntEQ(test_dtls13_ctx_lowered_cipher_list(), TEST_SUCCESS); +#ifdef HAVE_ECC_BRAINPOOL + ExpectIntEQ(test_dtls13_mask_default_list_sigalgs(), TEST_SUCCESS); +#if !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) + ExpectIntEQ(test_dtls13_tls_default_copy_key(), TEST_SUCCESS); +#endif +#endif +#ifdef BUILD_WDM_WITH_NULL_SHA256 + ExpectIntEQ(test_dtls13_mask_mcast_suite(), TEST_SUCCESS); +#endif +#endif ExpectIntEQ(test_dtls13_mask_max_keeps_options(0), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_keeps_options(1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_keeps_options(2), TEST_SUCCESS); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index aa7f51e94e3..4ddb22ce4e8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2376,7 +2376,13 @@ WOLFSSL_LOCAL void InitSSL_Method(WOLFSSL_METHOD* method, ProtocolVersion pv); WOLFSSL_LOCAL void InitSSL_CTX_Suites(const WOLFSSL_CTX* ctx, Suites* suites); WOLFSSL_LOCAL int InitSSL_Suites(WOLFSSL* ssl); +WOLFSSL_LOCAL void InitSuites_EitherSide(Suites* suites, ProtocolVersion pv, + int keySz, word16 haveRSA, word16 havePSK, word16 haveDH, + word16 haveECDSAsig, word16 haveECC, word16 haveStaticECC, + word16 haveAnon, int side); WOLFSSL_LOCAL int ResetSuitesForVersion(WOLFSSL* ssl); +WOLFSSL_LOCAL int FilterSuitesForVersion(WOLFSSL* ssl); +WOLFSSL_LOCAL ProtocolVersion SslNewestVersion(const WOLFSSL* ssl); WOLFSSL_LOCAL int InitSSL_Side(WOLFSSL* ssl, word16 side); @@ -7025,6 +7031,7 @@ struct WOLFSSL { * object needs separate instance of suites use * AllocateSuites(). */ Suites* clSuites; + Suites* suitesUser; /* unfiltered copy of user-set suites */ #if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) || \ defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) WOLF_STACK_OF(WOLFSSL_CIPHER)* suitesStack; /* stack of available cipher From 8a5482d619a270436bdb6442db6084b0c60788e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 10 Nov 2026 17:07:47 +0100 Subject: [PATCH 15/17] Keep a fixed DTLS method's version under a CTX maximum protoVerTbl now lists DTLS1_3_VERSION, so wolfSSL_CTX_set_max_proto_version() with 0 or DTLS1_3_VERSION turned a wolfDTLSv1_2_*_method() CTX into DTLS 1.3, and the fixed method could not downgrade back to 1.2. A maximum of DTLS1_VERSION already turned it into DTLS 1.0. OpenSSL stores no maximum for a fixed method, and wolfSSL_set_max_proto_version() already keeps the fixed version of an SSL. Set_CTX_max_proto_version() now moves the method version of a DTLS CTX only when the method may downgrade. A maximum at or above a fixed version leaves it, and one below it fails through CtxDtlsVersionLeft() and restores the CTX, as the SSL setter does. --- src/ssl.c | 9 ++++++--- tests/api/test_dtls.c | 27 ++++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index ababf485ac9..48db81af7a9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5610,14 +5610,17 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) #endif #ifdef WOLFSSL_DTLS case DTLS1_VERSION: - ctx->method->version.minor = DTLS_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = DTLS_MINOR; break; case DTLS1_2_VERSION: - ctx->method->version.minor = DTLSv1_2_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = DTLSv1_2_MINOR; break; #ifdef WOLFSSL_DTLS13 case DTLS1_3_VERSION: - ctx->method->version.minor = DTLSv1_3_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = DTLSv1_3_MINOR; break; #endif #endif diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index eca490d026e..6ad4a3b9f8d 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -17010,9 +17010,8 @@ static int test_dtls13_ctx_max_proto_reset(void) return EXPECT_RESULT(); } -/* A CTX maximum moves a fixed DTLS method down and back up, as it does a fixed - * TLS method. */ -static int test_dtls13_ctx_max_proto_fixed(method_provider method) +/* A CTX maximum never moves a fixed DTLS method, and one below it fails. */ +static int test_dtls13_ctx_max_proto_fixed(method_provider method, int ver) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -17023,17 +17022,19 @@ static int test_dtls13_ctx_max_proto_fixed(method_provider method) ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, method, wolfDTLS_server_method), 0); ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), - WOLFSSL_SUCCESS); + ver == DTLS1_2_VERSION ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE); +#ifndef NO_OLD_TLS + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_VERSION), + WOLFSSL_FAILURE); +#endif + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_3_VERSION), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, DTLS1_2_VERSION), - WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, method, wolfDTLS_server_method), 0); - ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_c), ver); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); + ExpectIntEQ(wolfSSL_version(ssl_s), ver); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); @@ -18327,10 +18328,10 @@ int test_dtls13_no_tls13_option(void) ExpectIntEQ(test_dtls13_mask_client_check(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_negotiate(), TEST_SUCCESS); ExpectIntEQ(test_dtls13_ctx_max_proto_reset(), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(wolfDTLSv1_3_client_method), - TEST_SUCCESS); - ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(wolfDTLSv1_2_client_method), - TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(wolfDTLSv1_3_client_method, + DTLS1_3_VERSION), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_ctx_max_proto_fixed(wolfDTLSv1_2_client_method, + DTLS1_2_VERSION), TEST_SUCCESS); ExpectIntEQ(test_dtls13_min_max_tls_version(0), TEST_SUCCESS); ExpectIntEQ(test_dtls13_min_max_tls_version(1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_min_max_tls_version(2), TEST_SUCCESS); From 0a94feeb69c8a8132878bf5738e2e746d25d09db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 11 Nov 2026 09:28:31 +0100 Subject: [PATCH 16/17] Keep the HRR version when a mask changes during ClientHello The DTLS mask filter in TLSX_SupportedVersions_Parse() also ran on the second ClientHello after a stateful HelloRetryRequest. An SNI callback that sets SSL_OP_NO_DTLSv1_3 while the server parses the first ClientHello made the second one skip DTLS 1.3, so the server took the legacy DoClientHello() path after a DTLS 1.3 HRR and failed with VERSION_ERROR. RFC 8446 4.1.4, applied to DTLS 1.3 by RFC 9147, requires the ServerHello to keep the version selected in the HRR. Skip the filter once the HRR has been sent. The first ClientHello already applied the mask, and the stateless cookie exchange resets serverState, so the ClientHello carrying the cookie is still checked. test_dtls13_mask_in_client_hello gains a stateful HRR case. --- src/tls.c | 3 +++ tests/api/test_dtls.c | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tls.c b/src/tls.c index a2752c1d1d3..667f5084ce7 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7969,7 +7969,10 @@ int TLSX_SupportedVersions_Parse(const WOLFSSL* ssl, const byte* input, continue; } #ifdef WOLFSSL_DTLS + /* The ClientHello after an HRR keeps the version the HRR chose. */ if (isDtls && ssl->options.downgrade && + ssl->options.serverState != + SERVER_HELLO_RETRY_REQUEST_COMPLETE && DtlsAllowedMinor(ssl, minor, DTLS_MINOR) != minor) { continue; } diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 6ad4a3b9f8d..764331e80f5 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16905,8 +16905,8 @@ static int test_dtls13_mask_sni_cb(WOLFSSL* ssl, int* ret, void* arg) } /* An option set while the server parses the ClientHello does not move the - * version that ClientHello already settled. */ -static int test_dtls13_mask_in_client_hello(void) + * version that ClientHello already settled, nor the one a stateful HRR chose. */ +static int test_dtls13_mask_in_client_hello(int hrr) { EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -16922,6 +16922,10 @@ static int test_dtls13_mask_in_client_hello(void) wolfDTLS_client_method, wolfDTLS_server_method), 0); ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, host, (word16)XSTRLEN(host)), WOLFSSL_SUCCESS); + if (hrr) { + ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + } ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(wolfSSL_version(ssl_c), DTLS1_3_VERSION); ExpectIntEQ(wolfSSL_version(ssl_s), DTLS1_3_VERSION); @@ -18431,7 +18435,8 @@ int test_dtls13_no_tls13_option(void) #endif ExpectIntEQ(test_dtls13_ctx_min_proto_zero(), TEST_SUCCESS); #ifdef HAVE_SNI - ExpectIntEQ(test_dtls13_mask_in_client_hello(), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_in_client_hello(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_mask_in_client_hello(1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_in_client_hello(0), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_in_client_hello(1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_mask_max_in_client_hello(2), TEST_SUCCESS); From 564bd95c2a80201f0df2b0e7db94fab340a535f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 12 Nov 2026 15:29:23 +0100 Subject: [PATCH 17/17] Keep a fixed TLS method's version under a maximum Set_CTX_max_proto_version() rewrote the method version of a TLS CTX whether or not the method may downgrade, so wolfSSL_CTX_set_max_proto_version() with 0 turned a wolfTLSv1_2_*_method() CTX into TLS 1.3 only, and with TLS1_1_VERSION into TLS 1.1 only. It also undid wolfSSL_CTX_require_psk(), which pins a v23 CTX at TLS 1.3 by clearing downgrade: a later TLS 1.2 maximum moved the CTX to TLS 1.2. wolfSSL_set_max_proto_version() accepted a maximum below the fixed version of an SSL and the handshake then ran at the fixed version anyway. Both minimum setters accepted a minimum above the fixed version, which masked it off, so wolfSSL_new() failed for a CTX. The DTLS setters already keep a fixed version. A fixed TLS method now keeps its version: a maximum at or above it leaves the method alone, and a maximum below it or a minimum above it fails in the CTX and SSL setters. This includes objects that wolfSSL_CTX_require_psk(), wolfSSL_require_psk() or wolfSSL_SetVersion() fixed. The checks run before the mask changes, through FixedTlsVersionCmp(). The CTX setters restore the mask, minimum and method version on failure, as they do for DTLS, so the version state save and restore are no longer DTLS only. wolfSSL_set_min_proto_version() also refuses a minimum above a fixed DTLS version, which it accepted before and which then failed in wolfSSL_connect(). wolfSSL_CTX_get_max_proto_version() reports the fixed version of a fixed TLS method, or 0 when the mask or the minimum excludes it, as it does for a fixed DTLS method. An application that relied on a fixed method plus a higher maximum to get TLS 1.3 now stays at the fixed version, which matches OpenSSL: it stores no version bound for a version-specific method. --- src/ssl.c | 98 +++++++++++++++++++------- tests/api/test_dtls.c | 10 +++ tests/api/test_tls.c | 159 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 2 + 4 files changed, 245 insertions(+), 24 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 48db81af7a9..ae98a243ff6 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5222,8 +5222,7 @@ static int CtxDtlsVersionLeft(const WOLFSSL_CTX* ctx) } #endif -#ifdef WOLFSSL_DTLS -/* The CTX version settings a rejected DTLS minimum or maximum restores. */ +/* The CTX version settings a rejected minimum or maximum restores. */ typedef struct CtxVersionState { unsigned long mask; ProtocolVersion version; @@ -5246,7 +5245,8 @@ static void CtxVersionSave(const WOLFSSL_CTX* ctx, CtxVersionState* st) static int CtxVersionRestore(WOLFSSL_CTX* ctx, const CtxVersionState* st, int ret) { - if (ret != WOLFSSL_SUCCESS && ctx->method->version.major == DTLS_MAJOR) { + if (ret != WOLFSSL_SUCCESS && (ctx->method->version.major == DTLS_MAJOR || + !ctx->method->downgrade)) { ctx->mask = st->mask; ctx->method->version = st->version; ctx->minDowngrade = st->minDowngrade; @@ -5256,7 +5256,15 @@ static int CtxVersionRestore(WOLFSSL_CTX* ctx, const CtxVersionState* st, } return ret; } -#endif + +/* Returns <0, >0 or 0 as ver is below, above or at a fixed TLS method's + * version, and 0 for any other method or version. */ +static int FixedTlsVersionCmp(ProtocolVersion pv, int downgrade, int ver) +{ + if (pv.major != SSLv3_MAJOR || downgrade || (ver >> 8) != SSLv3_MAJOR) + return 0; + return (ver & 0xff) - pv.minor; +} /** * protoVerTbl holds (D)TLS version numbers in ascending order. @@ -5300,7 +5308,8 @@ static const int protoVerTbl[] = { * * DTLS1_2_VERSION * * DTLS1_3_VERSION * @return WOLFSSL_SUCCESS on valid settings and WOLFSSL_FAILURE when no - * protocol versions are left enabled. + * protocol versions are left enabled or the version is above the + * version of a method that cannot downgrade. */ static int Set_CTX_min_proto_version(WOLFSSL_CTX* ctx, int version) { @@ -5309,6 +5318,11 @@ static int Set_CTX_min_proto_version(WOLFSSL_CTX* ctx, int version) if (ctx == NULL) { return WOLFSSL_FAILURE; } + if (FixedTlsVersionCmp(ctx->method->version, ctx->method->downgrade, + version) > 0) { + WOLFSSL_MSG("Minimum above the fixed TLS version"); + return WOLFSSL_FAILURE; + } switch (version) { #ifndef NO_TLS @@ -5410,9 +5424,7 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) int maxProto = 0; int i; int idx = 0; -#ifdef WOLFSSL_DTLS CtxVersionState st; -#endif WOLFSSL_ENTER("wolfSSL_CTX_set_min_proto_version"); @@ -5425,8 +5437,8 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) WOLFSSL_MSG("Mismatched protocol version"); return WOLFSSL_FAILURE; } - CtxVersionSave(ctx, &st); #endif + CtxVersionSave(ctx, &st); if (version != 0) { proto = version; @@ -5470,9 +5482,7 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) ctx->minVersionSet = (version != 0); } -#ifdef WOLFSSL_DTLS ret = CtxVersionRestore(ctx, &st, ret); -#endif return ret; } @@ -5497,7 +5507,8 @@ int wolfSSL_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) * * DTLS1_2_VERSION * * DTLS1_3_VERSION * @return WOLFSSL_SUCCESS on valid settings and WOLFSSL_FAILURE when no - * protocol versions are left enabled. + * protocol versions are left enabled or the version is below the + * version of a method that cannot downgrade. */ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) { @@ -5515,6 +5526,11 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) return WOLFSSL_FAILURE; } #endif + if (FixedTlsVersionCmp(ctx->method->version, ctx->method->downgrade, + ver) < 0) { + WOLFSSL_MSG("Maximum below the fixed TLS version"); + return WOLFSSL_FAILURE; + } switch (ver) { #ifndef NO_TLS @@ -5593,19 +5609,24 @@ static int Set_CTX_max_proto_version(WOLFSSL_CTX* ctx, int ver) switch (ver) { #ifndef NO_TLS case SSL3_VERSION: - ctx->method->version.minor = SSLv3_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = SSLv3_MINOR; break; case TLS1_VERSION: - ctx->method->version.minor = TLSv1_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = TLSv1_MINOR; break; case TLS1_1_VERSION: - ctx->method->version.minor = TLSv1_1_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = TLSv1_1_MINOR; break; case TLS1_2_VERSION: - ctx->method->version.minor = TLSv1_2_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = TLSv1_2_MINOR; break; case TLS1_3_VERSION: - ctx->method->version.minor = TLSv1_3_MINOR; + if (ctx->method->downgrade) + ctx->method->version.minor = TLSv1_3_MINOR; break; #endif #ifdef WOLFSSL_DTLS @@ -5645,18 +5666,14 @@ int wolfSSL_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); int minProto; byte minVersionSet; -#ifdef WOLFSSL_DTLS CtxVersionState st; -#endif WOLFSSL_ENTER("wolfSSL_CTX_set_max_proto_version"); if (ctx == NULL) { return ret; } -#ifdef WOLFSSL_DTLS CtxVersionSave(ctx, &st); -#endif /* clear out flags and reset min protocol version */ minProto = wolfSSL_CTX_get_min_proto_version(ctx); @@ -5669,9 +5686,7 @@ int wolfSSL_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) if (version != 0) { ctx->maxProto = 0; /* turn max proto flag off */ ret = Set_CTX_max_proto_version(ctx, version); - #ifdef WOLFSSL_DTLS ret = CtxVersionRestore(ctx, &st, ret); - #endif return ret; } @@ -5686,9 +5701,7 @@ int wolfSSL_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) } } -#ifdef WOLFSSL_DTLS ret = CtxVersionRestore(ctx, &st, ret); -#endif return ret; } @@ -5706,6 +5719,17 @@ static int Set_SSL_min_proto_version(WOLFSSL* ssl, int ver) return WOLFSSL_FAILURE; } #endif + if (FixedTlsVersionCmp(ssl->version, ssl->options.downgrade, ver) > 0) { + WOLFSSL_MSG("Minimum above the fixed TLS version"); + return WOLFSSL_FAILURE; + } +#ifdef WOLFSSL_DTLS + if (ssl->version.major == DTLS_MAJOR && !ssl->options.downgrade && + DtlsMinDowngrade((byte)ver) < ssl->version.minor) { + WOLFSSL_MSG("Minimum above the fixed DTLS version"); + return WOLFSSL_FAILURE; + } +#endif switch (ver) { #ifndef NO_TLS @@ -5849,6 +5873,10 @@ static int Set_SSL_max_proto_version(WOLFSSL* ssl, int ver) mask = ssl->options.mask; maxMask = ssl->options.dtlsMaxMask; #endif + if (FixedTlsVersionCmp(ssl->version, ssl->options.downgrade, ver) < 0) { + WOLFSSL_MSG("Maximum below the fixed TLS version"); + return WOLFSSL_FAILURE; + } switch (ver) { case SSL2_VERSION: @@ -6064,6 +6092,22 @@ static int GetMaxProtoVersion(long options) return WOLFSSL_FATAL_ERROR; } +/* Returns the WOLFSSL_OP_NO_* bit that disables a TLS version. */ +static long TlsVersionMaskBit(int version) +{ + switch (version) { + case SSL3_VERSION: + return WOLFSSL_OP_NO_SSLv3; + case TLS1_VERSION: + return WOLFSSL_OP_NO_TLSv1; + case TLS1_1_VERSION: + return WOLFSSL_OP_NO_TLSv1_1; + case TLS1_2_VERSION: + return WOLFSSL_OP_NO_TLSv1_2; + default: + return WOLFSSL_OP_NO_TLSv1_3; + } +} /* returns the maximum protocol version for 'ctx' */ int wolfSSL_CTX_get_max_proto_version(WOLFSSL_CTX* ctx) @@ -6093,6 +6137,12 @@ int wolfSSL_CTX_get_max_proto_version(WOLFSSL_CTX* ctx) WOLFSSL_FATAL_ERROR; } #endif + else if ((ctx != NULL) && !ctx->method->downgrade) { + ret = (SSLv3_MAJOR << 8) | ctx->method->version.minor; + if ((options & TlsVersionMaskBit(ret)) != 0 || + ctx->minDowngrade > ctx->method->version.minor) + ret = WOLFSSL_FATAL_ERROR; + } else { ret = GetMaxProtoVersion(options); } diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 764331e80f5..9c121b78c82 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -17021,6 +17021,7 @@ static int test_dtls13_ctx_max_proto_fixed(method_provider method, int ver) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; struct test_memio_ctx test_ctx; + long mask = 0; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, @@ -17037,6 +17038,15 @@ static int test_dtls13_ctx_max_proto_fixed(method_provider method, int ver) ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, method, wolfDTLS_server_method), 0); ExpectIntEQ(wolfSSL_version(ssl_c), ver); + if (ver == DTLS1_2_VERSION) { + if (ssl_c != NULL) + mask = wolfSSL_get_options(ssl_c); + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl_c, DTLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl_c, TLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl_c), mask); + } ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(wolfSSL_version(ssl_s), ver); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 61e1c86e62c..fb464e122e2 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -2963,6 +2963,165 @@ int test_tls_fallback_scsv_no_downgrade_runtime_max(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) +/* A maximum or minimum past a fixed TLS method fails and never moves it. */ +static int test_tls_max_proto_fixed_case(method_provider method, int ver) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + long mask = 0; + long verBit = (ver == TLS1_3_VERSION) ? WOLFSSL_OP_NO_TLSv1_3 : + WOLFSSL_OP_NO_TLSv1_2; + int minProto = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL, + method, wolfSSLv23_server_method), 0); + if (ctx_c != NULL) { + mask = wolfSSL_CTX_get_options(ctx_c); + minProto = wolfSSL_CTX_get_min_proto_version(ctx_c); + } + if (ver == TLS1_3_VERSION) { + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, TLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx_c), mask); + } + else { + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx_c, TLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx_c), mask); + ExpectIntEQ(wolfSSL_CTX_get_min_proto_version(ctx_c), minProto); + } + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, TLS1_1_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx_c), mask); + ExpectIntEQ(wolfSSL_CTX_get_min_proto_version(ctx_c), minProto); + ExpectIntEQ(ctx_c != NULL ? ctx_c->method->version.minor : 0, ver & 0xff); + ExpectIntEQ(wolfSSL_CTX_set_min_proto_version(ctx_c, ver), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx_c, TLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx_c), ver); + if (ctx_c != NULL) { + wolfSSL_CTX_set_options(ctx_c, verBit); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx_c), 0); + wolfSSL_CTX_clear_options(ctx_c, verBit); + } + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + method, wolfSSLv23_server_method), 0); + if (ssl_c != NULL) + mask = wolfSSL_get_options(ssl_c); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, TLS1_1_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl_c), mask); + if (ver == TLS1_2_VERSION) { + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl_c, TLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl_c), mask); + } + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl_c), ver); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_s), ver); +#ifdef WOLFSSL_TLS13 + if (ver == TLS1_2_VERSION) { + ExpectIntEQ(wolfSSL_CTX_SetMinVersion(ctx_c, WOLFSSL_TLSV1_3), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx_c), 0); + } +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) +/* require_psk and SetVersion pin a v23 object, so a lower maximum fails. */ +static int test_tls_max_proto_pinned(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + long mask = 0; +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectIntEQ(wolfSSL_CTX_require_psk(ctx), 0); + if (ctx != NULL) + mask = wolfSSL_CTX_get_options(ctx); + ExpectIntEQ(wolfSSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_get_options(ctx), mask); + ExpectIntEQ(ctx != NULL ? ctx->method->version.minor : 0, TLSv1_3_MINOR); + ExpectIntEQ(wolfSSL_CTX_get_max_proto_version(ctx), TLS1_3_VERSION); + wolfSSL_CTX_free(ctx); + ctx = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_require_psk(ssl), 0); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, TLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_version(ssl), TLS1_3_VERSION); + wolfSSL_free(ssl); + ssl = NULL; +#else + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); +#endif + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_SetVersion(ssl, WOLFSSL_TLSV1_3), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, TLS1_2_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set_max_proto_version(ssl, TLS1_3_VERSION), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_version(ssl), TLS1_3_VERSION); + wolfSSL_free(ssl); + ssl = NULL; +#ifndef WOLFSSL_NO_TLS12 + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_SetVersion(ssl, WOLFSSL_TLSV1_2), WOLFSSL_SUCCESS); + if (ssl != NULL) + mask = wolfSSL_get_options(ssl); + ExpectIntEQ(wolfSSL_set_min_proto_version(ssl, TLS1_3_VERSION), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_get_options(ssl), mask); + ExpectIntEQ(wolfSSL_version(ssl), TLS1_2_VERSION); + wolfSSL_free(ssl); +#endif + wolfSSL_CTX_free(ctx); + (void)mask; + return EXPECT_RESULT(); +} +#endif + +int test_tls_max_proto_fixed(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) +#ifdef WOLFSSL_TLS13 + ExpectIntEQ(test_tls_max_proto_fixed_case(wolfTLSv1_3_client_method, + TLS1_3_VERSION), TEST_SUCCESS); +#endif +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(test_tls_max_proto_fixed_case(wolfTLSv1_2_client_method, + TLS1_2_VERSION), TEST_SUCCESS); +#endif +#endif +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) + ExpectIntEQ(test_tls_max_proto_pinned(), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + /* RFC 5246 Section 7.2.2 and RFC 8446 Section 4.1.3: a server that cannot * negotiate any version at or below ClientHello.client_version MUST abort with * a fatal protocol_version alert. diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 1dc1563da69..56abaf03087 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -56,6 +56,7 @@ int test_dtls_fallback_scsv(void); int test_dtls_fallback_scsv_no_downgrade(void); int test_tls_fallback_scsv_no_downgrade(void); int test_tls_fallback_scsv_no_downgrade_runtime_max(void); +int test_tls_max_proto_fixed(void); int test_tls_no_acceptable_version_alert(void); int test_tls_lesser_version_alerts(void); int test_tls_version_mask_alert_record(void); @@ -170,6 +171,7 @@ int test_tls_unset_fd_no_stdin_io(void); TEST_DECL_GROUP("tls", test_dtls_fallback_scsv_no_downgrade), \ TEST_DECL_GROUP("tls", test_tls_fallback_scsv_no_downgrade), \ TEST_DECL_GROUP("tls", test_tls_fallback_scsv_no_downgrade_runtime_max),\ + TEST_DECL_GROUP("tls", test_tls_max_proto_fixed), \ TEST_DECL_GROUP("tls", test_tls_no_acceptable_version_alert), \ TEST_DECL_GROUP("tls", test_tls_lesser_version_alerts), \ TEST_DECL_GROUP("tls", test_tls_version_mask_alert_record), \