diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 1bca8d59f..e63918860 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -46,6 +46,8 @@ jobs: '--enable-ascon CPPFLAGS=-DWOLFSSL_ASCON_UNROLL --enable-experimental', '--enable-all CPPFLAGS=''-DNO_AES_192 -DNO_AES_256'' ', '--enable-sniffer --enable-curve25519 --enable-curve448 --enable-enckeys CFLAGS=-DWOLFSSL_DH_EXTRA', + '--enable-dtls --enable-dtls13 --enable-dtls-frag-ch + --enable-dtls-mtu CPPFLAGS=-DWOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS', ] name: make check if: github.repository_owner == 'wolfssl' diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index dc86b99ef..d2fe4da1b 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -659,6 +659,7 @@ WOLFSSL_DILITHIUM_VERIFY_NO_MALLOC WOLFSSL_DILITHIUM_VERIFY_SMALL_MEM WOLFSSL_DISABLE_EARLY_SANITY_CHECKS WOLFSSL_DTLS_DISALLOW_FUTURE +WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT WOLFSSL_DUMP_MEMIO_STREAM WOLFSSL_DUP_CERTPOL diff --git a/src/internal.c b/src/internal.c index 4eb5e6c20..6d3c780e2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -86,6 +86,8 @@ * WOLFSSL_NO_INIT_CTX_KEY * Allows SSL objects to be created from a CTX without a loaded key/cert * pair + * WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS: + * When defined, allows DTLS records to span across multiple datagrams. */ #ifndef WOLFCRYPT_ONLY @@ -11622,6 +11624,18 @@ int EarlySanityCheckMsgReceived(WOLFSSL* ssl, byte type, word32 msgSz) return ret; } +static int RecordsCanSpanReads(WOLFSSL *ssl) +{ +#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) + /* Only case where we return 0: DTLS mode (not SCTP) and can't span datagrams */ + if (IsDtlsNotSctpMode(ssl)) { + return 0; + } +#endif + (void)ssl; + return 1; +} + #ifdef WOLFSSL_DTLS13 static int GetInputData(WOLFSSL *ssl, word32 size); static int GetDtls13RecordHeader(WOLFSSL* ssl, word32* inOutIdx, @@ -11681,6 +11695,10 @@ static int GetDtls13RecordHeader(WOLFSSL* ssl, word32* inOutIdx, } if (readSize < ssl->dtls13CurRlLength + DTLS13_RN_MASK_SIZE) { + if (!RecordsCanSpanReads(ssl)) { + WOLFSSL_MSG("Partial record received"); + return DTLS_PARTIAL_RECORD_READ; + } /* when using DTLS over a medium that does not guarantee that a full * message is received in a single read, we may end up without the full * header and minimum ciphertext to decrypt record sequence numbers */ @@ -11773,6 +11791,10 @@ static int GetDtlsRecordHeader(WOLFSSL* ssl, word32* inOutIdx, /* not a unified header, check that we have at least * DTLS_RECORD_HEADER_SZ */ if (ssl->buffers.inputBuffer.length - *inOutIdx < DTLS_RECORD_HEADER_SZ) { + if (!RecordsCanSpanReads(ssl)) { + WOLFSSL_MSG("Partial record received"); + return DTLS_PARTIAL_RECORD_READ; + } ret = GetInputData(ssl, DTLS_RECORD_HEADER_SZ); /* Check if Dtls13RtxTimeout(ssl) returned socket error */ if (ret == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) @@ -21549,9 +21571,18 @@ static int GetInputData(WOLFSSL *ssl, word32 size) return RECV_OVERFLOW_E; } + if ((word32)in < size) { + if (!RecordsCanSpanReads(ssl)) { + WOLFSSL_MSG("DTLS: Received partial record, ignoring"); +#ifdef WOLFSSL_DTLS_DROP_STATS + ssl->replayDropCount++; +#endif /* WOLFSSL_DTLS_DROP_STATS */ + continue; + } + } + ssl->buffers.inputBuffer.length += (word32)in; inSz -= in; - } while (ssl->buffers.inputBuffer.length < size); #ifdef WOLFSSL_DEBUG_TLS @@ -21720,7 +21751,8 @@ static int DtlsShouldDrop(WOLFSSL* ssl, int retcode) if ((ssl->options.handShakeDone && retcode != 0) || retcode == WC_NO_ERR_TRACE(SEQUENCE_ERROR) - || retcode == WC_NO_ERR_TRACE(DTLS_CID_ERROR)) { + || retcode == WC_NO_ERR_TRACE(DTLS_CID_ERROR) + || retcode == WC_NO_ERR_TRACE(DTLS_PARTIAL_RECORD_READ)) { WOLFSSL_MSG_EX("Silently dropping DTLS message: %d", retcode); return 1; } @@ -21947,6 +21979,18 @@ static int DoDecrypt(WOLFSSL *ssl) return ret; } +#ifdef WOLFSSL_DTLS +static void DropAndRestartProcessReply(WOLFSSL* ssl) +{ + ssl->options.processReply = doProcessInit; + ssl->buffers.inputBuffer.length = 0; + ssl->buffers.inputBuffer.idx = 0; +#ifdef WOLFSSL_DTLS_DROP_STATS + if (ssl->options.dtls) + ssl->replayDropCount++; +#endif /* WOLFSSL_DTLS_DROP_STATS */ +} +#endif /* WOLFSSL_DTLS */ /* Process input requests. Return 0 is done, 1 is call again to complete, and negative number is error. If allowSocketErr is set, SOCKET_ERROR_E in ssl->error will be whitelisted. This is useful when the connection has been @@ -22056,6 +22100,11 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) used = ssl->buffers.inputBuffer.length - ssl->buffers.inputBuffer.idx; if (used < readSz) { + if (used > 0 && !RecordsCanSpanReads(ssl)) { + WOLFSSL_MSG("DTLS: Partial record in buffer, dropping"); + DropAndRestartProcessReply(ssl); + continue; + } if ((ret = GetInputData(ssl, (word32)readSz)) < 0) return ret; } @@ -22155,13 +22204,7 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) dtlsProcessPendingPeer(ssl, 0); #endif if (ssl->options.dtls && DtlsShouldDrop(ssl, ret)) { - ssl->options.processReply = doProcessInit; - ssl->buffers.inputBuffer.length = 0; - ssl->buffers.inputBuffer.idx = 0; -#ifdef WOLFSSL_DTLS_DROP_STATS - ssl->replayDropCount++; -#endif /* WOLFSSL_DTLS_DROP_STATS */ - + DropAndRestartProcessReply(ssl); #ifdef WOLFSSL_DTLS13 /* return to send ACKS and shortcut rtx timer */ if (IsAtLeastTLSv1_3(ssl->version) @@ -22223,9 +22266,15 @@ default: /* read ahead may already have */ used = ssl->buffers.inputBuffer.length - ssl->buffers.inputBuffer.idx; - if (used < ssl->curSize) + if (used < ssl->curSize) { + if (!RecordsCanSpanReads(ssl)) { + WOLFSSL_MSG("Partial record received, dropping"); + DropAndRestartProcessReply(ssl); + continue; + } if ((ret = GetInputData(ssl, ssl->curSize)) < 0) return ret; + } #endif } @@ -22276,9 +22325,7 @@ default: /* If in DTLS mode, if the decrypt fails for any * reason, pretend the datagram never happened. */ if (ssl->options.dtls) { - ssl->options.processReply = doProcessInit; - ssl->buffers.inputBuffer.idx = - ssl->buffers.inputBuffer.length; + DropAndRestartProcessReply(ssl); return HandleDTLSDecryptFailed(ssl); } #endif /* WOLFSSL_DTLS */ @@ -22331,9 +22378,7 @@ default: /* If in DTLS mode, if the decrypt fails for any * reason, pretend the datagram never happened. */ if (ssl->options.dtls) { - ssl->options.processReply = doProcessInit; - ssl->buffers.inputBuffer.idx = - ssl->buffers.inputBuffer.length; + DropAndRestartProcessReply(ssl); return HandleDTLSDecryptFailed(ssl); } #endif /* WOLFSSL_DTLS */ @@ -22401,9 +22446,7 @@ default: /* If in DTLS mode, if the decrypt fails for any * reason, pretend the datagram never happened. */ if (ssl->options.dtls) { - ssl->options.processReply = doProcessInit; - ssl->buffers.inputBuffer.idx = - ssl->buffers.inputBuffer.length; + DropAndRestartProcessReply(ssl); return HandleDTLSDecryptFailed(ssl); } #endif /* WOLFSSL_DTLS */ diff --git a/tests/api.c b/tests/api.c index e276f7d46..a43e40644 100644 --- a/tests/api.c +++ b/tests/api.c @@ -68040,6 +68040,13 @@ TEST_CASE testCases[] = { /* Can't memory test as client/server hangs. */ TEST_DECL(test_dtls_msg_from_other_peer), TEST_DECL(test_dtls_ipv6_check), + TEST_DECL(test_dtls_short_ciphertext), + TEST_DECL(test_dtls12_record_length_mismatch), + TEST_DECL(test_dtls12_short_read), + TEST_DECL(test_dtls13_longer_length), + TEST_DECL(test_dtls13_short_read), + TEST_DECL(test_records_span_network_boundaries), + TEST_DECL(test_dtls_record_cross_boundaries), TEST_DECL(test_wolfSSL_SCR_after_resumption), TEST_DECL(test_dtls_no_extensions), TEST_DECL(test_tls_alert_no_server_hello), diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index cfa7b36fa..503e66c31 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -776,3 +776,491 @@ int test_dtls_version_checking(void) #endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES && WOLFSSL_DTLS */ return EXPECT_RESULT(); } + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) +static int test_dtls_shutdown(WOLFSSL *s, WOLFSSL *c, WOLFSSL_CTX *cc, WOLFSSL_CTX *cs) +{ + EXPECT_DECLS; + /* Cleanup */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_shutdown(c), WOLFSSL_SHUTDOWN_NOT_DONE); + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_shutdown(s), WOLFSSL_SHUTDOWN_NOT_DONE); + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_shutdown(c), 1); + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_shutdown(s), 1); + + wolfSSL_SetLoggingPrefix(NULL); + wolfSSL_free(c); + wolfSSL_CTX_free(cc); + wolfSSL_free(s); + wolfSSL_CTX_free(cs); + return EXPECT_RESULT(); +} + +static int test_dtls_communication(WOLFSSL *s, WOLFSSL *c) +{ + EXPECT_DECLS; + unsigned char readBuf[50]; + + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(c, "client message", 14), 14); + + wolfSSL_SetLoggingPrefix("server"); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(s, readBuf, sizeof(readBuf)), 14); + ExpectStrEQ(readBuf, "client message"); + + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_write(s, "server message", 14), 14); + + wolfSSL_SetLoggingPrefix("client"); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(c, readBuf, sizeof(readBuf)), 14); + ExpectStrEQ(readBuf, "server message"); + + /* this extra round is consuming newSessionTicket and acks */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(c, "client message 2", 16), 16); + + wolfSSL_SetLoggingPrefix("server"); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(s, readBuf, sizeof(readBuf)), 16); + ExpectStrEQ(readBuf, "client message 2"); + + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_write(s, "server message 2", 16), 16); + + wolfSSL_SetLoggingPrefix("client"); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(c, readBuf, sizeof(readBuf)), 16); + ExpectStrEQ(readBuf, "server message 2"); + + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) +int test_dtls13_longer_length(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[50]; + int seq16bit = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), + 0); + + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Create good record with length mismatch */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(ssl_c, "client message", 14), 14); + + /* check client wrote the record */ + ExpectIntGT(test_ctx.s_len, 14); + /* check length is included in the record header */ + ExpectIntGT(test_ctx.s_buff[0x0] & (1 << 2), 0); + seq16bit = (test_ctx.s_buff[0x0] & (1 << 3)) != 0; + /* big endian, modify LSB byte */ + seq16bit *= 2; + /* modify length to be bigger */ + test_ctx.s_buff[0x2 + seq16bit] = 0xff; + + /* Try to read the malformed record */ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS); + ExpectIntEQ(test_dtls_shutdown(ssl_s, ssl_c, ctx_c, ctx_s), TEST_SUCCESS); + + return EXPECT_RESULT(); +} +#else +int test_dtls13_longer_length(void) +{ + return TEST_SKIPPED; +} +#endif /* WOLFSSL_DTLS13 && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) */ + +#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) +int test_dtls13_short_read(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[50]; + int i; + + /* we setup two test, in the first one the server reads just two bytes of + * the header, in the second one it reads just the header (5) */ + for (i = 0; i < 2; i++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), + 0); + + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* create a good record in the buffer */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(ssl_c, "client message", 14), 14); + + /* check client wrote the record */ + ExpectIntGT(test_ctx.s_len, 14); + /* return less data */ + ExpectIntEQ( + test_memio_modify_message_len(&test_ctx, 0, 0, i == 0 ? 2 : 5), 0); + /* Try to read the malformed record */ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS); + ExpectIntEQ(test_dtls_shutdown(ssl_s, ssl_c, ctx_c, ctx_s), + TEST_SUCCESS); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + } + + return EXPECT_RESULT(); +} +#else +int test_dtls13_short_read(void) +{ + return TEST_SKIPPED; +} +#endif /* WOLFSSL_DTLS13 && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) */ + +#if !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) +int test_dtls12_short_read(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[50]; + int i; + + for (i = 0; i < 3; i++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + 0); + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* create a good record in the buffer */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(ssl_c, "bad", 3), 3); + + /* check client wrote the record */ + ExpectIntGT(test_ctx.s_len, 13 + 3); + /* return less data */ + switch (i) { + case 0: + ExpectIntEQ(test_memio_modify_message_len(&test_ctx, 0, 0, 2), 0); + break; + case 1: + ExpectIntEQ(test_memio_modify_message_len(&test_ctx, 0, 0, 13), 0); + break; + case 2: + ExpectIntEQ(test_memio_modify_message_len(&test_ctx, 0, 0, 15), 0); + break; + } + + /* Try to read the malformed record */ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS); + ExpectIntEQ(test_dtls_shutdown(ssl_s, ssl_c, ctx_c, ctx_s), + TEST_SUCCESS); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + } + + return EXPECT_RESULT(); +} +#else +int test_dtls12_short_read(void) +{ + return TEST_SKIPPED; +} +#endif /* !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) */ + +#if !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) +int test_dtls12_record_length_mismatch(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[50]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + 0); + + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* write a message from client */ + ExpectIntEQ(wolfSSL_write(ssl_c, "bad", 3), 3); + + /* check that the message is written in the buffer */ + ExpectIntGT(test_ctx.s_len, 14); + /* modify the length field to be bigger than the content */ + test_ctx.s_buff[12] = 0xff; + + /* Try to read the malformed record */ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS); + ExpectIntEQ(test_dtls_shutdown(ssl_s, ssl_c, ctx_c, ctx_s), TEST_SUCCESS); + + return EXPECT_RESULT(); +} + +int test_dtls_record_cross_boundaries(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[100]; + int rec0_len, rec1_len; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + 0); + + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* create a first record in the buffer */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(ssl_c, "test0", 5), 5); + rec0_len = test_ctx.s_msg_sizes[0]; + + /* create a second record in the buffer */ + ExpectIntEQ(wolfSSL_write(ssl_c, "test1", 5), 5); + rec1_len = test_ctx.s_msg_sizes[1]; + + ExpectIntLE(rec0_len + rec1_len, sizeof(readBuf)); + XMEMCPY(readBuf, test_ctx.s_buff, rec0_len + rec1_len); + + /* clear buffer */ + test_memio_clear_buffer(&test_ctx, 0); + + /* inject first record + 1 bytes of second record */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)readBuf, + rec0_len + 1), + 0); + + /* inject second record */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)readBuf + rec0_len + 1, rec1_len - 1), + 0); + ExpectIntEQ(test_ctx.s_len, rec0_len + rec1_len); + + /* reading the record should return just the first message*/ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), 5); + ExpectBufEQ(readBuf, "test0", 5); + + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + + /* cleanup */ + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + + return EXPECT_RESULT(); +} +#else +int test_dtls12_record_length_mismatch(void) +{ + return TEST_SKIPPED; +} +int test_dtls_record_cross_boundaries(void) +{ + return TEST_SKIPPED; +} +#endif /* !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) */ + +int test_dtls_short_ciphertext(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[50]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + 0); + + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Create a message, that looks encrypted but shorter than minimum ciphertext length */ + /* create the data in the buffer */ + ExpectIntEQ(wolfSSL_write(ssl_c, "bad", 3), 3); + + /* check client wrote the record */ + ExpectIntGT(test_ctx.s_len, 14); + + /* modify the length field to be smaller than the content */ + test_ctx.s_buff[11] = 0x00; + test_ctx.s_buff[12] = 0x02; + /* modify the amount of data to send */ + ExpectIntEQ(test_memio_modify_message_len(&test_ctx, 0, 0, 15), 0); + + /* Try to read the malformed record */ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS); + + ExpectIntEQ(test_dtls_shutdown(ssl_s, ssl_c, ctx_c, ctx_s), TEST_SUCCESS); + + return EXPECT_RESULT(); +} +#else +int test_dtls_short_ciphertext(void) +{ + return TEST_SKIPPED; +} +int test_dtls12_record_length_mismatch(void) +{ + return TEST_SKIPPED; +} +int test_dtls12_short_read(void) +{ + return TEST_SKIPPED; +} +int test_dtls13_short_read(void) +{ + return TEST_SKIPPED; +} +int test_dtls13_longer_length(void) +{ + return TEST_SKIPPED; +} +int test_dtls_record_cross_boundaries(void) +{ + return TEST_SKIPPED; +} +#endif /* defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) */ + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(WOLFSSL_NO_TLS12) +/* This test that the DTLS record boundary check doesn't interfere with TLS + * records processing */ +int test_records_span_network_boundaries(void) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[50]; + int record_len; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Setup DTLS contexts */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), + 0); + + /* Complete handshake */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* create a good record in the buffer */ + wolfSSL_SetLoggingPrefix("client"); + ExpectIntEQ(wolfSSL_write(ssl_c, "test", 4), 4); + ExpectIntLE(test_ctx.s_len, 50); + ExpectIntGT(test_ctx.s_len, 10); + record_len = test_ctx.s_len; + XMEMCPY(readBuf, test_ctx.s_buff, record_len); + + /* drop record and simulate a split write */ + ExpectIntEQ(test_memio_drop_message(&test_ctx, 0, 0), 0); + ExpectIntEQ(test_ctx.s_msg_count, 0); + + /* inject first record header */ + ExpectIntEQ( + test_memio_inject_message(&test_ctx, 0, (const char*)readBuf, 5), 0); + ExpectIntEQ(test_ctx.s_msg_count, 1); + ExpectIntEQ(test_ctx.s_msg_sizes[0], 5); + + /* inject another 5 bytes of the record */ + ExpectIntEQ( + test_memio_inject_message(&test_ctx, 0, (const char*)readBuf + 5, 5), + 0); + ExpectIntEQ(test_ctx.s_msg_count, 2); + ExpectIntEQ(test_ctx.s_msg_sizes[1], 5); + + /* inject the rest of the record */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)readBuf + 10, record_len - 10), + 0); + ExpectIntEQ(test_ctx.s_msg_count, 3); + ExpectIntEQ(test_ctx.s_msg_sizes[2], record_len - 10); + + /* read the record */ + wolfSSL_SetLoggingPrefix("server"); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), 4); + ExpectIntEQ(test_ctx.s_len, 0); + + ExpectBufEQ(readBuf, "test", 4); + + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + + return EXPECT_RESULT(); +} +#else +int test_records_span_network_boundaries(void) +{ + return TEST_SKIPPED; +} +#endif /* defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) */ diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 25f9ab263..7f8f0457f 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -28,5 +28,11 @@ int test_wolfSSL_dtls_cid_parse(void); int test_dtls13_epochs(void); int test_dtls13_ack_order(void); int test_dtls_version_checking(void); - +int test_dtls_short_ciphertext(void); +int test_dtls12_record_length_mismatch(void); +int test_dtls12_short_read(void); +int test_dtls13_longer_length(void); +int test_dtls13_short_read(void); +int test_records_span_network_boundaries(void); +int test_dtls_record_cross_boundaries(void); #endif /* TESTS_API_DTLS_H */ diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index bc3e64100..63f8d35c5 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -220,6 +220,7 @@ enum wolfSSL_ErrorCodes { POST_HAND_AUTH_ERROR = -504, /* client won't do post-hand auth */ HRR_COOKIE_ERROR = -505, /* HRR msg cookie mismatch */ UNSUPPORTED_CERTIFICATE = -506, /* unsupported certificate type */ + DTLS_PARTIAL_RECORD_READ = -455, /* received a partial record in a datagram */ /* PEM and EVP errors */ WOLFSSL_PEM_R_NO_START_LINE_E = -507,