Skip to content
Open
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
1 change: 1 addition & 0 deletions .wolfssl_known_macro_extras
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions src/dtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down
15 changes: 11 additions & 4 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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. */
Expand Down
141 changes: 132 additions & 9 deletions tests/api/test_dtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion tests/api/test_dtls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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 */
Loading