From 2f09bd1ce63b331c821b49543bde3ce46f57b669 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 20 Aug 2026 18:57:16 -0700 Subject: [PATCH 1/7] Add crypto callback WC_PENDING_E support to wolfCrypt HKDF --- doc/dox_comments/header_files/hmac.h | 41 ++++++++ wolfcrypt/src/hmac.c | 46 ++++++++- wolfcrypt/test/test.c | 141 +++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 5 deletions(-) diff --git a/doc/dox_comments/header_files/hmac.h b/doc/dox_comments/header_files/hmac.h index b4ef2537859..45f3a9e55dc 100644 --- a/doc/dox_comments/header_files/hmac.h +++ b/doc/dox_comments/header_files/hmac.h @@ -245,6 +245,19 @@ int wc_HKDF_Extract( \return HMAC_MIN_KEYLEN_E May be returned when using a FIPS implementation and the key length specified is shorter than the minimum acceptable FIPS standard + \return WC_PENDING_E May be returned in a WOLF_CRYPTO_CB build when the + registered crypto callback device has taken the request but not yet + finished it. The caller must re-invoke with identical arguments until the + result is no longer WC_PENDING_E; HKDF has no WC_ASYNC_DEV, so this is a + poll and not a wc_AsyncWait(). In a WOLFSSL_ASYNC_CRYPT build the TLS 1.3 + key schedule resumes a pending HKDF request by re-invoking the callback + with identical arguments; without WOLFSSL_ASYNC_CRYPT a device also used + for TLS 1.3 must complete HKDF requests synchronously. wc_HKDF_ex() + follows the same contract and re-issues its extract step on every retry, + so a device that pends must serve a repeated identical request from its + completed result. There is no request handle: a device should key its + completion tracking on the output pointer plus the argument tuple, and + one that cannot correlate a retry that way must not pend. \param type hash type to use for the HKDF. Valid types are: WC_MD5, WC_SHA, WC_SHA256, WC_SHA384, WC_SHA512, WC_SHA3_224, WC_SHA3_256, WC_SHA3_384 or @@ -357,6 +370,19 @@ int wc_HKDF_Expand( \return HMAC_MIN_KEYLEN_E May be returned when using a FIPS implementation and the key length specified is shorter than the minimum acceptable FIPS standard + \return WC_PENDING_E May be returned in a WOLF_CRYPTO_CB build when the + registered crypto callback device has taken the request but not yet + finished it. The caller must re-invoke with identical arguments until the + result is no longer WC_PENDING_E; HKDF has no WC_ASYNC_DEV, so this is a + poll and not a wc_AsyncWait(). In a WOLFSSL_ASYNC_CRYPT build the TLS 1.3 + key schedule resumes a pending HKDF request by re-invoking the callback + with identical arguments; without WOLFSSL_ASYNC_CRYPT a device also used + for TLS 1.3 must complete HKDF requests synchronously. wc_HKDF_ex() + follows the same contract and re-issues its extract step on every retry, + so a device that pends must serve a repeated identical request from its + completed result. There is no request handle: a device should key its + completion tracking on the output pointer plus the argument tuple, and + one that cannot correlate a retry that way must not pend. \param type hash type to use for the HKDF. Valid types are: WC_MD5, WC_SHA, WC_SHA256, WC_SHA384, WC_SHA512, WC_SHA3_224, WC_SHA3_256, WC_SHA3_384 or @@ -459,6 +485,11 @@ int wc_Tls13_HKDF_Extract( \return HMAC_MIN_KEYLEN_E May be returned when using a FIPS implementation and the key length specified is shorter than the minimum acceptable FIPS standard + \return WC_PENDING_E May be returned in a WOLF_CRYPTO_CB build when the + registered crypto callback device has taken the request but not yet + finished it; the caller re-invokes with identical arguments until the + result is no longer WC_PENDING_E. The TLS 1.3 key schedule does this in + WOLFSSL_ASYNC_CRYPT builds. \param prk Generated pseudorandom key \param salt Salt. May be NULL; saltLen is then ignored unless a crypto @@ -510,6 +541,11 @@ int wc_Tls13_HKDF_Extract_ex( \return HMAC_MIN_KEYLEN_E May be returned when using a FIPS implementation and the key length specified is shorter than the minimum acceptable FIPS standard + \return WC_PENDING_E May be returned in a WOLF_CRYPTO_CB build when the + registered crypto callback device has taken the request but not yet + finished it; the caller re-invokes with identical arguments until the + result is no longer WC_PENDING_E. The TLS 1.3 key schedule does this in + WOLFSSL_ASYNC_CRYPT builds. \param okm Generated pseudorandom key - output key material. \param okmLen Length of generated pseudorandom key - output key material. @@ -591,6 +627,11 @@ int wc_Tls13_HKDF_Expand_Label( \return HMAC_MIN_KEYLEN_E May be returned when using a FIPS implementation and the key length specified is shorter than the minimum acceptable FIPS standard + \return WC_PENDING_E May be returned in a WOLF_CRYPTO_CB build when the + registered crypto callback device has taken the request but not yet + finished it; the caller re-invokes with identical arguments until the + result is no longer WC_PENDING_E. The TLS 1.3 key schedule does this in + WOLFSSL_ASYNC_CRYPT builds. \param okm Generated pseudorandom key - output key material. \param okmLen Length of generated pseudorandom key - output key material. diff --git a/wolfcrypt/src/hmac.c b/wolfcrypt/src/hmac.c index 60d29f30e03..cabcfa4164f 100644 --- a/wolfcrypt/src/hmac.c +++ b/wolfcrypt/src/hmac.c @@ -1774,6 +1774,26 @@ int wolfSSL_GetHmacMaxSize(void) } #ifdef HAVE_HKDF + /* Wait out an async HMAC sub-op: the HKDF loops cannot resume + * mid-chain. QAT/Cavium only (not covered by CI); a crypto callback + * pending must instead propagate so the caller can re-invoke. */ +#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_HMAC) && \ + (defined(HAVE_INTEL_QA) || defined(HAVE_CAVIUM)) + #define HKDF_HMAC_WAIT(ret, hmac) \ + do { \ + /* A callback pending (devId set) must propagate: the device \ + * poll cannot complete it. */ \ + if ((hmac)->devId == INVALID_DEVID) { \ + (ret) = wc_AsyncWait((ret), &(hmac)->asyncDev, \ + WC_ASYNC_FLAG_NONE); \ + } \ + } while (0) +#else + /* No HMAC device to wait on, or the pending must reach the caller + * (crypto callback re-invocation); hmac is unevaluated. */ + #define HKDF_HMAC_WAIT(ret, hmac) WC_DO_NOTHING +#endif + /* HMAC-KDF-Extract. * RFC 5869 - HMAC-based Extract-and-Expand Key Derivation Function (HKDF). * @@ -1799,7 +1819,9 @@ int wolfSSL_GetHmacMaxSize(void) } #ifdef WOLF_CRYPTO_CB - /* Try crypto callback first */ + /* Try crypto callback first. Only CRYPTOCB_UNAVAILABLE falls back + * to software. WC_PENDING_E is returned as-is: the caller polls, + * re-invoking with identical arguments until it clears. */ if (devId != INVALID_DEVID) { ret = wc_CryptoCb_Hkdf_Extract(type, salt, saltSz, inKey, inKeySz, out, devId); @@ -1832,10 +1854,14 @@ int wolfSSL_GetHmacMaxSize(void) #else ret = wc_HmacSetKey(myHmac, type, localSalt, saltSz); #endif - if (ret == 0) + if (ret == 0) { ret = wc_HmacUpdate(myHmac, inKey, inKeySz); - if (ret == 0) + HKDF_HMAC_WAIT(ret, myHmac); + } + if (ret == 0) { ret = wc_HmacFinal(myHmac, out); + HKDF_HMAC_WAIT(ret, myHmac); + } wc_HmacFree(myHmac); } WC_FREE_VAR_EX(myHmac, NULL, DYNAMIC_TYPE_HMAC); @@ -1891,7 +1917,8 @@ int wolfSSL_GetHmacMaxSize(void) return BAD_FUNC_ARG; #ifdef WOLF_CRYPTO_CB - /* Try crypto callback first for complete operation */ + /* Try crypto callback first. WC_PENDING_E is returned to the + * caller to poll, as in wc_HKDF_Extract_ex(). */ if (devId != INVALID_DEVID) { ret = wc_CryptoCb_Hkdf_Expand(type, inKey, inKeySz, info, infoSz, out, outSz, devId); @@ -1927,15 +1954,19 @@ int wolfSSL_GetHmacMaxSize(void) if (ret != 0) break; ret = wc_HmacUpdate(myHmac, tmp, tmpSz); + HKDF_HMAC_WAIT(ret, myHmac); if (ret != 0) break; ret = wc_HmacUpdate(myHmac, info, infoSz); + HKDF_HMAC_WAIT(ret, myHmac); if (ret != 0) break; ret = wc_HmacUpdate(myHmac, &n, 1); + HKDF_HMAC_WAIT(ret, myHmac); if (ret != 0) break; ret = wc_HmacFinal(myHmac, tmp); + HKDF_HMAC_WAIT(ret, myHmac); if (ret != 0) break; @@ -1988,7 +2019,8 @@ int wolfSSL_GetHmacMaxSize(void) (void)devId; /* suppress unused parameter warning */ #ifdef WOLF_CRYPTO_CB - /* Try crypto callback first for complete operation */ + /* Try crypto callback first. WC_PENDING_E is returned to the + * caller to poll, as in wc_HKDF_Extract_ex(). */ if (devId != INVALID_DEVID) { ret = wc_CryptoCb_Hkdf(type, inKey, inKeySz, salt, saltSz, info, infoSz, out, outSz, devId); @@ -2006,6 +2038,8 @@ int wolfSSL_GetHmacMaxSize(void) XMEMSET(prk, 0, WC_MAX_DIGEST_SIZE); wc_MemZero_Add("wc_HKDF_ex prk", prk, WC_MAX_DIGEST_SIZE); #endif + /* Restartable, not resumable: the retry redoes extract, so a + * device that pends must serve the repeat from its result. */ ret = wc_HKDF_Extract_ex(type, salt, saltSz, inKey, inKeySz, prk, heap, devId); if (ret == 0) { @@ -2027,6 +2061,8 @@ int wolfSSL_GetHmacMaxSize(void) outSz, NULL, INVALID_DEVID); } +#undef HKDF_HMAC_WAIT + #endif /* HAVE_HKDF */ #endif /* NO_HMAC */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index ec68a860936..6606d0cded3 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -79535,6 +79535,10 @@ typedef struct { #if defined(WC_RSA_PSS) && defined(WOLF_CRYPTO_CB_RSA_PAD) int rsaPssVerifyCount; /* RSA-PSS verify callback invocations */ #endif +#if defined(HAVE_HKDF) && !defined(NO_HMAC) + int hkdfPendArm; /* pend the next this-many HKDF callback calls */ + int hkdfPendCount; /* pends issued; test asserts non-zero */ +#endif } myCryptoDevCtx; #ifdef WOLF_CRYPTO_CB_ONLY_RSA @@ -82823,6 +82827,17 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) #endif /* WOLFSSL_CMAC && !(NO_AES) && WOLFSSL_AES_DIRECT */ else if (info->algo_type == WC_ALGO_TYPE_KDF) { #if defined(HAVE_HKDF) && !defined(NO_HMAC) + /* Simulate a device that queues the request and completes it on a + * later call, so the caller has to poll. */ + if (myCtx->hkdfPendArm > 0 && + (info->kdf.type == WC_KDF_TYPE_HKDF || + info->kdf.type == WC_KDF_TYPE_HKDF_EXTRACT || + info->kdf.type == WC_KDF_TYPE_HKDF_EXPAND)) { + myCtx->hkdfPendArm--; + myCtx->hkdfPendCount++; + return WC_PENDING_E; + } + if (info->kdf.type == WC_KDF_TYPE_HKDF) { /* Redirect to software implementation for testing */ #if !defined(HAVE_SELFTEST) && \ @@ -83012,6 +83027,121 @@ static int myCryptoCbFind(int currentId, int algoType) } #endif /* WOLF_CRYPTO_CB_FIND */ +#if defined(HAVE_HKDF) && !defined(NO_HMAC) && \ + !defined(NO_SHA256) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) + +/* Bound retries so a broken contract fails instead of spinning. */ +#define HKDF_CB_MAX_POLL 16 + +/* Drive the HKDF crypto callbacks against a device that pends first: the + * caller re-invokes with identical arguments until WC_PENDING_E clears. + * Vectors are RFC 5869 appendix A.1 (test case 1, SHA-256). */ +static wc_test_ret_t hkdf_cryptocb_async_test(myCryptoDevCtx* ctx) +{ + wc_test_ret_t ret = 0; + int rc; + int polls; + byte prk[WC_SHA256_DIGEST_SIZE]; + byte okm[42]; + WOLFSSL_SMALL_STACK_STATIC const byte ikm[22] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }; + WOLFSSL_SMALL_STACK_STATIC const byte salt[13] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c }; + WOLFSSL_SMALL_STACK_STATIC const byte info[10] = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9 }; + WOLFSSL_SMALL_STACK_STATIC const byte expectedPrk[WC_SHA256_DIGEST_SIZE] + = { + 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, + 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63, + 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, + 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 }; + WOLFSSL_SMALL_STACK_STATIC const byte expected[42] = { + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, + 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, + 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, + 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, + 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, + 0x58, 0x65 }; + + /* Three pends, so four passes: proves the caller loops, not retries + * exactly once. */ + ctx->hkdfPendArm = 3; + ctx->hkdfPendCount = 0; + polls = 0; + do { + rc = wc_HKDF_Extract_ex(WC_SHA256, salt, (word32)sizeof(salt), + ikm, (word32)sizeof(ikm), prk, + HEAP_HINT, devId); + polls++; + } while (rc == WC_NO_ERR_TRACE(WC_PENDING_E) && polls < HKDF_CB_MAX_POLL); + if (rc != 0) + ret = WC_TEST_RET_ENC_EC(rc); + else if (polls != 4) + ret = WC_TEST_RET_ENC_NC; + else if (XMEMCMP(prk, expectedPrk, sizeof(prk)) != 0) + ret = WC_TEST_RET_ENC_NC; + if (ret != 0) + goto exit_hkdf_async; + + /* Expand the PRK, pending three times as well. */ + ctx->hkdfPendArm = 3; + ctx->hkdfPendCount = 0; + polls = 0; + do { + rc = wc_HKDF_Expand_ex(WC_SHA256, prk, (word32)sizeof(prk), + info, (word32)sizeof(info), okm, + (word32)sizeof(okm), HEAP_HINT, devId); + polls++; + } while (rc == WC_NO_ERR_TRACE(WC_PENDING_E) && polls < HKDF_CB_MAX_POLL); + if (rc != 0) + ret = WC_TEST_RET_ENC_EC(rc); + else if (polls != 4) + ret = WC_TEST_RET_ENC_NC; + else if (XMEMCMP(okm, expected, sizeof(okm)) != 0) + ret = WC_TEST_RET_ENC_NC; + if (ret != 0) + goto exit_hkdf_async; + + /* Same vector through the one-shot wc_HKDF_ex(). */ + XMEMSET(okm, 0, sizeof(okm)); + ctx->hkdfPendArm = 1; + ctx->hkdfPendCount = 0; + polls = 0; + do { + rc = wc_HKDF_ex(WC_SHA256, ikm, (word32)sizeof(ikm), + salt, (word32)sizeof(salt), + info, (word32)sizeof(info), + okm, (word32)sizeof(okm), HEAP_HINT, devId); + polls++; + } while (rc == WC_NO_ERR_TRACE(WC_PENDING_E) && polls < HKDF_CB_MAX_POLL); + if (rc != 0) + ret = WC_TEST_RET_ENC_EC(rc); + else if (polls != 2) + ret = WC_TEST_RET_ENC_NC; + else if (XMEMCMP(okm, expected, sizeof(okm)) != 0) + ret = WC_TEST_RET_ENC_NC; + /* Counter is reset per leg, so an earlier leg cannot satisfy this. */ + else if (ctx->hkdfPendCount == 0) + ret = WC_TEST_RET_ENC_NC; + +exit_hkdf_async: + /* Disarm on every path, or a failing leg would leave the simulated + * device injecting WC_PENDING_E into later HKDF requests. */ + ctx->hkdfPendArm = 0; + + return ret; +} + +#undef HKDF_CB_MAX_POLL + +#endif /* HAVE_HKDF && !NO_HMAC && !NO_SHA256 && !HAVE_SELFTEST && ... */ + #if !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) @@ -83042,6 +83172,12 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #if defined(WC_RSA_PSS) && defined(WOLF_CRYPTO_CB_RSA_PAD) myCtx.rsaPssVerifyCount = 0; #endif +#if defined(HAVE_HKDF) && !defined(NO_HMAC) + /* myCtx is uninitialized stack: a garbage arm would inject + * WC_PENDING_E into callers that are not polling. */ + myCtx.hkdfPendArm = 0; + myCtx.hkdfPendCount = 0; +#endif /* set devId to something other than INVALID_DEVID */ devId = 1; @@ -83499,6 +83635,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #if defined(HAVE_HKDF) && !defined(NO_HMAC) if (ret == 0) ret = hkdf_test(); +#if !defined(NO_SHA256) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) + if (ret == 0) + ret = hkdf_cryptocb_async_test(&myCtx); +#endif #endif #if defined(HAVE_CMAC_KDF) if (ret == 0) From 59ebd02738234848f965770ac89acb290d3611c1 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 20 Aug 2026 18:57:16 -0700 Subject: [PATCH 2/7] Add TLS 1.3 handshake support for crypto callbacks returning WC_PENDING_E --- configure.ac | 7 +- src/internal.c | 55 ++- src/ssl.c | 22 + src/tls.c | 149 +++--- src/tls13.c | 935 ++++++++++++++++++++++++++--------- wolfcrypt/src/cryptocb.c | 5 +- wolfssl/internal.h | 95 +++- wolfssl/wolfcrypt/settings.h | 5 +- 8 files changed, 969 insertions(+), 304 deletions(-) diff --git a/configure.ac b/configure.ac index 48993b57edc..613eda0e65f 100644 --- a/configure.ac +++ b/configure.ac @@ -11686,8 +11686,9 @@ then fi fi -# Crypto callbacks with async crypt may not work for TLS unless -# WOLF_CRYPTO_CB_ASYNC_POLL is defined. Report it once here and silence the +# Crypto callbacks with async crypt cannot complete TLS 1.2 record ciphers +# unless WOLF_CRYPTO_CB_ASYNC_POLL is defined (TLS 1.3 resumes them by +# re-invoking the callback). Report it once here and silence the # source-level #warning. AC_MSG_NOTICE, not AC_MSG_WARN: the multi-test # harness fails any scenario whose configure emits "configure: WARNING:". if test "$ENABLED_ASYNCCRYPT" = "yes" && test "x$ENABLED_CRYPTOCB" != "xno" && @@ -11698,7 +11699,7 @@ then *WOLF_CRYPTO_CB_ASYNC_POLL*) ;; *) - AC_MSG_NOTICE([crypto callbacks with async crypt may not work for TLS. Define WOLF_CRYPTO_CB_ASYNC_POLL to enable it.]) + AC_MSG_NOTICE([crypto callbacks with async crypt cannot complete TLS 1.2 record ciphers. Define WOLF_CRYPTO_CB_ASYNC_POLL to enable them.]) AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_ASYNC_NO_WARN" ;; esac diff --git a/src/internal.c b/src/internal.c index afa472608b6..a8b605a8e09 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9432,6 +9432,11 @@ void FreeAsyncCtx(WOLFSSL* ssl, byte freeAsync) } #endif if (freeAsync) { +#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_TLS13) + /* Teardown only: a suspended record build must keep its + * resume marker across handler-tail cleanups. */ + ssl->options.buildArgs13Set = 0; +#endif XFREE(ssl->async, ssl->heap, DYNAMIC_TYPE_ASYNC); ssl->async = NULL; } @@ -19151,6 +19156,12 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, return ret; } + /* TLS 1.3 replays skip the sanity check that re-sets got_certificate; + * restore on completion or Finished reports out-of-order. */ + if (ret == 0 && IsAtLeastTLSv1_3(ssl->version) && + ssl->msgsReceived.got_certificate == 0) { + ssl->msgsReceived.got_certificate = 1; + } #endif /* WOLFSSL_ASYNC_CRYPT || WOLFSSL_NONBLOCK_OCSP */ #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) @@ -25052,6 +25063,26 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) return ssl->error; } +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_ASYNC_CRYPT) + /* Finish a TLS 1.3 key schedule the last handshake message left pending + * before any further record is read or decrypted: the derives install + * the very keys that record needs. See DoTls13MsgDerives(). */ + if (ssl->options.tls1_3 && ssl->kdfMsgStep > 0) { + ret = DoTls13MsgDerives(ssl, ssl->kdfMsgType); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) { + WOLFSSL_ERROR(ret); + } + return ret; + } + /* The pend is resolved; leaving ssl->error set would make the + * next message skip its sanity check and got_* marking. */ + if (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E)) { + ssl->error = 0; + } + } +#endif + #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_ASYNC_CRYPT) /* process any pending DTLS messages - this flow can happen with async */ if (ssl->dtls_rx_msg_list != NULL) { @@ -25740,6 +25771,24 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) ssl->buffers.inputBuffer.buffer, &ssl->buffers.inputBuffer.idx, ssl->curStartIdx + ssl->curSize); + #if defined(WOLFSSL_ASYNC_CRYPT) + /* A post-handler key-schedule pend consumed the + * message but not the record; finish it here so + * the retry reads the next record. */ + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) && + ssl->kdfMsgStep > 0) { + ssl->options.processReply = doProcessInit; + if ((ssl->buffers.inputBuffer.idx - + ssl->curStartIdx) < ssl->curSize) { + ssl->options.processReply = + runProcessingOneMessage; + } + else if (IsEncryptionOn(ssl, 0)) { + ssl->buffers.inputBuffer.idx += + ssl->keys.padSz; + } + } + #endif #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) /* Post-handshake auth resumes through * wolfSSL_negotiate() instead of reprocessing this @@ -25749,8 +25798,12 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) * 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. */ + * content is left, else skip the padding. Skipped for + * a key-schedule pend (kdfMsgStep != 0): the block + * above already finished the record, and running this + * one too would skip the padding twice. */ if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) && + ssl->kdfMsgStep == TLS13_MSG_KDF_NONE && ssl->options.processReply == doProcessInit) { if ((ssl->buffers.inputBuffer.idx - ssl->curStartIdx) < ssl->curSize) { diff --git a/src/ssl.c b/src/ssl.c index da91ff10ac2..8df61d85650 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5710,6 +5710,28 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.onlyPskDheKe = ssl->ctx->onlyPskDheKe; #endif #endif + /* An abandoned handshake can leave a key-schedule or record-build + * resume marker set; a reused object must not resume into the new + * handshake. */ + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + ssl->kdfMsgType = 0; + #ifdef WOLFSSL_ASYNC_CRYPT + ssl->options.buildArgs13Set = 0; + /* An abandoned handshake can leave a mid-flight handler resume + * state and a queued key-schedule event behind; a reused object + * must start fresh. */ + ssl->options.asyncState = TLS_ASYNC_BEGIN; + if (ssl->asyncDev == &ssl->kdfAsyncDev) { + if (ssl->kdfAsyncDev.event.state == WOLF_EVENT_STATE_PENDING && + ssl->ctx != NULL) { + (void)wolfEventQueue_Remove(&ssl->ctx->event_queue, + &ssl->kdfAsyncDev.event); + } + XMEMSET(&ssl->kdfAsyncDev.event, 0, sizeof(WOLF_EVENT)); + ssl->asyncDev = NULL; + } + #endif #endif #ifdef HAVE_SESSION_TICKET #ifdef WOLFSSL_TLS13 diff --git a/src/tls.c b/src/tls.c index 3fed04bdec6..c2558aeaf9b 100644 --- a/src/tls.c +++ b/src/tls.c @@ -8436,7 +8436,7 @@ static int TLSX_KeyShare_GenX25519Key(WOLFSSL *ssl, KeyShareEntry* kse) return MEMORY_E; } - /* Make an Curve25519 key. */ + /* Initialize the Curve25519 key. */ ret = wc_curve25519_init_ex((curve25519_key*)kse->key, ssl->heap, ssl->devId); if (ret == 0) { @@ -8465,28 +8465,32 @@ static int TLSX_KeyShare_GenX25519Key(WOLFSSL *ssl, KeyShareEntry* kse) } #endif /* WC_X25519_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW && WC_ASYNC_ENABLE_X25519 */ - if (ret == 0) { - #ifdef WOLFSSL_STATIC_EPHEMERAL - ret = wolfSSL_StaticEphemeralKeyLoad(ssl, WC_PK_TYPE_CURVE25519, kse->key); - if (ret != 0) /* on failure, fallback to local key generation */ + } + + /* Outside the allocation guard: a WC_PENDING_E retry must regenerate, + * not export an ungenerated key. pubKeyLen marks a completed export on + * every backend; pubSet stops the SW-async retry re-arming forever. */ + if (ret == 0 && key != NULL && kse->pubKeyLen == 0 && !key->pubSet) { + #ifdef WOLFSSL_STATIC_EPHEMERAL + ret = wolfSSL_StaticEphemeralKeyLoad(ssl, WC_PK_TYPE_CURVE25519, + kse->key); + if (ret != 0) /* on failure, fallback to local key generation */ + #endif + { + #ifdef WOLFSSL_ASYNC_CRYPT + /* initialize event */ + ret = wolfSSL_AsyncInit(ssl, &key->asyncDev, WC_ASYNC_FLAG_NONE); + if (ret != 0) + return ret; #endif - { - #ifdef WOLFSSL_ASYNC_CRYPT - /* initialize event */ - ret = wolfSSL_AsyncInit(ssl, &key->asyncDev, - WC_ASYNC_FLAG_NONE); - if (ret != 0) - return ret; - #endif - ret = wc_curve25519_make_key(ssl->rng, CURVE25519_KEYSIZE, key); + ret = wc_curve25519_make_key(ssl->rng, CURVE25519_KEYSIZE, key); - /* Handle async pending response */ - #ifdef WOLFSSL_ASYNC_CRYPT - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { - return wolfSSL_AsyncPush(ssl, &key->asyncDev); - } - #endif /* WOLFSSL_ASYNC_CRYPT */ + /* Handle async pending response */ + #ifdef WOLFSSL_ASYNC_CRYPT + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { + return wolfSSL_AsyncPush(ssl, &key->asyncDev); } + #endif /* WOLFSSL_ASYNC_CRYPT */ } } @@ -8715,6 +8719,10 @@ static int TLSX_KeyShare_GenEccKey(WOLFSSL *ssl, KeyShareEntry* kse) /* Initialize an ECC key struct for the ephemeral key */ ret = wc_ecc_init_ex((ecc_key*)kse->key, ssl->heap, ssl->devId); + if (ret == 0) { + /* setting eccKey means okay to call wc_ecc_free */ + eccKey = (ecc_key*)kse->key; + } #if defined(WC_ECC_NONBLOCK) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \ defined(WC_ASYNC_ENABLE_ECC) @@ -8737,49 +8745,40 @@ static int TLSX_KeyShare_GenEccKey(WOLFSSL *ssl, KeyShareEntry* kse) } #endif /* WC_ECC_NONBLOCK && WOLFSSL_ASYNC_CRYPT_SW && WC_ASYNC_ENABLE_ECC */ + } - if (ret == 0) { - kse->keyLen = keySize; - kse->pubKeyLen = keySize * 2 + 1; + /* Outside the allocation guard: a WC_PENDING_E retry must regenerate, + * not export an ungenerated key. The key type marks completion; + * kse->pubKey covers backends that never touch the ecc_key (TSIP). */ + if (ret == 0 && eccKey != NULL && kse->pubKey == NULL && + eccKey->type != ECC_PRIVATEKEY && + eccKey->type != ECC_PRIVATEKEY_ONLY) { + kse->keyLen = keySize; + kse->pubKeyLen = keySize * 2 + 1; - #if defined(WOLFSSL_RENESAS_TSIP_TLS) - ret = tsip_Tls13GenEccKeyPair(ssl, kse); - if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { - return ret; - } - #endif - /* setting eccKey means okay to call wc_ecc_free */ - eccKey = (ecc_key*)kse->key; + #if defined(WOLFSSL_RENESAS_TSIP_TLS) + ret = tsip_Tls13GenEccKeyPair(ssl, kse); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + #endif - #ifdef WOLFSSL_STATIC_EPHEMERAL - ret = wolfSSL_StaticEphemeralKeyLoad(ssl, WC_PK_TYPE_ECDH, kse->key); - if (ret != 0 || eccKey->dp->id != curveId) - #endif - { - /* set curve info for EccMakeKey "peer" info */ - ret = wc_ecc_set_curve(eccKey, (int)kse->keyLen, curveId); - if (ret == 0) { - #ifdef WOLFSSL_ASYNC_CRYPT - /* Detect when private key generation is done */ - if (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E) && - eccKey->type == ECC_PRIVATEKEY) { - ret = 0; /* ECC Key Generation is done */ - } - else - #endif - { - /* Generate ephemeral ECC key */ - /* For async this is called once and when event is done, the - * provided buffers in key be populated. - * Final processing is x963 key export below. */ - ret = EccMakeKey(ssl, eccKey, eccKey); - } - } - #ifdef WOLFSSL_ASYNC_CRYPT - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) - return ret; - #endif + #ifdef WOLFSSL_STATIC_EPHEMERAL + ret = wolfSSL_StaticEphemeralKeyLoad(ssl, WC_PK_TYPE_ECDH, kse->key); + if (ret != 0 || eccKey->dp->id != curveId) + #endif + { + /* set curve info for EccMakeKey "peer" info */ + ret = wc_ecc_set_curve(eccKey, (int)kse->keyLen, curveId); + if (ret == 0) { + /* Generate ephemeral ECC key; a crypto callback retry + * re-enters here, x963 export follows below. */ + ret = EccMakeKey(ssl, eccKey, eccKey); } + #ifdef WOLFSSL_ASYNC_CRYPT + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + return ret; + #endif } } @@ -10609,7 +10608,8 @@ static int TLSX_KeyShare_Process(WOLFSSL* ssl, KeyShareEntry* keyShareEntry) WOLFSSL_BUFFER(ssl->arrays->preMasterSecret, ssl->arrays->preMasterSz); } #endif -#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) || \ + defined(WOLFSSL_ASYNC_CRYPT) keyShareEntry->derived = (ret == 0); #endif #ifdef WOLFSSL_ASYNC_CRYPT @@ -12226,6 +12226,27 @@ int TLSX_KeyShare_DeriveSecret(WOLFSSL *ssl) TLSX* extension; KeyShareEntry* list = NULL; + /* Find the KeyShare extension if it exists. */ + extension = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); + if (extension != NULL) + list = (KeyShareEntry*)extension->data; + + if (list == NULL) { + /* Unreachable once the handshake reached this accept state + * (TLSX_KeyShare_Setup installed the extension), so no async event + * can be stranded by returning before the pop below. */ + return KEY_SHARE_ERROR; + } + +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) || \ + defined(WOLFSSL_ASYNC_CRYPT) + /* Already derived: a later pend's retry re-enters here with the peer + * key freed. Checked before the pop so the later operation's queued + * event is not stolen. */ + if (list->derived) + return 0; +#endif + #ifdef WOLFSSL_ASYNC_CRYPT ret = wolfSSL_AsyncPop(ssl, NULL); /* Check for error */ @@ -12234,14 +12255,6 @@ int TLSX_KeyShare_DeriveSecret(WOLFSSL *ssl) } #endif - /* Find the KeyShare extension if it exists. */ - extension = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); - if (extension != NULL) - list = (KeyShareEntry*)extension->data; - - if (list == NULL) - return KEY_SHARE_ERROR; - /* Calculate secret. */ ret = TLSX_KeyShare_Process(ssl, list); diff --git a/src/tls13.c b/src/tls13.c index 8809ee8b1a4..c7a4ad5e304 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -172,6 +172,46 @@ */ #define ERROR_OUT(err, eLabel) { ret = (err); goto eLabel; } +/* Senders suspend/resume a pending record build only on the re-invoke path; + * poll-completing backends block inside EncryptTls13() as before. */ +#if defined(WOLFSSL_ASYNC_REINVOKE) && !defined(WOLF_CRYPTO_CB_ASYNC_POLL) + #define TLS13_HS_ASYNC_OKAY 1 +#else + #define TLS13_HS_ASYNC_OKAY 0 +#endif + +#ifdef WOLFSSL_ASYNC_REINVOKE +/* Arm ssl->kdfAsyncDev for a key-schedule op that may pend, retiring this + * connection's previous KDF event first (re-pushing a queued event would + * self-link the event list). Returns 0 on success. */ +static int Tls13KdfAsyncInit(WOLFSSL* ssl) +{ + int ret; + + if (ssl->asyncDev == &ssl->kdfAsyncDev) { + ret = wolfSSL_AsyncPop(ssl, NULL); + if (ret != 0 && ret != WC_NO_ERR_TRACE(WC_NO_PENDING_E) && + ret != WC_NO_ERR_TRACE(WC_PENDING_E)) { + return ret; + } + } + +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_ASYNC_POLL) + /* Never poll-routed: a zeroed cryptocbDevId would look poll-stamped to + * wolfSSL_AsyncPop() and stop the resume state advancing. */ + ssl->kdfAsyncDev.cryptocbDevId = INVALID_DEVID; +#endif + return wolfSSL_AsyncInit(ssl, &ssl->kdfAsyncDev, WC_ASYNC_FLAG_CALL_AGAIN); +} + +#endif /* WOLFSSL_ASYNC_REINVOKE */ + +/* Cap on re-invoking a callback for a record the caller cannot resume + * (alerts, asyncOkay = 0 senders); bounds the otherwise unbounded spin. */ +#ifndef WOLFSSL_ASYNC_MAX_REINVOKE +#define WOLFSSL_ASYNC_MAX_REINVOKE 1000 +#endif + /* Size of the TLS v1.3 label use when deriving keys. */ #define TLS13_PROTOCOL_LABEL_SZ 6 /* The protocol label for TLS v1.3. */ @@ -213,6 +253,15 @@ static int Tls13HKDFExpandLabel(WOLFSSL* ssl, byte* okm, word32 okmLen, int digest) { int ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); +#ifdef WOLFSSL_ASYNC_REINVOKE + int aret; + + /* Armed before the provider runs so a pending from the PK callback or + * wolfCrypt path falls through to the single push below. */ + aret = Tls13KdfAsyncInit(ssl); + if (aret != 0) + return aret; +#endif #if defined(HAVE_PK_CALLBACKS) if (ssl->ctx && ssl->ctx->HKDFExpandLabelCb) { @@ -223,10 +272,9 @@ static int Tls13HKDFExpandLabel(WOLFSSL* ssl, byte* okm, word32 okmLen, WOLFSSL_CLIENT_END /* ignored */); } - if (ret != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) - return ret; + if (ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) #endif - (void)ssl; + { PRIVATE_KEY_UNLOCK(); #if !defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(6,0)) ret = wc_Tls13_HKDF_Expand_Label_ex(okm, okmLen, prk, prkLen, @@ -235,12 +283,21 @@ static int Tls13HKDFExpandLabel(WOLFSSL* ssl, byte* okm, word32 okmLen, info, infoLen, digest, ssl->heap, ssl->devId); #else + (void)ssl; ret = wc_Tls13_HKDF_Expand_Label(okm, okmLen, prk, prkLen, protocol, protocolLen, label, labelLen, info, infoLen, digest); #endif PRIVATE_KEY_LOCK(); + } +#ifdef WOLFSSL_ASYNC_REINVOKE + /* HKDF has no key object to carry a WC_ASYNC_DEV, so queue the + * SSL-owned KDF device; without it the pop finds nothing pending and + * replays the handshake message. CALL_AGAIN keeps the state put. */ + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + ret = wolfSSL_AsyncPush(ssl, &ssl->kdfAsyncDev); +#endif return ret; } @@ -255,6 +312,12 @@ static int Tls13HKDFExpandKeyLabel(WOLFSSL* ssl, byte* okm, word32 okmLen, int digest, int side) { int ret; +#ifdef WOLFSSL_ASYNC_REINVOKE + ret = Tls13KdfAsyncInit(ssl); + if (ret != 0) + return ret; +#endif + #if defined(HAVE_PK_CALLBACKS) ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); if (ssl->ctx && ssl->ctx->HKDFExpandLabelCb) { @@ -264,9 +327,10 @@ static int Tls13HKDFExpandKeyLabel(WOLFSSL* ssl, byte* okm, word32 okmLen, info, infoLen, digest, side); } - if (ret != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) - return ret; + /* No early return: a pending here must reach the push at the end. */ + if (ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) #endif + { #if !defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(6,0)) ret = wc_Tls13_HKDF_Expand_Label_ex(okm, okmLen, prk, prkLen, @@ -285,6 +349,14 @@ static int Tls13HKDFExpandKeyLabel(WOLFSSL* ssl, byte* okm, word32 okmLen, protocol, protocolLen, label, labelLen, info, infoLen, digest); +#endif + } +#ifdef WOLFSSL_ASYNC_REINVOKE + /* HKDF has no key object to carry a WC_ASYNC_DEV, so queue the + * SSL-owned KDF device; without it the pop finds nothing pending and + * replays the handshake message. CALL_AGAIN keeps the state put. */ + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + ret = wolfSSL_AsyncPush(ssl, &ssl->kdfAsyncDev); #endif (void)ssl; (void)side; @@ -1176,8 +1248,19 @@ static int Tls13_HKDF_Extract(WOLFSSL *ssl, byte* prk, const byte* salt, { int ret; #ifdef HAVE_PK_CALLBACKS - void *cb_ctx = ssl->HkdfExtractCtx; - CallbackHKDFExtract cb = ssl->ctx->HkdfExtractCb; + void *cb_ctx; + CallbackHKDFExtract cb; +#endif + +#ifdef WOLFSSL_ASYNC_REINVOKE + ret = Tls13KdfAsyncInit(ssl); + if (ret != 0) + return ret; +#endif + +#ifdef HAVE_PK_CALLBACKS + cb_ctx = ssl->HkdfExtractCtx; + cb = ssl->ctx->HkdfExtractCb; if (cb != NULL) { ret = cb(prk, salt, (word32)saltLen, ikm, (word32)ikmLen, digest, cb_ctx); } @@ -1199,6 +1282,10 @@ static int Tls13_HKDF_Extract(WOLFSSL *ssl, byte* prk, const byte* salt, (void)ssl; #endif } +#ifdef WOLFSSL_ASYNC_REINVOKE + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + ret = wolfSSL_AsyncPush(ssl, &ssl->kdfAsyncDev); +#endif return ret; } @@ -2610,6 +2697,9 @@ static int EncryptTls13(WOLFSSL* ssl, byte* output, const byte* input, word16 macSz = ssl->specs.aead_mac_size; word32 nonceSz = 0; #ifdef WOLFSSL_ASYNC_CRYPT + /* Only AES-GCM/AES-CCM assign asyncDev, so only they may pend under a + * poll-completing device; the crypto-callback re-invoke path returns + * before the push and is not limited this way. */ WC_ASYNC_DEV* asyncDev = NULL; word32 event_flags = WC_ASYNC_FLAG_CALL_AGAIN; #endif @@ -2808,30 +2898,35 @@ static int EncryptTls13(WOLFSSL* ssl, byte* output, const byte* input, return ENCRYPT_ERROR; } - /* Advance state */ - ssl->encrypt.state = CIPHER_STATE_END; - #ifdef WOLFSSL_ASYNC_CRYPT if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { - #if defined(WOLF_CRYPTO_CB) && \ - !defined(WOLF_CRYPTO_CB_ASYNC_POLL) && \ - !defined(WOLFSSL_ASYNC_CRYPT_SW) && \ - !defined(HAVE_INTEL_QA) && !defined(HAVE_CAVIUM) - /* No completion path for a pending bulk cipher op. */ - WOLFSSL_ERROR_VERBOSE(ASYNC_OP_E); - return ASYNC_OP_E; - #else /* if async is not okay, then block */ if (!asyncOkay) { + #if defined(WOLFSSL_ASYNC_REINVOKE) && \ + !defined(WOLF_CRYPTO_CB_ASYNC_POLL) + /* wc_AsyncWait() never runs a callback: leave the state + * at CIPHER_STATE_DO for the caller to re-invoke. */ + return ret; + #else ret = wc_AsyncWait(ret, asyncDev, event_flags); + #endif } else { - /* If pending, then leave and return will resume below */ + #if !defined(WOLFSSL_ASYNC_REINVOKE) || \ + defined(WOLF_CRYPTO_CB_ASYNC_POLL) + /* Poll completes into output; the resume must skip the + * in-place AEAD or it would encrypt its own output. */ + ssl->encrypt.state = CIPHER_STATE_END; + #endif + /* Else stay at CIPHER_STATE_DO: the retry must re-invoke + * the callback or the record goes out unencrypted. */ return wolfSSL_AsyncPush(ssl, asyncDev); } - #endif } #endif + + /* Advance state */ + ssl->encrypt.state = CIPHER_STATE_END; } FALL_THROUGH; @@ -3177,24 +3272,25 @@ int DecryptTls13(WOLFSSL* ssl, byte* output, const byte* input, word16 sz, return DECRYPT_ERROR; } - /* Advance state */ - ssl->decrypt.state = CIPHER_STATE_END; - #ifdef WOLFSSL_ASYNC_CRYPT - /* If pending, leave now */ if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { - #if defined(WOLF_CRYPTO_CB) && \ - !defined(WOLF_CRYPTO_CB_ASYNC_POLL) && \ - !defined(WOLFSSL_ASYNC_CRYPT_SW) && \ - !defined(HAVE_INTEL_QA) && !defined(HAVE_CAVIUM) - /* No completion path for a pending bulk cipher op. */ - WOLFSSL_ERROR_VERBOSE(ASYNC_OP_E); - return ASYNC_OP_E; + #if !defined(WOLFSSL_ASYNC_REINVOKE) || \ + defined(WOLF_CRYPTO_CB_ASYNC_POLL) + /* The poll completes the operation into the output buffer, + * so the resume must not run the AEAD again: advance past + * it now (the pop does not, the event is CALL_AGAIN). */ + ssl->decrypt.state = CIPHER_STATE_END; #else - return ret; + /* Crypto callback re-invocation: leave the state at + * CIPHER_STATE_DO so the retry re-enters the AEAD; + * advancing would hand back the undecrypted record. */ #endif + return ret; } #endif + + /* Advance state */ + ssl->decrypt.state = CIPHER_STATE_END; } FALL_THROUGH; @@ -3233,25 +3329,6 @@ int DecryptTls13(WOLFSSL* ssl, byte* output, const byte* input, word16 sz, return ret; } -/* Persistable BuildTls13Message arguments */ -typedef struct BuildMsg13Args { - word32 sz; - word32 idx; - word32 headerSz; - word16 size; - word32 paddingSz; -} BuildMsg13Args; - -static void FreeBuildMsg13Args(WOLFSSL* ssl, void* pArgs) -{ - BuildMsg13Args* args = (BuildMsg13Args*)pArgs; - - (void)ssl; - (void)args; - - /* no allocations in BuildTls13Message */ -} - /* Build SSL Message, encrypted. * TLS v1.3 encryption is AEAD only. * @@ -3270,8 +3347,8 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, int inSz, int type, int hashOutput, int sizeOnly, int asyncOkay) { int ret; - BuildMsg13Args* args; - BuildMsg13Args lcl_args; + BuildMsgArgs* args; + BuildMsgArgs lcl_args; WOLFSSL_ENTER("BuildTls13Message"); @@ -3282,16 +3359,17 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, #ifdef WOLFSSL_ASYNC_CRYPT ret = WC_NO_PENDING_E; if (asyncOkay) { - WOLFSSL_ASSERT_SIZEOF_GE(ssl->async->args, *args); - if (ssl->async == NULL) { ssl->async = (struct WOLFSSL_ASYNC*) XMALLOC(sizeof(struct WOLFSSL_ASYNC), ssl->heap, DYNAMIC_TYPE_ASYNC); if (ssl->async == NULL) return MEMORY_E; + XMEMSET(ssl->async, 0, sizeof(struct WOLFSSL_ASYNC)); } - args = (BuildMsg13Args*)ssl->async->args; + /* Not ssl->async->args: that buffer belongs to the handler that + * called down into the record builder. */ + args = &ssl->async->buildArgs; ret = wolfSSL_AsyncPop(ssl, &ssl->options.buildMsgState); if (ret != WC_NO_ERR_TRACE(WC_NO_PENDING_E)) { @@ -3306,9 +3384,11 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, args = &lcl_args; } - /* Reset state */ + /* buildArgs13Set, not the pop result, marks a resume: the handler + * already popped the pending, and resetting on the pop result would + * rebuild a half-built record (transcript/sequence advance twice). */ #ifdef WOLFSSL_ASYNC_CRYPT - if (ret == WC_NO_ERR_TRACE(WC_NO_PENDING_E)) + if (!asyncOkay || !ssl->options.buildArgs13Set) #endif { /* Note: these hit ssl->options even for a sizeOnly probe, where every @@ -3317,7 +3397,12 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, * 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)); + /* A fresh record must not inherit a suspended build's mid-way + * cipher state. Not for sizeOnly probes: GetRecordSize() does not + * save this field and rewinding would re-run the AEAD. */ + if (!sizeOnly) + ssl->encrypt.state = CIPHER_STATE_BEGIN; + XMEMSET(args, 0, sizeof(BuildMsgArgs)); args->headerSz = RECORD_HEADER_SZ; #ifdef WOLFSSL_DTLS13 @@ -3327,13 +3412,13 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, args->sz = args->headerSz + (word32)inSz; args->idx = args->headerSz; - - #ifdef WOLFSSL_ASYNC_CRYPT - if (asyncOkay) - ssl->async->freeArgs = FreeBuildMsg13Args; - #endif } +#ifdef WOLFSSL_ASYNC_CRYPT + if (ret == WC_NO_ERR_TRACE(WC_NO_PENDING_E)) + ret = 0; +#endif + switch (ssl->options.buildMsgState) { case BUILD_MSG_BEGIN: { @@ -3357,7 +3442,7 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, /* Pad to minimum length */ if (ssl->options.dtls && args->sz < (word32)Dtls13MinimumRecordLength(ssl)) { - args->paddingSz = Dtls13MinimumRecordLength(ssl) - args->sz; + args->pad = Dtls13MinimumRecordLength(ssl) - args->sz; args->sz = Dtls13MinimumRecordLength(ssl); } #endif @@ -3390,6 +3475,13 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, XMEMCPY(output + args->idx, input, (size_t)inSz); args->idx += (word32)inSz; + #ifdef WOLFSSL_ASYNC_CRYPT + /* Set only after the argument checks above cannot return any + * more: an early error return must not leave the resume marker + * set, or the next build would reuse stale args. */ + if (asyncOkay) + ssl->options.buildArgs13Set = 1; + #endif ssl->options.buildMsgState = BUILD_MSG_HASH; } FALL_THROUGH; @@ -3405,8 +3497,8 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, /* The real record content type goes at the end of the data. */ output[args->idx++] = (byte)type; /* Double check that any necessary padding is zero'd out */ - XMEMSET(output + args->idx, 0, args->paddingSz); - args->idx += args->paddingSz; + XMEMSET(output + args->idx, 0, args->pad); + args->idx += args->pad; ssl->options.buildMsgState = BUILD_MSG_ENCRYPT; } @@ -3440,6 +3532,28 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, output += args->headerSz; ret = EncryptTls13(ssl, output, output, args->size, aad, (word16)args->headerSz, asyncOkay); + #ifdef WOLFSSL_ASYNC_REINVOKE + /* Non-resumable caller (alerts): finish the encryption by + * re-invoking; devices were already waited on above. */ + if (!asyncOkay) { + int reinvoke = 0; + + while (ret == WC_NO_ERR_TRACE(WC_PENDING_E) && + reinvoke++ < WOLFSSL_ASYNC_MAX_REINVOKE) { + ret = EncryptTls13(ssl, output, output, args->size, + aad, (word16)args->headerSz, + asyncOkay); + } + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { + /* A device that never completes would otherwise spin + * here forever, which is what wc_AsyncWait() used to + * do. Report it instead. */ + WOLFSSL_MSG("Crypto callback still pending after " + "retry limit on a blocking record"); + ret = WC_HW_WAIT_E; + } + } + #endif if (ret != 0) { #ifdef WOLFSSL_ASYNC_CRYPT if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) @@ -3488,10 +3602,8 @@ int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, /* Final cleanup */ #ifdef WOLFSSL_ASYNC_CRYPT if (asyncOkay) - FreeAsyncCtx(ssl, 0); - else + ssl->options.buildArgs13Set = 0; #endif - FreeBuildMsg13Args(ssl, args); return ret; } @@ -6075,8 +6187,15 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } /* switch (ssl->options.asyncState) */ #ifdef WOLFSSL_ASYNC_CRYPT - if (ret == 0) + if (ret == 0) { FreeAsyncCtx(ssl, 0); + /* Replays skip the sanity check that re-sets got_server_hello; + * restore on completion (not for HRR, which re-counts it). */ + if (*extMsgType == server_hello && + ssl->msgsReceived.got_server_hello == 0) { + ssl->msgsReceived.got_server_hello = 1; + } + } #endif WOLFSSL_LEAVE("DoTls13ServerHello", ret); @@ -7545,6 +7664,9 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, DYNAMIC_TYPE_ASYNC); if (ssl->async == NULL) ERROR_OUT(MEMORY_E, exit_dch); + /* Zeroed so the mid-flight resume test below can never route into + * uninitialised args. */ + XMEMSET(ssl->async, 0, sizeof(struct WOLFSSL_ASYNC)); } args = (Dch13Args*)ssl->async->args; @@ -7555,6 +7677,13 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, goto exit_dch; } } + else if (ssl->options.asyncState > TLS_ASYNC_BEGIN && + ssl->options.asyncState < TLS_ASYNC_END) { + /* Mid-flight replay: the event may already be retired but + * asyncState/args are intact, so resume; resetting would hash the + * message twice. Fresh ClientHellos arrive at TLS_ASYNC_BEGIN. */ + ret = 0; + } else #endif { @@ -8108,7 +8237,6 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, case TLS_ASYNC_FINALIZE: { *inOutIdx = args->idx; - ssl->options.clientState = CLIENT_HELLO_COMPLETE; #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) ssl->options.pskNegotiated = (args->usingPSK != 0); #endif @@ -8157,6 +8285,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, goto exit_dch; #endif /* !NO_CERTS */ } + + /* Advanced only after the derive: earlier would let the accept loop + * proceed on an unfinished early secret. */ + ssl->options.clientState = CLIENT_HELLO_COMPLETE; break; } /* case TLS_ASYNC_FINALIZE */ default: @@ -8235,6 +8367,14 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, FreeDch13Args(ssl, args); #ifdef WOLFSSL_ASYNC_CRYPT FreeAsyncCtx(ssl, 0); + /* Back to BEGIN so a later ClientHello (HRR, duplicate) can never be + * mistaken for a replay and resume into freed args. */ + ssl->options.asyncState = TLS_ASYNC_BEGIN; + /* Replays skip the sanity check that re-sets got_client_hello; restore + * on completion (only from 0: an HRR second ClientHello counts to 2). */ + if (ret == 0 && ssl->msgsReceived.got_client_hello == 0) { + ssl->msgsReceived.got_client_hello = 1; + } #endif WOLFSSL_END(WC_FUNC_CLIENT_HELLO_DO); @@ -8550,6 +8690,13 @@ static int SendTls13EncryptedExtensions(WOLFSSL* ssl) idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; } +#ifdef WOLFSSL_ASYNC_CRYPT + /* A suspended build already ran the key schedule below; re-running it + * would extract in place over preMasterSecret a second time. */ + if (ssl->options.buildArgs13Set) + goto tls13_send_ee_build; +#endif + #if defined(HAVE_SUPPORTED_CURVES) && !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) if ((ret = TLSX_SupportedCurve_CheckPriority(ssl)) != 0) return ret; @@ -8558,23 +8705,57 @@ static int SendTls13EncryptedExtensions(WOLFSSL* ssl) /* Derive the handshake secret now that we are at first message to be * encrypted under the keys. */ - if ((ret = DeriveHandshakeSecret(ssl)) != 0) - return ret; - if ((ret = DeriveTls13Keys(ssl, handshake_key, - ENCRYPT_AND_DECRYPT_SIDE, 1)) != 0) - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_NONE) { + ret = DeriveHandshakeSecret(ssl); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_EE_HS_SECRET; + } + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_EE_HS_SECRET) { + ret = DeriveTls13Keys(ssl, handshake_key, ENCRYPT_AND_DECRYPT_SIDE, 1); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_EE_HS_KEYS; + } /* Setup encrypt/decrypt keys for following messages. */ #ifdef WOLFSSL_EARLY_DATA - if ((ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY)) != 0) - return ret; - if (ssl->earlyData != process_early_data) { - if ((ret = SetKeysSide(ssl, DECRYPT_SIDE_ONLY)) != 0) + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_EE_HS_KEYS) { + ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_EE_ENC_KEYS_SET; + } + if (ssl->earlyData != process_early_data) { + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_EE_ENC_KEYS_SET) { + ret = SetKeysSide(ssl, DECRYPT_SIDE_ONLY); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_EE_KEYS_SET; + } } #else - if ((ret = SetKeysSide(ssl, ENCRYPT_AND_DECRYPT_SIDE)) != 0) - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_EE_ENC_KEYS_SET) { + ret = SetKeysSide(ssl, ENCRYPT_AND_DECRYPT_SIDE); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_EE_KEYS_SET; + } #endif #ifdef WOLFSSL_QUIC if (IsAtLeastTLSv1_3(ssl->version) && WOLFSSL_IS_QUIC(ssl)) { @@ -8589,13 +8770,23 @@ static int SendTls13EncryptedExtensions(WOLFSSL* ssl) w64wrapper epochHandshake = w64From32(0, DTLS13_EPOCH_HANDSHAKE); ssl->dtls13Epoch = epochHandshake; - ret = Dtls13SetEpochKeys( - ssl, epochHandshake, ENCRYPT_AND_DECRYPT_SIDE); - if (ret != 0) - return ret; - + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_EE_KEYS_SET) { + ret = Dtls13SetEpochKeys(ssl, epochHandshake, + ENCRYPT_AND_DECRYPT_SIDE); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_EE_DTLS_EPOCH; + } } #endif /* WOLFSSL_DTLS13 */ + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + +#ifdef WOLFSSL_ASYNC_CRYPT +tls13_send_ee_build: +#endif ret = TLSX_GetResponseSize(ssl, encrypted_extensions, &length); if (ret != 0) @@ -8613,12 +8804,19 @@ static int SendTls13EncryptedExtensions(WOLFSSL* ssl) /* Get position in output buffer to write new message to. */ output = GetOutputBuffer(ssl); - /* Put the record and handshake headers on. */ - AddTls13Headers(output, length, encrypted_extensions, ssl); +#ifdef WOLFSSL_ASYNC_CRYPT + /* Skip on a resume: BuildTls13Message already replaced the record + * header; rewriting the plaintext headers would corrupt it. */ + if (!ssl->options.buildArgs13Set) +#endif + { + /* Put the record and handshake headers on. */ + AddTls13Headers(output, length, encrypted_extensions, ssl); - ret = TLSX_WriteResponse(ssl, output + idx, encrypted_extensions, NULL); - if (ret != 0) - return ret; + ret = TLSX_WriteResponse(ssl, output + idx, encrypted_extensions, NULL); + if (ret != 0) + return ret; + } idx += length; #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) @@ -8651,7 +8849,7 @@ static int SendTls13EncryptedExtensions(WOLFSSL* ssl) /* This handshake message is always encrypted. */ sendSz = BuildTls13Message(ssl, output, sendSz, output + RECORD_HEADER_SZ, (int)(idx - RECORD_HEADER_SZ), - handshake, 1, 0, 0); + handshake, 1, 0, TLS13_HS_ASYNC_OKAY); if (sendSz < 0) return sendSz; @@ -11398,12 +11596,14 @@ static int SendTls13CertificateVerify(WOLFSSL* ssl) /* Fits in a single record: the common path used by RSA, ECC, * EdDSA and ML-DSA is left byte-for-byte unchanged. */ - /* This message is always encrypted. */ + /* Always encrypted. A record AEAD pend propagates through + * exit_scv (args kept) and the retry resumes the build. */ ret = BuildTls13Message(ssl, args->output, (int)args->outputSz, args->output + RECORD_HEADER_SZ, args->sendSz - RECORD_HEADER_SZ, - handshake, 1, 0, 0); + handshake, 1, 0, + TLS13_HS_ASYNC_OKAY); if (ret < 0) { goto exit_scv; @@ -12721,6 +12921,13 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input, /* Cleanup async */ FreeAsyncCtx(ssl, 0); #endif +#ifdef WOLFSSL_ASYNC_CRYPT + /* Replays skip the sanity check that re-sets got_certificate_verify; + * restore on completion or Finished reports out-of-order. */ + if (ret == 0 && ssl->msgsReceived.got_certificate_verify == 0) { + ssl->msgsReceived.got_certificate_verify = 1; + } +#endif return ret; } @@ -12952,7 +13159,7 @@ static int SendTls13Finished(WOLFSSL* ssl) byte finishedSz = ssl->specs.hash_size; byte* input; byte* output; - int ret; + int ret = 0; /* the resume goto can skip every build-phase assign */ int headerSz = HANDSHAKE_HEADER_SZ; int outputSz; byte* secret; @@ -12964,7 +13171,6 @@ static int SendTls13Finished(WOLFSSL* ssl) WOLFSSL_START(WC_FUNC_FINISHED_SEND); WOLFSSL_ENTER("SendTls13Finished"); - ssl->options.buildingMsg = 1; #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) { headerSz = DTLS_HANDSHAKE_HEADER_SZ; @@ -12974,6 +13180,13 @@ static int SendTls13Finished(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 */ + /* Post-send key-schedule resume: the Finished record is already + * queued, so skip the build phase (re-running would queue it twice). */ + if (ssl->kdfDeriveStep > 0) + goto tls13_send_finished_derives; + + ssl->options.buildingMsg = 1; + outputSz = WC_MAX_DIGEST_SIZE + DTLS_HANDSHAKE_HEADER_SZ + MAX_MSG_EXTRA; /* Check buffers are big enough and grow if needed. */ if ((ret = CheckAvailableSize(ssl, outputSz)) != 0) @@ -12991,6 +13204,13 @@ static int SendTls13Finished(WOLFSSL* ssl) AddTls13HandShakeHeader(input, (word32)finishedSz, 0, (word32)finishedSz, finished, ssl); +#ifdef WOLFSSL_ASYNC_CRYPT + /* A suspended build already wrote the verify data and hashed it; + * recomputing would hash the body twice. */ + if (ssl->options.buildArgs13Set) + goto tls13_send_finished_encrypt; +#endif + #if defined(WOLFSSL_RENESAS_TSIP_TLS) if (ssl->options.side == WOLFSSL_CLIENT_END) { ret = tsip_Tls13SendFinished(ssl, output, outputSz, input, 1); @@ -13045,6 +13265,10 @@ static int SendTls13Finished(WOLFSSL* ssl) } #endif /* WOLFSSL_HAVE_TLS_UNIQUE */ +#ifdef WOLFSSL_ASYNC_CRYPT +tls13_send_finished_encrypt: +#endif + #ifdef WOLFSSL_DTLS13 if (isDtls) { dtlsRet = Dtls13HandshakeSend(ssl, output, (word16)outputSz, @@ -13058,8 +13282,15 @@ static int SendTls13Finished(WOLFSSL* ssl) { /* This message is always encrypted. */ int sendSz = BuildTls13Message(ssl, output, outputSz, input, - headerSz + finishedSz, handshake, 1, 0, 0); + headerSz + finishedSz, handshake, 1, 0, + TLS13_HS_ASYNC_OKAY); if (sendSz < 0) { + #ifdef WOLFSSL_ASYNC_CRYPT + /* Propagate a pending record encryption rather than reporting it + * as a build failure: the retry resumes the record. */ + if (sendSz == WC_NO_ERR_TRACE(WC_PENDING_E)) + return sendSz; + #endif WOLFSSL_ERROR_VERBOSE(BUILD_MSG_ERROR); return BUILD_MSG_ERROR; } @@ -13078,13 +13309,24 @@ static int SendTls13Finished(WOLFSSL* ssl) ssl->options.buildingMsg = 0; } + /* Build phase complete; steps below are individually resumable. */ + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_ENTERED; +tls13_send_finished_derives: + if (ssl->options.side == WOLFSSL_SERVER_END) { #ifdef WOLFSSL_EARLY_DATA byte storeTrafficDecKeys = ssl->earlyData == no_early_data; #endif /* Can send application data now. */ - if ((ret = DeriveMasterSecret(ssl)) != 0) - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_ENTERED) { + ret = DeriveMasterSecret(ssl); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_MASTER_SECRET; + } /* Last use of preMasterSecret - zeroize as soon as possible. */ ForceZero(ssl->arrays->preMasterSecret, ssl->arrays->preMasterSz); #ifdef WOLFSSL_EARLY_DATA @@ -13096,22 +13338,46 @@ static int SendTls13Finished(WOLFSSL* ssl) storeTrafficDecKeys = 1; #endif /* WOLFSSL_DTLS13 */ - if ((ret = DeriveTls13Keys(ssl, traffic_key, ENCRYPT_SIDE_ONLY, 1)) - != 0) { - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_MASTER_SECRET) { + ret = DeriveTls13Keys(ssl, traffic_key, ENCRYPT_SIDE_ONLY, 1); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_ENC_TRAFFIC_KEYS; } - if ((ret = DeriveTls13Keys(ssl, traffic_key, DECRYPT_SIDE_ONLY, - storeTrafficDecKeys)) != 0) { - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_ENC_TRAFFIC_KEYS) { + ret = DeriveTls13Keys(ssl, traffic_key, DECRYPT_SIDE_ONLY, + storeTrafficDecKeys); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_TRAFFIC_KEYS; } #else - if ((ret = DeriveTls13Keys(ssl, traffic_key, ENCRYPT_AND_DECRYPT_SIDE, - 1)) != 0) { - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_ENC_TRAFFIC_KEYS) { + ret = DeriveTls13Keys(ssl, traffic_key, ENCRYPT_AND_DECRYPT_SIDE, + 1); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_TRAFFIC_KEYS; } #endif - if ((ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY)) != 0) - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_TRAFFIC_KEYS) { + ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_ENC_KEYS_SET; + } #ifdef WOLFSSL_DTLS13 if (isDtls) { @@ -13120,11 +13386,16 @@ static int SendTls13Finished(WOLFSSL* ssl) ssl->dtls13Epoch = epochTraffic0; ssl->dtls13PeerEpoch = epochTraffic0; - ret = Dtls13SetEpochKeys( - ssl, epochTraffic0, ENCRYPT_AND_DECRYPT_SIDE); - if (ret != 0) - return ret; - + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_ENC_KEYS_SET) { + ret = Dtls13SetEpochKeys(ssl, epochTraffic0, + ENCRYPT_AND_DECRYPT_SIDE); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_DTLS_TRAFFIC_EPOCH; + } } #endif /* WOLFSSL_DTLS13 */ @@ -13134,20 +13405,38 @@ static int SendTls13Finished(WOLFSSL* ssl) !ssl->options.handShakeDone) { #ifdef WOLFSSL_EARLY_DATA if (ssl->earlyData != no_early_data) { - if ((ret = DeriveTls13Keys(ssl, no_key, ENCRYPT_SIDE_ONLY, - 1)) != 0) { + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_DTLS_TRAFFIC_EPOCH) { + ret = DeriveTls13Keys(ssl, no_key, ENCRYPT_SIDE_ONLY, 1); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_EARLY_ENC_KEYS; } } #endif /* Setup keys for application data messages. */ - if ((ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY)) != 0) - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_EARLY_ENC_KEYS) { + ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_EARLY_KEYS_SET; + } #if defined(HAVE_SESSION_TICKET) - ret = DeriveResumptionSecret(ssl, ssl->session->masterSecret); - if (ret != 0) - return ret; + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_EARLY_KEYS_SET) { + ret = DeriveResumptionSecret(ssl, ssl->session->masterSecret); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_RESUMPTION_SECRET; + } #endif #ifdef WOLFSSL_DTLS13 @@ -13157,15 +13446,24 @@ static int SendTls13Finished(WOLFSSL* ssl) ssl->dtls13Epoch = epochTraffic0; ssl->dtls13PeerEpoch = epochTraffic0; - ret = Dtls13SetEpochKeys( - ssl, epochTraffic0, ENCRYPT_AND_DECRYPT_SIDE); - if (ret != 0) - return ret; - + /* Step-guarded like every other derive in this function, so a + * pend resumes here and a real error clears the resume state. */ + if (ssl->kdfDeriveStep <= TLS13_SEND_KDF_FIN_RESUMPTION_SECRET) { + ret = Dtls13SetEpochKeys( + ssl, epochTraffic0, ENCRYPT_AND_DECRYPT_SIDE); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + return ret; + } + ssl->kdfDeriveStep = TLS13_SEND_KDF_FIN_DTLS_EPOCH_SET; + } } #endif /* WOLFSSL_DTLS13 */ } + ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; + #ifndef NO_WOLFSSL_CLIENT if (ssl->options.side == WOLFSSL_CLIENT_END) { ssl->options.clientState = CLIENT_FINISHED_COMPLETE; @@ -14049,8 +14347,13 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) return SIDE_ERROR; } #endif - /* Check state. */ - if (ssl->options.clientState >= CLIENT_HELLO_COMPLETE) { + /* A replay after a pend arrives with got_client_hello cleared + * (see exit_dch); a genuine duplicate arrives with it set. */ + if (ssl->options.clientState >= CLIENT_HELLO_COMPLETE + #ifdef WOLFSSL_ASYNC_CRYPT + && ssl->msgsReceived.got_client_hello != 0 + #endif + ) { WOLFSSL_MSG("ClientHello received out of order"); WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E); return OUT_OF_ORDER_E; @@ -14663,6 +14966,212 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) * totalSz Length of remaining data in the message buffer. * returns 0 on success and otherwise failure. */ +/* Run the key schedule belonging to a just-processed handshake message. + * A pend here cannot replay the message (its handler already advanced + * state); the record is consumed and this is called again from + * DoProcessReplyEx() entry, pre-dispatch, or the reply-loop exits. + * Returns 0, WC_PENDING_E while the device is busy, else an error. */ +int DoTls13MsgDerives(WOLFSSL* ssl, byte type) +{ + int ret = 0; + + (void)type; + +#ifndef NO_WOLFSSL_CLIENT + if (ssl->options.side == WOLFSSL_CLIENT_END) { + if (type == server_hello) { + /* Entered before the first derive, or a pend there would + * route the retry back to the message handler. */ + if (ssl->kdfMsgStep == TLS13_MSG_KDF_NONE) { + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_ENTERED; + ssl->kdfMsgType = type; + } + + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SH_ENTERED) { + ret = DeriveEarlySecret(ssl); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_EARLY_SECRET; + } + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SH_EARLY_SECRET) { + ret = DeriveHandshakeSecret(ssl); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_HS_SECRET; + } + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SH_HS_SECRET) { + ret = DeriveTls13Keys(ssl, handshake_key, + ENCRYPT_AND_DECRYPT_SIDE, 1); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_HS_KEYS; + } + #ifdef WOLFSSL_EARLY_DATA + if (ssl->earlyData != no_early_data) { + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SH_HS_KEYS) { + ret = SetKeysSide(ssl, DECRYPT_SIDE_ONLY); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_KEYS_SET; + } + } + else + #endif + { + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SH_HS_KEYS) { + ret = SetKeysSide(ssl, ENCRYPT_AND_DECRYPT_SIDE); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_KEYS_SET; + } + } + +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + w64wrapper epochHandshake; + epochHandshake = w64From32(0, DTLS13_EPOCH_HANDSHAKE); + ssl->dtls13Epoch = epochHandshake; + ssl->dtls13PeerEpoch = epochHandshake; + + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SH_KEYS_SET) { + ret = Dtls13SetEpochKeys(ssl, epochHandshake, + ENCRYPT_AND_DECRYPT_SIDE); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SH_DTLS_EPOCH; + } + } +#endif /* WOLFSSL_DTLS13 */ + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + } + + if (type == finished) { + /* Mark the phase entered before the first derive, so a pending + * there still routes the retry back here. */ + if (ssl->kdfMsgStep == TLS13_MSG_KDF_NONE) { + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_ENTERED; + ssl->kdfMsgType = type; + } + + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_FIN_ENTERED) { + if ((ret = DeriveMasterSecret(ssl)) != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + /* Zeroized only after the derive completed: a pend retry + * still reads preMasterSecret. */ + ForceZero(ssl->arrays->preMasterSecret, + ssl->arrays->preMasterSz); + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_MASTER_SECRET; + } + #ifdef WOLFSSL_EARLY_DATA + #ifdef WOLFSSL_QUIC + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_FIN_MASTER_SECRET) { + if (WOLFSSL_IS_QUIC(ssl) && + ssl->earlyData != no_early_data) { + /* QUIC never sends/receives EndOfEarlyData, but + * having early data means the last encryption keys + * had not been set yet. */ + if ((ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY)) != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + } + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_QUIC_EARLY_KEYS; + } + #endif + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_FIN_QUIC_EARLY_KEYS) { + ret = DeriveTls13Keys(ssl, traffic_key, + ENCRYPT_AND_DECRYPT_SIDE, + ssl->earlyData == no_early_data); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_TRAFFIC_KEYS; + } + if (ssl->earlyData != no_early_data) { + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_FIN_TRAFFIC_KEYS) { + if ((ret = DeriveTls13Keys(ssl, no_key, + DECRYPT_SIDE_ONLY, 1)) != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_TRAFFIC_DONE; + } + } + #else + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_FIN_QUIC_EARLY_KEYS) { + ret = DeriveTls13Keys(ssl, traffic_key, + ENCRYPT_AND_DECRYPT_SIDE, 1); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_TRAFFIC_DONE; + } + #endif + /* Setup keys for application data messages. */ + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_FIN_TRAFFIC_DONE) { + if ((ret = SetKeysSide(ssl, DECRYPT_SIDE_ONLY)) != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_FIN_KEYS_SET; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + } + } +#endif /* NO_WOLFSSL_CLIENT */ + +#ifndef NO_WOLFSSL_SERVER + #if defined(HAVE_SESSION_TICKET) + if (ssl->options.side == WOLFSSL_SERVER_END && type == finished) { + if (ssl->kdfMsgStep == TLS13_MSG_KDF_NONE) { + ssl->kdfMsgStep = TLS13_MSG_KDF_SFIN_ENTERED; + ssl->kdfMsgType = type; + } + if (ssl->kdfMsgStep <= TLS13_MSG_KDF_SFIN_ENTERED) { + ret = DeriveResumptionSecret(ssl, ssl->session->masterSecret); + if (ret != 0) { + if (ret != WC_NO_ERR_TRACE(WC_PENDING_E)) + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + return ret; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_SFIN_RESUMPTION_SECRET; + } + ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; + } + #endif +#endif /* NO_WOLFSSL_SERVER */ + + return ret; +} + int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, byte type, word32 size, word32 totalSz) { @@ -14682,8 +15191,14 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (*inOutIdx + size > totalSz) return INCOMPLETE_DATA; - /* sanity check msg received */ - if ((ret = SanityCheckTls13MsgReceived(ssl, type)) != 0) { + /* Sanity check msg received. Skipped on a WC_PENDING_E resume (it + * would reject the replay against already-advanced state); handlers + * restore their cleared got_* markers on completion instead. */ + if ( +#ifdef WOLFSSL_ASYNC_CRYPT + ssl->error != WC_NO_ERR_TRACE(WC_PENDING_E) && +#endif + (ret = SanityCheckTls13MsgReceived(ssl, type)) != 0) { WOLFSSL_MSG("Sanity Check on handshake message type received failed"); if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) SendAlert(ssl, alert_fatal, wolfssl_alert_protocol_version); @@ -14739,6 +15254,18 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, } /* above checks handshake state */ + /* Finish an earlier message's key schedule before this message is + * judged: it installs state this one is checked against. */ + if (ssl->kdfMsgStep > 0) { + ret = DoTls13MsgDerives(ssl, ssl->kdfMsgType); + if (ret != 0) + return ret; + /* A resolved pend must not suppress the next sanity check. */ + if (ssl->error == WC_NO_ERR_TRACE(WC_PENDING_E)) { + ssl->error = 0; + } + } + switch (type) { #ifndef NO_WOLFSSL_CLIENT /* Messages only received by client. */ @@ -14982,83 +15509,14 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, } if (ret == 0 && ssl->options.tls1_3) { - /* Need to hash input message before deriving secrets. */ - #ifndef NO_WOLFSSL_CLIENT - if (ssl->options.side == WOLFSSL_CLIENT_END) { - if (type == server_hello) { - if ((ret = DeriveEarlySecret(ssl)) != 0) - return ret; - if ((ret = DeriveHandshakeSecret(ssl)) != 0) - return ret; - - if ((ret = DeriveTls13Keys(ssl, handshake_key, - ENCRYPT_AND_DECRYPT_SIDE, 1)) != 0) { - return ret; - } - #ifdef WOLFSSL_EARLY_DATA - if (ssl->earlyData != no_early_data) { - if ((ret = SetKeysSide(ssl, DECRYPT_SIDE_ONLY)) != 0) - return ret; - } - else - #endif - if ((ret = SetKeysSide(ssl, ENCRYPT_AND_DECRYPT_SIDE)) != 0) - return ret; - -#ifdef WOLFSSL_DTLS13 - if (ssl->options.dtls) { - w64wrapper epochHandshake; - epochHandshake = w64From32(0, DTLS13_EPOCH_HANDSHAKE); - ssl->dtls13Epoch = epochHandshake; - ssl->dtls13PeerEpoch = epochHandshake; - - ret = Dtls13SetEpochKeys( - ssl, epochHandshake, ENCRYPT_AND_DECRYPT_SIDE); - if (ret != 0) - return ret; - - } -#endif /* WOLFSSL_DTLS13 */ - } + /* The message is hashed by now; run the key schedule that belongs to + * it. */ + ret = DoTls13MsgDerives(ssl, type); + if (ret != 0) + return ret; - if (type == finished) { - if ((ret = DeriveMasterSecret(ssl)) != 0) - return ret; - /* Last use of preMasterSecret - zeroize as soon as possible. */ - ForceZero(ssl->arrays->preMasterSecret, - ssl->arrays->preMasterSz); - #ifdef WOLFSSL_EARLY_DATA - #ifdef WOLFSSL_QUIC - if (WOLFSSL_IS_QUIC(ssl) && ssl->earlyData != no_early_data) { - /* QUIC never sends/receives EndOfEarlyData, but having - * early data means the last encryption keys had not been - * set yet. */ - if ((ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY)) != 0) - return ret; - } - #endif - if ((ret = DeriveTls13Keys(ssl, traffic_key, - ENCRYPT_AND_DECRYPT_SIDE, - ssl->earlyData == no_early_data)) != 0) { - return ret; - } - if (ssl->earlyData != no_early_data) { - if ((ret = DeriveTls13Keys(ssl, no_key, DECRYPT_SIDE_ONLY, - 1)) != 0) { - return ret; - } - } - #else - if ((ret = DeriveTls13Keys(ssl, traffic_key, - ENCRYPT_AND_DECRYPT_SIDE, 1)) != 0) { - return ret; - } - #endif - /* Setup keys for application data messages. */ - if ((ret = SetKeysSide(ssl, DECRYPT_SIDE_ONLY)) != 0) - return ret; - } - #ifdef WOLFSSL_POST_HANDSHAKE_AUTH + #if !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) + if (ssl->options.side == WOLFSSL_CLIENT_END) { if (type == certificate_request && ssl->options.handShakeState == HANDSHAKE_DONE) { #if defined(HAVE_WRITE_DUP) @@ -15118,19 +15576,8 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, } } } - #endif - } - #endif /* NO_WOLFSSL_CLIENT */ - -#ifndef NO_WOLFSSL_SERVER - #if defined(HAVE_SESSION_TICKET) - if (ssl->options.side == WOLFSSL_SERVER_END && type == finished) { - ret = DeriveResumptionSecret(ssl, ssl->session->masterSecret); - if (ret != 0) - return ret; } - #endif -#endif /* NO_WOLFSSL_SERVER */ + #endif /* !NO_WOLFSSL_CLIENT && WOLFSSL_POST_HANDSHAKE_AUTH */ } #ifdef WOLFSSL_DTLS13 @@ -15162,6 +15609,11 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 inputLength; byte type; word32 size = 0; +#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) + /* Nonzero on entry: an earlier message's schedule is unfinished, so a + * pend from its pre-dispatch drain must re-present this message. */ + byte kdfStepEntry = ssl->kdfMsgStep; +#endif WOLFSSL_ENTER("DoTls13HandShakeMsg"); @@ -15174,6 +15626,9 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, /* If there is a pending fragmented handshake message, * pending message size will be non-zero. */ if (ssl->pendingMsgSz == 0) { + #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) + word32 startIdx = *inOutIdx; + #endif if (GetHandshakeHeader(ssl, input, inOutIdx, &type, &size, totalSz) != 0) { @@ -15217,6 +15672,16 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, ret = DoTls13HandShakeMsgType(ssl, input, inOutIdx, type, size, totalSz); + #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) + if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E) && + (ssl->kdfMsgStep == 0 || kdfStepEntry != 0)) || + ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { + /* Re-present for in-handler pends and pre-dispatch drain pends; + * a post-handler key-schedule pend must NOT replay (state is + * committed; DoTls13MsgDerives() finishes it instead). */ + *inOutIdx = startIdx; + } + #endif } else { if (inputLength + ssl->pendingMsgOffset > ssl->pendingMsgSz) { @@ -15244,9 +15709,11 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->pendingMsgSz - HANDSHAKE_HEADER_SZ, ssl->pendingMsgSz); #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) || + if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E) && + (ssl->kdfMsgStep == 0 || kdfStepEntry != 0)) || ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { - /* setup to process fragment again */ + /* Re-present the fragment; a post-handler key-schedule + * pend falls through and consumes the message. */ ssl->pendingMsgOffset -= inputLength; *inOutIdx -= inputLength; } @@ -15522,6 +15989,17 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS13 */ } + /* Finish a key schedule the Finished handler left pending: + * it must complete before this side's sends advance the + * transcript the traffic secrets hash. */ + if (ssl->kdfMsgStep > 0) { + ssl->error = DoTls13MsgDerives(ssl, ssl->kdfMsgType); + if (ssl->error != 0) { + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + } + ssl->options.connectState = FIRST_REPLY_DONE; WOLFSSL_MSG("connect state: FIRST_REPLY_DONE"); FALL_THROUGH; @@ -16983,6 +17461,17 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS13 */ } + /* Finish a key schedule the Finished handler left pending: + * it must complete before this side's sends advance the + * transcript the traffic secrets hash. */ + if (ssl->kdfMsgStep > 0) { + ssl->error = DoTls13MsgDerives(ssl, ssl->kdfMsgType); + if (ssl->error != 0) { + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + } + ssl->options.acceptState = TLS13_ACCEPT_FINISHED_DONE; WOLFSSL_MSG("accept state ACCEPT_FINISHED_DONE"); FALL_THROUGH; diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index ba4f53fcb09..31d4cd665f6 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -77,8 +77,9 @@ Crypto Callback Build Options: #if defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLF_CRYPTO_CB_ASYNC_POLL) && \ !defined(WOLFSSL_ASYNC_CRYPT_SW) && !defined(HAVE_INTEL_QA) && \ !defined(HAVE_CAVIUM) && !defined(WOLF_CRYPTO_CB_ASYNC_NO_WARN) - #warning "crypto callbacks with async crypt may not work for TLS. Define \ -WOLF_CRYPTO_CB_ASYNC_POLL to enable it, or WOLF_CRYPTO_CB_ASYNC_NO_WARN to \ + #warning "crypto callbacks with async crypt cannot complete TLS 1.2 \ +record ciphers (TLS 1.3 resumes them by re-invoking the callback). Define \ +WOLF_CRYPTO_CB_ASYNC_POLL to enable them, or WOLF_CRYPTO_CB_ASYNC_NO_WARN to \ silence." #endif diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 7569a5cb2aa..e639f751a5a 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2365,6 +2365,7 @@ WOLFSSL_LOCAL int ChachaAEADDecrypt(WOLFSSL* ssl, byte* plain, const byte* input #ifdef WOLFSSL_TLS13 WOLFSSL_LOCAL int DecryptTls13(WOLFSSL* ssl, byte* output, const byte* input, word16 sz, const byte* aad, word16 aadSz); +WOLFSSL_LOCAL int DoTls13MsgDerives(WOLFSSL* ssl, byte type); WOLFSSL_LOCAL int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, byte type, word32 size, word32 totalSz); @@ -3949,6 +3950,12 @@ typedef struct KeyShareEntry { #endif #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) word16 session; /* NamedGroup that was in session */ +#endif +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) || \ + defined(WOLFSSL_ASYNC_CRYPT) + /* Also under WOLFSSL_ASYNC_CRYPT: a pending operation retried on the + * same accept state re-enters the derive with the peer key freed, and + * this is the marker that stops the re-derive. */ word16 derived; /* preMaster has been derived */ #endif #ifdef WOLFSSL_ASYNC_CRYPT @@ -5545,6 +5552,11 @@ struct Options { #ifdef WOLFSSL_ASYNC_CRYPT word16 buildArgsSet:1; /* buildArgs are set and need to * be free'd */ +#ifdef WOLFSSL_TLS13 + word16 buildArgs13Set:1; /* a TLS 1.3 record build is in + * progress and must resume, + * not restart */ +#endif #endif #ifdef WOLFSSL_DTLS13 word16 dtls13SendMoreAcks:1; /* Send more acks during the @@ -6140,8 +6152,8 @@ typedef struct HS_Hashes { } HS_Hashes; -#ifndef WOLFSSL_NO_TLS12 -/* Persistable BuildMessage arguments */ +#if !defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13) +/* Persistable BuildMessage/BuildTls13Message arguments */ typedef struct BuildMsgArgs { word32 digestSz; word32 sz; @@ -6161,8 +6173,11 @@ typedef struct BuildMsgArgs { typedef void (*FreeArgsCb)(struct WOLFSSL* ssl, void* pArgs); struct WOLFSSL_ASYNC { -#if defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NO_TLS12) - BuildMsgArgs buildArgs; /* holder for current BuildMessage args */ +#if defined(WOLFSSL_ASYNC_CRYPT) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_TLS13)) + /* Record builder resume args, shared by BuildMessage() and + * BuildTls13Message(): a connection runs only one of them. */ + BuildMsgArgs buildArgs; #endif FreeArgsCb freeArgs; /* function pointer to cleanup args */ #ifdef WC_NO_PTR_INT_CAST @@ -6408,6 +6423,66 @@ enum ConnectionIdUsage { (ssl)->ctx->suites)) /* wolfSSL ssl type */ + +/* A crypto/PK callback pending is finished by re-invoking the provider: + * wolfSSL_AsyncPoll() never runs a callback. Exported so tests compile in + * only where a callback pend is resumable. */ +#if defined(WOLFSSL_ASYNC_CRYPT) && \ + (defined(WOLF_CRYPTO_CB) || defined(HAVE_PK_CALLBACKS)) && \ + !defined(WOLFSSL_ASYNC_CRYPT_SW) && !defined(HAVE_INTEL_QA) && \ + !defined(HAVE_CAVIUM) + #define WOLFSSL_ASYNC_REINVOKE +#endif + +/* TLS 1.3 key-schedule resume steps (kdfMsgStep/kdfDeriveStep). A value is + * recorded after its named operation completes ("step <= X" = X not done); + * 0 = sequence not entered or finished. Values repeat across sequences. */ + +/* Receive side (kdfMsgStep), driven by DoTls13MsgDerives(). */ +enum Tls13KdfMsgStep { + TLS13_MSG_KDF_NONE = 0, + /* client processing server_hello */ + TLS13_MSG_KDF_SH_ENTERED = 1, + TLS13_MSG_KDF_SH_EARLY_SECRET = 2, + TLS13_MSG_KDF_SH_HS_SECRET = 3, + TLS13_MSG_KDF_SH_HS_KEYS = 4, + TLS13_MSG_KDF_SH_KEYS_SET = 5, + TLS13_MSG_KDF_SH_DTLS_EPOCH = 6, + /* client processing finished */ + TLS13_MSG_KDF_FIN_ENTERED = 1, + TLS13_MSG_KDF_FIN_MASTER_SECRET = 2, + TLS13_MSG_KDF_FIN_QUIC_EARLY_KEYS = 3, + TLS13_MSG_KDF_FIN_TRAFFIC_KEYS = 4, + TLS13_MSG_KDF_FIN_TRAFFIC_DONE = 5, + TLS13_MSG_KDF_FIN_KEYS_SET = 6, + /* server processing finished (resumption secret for tickets) */ + TLS13_MSG_KDF_SFIN_ENTERED = 1, + TLS13_MSG_KDF_SFIN_RESUMPTION_SECRET = 2 +}; + +/* Send side (kdfDeriveStep), inside the senders themselves. */ +enum Tls13KdfSendStep { + TLS13_SEND_KDF_NONE = 0, + /* SendTls13EncryptedExtensions() */ + TLS13_SEND_KDF_EE_HS_SECRET = 1, + TLS13_SEND_KDF_EE_HS_KEYS = 2, + TLS13_SEND_KDF_EE_ENC_KEYS_SET = 3, + TLS13_SEND_KDF_EE_KEYS_SET = 4, + TLS13_SEND_KDF_EE_DTLS_EPOCH = 5, + /* SendTls13Finished() */ + TLS13_SEND_KDF_FIN_ENTERED = 1, + TLS13_SEND_KDF_FIN_MASTER_SECRET = 2, + TLS13_SEND_KDF_FIN_ENC_TRAFFIC_KEYS = 3, + TLS13_SEND_KDF_FIN_TRAFFIC_KEYS = 4, + TLS13_SEND_KDF_FIN_ENC_KEYS_SET = 5, + TLS13_SEND_KDF_FIN_DTLS_TRAFFIC_EPOCH = 6, + TLS13_SEND_KDF_FIN_EARLY_ENC_KEYS = 7, + TLS13_SEND_KDF_FIN_EARLY_KEYS_SET = 8, + TLS13_SEND_KDF_FIN_RESUMPTION_SECRET = 9, + TLS13_SEND_KDF_FIN_DTLS_EPOCH_SET = 10 +}; + + struct WOLFSSL { WOLFSSL_CTX* ctx; #if defined(WOLFSSL_HAPROXY) @@ -6989,6 +7064,18 @@ struct WOLFSSL { * ciphers; 0 means uncached and is never a valid AEAD overhead. EtM does * not apply to AEAD. */ word32 recordSzOverhead; +#ifdef WOLFSSL_ASYNC_CRYPT + /* Async device for the TLS 1.3 key schedule: HKDF has no key object + * to carry one. Event bookkeeping only, for the callback re-invoke + * path (never wolfAsync_DevCtxInit'd, no hardware context). */ + WC_ASYNC_DEV kdfAsyncDev; +#endif + /* Key-schedule resume steps: completed derives must not re-run (e.g. + * the extract is in place over preMasterSecret). Unconditional so the + * schedule needs no ifdefs; without async they stay 0. */ + byte kdfDeriveStep; /* enum Tls13KdfSendStep (send side) */ + byte kdfMsgStep; /* enum Tls13KdfMsgStep (receive side) */ + byte kdfMsgType; /* handshake type kdfMsgStep belongs to */ }; #if defined(WOLFSSL_SYS_CRYPTO_POLICY) diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 2bfbb4ddef4..ee110e1856d 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4038,9 +4038,8 @@ #error WOLF_CRYPTO_CB_ASYNC_POLL requires bulk cipher async support #endif - /* Crypto callbacks are the only async backend and cannot finish a pending - * bulk cipher op: the record layer has already advanced past the crypto - * call, and without poll routing nothing refills the output buffer. */ + /* Callback-only async cannot finish a pending TLS 1.2 bulk cipher op + * (TLS 1.3 resumes them by re-invoking the callback). */ #if defined(WOLF_CRYPTO_CB) && !defined(WOLF_CRYPTO_CB_ASYNC_POLL) && \ !defined(WOLFSSL_ASYNC_CRYPT_SW) && !defined(HAVE_INTEL_QA) && \ !defined(HAVE_CAVIUM) From d61cbd0fe6265e693dffdfff9c997c773d2d1e0d Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 20 Aug 2026 18:57:16 -0700 Subject: [PATCH 3/7] Add TLS 1.3 crypto callback pend tests and byte-stream test memio --- .github/workflows/async.yml | 2 +- tests/api/test_tls13.c | 327 ++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 4 +- tests/utils.c | 39 ++++- 4 files changed, 369 insertions(+), 3 deletions(-) diff --git a/.github/workflows/async.yml b/.github/workflows/async.yml index 2d119239049..69865b7c2fb 100644 --- a/.github/workflows/async.yml +++ b/.github/workflows/async.yml @@ -71,7 +71,7 @@ jobs: 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.", + {"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 their built-in devices never return WC_PENDING_E; pending is exercised there by the tests that register their own pending callback (hkdf_cryptocb_async_test() in wolfCrypt, test_tls13_cryptocb_async in the api suite). 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", diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index cc81b5a119f..d0ea3d7f55d 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -9526,3 +9526,330 @@ int test_tls13_pha_status_request(void) #endif return EXPECT_RESULT(); } + +#if defined(WOLFSSL_TLS13) && defined(WOLF_CRYPTO_CB) && \ + defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_REINVOKE) && \ + defined(HAVE_ECC) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + +#define TEST_TLS13_CB_PEND_DEVID_C 1 +#define TEST_TLS13_CB_PEND_DEVID_S 2 +#define TEST_TLS13_CB_PEND_JOBS 64 + +/* Which class of operation the callback pends. */ +enum TestTls13PendTarget { + TEST_TLS13_PEND_AESGCM, + TEST_TLS13_PEND_ECC_KEYGEN, + TEST_TLS13_PEND_ECDSA_SIGN, + TEST_TLS13_PEND_ECDSA_VERIFY, + TEST_TLS13_PEND_KDF +}; + +typedef struct TestTls13PendCtx { + /* Job table keyed by request: first sight queues and pends, the + * identical re-invocation completes (falls through to software). */ + unsigned long jobs[TEST_TLS13_CB_PEND_JOBS]; + int jobCount; + int target; /* enum TestTls13PendTarget */ + int pended; /* WC_PENDING_E results issued; asserted non-zero */ + int seen; /* matching requests observed; asserted non-zero */ +} TestTls13PendCtx; + +static int TestTls13PendMatches(int target, wc_CryptoInfo* info) +{ + int match = 0; + + switch (target) { +#ifndef WOLF_CRYPTO_CB_ASYNC_POLL + /* With WOLF_CRYPTO_CB_ASYNC_POLL the record ciphers follow the + * poll-completion contract instead of re-invocation; that model is + * covered by tests/api/test_async.c. */ + case TEST_TLS13_PEND_AESGCM: + match = (info->algo_type == WC_ALGO_TYPE_CIPHER) && + (info->cipher.type == WC_CIPHER_AES_GCM); + break; +#endif + case TEST_TLS13_PEND_ECC_KEYGEN: + match = (info->algo_type == WC_ALGO_TYPE_PK) && + (info->pk.type == WC_PK_TYPE_EC_KEYGEN); + break; + case TEST_TLS13_PEND_ECDSA_SIGN: + match = (info->algo_type == WC_ALGO_TYPE_PK) && + (info->pk.type == WC_PK_TYPE_ECDSA_SIGN); + break; + case TEST_TLS13_PEND_ECDSA_VERIFY: + match = (info->algo_type == WC_ALGO_TYPE_PK) && + (info->pk.type == WC_PK_TYPE_ECDSA_VERIFY); + break; + case TEST_TLS13_PEND_KDF: + match = (info->algo_type == WC_ALGO_TYPE_KDF); + break; + default: + break; + } + + return match; +} + +/* Request fingerprint over the whole info struct: op class, sizes, pointers + * and inline content distinguish interleaved requests. */ +static unsigned long TestTls13PendHash(wc_CryptoInfo* info) +{ + unsigned long h = 5381; + const unsigned char* b = (const unsigned char*)info; + size_t i; + + for (i = 0; i < sizeof(*info); i++) + h = h * 33 + b[i]; + return h; +} + +static int TestTls13PendCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + TestTls13PendCtx* c = (TestTls13PendCtx*)ctx; + unsigned long h; + int i; + + (void)devIdArg; + + if (info == NULL || c == NULL) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + if (!TestTls13PendMatches(c->target, info)) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + + c->seen++; + h = TestTls13PendHash(info); + for (i = 0; i < c->jobCount; i++) { + if (c->jobs[i] == h) { + /* Re-invocation of a pended request: complete it. */ + c->jobs[i] = c->jobs[--c->jobCount]; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + } + if (c->jobCount < TEST_TLS13_CB_PEND_JOBS) { + c->jobs[c->jobCount++] = h; + c->pended++; + return WC_PENDING_E; + } + + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} + +/* wolfSSL_write()/wolfSSL_read() can return WC_PENDING_E just as the + * handshake does; drive them with the poll-and-retry loop the application is + * expected to use. Returns the byte count, or the error. */ +static int TestTls13PendWrite(WOLFSSL* ssl, const char* buf, int sz) +{ + int ret; + int err; + int rounds = 0; + + do { + ret = wolfSSL_write(ssl, buf, sz); + if (ret > 0) + break; + err = wolfSSL_get_error(ssl, ret); + if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) { + if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) + return -1; + } + else if (err != WOLFSSL_ERROR_WANT_READ && + err != WOLFSSL_ERROR_WANT_WRITE) { + return ret; + } + } while (++rounds < 100); + + return ret; +} + +static int TestTls13PendRead(WOLFSSL* ssl, char* buf, int sz) +{ + int ret; + int err; + int rounds = 0; + + do { + ret = wolfSSL_read(ssl, buf, sz); + if (ret > 0) + break; + err = wolfSSL_get_error(ssl, ret); + if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) { + if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) + return -1; + } + else if (err != WOLFSSL_ERROR_WANT_READ && + err != WOLFSSL_ERROR_WANT_WRITE) { + return ret; + } + } while (++rounds < 100); + + return ret; +} + +/* One TLS 1.3 handshake with the given operation class pending on both sides, + * then application data both ways so records queued after the handshake are + * parsed by the peer as well. */ +static int test_tls13_cryptocb_pend_one(int target, int mutual) +{ + EXPECT_DECLS; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + TestTls13PendCtx cliCtx; + TestTls13PendCtx srvCtx; + struct test_memio_ctx memio; + const char msg[] = "hello over TLS 1.3"; + char buf[64]; + + XMEMSET(&cliCtx, 0, sizeof(cliCtx)); + XMEMSET(&srvCtx, 0, sizeof(srvCtx)); + XMEMSET(&memio, 0, sizeof(memio)); + cliCtx.target = target; + srvCtx.target = target; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_TLS13_CB_PEND_DEVID_C, + TestTls13PendCb, &cliCtx), 0); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_TLS13_CB_PEND_DEVID_S, + TestTls13PendCb, &srvCtx), 0); + + /* devId set on the CTX before credentials and SSL objects so all of + * it inherits the devId. ECC credentials: an RSA key under a devId + * would be treated as device-held. */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_c, TEST_TLS13_CB_PEND_DEVID_C), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_s, TEST_TLS13_CB_PEND_DEVID_S), + WOLFSSL_SUCCESS); +#ifdef HAVE_AESGCM + if (target == TEST_TLS13_PEND_AESGCM) { + /* Pin the AEAD so the pend assertion cannot depend on suite + * preference ordering. */ + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx_c, + "TLS13-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx_s, + "TLS13-AES128-GCM-SHA256"), WOLFSSL_SUCCESS); + } +#endif + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, + "./certs/ca-ecc-cert.pem", 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx_s, + "./certs/server-ecc.pem"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, "./certs/ecc-key.pem", + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + if (mutual) { + /* Mutual auth: pending verifies of the client's chain and CV + * cover the received-marker restores. */ + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx_c, + "./certs/client-ecc-cert.pem"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, + "./certs/ecc-client-key.pem", WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, + "./certs/client-ecc-cert.pem", 0), WOLFSSL_SUCCESS); + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + } + + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + + /* Pin the group so every run negotiates the same way; the default offer + * can trigger HelloRetryRequest or a PQC key share, which are separate + * scenarios from the pend-resume paths this test covers. */ + if (EXPECT_SUCCESS()) { + int groups[1]; + groups[0] = WOLFSSL_ECC_SECP256R1; + ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_s, groups, 1), WOLFSSL_SUCCESS); + } + + /* The shared memio transport; its TLS path serves a byte stream, so + * the re-read patterns of pending crypto operations cannot desync it. */ + if (EXPECT_SUCCESS()) { + wolfSSL_SSLSetIORecv(ssl_c, test_memio_read_cb); + wolfSSL_SSLSetIOSend(ssl_c, test_memio_write_cb); + wolfSSL_SSLSetIORecv(ssl_s, test_memio_read_cb); + wolfSSL_SSLSetIOSend(ssl_s, test_memio_write_cb); + wolfSSL_SetIOReadCtx(ssl_c, &memio); + wolfSSL_SetIOWriteCtx(ssl_c, &memio); + wolfSSL_SetIOReadCtx(ssl_s, &memio); + wolfSSL_SetIOWriteCtx(ssl_s, &memio); + } + + /* Generous rounds: pending every KDF request costs one poll-and-retry + * round trip per operation, and a TLS 1.3 handshake with session tickets + * runs a couple of hundred of them. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 600, NULL), 0); + + /* Assert per-side pends so the test cannot pass without exercising a + * resume path; the client signs nothing unless mutual. */ + ExpectIntGT(srvCtx.seen, 0); + ExpectIntGT(srvCtx.pended, 0); + if (mutual || target != TEST_TLS13_PEND_ECDSA_SIGN) { + ExpectIntGT(cliCtx.seen, 0); + ExpectIntGT(cliCtx.pended, 0); + } + + ExpectIntEQ(TestTls13PendWrite(ssl_c, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntEQ(TestTls13PendRead(ssl_s, buf, (int)sizeof(buf)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0); + + ExpectIntEQ(TestTls13PendWrite(ssl_s, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntEQ(TestTls13PendRead(ssl_c, buf, (int)sizeof(buf)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(buf, msg, sizeof(msg)), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wc_CryptoCb_UnRegisterDevice(TEST_TLS13_CB_PEND_DEVID_C); + wc_CryptoCb_UnRegisterDevice(TEST_TLS13_CB_PEND_DEVID_S); + + return EXPECT_RESULT(); +} + +#endif /* guards */ + +/* Drive TLS 1.3 handshakes and application data with the poll-and-retry + * loop while a crypto callback pends each matching request. */ +int test_tls13_cryptocb_async(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLF_CRYPTO_CB) && \ + defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_REINVOKE) && \ + defined(HAVE_ECC) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +#if defined(HAVE_AESGCM) && !defined(WOLF_CRYPTO_CB_ASYNC_POLL) + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_AESGCM, 0), + TEST_SUCCESS); +#endif +#ifdef HAVE_ECC + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_ECC_KEYGEN, 0), + TEST_SUCCESS); + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_ECDSA_SIGN, 0), + TEST_SUCCESS); + /* Mutual auth with pending verifies: regression for the msgsReceived + * marker restores (a pended Certificate/CertificateVerify replay used + * to leave its marker clear and fail Finished with OUT_OF_ORDER_E). */ + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_ECDSA_VERIFY, 1), + TEST_SUCCESS); +#endif +#if defined(HAVE_HKDF) && !defined(NO_HMAC) + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_KDF, 0), + TEST_SUCCESS); + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_KDF, 1), + TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index e152293f12f..818404835a3 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -114,6 +114,7 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); int test_tls13_KeyUpdate_sender_limit(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); +int test_tls13_cryptocb_async(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -205,6 +206,7 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ - TEST_DECL_GROUP("tls13", test_tls13_pha_status_request) + TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ + TEST_DECL_GROUP("tls13", test_tls13_cryptocb_async) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/tests/utils.c b/tests/utils.c index a8566adc86e..6a4f7b4a01b 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -129,7 +129,44 @@ int test_memio_read_cb(WOLFSSL *ssl, char *data, int sz, void *ctx) if (*len == 0 || *msg_pos >= *msg_count) return WOLFSSL_CBIO_ERR_WANT_READ; - /* Calculate how much we can read from current message */ + if (!is_dtls) { + /* TLS is a byte stream: serve across message boundaries so + * pending-crypto re-read patterns cannot desync the slots. */ + int rem; + + read_sz = *len; + if (read_sz > sz) + read_sz = sz; + + XMEMCPY(data, buf, (size_t)read_sz); + XMEMMOVE(buf, buf + read_sz, (size_t)(*len - read_sz)); + *len -= read_sz; + + rem = read_sz; + while (rem > 0 && *msg_pos < *msg_count) { + if (msg_sizes[*msg_pos] > rem) { + msg_sizes[*msg_pos] -= rem; + rem = 0; + } + else { + rem -= msg_sizes[*msg_pos]; + msg_sizes[*msg_pos] = 0; + (*msg_pos)++; + } + } + if (rem != 0) { + /* Slot accounting desynced from the byte count; fail loudly. */ + return WOLFSSL_CBIO_ERR_GENERAL; + } + if (*msg_pos >= *msg_count && *len == 0) { + *msg_pos = 0; + *msg_count = 0; + } + + return read_sz; + } + + /* DTLS: datagram boundaries matter, serve one message at a time. */ read_sz = msg_sizes[*msg_pos]; if (read_sz > sz) read_sz = sz; From 0890ae77a91b29ccc672262b02b13be73a2435fe Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 20 Aug 2026 18:57:16 -0700 Subject: [PATCH 4/7] Simulate pending for all supported ops in async example --- examples/async/README.md | 11 +++- examples/async/async_client.c | 5 ++ examples/async/async_server.c | 5 ++ examples/async/async_tls.c | 94 ++++++++++++++++++++++++++++------- examples/async/async_tls.h | 17 ++++++- 5 files changed, 112 insertions(+), 20 deletions(-) diff --git a/examples/async/README.md b/examples/async/README.md index 6412ffa5007..d59a5f9ff69 100644 --- a/examples/async/README.md +++ b/examples/async/README.md @@ -29,7 +29,14 @@ make -C examples/async ASYNC_MODE=sw ### Crypto Callback Mode Uses `WOLF_CRYPTO_CB` with the `AsyncTlsCryptoCb` callback that simulates hardware -crypto delays by returning `WC_PENDING_E` for a configurable number of iterations: +crypto delays by returning `WC_PENDING_E` for a configurable number of iterations. +The simulated device keeps a job table keyed by the request, like a hardware +crypto manager: a request pends `TEST_PEND_COUNT` times (default 2) and the +next re-invocation with identical arguments completes it. On TLS 1.3 every supported +operation class pends (HKDF, AES-GCM, ECC/X25519 key generation and shared +secret, ECDSA/Ed25519 sign and verify), including mutual authentication. On +TLS 1.2 (`--tls12`) only the RSA and ECDSA signing set pends; the TLS 1.2 state +machines do not resume the other classes. ``` make -C examples/async ASYNC_MODE=cryptocb ``` @@ -68,7 +75,7 @@ Define `NET_USER_HEADER` to include your network shim and provide the ## Asynchronous Cryptography Design -When a cryptographic call is handed off to hardware it return `WC_PENDING_E` up to caller. Then it can keep calling until the operation completes. For some platforms it is required to call `wolfSSL_AsyncPoll`. At the TLS layer a "devId" (Device ID) must be set using `wolfSSL_CTX_SetDevId` to indicate desire to offload cryptography. +When a cryptographic call is handed off to hardware, `WC_PENDING_E` is returned up to the caller, which keeps calling until the operation completes. For some platforms it is required to call `wolfSSL_AsyncPoll`. At the TLS layer a "devId" (Device ID) must be set using `wolfSSL_CTX_SetDevId` to indicate the desire to offload cryptography. For further design details please see: https://github.com/wolfSSL/wolfAsyncCrypt#design diff --git a/examples/async/async_client.c b/examples/async/async_client.c index 510857ebdf0..91ee59a6026 100644 --- a/examples/async/async_client.c +++ b/examples/async/async_client.c @@ -304,6 +304,7 @@ int client_async_test(int argc, char** argv) if (devId == INVALID_DEVID) devId = 1; XMEMSET(&cryptoCbCtx, 0, sizeof(cryptoCbCtx)); + cryptoCbCtx.tls12 = tls12; if (wc_CryptoCb_RegisterDevice(devId, AsyncTlsCryptoCb, &cryptoCbCtx) != 0) { fprintf(stderr, "ERROR: wc_CryptoCb_RegisterDevice failed\n"); goto out; @@ -567,6 +568,10 @@ int client_async_test(int argc, char** argv) #ifdef WOLFSSL_DEBUG_NONBLOCK printf("WANT_READ/WRITE count: %d\n", wouldblock_count); printf("WC_PENDING_E count: %d\n", pending_count); +#ifdef WOLF_CRYPTO_CB + printf("Device WC_PENDING_E returns: %d (table-full completions: %d)\n", + cryptoCbCtx.pendingCount, cryptoCbCtx.jobFullCount); +#endif #endif ret = 0; diff --git a/examples/async/async_server.c b/examples/async/async_server.c index 9d745fbeea3..eebb0b7167f 100644 --- a/examples/async/async_server.c +++ b/examples/async/async_server.c @@ -311,6 +311,7 @@ int server_async_test(int argc, char** argv) if (devId == INVALID_DEVID) devId = 1; XMEMSET(&cryptoCbCtx, 0, sizeof(cryptoCbCtx)); + cryptoCbCtx.tls12 = tls12; if (wc_CryptoCb_RegisterDevice(devId, AsyncTlsCryptoCb, &cryptoCbCtx) != 0) { fprintf(stderr, "ERROR: wc_CryptoCb_RegisterDevice failed\n"); goto exit; @@ -666,6 +667,10 @@ int server_async_test(int argc, char** argv) #ifdef WOLFSSL_DEBUG_NONBLOCK printf("WANT_READ/WRITE count: %d\n", wouldblock_count); printf("WC_PENDING_E count: %d\n", pending_count); +#ifdef WOLF_CRYPTO_CB + printf("Device WC_PENDING_E returns: %d (table-full completions: %d)\n", + cryptoCbCtx.pendingCount, cryptoCbCtx.jobFullCount); +#endif #endif ret = 0; diff --git a/examples/async/async_tls.c b/examples/async/async_tls.c index 2a4ee7fab8e..da7da428642 100644 --- a/examples/async/async_tls.c +++ b/examples/async/async_tls.c @@ -162,6 +162,78 @@ int posix_getdevrandom(unsigned char *out, unsigned int sz) #define TEST_PEND_COUNT 2 #endif +#ifdef WOLFSSL_ASYNC_CRYPT +/* Return 1 to simulate WC_PENDING_E. A request (hash of wc_CryptoInfo) + * pends TEST_PEND_COUNT times and completes on re-invocation, like a + * hardware crypto manager job table. A full table completes requests + * synchronously (jobFullCount records the degradation). */ +static int AsyncTlsCryptoCbPend(AsyncTlsCryptoCbCtx* myCtx, + wc_CryptoInfo* info) +{ + unsigned long h = 5381; + const unsigned char* b = (const unsigned char*)info; + size_t i; + int simulate = 0; + + /* TLS 1.3 resumes every class below; TLS 1.2 only retries the + * signing set, so restrict when the app selected TLS 1.2. */ + if (myCtx->tls12) { + if (info->algo_type == WC_ALGO_TYPE_PK) { + simulate = (info->pk.type == WC_PK_TYPE_RSA || + info->pk.type == WC_PK_TYPE_ECDSA_SIGN); + } + } + else if (info->algo_type == WC_ALGO_TYPE_PK) { + simulate = (info->pk.type == WC_PK_TYPE_RSA || + info->pk.type == WC_PK_TYPE_EC_KEYGEN || + info->pk.type == WC_PK_TYPE_ECDSA_SIGN || + info->pk.type == WC_PK_TYPE_ECDSA_VERIFY || + info->pk.type == WC_PK_TYPE_ECDH || + info->pk.type == WC_PK_TYPE_CURVE25519_KEYGEN || + info->pk.type == WC_PK_TYPE_CURVE25519 || + info->pk.type == WC_PK_TYPE_ED25519_SIGN || + info->pk.type == WC_PK_TYPE_ED25519_VERIFY); + } + else if (info->algo_type == WC_ALGO_TYPE_KDF) { + simulate = 1; /* TLS 1.3 HKDF key schedule */ + } + else if (info->algo_type == WC_ALGO_TYPE_CIPHER) { + simulate = (info->cipher.type == WC_CIPHER_AES_GCM); + } + if (!simulate) + return 0; + + for (i = 0; i < sizeof(*info); i++) + h = (h * 33) + b[i]; + + for (i = 0; i < (size_t)myCtx->jobCount; i++) { + if (myCtx->jobHash[i] == h) { + myCtx->jobTries[i]++; + if (myCtx->jobTries[i] <= TEST_PEND_COUNT) { + myCtx->pendingCount++; + return 1; /* still pending */ + } + /* complete: remove job and run the operation below */ + myCtx->jobCount--; + myCtx->jobHash[i] = myCtx->jobHash[myCtx->jobCount]; + myCtx->jobTries[i] = myCtx->jobTries[myCtx->jobCount]; + return 0; + } + } + if (myCtx->jobCount >= ASYNC_TLS_PEND_JOBS) { + /* Full (non-identical retries strand entries): complete + * synchronously and count the degradation. */ + myCtx->jobFullCount++; + return 0; + } + myCtx->jobHash[myCtx->jobCount] = h; + myCtx->jobTries[myCtx->jobCount] = 1; + myCtx->jobCount++; + myCtx->pendingCount++; + return 1; +} +#endif /* WOLFSSL_ASYNC_CRYPT */ + /* Example crypto dev callback function that calls software version */ /* This is where you would plug-in calls to your own hardware crypto */ int AsyncTlsCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) @@ -169,32 +241,20 @@ int AsyncTlsCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); /* bypass HW by default */ AsyncTlsCryptoCbCtx* myCtx = (AsyncTlsCryptoCbCtx*)ctx; - if (info == NULL) + if (info == NULL || myCtx == NULL) return BAD_FUNC_ARG; #ifdef DEBUG_CRYPTOCB wc_CryptoCb_InfoString(info); #endif - if (info->algo_type == WC_ALGO_TYPE_PK) { #ifdef WOLFSSL_ASYNC_CRYPT - /* Simulate async pending for RSA and ECC signing operations. - * This matches a typical hardware crypto scenario (e.g., TPM) where - * only signing is offloaded to hardware. Keygen, verify, and ECDH - * are performed synchronously in software. - * Note: WOLFSSL_ASYNC_CRYPT + WOLF_CRYPTO_CB pending simulation - * requires operations whose TLS state machines properly handle retry - * via wolfSSL_AsyncPop. ECC keygen in TLSX_KeyShare_GenEccKey does - * not support this because the keygen call is inside the key - * allocation guard (kse->key == NULL) which is skipped on retry. */ - if (info->pk.type == WC_PK_TYPE_RSA || - info->pk.type == WC_PK_TYPE_ECDSA_SIGN) - { - if (myCtx->pendingCount++ < TEST_PEND_COUNT) return WC_PENDING_E; - myCtx->pendingCount = 0; - } + if (AsyncTlsCryptoCbPend(myCtx, info)) { + return WC_PENDING_E; + } #endif + if (info->algo_type == WC_ALGO_TYPE_PK) { #ifndef NO_RSA if (info->pk.type == WC_PK_TYPE_RSA) { /* set devId to invalid, so software is used */ diff --git a/examples/async/async_tls.h b/examples/async/async_tls.h index 52814639da9..1dc43220a8e 100644 --- a/examples/async/async_tls.h +++ b/examples/async/async_tls.h @@ -46,8 +46,23 @@ typedef struct wc_CryptoInfo wc_CryptoInfo; #ifdef WOLF_CRYPTO_CB /* Example custom context for crypto callback */ +/* Max simultaneous simulated pending requests (device job table) */ +#ifndef ASYNC_TLS_PEND_JOBS +#define ASYNC_TLS_PEND_JOBS 64 +#endif typedef struct { - int pendingCount; /* track pending tries test count */ + int pendingCount; /* total WC_PENDING_E returns (statistic) */ + /* Simulated device job table. A pended request is identified by a + * hash of its wc_CryptoInfo so the re-invocation with identical + * arguments can be matched and completed. */ + unsigned long jobHash[ASYNC_TLS_PEND_JOBS]; + int jobTries[ASYNC_TLS_PEND_JOBS]; + int jobCount; + int jobFullCount; /* requests completed synchronously: table full */ + /* Set by the application when TLS 1.2 was selected: restricts the + * simulated pending to the operations the TLS 1.2 state machines can + * retry. TLS 1.3 (0, the default) pends every supported class. */ + int tls12; } AsyncTlsCryptoCbCtx; int AsyncTlsCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx); #endif /* WOLF_CRYPTO_CB */ From 2889bf1f8d4b93edf7ed8464aa002ac779e95783 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 20 Aug 2026 20:33:16 -0700 Subject: [PATCH 5/7] Peer review and CI fixes --- wolfssl/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index e639f751a5a..93f72779ce6 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3975,7 +3975,7 @@ WOLFSSL_LOCAL int TLSX_KeyShare_Choose(const WOLFSSL *ssl, TLSX* extensions, byte* searched); WOLFSSL_LOCAL int TLSX_KeyShare_Setup(WOLFSSL *ssl, KeyShareEntry* clientKSE); WOLFSSL_LOCAL int TLSX_KeyShare_Establish(WOLFSSL* ssl, int* doHelloRetry); -WOLFSSL_LOCAL int TLSX_KeyShare_DeriveSecret(WOLFSSL* sclientKSEclientKSEsl); +WOLFSSL_LOCAL int TLSX_KeyShare_DeriveSecret(WOLFSSL* ssl); WOLFSSL_LOCAL int TLSX_KeyShare_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType); WOLFSSL_LOCAL int TLSX_KeyShare_Parse_ClientHello(const WOLFSSL* ssl, From 7e4d2dee17b93ce34ee250af0e0e3076af5837af Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 21 Aug 2026 08:58:21 -0700 Subject: [PATCH 6/7] Fixes for async CI --- examples/async/async_client.c | 7 +++++-- examples/async/async_server.c | 7 +++++-- src/tls13.c | 19 ++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/examples/async/async_client.c b/examples/async/async_client.c index 91ee59a6026..070d8589334 100644 --- a/examples/async/async_client.c +++ b/examples/async/async_client.c @@ -252,8 +252,11 @@ int client_async_test(int argc, char** argv) AsyncTlsCryptoCbCtx cryptoCbCtx; #endif #ifdef WOLFSSL_STATIC_MEMORY - static byte memory[300000]; - static byte memoryIO[34500]; + /* Sized for a TLS 1.3 mutual-auth handshake with every supported + * operation class pending: suspended verifies during mutual auth raise + * the bucket high-water mark well above the synchronous footprint. */ + static byte memory[800000]; + static byte memoryIO[64000]; #if !defined(WOLFSSL_STATIC_MEMORY_LEAN) WOLFSSL_MEM_CONN_STATS ssl_stats; #endif diff --git a/examples/async/async_server.c b/examples/async/async_server.c index eebb0b7167f..d4bcd227418 100644 --- a/examples/async/async_server.c +++ b/examples/async/async_server.c @@ -212,8 +212,11 @@ int server_async_test(int argc, char** argv) AsyncTlsCryptoCbCtx cryptoCbCtx; #endif #ifdef WOLFSSL_STATIC_MEMORY - static byte memory[300000]; - static byte memoryIO[34500]; + /* Sized for a TLS 1.3 mutual-auth handshake with every supported + * operation class pending: suspended verifies during mutual auth raise + * the bucket high-water mark well above the synchronous footprint. */ + static byte memory[800000]; + static byte memoryIO[64000]; #if !defined(WOLFSSL_STATIC_MEMORY_LEAN) WOLFSSL_MEM_CONN_STATS ssl_stats; #endif diff --git a/src/tls13.c b/src/tls13.c index c7a4ad5e304..6a625530c94 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -15674,11 +15674,15 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, totalSz); #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E) && - (ssl->kdfMsgStep == 0 || kdfStepEntry != 0)) || + (ssl->kdfMsgStep == 0 || kdfStepEntry != 0) && + ssl->options.processReply != 0 /* doProcessInit */) || ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { - /* Re-present for in-handler pends and pre-dispatch drain pends; - * a post-handler key-schedule pend must NOT replay (state is - * committed; DoTls13MsgDerives() finishes it instead). */ + /* Re-present for in-handler pends and pre-dispatch drain pends. + * Not for post-handler pends, which committed the message: a + * key-schedule pend (kdfMsgStep != 0) resumes through + * DoTls13MsgDerives(), and a post-handshake-auth send pend + * (processReply reset to doProcessInit) resumes through + * wolfSSL_negotiate(). */ *inOutIdx = startIdx; } #endif @@ -15710,10 +15714,11 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->pendingMsgSz); #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E) && - (ssl->kdfMsgStep == 0 || kdfStepEntry != 0)) || + (ssl->kdfMsgStep == 0 || kdfStepEntry != 0) && + ssl->options.processReply != 0 /* doProcessInit */) || ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { - /* Re-present the fragment; a post-handler key-schedule - * pend falls through and consumes the message. */ + /* Re-present the fragment; a post-handler pend falls + * through and consumes the message. */ ssl->pendingMsgOffset -= inputLength; *inOutIdx -= inputLength; } From 78e92edf0c6c475bfe63185b5b286b7b3801a799 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 21 Aug 2026 12:07:51 -0700 Subject: [PATCH 7/7] Add TLS 1.3 crypto callback pending support for transcript HMAC --- src/internal.c | 4 +++ src/ssl.c | 3 ++ src/tls.c | 10 +++++-- src/tls13.c | 68 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.c | 46 +++++++++++++++++++++++++++- wolfssl/internal.h | 31 ++++++++++++------- 6 files changed, 148 insertions(+), 14 deletions(-) diff --git a/src/internal.c b/src/internal.c index a8b605a8e09..6016b1051a5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -9741,6 +9741,10 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) #ifdef WOLFSSL_ASYNC_IO /* Cleanup async */ FreeAsyncCtx(ssl, 1); +#endif +#if defined(WOLFSSL_ASYNC_REINVOKE) && defined(WOLFSSL_TLS13) && \ + !defined(NO_HMAC) + Tls13FreeHsHmac(ssl); #endif if (ssl->options.weOwnRng) { wc_FreeRng(ssl->rng); diff --git a/src/ssl.c b/src/ssl.c index 8df61d85650..118e8b5217f 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5716,6 +5716,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->kdfDeriveStep = TLS13_SEND_KDF_NONE; ssl->kdfMsgStep = TLS13_MSG_KDF_NONE; ssl->kdfMsgType = 0; + #if defined(WOLFSSL_ASYNC_REINVOKE) && !defined(NO_HMAC) + Tls13FreeHsHmac(ssl); + #endif #ifdef WOLFSSL_ASYNC_CRYPT ssl->options.buildArgs13Set = 0; /* An abandoned handshake can leave a mid-flight handler resume diff --git a/src/tls.c b/src/tls.c index c2558aeaf9b..d8c4ad74f37 100644 --- a/src/tls.c +++ b/src/tls.c @@ -8750,12 +8750,16 @@ static int TLSX_KeyShare_GenEccKey(WOLFSSL *ssl, KeyShareEntry* kse) /* Outside the allocation guard: a WC_PENDING_E retry must regenerate, * not export an ungenerated key. The key type marks completion; * kse->pubKey covers backends that never touch the ecc_key (TSIP). */ - if (ret == 0 && eccKey != NULL && kse->pubKey == NULL && - eccKey->type != ECC_PRIVATEKEY && - eccKey->type != ECC_PRIVATEKEY_ONLY) { + if (ret == 0 && eccKey != NULL) { + /* Outside the generation guard below: the export alloc reads + * pubKeyLen even when generation is skipped. */ kse->keyLen = keySize; kse->pubKeyLen = keySize * 2 + 1; + } + if (ret == 0 && eccKey != NULL && kse->pubKey == NULL && + eccKey->type != ECC_PRIVATEKEY && + eccKey->type != ECC_PRIVATEKEY_ONLY) { #if defined(WOLFSSL_RENESAS_TSIP_TLS) ret = tsip_Tls13GenEccKeyPair(ssl, kse); if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { diff --git a/src/tls13.c b/src/tls13.c index 6a625530c94..f64a033d8f3 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -1505,10 +1505,26 @@ int DeriveResumptionPSK(WOLFSSL* ssl, byte* nonce, byte nonceLen, byte* secret) * hash The hash result - verify data. * returns length of verify data generated. */ +#if defined(WOLFSSL_ASYNC_REINVOKE) && !defined(NO_HMAC) +/* Release the held transcript Hmac and its resume state. */ +void Tls13FreeHsHmac(WOLFSSL* ssl) +{ + if (ssl->hsHmac != NULL) { + wc_HmacFree(ssl->hsHmac); + XFREE(ssl->hsHmac, ssl->heap, DYNAMIC_TYPE_HMAC); + ssl->hsHmac = NULL; + } + ssl->hsHmacStep = 0; + ssl->hsHmacOut = NULL; +} +#endif /* WOLFSSL_ASYNC_REINVOKE && !NO_HMAC */ + static int BuildTls13HandshakeHmac(WOLFSSL* ssl, byte* key, byte* hash, word32* pHashSz) { +#ifndef WOLFSSL_ASYNC_REINVOKE WC_DECLARE_VAR(verifyHmac, Hmac, 1, 0); +#endif int hashType = WC_SHA256; int hashSz = WC_SHA256_DIGEST_SIZE; int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); @@ -1561,6 +1577,51 @@ static int BuildTls13HandshakeHmac(WOLFSSL* ssl, byte* key, byte* hash, WOLFSSL_BUFFER(hash, hashSz); #endif +#ifdef WOLFSSL_ASYNC_REINVOKE + /* Held on the SSL object so a crypto callback WC_PENDING_E resumes by + * re-invoking the same Hmac with identical arguments; the transcript + * hash input is recomputed deterministically by the caller's retry. + * Bound to the output buffer: a replayed caller that computes several + * HMACs (the PSK binder list) must not resume one request against + * another's key, so a different output discards the held state. */ + if (ssl->hsHmac != NULL && ssl->hsHmacOut != hash) + Tls13FreeHsHmac(ssl); + if (ssl->hsHmac == NULL) { + ssl->hsHmac = (Hmac*)XMALLOC(sizeof(Hmac), ssl->heap, + DYNAMIC_TYPE_HMAC); + if (ssl->hsHmac == NULL) + return MEMORY_E; + ret = wc_HmacInit(ssl->hsHmac, ssl->heap, ssl->devId); + if (ret != 0) { + Tls13FreeHsHmac(ssl); + return ret; + } + ssl->hsHmacStep = 0; + ssl->hsHmacOut = hash; + } + /* Armed before the operations, matching the HKDF helpers. */ + ret = Tls13KdfAsyncInit(ssl); + if (ret != 0) { + Tls13FreeHsHmac(ssl); + return ret; + } + if (ssl->hsHmacStep == 0) { + ret = wc_HmacSetKey(ssl->hsHmac, hashType, key, + ssl->specs.hash_size); + if (ret == 0) + ssl->hsHmacStep = 1; + } + if (ret == 0 && ssl->hsHmacStep == 1) { + ret = wc_HmacUpdate(ssl->hsHmac, hash, (word32)hashSz); + if (ret == 0) + ssl->hsHmacStep = 2; + } + if (ret == 0 && ssl->hsHmacStep == 2) + ret = wc_HmacFinal(ssl->hsHmac, hash); + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + return wolfSSL_AsyncPush(ssl, &ssl->kdfAsyncDev); + Tls13FreeHsHmac(ssl); +#else WC_ALLOC_VAR_EX(verifyHmac, Hmac, 1, NULL, DYNAMIC_TYPE_HMAC, return MEMORY_E); @@ -1576,6 +1637,7 @@ static int BuildTls13HandshakeHmac(WOLFSSL* ssl, byte* key, byte* hash, } WC_FREE_VAR_EX(verifyHmac, NULL, DYNAMIC_TYPE_HMAC); +#endif /* WOLFSSL_ASYNC_REINVOKE */ #ifdef WOLFSSL_DEBUG_TLS WOLFSSL_MSG(" Hash"); @@ -14031,6 +14093,7 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input, static int ExpectedResumptionSecret(WOLFSSL* ssl) { int ret; + int saveRet = 0; word32 finishedSz = 0; byte mac[WC_MAX_DIGEST_SIZE]; Digest digest; @@ -14094,6 +14157,9 @@ static int ExpectedResumptionSecret(WOLFSSL* ssl) /* Restore the hash inline with currently seen messages. */ restore: + /* The restore result must not mask the error that got here, or a + * WC_PENDING_E would be reported as success with the derive skipped. */ + saveRet = ret; switch (ssl->specs.mac_algorithm) { #ifndef NO_SHA256 case sha256_mac: @@ -14124,6 +14190,8 @@ static int ExpectedResumptionSecret(WOLFSSL* ssl) break; #endif } + if (saveRet != 0) + ret = saveRet; ForceZero(mac, sizeof(mac)); return ret; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index d0ea3d7f55d..80a8abbbd5f 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -9543,7 +9543,8 @@ enum TestTls13PendTarget { TEST_TLS13_PEND_ECC_KEYGEN, TEST_TLS13_PEND_ECDSA_SIGN, TEST_TLS13_PEND_ECDSA_VERIFY, - TEST_TLS13_PEND_KDF + TEST_TLS13_PEND_KDF, + TEST_TLS13_PEND_HMAC }; typedef struct TestTls13PendCtx { @@ -9585,6 +9586,9 @@ static int TestTls13PendMatches(int target, wc_CryptoInfo* info) case TEST_TLS13_PEND_KDF: match = (info->algo_type == WC_ALGO_TYPE_KDF); break; + case TEST_TLS13_PEND_HMAC: + match = (info->algo_type == WC_ALGO_TYPE_HMAC); + break; default: break; } @@ -9615,6 +9619,38 @@ static int TestTls13PendCb(int devIdArg, wc_CryptoInfo* info, void* ctx) if (info == NULL || c == NULL) return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +#if defined(HAVE_HKDF) && !defined(NO_HMAC) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) + if (c->target == TEST_TLS13_PEND_HMAC && + info->algo_type == WC_ALGO_TYPE_KDF) { + /* Serve the key schedule synchronously in software; falling back + * with the devId set would route its internal HMACs back here and + * those cannot resume (see wolfcrypt/src/hmac.c). Only the + * TLS-layer transcript HMACs are left to pend. */ + if (info->kdf.type == WC_KDF_TYPE_HKDF_EXTRACT) { + return wc_HKDF_Extract_ex(info->kdf.hkdf_extract.hashType, + info->kdf.hkdf_extract.salt, info->kdf.hkdf_extract.saltSz, + info->kdf.hkdf_extract.inKey, info->kdf.hkdf_extract.inKeySz, + info->kdf.hkdf_extract.out, NULL, INVALID_DEVID); + } + if (info->kdf.type == WC_KDF_TYPE_HKDF_EXPAND) { + return wc_HKDF_Expand_ex(info->kdf.hkdf_expand.hashType, + info->kdf.hkdf_expand.inKey, info->kdf.hkdf_expand.inKeySz, + info->kdf.hkdf_expand.info, info->kdf.hkdf_expand.infoSz, + info->kdf.hkdf_expand.out, info->kdf.hkdf_expand.outSz, + NULL, INVALID_DEVID); + } + if (info->kdf.type == WC_KDF_TYPE_HKDF) { + return wc_HKDF_ex(info->kdf.hkdf.hashType, + info->kdf.hkdf.inKey, info->kdf.hkdf.inKeySz, + info->kdf.hkdf.salt, info->kdf.hkdf.saltSz, + info->kdf.hkdf.info, info->kdf.hkdf.infoSz, + info->kdf.hkdf.out, info->kdf.hkdf.outSz, + NULL, INVALID_DEVID); + } + } +#endif + if (!TestTls13PendMatches(c->target, info)) return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); @@ -9849,6 +9885,14 @@ int test_tls13_cryptocb_async(void) TEST_SUCCESS); ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_KDF, 1), TEST_SUCCESS); +#if !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) + /* Transcript HMACs (Finished verify_data) pending on both sides. */ + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_HMAC, 0), + TEST_SUCCESS); + ExpectIntEQ(test_tls13_cryptocb_pend_one(TEST_TLS13_PEND_HMAC, 1), + TEST_SUCCESS); +#endif #endif #endif return EXPECT_RESULT(); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 93f72779ce6..4164a960e4b 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2366,6 +2366,18 @@ WOLFSSL_LOCAL int ChachaAEADDecrypt(WOLFSSL* ssl, byte* plain, const byte* input WOLFSSL_LOCAL int DecryptTls13(WOLFSSL* ssl, byte* output, const byte* input, word16 sz, const byte* aad, word16 aadSz); WOLFSSL_LOCAL int DoTls13MsgDerives(WOLFSSL* ssl, byte type); +/* A crypto/PK callback pending is finished by re-invoking the provider: + * wolfSSL_AsyncPoll() never runs a callback. Exported so tests compile in + * only where a callback pend is resumable. */ +#if defined(WOLFSSL_ASYNC_CRYPT) && \ + (defined(WOLF_CRYPTO_CB) || defined(HAVE_PK_CALLBACKS)) && \ + !defined(WOLFSSL_ASYNC_CRYPT_SW) && !defined(HAVE_INTEL_QA) && \ + !defined(HAVE_CAVIUM) + #define WOLFSSL_ASYNC_REINVOKE +#endif +#if defined(WOLFSSL_ASYNC_REINVOKE) && !defined(NO_HMAC) +WOLFSSL_LOCAL void Tls13FreeHsHmac(WOLFSSL* ssl); +#endif WOLFSSL_LOCAL int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, byte type, word32 size, word32 totalSz); @@ -6424,16 +6436,6 @@ enum ConnectionIdUsage { /* wolfSSL ssl type */ -/* A crypto/PK callback pending is finished by re-invoking the provider: - * wolfSSL_AsyncPoll() never runs a callback. Exported so tests compile in - * only where a callback pend is resumable. */ -#if defined(WOLFSSL_ASYNC_CRYPT) && \ - (defined(WOLF_CRYPTO_CB) || defined(HAVE_PK_CALLBACKS)) && \ - !defined(WOLFSSL_ASYNC_CRYPT_SW) && !defined(HAVE_INTEL_QA) && \ - !defined(HAVE_CAVIUM) - #define WOLFSSL_ASYNC_REINVOKE -#endif - /* TLS 1.3 key-schedule resume steps (kdfMsgStep/kdfDeriveStep). A value is * recorded after its named operation completes ("step <= X" = X not done); * 0 = sequence not entered or finished. Values repeat across sequences. */ @@ -7076,6 +7078,15 @@ struct WOLFSSL { byte kdfDeriveStep; /* enum Tls13KdfSendStep (send side) */ byte kdfMsgStep; /* enum Tls13KdfMsgStep (receive side) */ byte kdfMsgType; /* handshake type kdfMsgStep belongs to */ +#if defined(WOLFSSL_ASYNC_REINVOKE) && defined(WOLFSSL_TLS13) && \ + !defined(NO_HMAC) + /* Transcript HMAC (Finished verify_data, PSK binders) held across a + * WC_PENDING_E so the retry re-invokes the same object and arguments, + * bound to its output buffer. */ + Hmac* hsHmac; + byte* hsHmacOut; + byte hsHmacStep; +#endif }; #if defined(WOLFSSL_SYS_CRYPTO_POLICY)