Skip to content

Commit 7842bf3

Browse files
committed
Fix for DTLS1.3 HRR group handling
When a server uses a HRR to negotiate the key exchange group to use, the selected group is advertised in the HRR key share extension. Furthermore, this group is also stored in the Cookie that is sent to the client. When the server receives the second CH, the group used in the key share extension MUST be the one of the HRR. For stateless DTLS servers, the handling of this check had a bug. The key share group of the HRR is stored in the ssl->hrr_keyshare_group variable and is checked against the received key share of the second CH. However, in the stateless server case, another CH message may be received inbetween the two CH message of the desired client, potentially overwriting the ssl->hrr_keyshare_group variable. This then causes handshake failures when the ssl->hrr_keyshare_group variable contains another group than the second CH message of the desired client. To fix this, the following changes are conducted: 1. Disable the ssl->hrr_keyshare_group check for stateless DTLS 1.3 servers. As long as the server is stateless, CHs from multiple clients may be received that individually cause HRRs with different groups. For each of these clients, the HRR group is properly stored in the cookie. 2. When a valid cookie is received from the client, the server becomes stateful. In this case, we now parse the cookie for a stored HRR group in the RestartHandshakeHashWithCookie() method. If present, we restore the ssl->hrr_keyshare_group variable to this group to ensure the error checks succeed. 3. Move the check of ssl->hrr_keyshare_group of the the KeyShare extension parsing logic into the general TLS1.3 ClientHello parsing after extension handling. This ensures that the order of the cookie and key share extensions does not matter. A new test is added to check for this behavior.
1 parent 350706d commit 7842bf3

3 files changed

Lines changed: 155 additions & 15 deletions

File tree

src/tls.c

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10216,20 +10216,6 @@ int TLSX_KeyShare_Parse_ClientHello(const WOLFSSL* ssl,
1021610216
offset += ret;
1021710217
}
1021810218

10219-
if (ssl->hrr_keyshare_group != 0) {
10220-
/*
10221-
* https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8
10222-
* when sending the new ClientHello, the client MUST
10223-
* replace the original "key_share" extension with one containing only a
10224-
* new KeyShareEntry for the group indicated in the selected_group field
10225-
* of the triggering HelloRetryRequest
10226-
*/
10227-
if (seenGroupsCnt != 1 || seenGroups[0] != ssl->hrr_keyshare_group) {
10228-
WOLFSSL_ERROR_VERBOSE(BAD_KEY_SHARE_DATA);
10229-
return BAD_KEY_SHARE_DATA;
10230-
}
10231-
}
10232-
1023310219
return 0;
1023410220
}
1023510221

src/tls13.c

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6502,6 +6502,8 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie)
65026502
hrrIdx += 2;
65036503
c16toa(OPAQUE16_LEN, hrr + hrrIdx);
65046504
hrrIdx += 2;
6505+
/* Restore the HRR key share group from the cookie. */
6506+
ato16(cookieData + idx, &ssl->hrr_keyshare_group);
65056507
hrr[hrrIdx++] = cookieData[idx++];
65066508
hrr[hrrIdx++] = cookieData[idx++];
65076509
}
@@ -7015,6 +7017,29 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
70157017
}
70167018
#endif
70177019

7020+
#ifdef HAVE_SUPPORTED_CURVES
7021+
if (ssl->hrr_keyshare_group != 0) {
7022+
/*
7023+
* https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8
7024+
* when sending the new ClientHello, the client MUST
7025+
* replace the original "key_share" extension with one containing only
7026+
* a new KeyShareEntry for the group indicated in the selected_group
7027+
* field of the triggering HelloRetryRequest.
7028+
*/
7029+
TLSX* extension = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE);
7030+
if (extension != NULL) {
7031+
KeyShareEntry* kse = (KeyShareEntry*)extension->data;
7032+
/* Exactly one KeyShareEntry with the HRR group must be present. */
7033+
if (kse == NULL || kse->next != NULL ||
7034+
kse->group != ssl->hrr_keyshare_group) {
7035+
ERROR_OUT(BAD_KEY_SHARE_DATA, exit_dch);
7036+
}
7037+
}
7038+
else
7039+
ERROR_OUT(BAD_KEY_SHARE_DATA, exit_dch);
7040+
}
7041+
#endif
7042+
70187043
#if defined(HAVE_ECH)
70197044
/* hash clientHelloInner to hsHashesEch independently since it can't include
70207045
* the HRR */
@@ -7509,7 +7534,18 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
75097534
if (ret != 0)
75107535
return ret;
75117536

