Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wolfssl-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wolfssl-sys"
version = "4.0.0"
version = "4.1.0"
description = "System bindings for WolfSSL"
readme = "README.md"
authors.workspace = true
Expand Down
6 changes: 5 additions & 1 deletion wolfssl-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> std::io::Result<()> {
}

const PATCH_DIR: &str = "patches";
const PATCHES: &[&str] = &["CVPN-1945-Lower-max-mtu-for-DTLS-1.3-handshake-message.patch"];
const PATCHES: &[&str] = &[
"CVPN-1945-Lower-max-mtu-for-DTLS-1.3-handshake-message.patch",
"PR10492-DTLS13-backward-compatible.patch",
];
const OPTIONAL_FEATURES: &[&str] = &["aesccm", "dh", "opensslall", "opensslextra", "psk"];
const MACRO_FEATURES: &[(&str, &str)] = &[("ex_data", "HAVE_EX_DATA"), ("alpn", "HAVE_ALPN")];

Expand Down Expand Up @@ -322,6 +325,7 @@ fn build_wolfssl(wolfssl_src: &Path) -> PathBuf {
.cflag("-DUSE_CERT_BUFFERS_4096")
.cflag("-DUSE_CERT_BUFFERS_256")
.cflag("-DWOLFSSL_NO_SPHINCS")
.cflag("-DWOLFSSL_DTLS13_5_9_0_COMPAT")
.cflag("-DWOLFSSL_TLS13_MIDDLEBOX_COMPAT");

for feature in OPTIONAL_FEATURES {
Expand Down
296 changes: 296 additions & 0 deletions wolfssl-sys/patches/PR10492-DTLS13-backward-compatible.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
From ded6d604e05821aacccfeff6b6e6b294b0c42aba Mon Sep 17 00:00:00 2001
From: Marco Oliverio <marco@wolfssl.com>
Date: Mon, 18 May 2026 09:30:28 +0200
Subject: [PATCH 1/2] dtls: add compat flag for buggy pre 5.9.0 DTLSv1.3
clients

---
src/dtls.c | 10 ++++++++--
src/tls13.c | 15 +++++++++++----
2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/dtls.c b/src/dtls.c
index b79db10330e..a22b5768bb2 100644
--- a/src/dtls.c
+++ b/src/dtls.c
@@ -635,9 +635,8 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch)

XMEMSET(&cs, 0, sizeof(cs));

- /* We need to echo the session ID sent by the client */
if (ch->sessionId.size > ID_LEN) {
- /* Too large. We can't echo this. */
+ /* Too large */
ERROR_OUT(INVALID_PARAMETER, dtls13_cleanup);
}

@@ -861,9 +860,16 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch)
nonConstSSL->options.tls1_1 = 1;
nonConstSSL->options.tls1_3 = 1;

+#ifdef WOLFSSL_DTLS13_5_9_0_COMPAT
+ nonConstSSL->session->sessionIDSz = (byte)ch->sessionId.size;
+ if (ch->sessionId.size > 0)
+ XMEMCPY(nonConstSSL->session->sessionID, ch->sessionId.elements,
+ ch->sessionId.size);
+#else
/* 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;
+#endif
nonConstSSL->options.cipherSuite0 = cs.cipherSuite0;
nonConstSSL->options.cipherSuite = cs.cipherSuite;
nonConstSSL->extensions = parsedExts;
diff --git a/src/tls13.c b/src/tls13.c
index 658b742f5ed..dc09a179bf3 100644
--- a/src/tls13.c
+++ b/src/tls13.c
@@ -5775,7 +5775,14 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
) {
/* 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) {
+ int requireEmptyEcho = 1;
+#ifdef WOLFSSL_DTLS13_5_9_0_COMPAT
+ /* Compat: a wolfSSL <= 5.9.0 DTLS 1.3 server echoes the client's
+ * legacy_session_id; accept any echo. */
+ if (ssl->options.dtls)
+ requireEmptyEcho = 0;
+#endif
+ if (requireEmptyEcho && args->sessIdSz != 0) {
WOLFSSL_MSG("args->sessIdSz != 0");
WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER);
return INVALID_PARAMETER;
@@ -6979,7 +6986,7 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie)

