Skip to content

Commit bdb6035

Browse files
committed
6824 - Verify echoed legacy_session_id matches the value sent in DTLS 1.3
1 parent 7c76d55 commit bdb6035

5 files changed

Lines changed: 213 additions & 21 deletions

File tree

.github/workflows/os-check.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,11 @@ jobs:
441441
"name": "no-asn-strict-certs",
442442
"configure": ["--enable-testcert", "--enable-opensslextra", "--enable-certgen",
443443
"--enable-certreq", "--enable-certext", "--enable-ecc",
444-
"CPPFLAGS=-DWOLFSSL_NO_ASN_STRICT -DWOLFSSL_CUSTOM_OID -DHAVE_OID_ENCODING -DWOLFSSL_ALT_NAMES"]}
444+
"CPPFLAGS=-DWOLFSSL_NO_ASN_STRICT -DWOLFSSL_CUSTOM_OID -DHAVE_OID_ENCODING -DWOLFSSL_ALT_NAMES"]},
445+
{"comment": "wolfSSL <= 5.9.0 DTLS 1.3 legacy_session_id echo compatibility shim",
446+
"name": "dtls13-echo-legacy-session-id",
447+
"configure": ["--enable-dtls", "--enable-dtls13", "--enable-session-ticket",
448+
"--enable-ecc", "CPPFLAGS=-DWOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID"]}
445449
]
446450
EOF
447451
.github/scripts/parallel-make-check.py \

src/dtls.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -866,8 +866,8 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch)
866866
XMEMCPY(nonConstSSL->session->sessionID, ch->sessionId.elements,
867867
ch->sessionId.size);
868868
#else
869-
/* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty
870-
* legacy_session_id_echo. Don't copy the client's session ID. */
869+
/* RFC 9147 Section 5: "DTLS servers MUST NOT echo the
870+
* legacy_session_id value from the client." */
871871
nonConstSSL->session->sessionIDSz = 0;
872872
#endif
873873
nonConstSSL->options.cipherSuite0 = cs.cipherSuite0;

src/tls13.c

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,16 @@
4141
* WOLFSSL_EARLY_DATA_GROUP: Group early data with ClientHello default: off
4242
* WOLFSSL_POST_HANDSHAKE_AUTH: Post-handshake client auth default: off
4343
* WOLFSSL_TLS13_TICKET_BEFORE_FINISHED: Send NewSessionTicket default: off
44-
* before client Finished message
44+
* before client Finished message. Violates the
45+
* RFC 8446 Section 4.6.1 ordering requirement; for
46+
* interop with peers that expect the early ticket.
47+
* WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID: Echo legacy_session_id default: off
48+
* in DTLS 1.3. Violates RFC 9147 Section 5 ("DTLS
49+
* servers MUST NOT echo the legacy_session_id
50+
* value from the client"). A server built with this
51+
* option echoes the session ID, which a compliant
52+
* client rejects, so enable it only where the peer
53+
* is wolfSSL <= 5.9.0 or shares this option.
4554
* WOLFSSL_NO_CLIENT_AUTH: Disable TLS 1.3 client authentication default: off
4655
* WOLFSSL_NO_CLIENT_CERT_ERROR: Require client certificate default: off
4756
* WOLFSSL_CERT_SETUP_CB: Certificate setup callback default: off
@@ -5854,12 +5863,12 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
58545863
|| ssl->options.dtls
58555864
#endif
58565865
) {
5857-
/* RFC 9147 Section 5.3 / RFC 9001 Section 8.4: DTLS 1.3 and QUIC
5866+
/* RFC 9147 Section 5 / RFC 9001 Section 8.4: DTLS 1.3 and QUIC
58585867
* ServerHello must have empty legacy_session_id_echo. */
58595868
int requireEmptyEcho = 1;
58605869
#ifdef WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID
58615870
/* Compat: a wolfSSL <= 5.9.0 DTLS 1.3 server echoes the client's
5862-
* legacy_session_id; accept any echo. */
5871+
* legacy_session_id back instead of omitting it. */
58635872
if (ssl->options.dtls)
58645873
requireEmptyEcho = 0;
58655874
#endif
@@ -5868,6 +5877,19 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
58685877
WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER);
58695878
return INVALID_PARAMETER;
58705879
}
5880+
#ifdef WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID
5881+
/* An echoing wolfSSL <= 5.9.0 server must still send back what was
5882+
* sent (RFC 8446 Section 4.1.3), so don't accept an arbitrary value.
5883+
* An empty echo is the compliant server case and stays acceptable. */
5884+
if (!requireEmptyEcho && args->sessIdSz != 0 &&
5885+
(args->sessIdSz != ssl->session->sessionIDSz ||
5886+
XMEMCMP(ssl->session->sessionID, args->sessId,
5887+
args->sessIdSz) != 0)) {
5888+
WOLFSSL_MSG("Server sent different session id");
5889+
WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER);
5890+
return INVALID_PARAMETER;
5891+
}
5892+
#endif
58715893
}
58725894
else
58735895
#endif /* WOLFSSL_QUIC || WOLFSSL_DTLS13 */
@@ -7181,7 +7203,8 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie)
71817203
/* Reconstruct the HelloRetryMessage for handshake hash. */
71827204
sessIdSz = ssl->session->sessionIDSz;
71837205
#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID)
7184-
/* RFC 9147 Section 5.3: DTLS 1.3 must use empty legacy_session_id. */
7206+
/* RFC 9147 Section 5: a DTLS 1.3 server does not echo the session ID, so
7207+
* the reconstructed transcript must not carry one either. */
71857208
if (ssl->options.dtls)
71867209
sessIdSz = 0;
71877210
#endif
@@ -7662,9 +7685,9 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
76627685
ERROR_OUT(BUFFER_ERROR, exit_dch);
76637686

