diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 5e30c64b3b..5a404bcbdf 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 @@ -968,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 diff --git a/src/dtls.c b/src/dtls.c index b79db10330..a22b5768bb 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 658b742f5e..dc09a179bf 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. */ diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 7453357ba2..febee9dfaa 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 ee399fbe98..96e524b537 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 */