mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-07 04:30:51 +02:00
Merge pull request #10738 from dgarske/zd_ecc_nonblock_certchain
Add WOLFSSL_ASYNC_CERT_YIELD: per-certificate non-blocking yield
This commit is contained in:
@@ -110,3 +110,81 @@ jobs:
|
||||
cat "$f"
|
||||
fi
|
||||
done
|
||||
|
||||
# Per-certificate non-blocking yield (WOLFSSL_ASYNC_CERT_YIELD): the server
|
||||
# presents a multi-certificate ECC chain (--cert-chain) and the client must
|
||||
# return WC_PENDING_E once per certificate while verifying it.
|
||||
cert_chain_yield:
|
||||
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
async_mode: ['sw', 'cryptocb']
|
||||
name: Per-certificate yield (${{ matrix.async_mode }})
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
name: Checkout wolfSSL
|
||||
|
||||
- name: Build async examples with WOLFSSL_ASYNC_CERT_YIELD
|
||||
run: |
|
||||
make -C examples/async clean
|
||||
make -j -C examples/async ASYNC_MODE=${{ matrix.async_mode }} \
|
||||
EXTRA_CFLAGS="-DWOLFSSL_ASYNC_CERT_YIELD"
|
||||
|
||||
- name: Run --cert-chain pair and assert per-certificate yield
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ASYNC_MODE="${{ matrix.async_mode }}"
|
||||
ready="/tmp/wolfssl_cert_chain_ready"
|
||||
rm -f "$ready"
|
||||
|
||||
WOLFSSL_ASYNC_READYFILE="$ready" \
|
||||
./examples/async/async_server --ecc --cert-chain \
|
||||
> /tmp/cert_chain_server.log 2>&1 &
|
||||
pid=$!
|
||||
|
||||
rc=0
|
||||
WOLFSSL_ASYNC_READYFILE="$ready" \
|
||||
./examples/async/async_client --ecc --cert-chain 127.0.0.1 11111 \
|
||||
> /tmp/cert_chain_client.log 2>&1 || rc=$?
|
||||
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
wait "$pid" >/dev/null 2>&1 || true
|
||||
|
||||
cat /tmp/cert_chain_client.log
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "FAIL: handshake (exit=$rc)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
count=$(awk '/WC_PENDING_E count:/ {print $NF}' \
|
||||
/tmp/cert_chain_client.log)
|
||||
# The 2-cert chain (leaf + root) yields once per certificate.
|
||||
# cryptocb mode has no crypto chunking, so the count is just the
|
||||
# per-certificate yields (>= 2: one per intermediate plus the leaf).
|
||||
# sw mode also chunks the SP ECC math, so the count is much larger.
|
||||
if [ "$ASYNC_MODE" = "cryptocb" ]; then
|
||||
if [ -z "$count" ] || [ "$count" -lt 2 ]; then
|
||||
echo "FAIL: expected >= 2 per-certificate yields," \
|
||||
"got ${count:-missing}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [ -z "$count" ] || [ "$count" -lt 100 ]; then
|
||||
echo "FAIL: WC_PENDING_E count too low: ${count:-missing}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "PASS: $ASYNC_MODE per-certificate yield (WC_PENDING_E: $count)"
|
||||
|
||||
- name: Print cert-chain logs
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
for f in /tmp/cert_chain_server.log /tmp/cert_chain_client.log; do
|
||||
if [ -f "$f" ]; then
|
||||
echo "==> $f"
|
||||
cat "$f"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -729,6 +729,7 @@ WOLFSSL_ASCON_UNROLL
|
||||
WOLFSSL_ASN_EXTRA
|
||||
WOLFSSL_ASN_TEMPLATE_NEED_SET_INT32
|
||||
WOLFSSL_ASN_TEMPLATE_TYPE_CHECK
|
||||
WOLFSSL_ASYNC_CERT_YIELD
|
||||
WOLFSSL_ATECC508
|
||||
WOLFSSL_ATECC508A_NOSOFTECC
|
||||
WOLFSSL_ATECC508A_TLS
|
||||
|
||||
@@ -28,6 +28,14 @@ Supported hardware backends:
|
||||
|
||||
The wolfCrypt backend uses the same API as the hardware backends do. Once an asynchronous operation is initiated with the software backend, subsequent calls to `wolfSSL_AsyncPoll` will call into wolfCrypt to complete the operation. If non-blocking is enabled, for example, for ECC (via `WC_ECC_NONBLOCK`), each `wolfSSL_AsyncPoll` will do a chunk of work for the operation and return, to minimize blocking time.
|
||||
|
||||
## Per-certificate Yield During Chain Verification (`WOLFSSL_ASYNC_CERT_YIELD`)
|
||||
|
||||
By default the TLS handshake verifies every certificate in the peer's chain in a single `wolfSSL_connect()` / `wolfSSL_accept()` call. On a cooperative, single-threaded scheduler a long chain can therefore hold the CPU long enough to trip a watchdog. Building with `WOLFSSL_ASYNC_CRYPT` and the opt-in `WOLFSSL_ASYNC_CERT_YIELD` makes `ProcessPeerCerts()` return `WC_PENDING_E` to the caller after each chain certificate (and after the peer/leaf certificate) is verified, so the application's loop regains control between certificates and can service its watchdog or run other tasks before re-entering. This is independent of `WC_ECC_NONBLOCK`: you get one yield per certificate even when each signature verify is a single blocking call. `WC_ECC_NONBLOCK` additionally subdivides each verify into smaller chunks. The macro is registered with the example/test in `examples/async` (run the client/server with `--cert-chain`).
|
||||
|
||||
Important: these per-certificate yields return `WC_PENDING_E` WITHOUT enqueuing an async device event (`ssl->asyncDev` stays NULL, the event queue stays empty). They are intended for cooperative schedulers that unconditionally re-call `wolfSSL_connect()` / `wolfSSL_accept()` (optionally after a best-effort `wolfSSL_AsyncPoll()`, which simply returns 0 events). They are NOT suitable for event-loop callers that block waiting on the async device file descriptor for a hardware completion, because no such completion is delivered for these yields and the caller would stall during peer certificate processing. Leave `WOLFSSL_ASYNC_CERT_YIELD` undefined (the default) for fd/event-driven async usage.
|
||||
|
||||
If a handshake is abandoned after a per-certificate yield rather than driven to completion, call `wolfSSL_clear()` (or free and recreate the `WOLFSSL` object) before reusing it; `wolfSSL_clear()` clears the pending-yield state so the next handshake starts cleanly.
|
||||
|
||||
## API's
|
||||
|
||||
### ```wolfSSL_AsyncPoll```
|
||||
|
||||
@@ -158,7 +158,8 @@ static int posix_net_connect(const char* host, int port)
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void usage(const char* prog)
|
||||
{
|
||||
printf("usage: %s [--ecc|--x25519] [--mutual] [--tls12] [host] [port]\n",
|
||||
printf("usage: %s [--ecc|--x25519] [--mutual] [--cert-chain] [--tls12] "
|
||||
"[host] [port]\n",
|
||||
prog);
|
||||
}
|
||||
|
||||
@@ -175,7 +176,8 @@ static const char* group_name(word16 group)
|
||||
}
|
||||
|
||||
static int parse_client_args(int argc, char** argv,
|
||||
const char** host, int* port, word16* group, int* mutual, int* tls12)
|
||||
const char** host, int* port, word16* group, int* mutual, int* tls12,
|
||||
int* certChain)
|
||||
{
|
||||
int i;
|
||||
int host_set = 0;
|
||||
@@ -186,6 +188,7 @@ static int parse_client_args(int argc, char** argv,
|
||||
*group = WOLFSSL_ECC_SECP256R1;
|
||||
*mutual = 0;
|
||||
*tls12 = 0;
|
||||
*certChain = 0;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (XSTRCMP(argv[i], "--ecc") == 0) {
|
||||
@@ -197,6 +200,10 @@ static int parse_client_args(int argc, char** argv,
|
||||
else if (XSTRCMP(argv[i], "--mutual") == 0) {
|
||||
*mutual = 1;
|
||||
}
|
||||
else if (XSTRCMP(argv[i], "--cert-chain") == 0) {
|
||||
/* Verify the server's multi-certificate ECC chain (leaf + root). */
|
||||
*certChain = 1;
|
||||
}
|
||||
else if (XSTRCMP(argv[i], "--tls12") == 0) {
|
||||
*tls12 = 1;
|
||||
}
|
||||
@@ -216,6 +223,11 @@ static int parse_client_args(int argc, char** argv,
|
||||
}
|
||||
}
|
||||
|
||||
/* --cert-chain verifies an ECC certificate chain; it is ECC-only. */
|
||||
if (*certChain && *group == WOLFSSL_ECC_X25519) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -252,9 +264,10 @@ int client_async_test(int argc, char** argv)
|
||||
const char* mode = NULL;
|
||||
int mutual = 0;
|
||||
int tls12 = 0;
|
||||
int certChain = 0;
|
||||
|
||||
if (parse_client_args(argc, argv, &host, &port, &group, &mutual,
|
||||
&tls12) != 0) {
|
||||
&tls12, &certChain) != 0) {
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
@@ -383,6 +396,17 @@ int client_async_test(int argc, char** argv)
|
||||
}
|
||||
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL);
|
||||
}
|
||||
else if (certChain) {
|
||||
/* Verify the server's multi-certificate ECC chain (leaf + root)
|
||||
* against the root CA, without presenting a client certificate. */
|
||||
ret = wolfSSL_CTX_load_verify_buffer(ctx, ca_ecc_cert_der_256,
|
||||
sizeof_ca_ecc_cert_der_256, WOLFSSL_FILETYPE_ASN1);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "ERROR: failed to load ECC CA cert.\n");
|
||||
goto out;
|
||||
}
|
||||
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL);
|
||||
}
|
||||
else {
|
||||
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,8 @@ static int posix_set_nonblocking(int fd)
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void usage(const char* prog)
|
||||
{
|
||||
printf("usage: %s [--ecc|--x25519] [--mutual] [--tls12] [port]\n", prog);
|
||||
printf("usage: %s [--ecc|--x25519] [--mutual] [--cert-chain] [--tls12] "
|
||||
"[port]\n", prog);
|
||||
}
|
||||
|
||||
static const char* group_name(word16 group)
|
||||
@@ -133,7 +134,7 @@ static const char* group_name(word16 group)
|
||||
}
|
||||
|
||||
static int parse_server_args(int argc, char** argv, int* port, word16* group,
|
||||
int* mutual, int* tls12)
|
||||
int* mutual, int* tls12, int* certChain)
|
||||
{
|
||||
int i;
|
||||
int port_set = 0;
|
||||
@@ -142,6 +143,7 @@ static int parse_server_args(int argc, char** argv, int* port, word16* group,
|
||||
*group = WOLFSSL_ECC_SECP256R1;
|
||||
*mutual = 0;
|
||||
*tls12 = 0;
|
||||
*certChain = 0;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (XSTRCMP(argv[i], "--ecc") == 0) {
|
||||
@@ -153,6 +155,12 @@ static int parse_server_args(int argc, char** argv, int* port, word16* group,
|
||||
else if (XSTRCMP(argv[i], "--mutual") == 0) {
|
||||
*mutual = 1;
|
||||
}
|
||||
else if (XSTRCMP(argv[i], "--cert-chain") == 0) {
|
||||
/* Present a multi-certificate ECC chain (leaf + root) so the peer
|
||||
* exercises per-certificate processing (and, with
|
||||
* WOLFSSL_ASYNC_CERT_YIELD, the per-cert non-blocking yield). */
|
||||
*certChain = 1;
|
||||
}
|
||||
else if (XSTRCMP(argv[i], "--tls12") == 0) {
|
||||
*tls12 = 1;
|
||||
}
|
||||
@@ -168,6 +176,11 @@ static int parse_server_args(int argc, char** argv, int* port, word16* group,
|
||||
}
|
||||
}
|
||||
|
||||
/* --cert-chain assembles an ECC certificate chain; it is ECC-only. */
|
||||
if (*certChain && *group == WOLFSSL_ECC_X25519) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -187,6 +200,7 @@ int server_async_test(int argc, char** argv)
|
||||
const char* mode = NULL;
|
||||
int mutual = 0;
|
||||
int tls12 = 0;
|
||||
int certChain = 0;
|
||||
#ifdef WOLFSSL_ASYNC_CRYPT
|
||||
int devId = INVALID_DEVID;
|
||||
#endif
|
||||
@@ -216,7 +230,8 @@ int server_async_test(int argc, char** argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (parse_server_args(argc, argv, &port, &group, &mutual, &tls12) != 0) {
|
||||
if (parse_server_args(argc, argv, &port, &group, &mutual, &tls12,
|
||||
&certChain) != 0) {
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
@@ -378,6 +393,42 @@ int server_async_test(int argc, char** argv)
|
||||
goto exit;
|
||||
#endif
|
||||
}
|
||||
else if (certChain) {
|
||||
/* Present a 2-cert ECC chain (leaf + root) assembled from the bundled
|
||||
* buffers so the peer verifies a multi-certificate chain. With
|
||||
* WOLFSSL_ASYNC_CERT_YIELD this exercises the per-certificate
|
||||
* non-blocking yield in ProcessPeerCerts(). Kept static to avoid a
|
||||
* >1KB stack buffer on the small-stack targets this example targets. */
|
||||
static byte eccChain[sizeof_serv_ecc_der_256 +
|
||||
sizeof_ca_ecc_cert_der_256];
|
||||
XMEMCPY(eccChain, serv_ecc_der_256, sizeof_serv_ecc_der_256);
|
||||
XMEMCPY(eccChain + sizeof_serv_ecc_der_256, ca_ecc_cert_der_256,
|
||||
sizeof_ca_ecc_cert_der_256);
|
||||
ret = wolfSSL_CTX_use_certificate_chain_buffer_format(ctx, eccChain,
|
||||
(long)sizeof(eccChain), WOLFSSL_FILETYPE_ASN1);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "ERROR: failed to load ECC server cert chain.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = wolfSSL_CTX_use_PrivateKey_buffer(ctx, ecc_key_der_256,
|
||||
sizeof_ecc_key_der_256, WOLFSSL_FILETYPE_ASN1);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr, "ERROR: failed to load ECC server key buffer.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (mutual) {
|
||||
/* client-ecc-cert is self-signed, so load it as its own CA */
|
||||
ret = wolfSSL_CTX_load_verify_buffer(ctx, cliecc_cert_der_256,
|
||||
sizeof_cliecc_cert_der_256, WOLFSSL_FILETYPE_ASN1);
|
||||
if (ret != WOLFSSL_SUCCESS) {
|
||||
fprintf(stderr,
|
||||
"ERROR: failed to load ECC client CA cert.\n");
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = wolfSSL_CTX_use_certificate_buffer(ctx, serv_ecc_der_256,
|
||||
sizeof_serv_ecc_der_256, WOLFSSL_FILETYPE_ASN1);
|
||||
|
||||
@@ -8766,6 +8766,13 @@ void FreeAsyncCtx(WOLFSSL* ssl, byte freeAsync)
|
||||
ssl->async->freeArgs(ssl, ssl->async->args);
|
||||
ssl->async->freeArgs = NULL;
|
||||
}
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_CERT_YIELD)
|
||||
/* The per-certificate yield flag is tied to an in-progress
|
||||
* ProcessPeerCerts context (which only persists across a yield, never
|
||||
* across this teardown). Clear it here so a later, freshly-allocated
|
||||
* ssl->async can never resume on a stale flag. */
|
||||
ssl->options.certYieldPending = 0;
|
||||
#endif
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NO_TLS12)
|
||||
if (ssl->options.buildArgsSet) {
|
||||
FreeBuildMsgArgs(ssl, &ssl->async->buildArgs);
|
||||
@@ -16052,6 +16059,17 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
|
||||
if (ret < 0)
|
||||
goto exit_ppc;
|
||||
}
|
||||
#ifdef WOLFSSL_ASYNC_CERT_YIELD
|
||||
/* Re-entry after a deliberate per-certificate yield. No async crypto event
|
||||
* was queued, so AsyncPop returns WC_NO_PENDING_E; keep the saved state and
|
||||
* resume cert processing instead of resetting. The flag lives in
|
||||
* ssl->options (zero-initialized) so this never fires on a fresh entry with
|
||||
* a stale args scratch buffer. */
|
||||
else if (ssl->options.certYieldPending) {
|
||||
ssl->options.certYieldPending = 0;
|
||||
ret = 0;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
#endif /* WOLFSSL_ASYNC_CRYPT */
|
||||
#ifdef WOLFSSL_NONBLOCK_OCSP
|
||||
@@ -16775,6 +16793,23 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
|
||||
FreeDecodedCert(args->dCert);
|
||||
args->dCertInit = 0;
|
||||
args->count--;
|
||||
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) && \
|
||||
defined(WOLFSSL_ASYNC_CERT_YIELD)
|
||||
/* return WC_PENDING_E after each chain certificate is
|
||||
* verified so a cooperative scheduler regains control
|
||||
* between certificates. The verify above has fully
|
||||
* completed for this certificate; no async crypto event is
|
||||
* queued, so the certYieldPending flag tells the re-entry
|
||||
* path to resume the loop at the next certificate. */
|
||||
if (ret == 0) {
|
||||
WOLFSSL_MSG("Yielding WC_PENDING_E between chain "
|
||||
"certificate verifies");
|
||||
ssl->options.certYieldPending = 1;
|
||||
ret = WC_PENDING_E;
|
||||
goto exit_ppc;
|
||||
}
|
||||
#endif
|
||||
} /* while (count > 1 && !args->haveTrustPeer) */
|
||||
} /* if (count > 0) */
|
||||
|
||||
@@ -16990,6 +17025,21 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
|
||||
|
||||
/* Advance state and proceed */
|
||||
ssl->options.asyncState = TLS_ASYNC_VERIFY;
|
||||
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_CERT_YIELD)
|
||||
/* Opt-in (WOLFSSL_ASYNC_CERT_YIELD): yield once more after the peer
|
||||
* (leaf) certificate is verified, before OCSP/CRL and finalization.
|
||||
* The state has already advanced to TLS_ASYNC_VERIFY, so the
|
||||
* certYieldPending re-entry path resumes there rather than
|
||||
* re-processing the leaf. */
|
||||
if (ret == 0 && args->count > 0) {
|
||||
WOLFSSL_MSG("Yielding WC_PENDING_E after peer certificate "
|
||||
"verify");
|
||||
ssl->options.certYieldPending = 1;
|
||||
ret = WC_PENDING_E;
|
||||
goto exit_ppc;
|
||||
}
|
||||
#endif
|
||||
} /* case TLS_ASYNC_DO */
|
||||
FALL_THROUGH;
|
||||
|
||||
|
||||
@@ -7901,6 +7901,13 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
|
||||
ssl->options.acceptState = ACCEPT_BEGIN;
|
||||
ssl->options.handShakeState = NULL_STATE;
|
||||
ssl->options.handShakeDone = 0;
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_CERT_YIELD)
|
||||
/* A per-certificate yield (WOLFSSL_ASYNC_CERT_YIELD) sets this and it is
|
||||
* normally cleared on the next ProcessPeerCerts re-entry. Clear it here
|
||||
* so reusing this object after abandoning a yielded handshake cannot
|
||||
* skip the ProcessPeerCerts state reset on the next fresh entry. */
|
||||
ssl->options.certYieldPending = 0;
|
||||
#endif
|
||||
ssl->recordSzOverhead = 0;
|
||||
ssl->options.processReply = 0; /* doProcessInit */
|
||||
ssl->options.havePeerVerify = 0;
|
||||
|
||||
@@ -5302,6 +5302,14 @@ struct Options {
|
||||
#endif
|
||||
word16 returnOnGoodCh:1;
|
||||
word16 disableRead:1;
|
||||
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_CERT_YIELD)
|
||||
/* Opt-in (WOLFSSL_ASYNC_CERT_YIELD): set when we deliberately returned
|
||||
* WC_PENDING_E between peer certificate verifies so a cooperative scheduler
|
||||
* can run. Lives in (zero-initialized, persistent) ssl->options so the
|
||||
* fresh-entry vs. resume decision in ProcessPeerCerts is reliable; the
|
||||
* transient ProcPeerCertArgs scratch buffer is not zeroed on alloc. */
|
||||
word16 certYieldPending:1;
|
||||
#endif
|
||||
|
||||
#ifdef WOLFSSL_EARLY_DATA
|
||||
word16 clientInEarlyData:1; /* Client is in wolfSSL_read_early_data */
|
||||
|
||||
Reference in New Issue
Block a user