7512-
if (extMsgType == hello_retry_request) {
7537+
/* When we send a HRR, we store the selected key share group to later check
7538+
* that the client uses the same group in the second ClientHello.
7539+
*
7540+
* In case of stateless DTLS, we do not store the group, however, as it is
7541+
* already stored in the cookie that is sent to the client. We later recover
7542+
* the group from the cookie to prevent storing a state in a stateless
7543+
* server. */
7544+
if (extMsgType == hello_retry_request
7545+
#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE)
7546+
&& (!ssl->options.dtls || ssl->options.dtlsStateful)
7547+
#endif
7548+
) {
75137549
TLSX* ksExt = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE);
75147550
if (ksExt != NULL) {
75157551
KeyShareEntry* kse = (KeyShareEntry*)ksExt->data;

tests/api.c

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24180,6 +24180,12 @@ static word32 test_wolfSSL_dtls_stateless_HashWOLFSSL(const WOLFSSL* ssl)
2418024180
sslCopy.keys.dtls_peer_handshake_number = 0;
2418124181
XMEMSET(&sslCopy.alert_history, 0, sizeof(sslCopy.alert_history));
2418224182
sslCopy.hsHashes = NULL;
24183+
#if !defined(WOLFSSL_NO_CLIENT_AUTH) && \
24184+
((defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3)) || \
24185+
(defined(HAVE_ED25519) && !defined(NO_ED25519_CLIENT_AUTH)) || \
24186+
(defined(HAVE_ED448) && !defined(NO_ED448_CLIENT_AUTH)))
24187+
sslCopy.options.cacheMessages = 0;
24188+
#endif
2418324189
#ifdef WOLFSSL_ASYNC_IO
2418424190
#ifdef WOLFSSL_ASYNC_CRYPT
2418524191
sslCopy.asyncDev = NULL;
@@ -24317,11 +24323,122 @@ static int test_wolfSSL_dtls_stateless(void)
2431724323

2431824324
return TEST_SUCCESS;
2431924325
}
24326+
24327+
/* DTLS stateless API handling multiple CHs with different HRR groups */
24328+
static int test_wolfSSL_dtls_stateless_hrr_group(void)
24329+
{
24330+
EXPECT_DECLS;
24331+
#if defined(WOLFSSL_SEND_HRR_COOKIE)
24332+
size_t i;
24333+
word32 initHash;
24334+
struct {
24335+
method_provider client_meth;
24336+
method_provider server_meth;
24337+
} params[] = {
24338+
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_DTLS13)
24339+
{ wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method },
24340+
#endif
24341+
#if !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_DTLS)
24342+
{ wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method },
24343+
#endif
24344+
};
24345+
for (i = 0; i < XELEM_CNT(params) && !EXPECT_FAIL(); i++) {
24346+
WOLFSSL_CTX *ctx_s = NULL, *ctx_c = NULL;
24347+
WOLFSSL *ssl_s = NULL, *ssl_c = NULL, *ssl_c2 = NULL;
24348+
struct test_memio_ctx test_ctx;
24349+
int groups_1[] = {
24350+
WOLFSSL_ECC_SECP256R1,
24351+
WOLFSSL_ECC_SECP384R1,
24352+
WOLFSSL_ECC_SECP521R1
24353+
};
24354+
int groups_2[] = {
24355+
WOLFSSL_ECC_SECP384R1,
24356+
WOLFSSL_ECC_SECP521R1
24357+
};
24358+
char hrrBuf[1000];
24359+
int hrrSz = sizeof(hrrBuf);
24360+
24361+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
24362+
24363+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
24364+
params[i].client_meth, params[i].server_meth), 0);
24365+
24366+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c2, NULL,
24367+
params[i].client_meth, params[i].server_meth), 0);
24368+
24369+
24370+
wolfSSL_SetLoggingPrefix("server");
24371+
wolfSSL_dtls_set_using_nonblock(ssl_s, 1);
24372+
24373+
initHash = test_wolfSSL_dtls_stateless_HashWOLFSSL(ssl_s);
24374+
24375+
/* Set groups and disable key shares. This ensures that only the given
24376+
* groups are in the SupportedGroups extension and that an empty key
24377+
* share extension is sent in the initial ClientHello of each session.
24378+
* This triggers the server to send a HelloRetryRequest with the first
24379+
* group in the SupportedGroups extension selected. */
24380+
wolfSSL_SetLoggingPrefix("client1");
24381+
ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups_1, 3), WOLFSSL_SUCCESS);
24382+
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS);
24383+
24384+
wolfSSL_SetLoggingPrefix("client2");
24385+
ExpectIntEQ(wolfSSL_set_groups(ssl_c2, groups_2, 2), WOLFSSL_SUCCESS);
24386+
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c2), WOLFSSL_SUCCESS);
24387+
24388+
/* Start handshake, send first ClientHello */
24389+
wolfSSL_SetLoggingPrefix("client1");
24390+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
24391+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
24392+
24393+
/* Read first ClientHello, send HRR with WOLFSSL_ECC_SECP256R1 */
24394+
wolfSSL_SetLoggingPrefix("server");
24395+
ExpectIntEQ(wolfDTLS_accept_stateless(ssl_s), 0);
24396+
ExpectIntEQ(test_memio_copy_message(&test_ctx, 1, hrrBuf, &hrrSz, 0), 0);
24397+
ExpectIntGT(hrrSz, 0);
24398+
ExpectIntEQ(initHash, test_wolfSSL_dtls_stateless_HashWOLFSSL(ssl_s));
24399+
test_memio_clear_buffer(&test_ctx, 1);
24400+
24401+
/* Send second ClientHello */
24402+
wolfSSL_SetLoggingPrefix("client2");
24403+
ExpectIntEQ(wolfSSL_connect(ssl_c2), -1);
24404+
ExpectIntEQ(wolfSSL_get_error(ssl_c2, -1), WOLFSSL_ERROR_WANT_READ);
24405+
24406+
/* Read second ClientHello, send HRR now with WOLFSSL_ECC_SECP384R1 */
24407+
wolfSSL_SetLoggingPrefix("server");
24408+
ExpectIntEQ(wolfDTLS_accept_stateless(ssl_s), 0);
24409+
ExpectIntEQ(initHash, test_wolfSSL_dtls_stateless_HashWOLFSSL(ssl_s));
24410+
test_memio_clear_buffer(&test_ctx, 1);
24411+
24412+
/* Complete first handshake with WOLFSSL_ECC_SECP256R1 */
24413+
wolfSSL_SetLoggingPrefix("client1");
24414+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, hrrBuf, hrrSz), 0);
24415+
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
24416+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
24417+
24418+
wolfSSL_SetLoggingPrefix("server");
24419+
ExpectIntEQ(wolfDTLS_accept_stateless(ssl_s), WOLFSSL_SUCCESS);
24420+
24421+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
24422+
24423+
wolfSSL_free(ssl_s);
24424+
wolfSSL_free(ssl_c);
24425+
wolfSSL_free(ssl_c2);
24426+
wolfSSL_CTX_free(ctx_s);
24427+
wolfSSL_CTX_free(ctx_c);
24428+
}
24429+
#endif /* WOLFSSL_SEND_HRR_COOKIE */
24430+
return EXPECT_RESULT();
24431+
}
2432024432
#else
2432124433
static int test_wolfSSL_dtls_stateless(void)
2432224434
{
2432324435
return TEST_SKIPPED;
2432424436
}
24437+
24438+
static int test_wolfSSL_dtls_stateless_hrr_group(void)
24439+
{
24440+
return TEST_SKIPPED;
24441+
}
2432524442
#endif /* WOLFSSL_DTLS13 && WOLFSSL_SEND_HRR_COOKIE &&
2432624443
* HAVE_IO_TESTS_DEPENDENCIES && !SINGLE_THREADED */
2432724444

@@ -33186,6 +33303,7 @@ TEST_CASE testCases[] = {
3318633303
TEST_DECL(test_wolfSSL_dtls_bad_record),
3318733304
/* Uses Assert in handshake callback. */
3318833305
TEST_DECL(test_wolfSSL_dtls_stateless),
33306+
TEST_DECL(test_wolfSSL_dtls_stateless_hrr_group),
3318933307
TEST_DECL(test_generate_cookie),
3319033308

3319133309
#ifndef NO_BIO

0 commit comments

Comments
 (0)