From 576580568e63c4336bb649b09dfb93a9009fb0d6 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 2 Sep 2026 11:11:39 -0600 Subject: [PATCH 1/4] Add TLS 1.3 cover traffic padding to the record layer --- src/ssl.c | 2 ++ src/tls13.c | 42 ++++++++++++++++++++++++++++++++++++++++++ wolfssl/internal.h | 10 ++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index da91ff10ac2..99764053ae0 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5699,6 +5699,8 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.hrrSentCookie = 0; #endif ssl->options.hrrSentKeyShare = 0; + /* Don't let a request abandoned mid-pending survive object reuse. */ + Tls13ClearCoverTraffic(ssl); #endif #ifdef WOLFSSL_DTLS ssl->options.dtlsStateful = 0; diff --git a/src/tls13.c b/src/tls13.c index 1322d76df55..30bce995712 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3252,6 +3252,37 @@ static void FreeBuildMsg13Args(WOLFSSL* ssl, void* pArgs) /* no allocations in BuildTls13Message */ } +/* Padding for an outstanding cover traffic request, or 0 if none applies. + * Shared by BuildTls13Message() and SendData() so they size the record the + * same way. DTLS 1.3 is rejected in the guard defensively, even though the + * public API already excludes it. */ +word16 Tls13GetCoverTrafficPaddingSz(WOLFSSL* ssl) +{ + word16 padSz; + int maxFrag; + + if (ssl->options.dtls || !ssl->options.sendCoverTraffic) + return 0; + + padSz = ssl->options.coverTrafficPadSz; + /* Re-clamp against negotiated max_fragment_length, which may be + * smaller than when the request was armed. + * One byte is left for the record layer content type. */ + maxFrag = wolfSSL_GetMaxFragSize(ssl); + if (maxFrag > 0 && padSz >= (word16)maxFrag) + padSz = (word16)(maxFrag - 1); + + return padSz; +} + +/* Clears an outstanding cover traffic request. sendCoverTraffic and + * coverTrafficPadSz always change together; centralize the reset here. */ +void Tls13ClearCoverTraffic(WOLFSSL* ssl) +{ + ssl->options.sendCoverTraffic = 0; + ssl->options.coverTrafficPadSz = 0; +} + /* Build SSL Message, encrypted. * TLS v1.3 encryption is AEAD only. * @@ -3364,6 +3395,17 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, if (sizeOnly) return (int)args->sz; + /* Add cover traffic padding for application data records. + * Excluded from sizeOnly to preserve the record overhead cache; + * SendData() sizes for it via the same helper call. */ + if (type == application_data) { + word16 padSz = Tls13GetCoverTrafficPaddingSz(ssl); + if (padSz > 0) { + args->paddingSz += padSz; + args->sz += padSz; + } + } + if (args->sz > (word32)outSz) { WOLFSSL_MSG("Oops, want to write past output buffer size"); return BUFFER_E; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 426894de5b0..52cb61eceb0 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5444,6 +5444,9 @@ struct Options { word16 failNoCertxPSK:1; /* fail for no cert except with PSK */ word16 failNoPSK:1; /* fail if no PSK is negotiated */ word16 downgrade:1; /* allow downgrade of versions */ +#ifdef WOLFSSL_TLS13 + word16 sendCoverTraffic:1; /* TLS 1.3 cover traffic request */ +#endif word16 resuming:1; #ifdef HAVE_SECURE_RENEGOTIATION word16 resumed:1; /* resuming may be reset on SCR */ @@ -5621,6 +5624,9 @@ struct Options { word16 peerSha1CertOk:1; /* Peer advertised a SHA-1 signature * scheme for certificates */ #endif +#ifdef WOLFSSL_TLS13 + word16 coverTrafficPadSz; /* padding length of cover traffic */ +#endif #ifdef WOLFSSL_DTLS byte haveMcast; /* using multicast ? */ #endif @@ -7709,6 +7715,10 @@ WOLFSSL_TEST_VIS int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, WOLFSSL_TEST_VIS int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, int inSz, int type, int hashOutput, int sizeOnly, int asyncOkay); WOLFSSL_LOCAL int Tls13UpdateKeys(WOLFSSL* ssl); +/* Cover traffic padding for the next record, or 0 if none is requested. */ +WOLFSSL_LOCAL word16 Tls13GetCoverTrafficPaddingSz(WOLFSSL* ssl); +/* Clears an outstanding cover traffic request. */ +WOLFSSL_LOCAL void Tls13ClearCoverTraffic(WOLFSSL* ssl); #endif WOLFSSL_LOCAL int AllocKey(WOLFSSL* ssl, int type, void** pKey); From d070c381a3d6e5e8a39ac9ab09fc1bd3d9e6cbfc Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 2 Sep 2026 11:11:39 -0600 Subject: [PATCH 2/4] Send cover traffic records from SendData() --- src/internal.c | 59 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/src/internal.c b/src/internal.c index c78193cd183..41932ca35e9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -28803,6 +28803,14 @@ static int ssl_in_handshake(WOLFSSL *ssl, int sending_data) return 0; } +/* TLS 1.3 server can send app data before client's Finished. + * Caller checks version. */ +static int IsTls13HalfRttSend(const WOLFSSL* ssl) +{ + return ssl->options.side == WOLFSSL_SERVER_END && + ssl->options.acceptState >= TLS13_ACCEPT_FINISHED_SENT; +} + int SendData(WOLFSSL* ssl, const void* data, size_t sz) { word32 sent = 0; /* plainText size */ @@ -28856,9 +28864,7 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) } else #endif - if (IsAtLeastTLSv1_3(ssl->version) && - ssl->options.side == WOLFSSL_SERVER_END && - ssl->options.acceptState >= TLS13_ACCEPT_FINISHED_SENT) { + if (IsAtLeastTLSv1_3(ssl->version) && IsTls13HalfRttSend(ssl)) { /* We can send data without waiting on peer finished msg */ WOLFSSL_MSG("server sending data before receiving client finished"); } @@ -29011,10 +29017,24 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) } #endif /* WOLFSSL_DTLS13 */ - if (sent == (word32)sz) break; + if (sz == 0) { + int coverTraffic = 0; +#ifdef WOLFSSL_TLS13 + /* Check sendCoverTraffic; paddingSz 0 is valid. handShakeDone + * excludes early data. If downgraded to TLS 1.2, leave armed + * so caller sees failure and clears it. */ + coverTraffic = ssl->options.tls1_3 && !ssl->options.dtls && + (ssl->options.handShakeDone || + IsTls13HalfRttSend(ssl)) && + ssl->options.sendCoverTraffic; +#endif + if (!coverTraffic) + break; + } + else if (sent == (word32)sz) break; buffSz = (int)((word32)sz - sent); - if (buffSz <= 0) { + if (buffSz < 0 || (buffSz == 0 && sz != 0)) { WOLFSSL_MSG("error: sent size exceeds input size"); ssl->error = BAD_FUNC_ARG; return WOLFSSL_FATAL_ERROR; @@ -29044,9 +29064,26 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) #endif /* WOLFSSL_DTLS */ { int maxFrag = wolfSSL_GetMaxFragSize(ssl); - if (maxFrag > 0) - buffSz = min((word32)buffSz, (word32)maxFrag); + if (maxFrag > 0) { + int maxData; +#ifdef WOLFSSL_TLS13 + /* Leave room: BuildTls13Message() merges pending cover + * traffic padding into the next record too. The public API + * keeps padding below maxFrag, so at least one plaintext + * byte always fits; the clamp is only a backstop. */ + word16 padSz = Tls13GetCoverTrafficPaddingSz(ssl); + maxData = (padSz < (word16)maxFrag) ? maxFrag - padSz : 0; +#else + maxData = maxFrag; +#endif + buffSz = min((word32)buffSz, (word32)maxData); + } outputSz = wolfssl_local_GetRecordSize(ssl, (word32)buffSz, 1); +#ifdef WOLFSSL_TLS13 + /* wolfssl_local_GetRecordSize() doesn't know about cover traffic + * padding; account for what BuildTls13Message() will add. */ + outputSz += (int)Tls13GetCoverTrafficPaddingSz(ssl); +#endif } /* check for available size, it does also DTLS MTU checks */ @@ -29111,6 +29148,14 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) #ifdef WOLFSSL_TLS13 sendSz = BuildTls13Message(ssl, out, outputSz, sendBuffer, buffSz, application_data, 0, 0, 1); + /* Clear cover traffic request for subsequent records, unless + * an asynchronous build is pending. */ + #ifdef WOLFSSL_ASYNC_CRYPT + if (sendSz != WC_NO_ERR_TRACE(WC_PENDING_E)) + #endif + { + Tls13ClearCoverTraffic(ssl); + } #else sendSz = BUFFER_ERROR; #endif From 30a4a288824d5c89f8702440773ccab58b320b26 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 2 Sep 2026 11:11:39 -0600 Subject: [PATCH 3/4] Add wolfSSL_send_tls13_cover_traffic() --- doc/dox_comments/header_files/ssl.h | 47 ++++++++++++ src/ssl_api_rw.c | 110 ++++++++++++++++++++++++++++ wolfssl/ssl.h | 2 + 3 files changed, 159 insertions(+) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index a58c6b4f202..1c40488c423 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -15786,6 +15786,53 @@ int wolfSSL_CTX_no_early_data_fresh_start_check(WOLFSSL_CTX* ctx); */ int wolfSSL_inject(WOLFSSL* ssl, const void* data, int sz); +/*! + \ingroup IO + + \brief Sends a TLS 1.3 application data record containing only padding. + Lets an application generate the cover traffic described in RFC 8446 + Appendix E. The request applies to the next record only. Requires a stream + TLS 1.3 session; DTLS 1.3 is unsupported. + + Completes any in-progress handshake. Padding is reduced to fit the + negotiated max fragment size. + + Returns WOLFSSL_FATAL_ERROR if no cover traffic record was sent (e.g. + due to downgrade or peer reset). wolfSSL_get_error() gives the reason. + + On WOLFSSL_ERROR_WANT_WRITE, the request is disarmed. Calling again may + queue a second record. + + With WOLFSSL_ASYNC_CRYPT, if suspended with WC_PENDING_E, call again + to resume. The original paddingSz is used. Unrelated pending async + operations cause BAD_STATE_E. + + \param [in,out] ssl WOLFSSL structure. + \param [in] paddingSz Number of padding bytes. Must be less than the max + fragment size. + + \return 0 on success + \return BAD_FUNC_ARG if ssl is NULL, paddingSz is invalid, or session is not stream TLS 1.3 + \return BAD_STATE_E if an application write or an unrelated asynchronous operation is pending + \return WOLFSSL_FATAL_ERROR if the record could not be sent; the reason, + e.g. WOLFSSL_ERROR_WANT_WRITE or WC_PENDING_E, is available from + wolfSSL_get_error() + \return NOT_COMPILED_IN if TLS 1.3 support is not built in + + _Example_ + \code + // send a 256 byte cover traffic record while the connection is idle + if (wolfSSL_send_tls13_cover_traffic(ssl, 256) != 0) { + err = wolfSSL_get_error(ssl, -1); + printf("error = %d, %s\n", err, wolfSSL_ERR_error_string(err, buffer)); + } + \endcode + + \sa wolfSSL_write + \sa wolfSSL_get_error +*/ +int wolfSSL_send_tls13_cover_traffic(WOLFSSL* ssl, int paddingSz); + /*! \ingroup Setup diff --git a/src/ssl_api_rw.c b/src/ssl_api_rw.c index 589c5950575..7c036e8eb04 100644 --- a/src/ssl_api_rw.c +++ b/src/ssl_api_rw.c @@ -376,6 +376,116 @@ int wolfSSL_write(WOLFSSL* ssl, const void* data, int sz) return ret; } +/* Send a TLS 1.3 application data record containing only padding. + * + * Generates cover traffic (RFC 8446 Appendix E). The request applies + * to the next record only. + * + * Arms and clears the request, except when async build is pending. + * If still armed after write, no record was built (e.g. TLS 1.2 downgrade). + * Completes any in-progress handshake. + * + * On WANT_WRITE the request is not left armed: the record was either + * already queued (and gets flushed by the next write) or dropped. Calling + * this function again is safe, but may put a second cover traffic record + * on the wire if the first one had been queued. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] paddingSz Length of padding in bytes. + * @return 0 on success. + * @return BAD_FUNC_ARG when arguments are invalid or session is not stream TLS 1.3. + * @return BAD_STATE_E when an application write or an unrelated asynchronous + * operation is pending. + * @return WOLFSSL_FATAL_ERROR when the write fails. + * @return NOT_COMPILED_IN when TLS 1.3 support is not built in. + */ +int wolfSSL_send_tls13_cover_traffic(WOLFSSL* ssl, int paddingSz) +{ +#ifdef WOLFSSL_TLS13 + int ret; + int maxFrag; + char dummy = 0; +#endif + + WOLFSSL_ENTER("wolfSSL_send_tls13_cover_traffic"); + + if (ssl == NULL || paddingSz < 0) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_TLS13 + /* DTLS 1.3 pads to its own minimum length and is unsupported. */ + if (!IsAtLeastTLSv1_3(ssl->version) || ssl->options.dtls) { + WOLFSSL_MSG("Cover traffic needs a stream TLS 1.3 session"); + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_ASYNC_CRYPT + /* Check if armed request matches pending async op. + * Use original padding size if resuming. */ + if (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E)) { + if (!ssl->options.sendCoverTraffic) { + WOLFSSL_MSG("Cover traffic blocked by pending async op"); + return BAD_STATE_E; + } + paddingSz = (int)ssl->options.coverTrafficPadSz; + } +#endif + + /* Keep padding within the negotiated fragment size. */ + maxFrag = wolfSSL_GetMaxFragSize(ssl); + /* The record also carries the content type byte, so padding equal to + * the fragment size would overflow the plaintext limit. */ + if (paddingSz >= maxFrag) { + WOLFSSL_MSG("Cover traffic padding larger than the max fragment size"); + return BAD_FUNC_ARG; + } + + /* Disallow if an application write is pending to avoid losing data. */ + if (ssl->buffers.plainSz > 0) { + WOLFSSL_MSG("Cover traffic needs the pending write to finish first"); + return BAD_STATE_E; + } + + ssl->options.coverTrafficPadSz = (word16)paddingSz; + ssl->options.sendCoverTraffic = 1; + + ret = wolfSSL_write(ssl, &dummy, 0); + if (ret < 0) { + #ifdef WOLFSSL_ASYNC_CRYPT + /* An asynchronous build is pending and consumes the padding when it + * resumes, so leave the request armed for it. Trust ssl->error only + * when ret is SendData()'s own sentinel -- some early returns skip + * it. Any other error means no record was built here. */ + if (ret == WOLFSSL_FATAL_ERROR && + ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E)) { + return ret; + } + #endif + Tls13ClearCoverTraffic(ssl); + } + else if (ssl->options.sendCoverTraffic || + (ret == 0 && ssl->error != 0)) { + /* Write returned 0 but record wasn't sent (e.g. peer reset + * or TLS downgrade). Not a success. */ + if (ssl->error == 0) + ssl->error = BAD_STATE_E; + Tls13ClearCoverTraffic(ssl); + ret = WOLFSSL_FATAL_ERROR; + } + else { + /* SendData() returns the ciphertext length under + * WOLFSSL_THREADED_CRYPT, not 0. Normalize to the documented + * contract. */ + ret = 0; + } + + return ret; +#else + (void)paddingSz; + return NOT_COMPILED_IN; +#endif /* WOLFSSL_TLS13 */ +} + /* Inject data into the input buffer as if it was received from the peer. * * Used when the application reads the transport itself. diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index dd35a5f9291..e5eaefd5c75 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1481,6 +1481,8 @@ WOLFSSL_API int wolfSSL_get_wfd(const WOLFSSL* ssl); WOLFSSL_ABI WOLFSSL_API int wolfSSL_connect(WOLFSSL* ssl); WOLFSSL_ABI WOLFSSL_API int wolfSSL_write( WOLFSSL* ssl, const void* data, int sz); +WOLFSSL_API int wolfSSL_send_tls13_cover_traffic( + WOLFSSL* ssl, int paddingSz); WOLFSSL_API int wolfSSL_write_ex(WOLFSSL* ssl, const void* data, size_t sz, size_t* wr); WOLFSSL_ABI WOLFSSL_API int wolfSSL_read(WOLFSSL* ssl, void* data, int sz); From 1c3370f0fe705df5d7f1bcdaceadd0d31d9d45e6 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 2 Sep 2026 11:11:40 -0600 Subject: [PATCH 4/4] Add tests for wolfSSL_send_tls13_cover_traffic() --- tests/api.c | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) diff --git a/tests/api.c b/tests/api.c index 88999c0cfb7..3aeb9351249 100644 --- a/tests/api.c +++ b/tests/api.c @@ -38867,6 +38867,395 @@ static int test_wolfSSL_read_ahead_ctx_inherit(void) } #endif +#ifdef WOLFSSL_TLS13 +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_SHA256) +/* Simulates a peer reset mid-send. */ +static int test_cover_traffic_conn_rst_io_cb(WOLFSSL *ssl, char *data, int sz, + void *ctx) +{ + (void)ssl; + (void)data; + (void)sz; + (void)ctx; + return WOLFSSL_CBIO_ERR_CONN_RST; +} +#endif + +static int test_wolfSSL_send_tls13_cover_traffic(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_SHA256) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + char msg[] = "hello"; + char reply[64]; + int emptySz = 0; + int baseSz = 0; + byte* bigMsg = NULL; + byte* bigReply = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + test_memio_clear_buffer(&test_ctx, 1); + test_memio_clear_buffer(&test_ctx, 0); + + /* Bad arguments. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(NULL, 100), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, -1), BAD_FUNC_ARG); + /* Reject padding exceeding record layer limits. The record also carries + * the content type byte, so padding of exactly the fragment size would + * overflow the plaintext limit. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, MAX_RECORD_SIZE + 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, MAX_RECORD_SIZE), + BAD_FUNC_ARG); + ExpectIntEQ(test_ctx.s_len, 0); + + /* Allow the largest padding that still fits. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, MAX_RECORD_SIZE - 1), + 0); + ExpectIntGT(test_ctx.s_len, MAX_RECORD_SIZE - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + test_memio_clear_buffer(&test_ctx, 0); + + /* Zero padding produces a record. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 0), 0); + ExpectIntGT(test_ctx.s_len, 0); + emptySz = test_ctx.s_len; + /* Peer reads no application data. */ + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + test_memio_clear_buffer(&test_ctx, 0); + + /* Padding grows the record. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), 0); + ExpectIntEQ(test_ctx.s_len, emptySz + 100); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + test_memio_clear_buffer(&test_ctx, 0); + + /* Exclude padding from cached record overhead. */ + ExpectIntEQ((int)ssl_c->recordSzOverhead, emptySz); + + /* Request applies to one record only. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), 5); + baseSz = test_ctx.s_len; + ExpectIntEQ(baseSz, emptySz + 5); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), 5); + test_memio_clear_buffer(&test_ctx, 0); + + /* Works after sending application data despite cached record size. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), 0); + ExpectIntEQ(test_ctx.s_len, emptySz + 100); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + test_memio_clear_buffer(&test_ctx, 0); + + /* No padding leak into later records. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), 5); + ExpectIntEQ(test_ctx.s_len, baseSz); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), 5); + test_memio_clear_buffer(&test_ctx, 0); + + /* No padding leak into close_notify. */ + ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SHUTDOWN_NOT_DONE); + ExpectIntGT(test_ctx.s_len, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* Refuse request if application write is waiting. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_WRITE); + test_memio_simulate_want_write(&test_ctx, 1, 0); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), BAD_STATE_E); + /* Blocked write completes without padding. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), 5); + ExpectIntEQ(test_ctx.s_len, baseSz); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), 5); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* An overlapping request isn't refused up front; if it never reaches + * its own record build, it must be dropped, not merged into a later + * real write. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + + bigMsg = (byte*)XMALLOC(MAX_RECORD_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); + bigReply = (byte*)XMALLOC(MAX_RECORD_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(bigMsg); + ExpectNotNull(bigReply); + if (bigMsg != NULL) + XMEMSET(bigMsg, 'A', MAX_RECORD_SIZE); + + test_memio_simulate_want_write(&test_ctx, 1, 1); + /* Builds and queues its record, then blocks flushing it. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 50), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_WRITE); + /* Overlapping request: blocks flushing the first record before its + * own is ever built. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_WRITE); + test_memio_simulate_want_write(&test_ctx, 1, 0); + + if (bigMsg != NULL) { + int totalRead = 0; + + /* Flushes the first request's queued record; the dropped second + * request's padding must not attach to this unrelated write. */ + ExpectIntEQ(wolfSSL_write(ssl_c, bigMsg, MAX_RECORD_SIZE), + MAX_RECORD_SIZE); + ExpectIntEQ(test_ctx.s_len, 2 * emptySz + MAX_RECORD_SIZE + 50); + + while (bigReply != NULL && totalRead < MAX_RECORD_SIZE && + !EXPECT_FAIL()) { + int ret = wolfSSL_read(ssl_s, bigReply + totalRead, + MAX_RECORD_SIZE - totalRead); + ExpectIntGT(ret, 0); + if (ret <= 0) + break; + totalRead += ret; + } + ExpectIntEQ(totalRead, MAX_RECORD_SIZE); + if (bigReply != NULL) + ExpectIntEQ(XMEMCMP(bigMsg, bigReply, MAX_RECORD_SIZE), 0); + } + + XFREE(bigMsg, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(bigReply, NULL, DYNAMIC_TYPE_TMP_BUFFER); + bigMsg = NULL; + bigReply = NULL; + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* Fails cleanly before handshake finishes. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), -1); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), 5); + ExpectIntEQ(test_ctx.s_len, baseSz); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), 5); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* A mid-handshake request whose own completion retry blocks on + * WANT_WRITE before its record is built must not stay armed. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Server sends its whole flight; client hasn't sent Finished yet. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + wolfSSL_accept(ssl_s); + + /* Reads the buffered flight fine, blocks sending Finished. */ + test_memio_simulate_want_write(&test_ctx, 1, 1); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_WRITE); + test_memio_simulate_want_write(&test_ctx, 1, 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), 5); + ExpectIntEQ(test_ctx.s_len, baseSz); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), 5); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* A TLS 1.3 server may send cover traffic in the half-RTT window, + * after its own Finished and before the client's. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + + test_memio_clear_buffer(&test_ctx, 1); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_s, 100), 0); + ExpectIntGT(test_ctx.c_len, 100); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* A downgraded session cannot carry the request and must fail. + * Model this by clearing the negotiated TLS 1.3 flag. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + + if (ssl_c != NULL) + ssl_c->options.tls1_3 = 0; + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), + WOLFSSL_FATAL_ERROR); + /* Nothing sent; reason can be retrieved. */ + ExpectIntEQ(test_ctx.s_len, 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), BAD_STATE_E); + if (ssl_c != NULL) + ssl_c->options.tls1_3 = 1; + + /* Dropped request is disarmed. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 5), 5); + ExpectIntEQ(test_ctx.s_len, baseSz); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), 5); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + + /* Peer reset during flush is a failure (0 bytes sent). */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + + wolfSSL_SSLSetIOSend(ssl_c, test_cover_traffic_conn_rst_io_cb); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), + WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), SOCKET_PEER_CLOSED_E); + ExpectIntEQ(test_ctx.s_len, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; + +#ifndef WOLFSSL_NO_TLS12 + /* Requires stream TLS 1.3. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), BAD_FUNC_ARG); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; +#endif /* !WOLFSSL_NO_TLS12 */ + +#ifdef HAVE_MAX_FRAGMENT + /* Re-clamps over-large padding if max_fragment_length is negotiated + * after request was armed. Arm directly, as API rejects it now. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 512), BAD_FUNC_ARG); + if (ssl_c != NULL) { + ssl_c->options.coverTrafficPadSz = MAX_RECORD_SIZE - 1; + ssl_c->options.sendCoverTraffic = 1; + } + ExpectIntEQ(wolfSSL_write(ssl_c, msg, 0), 0); + ExpectIntGT(test_ctx.s_len, emptySz); + ExpectIntLE(test_ctx.s_len, emptySz + 512); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, sizeof(reply)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = ssl_s = NULL; + ctx_c = ctx_s = NULL; +#endif /* HAVE_MAX_FRAGMENT */ + +#ifdef WOLFSSL_DTLS13 + /* Refuse for DTLS 1.3. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic(ssl_c, 100), BAD_FUNC_ARG); + ExpectIntEQ(test_ctx.s_len, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif /* WOLFSSL_DTLS13 */ +#endif + return EXPECT_RESULT(); +} +#else +static int test_wolfSSL_send_tls13_cover_traffic(void) +{ + EXPECT_DECLS; + /* Never dereferenced: TLS13 off means NOT_COMPILED_IN unconditionally. */ + ExpectIntEQ(wolfSSL_send_tls13_cover_traffic((WOLFSSL*)1, 0), + NOT_COMPILED_IN); + return EXPECT_RESULT(); +} +#endif + static int test_wolfSSL_inject(void) { EXPECT_DECLS; @@ -41135,6 +41524,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_read_ahead_coalesced), TEST_DECL(test_wolfSSL_read_ahead_buffer_len), TEST_DECL(test_wolfSSL_read_ahead_ctx_inherit), + TEST_DECL(test_wolfSSL_send_tls13_cover_traffic), TEST_DECL(test_wolfSSL_inject), TEST_DECL(test_wolfSSL_inject_partial_record), TEST_DECL(test_ocsp_status_callback),