From 6c972ad5052dcfb68fe865c0a9afaba20a377764 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 18 Mar 2026 19:26:44 +0100 Subject: [PATCH 1/5] DTLS 1.3: don't echo legacy_session_id in ServerHello --- src/tls13.c | 82 ++++++++++++++++++++++++++++++++++--------- tests/api/test_dtls.c | 78 ++++++++++++++++++++++++++++++++++++++++ tests/api/test_dtls.h | 4 ++- 3 files changed, 147 insertions(+), 17 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 50192f763c0..00da4107515 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -5571,6 +5571,18 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, case TLS_ASYNC_FINALIZE: { +#ifdef WOLFSSL_DTLS13 + /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty + * legacy_session_id_echo. */ + if (ssl->options.dtls) { + if (args->sessIdSz != 0) { + WOLFSSL_MSG("DTLS 1.3 ServerHello must have empty session ID"); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + return INVALID_PARAMETER; + } + } + else +#endif #ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT if (ssl->options.tls13MiddleBoxCompat) { if (args->sessIdSz == 0) { @@ -6592,8 +6604,11 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) return ret; /* Reconstruct the HelloRetryMessage for handshake hash. */ - length = HRR_BODY_SZ - ID_LEN + ssl->session->sessionIDSz + - HRR_COOKIE_HDR_SZ + cookie->len; + length = HRR_BODY_SZ - ID_LEN + HRR_COOKIE_HDR_SZ + cookie->len; +#ifdef WOLFSSL_DTLS13 + if (!ssl->options.dtls) +#endif + length += ssl->session->sessionIDSz; length += HRR_VERSIONS_SZ; /* HashSz (1 byte) + Hash (HashSz bytes) + CipherSuite (2 bytes) */ if (cookieDataSz > OPAQUE8_LEN + hashSz + OPAQUE16_LEN) { @@ -6619,10 +6634,17 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) XMEMCPY(hrr + hrrIdx, helloRetryRequestRandom, RAN_LEN); hrrIdx += RAN_LEN; - hrr[hrrIdx++] = ssl->session->sessionIDSz; - if (ssl->session->sessionIDSz > 0) { - XMEMCPY(hrr + hrrIdx, ssl->session->sessionID, ssl->session->sessionIDSz); - hrrIdx += ssl->session->sessionIDSz; +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) + hrr[hrrIdx++] = 0; + else +#endif + { + hrr[hrrIdx++] = ssl->session->sessionIDSz; + if (ssl->session->sessionIDSz > 0) { + XMEMCPY(hrr + hrrIdx, ssl->session->sessionID, ssl->session->sessionIDSz); + hrrIdx += ssl->session->sessionIDSz; + } } /* Cipher Suite */ @@ -6633,7 +6655,11 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) hrr[hrrIdx++] = 0; /* Extensions' length */ - length -= HRR_BODY_SZ - ID_LEN + ssl->session->sessionIDSz; + length -= HRR_BODY_SZ - ID_LEN; +#ifdef WOLFSSL_DTLS13 + if (!ssl->options.dtls) +#endif + length -= ssl->session->sessionIDSz; c16toa(length, hrr + hrrIdx); hrrIdx += 2; @@ -7058,9 +7084,17 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, if (sessIdSz + args->idx > helloSz) ERROR_OUT(BUFFER_ERROR, exit_dch); - ssl->session->sessionIDSz = sessIdSz; - if (sessIdSz > 0) - XMEMCPY(ssl->session->sessionID, input + args->idx, sessIdSz); +#ifdef WOLFSSL_DTLS13 + /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty + * legacy_session_id_echo. Don't store the client's value so it + * won't be echoed in SendTls13ServerHello. */ + if (!ssl->options.dtls) +#endif + { + ssl->session->sessionIDSz = sessIdSz; + if (sessIdSz > 0) + XMEMCPY(ssl->session->sessionID, input + args->idx, sessIdSz); + } args->idx += sessIdSz; #ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT @@ -7573,8 +7607,13 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) /* Protocol version, server random, session id, cipher suite, compression * and extensions. */ - length = VERSION_SZ + RAN_LEN + ENUM_LEN + ssl->session->sessionIDSz + - SUITE_LEN + COMP_LEN; + length = VERSION_SZ + RAN_LEN + ENUM_LEN + SUITE_LEN + COMP_LEN; +#ifdef WOLFSSL_DTLS13 + /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty + * legacy_session_id_echo. */ + if (!ssl->options.dtls) +#endif + length += ssl->session->sessionIDSz; ret = TLSX_GetResponseSize(ssl, extMsgType, &length); if (ret != 0) return ret; @@ -7618,10 +7657,21 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) WOLFSSL_BUFFER(ssl->arrays->serverRandom, RAN_LEN); #endif - output[idx++] = ssl->session->sessionIDSz; - if (ssl->session->sessionIDSz > 0) { - XMEMCPY(output + idx, ssl->session->sessionID, ssl->session->sessionIDSz); - idx += ssl->session->sessionIDSz; +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty + * legacy_session_id_echo. */ + output[idx++] = 0; + } + else +#endif + { + output[idx++] = ssl->session->sessionIDSz; + if (ssl->session->sessionIDSz > 0) { + XMEMCPY(output + idx, ssl->session->sessionID, + ssl->session->sessionIDSz); + idx += ssl->session->sessionIDSz; + } } /* Chosen cipher suite */ diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index bc277066dcf..feb23118767 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -2609,3 +2609,81 @@ int test_dtls13_min_rtx_interval(void) #endif return EXPECT_RESULT(); } + +/* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty + * legacy_session_id_echo, even if the ClientHello had a non-empty + * legacy_session_id. */ +int test_dtls13_no_session_id_echo(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + char readBuf[1]; + + /* First connection: complete a DTLS 1.3 handshake to get a session */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Read to process any NewSessionTicket */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + + /* Ensure the session has a non-empty session ID so the ClientHello + * will have a populated legacy_session_id field (which is legal per + * RFC 9147). */ + if (sess->sessionIDSz == 0) { + sess->sessionIDSz = ID_LEN; + XMEMSET(sess->sessionID, 0x42, ID_LEN); + } + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Second connection: set the session on the client so the ClientHello + * contains a non-empty legacy_session_id. Verify the server does NOT + * echo it in the ServerHello. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + /* Disable HRR cookie so the server directly sends a ServerHello */ + ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); + + /* Client sends ClientHello (with non-empty legacy_session_id) */ + ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* Server processes ClientHello and sends ServerHello + flight */ + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + + /* Verify the ServerHello on the wire. + * Layout: DTLS Record Header (13) + DTLS Handshake Header (12) + + * ProtocolVersion (2) + Random (32) = offset 59 for + * legacy_session_id_echo length byte. */ + ExpectIntGE(test_ctx.c_len, 60); + ExpectIntEQ(test_ctx.c_buff[0], handshake); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], server_hello); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ + + DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN + RAN_LEN], 0); + + /* Complete the handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 72a82d58b76..a104a241cff 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -50,6 +50,7 @@ int test_dtls_memio_wolfio_stateless(void); int test_dtls_mtu_fragment_headroom(void); int test_dtls_mtu_split_messages(void); int test_dtls13_min_rtx_interval(void); +int test_dtls13_no_session_id_echo(void); #define TEST_DTLS_DECLS \ TEST_DECL_GROUP("dtls", test_dtls12_basic_connection_id), \ @@ -79,5 +80,6 @@ int test_dtls13_min_rtx_interval(void); TEST_DECL_GROUP("dtls", test_dtls_mtu_fragment_headroom), \ TEST_DECL_GROUP("dtls", test_dtls_mtu_split_messages), \ TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_stateless), \ - TEST_DECL_GROUP("dtls", test_dtls13_min_rtx_interval) + TEST_DECL_GROUP("dtls", test_dtls13_min_rtx_interval), \ + TEST_DECL_GROUP("dtls", test_dtls13_no_session_id_echo) #endif /* TESTS_API_DTLS_H */ From 7d4865cf05f8a88e04883a1c9a95407e76e0f71f Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 19 Mar 2026 12:33:49 +0100 Subject: [PATCH 2/5] Address review --- src/tls13.c | 77 +++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 00da4107515..9d3b0199cf8 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4653,6 +4653,13 @@ int SendTls13ClientHello(WOLFSSL* ssl) ssl->session->sessionIDSz = 0; ssl->options.tls13MiddleBoxCompat = 0; } +#endif +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + /* RFC 9147 Section 5: DTLS implementations do not use the + * TLS 1.3 "compatibility mode" */ + ssl->options.tls13MiddleBoxCompat = 0; + } #endif GetTls13SessionId(ssl, NULL, &sessIdSz); args->length += (word16)sessIdSz; @@ -5571,18 +5578,6 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, case TLS_ASYNC_FINALIZE: { -#ifdef WOLFSSL_DTLS13 - /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty - * legacy_session_id_echo. */ - if (ssl->options.dtls) { - if (args->sessIdSz != 0) { - WOLFSSL_MSG("DTLS 1.3 ServerHello must have empty session ID"); - WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); - return INVALID_PARAMETER; - } - } - else -#endif #ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT if (ssl->options.tls13MiddleBoxCompat) { if (args->sessIdSz == 0) { @@ -5608,8 +5603,17 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } else #endif /* WOLFSSL_TLS13_MIDDLEBOX_COMPAT */ +#if defined(WOLFSSL_QUIC) || defined(WOLFSSL_DTLS13) + if (0 #ifdef WOLFSSL_QUIC - if (WOLFSSL_IS_QUIC(ssl)) { + || WOLFSSL_IS_QUIC(ssl) +#endif +#ifdef WOLFSSL_DTLS13 + || ssl->options.dtls +#endif + ) { + /* RFC 9147 Section 5.3 / RFC 9001 Section 8.4: DTLS 1.3 and QUIC + * ServerHello must have empty legacy_session_id_echo. */ if (args->sessIdSz != 0) { WOLFSSL_MSG("args->sessIdSz != 0"); WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); @@ -5617,7 +5621,7 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } } else -#endif /* WOLFSSL_QUIC */ +#endif /* WOLFSSL_QUIC || WOLFSSL_DTLS13 */ if (args->sessIdSz != ssl->session->sessionIDSz || (args->sessIdSz > 0 && XMEMCMP(ssl->session->sessionID, args->sessId, args->sessIdSz) != 0)) { @@ -6580,6 +6584,7 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) word16 length; int keyShareExt = 0; int ret; + byte sessIdSz; ret = TlsCheckCookie(ssl, cookie->data, (byte)cookie->len); if (ret < 0) @@ -6604,11 +6609,14 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) return ret; /* Reconstruct the HelloRetryMessage for handshake hash. */ - length = HRR_BODY_SZ - ID_LEN + HRR_COOKIE_HDR_SZ + cookie->len; + sessIdSz = ssl->session->sessionIDSz; #ifdef WOLFSSL_DTLS13 - if (!ssl->options.dtls) + /* RFC 9147 Section 5.3: DTLS 1.3 must use empty legacy_session_id. */ + if (ssl->options.dtls) + sessIdSz = 0; #endif - length += ssl->session->sessionIDSz; + length = HRR_BODY_SZ - ID_LEN + sessIdSz + + HRR_COOKIE_HDR_SZ + cookie->len; length += HRR_VERSIONS_SZ; /* HashSz (1 byte) + Hash (HashSz bytes) + CipherSuite (2 bytes) */ if (cookieDataSz > OPAQUE8_LEN + hashSz + OPAQUE16_LEN) { @@ -6634,17 +6642,10 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) XMEMCPY(hrr + hrrIdx, helloRetryRequestRandom, RAN_LEN); hrrIdx += RAN_LEN; -#ifdef WOLFSSL_DTLS13 - if (ssl->options.dtls) - hrr[hrrIdx++] = 0; - else -#endif - { - hrr[hrrIdx++] = ssl->session->sessionIDSz; - if (ssl->session->sessionIDSz > 0) { - XMEMCPY(hrr + hrrIdx, ssl->session->sessionID, ssl->session->sessionIDSz); - hrrIdx += ssl->session->sessionIDSz; - } + hrr[hrrIdx++] = sessIdSz; + if (sessIdSz > 0) { + XMEMCPY(hrr + hrrIdx, ssl->session->sessionID, sessIdSz); + hrrIdx += sessIdSz; } /* Cipher Suite */ @@ -6655,11 +6656,7 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) hrr[hrrIdx++] = 0; /* Extensions' length */ - length -= HRR_BODY_SZ - ID_LEN; -#ifdef WOLFSSL_DTLS13 - if (!ssl->options.dtls) -#endif - length -= ssl->session->sessionIDSz; + length -= HRR_BODY_SZ - ID_LEN + sessIdSz; c16toa(length, hrr + hrrIdx); hrrIdx += 2; @@ -7088,7 +7085,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty * legacy_session_id_echo. Don't store the client's value so it * won't be echoed in SendTls13ServerHello. */ - if (!ssl->options.dtls) + if (ssl->options.dtls) { + ssl->session->sessionIDSz = 0; + } + else #endif { ssl->session->sessionIDSz = sessIdSz; @@ -7607,13 +7607,8 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) /* Protocol version, server random, session id, cipher suite, compression * and extensions. */ - length = VERSION_SZ + RAN_LEN + ENUM_LEN + SUITE_LEN + COMP_LEN; -#ifdef WOLFSSL_DTLS13 - /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty - * legacy_session_id_echo. */ - if (!ssl->options.dtls) -#endif - length += ssl->session->sessionIDSz; + length = VERSION_SZ + RAN_LEN + ENUM_LEN + ssl->session->sessionIDSz + + SUITE_LEN + COMP_LEN; ret = TLSX_GetResponseSize(ssl, extMsgType, &length); if (ret != 0) return ret; From bf9686e961311ef3ba307c192832aaec78037eda Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 19 Mar 2026 18:04:25 +0100 Subject: [PATCH 3/5] Add a test that does a session resumption with session ID with downgrade --- tests/api/test_tls.c | 72 ++++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 4 ++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 565006171bc..92bfc47f499 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -768,3 +768,75 @@ int test_tls_set_curves_list_ecc_fallback(void) return EXPECT_RESULT(); } +int test_tls_session_id_resume_downgrade(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_SESSION_CACHE) + struct { + method_provider client_meth_v12; + method_provider server_meth; + method_provider client_meth; + int expected_version; + } params[] = { +#ifdef WOLFSSL_TLS13 + /* TLS 1.2 client → server, then client resumes at 1.2 */ + { wolfTLSv1_2_client_method, wolfTLS_server_method, + wolfTLS_client_method, TLS1_2_VERSION }, +#endif +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS13) + /* DTLS 1.2 client → server, then client resumes at 1.2 */ + { wolfDTLSv1_2_client_method, wolfDTLS_server_method, + wolfDTLS_client_method, DTLS1_2_VERSION }, +#endif + }; + size_t i; + + for (i = 0; i < sizeof(params)/sizeof(*params) && !EXPECT_FAIL(); i++) { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + + /* --- first connection: v1.2-only client to server --- */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, params[i].client_meth_v12, + params[i].server_meth), 0); + /* Disable tickets so resumption must use session IDs */ + if (EXPECT_SUCCESS()) + wolfSSL_CTX_set_options(ctx_s, WOLFSSL_OP_NO_TICKET); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), params[i].expected_version); + + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); +#ifdef HAVE_SESSION_TICKET + ExpectIntEQ(sess->ticketLen, 0); +#endif + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + /* keep ctx_s so the server session cache is available */ + + /* --- second connection: client resumes the v1.2 session --- */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, params[i].client_meth, + params[i].server_meth), 0); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectTrue(wolfSSL_session_reused(ssl_c)); + ExpectIntEQ(wolfSSL_version(ssl_c), params[i].expected_version); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 46d54043446..ea413ed5195 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -31,6 +31,7 @@ int test_tls_certreq_order(void); int test_tls12_bad_cv_sig_alg(void); int test_tls12_no_null_compression(void); int test_tls_set_curves_list_ecc_fallback(void); +int test_tls_session_id_resume_downgrade(void); #define TEST_TLS_DECLS \ TEST_DECL_GROUP("tls", test_utils_memio_move_message), \ @@ -41,6 +42,7 @@ int test_tls_set_curves_list_ecc_fallback(void); TEST_DECL_GROUP("tls", test_tls_certreq_order), \ TEST_DECL_GROUP("tls", test_tls12_bad_cv_sig_alg), \ TEST_DECL_GROUP("tls", test_tls12_no_null_compression), \ - TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback) + TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \ + TEST_DECL_GROUP("tls", test_tls_session_id_resume_downgrade) #endif /* TESTS_API_TEST_TLS_H */ From 6f6af91ba6d679964b05e77e70607d0fad1a30d4 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 20 Mar 2026 15:19:19 +0100 Subject: [PATCH 4/5] fixup! DTLS 1.3: don't echo legacy_session_id in ServerHello --- src/dtls.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dtls.c b/src/dtls.c index 17e8b1b4c20..20023329d3f 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -857,9 +857,9 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch) nonConstSSL->options.tls1_1 = 1; nonConstSSL->options.tls1_3 = 1; - XMEMCPY(nonConstSSL->session->sessionID, ch->sessionId.elements, - ch->sessionId.size); - nonConstSSL->session->sessionIDSz = (byte)ch->sessionId.size; + /* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty + * legacy_session_id_echo. Don't copy the client's session ID. */ + nonConstSSL->session->sessionIDSz = 0; nonConstSSL->options.cipherSuite0 = cs.cipherSuite0; nonConstSSL->options.cipherSuite = cs.cipherSuite; nonConstSSL->extensions = parsedExts; From 9d11f91a5664dea6de041a5fe02d4d5426df3491 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 20 Mar 2026 15:58:05 +0100 Subject: [PATCH 5/5] fixup! DTLS 1.3: don't echo legacy_session_id in ServerHello --- tests/api/test_dtls.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index feb23118767..b1ae877ee64 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -2622,11 +2622,17 @@ int test_dtls13_no_session_id_echo(void) WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL_SESSION *sess = NULL; char readBuf[1]; + /* Use traditional groups to avoid HRR from PQ key share mismatch */ + int groups[] = { + WOLFSSL_ECC_SECP256R1, + WOLFSSL_ECC_SECP384R1, + }; /* First connection: complete a DTLS 1.3 handshake to get a session */ XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 2), WOLFSSL_SUCCESS); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); /* Read to process any NewSessionTicket */ @@ -2655,6 +2661,8 @@ int test_dtls13_no_session_id_echo(void) ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + /* Use traditional groups to avoid HRR from key share mismatch */ + ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 2), WOLFSSL_SUCCESS); /* Disable HRR cookie so the server directly sends a ServerHello */ ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS);