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
38 changes: 22 additions & 16 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -31318,7 +31318,10 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz)
return WOLFSSL_FATAL_ERROR;
}
#ifdef WOLFSSL_EARLY_DATA_GROUP
groupMsgs = 1;
/* A retry of a blocked write flushes its record instead. */
if (ssl->buffers.prevSent == 0 && ssl->buffers.plainSz == 0) {
groupMsgs = 1;
}
#endif
}
else
Expand Down Expand Up @@ -31374,18 +31377,6 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz)
}
return (ssl->error = error);
}
else {
/* advance sent to previous sent + plain size just sent */
sent = ssl->buffers.prevSent + ssl->buffers.plainSz;
ssl->buffers.prevSent = 0;
ssl->buffers.plainSz = 0;
WOLFSSL_MSG("sent write buffered data");

if (sent > (word32)sz) {
WOLFSSL_MSG("error: write() after WANT_WRITE with short size");
return (ssl->error = BAD_FUNC_ARG);
}
}
}

ret = RetrySendAlert(ssl);
Expand Down Expand Up @@ -31416,9 +31407,8 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz)

#ifdef WOLFSSL_TLS13
/* A KeyUpdate response the read side left for the write side to send. It
* goes out ahead of the application data, and only once the output buffer
* is flushed, so that the record of an earlier call is accounted for above
* instead of being sent from here and then built again. */
* goes out ahead of the application data, once the output buffer is
* flushed. */
if (ssl->options.sendKeyUpdate) {
/* RFC 9846 Section 4.7.3: at the key update limit, ignore the
* request rather than fail the write. */
Expand Down Expand Up @@ -31449,6 +31439,22 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz)
}
#endif

/* The record the last write queued has been sent, by this call's flush or
* by another call such as wolfSSL_negotiate(). Taken only after the early
* returns, so a WANT_WRITE from the KeyUpdate or alert keeps the credit. */
if (ssl->buffers.outputBuffer.length == 0 &&
(ssl->buffers.prevSent != 0 || ssl->buffers.plainSz != 0)) {
sent = ssl->buffers.prevSent + ssl->buffers.plainSz;
ssl->buffers.prevSent = 0;
ssl->buffers.plainSz = 0;
WOLFSSL_MSG("sent write buffered data");

if (sent > (word32)sz) {
WOLFSSL_MSG("error: write() after WANT_WRITE with short size");
return (ssl->error = BAD_FUNC_ARG);
}
}

#ifdef WOLFSSL_THREADED_CRYPT
ret = SendAsyncData(ssl);
if (ret != 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -6040,6 +6040,8 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
ssl->buffers.clearOutputBuffer.length = 0;
ssl->buffers.inputBuffer.idx = 0;
ssl->buffers.inputBuffer.length = 0;
ssl->buffers.prevSent = 0;
ssl->buffers.plainSz = 0;
Comment thread
yosuke-wolfssl marked this conversation as resolved.
Resolved
/* Recycling the object for a new connection must not carry the
* previous connection's key material along with it. A freshly
* created object has all of this zeroed. */
Expand Down
7 changes: 6 additions & 1 deletion src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -5196,8 +5196,11 @@ int SendTls13ClientHello(WOLFSSL* ssl)
#ifdef WOLFSSL_EARLY_DATA
if (!EarlyDataPossible(ssl))
ssl->earlyData = no_early_data;
if (ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE)
if (ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE) {
ssl->earlyData = no_early_data;
ssl->buffers.prevSent = 0;
ssl->buffers.plainSz = 0;
}
if (ssl->earlyData == no_early_data)
TLSX_Remove(&ssl->extensions, TLSX_EARLY_DATA, ssl->heap);
if (ssl->earlyData != no_early_data &&
Expand Down Expand Up @@ -6601,6 +6604,8 @@ static int DoTls13EncryptedExtensions(WOLFSSL* ssl, const byte* input,
WOLFSSL_MSG("Early data rejected by server (no early_data "
"EncryptedExtensions response)");
ssl->earlyData = no_early_data;
ssl->buffers.prevSent = 0;
ssl->buffers.plainSz = 0;
}
}

Expand Down
270 changes: 270 additions & 0 deletions tests/api/test_ssl_rw.c
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,276 @@ int test_wolfSSL_clear_app_data_pending(void)
return EXPECT_RESULT();
}

/* Test that a retried write does not send again the record that
* wolfSSL_update_keys() flushed ahead of its KeyUpdate. */
int test_wolfSSL_write_retry_flushed_by_update_keys(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) \
&& !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
const char msg[] = "queued while blocked";
char reply[64];

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_simulate_want_write(&test_ctx, 1, 1);
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_WRITE);
test_memio_simulate_want_write(&test_ctx, 1, 0);

/* The KeyUpdate goes out with the queued record ahead of it. */
ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS);
if (ssl_c != NULL) {
ExpectIntEQ(ssl_c->buffers.outputBuffer.length, 0);
}
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), (int)sizeof(msg));

/* One copy only. */
XMEMSET(reply, 0, sizeof(reply));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
(int)sizeof(msg));
ExpectBufEQ(reply, msg, sizeof(msg));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

/* Test that a write left blocked on one connection does not change the first
* write after wolfSSL_clear() reuses the object. */
int test_wolfSSL_clear_write_pending(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_TLS) \
&& !defined(WOLFSSL_NO_TLS12)
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
const char msg1[] = "blocked";
const char msg2[] = "first write on the new connection";
char reply[64];

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);

/* The write blocks, then the shutdown flushes its record. */
test_memio_simulate_want_write(&test_ctx, 1, 1);
ExpectIntEQ(wolfSSL_write(ssl_c, msg1, (int)sizeof(msg1)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_WRITE);
test_memio_simulate_want_write(&test_ctx, 1, 0);
ExpectIntEQ(wolfSSL_shutdown(ssl_c), WOLFSSL_SHUTDOWN_NOT_DONE);
if (ssl_c != NULL) {
ExpectIntEQ(ssl_c->buffers.outputBuffer.length, 0);
}

/* Reuse the client object for a new connection to a new server. */
ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS);
wolfSSL_free(ssl_s);
ssl_s = NULL;
ExpectNotNull(ssl_s = wolfSSL_new(ctx_s));
wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx);
wolfSSL_SetIOReadCtx(ssl_s, &test_ctx);
#ifndef NO_DH
SetDH(ssl_s);
#endif
test_memio_clear_buffer(&test_ctx, 0);
test_memio_clear_buffer(&test_ctx, 1);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

/* The whole of the new write arrives. */
ExpectIntEQ(wolfSSL_write(ssl_c, msg2, (int)sizeof(msg2)),
(int)sizeof(msg2));
XMEMSET(reply, 0, sizeof(reply));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
(int)sizeof(msg2));
ExpectBufEQ(reply, msg2, sizeof(msg2));

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
struct test_write_retry_block_ctx {
struct test_memio_ctx* test_ctx;
int calls;
};

/* Return WANT_WRITE on the first and third send, pass the rest to memio. */
static int test_write_retry_block_send_cb(WOLFSSL* ssl, char* data, int sz,
void* ctx)
{
struct test_write_retry_block_ctx* wctx =
(struct test_write_retry_block_ctx*)ctx;

wctx->calls++;
if (wctx->calls == 1 || wctx->calls == 3) {
return WOLFSSL_CBIO_ERR_WANT_WRITE;
}
return test_memio_write_cb(ssl, data, sz, wctx->test_ctx);
}
#endif

/* Test that a retried write does not send again a record that went out before
* the deferred KeyUpdate reply blocked. */
int test_wolfSSL_write_retry_keyupdate_blocked(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
struct test_write_retry_block_ctx wctx;
const char msg[] = "sent before the KeyUpdate reply";
char reply[64];

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(&wctx, 0, sizeof(wctx));
wctx.test_ctx = &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);
wolfSSL_SSLSetIOSend(ssl_c, test_write_retry_block_send_cb);
wolfSSL_SetIOWriteCtx(ssl_c, &wctx);

ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_WRITE);

