Merge pull request #10492 from rizlik/legacy_session_id_bad_client

Add compatibility flag and tests for pre-5.9.0 DTLSv1.3 clients
This commit is contained in:
Sean Parkinson
2026-05-28 08:57:48 +10:00
committed by GitHub
5 changed files with 155 additions and 16 deletions
+1
View File
@@ -744,6 +744,7 @@ WOLFSSL_DRBG_SHA256
WOLFSSL_DTLS_DISALLOW_FUTURE
WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS
WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT
WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID
WOLFSSL_DUMP_MEMIO_STREAM
WOLFSSL_DUP_CERTPOL
WOLFSSL_EARLY_DATA_NO_ANTI_REPLAY
+8 -2
View File
@@ -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_ECHO_LEGACY_SESSION_ID
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;
+11 -4
View File
@@ -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_ECHO_LEGACY_SESSION_ID
/* 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;
@@ -6973,7 +6980,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_ECHO_LEGACY_SESSION_ID)
/* RFC 9147 Section 5.3: DTLS 1.3 must use empty legacy_session_id. */
if (ssl->options.dtls)
sessIdSz = 0;
@@ -7453,7 +7460,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_ECHO_LEGACY_SESSION_ID)
/* 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. */
@@ -8058,7 +8065,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_ECHO_LEGACY_SESSION_ID)
if (ssl->options.dtls) {
/* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty
* legacy_session_id_echo. */
+132 -9
View File
@@ -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_ECHO_LEGACY_SESSION_ID)
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_ECHO_LEGACY_SESSION_ID 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_ECHO_LEGACY_SESSION_ID) && \
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)
+3 -1
View File
@@ -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 */