76647687
#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID)
7665-
/* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty
7666-
* legacy_session_id_echo. Don't store the client's value so it
7667-
* won't be echoed in SendTls13ServerHello. */
7688+
/* RFC 9147 Section 5: "DTLS servers MUST NOT echo the legacy_session_id
7689+
* value from the client." Don't store it so SendTls13ServerHello can't
7690+
* echo it. */
76687691
if (ssl->options.dtls) {
76697692
ssl->session->sessionIDSz = 0;
76707693
}
@@ -8271,8 +8294,8 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
82718294

82728295
#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID)
82738296
if (ssl->options.dtls) {
8274-
/* RFC 9147 Section 5.3: DTLS 1.3 ServerHello must have empty
8275-
* legacy_session_id_echo. */
8297+
/* RFC 9147 Section 5: "DTLS servers MUST NOT echo the
8298+
* legacy_session_id value from the client." */
82768299
output[idx++] = 0;
82778300
}
82788301
else

tests/api/test_dtls13.c

Lines changed: 169 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,19 @@ int test_dtls13_no_session_id_echo(void)
17401740
return EXPECT_RESULT();
17411741
}
17421742

1743+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
1744+
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && \
1745+
defined(HAVE_ECC)
1746+
/* RFC 8446 Section 4.1.3: an HRR is a ServerHello carrying this magic random.
1747+
* Used to assert a real ServerHello was captured, not an HRR. */
1748+
static const byte hrrRandom[RAN_LEN] = {
1749+
0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11,
1750+
0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
1751+
0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E,
1752+
0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C
1753+
};
1754+
#endif
1755+
17431756
/*-- 5_9_0_compat (test_dtls.c lines 3049,3170) ---*/
17441757
int test_dtls13_5_9_0_compat(void)
17451758
{
@@ -1754,14 +1767,6 @@ int test_dtls13_5_9_0_compat(void)
17541767
char readBuf[1];
17551768
/* Pin to SECP256R1 to avoid a PQ-induced key-share HRR */
17561769
int groups[] = { WOLFSSL_ECC_SECP256R1 };
1757-
/* RFC 8446 Section 4.1.3: an HRR is a ServerHello carrying this magic
1758-
* random. Used to assert sub-test 1 is a real ServerHello, not an HRR. */
1759-
static const byte hrrRandom[RAN_LEN] = {
1760-
0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11,
1761-
0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
1762-
0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E,
1763-
0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C
1764-
};
17651770

17661771
/* --- initial connection: get a real session to carry the session ID --- */
17671772
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
@@ -1863,3 +1868,159 @@ int test_dtls13_5_9_0_compat(void)
18631868
#endif
18641869
return EXPECT_RESULT();
18651870
}
1871+
1872+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
1873+
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && \
1874+
defined(HAVE_ECC)
1875+
/* Drive a compat-build resumption up to the point where the server's
1876+
* ServerHello is sitting in the client's inbound buffer, echoing the session
1877+
* ID the client sent. Returns 0 on success. */
1878+
static int compat_echo_setup(struct test_memio_ctx* test_ctx,
1879+
WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s,
1880+
WOLFSSL_SESSION** sess)
1881+
{
1882+
EXPECT_DECLS;
1883+
char readBuf[1];
1884+
int groups[] = { WOLFSSL_ECC_SECP256R1 };
1885+
int echoIdx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ +
1886+
OPAQUE16_LEN + RAN_LEN;
1887+
1888+
XMEMSET(test_ctx, 0, sizeof(*test_ctx));
1889+
ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s,
1890+
wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0);
1891+
ExpectIntEQ(wolfSSL_set_groups(*ssl_c, groups, 1), WOLFSSL_SUCCESS);
1892+
ExpectIntEQ(test_memio_do_handshake(*ssl_c, *ssl_s, 10, NULL), 0);
1893+
1894+
ExpectIntEQ(wolfSSL_read(*ssl_c, readBuf, sizeof(readBuf)), -1);
1895+
ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1896+
ExpectNotNull(*sess = wolfSSL_get1_session(*ssl_c));
1897+
1898+
/* A DTLS 1.3 client carries a ticket-derived session ID (SetTicket), so
1899+
* the ClientHello legacy_session_id is non-empty. */
1900+
ExpectIntEQ((*sess)->sessionIDSz, ID_LEN);
1901+
1902+
wolfSSL_free(*ssl_c); *ssl_c = NULL;
1903+
wolfSSL_free(*ssl_s); *ssl_s = NULL;
1904+
wolfSSL_CTX_free(*ctx_c); *ctx_c = NULL;
1905+
wolfSSL_CTX_free(*ctx_s); *ctx_s = NULL;
1906+
1907+
XMEMSET(test_ctx, 0, sizeof(*test_ctx));
1908+
ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s,
1909+
wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0);
1910+
ExpectIntEQ(wolfSSL_set_session(*ssl_c, *sess), WOLFSSL_SUCCESS);
1911+
ExpectIntEQ(wolfSSL_set_groups(*ssl_c, groups, 1), WOLFSSL_SUCCESS);
1912+
ExpectIntEQ(wolfSSL_disable_hrr_cookie(*ssl_s), WOLFSSL_SUCCESS);
1913+
1914+
ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1);
1915+
ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1916+
ExpectIntEQ(wolfSSL_negotiate(*ssl_s), -1);
1917+
ExpectIntEQ(wolfSSL_get_error(*ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
1918+
1919+
/* A real ServerHello (not an HRR) echoing the client's session ID. */
1920+
ExpectIntGE(test_ctx->c_len, echoIdx + 1 + ID_LEN);
1921+
ExpectIntEQ(test_ctx->c_buff[DTLS_RECORD_HEADER_SZ], server_hello);
1922+
ExpectIntNE(XMEMCMP(&test_ctx->c_buff[DTLS_RECORD_HEADER_SZ +
1923+
DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN], hrrRandom, RAN_LEN), 0);
1924+
ExpectIntEQ(test_ctx->c_buff[echoIdx], ID_LEN);
1925+
1926+
return EXPECT_RESULT() == TEST_SUCCESS ? 0 : -1;
1927+
}
1928+
#endif
1929+
1930+
/* The 5.9.0 compat path accepts an echoed legacy_session_id, but RFC 8446
1931+
* Section 4.1.3 still requires the echo to be the value the client sent, so a
1932+
* server echoing something else must be rejected. */
1933+
int test_dtls13_5_9_0_compat_bad_echo(void)
1934+
{
1935+
EXPECT_DECLS;
1936+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
1937+
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && \
1938+
defined(HAVE_ECC)
1939+
struct test_memio_ctx test_ctx;
1940+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1941+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1942+
WOLFSSL_SESSION *sess = NULL;
1943+
int echoIdx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ +
1944+
OPAQUE16_LEN + RAN_LEN;
1945+
1946+
ExpectIntEQ(compat_echo_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1947+
&sess), 0);
1948+
1949+
/* Corrupt the echo so it no longer matches what the client sent. */
1950+
if (EXPECT_SUCCESS())
1951+
test_ctx.c_buff[echoIdx + 1] ^= 0xFF;
1952+
1953+
ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1);
1954+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1),
1955+
WC_NO_ERR_TRACE(INVALID_PARAMETER));
1956+
1957+
wolfSSL_SESSION_free(sess);
1958+
wolfSSL_free(ssl_c);
1959+
wolfSSL_free(ssl_s);
1960+
wolfSSL_CTX_free(ctx_c);
1961+
wolfSSL_CTX_free(ctx_s);
1962+
#endif
1963+
return EXPECT_RESULT();
1964+
}
1965+
1966+
/* A compat-enabled client must still interoperate with an RFC 9147 compliant
1967+
* server, which omits the echo entirely. Rewrite the ServerHello to carry an
1968+
* empty legacy_session_id_echo and confirm the client does not reject it. */
1969+
int test_dtls13_5_9_0_compat_empty_echo(void)
1970+
{
1971+
EXPECT_DECLS;
1972+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \
1973+
defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && \
1974+
defined(HAVE_ECC)
1975+
struct test_memio_ctx test_ctx;
1976+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1977+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1978+
WOLFSSL_SESSION *sess = NULL;
1979+
int echoIdx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ +
1980+
OPAQUE16_LEN + RAN_LEN;
1981+
/* DTLS record header: ... | length(2) at the end. */
1982+
int recLenIdx = DTLS_RECORD_HEADER_SZ - OPAQUE16_LEN;
1983+
/* DtlsHandShakeHeader: type(1) | length(3) | seq(2) | fragOff(3) |
1984+
* fragLen(3). */
1985+
int hsLenIdx = DTLS_RECORD_HEADER_SZ + OPAQUE8_LEN;
1986+
int fragLenIdx = hsLenIdx + OPAQUE24_LEN + OPAQUE16_LEN + OPAQUE24_LEN;
1987+
word32 hsLen, fragLen;
1988+
word16 recLen;
1989+
1990+
ExpectIntEQ(compat_echo_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1991+
&sess), 0);
1992+
1993+
if (EXPECT_SUCCESS()) {
1994+
byte* b = test_ctx.c_buff;
1995+
1996+
/* Drop the ID_LEN echo bytes and shrink the three enclosing lengths:
1997+
* DTLS record length, handshake length, fragment length. */
1998+
XMEMMOVE(&b[echoIdx + 1], &b[echoIdx + 1 + ID_LEN],
1999+
(size_t)test_ctx.c_len - (size_t)(echoIdx + 1 + ID_LEN));
2000+
b[echoIdx] = 0;
2001+
test_ctx.c_len -= ID_LEN;
2002+
2003+
ato16(&b[recLenIdx], &recLen);
2004+
c16toa((word16)(recLen - ID_LEN), &b[recLenIdx]);
2005+
2006+
ato24(&b[hsLenIdx], &hsLen);
2007+
c32to24(hsLen - ID_LEN, &b[hsLenIdx]);
2008+
2009+
ato24(&b[fragLenIdx], &fragLen);
2010+
c32to24(fragLen - ID_LEN, &b[fragLenIdx]);
2011+
}
2012+
2013+
/* The rewrite breaks the transcript, so the handshake still fails - but it
2014+
* must not fail by rejecting the empty echo. */
2015+
ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1);
2016+
ExpectIntNE(wolfSSL_get_error(ssl_c, -1),
2017+
WC_NO_ERR_TRACE(INVALID_PARAMETER));
2018+
2019+
wolfSSL_SESSION_free(sess);
2020+
wolfSSL_free(ssl_c);
2021+
wolfSSL_free(ssl_s);
2022+
wolfSSL_CTX_free(ctx_c);
2023+
wolfSSL_CTX_free(ctx_s);
2024+
#endif
2025+
return EXPECT_RESULT();
2026+
}

tests/api/test_dtls13.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ int test_dtls_srtp(void);
5353
int test_dtls13_min_rtx_interval(void);
5454
int test_dtls13_no_session_id_echo(void);
5555
int test_dtls13_5_9_0_compat(void);
56+
int test_dtls13_5_9_0_compat_bad_echo(void);
57+
int test_dtls13_5_9_0_compat_empty_echo(void);
5658

5759
#define TEST_DTLS13_DECLS \
5860
TEST_DECL_GROUP("dtls13", test_dtls13_bad_epoch_ch), \
@@ -76,6 +78,8 @@ int test_dtls13_5_9_0_compat(void);
7678
TEST_DECL_GROUP("dtls13", test_dtls_srtp), \
7779
TEST_DECL_GROUP("dtls13", test_dtls13_min_rtx_interval), \
7880
TEST_DECL_GROUP("dtls13", test_dtls13_no_session_id_echo), \
79-
TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat)
81+
TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat), \
82+
TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat_bad_echo), \
83+
TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat_empty_echo)
8084

8185
#endif /* TESTS_API_DTLS13_H */

0 commit comments

Comments
 (0)