Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions doc/dox_comments/header_files/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
59 changes: 52 additions & 7 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tls13ClearCoverTraffic() called from wolfSSL_clear() under the wrong preprocessor guard · API contract violations

Tls13ClearCoverTraffic() is defined in src/tls13.c, whose body is #if !defined(NO_TLS) && defined(WOLFSSL_TLS13), but the new call site is guarded only by #ifdef WOLFSSL_TLS13. configure.ac:1354 keeps WOLFSSL_TLS13 defined for --disable-tls, so a --disable-tls (non-cryptonly) build compiles ssl.c with an undefined reference. Other TLS-only work in the same function (e.g. TLSX_FreeAll at ssl.c:5736) is guarded with !defined(NO_TLS).

Fix: Change the guard to #if defined(WOLFSSL_TLS13) && !defined(NO_TLS).

#endif
#ifdef WOLFSSL_DTLS
ssl->options.dtlsStateful = 0;
Expand Down
110 changes: 110 additions & 0 deletions src/ssl_api_rw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
42 changes: 42 additions & 0 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading