Skip to content

Commit d7bdfd3

Browse files
authored
Merge pull request #10349 from rizlik/dtls13_rtx_fixes
DTLS13: Fixes unnecessary client rtx and increase server robustness
2 parents 6942797 + 74570a2 commit d7bdfd3

3 files changed

Lines changed: 243 additions & 15 deletions

File tree

src/dtls13.c

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ static void Dtls13RtxRemoveCurAck(WOLFSSL* ssl)
897897
#endif
898898
}
899899

900-
static void Dtls13MaybeSaveClientHello(WOLFSSL* ssl)
900+
static void Dtls13SaveOrFlushClientHello(WOLFSSL* ssl)
901901
{
902902
Dtls13RtxRecord *r, **prev_next;
903903

@@ -906,15 +906,18 @@ static void Dtls13MaybeSaveClientHello(WOLFSSL* ssl)
906906

907907
if (ssl->options.side == WOLFSSL_CLIENT_END &&
908908
ssl->options.connectState >= CLIENT_HELLO_SENT &&
909-
ssl->options.connectState <= HELLO_AGAIN_REPLY &&
910-
ssl->options.downgrade && ssl->options.minDowngrade >= DTLSv1_2_MINOR) {
909+
ssl->options.connectState <= HELLO_AGAIN_REPLY) {
911910
while (r != NULL) {
912911
if (r->handshakeType == client_hello) {
913912
Dtls13RtxRecordUnlink(ssl, prev_next, r);
914-
XFREE(ssl->dtls13ClientHello, ssl->heap, DYNAMIC_TYPE_DTLS_MSG);
915-
ssl->dtls13ClientHello = r->data;
916-
ssl->dtls13ClientHelloSz = r->length;
917-
r->data = NULL;
913+
if (ssl->options.downgrade &&
914+
ssl->options.minDowngrade >= DTLSv1_2_MINOR) {
915+
XFREE(ssl->dtls13ClientHello, ssl->heap,
916+
DYNAMIC_TYPE_DTLS_MSG);
917+
ssl->dtls13ClientHello = r->data;
918+
ssl->dtls13ClientHelloSz = r->length;
919+
r->data = NULL;
920+
}
918921
Dtls13FreeRtxBufferRecord(ssl, r);
919922
return;
920923
}
@@ -934,7 +937,7 @@ static int Dtls13RtxMsgRecvd(WOLFSSL* ssl, enum HandShakeType hs,
934937
ssl->keys.dtls_expected_peer_handshake_number) {
935938

936939
if (hs == server_hello)
937-
Dtls13MaybeSaveClientHello(ssl);
940+
Dtls13SaveOrFlushClientHello(ssl);
938941

939942
/* In the handshake, receiving part of the next flight, acknowledge the
940943
* sent flight. */
@@ -1869,13 +1872,15 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size,
18691872
*processedSize = size;
18701873
return 0;
18711874
}
1872-
/* To be able to operate in stateless mode, we assume the ClientHello
1873-
* is in order and we use its Handshake Message number and Sequence
1874-
* Number for our Tx. */
1875-
ssl->keys.dtls_expected_peer_handshake_number =
1876-
ssl->keys.dtls_handshake_number =
1877-
ssl->keys.dtls_peer_handshake_number;
1878-
ssl->dtls13Epochs[0].nextSeqNumber = ssl->keys.curSeq;
1875+
if (!ssl->options.dtlsStateful) {
1876+
/* To be able to operate in stateless mode, we assume the
1877+
* ClientHello is in order and we use its Handshake Message number
1878+
* and Sequence Number for our Tx. */
1879+
ssl->keys.dtls_expected_peer_handshake_number =
1880+
ssl->keys.dtls_handshake_number =
1881+
ssl->keys.dtls_peer_handshake_number;
1882+
ssl->dtls13Epochs[0].nextSeqNumber = ssl->keys.curSeq;
1883+
}
18791884
}
18801885

18811886
if (idx + fragLength > size) {

tests/api/test_dtls.c

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,225 @@ int test_dtls_rtx_across_epoch_change(void)
18361836
return EXPECT_RESULT();
18371837
}
18381838

1839+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
1840+
defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS)
1841+
static int test_dtls13_get_message_seq(const char* msg, int msgSz,
1842+
word16* msgSeq)
1843+
{
1844+
int hsOff = DTLS_RECORD_HEADER_SZ;
1845+
1846+
if (msg == NULL || msgSeq == NULL ||
1847+
msgSz < DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ) {
1848+
return BAD_FUNC_ARG;
1849+
}
1850+
1851+
*msgSeq = ((word16)(byte)msg[hsOff + 4] << 8) |
1852+
(word16)(byte)msg[hsOff + 5];
1853+
1854+
return WOLFSSL_SUCCESS;
1855+
}
1856+
#endif
1857+
1858+
int test_dtls13_ch2_rtx_no_ch1(void)
1859+
{
1860+
EXPECT_DECLS;
1861+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
1862+
defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS)
1863+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1864+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1865+
struct test_memio_ctx test_ctx;
1866+
const char* msg = NULL;
1867+
int msgSz = 0;
1868+
word16 ch1Seq = 0;
1869+
int i;
1870+
int foundCh1Seq = 0;
1871+
1872+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1873+
1874+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1875+
wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method),
1876+
0);
1877+
1878+
/* To force HRR */
1879+
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS);
1880+
1881+
/* CH1 */
1882+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
1883+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1884+
ExpectIntEQ(test_memio_get_message(&test_ctx, 0, &msg, &msgSz, 0), 0);
1885+
ExpectIntGE(msgSz, DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ);
1886+
ExpectIntEQ(test_dtls13_get_message_seq(msg, msgSz, &ch1Seq),
1887+
WOLFSSL_SUCCESS);
1888+
1889+
/* HRR */
1890+
ExpectIntEQ(wolfSSL_accept(ssl_s), -1);
1891+
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
1892+
ExpectIntGT(test_ctx.c_msg_count, 0);
1893+
1894+
/* CH2 */
1895+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
1896+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1897+
ExpectIntGT(test_ctx.s_msg_count, 0);
1898+
1899+
/* Drop CH2 and trigger the client retransmission timeout. */
1900+
test_memio_clear_buffer(&test_ctx, 0);
1901+
if (wolfSSL_dtls13_use_quick_timeout(ssl_c))
1902+
ExpectIntEQ(wolfSSL_dtls_got_timeout(ssl_c), WOLFSSL_SUCCESS);
1903+
ExpectIntEQ(wolfSSL_dtls_got_timeout(ssl_c), WOLFSSL_SUCCESS);
1904+
ExpectIntGT(test_ctx.s_msg_count, 0);
1905+
1906+
for (i = 0; i < test_ctx.s_msg_count && EXPECT_SUCCESS(); i++) {
1907+
int hsOff = DTLS_RECORD_HEADER_SZ;
1908+
word16 msgSeq = 0;
1909+
1910+
ExpectIntEQ(test_memio_get_message(&test_ctx, 0, &msg, &msgSz, i), 0);
1911+
/* memio stores one DTLS record per message in this handshake path. */
1912+
if (msgSz >= DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ &&
1913+
(byte)msg[0] == handshake && msg[hsOff] == client_hello) {
1914+
ExpectIntEQ(test_dtls13_get_message_seq(msg, msgSz, &msgSeq),
1915+
WOLFSSL_SUCCESS);
1916+
if (msgSeq == ch1Seq)
1917+
foundCh1Seq = 1;
1918+
}
1919+
}
1920+
1921+
ExpectIntEQ(foundCh1Seq, 0);
1922+
1923+
wolfSSL_free(ssl_c);
1924+
wolfSSL_CTX_free(ctx_c);
1925+
wolfSSL_free(ssl_s);
1926+
wolfSSL_CTX_free(ctx_s);
1927+
#endif
1928+
return EXPECT_RESULT();
1929+
}
1930+
1931+
int test_dtls13_frag_ch2_with_ch1_rtx(void)
1932+
{
1933+
EXPECT_DECLS;
1934+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
1935+
defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS) && \
1936+
defined(WOLFSSL_DTLS_MTU) && defined(WOLFSSL_DTLS_CH_FRAG)
1937+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1938+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1939+
struct test_memio_ctx test_ctx;
1940+
char hrr[TEST_MEMIO_BUF_SZ];
1941+
int hrrSz = (int)sizeof(hrr);
1942+
char ch1Rtx[TEST_MEMIO_BUF_SZ];
1943+
int ch1RtxSz = (int)sizeof(ch1Rtx);
1944+
char ch2[TEST_MEMIO_BUF_SZ];
1945+
int ch2Sz = 0;
1946+
int ch2MsgCount = 0;
1947+
int ch2MsgSizes[TEST_MEMIO_MAX_MSGS] = {0};
1948+
/* The DTLS record sequence number occupies the last 8 bytes of the
1949+
* record header. */
1950+
int recordSeqOff = DTLS_RECORD_HEADER_SZ - 8;
1951+
int ch2Seq = 0;
1952+
int ch1RtxSeq = 0;
1953+
int off;
1954+
int i;
1955+
1956+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1957+
1958+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1959+
wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method),
1960+
0);
1961+
1962+
/* To force HRR */
1963+
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS);
1964+
ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS);
1965+
1966+
/* CH1 */
1967+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
1968+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1969+
1970+
/* HRR */
1971+
ExpectIntEQ(wolfSSL_accept(ssl_s), -1);
1972+
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
1973+
ExpectIntEQ(test_memio_copy_message(&test_ctx, 1, hrr, &hrrSz, 0), 0);
1974+
1975+
/* Drop HRR, trigger CH1 retransmission, copy and drop it */
1976+
test_memio_clear_buffer(&test_ctx, 1);
1977+
if (wolfSSL_dtls13_use_quick_timeout(ssl_c))
1978+
ExpectIntEQ(wolfSSL_dtls_got_timeout(ssl_c), WOLFSSL_SUCCESS);
1979+
ExpectIntEQ(wolfSSL_dtls_got_timeout(ssl_c), WOLFSSL_SUCCESS);
1980+
ExpectIntEQ(test_memio_copy_message(&test_ctx, 0, ch1Rtx, &ch1RtxSz, 0), 0);
1981+
test_memio_clear_buffer(&test_ctx, 0);
1982+
1983+
/* Force CH2 fragmentation. MTU must be small enough to fragment but large
1984+
* enough that the cookie extension lands in the first fragment, otherwise
1985+
* the server can't validate it statelessly and the test scenario (server
1986+
* stateful after frag 1) does not hold. With --enable-all (PQ groups in
1987+
* supported_groups), the cookie extension can sit ~400 bytes into CH2; 600
1988+
* gives margin while still producing multiple fragments (CH2 is ~2KB). */
1989+
ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, 600), WOLFSSL_SUCCESS);
1990+
1991+
/* Forward HRR and let the client create fragmented CH2 */
1992+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, hrr, hrrSz), 0);
1993+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
1994+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1995+
1996+
ExpectIntGT(test_ctx.s_msg_count, 1);
1997+
ExpectIntLE(test_ctx.s_msg_count, TEST_MEMIO_MAX_MSGS);
1998+
ExpectIntLE(test_ctx.s_len, (int)sizeof(ch2));
1999+
if (EXPECT_SUCCESS()) {
2000+
ch2Sz = test_ctx.s_len;
2001+
ch2MsgCount = test_ctx.s_msg_count;
2002+
XMEMCPY(ch2, test_ctx.s_buff, ch2Sz);
2003+
XMEMCPY(ch2MsgSizes, test_ctx.s_msg_sizes,
2004+
sizeof(ch2MsgSizes[0]) * (size_t)ch2MsgCount);
2005+
2006+
ch2Seq = ((byte)ch2[recordSeqOff + 4] << 8) |
2007+
(byte)ch2[recordSeqOff + 5];
2008+
ch1RtxSeq = ch2Seq + ch2MsgCount;
2009+
2010+
/* Synthesize a CH1 retransmission that can pass the replay window after
2011+
* the first CH2 fragment makes the server stateful. The handshake
2012+
* message_seq remains the original CH1 value; only the DTLS record
2013+
* sequence is moved past the fragmented CH2 flight */
2014+
ch1Rtx[recordSeqOff + 0] = 0;
2015+
ch1Rtx[recordSeqOff + 1] = 0;
2016+
ch1Rtx[recordSeqOff + 2] = 0;
2017+
ch1Rtx[recordSeqOff + 3] = 0;
2018+
ch1Rtx[recordSeqOff + 4] = (byte)(ch1RtxSeq >> 8);
2019+
ch1Rtx[recordSeqOff + 5] = (byte)ch1RtxSeq;
2020+
}
2021+
2022+
test_memio_clear_buffer(&test_ctx, 0);
2023+
2024+
/* Deliver CH2 first fragment only. Now server is stateful */
2025+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, ch2, ch2MsgSizes[0]), 0);
2026+
ExpectIntEQ(wolfSSL_accept(ssl_s), -1);
2027+
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
2028+
2029+
/* Deliver the retransmitted CH1 between CH2 fragments, it should be
2030+
* discarded as rtx */
2031+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, ch1Rtx, ch1RtxSz), 0);
2032+
ExpectIntEQ(wolfSSL_accept(ssl_s), -1);
2033+
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
2034+
test_memio_clear_buffer(&test_ctx, 1);
2035+
2036+
/* Deliver the rest of CH2 */
2037+
off = ch2MsgSizes[0];
2038+
for (i = 1; i < ch2MsgCount && EXPECT_SUCCESS(); i++) {
2039+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, ch2 + off,
2040+
ch2MsgSizes[i]), 0);
2041+
off += ch2MsgSizes[i];
2042+
}
2043+
2044+
/* Restore MTU so the client's input buffer can hold the full server
2045+
* flight (e.g. an SH carrying a hybrid PQC key share). */
2046+
ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, 1500), WOLFSSL_SUCCESS);
2047+
2048+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
2049+
2050+
wolfSSL_free(ssl_c);
2051+
wolfSSL_CTX_free(ctx_c);
2052+
wolfSSL_free(ssl_s);
2053+
wolfSSL_CTX_free(ctx_s);
2054+
#endif
2055+
return EXPECT_RESULT();
2056+
}
2057+
18392058
int test_dtls_drop_client_ack(void)
18402059
{
18412060
EXPECT_DECLS;

tests/api/test_dtls.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ int test_dtls13_short_read(void);
4141
int test_records_span_network_boundaries(void);
4242
int test_dtls_record_cross_boundaries(void);
4343
int test_dtls_rtx_across_epoch_change(void);
44+
int test_dtls13_ch2_rtx_no_ch1(void);
45+
int test_dtls13_frag_ch2_with_ch1_rtx(void);
4446
int test_dtls_drop_client_ack(void);
4547
int test_dtls_bogus_finished_epoch_zero(void);
4648
int test_dtls_replay(void);
@@ -75,6 +77,8 @@ int test_dtls13_oversized_cert_chain(void);
7577
TEST_DECL_GROUP("dtls", test_records_span_network_boundaries), \
7678
TEST_DECL_GROUP("dtls", test_dtls_record_cross_boundaries), \
7779
TEST_DECL_GROUP("dtls", test_dtls_rtx_across_epoch_change), \
80+
TEST_DECL_GROUP("dtls", test_dtls13_ch2_rtx_no_ch1), \
81+
TEST_DECL_GROUP("dtls", test_dtls13_frag_ch2_with_ch1_rtx), \
7882
TEST_DECL_GROUP("dtls", test_dtls_drop_client_ack), \
7983
TEST_DECL_GROUP("dtls", test_dtls_bogus_finished_epoch_zero), \
8084
TEST_DECL_GROUP("dtls", test_dtls_replay), \

0 commit comments

Comments
 (0)