/* Reconstruct the HelloRetryMessage for handshake hash. */
sessIdSz = ssl->session->sessionIDSz;
-#ifdef WOLFSSL_DTLS13
+#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS13_5_9_0_COMPAT)
/* RFC 9147 Section 5.3: DTLS 1.3 must use empty legacy_session_id. */
if (ssl->options.dtls)
sessIdSz = 0;
@@ -7459,7 +7466,7 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
if (sessIdSz + args->idx > helloSz)
ERROR_OUT(BUFFER_ERROR, exit_dch);

-#ifdef WOLFSSL_DTLS13
+#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS13_5_9_0_COMPAT)
/* 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. */
@@ -8064,7 +8071,7 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
WOLFSSL_BUFFER(ssl->arrays->serverRandom, RAN_LEN);
#endif

-#ifdef WOLFSSL_DTLS13
+#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS13_5_9_0_COMPAT)
if (ssl->options.dtls) {
/* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty
* legacy_session_id_echo. */

From baed98e35559c97dd78c06989622fd555a16172f Mon Sep 17 00:00:00 2001
From: Marco Oliverio <marco@wolfssl.com>
Date: Mon, 18 May 2026 09:31:13 +0200
Subject: [PATCH 2/2] test: dtls: add WOLFSSL_DTLS13_5_9_0_COMPAT related tests

---
tests/api/test_dtls.c | 141 +++++++++++++++++++++++++++++++++++++++---
tests/api/test_dtls.h | 4 +-
2 files changed, 135 insertions(+), 10 deletions(-)

diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c
index 7453357ba2b..a4a2603a1d8 100644
--- a/tests/api/test_dtls.c
+++ b/tests/api/test_dtls.c
@@ -2955,23 +2955,21 @@ int test_dtls13_no_session_id_echo(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
- defined(HAVE_SESSION_TICKET)
+ defined(HAVE_SESSION_TICKET) && defined(HAVE_ECC) && \
+ !defined(WOLFSSL_DTLS13_5_9_0_COMPAT)
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];
- /* Use traditional groups to avoid HRR from PQ key share mismatch */
- int groups[] = {
- WOLFSSL_ECC_SECP256R1,
- WOLFSSL_ECC_SECP384R1,
- };
+ /* Pin to SECP256R1 to avoid a PQ-induced key-share HRR */
+ int groups[] = { WOLFSSL_ECC_SECP256R1 };

/* 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(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

/* Read to process any NewSessionTicket */
@@ -3000,8 +2998,7 @@ 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);
+ ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS);
/* Disable HRR cookie so the server directly sends a ServerHello */
ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS);

@@ -3035,6 +3032,132 @@ int test_dtls13_no_session_id_echo(void)
return EXPECT_RESULT();
}

