From 3f33046f3a28da2b8e7f7505c14781ddf7a46245 Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Mon, 18 May 2026 09:30:28 +0200 Subject: [PATCH 1/4] dtls: add compat flag for buggy pre 5.9.0 DTLSv1.3 clients --- .wolfssl_known_macro_extras | 1 + src/dtls.c | 10 ++++++++-- src/tls13.c | 15 +++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 5e30c64b3b0..920e1626f7b 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -749,6 +749,7 @@ WOLFSSL_DRBG_SHA256 WOLFSSL_DTLS_DISALLOW_FUTURE WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT +WOLFSSL_DTLS13_5_9_0_COMPAT WOLFSSL_DUMP_MEMIO_STREAM WOLFSSL_DUP_CERTPOL WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY 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 6e39d959e26f7982d3cfc38e64520b2dba76c362 Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Mon, 18 May 2026 09:31:13 +0200 Subject: [PATCH 2/4] 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 @@ -55,6 +55,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); int test_dtls_set_session_min_downgrade(void); @@ -93,5 +94,6 @@ int test_dtls_set_session_min_downgrade(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_dtls_set_session_min_downgrade) + TEST_DECL_GROUP("dtls", test_dtls_set_session_min_downgrade), \ + TEST_DECL_GROUP("dtls", test_dtls13_5_9_0_compat) #endif /* TESTS_API_DTLS_H */ From 693d47178f373b7ea8307ccb7ed0a5bb04c1eed8 Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Tue, 19 May 2026 08:34:27 +0200 Subject: [PATCH 3/4] test_dtls: remove non-ASCII chars --- tests/api/test_dtls.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index a4a2603a1d8..febee9dfaa6 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -3070,7 +3070,7 @@ int test_dtls13_5_9_0_compat(void) ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); - /* Force a non-zero session ID — simulates a wolfSSL <=v5.9.0 client that + /* 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; @@ -3114,7 +3114,7 @@ int test_dtls13_5_9_0_compat(void) 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 */ + /* 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; @@ -3146,7 +3146,7 @@ int test_dtls13_5_9_0_compat(void) 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 */ + /* Complete the handshake - Finished MAC validates RestartHandshakeHashWithCookie */ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); wolfSSL_SESSION_free(sess); From 64a0f0d6fda047586dd127d9d195f1ddd307852d Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Tue, 19 May 2026 08:18:08 +0200 Subject: [PATCH 4/4] .wolfssl_known_macro_extra: add ONLY_AES and ONLY_SHA256 --- .wolfssl_known_macro_extras | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 920e1626f7b..5a404bcbdf8 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -969,8 +969,10 @@ WOLFSSL_XIL_MSG_NO_SLEEP WOLFSSL_ZEPHYR WOLF_ALLOW_BUILTIN WOLF_CRYPTO_CB_CMD +WOLF_CRYPTO_CB_ONLY_AES WOLF_CRYPTO_CB_ONLY_ECC WOLF_CRYPTO_CB_ONLY_RSA +WOLF_CRYPTO_CB_ONLY_SHA256 WOLF_CRYPTO_DEV WOLF_NO_TRAILING_ENUM_COMMAS WindowsCE