Skip to content

Commit f0bfcc5

Browse files
Merge pull request #6748 from julek-wolfssl/dtls13-frag-ch2
DTLS 1.3: allow fragmenting the second ClientHello message
2 parents 5e4baf8 + ca73a31 commit f0bfcc5

16 files changed

Lines changed: 670 additions & 157 deletions

File tree

.github/workflows/curl.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ on:
44
workflow_call:
55

66
jobs:
7-
build:
7+
build-and-test:
88
runs-on: ubuntu-latest
99
# This should be a safe limit for the tests to run.
10-
timeout-minutes: 14
10+
timeout-minutes: 25
1111
steps:
1212
- name: Install test dependencies
1313
run: |
1414
sudo apt-get update
1515
sudo apt-get install nghttp2
16+
sudo pip install impacket
1617
1718
- name: Build wolfSSL
1819
uses: wolfSSL/actions-build-autotools-project@v1
@@ -21,10 +22,14 @@ jobs:
2122
configure: --enable-curl
2223
install: true
2324

24-
- name: Build and test curl
25+
- name: Build curl
2526
uses: wolfSSL/actions-build-autotools-project@v1
2627
with:
2728
repository: curl/curl
2829
path: curl
2930
configure: --with-wolfssl=$GITHUB_WORKSPACE/build-dir
30-
check: true
31+
check: false
32+
33+
- name: Test curl
34+
working-directory: curl
35+
run: make -j test-ci

Docker/OpenWrt/runTests.sh

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/bin/sh
22

33
runCMD() { # usage: runCMD "<command>" "<retVal>"
4-
eval $1 >/dev/null 2>&1
4+
TMP_FILE=$(mktemp)
5+
eval $1 > $TMP_FILE 2>&1
56
RETVAL=$?
67
if [ "$RETVAL" != "$2" ]; then
7-
echo "Command ($1) returned ${RETVAL}, but expected $2. Rerunning with output to terminal:"
8-
eval $1
8+
echo "Command ($1) returned ${RETVAL}, but expected $2. Error output:"
9+
cat $TMP_FILE
910
exit 1
1011
fi
1112
}
@@ -16,11 +17,11 @@ runCMD "ldd /lib/libustream-ssl.so" 0
1617
# Remove after fixed upstream.
1718
runCMD "sed '\/src\/gz openwrt_kmods https:\/\/downloads.openwrt.org\/releases\/21.02-SNAPSHOT\/targets\/x86\/64\/kmods\/5.4.238-1-5a722da41bc36de95a7195be6fce1b45/s//#&/' -i /etc/opkg/distfeeds.conf" 0
1819
runCMD "opkg update" 0
19-
runCMD "uclient-fetch -O /dev/null 'https://letsencrypt.org'" 0
20+
runCMD "uclient-fetch 'https://letsencrypt.org'" 0
2021
# Negative tests
21-
runCMD "uclient-fetch --ca-certificate=/dev/null -O /dev/null 'https://letsencrypt.org'" 5
22-
runCMD "uclient-fetch -O /dev/null 'https://self-signed.badssl.com/'" 5
23-
runCMD "uclient-fetch -O /dev/null 'https://untrusted-root.badssl.com/'" 5
24-
runCMD "uclient-fetch -O /dev/null 'https://expired.badssl.com/'" 5
22+
runCMD "uclient-fetch --ca-certificate=/dev/null 'https://letsencrypt.org'" 5
23+
runCMD "uclient-fetch 'https://self-signed.badssl.com/'" 5
24+
runCMD "uclient-fetch 'https://untrusted-root.badssl.com/'" 5
25+
runCMD "uclient-fetch 'https://expired.badssl.com/'" 5
2526

2627
echo "All tests passed."

configure.ac

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4516,6 +4516,21 @@ then
45164516
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DTLS_CID"
45174517
fi
45184518

4519+
# DTLS 1.3 Fragment Second ClientHello
4520+
AC_ARG_ENABLE([dtls-frag-ch],
4521+
[AS_HELP_STRING([--enable-dtls-frag-ch],[Enable wolfSSL DTLS 1.3 ClientHello fragmenting (default: disabled)])],
4522+
[ ENABLED_DTLS_CH_FRAG=$enableval ],
4523+
[ ENABLED_DTLS_CH_FRAG=no ]
4524+
)
4525+
if test "x$ENABLED_DTLS_CH_FRAG" = "xyes"
4526+
then
4527+
if test "x$ENABLED_DTLS13" != "xyes"
4528+
then
4529+
AC_MSG_ERROR([You need to enable DTLSv1.3 to use DTLS ClientHello fragmenting])
4530+
fi
4531+
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DTLS_CH_FRAG"
4532+
fi
4533+
45194534
# CODING
45204535
AC_ARG_ENABLE([coding],
45214536
[AS_HELP_STRING([--enable-coding],[Enable Coding base 16/64 (default: enabled)])],

examples/server/server.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3322,6 +3322,11 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args)
33223322
}
33233323
#endif /* WOLFSSL_DTLS_CID */
33243324

