Skip to content
Merged
5 changes: 5 additions & 0 deletions .github/workflows/async.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name: Async Tests

# START OF COMMON SECTION
Expand Down Expand Up @@ -71,6 +71,11 @@
run: |
cat > "$RUNNER_TEMP/async-configs.json" <<'EOF'
[
{"comment": "The only entry that pairs the software async simulator with --enable-all. --enable-all turns on cryptocb, which stops configure.ac from auto-enabling the simulator, so the asynccrypt-all entries below define WOLFSSL_ASYNC_CRYPT but never actually return WC_PENDING_E. Without this one nothing exercises TLS 1.3 post-handshake auth or DTLS writes against a pending crypto op. The minutes value is a projection, not a CI measurement: this config takes 1.6 min locally where the asynccrypt-all entries below take 1.4 against their declared 3. Refresh it from the first real run.",
"name": "asynccrypt-sw-all-dtls13", "minutes": 3,
"configure": ["--enable-asynccrypt-sw", "--enable-all",
"--enable-dtls13",
"CFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"]},
{"name": "asynccrypt-all-no-mlkem", "minutes": 3,
"configure": ["--enable-asynccrypt", "--enable-all",
"--enable-dtls13", "--disable-mlkem",
Expand Down
61 changes: 55 additions & 6 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -6177,9 +6177,11 @@ int EccVerify(WOLFSSL* ssl, const byte* in, word32 inSz, const byte* out,

#ifdef WOLFSSL_ASYNC_CRYPT
/* initialize event */
ret = wolfSSL_AsyncInit(ssl, &key->asyncDev, WC_ASYNC_FLAG_CALL_AGAIN);
if (ret != 0)
return ret;
if (key) {
ret = wolfSSL_AsyncInit(ssl, &key->asyncDev, WC_ASYNC_FLAG_CALL_AGAIN);
if (ret != 0)
return ret;
}
#endif

#ifdef HAVE_PK_CALLBACKS
Expand All @@ -6203,7 +6205,11 @@ int EccVerify(WOLFSSL* ssl, const byte* in, word32 inSz, const byte* out,
/* Handle async pending response */
#ifdef WOLFSSL_ASYNC_CRYPT
if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) {
ret = wolfSSL_AsyncPush(ssl, &key->asyncDev);
/* with a PK callback the private key can live only in the callback,
* leaving no async device to push */
if (key != NULL) {
ret = wolfSSL_AsyncPush(ssl, &key->asyncDev);
}
}
else
#endif /* WOLFSSL_ASYNC_CRYPT */
Expand Down Expand Up @@ -25480,6 +25486,29 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
ssl->buffers.inputBuffer.buffer,
&ssl->buffers.inputBuffer.idx,
ssl->curStartIdx + ssl->curSize);
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_POST_HANDSHAKE_AUTH)
/* Post-handshake auth resumes through
* wolfSSL_negotiate() instead of reprocessing this
* record, so it leaves processReply at doProcessInit
* (an ordinary pending message leaves it at
* runProcessingOneMessage). Finish the record here or
* the trailing MAC is read as the next record header
* and fails with VERSION_ERROR. Mirrors the end of
* record block below: resume inside the record when
* content is left, else skip the padding. */
if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) &&
ssl->options.processReply == doProcessInit) {
if ((ssl->buffers.inputBuffer.idx -
ssl->curStartIdx) < ssl->curSize) {
ssl->options.processReply =
runProcessingOneMessage;
}
else if (IsEncryptionOn(ssl, 0)) {
Comment thread
Frauschi marked this conversation as resolved.
ssl->buffers.inputBuffer.idx +=
ssl->keys.padSz;
}
}
#endif
#ifdef WOLFSSL_EARLY_DATA
if (ret != 0)
return ret;
Expand Down Expand Up @@ -26254,6 +26283,10 @@ int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, const byte* input,
if (ret == WC_NO_ERR_TRACE(WC_NO_PENDING_E))
#endif
{
/* Note: these hit ssl->options even for a sizeOnly probe, where every
* other result goes to lcl_args, so a probe destroys the resume point
* of a suspended asynchronous build. wolfssl_local_GetRecordSize() is
* the only sizeOnly caller and restores them; a new one must too. */
ret = 0;
#ifdef WOLFSSL_ASYNC_CRYPT
ssl->options.buildArgsSet = 1;
Expand All @@ -26271,7 +26304,10 @@ int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, const byte* input,
case BUILD_MSG_BEGIN:
{
#if defined(WOLFSSL_DTLS) && defined(HAVE_SECURE_RENEGOTIATION)
if (ssl->options.dtls && DtlsSCRKeysSet(ssl)) {
/* Skipped for a size probe: the size is the same either way, and
* SetKeysSide() would swap the active encryption state and clear
* recordSzOverhead under a suspended asynchronous build. */
if (!sizeOnly && ssl->options.dtls && DtlsSCRKeysSet(ssl)) {
/* For epochs >1 the current cipher parameters are located in
* ssl->secure_renegotiation->tmp_keys. Previous cipher
* parameters and for epoch 1 use ssl->keys */
Expand Down Expand Up @@ -45157,7 +45193,10 @@ int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz, int isEncrypted)
#ifdef WOLFSSL_DTLS13
int isDtls13 = ssl->options.dtls && ssl->options.tls1_3;
#endif

#ifdef WOLFSSL_ASYNC_CRYPT
byte savedBuildMsgState = ssl->options.buildMsgState;
byte savedBuildArgsSet = ssl->options.buildArgsSet;
#endif
if (ssl->specs.cipher_type == aead && ssl->recordSzOverhead != 0
#ifdef WOLFSSL_DTLS13
&& (!isDtls13 || payloadSz + (int)ssl->recordSzOverhead
Expand All @@ -45169,6 +45208,16 @@ int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz, int isEncrypted)

recordSz = BuildMessage(ssl, NULL, 0, NULL, payloadSz, application_data,
0, 1, 0, CUR_ORDER);
#ifdef WOLFSSL_ASYNC_CRYPT
/* Sizing shares the build state machine with an asynchronous
* BuildMessage that SendData() re-sizes on every retry, so the probe
* runs while that record is suspended. Restore its resume point, or
* the record is sized twice and rejected with BUFFER_E. Skipping the
* probe is not an option: wolfssl_local_GetMaxPlaintextSize() derives
* the DTLS fragment size from this result, so it must stay exact. */
ssl->options.buildMsgState = savedBuildMsgState;
ssl->options.buildArgsSet = savedBuildArgsSet;
#endif
/* use a safe upper bound in case of error */
if (recordSz < 0) {
recordSz = payloadSz + RECORD_HEADER_SZ
Expand Down
4 changes: 4 additions & 0 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -3366,6 +3366,10 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input,
if (ret == WC_NO_ERR_TRACE(WC_NO_PENDING_E))
#endif
{
/* Note: these hit ssl->options even for a sizeOnly probe, where every
* other result goes to lcl_args, so a probe destroys the resume point
* of a suspended asynchronous build. wolfssl_local_GetRecordSize() is
* the only sizeOnly caller and restores them; a new one must too. */
ret = 0;
ssl->options.buildMsgState = BUILD_MSG_BEGIN;
XMEMSET(args, 0, sizeof(BuildMsg13Args));
Expand Down
80 changes: 80 additions & 0 deletions tests/api/test_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -2927,6 +2927,86 @@ int test_record_size_matches_build_message(void)
return EXPECT_RESULT();
}

#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_ASYNC_CRYPT)
/* SendData() sizes the output buffer on every retry, including while an
* asynchronous BuildMessage is suspended part way through a record. Sizing
* runs the same build state machine, so the probe must not re-enter it: it
* would rewind buildMsgState, clear buildArgsSet, and the resumed record would
* be sized a second time. Check that a suspended build survives a probe, and
* that a probe from a clean state still returns the exact record size. */
static int record_size_state_check(method_provider client_method,
method_provider server_method)
{
EXPECT_DECLS;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
int expectedSz = 0, cleanSz = 0, busySz = 0;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
client_method, server_method), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

if (ssl_c != NULL) {
expectedSz = BuildMessage(ssl_c, NULL, 0, NULL, 256,
application_data, 0, 1, 0, CUR_ORDER);
ssl_c->options.buildMsgState = BUILD_MSG_BEGIN;
ssl_c->options.buildArgsSet = 0;
ExpectIntGT(expectedSz, 256);

/* Clearing the cache is what forces the BuildMessage path; an AEAD
* suite would otherwise answer from ssl->recordSzOverhead and the
* assertions below would hold no matter what the probe did. */
ssl_c->recordSzOverhead = 0;
cleanSz = wolfssl_local_GetRecordSize(ssl_c, 256, 1);
ExpectIntEQ(cleanSz, expectedSz);

/* Same probe with a build suspended mid-record. */
ssl_c->recordSzOverhead = 0;
ssl_c->options.buildMsgState = BUILD_MSG_ENCRYPT;
ssl_c->options.buildArgsSet = 1;

busySz = wolfssl_local_GetRecordSize(ssl_c, 256, 1);

ExpectIntEQ(ssl_c->options.buildMsgState, BUILD_MSG_ENCRYPT);
ExpectIntEQ(ssl_c->options.buildArgsSet, 1);
/* Still exact: wolfssl_local_GetMaxPlaintextSize() sizes DTLS
* fragments from this, so it may not degrade to an upper bound. */
ExpectIntEQ(busySz, expectedSz);

ssl_c->options.buildMsgState = BUILD_MSG_BEGIN;
ssl_c->options.buildArgsSet = 0;
}

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
return EXPECT_RESULT();
}
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES && WOLFSSL_ASYNC_CRYPT */

int test_record_size_preserves_build_msg_state(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(WOLFSSL_ASYNC_CRYPT)
#ifndef WOLFSSL_NO_TLS12
ExpectIntEQ(record_size_state_check(wolfTLSv1_2_client_method,
wolfTLSv1_2_server_method), TEST_SUCCESS);
#endif
#ifdef WOLFSSL_TLS13
/* BuildTls13Message() clobbers buildMsgState by a different route: its
* sizeOnly return bypasses exit_buildmsg entirely. */
ExpectIntEQ(record_size_state_check(wolfTLSv1_3_client_method,
wolfTLSv1_3_server_method), TEST_SUCCESS);
#endif
#endif
return EXPECT_RESULT();
}

int test_record_size_cache_invalidated_on_renegotiation(void)
{
EXPECT_DECLS;
Expand Down
3 changes: 3 additions & 0 deletions tests/api/test_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ int test_tls12_ecdhe_rsa_ecdsa_client_cert(void);
int test_wolfSSL_alert_type_string(void);
int test_wolfSSL_alert_desc_string(void);
int test_record_size_matches_build_message(void);
int test_record_size_preserves_build_msg_state(void);
int test_record_size_cache_invalidated_on_renegotiation(void);
int test_wolfSSL_get_shared_ciphers(void);

Expand Down Expand Up @@ -101,6 +102,8 @@ int test_wolfSSL_get_shared_ciphers(void);
TEST_DECL_GROUP("tls", test_wolfSSL_alert_type_string), \
TEST_DECL_GROUP("tls", test_wolfSSL_alert_desc_string), \
TEST_DECL_GROUP("tls", test_record_size_matches_build_message), \
TEST_DECL_GROUP("tls", \
test_record_size_preserves_build_msg_state), \
TEST_DECL_GROUP("tls", \
test_record_size_cache_invalidated_on_renegotiation), \
TEST_DECL_GROUP("tls", test_wolfSSL_get_shared_ciphers)
Expand Down
8 changes: 8 additions & 0 deletions wolfcrypt/src/eccsi.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ int wc_MakeEccsiKey(EccsiKey* key, WC_RNG* rng)
if (err == 0) {
err = wc_ecc_make_key_ex(rng, key->ecc.dp->size, &key->ecc,
key->ecc.dp->id);
#ifdef WOLFSSL_ASYNC_CRYPT
/* ECCSI has no asynchronous API, so the caller cannot resume a pending
* key generation - complete it here. The key->pubkey sites in
* eccsi_make_pair() and eccsi_gen_sig() need no wait: each is preceded
* by wc_ecc_free(&key->pubkey), which clears the marker that
* _ecc_make_key_ex() gates its pending path on. */
err = wc_AsyncWait(err, &key->ecc.asyncDev, WC_ASYNC_FLAG_NONE);
#endif
}

return err;
Expand Down
9 changes: 9 additions & 0 deletions wolfcrypt/test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -45991,8 +45991,14 @@ static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng)
userA->devId = INVALID_DEVID;
userB->devId = INVALID_DEVID;
ret = wc_ecc_make_key(rng, ECC_KEYGEN_SIZE, userA);
#if defined(WOLFSSL_ASYNC_CRYPT)
ret = wc_AsyncWait(ret, &userA->asyncDev, WC_ASYNC_FLAG_NONE);
#endif
if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; }
ret = wc_ecc_make_key(rng, ECC_KEYGEN_SIZE, userB);
#if defined(WOLFSSL_ASYNC_CRYPT)
ret = wc_AsyncWait(ret, &userB->asyncDev, WC_ASYNC_FLAG_NONE);
#endif
if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; }
#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \
(!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \
Expand Down Expand Up @@ -81229,6 +81235,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void)
if (ret == 0) {
haveSrc = 1;
ret = wc_ecc_make_key(eccRng, 32, srcKey);
#if defined(WOLFSSL_ASYNC_CRYPT)
ret = wc_AsyncWait(ret, &srcKey->asyncDev, WC_ASYNC_FLAG_NONE);
#endif
}
if (ret == 0) {
outPub = wc_ecc_new_point_h(HEAP_HINT);
Expand Down
Loading