From 279a8df83b9f36207db12af983f46fe203d86a1b 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/13] 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 | 97 ++++++++++------ tests/api.c | 1 + tests/api/test_tls_ext.c | 231 +++++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 2 + 4 files changed, 298 insertions(+), 33 deletions(-) diff --git a/src/tls.c b/src/tls.c index 60980f51a53..82af100d316 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16346,10 +16346,65 @@ 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) { int ret = WOLFSSL_SUCCESS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_BRAINPOOL) + int preTls13; +#endif #ifdef WOLFSSL_TLS13 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) if (ssl->options.resuming && ssl->session->namedGroup != 0) { @@ -16370,6 +16425,12 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) } #endif /* WOLFSSL_TLS13 */ +#if defined(HAVE_ECC) && defined(HAVE_ECC_BRAINPOOL) + preTls13 = !IsAtLeastTLSv1_3(ssl->version) || + TLSX_PreTls13Reachable(ssl); + (void)preTls13; +#endif + #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) /* Prefer non-experimental PQ/T hybrid groups (only for TLS 1.3) */ @@ -16425,18 +16486,8 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_BRAINPOOLP512R1TLS13, ssl->heap, ssl->options.side); if (ret != WOLFSSL_SUCCESS) return ret; - - /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ - if (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP512R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; - } } - else { - /* TLS 1.2 only */ + if (preTls13) { ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_BRAINPOOLP512R1, ssl->heap, ssl->options.side); if (ret != WOLFSSL_SUCCESS) return ret; @@ -16469,18 +16520,8 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_BRAINPOOLP384R1TLS13, ssl->heap, ssl->options.side); if (ret != WOLFSSL_SUCCESS) return ret; - - /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ - if (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP384R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; - } } - else { - /* TLS 1.2 only */ + if (preTls13) { ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_BRAINPOOLP384R1, ssl->heap, ssl->options.side); if (ret != WOLFSSL_SUCCESS) return ret; @@ -16526,18 +16567,8 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_BRAINPOOLP256R1TLS13, ssl->heap, ssl->options.side); if (ret != WOLFSSL_SUCCESS) return ret; - - /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ - if (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP256R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; - } } - else { - /* TLS 1.2 only */ + if (preTls13) { ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_BRAINPOOLP256R1, ssl->heap, ssl->options.side); if (ret != WOLFSSL_SUCCESS) return ret; diff --git a/tests/api.c b/tests/api.c index be5c1267a4c..43c9493d094 100644 --- a/tests/api.c +++ b/tests/api.c @@ -47027,6 +47027,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 a4eefb08ee2..6cc56aec76a 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3178,6 +3178,237 @@ 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) +{ + SupportedCurve* curve = NULL; + int i; + + *tls13Cnt = 0; + *tls12Cnt = 0; + if (ext != NULL) + curve = (SupportedCurve*)ext->data; + for (; curve != NULL; curve = curve->next) { + for (i = 0; i < TEST_BP_CNT; i++) { + if (curve->name == test_bp_tls13[i]) + (*tls13Cnt)++; + else if (curve->name == 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 3ba806a662b..eb7d76e0222 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -67,6 +67,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); @@ -102,6 +103,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 ee871a2c8b349b3bbca16fd015355a25d6b920ae 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/13] 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 4a6d962be7d..ee64a075974 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8085,6 +8085,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. */ @@ -8103,6 +8126,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 d4dfe1afee7..b55b548cd92 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7599,6 +7599,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 1219f4fbc9a..a036226158f 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15603,3 +15603,144 @@ int test_dtls_max_fragment_write(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 2e5411a8fe5..035e9d5556e 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -178,6 +178,7 @@ int test_dtls13_hrr_cookie_state_guard(void); 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_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); @@ -388,5 +389,6 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_disabled), \ 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_max_fragment_write), \ + 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 6cc56aec76a..e1e178ec439 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3397,6 +3397,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 991a818a541..a7ed528bb36 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8282,6 +8282,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 c39d83bb821ff4387c7e7525a8efbbd1ad3f5afa 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/13] 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 ee64a075974..11fc9e8039b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -36460,6 +36460,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 82af100d316..544e987c65e 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16790,6 +16790,39 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) return ret; } +#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) @@ -16829,7 +16862,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; @@ -17247,6 +17281,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 @@ -17463,9 +17502,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; @@ -17502,7 +17541,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) @@ -17559,7 +17599,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) @@ -18118,7 +18158,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 e1e178ec439..5ef90f333fa 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3306,6 +3306,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 @@ -3412,6 +3649,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 4fcd3396f2934325d02521605f5c56baa357785e 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/13] 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 11fc9e8039b..f18c8388239 100644 --- a/src/internal.c +++ b/src/internal.c @@ -36462,7 +36462,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 b55b548cd92..d23d5e901b9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7601,14 +7601,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 544e987c65e..5c489d7087a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16799,12 +16799,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; } @@ -16812,12 +16816,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 a036226158f..072233c9e0d 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15652,6 +15652,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); @@ -15714,9 +15717,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 18730cb07f5..6c0a2fac729 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8394,6 +8394,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 123831145b1..2db640f8819 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -57,6 +57,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); @@ -236,6 +237,7 @@ int test_tls13_cks_hrr_reparse(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 5ef90f333fa..c1fcac2e3af 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3542,6 +3542,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 @@ -3671,6 +3733,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 24ebab8000d..af80e86e6ec 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3072,6 +3072,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 a289daaf360cfa3f405363cc2a985b9cdd470aa4 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/13] 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 f18c8388239..6a2db1f2f46 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8085,13 +8085,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; } @@ -8103,9 +8110,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 @@ -8134,7 +8163,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."); @@ -36461,14 +36490,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; @@ -43087,6 +43117,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; @@ -43160,6 +43193,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 d23d5e901b9..d4dfe1afee7 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7599,19 +7599,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 5feaff1d091..2b59bb6d62a 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 5c489d7087a..3f008dd8702 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16821,12 +16821,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 072233c9e0d..d766afe922b 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15606,10 +15606,10 @@ int test_dtls_max_fragment_write(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; @@ -15628,6 +15628,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); @@ -15638,14 +15639,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) { @@ -15658,6 +15660,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)); } } @@ -15667,6 +15671,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 @@ -15681,56 +15713,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; @@ -15746,6 +15779,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 6c0a2fac729..7081a67ce8a 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8470,6 +8470,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 a7ed528bb36..9c7437556a4 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8276,14 +8276,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 af80e86e6ec..d2446bf7009 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3075,7 +3075,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 8a6a3213d9e7bc4fecc7cade3555f209aa93ba9f 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/13] 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 6a2db1f2f46..634a85bd22c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8114,27 +8114,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 @@ -36391,6 +36370,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 d4dfe1afee7..b171adee4a3 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7581,60 +7581,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 @@ -7647,7 +7600,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) @@ -7709,10 +7662,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 437218fefa0..bc841f8da05 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16681,6 +16681,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) { @@ -17958,6 +17972,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 d766afe922b..4bc22ffc33a 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15606,6 +15606,18 @@ int test_dtls_max_fragment_write(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, @@ -15654,6 +15666,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); @@ -15698,6 +15712,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 @@ -15723,7 +15796,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++) { @@ -15757,6 +15830,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 1f1c30ef7c53d7429688eb3870ee2f8ba5a37994 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/13] 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 634a85bd22c..2821322534e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -36372,6 +36372,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"); @@ -36483,8 +36484,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. */ @@ -43106,9 +43110,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; @@ -43182,23 +43183,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; @@ -43219,6 +43203,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 b171adee4a3..312a1f7b193 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7582,7 +7582,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; @@ -7713,6 +7713,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) @@ -7732,6 +7737,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; @@ -7744,6 +7751,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 4bc22ffc33a..3f7d82f5a86 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15659,6 +15659,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); @@ -15737,6 +15739,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) { @@ -15832,6 +15934,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 9c7437556a4..a05f629b81b 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2380,6 +2380,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 a19568ca76701456444960fa5fa6adbd64ceb7fb 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/13] 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 2821322534e..b204f16c2a9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -43205,6 +43205,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 312a1f7b193..98b72b1e892 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7665,6 +7665,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"); @@ -7714,8 +7762,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 @@ -7727,38 +7774,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 2b59bb6d62a..ffae9951d46 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 3f7d82f5a86..1b13ab0b032 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15786,8 +15786,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; @@ -15799,8 +15801,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); @@ -15816,18 +15820,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); @@ -15928,15 +15932,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 d2446bf7009..694e124f489 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3072,8 +3072,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 885727e2670b479c24e927e79fae1ce205dc25e6 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/13] 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 b204f16c2a9..719e81760cb 100644 --- a/src/internal.c +++ b/src/internal.c @@ -36480,8 +36480,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); @@ -43204,30 +43204,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 98b72b1e892..7967b20911a 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7667,8 +7667,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; @@ -7697,6 +7696,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 3f008dd8702..3061168155f 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7480,6 +7480,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 bc841f8da05..79379ea1d08 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16690,8 +16690,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 1b13ab0b032..e07ef7e0421 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15739,9 +15739,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; @@ -15753,10 +15753,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 @@ -15843,6 +15843,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) { @@ -15948,7 +16047,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 5cd7874e201fc5c0ae7b0b0547e0b7f9c60c9258 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/13] 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 | 52 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/internal.c b/src/internal.c index 719e81760cb..153f5e0c988 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8090,26 +8090,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 7967b20911a..33aee7e48d6 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7671,6 +7671,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_CLIENT_END); byte minor; /* A minimum of DTLS_MINOR tests the mask alone. */ @@ -7683,7 +7685,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) { @@ -7693,7 +7695,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 @@ -7764,10 +7766,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; @@ -7776,7 +7778,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 ba42dea2168..48bdbdb44b8 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1733,6 +1733,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 e07ef7e0421..6f82c5b4408 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15844,8 +15844,8 @@ 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. */ +/* 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; @@ -15879,8 +15879,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); @@ -15942,6 +15946,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) { @@ -16037,6 +16080,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 b3068ddb014450d4a5ba4ef03714b4bc48a8964a 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/13] 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. 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 | 138 ++++++++++++- tests/api/test_dtls.c | 306 +++++++++++++++++++++++++++- wolfssl/internal.h | 1 + wolfssl/ssl.h | 1 + 6 files changed, 462 insertions(+), 19 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 4328cbe7411..26d2d13ed29 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -6733,6 +6733,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 153f5e0c988..16e4d9cffb0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8075,17 +8075,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) @@ -8124,6 +8126,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 33aee7e48d6..a8c2f41d892 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -2163,9 +2163,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) @@ -2216,6 +2225,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; } @@ -2240,7 +2253,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; @@ -2283,7 +2297,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); @@ -5062,7 +5077,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)) @@ -5086,6 +5104,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. */ @@ -5134,11 +5153,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 @@ -5162,6 +5190,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: @@ -5187,6 +5218,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; @@ -5218,7 +5256,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); @@ -5252,6 +5290,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. */ @@ -5264,6 +5303,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 @@ -5289,8 +5335,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: @@ -5317,6 +5368,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; @@ -5350,6 +5404,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"); @@ -5410,6 +5474,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 @@ -5448,11 +5518,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 @@ -5476,6 +5555,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: @@ -5519,6 +5601,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"); @@ -5526,6 +5612,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: @@ -5549,15 +5642,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); } @@ -5616,6 +5726,19 @@ 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; + case DTLSv1_3_MINOR: + ret = DTLS1_3_VERSION; + break; #endif default: ret = 0; @@ -7675,13 +7798,12 @@ static int DtlsMaskVersion(WOLFSSL* ssl) ssl->options.side == WOLFSSL_CLIENT_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 6f82c5b4408..db27f2a8c77 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15985,6 +15985,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) { @@ -16074,12 +16306,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 @@ -16123,6 +16386,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 a05f629b81b..10e9946cdb6 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8277,6 +8277,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 694e124f489..c594eb143db 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3072,6 +3072,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 a74729cebd20a6e931cbf564b319f7dbfa0f9c5a 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/13] 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 79379ea1d08..e05ad9ab472 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16682,21 +16682,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 @@ -17977,17 +17977,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 db27f2a8c77..f6a84e7bddb 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -16217,6 +16217,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) { @@ -16342,6 +16443,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 a6a52053488afc6b1f422ee3d42b2f66a139eab0 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/13] 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. 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_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, the CTX maximum under a DTLS 1.3 minimum with SSL_OP_NO_DTLSv1_3, 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, and the exact error for a DTLS 1.3-only client against a DTLS 1.2 server. --- src/internal.c | 31 +-- src/ssl.c | 245 ++++++++++++++------ src/ssl_api_hs.c | 4 +- src/ssl_sess.c | 2 +- src/tls13.c | 4 +- tests/api/test_dtls.c | 520 ++++++++++++++++++++++++++++++++++++++++-- tests/api/test_dtls.h | 4 +- wolfssl/internal.h | 15 +- 8 files changed, 714 insertions(+), 111 deletions(-) diff --git a/src/internal.c b/src/internal.c index 16e4d9cffb0..6942994d81c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2816,10 +2816,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) @@ -8090,15 +8086,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) { @@ -8118,12 +8107,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. @@ -9114,7 +9114,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; @@ -43224,8 +43224,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 a8c2f41d892..aaf3d80cd85 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -4905,6 +4905,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_CLIENT_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 @@ -5337,12 +5426,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"); @@ -5406,11 +5501,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 @@ -5603,6 +5693,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 @@ -5613,11 +5706,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) { @@ -5644,11 +5738,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: @@ -5661,11 +5757,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); @@ -5681,6 +5795,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); } @@ -5809,6 +5932,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"); @@ -5819,6 +5945,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); } @@ -6024,6 +6160,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; @@ -6044,10 +6183,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) { @@ -7788,57 +7925,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_CLIENT_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"); @@ -7888,8 +7974,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 */ @@ -7957,10 +8043,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 ffae9951d46..961531df9c5 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 48bdbdb44b8..0311a03b4d3 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1734,7 +1734,7 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) ssl->peerVerifyRet = (unsigned long)ssl->session->peerVerifyRet; #endif #ifdef WOLFSSL_DTLS - (void)DtlsCheckVersionMask(ssl); + (void)DtlsCheckVersionMask(ssl, DTLS_MASK_START); #endif return WOLFSSL_SUCCESS; diff --git a/src/tls13.c b/src/tls13.c index e05ad9ab472..8093fc1619d 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16683,7 +16683,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; @@ -17978,7 +17978,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 f6a84e7bddb..9de0aa06136 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -15983,6 +15983,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. */ @@ -15998,6 +16045,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); @@ -16011,9 +16061,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; @@ -16022,19 +16072,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); @@ -16085,6 +16135,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 @@ -16120,7 +16172,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); @@ -16192,6 +16246,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) { @@ -16355,6 +16751,44 @@ 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(); +} + /* 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. * @@ -16414,7 +16848,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); @@ -16430,27 +16867,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, @@ -16477,21 +16933,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); @@ -16503,6 +16986,7 @@ 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); ExpectIntEQ(wolfSSL_CTX_get_options(ctx) & WOLFSSL_OP_NO_TLSv1_3, diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 035e9d5556e..718cdf132a0 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -179,6 +179,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_dtls13_no_tls13_option(void); +int test_dtls_max_proto_after_clear(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_import_dh_key_sz(void); int test_wolfSSL_dtls_export_peers(void); @@ -390,5 +391,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_dtls13_no_tls13_option) + TEST_DECL_GROUP("dtls", test_dtls13_no_tls13_option), \ + TEST_DECL_GROUP("dtls", test_dtls_max_proto_after_clear) #endif /* TESTS_API_DTLS_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 10e9946cdb6..faf1788f7a8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5760,6 +5760,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 */ @@ -8278,9 +8281,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