3325+
#ifdef WOLFSSL_DTLS_CH_FRAG
3326+
if (doDTLS)
3327+
wolfSSL_dtls13_allow_ch_frag(ssl, 1);
3328+
#endif
3329+
33253330
#ifndef WOLFSSL_CALLBACKS
33263331
if (nonBlocking) {
33273332
#ifdef WOLFSSL_DTLS

src/dtls.c

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
* will consume less bandwidth (one ClientHello and one HelloVerifyRequest
2727
* less). On the other hand, if a valid SessionID is collected, forged
2828
* clientHello messages will consume resources on the server.
29+
* WOLFSSL_DTLS_CH_FRAG
30+
* Allow a server to process a fragmented second/verified (one containing a
31+
* valid cookie response) ClientHello message. The first/unverified (one
32+
* without a cookie extension) ClientHello MUST be unfragmented so that the
33+
* DTLS server can process it statelessly. This is only implemented for
34+
* DTLS 1.3. The user MUST call wolfSSL_dtls13_allow_ch_frag() on the server
35+
* to explicitly enable this during runtime.
2936
*/
3037

3138
#ifdef HAVE_CONFIG_H
@@ -263,10 +270,13 @@ static int CheckDtlsCookie(const WOLFSSL* ssl, WolfSSL_CH* ch,
263270
return ret;
264271
}
265272

266-
static int ParseClientHello(const byte* input, word32 helloSz, WolfSSL_CH* ch)
273+
static int ParseClientHello(const byte* input, word32 helloSz, WolfSSL_CH* ch,
274+
byte isFirstCHFrag)
267275
{
268276
word32 idx = 0;
269277

278+
(void)isFirstCHFrag;
279+
270280
/* protocol version, random and session id length check */
271281
if (OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN > helloSz)
272282
return BUFFER_ERROR;
@@ -288,9 +298,20 @@ static int ParseClientHello(const byte* input, word32 helloSz, WolfSSL_CH* ch)
288298
idx += ReadVector8(input + idx, &ch->compression);
289299
if (idx < helloSz - OPAQUE16_LEN) {
290300
/* Extensions are optional */
301+
#ifdef WOLFSSL_DTLS_CH_FRAG
302+
word32 extStart = idx + OPAQUE16_LEN;
303+
#endif
291304
idx += ReadVector16(input + idx, &ch->extension);
292-
if (idx > helloSz)
293-
return BUFFER_ERROR;
305+
if (idx > helloSz) {
306+
#ifdef WOLFSSL_DTLS_CH_FRAG
307+
idx = helloSz;
308+
/* Allow incomplete extensions if we are parsing a fragment */
309+
if (isFirstCHFrag && extStart < helloSz)
310+
ch->extension.size = helloSz - extStart;
311+
else
312+
#endif
313+
return BUFFER_ERROR;
314+
}
294315
}
295316
if (idx != helloSz)
296317
return BUFFER_ERROR;
@@ -860,17 +881,30 @@ static int ClientHelloSanityCheck(WolfSSL_CH* ch, byte isTls13)
860881
return 0;
861882
}
862883

863-
int DoClientHelloStateless(WOLFSSL* ssl, const byte* input,
864-
word32* inOutIdx, word32 helloSz)
884+
int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz,
885+
byte isFirstCHFrag, byte* tls13)
865886
{
866887
int ret;
867888
WolfSSL_CH ch;
868889
byte isTls13 = 0;
869890

891+
WOLFSSL_ENTER("DoClientHelloStateless");
892+
if (isFirstCHFrag) {
893+
#ifdef WOLFSSL_DTLS_CH_FRAG
894+
WOLFSSL_MSG("\tProcessing fragmented ClientHello");
895+
#else
896+
WOLFSSL_MSG("\tProcessing fragmented ClientHello but "
897+
"WOLFSSL_DTLS_CH_FRAG is not defined. This should not happen.");
898+
return BAD_STATE_E;
899+
#endif
900+
}
901+
if (tls13 != NULL)
902+
*tls13 = 0;
903+
870904
XMEMSET(&ch, 0, sizeof(ch));
871905

872906
ssl->options.dtlsStateful = 0;
873-
ret = ParseClientHello(input + *inOutIdx, helloSz, &ch);
907+
ret = ParseClientHello(input, helloSz, &ch, isFirstCHFrag);
874908
if (ret != 0)
875909
return ret;
876910

@@ -879,6 +913,8 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input,
879913
ret = TlsCheckSupportedVersion(ssl, &ch, &isTls13);
880914
if (ret != 0)
881915
return ret;
916+
if (tls13 != NULL)
917+
*tls13 = isTls13;
882918
if (isTls13) {
883919
int tlsxFound;
884920
ret = FindExtByType(&ch.cookieExt, TLSX_COOKIE, ch.extension,
@@ -894,7 +930,7 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input,
894930
return ret;
895931

896932
#ifdef WOLFSSL_DTLS_NO_HVR_ON_RESUME
897-
if (!isTls13) {
933+
if (!isTls13 && !isFirstCHFrag) {
898934
int resume = FALSE;
899935
ret = TlsResumptionIsValid(ssl, &ch, &resume);
900936
if (ret != 0)
@@ -907,7 +943,13 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input,
907943
#endif
908944

909945
if (ch.cookie.size == 0 && ch.cookieExt.size == 0) {
910-
ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13);
946+
#ifdef WOLFSSL_DTLS_CH_FRAG
947+
/* Don't send anything here when processing fragment */
948+
if (isFirstCHFrag)
949+
ret = COOKIE_ERROR;
950+
else
951+
#endif
952+
ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13);
911953
}
912954
else {
913955
byte cookieGood;
@@ -921,11 +963,25 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input,
921963
if (isTls13)
922964
ret = INVALID_PARAMETER;
923965
else
966+
#endif
967+
#ifdef WOLFSSL_DTLS_CH_FRAG
968+
/* Don't send anything here when processing fragment */
969+
if (isFirstCHFrag)
970+
ret = COOKIE_ERROR;
971+
else
924972
#endif
925973
ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13);
926974
}
927-
else
975+
else {
928976
ssl->options.dtlsStateful = 1;
977+
/* Update the window now that we enter the stateful parsing */
978+
#ifdef WOLFSSL_DTLS13
979+
if (isTls13)
980+
ret = Dtls13UpdateWindowRecordRecvd(ssl);
981+
else
982+
#endif
983+
DtlsUpdateWindow(ssl);
984+
}
929985
}
930986

931987
return ret;

src/dtls13.c

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,6 +1573,19 @@ static int Dtls13RtxSendBuffered(WOLFSSL* ssl)
15731573
return 0;
15741574
}
15751575