/* The read leaves the KeyUpdate reply to the next write. */
ExpectIntEQ(wolfSSL_update_keys(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
if (ssl_c != NULL) {
ExpectIntEQ(ssl_c->options.sendKeyUpdate, 1);
}

/* The record goes out, then the KeyUpdate reply blocks. */
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_WRITE);
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), (int)sizeof(msg));
/* Record, record, KeyUpdate, KeyUpdate: no new record. */
ExpectIntEQ(wctx.calls, 4);

/* One copy only. */
XMEMSET(reply, 0, sizeof(reply));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
(int)sizeof(msg));
ExpectBufEQ(reply, msg, sizeof(msg));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

/* Test that a retried write does not send again a record that went out before
* a pending alert blocked. */
int test_wolfSSL_write_retry_alert_blocked(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_TLS13) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
struct test_write_retry_block_ctx wctx;
const char msg[] = "sent before the pending alert";
char reply[64];

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(&wctx, 0, sizeof(wctx));
wctx.test_ctx = &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);
wolfSSL_SSLSetIOSend(ssl_c, test_write_retry_block_send_cb);
wolfSSL_SetIOWriteCtx(ssl_c, &wctx);

ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_WRITE);

/* A warning alert that leaves the connection open is waiting to go. */
if (ssl_c != NULL) {
ssl_c->pendingAlert.code = user_canceled;
ssl_c->pendingAlert.level = alert_warning;
}

/* The record goes out, then the alert blocks. */
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_WRITE);
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), (int)sizeof(msg));
/* Record, record, alert, alert: no new record. */
ExpectIntEQ(wctx.calls, 4);

/* One copy only. */
XMEMSET(reply, 0, sizeof(reply));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
(int)sizeof(msg));
ExpectBufEQ(reply, msg, sizeof(msg));
ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)),
WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

/* Test that a renegotiation does not read input while decrypted application
* data is still waiting to be read.
*
Expand Down
9 changes: 9 additions & 0 deletions tests/api/test_ssl_rw.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ int test_wolfSSL_write_ex_partial(void);
int test_wolfSSL_inject_app_data_ready(void);
int test_wolfSSL_inject_app_data_partial_record(void);
int test_wolfSSL_clear_app_data_pending(void);
int test_wolfSSL_write_retry_flushed_by_update_keys(void);
int test_wolfSSL_clear_write_pending(void);
int test_wolfSSL_write_retry_keyupdate_blocked(void);
int test_wolfSSL_write_retry_alert_blocked(void);
int test_wolfSSL_rehandshake_app_data_pending(void);
int test_wolfSSL_rehandshake_app_data_partial_record(void);
int test_wolfSSL_accept_early_data_pending(void);
Expand Down Expand Up @@ -65,6 +69,11 @@ int test_wolfSSL_write_dup_max_fragment(void);
TEST_DECL_GROUP("ssl_rw", \
test_wolfSSL_inject_app_data_partial_record), \
TEST_DECL_GROUP("ssl_rw", test_wolfSSL_clear_app_data_pending), \
TEST_DECL_GROUP("ssl_rw", \
test_wolfSSL_write_retry_flushed_by_update_keys), \
TEST_DECL_GROUP("ssl_rw", test_wolfSSL_clear_write_pending), \
TEST_DECL_GROUP("ssl_rw", test_wolfSSL_write_retry_keyupdate_blocked), \
TEST_DECL_GROUP("ssl_rw", test_wolfSSL_write_retry_alert_blocked), \
TEST_DECL_GROUP("ssl_rw", test_wolfSSL_rehandshake_app_data_pending), \
TEST_DECL_GROUP("ssl_rw", \
test_wolfSSL_rehandshake_app_data_partial_record), \
Expand Down
Loading
Loading