+/* Test that a server built with WOLFSSL_DTLS13_5_9_0_COMPAT echoes the
+ * client's legacy_session_id in both the direct ServerHello path and the
+ * stateless HRR path (which also exercises RestartHandshakeHashWithCookie). */
+int test_dtls13_5_9_0_compat(void)
+{
+ EXPECT_DECLS;
+#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
+ defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_DTLS13_5_9_0_COMPAT) && \
+ defined(HAVE_ECC)
+ 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];
+ /* Pin to SECP256R1 to avoid a PQ-induced key-share HRR */
+ int groups[] = { WOLFSSL_ECC_SECP256R1 };
+ /* RFC 8446 Section 4.1.3: an HRR is a ServerHello carrying this magic
+ * random. Used to assert sub-test 1 is a real ServerHello, not an HRR. */
+ static const byte hrrRandom[RAN_LEN] = {
+ 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11,
+ 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
+ 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E,
+ 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C
+ };
+
+ /* --- initial connection: get a real session to carry the session ID --- */
+ 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, 1), WOLFSSL_SUCCESS);
+ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
+
+ /* drain any NewSessionTicket before calling get1_session */
+ 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));
+
+ /* Force a non-zero session ID — simulates a wolfSSL <=v5.9.0 client that
+ * mistakenly sends 32 bytes as legacy_session_id in DTLS 1.3. */
+ if (sess != NULL && 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;
+
+ /* --- sub-test 1: direct ServerHello (HRR cookie disabled) ---
+ * Exercises DoTls13ClientHello (change 1) and
+ * SendTls13ServerHello (change 2). */
+ 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);
+ ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS);
+ ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS);
+
+ /* Client sends CH1 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 CH1 and sends ServerHello */
+ ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1);
+ ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
+
+ /* Verify that the ServerHello on the wire echoes the session ID.
+ * Layout: DTLS Record Header (13) + DTLS Handshake Header (12) +
+ * ProtocolVersion (2) + Random (32) = byte 59 for
+ * legacy_session_id_echo length. */
+ ExpectIntGE(test_ctx.c_len, 60);
+ ExpectIntEQ(test_ctx.c_buff[0], handshake);
+ ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], server_hello);
+ /* Confirm it is a real ServerHello, not an HRR (also encoded as a
+ * ServerHello but bearing the HelloRetryRequest magic random). */
+ ExpectIntNE(XMEMCMP(&test_ctx.c_buff[DTLS_RECORD_HEADER_SZ +
+ DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN], hrrRandom, RAN_LEN), 0);
+ ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ +
+ DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN + RAN_LEN], ID_LEN);
+
+ /* Complete the handshake — Finished MAC validates the transcript */
+ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
+
+ 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;
+
+ /* --- sub-test 2: stateless HRR (HRR cookie enabled by default) ---
+ * Exercises SendStatelessReplyDtls13 (change 4) and
+ * RestartHandshakeHashWithCookie (change 3). */
+ 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);
+ ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS);
+
+ /* Client sends CH1 */
+ ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1);
+ ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
+
+ /* Server sends stateless HRR (SendStatelessReplyDtls13) */
+ ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1);
+ ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
+
+ /* Verify the HRR echoes the session ID at the same wire offset */
+ 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], ID_LEN);
+
+ /* Complete the handshake — Finished MAC validates RestartHandshakeHashWithCookie */
+ 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();
+}
+
/* Test that a DTLS 1.3 handshake with an oversized certificate chain does
* not crash or cause out-of-bounds access in SendTls13Certificate. */
int test_dtls13_oversized_cert_chain(void)
diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h
index ee399fbe989..96e524b5372 100644
--- a/tests/api/test_dtls.h
+++ b/tests/api/test_dtls.h
@@ -53,6 +53,7 @@ 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);
+int test_dtls13_5_9_0_compat(void);
int test_dtls13_oversized_cert_chain(void);

#define TEST_DTLS_DECLS \
@@ -88,4 +89,5 @@ int test_dtls13_oversized_cert_chain(void);
TEST_DECL_GROUP("dtls", test_dtls13_min_rtx_interval), \
TEST_DECL_GROUP("dtls", test_dtls13_no_session_id_echo), \
- TEST_DECL_GROUP("dtls", test_dtls13_oversized_cert_chain)
+ TEST_DECL_GROUP("dtls", test_dtls13_oversized_cert_chain), \
+ TEST_DECL_GROUP("dtls", test_dtls13_5_9_0_compat)
#endif /* TESTS_API_DTLS_H */
3 changes: 3 additions & 0 deletions wolfssl-sys/windows/user_settings-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@
#undef WOLFSSL_TLS13_MIDDLEBOX_COMPAT
#define WOLFSSL_TLS13_MIDDLEBOX_COMPAT

#undef WOLFSSL_DTLS13_5_9_0_COMPAT
#define WOLFSSL_DTLS13_5_9_0_COMPAT

#undef WOLFSSL_PQC_HYBRIDS
#define WOLFSSL_PQC_HYBRIDS

Expand Down
4 changes: 2 additions & 2 deletions wolfssl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wolfssl"
version = "7.0.0"
version = "7.1.0"
description = "High-level bindings for WolfSSL"
authors.workspace = true
license.workspace = true
Expand Down Expand Up @@ -29,7 +29,7 @@ unnecessary_safety_comment = "deny"
bytes = "1"
log = "0.4"
thiserror = "2.0"
wolfssl-sys = { path = "../wolfssl-sys", version = "4.0.0" }
wolfssl-sys = { path = "../wolfssl-sys", version = "4.1.0" }

[dev-dependencies]
async-trait = "0.1.73"
Expand Down
Loading