1576+
static int Dtls13AcceptFragmented(WOLFSSL *ssl, enum HandShakeType type)
1577+
{
1578+
if (IsEncryptionOn(ssl, 0))
1579+
return 1;
1580+
if (ssl->options.side == WOLFSSL_CLIENT_END && type == server_hello)
1581+
return 1;
1582+
#ifdef WOLFSSL_DTLS_CH_FRAG
1583+
if (ssl->options.side == WOLFSSL_SERVER_END && type == client_hello &&
1584+
ssl->options.dtls13ChFrag && ssl->options.dtlsStateful)
1585+
return 1;
1586+
#endif
1587+
return 0;
1588+
}
15761589
/**
15771590
* Dtls13HandshakeRecv() - process an handshake message. Deal with
15781591
fragmentation if needed
@@ -1646,13 +1659,35 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size,
16461659
isFirst = fragOff == 0;
16471660
isComplete = isFirst && fragLength == messageLength;
16481661

1649-
if (!isComplete && !IsEncryptionOn(ssl, 0)) {
1662+
if (!isComplete && !Dtls13AcceptFragmented(ssl, handshakeType)) {
1663+
#ifdef WOLFSSL_DTLS_CH_FRAG
1664+
byte tls13 = 0;
1665+
/* check if the first CH fragment contains a valid cookie */
1666+
if (ssl->options.dtls13ChFrag && !ssl->options.dtlsStateful &&
1667+
isFirst && handshakeType == client_hello &&
1668+
DoClientHelloStateless(ssl, input + idx, fragLength, 1, &tls13)
1669+
== 0 && tls13) {
1670+
/* We can save this message and continue as stateful. */
1671+
if (ssl->chGoodCb != NULL) {
1672+
int cbret = ssl->chGoodCb(ssl, ssl->chGoodCtx);
1673+
if (cbret < 0) {
1674+
ssl->error = cbret;
1675+
WOLFSSL_MSG("ClientHello Good Cb don't continue error");
1676+
return WOLFSSL_FATAL_ERROR;
1677+
}
1678+
}
1679+
WOLFSSL_MSG("ClientHello fragment verified");
1680+
}
1681+
else
1682+
#endif
1683+
{
16501684
#ifdef WOLFSSL_DEBUG_TLS
1651-
WOLFSSL_MSG("DTLS1.3 not accepting fragmented plaintext message");
1685+
WOLFSSL_MSG("DTLS1.3 not accepting fragmented plaintext message");
16521686
#endif /* WOLFSSL_DEBUG_TLS */
1653-
/* ignore the message */
1654-
*processedSize = idx + fragLength + ssl->keys.padSz;
1655-
return 0;
1687+
/* ignore the message */
1688+
*processedSize = idx + fragLength + ssl->keys.padSz;
1689+
return 0;
1690+
}
16561691
}
16571692

16581693
usingAsyncCrypto = ssl->devId != INVALID_DEVID;
@@ -2369,7 +2404,11 @@ static int Dtls13WriteAckMessage(WOLFSSL* ssl,
23692404
c16toa(msgSz, ackMessage);
23702405
ackMessage += OPAQUE16_LEN;
23712406

2407+
WOLFSSL_MSG("write ack records");
2408+
23722409
while (recordNumberList != NULL) {
2410+
WOLFSSL_MSG_EX("epoch %d seq %d", recordNumberList->epoch,
2411+
recordNumberList->seq);
23732412
c64toa(&recordNumberList->epoch, ackMessage);
23742413
ackMessage += OPAQUE64_LEN;
23752414
c64toa(&recordNumberList->seq, ackMessage);
@@ -2561,10 +2600,13 @@ int DoDtls13Ack(WOLFSSL* ssl, const byte* input, word32 inputSize,
25612600
if (length % (DTLS13_RN_SIZE) != 0)
25622601
return PARSE_ERROR;
25632602

2603+
WOLFSSL_MSG("read ack records");
2604+
25642605
ackMessage = input + OPAQUE16_LEN;
25652606
for (i = 0; i < length; i += DTLS13_RN_SIZE) {
25662607
ato64(ackMessage + i, &epoch);
25672608
ato64(ackMessage + i + OPAQUE64_LEN, &seq);
2609+
WOLFSSL_MSG_EX("epoch %d seq %d", epoch, seq);
25682610
Dtls13RtxRemoveRecord(ssl, epoch, seq);
25692611
}
25702612

@@ -2635,28 +2677,20 @@ int SendDtls13Ack(WOLFSSL* ssl)
26352677
if (ret != 0)
26362678
return ret;
26372679

2638-
if (w64IsZero(ssl->dtls13EncryptEpoch->epochNumber)) {
2639-
2640-
ret = Dtls13WriteAckMessage(ssl, ssl->dtls13Rtx.seenRecords, &length);
2641-
if (ret != 0)
2642-
return ret;
2680+
ret = Dtls13WriteAckMessage(ssl, ssl->dtls13Rtx.seenRecords, &length);
2681+
if (ret != 0)
2682+
return ret;
26432683

2644-
output = GetOutputBuffer(ssl);
2684+
output = GetOutputBuffer(ssl);
26452685

2686+
if (w64IsZero(ssl->dtls13EncryptEpoch->epochNumber)) {
26462687
ret = Dtls13RlAddPlaintextHeader(ssl, output, ack, (word16)length);
26472688
if (ret != 0)
26482689
return ret;
26492690

26502691
ssl->buffers.outputBuffer.length += length + DTLS_RECORD_HEADER_SZ;
26512692
}
26522693
else {
2653-
2654-
ret = Dtls13WriteAckMessage(ssl, ssl->dtls13Rtx.seenRecords, &length);
2655-
if (ret != 0)
2656-
return ret;
2657-
2658-
output = GetOutputBuffer(ssl);
2659-
26602694
outputSize = ssl->buffers.outputBuffer.bufferSize -
26612695
ssl->buffers.outputBuffer.idx -
26622696
ssl->buffers.outputBuffer.length;
@@ -2797,4 +2831,16 @@ int Dtls13CheckAEADFailLimit(WOLFSSL* ssl)
27972831
}
27982832
#endif
27992833

2834+
#ifdef WOLFSSL_DTLS_CH_FRAG
2835+
int wolfSSL_dtls13_allow_ch_frag(WOLFSSL *ssl, int enabled)
2836+
{
2837+
if (ssl->options.side == WOLFSSL_CLIENT_END) {
2838+
return WOLFSSL_FAILURE;
2839+
}
2840+
ssl->options.dtls13ChFrag = !!enabled;
2841+
return WOLFSSL_SUCCESS;
2842+
}
2843+
#endif
2844+
2845+
28002846
#endif /* WOLFSSL_DTLS13 */

0 commit comments

Comments
 (0)