SCEP: add keep-alive / async SCEP session API - #12
Conversation
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #12
Scan targets checked: wolfcert-bugs, wolfcert-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #12
Scan targets checked: wolfcert-bugs
Failed targets: wolfcert-src
Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #12
Scan targets checked: wolfcert-bugs, wolfcert-src
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #12
Scan targets checked: wolfcert-bugs, wolfcert-src
Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #12
Scan targets checked: wolfcert-bugs, wolfcert-src
No new issues found in the changed files. ✅
philljj
left a comment
There was a problem hiding this comment.
First pass, looks good, just a few suggestions.
There was a problem hiding this comment.
Pull request overview
This PR adds a keep-alive, non-blocking SCEP client session API (mirroring the existing EST session), and refactors shared SCEP round-trip logic so one-shot and session paths reuse common prepare/transport/finish helpers. It also factors the shared scheme://host[:port] origin builder into wolfcert_http_url_origin to keep EST/SCEP session-open behavior consistent.
Changes:
- Add public
WolfCertScepSessionAPI with blocking (*_ex) and non-blocking (*_nb) round trips (PKCSReq, RenewalReq, GetCertInitial). - Refactor SCEP client internals into
scep_prepare,scep_build_transport, andscep_finish, shared by one-shot and session implementations. - Add new unit/integration test coverage and build wiring for the origin helper and async SCEP session roundtrips.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| wolfcert/scep.h | Introduces new public SCEP session API and documents async usage/contracts. |
| src/scep/scep_client.c | Implements the session API and refactors SCEP round-trip logic into reusable helpers. |
| src/http.c | Adds wolfcert_http_url_origin helper for consistent origin construction. |
| src/internal.h | Exposes wolfcert_http_url_origin to internal callers/tests. |
| src/est/est_client.c | Switches EST session open to use the shared origin helper. |
| tests/unit/test_http.c | Adds unit tests for wolfcert_http_url_origin. |
| tests/integration/test_scep_async_roundtrip.c | Adds end-to-end poll-driven async SCEP session integration test scenarios. |
| tests/CMakeLists.txt | Registers the new async SCEP integration test under CMake. |
| Makefile.am | Registers the new async SCEP integration test under autoconf/automake. |
| docs/ARCHITECTURE.md | Documents SCEP keep-alive/async sessions and non-blocking behavior constraints. |
| CLAUDE.md | Updates repository guidance to include the new SCEP non-blocking session entry points. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add WolfCertScepSession with blocking (wolfcert_scep_session_open) and non-blocking (wolfcert_scep_session_open_async) opens, plus session PKCSReq, RenewalReq and GetCertInitial in _ex (blocking) and _nb (WANT_READ/WANT_WRITE pumped) forms, with wolfcert_scep_session_fd and wolfcert_scep_session_close. The session reuses the same non-blocking HTTP session layer as EST. Unlike EST it does not require TLS, since SCEP authenticates at the pkiMessage layer, so a plaintext http:// endpoint is accepted. do_scep_round_trip is refactored into reusable scep_prepare (envelope + sign, producing the transactionID and senderNonce) and scep_finish (parse + verify + de-envelop) phases, with the POST/GET transport decision factored into scep_build_transport. The one-shot and session paths share these; existing entry-point behavior is unchanged. An async round trip builds the message once, pumps only the HTTP POST/GET through the session state machine, then parses the CertRep on completion; the crypto stays synchronous. The txid, senderNonce and finish-phase inputs (CA bundle, signer cert/key) are captured in the session so they survive across pumps. scep_prepare now checks the wc_InitRng_ex / wc_RNG_GenerateBlock return codes and fails the round trip on error, rather than building a pkiMessage over an uninitialized transactionID and anti-replay senderNonce. The session records which PKIOperation occupies its single in-flight slot and rejects an async _nb resume that names a different operation or passes a different WolfCertScepResult* out than the one captured at begin; it also records whether it was opened blocking or async so a mismatched _ex/_nb call is rejected with WOLFCERT_ERR_BAD_ARG instead of overwriting (and leaking) the in-flight buffers - including the zeroized private-key copy - or blocking on a non-blocking socket. Like the one-shot path a plaintext http:// session is accepted, but an https:// session now requires verify_server rather than silently running an unauthenticated TLS handshake. The scheme://host[:port] origin builder shared by the EST and SCEP session opens is factored into wolfcert_http_url_origin so the two cannot drift. scep_prepare hands the effective transactionID back on the heap rather than filling a fixed buffer, so a caller-supplied value - whatever the server chose for the request being polled - is carried whole by the one-shot and the session path alike. build_signed_attribs used to copy the attribute through a 128-byte NUL-terminated buffer and silently truncate anything longer, which put an identifier on the wire naming a different transaction than the one being polled for; it now encodes by length, and a value too large for the attribute scratch buffer is an error rather than a quiet trim. Neither the transactionID nor the senderNonce is a secret, since both are sent in the clear as signed attributes, but the RNG scratch behind them is cleared once it is dead and on the RNG failure path, and the session clears its senderNonce when a round trip ends. Tests: test_scep_async_roundtrip drives a direct async enrollment; a blocking-session enrollment followed by a blocking RenewalReq; an async PENDING -> GetCertInitial poll and its blocking mirror (GetCertInitial_ex); an async enrollment followed by an async RenewalReq; and a session forced onto the base64 GET transport. It also exercises the session misuse guards: an _ex call on an async session, an _nb call on a blocking session, and starting a second operation while one is in flight. The async scenarios are pumped through a real poll(2) loop, which treats POLLERR and POLLNVAL as fatal since poll(2) can report readiness and an error event in the same call. test_scep_msg checks that a 200-byte transactionID survives a build/parse round trip byte for byte, and the one-shot and session polls each carry a transactionID longer than the generated one through to the server. Registered in both CMake and autoconf. ARCHITECTURE.md, EMBEDDED context and CLAUDE.md notes updated.
Summary
Adds a keep-alive / non-blocking SCEP (RFC 8894) client session, the SCEP counterpart to the existing EST session. This is the last piece needed for feature-equivalence with wolfSCEP: SCEP enrollment can now run several PKIOperation round trips over one connection, and drive them non-blocking through a caller-owned event loop (
poll/epoll/kqueue).No public API is changed or removed; this is purely additive plus an internal refactor shared with the EST path.
What's included
WolfCertScepSessionwith blocking and non-blocking opens, plus PKCSReq, RenewalReq and GetCertInitial in_ex(blocking) and_nb(WANT_READ/WANT_WRITEpumped) forms.do_scep_round_tripsplit into reusablescep_prepare(envelope + sign),scep_build_transport(POST vs base64 GET), andscep_finish(parse + verify + de-envelop). The one-shot and session paths share these; one-shot behavior is unchanged.scheme://host[:port]construction duplicated by the EST and SCEP session opens is factored intowolfcert_http_url_originso the two cannot drift.ARCHITECTURE.md,EMBEDDED.mdcontext,CLAUDE.md, plus CMake + autoconf registration of the new test.New public API (
wolfcert/scep.h)wolfcert_scep_session_open/_open_asyncwolfcert_scep_session_closewolfcert_scep_session_fdpoll/epoll/kqueuewolfcert_scep_session_pkcs_req_ex/_nbwolfcert_scep_session_renewal_req_ex/_nbwolfcert_scep_session_get_cert_initial_ex/_nbDesign notes
_ex/_nbsplit, same non-blocking HTTP session layer). The crypto stays synchronous; only the HTTP POST/GET is pumped through the state machine. The transactionID, senderNonce, and finish-phase inputs (CA bundle, signer cert/key) are captured in the session so they survive across pumps.http://endpoint is accepted. Anhttps://endpoint, however, now requiresverify_serverrather than silently running an unauthenticated handshake.Hardening and correctness
scep_prepare: awc_InitRng_ex/wc_RNG_GenerateBlockfailure now fails the round trip instead of building a pkiMessage over an uninitialized transactionID and anti-replay senderNonce. (Pre-existing behavior in the one-shot path; fixed for both, now that the code is shared.)WOLFCERT_ERR_BAD_ARG:_ex/_nbpairing against the session's open mode (this also makes an in-flight-buffer / private-key-copy leak unreachable),WolfCertScepResult*than the one captured at begin (guards against a silently-empty result or a dangling-pointer write).Testing
tests/integration/test_scep_async_roundtrip.cdrives the in-tree test server end to end:_ex-on-async,_nb-on-blocking, in-flight op-type mismatchget_cert_initial_ex)The async scenarios are pumped through a real
poll(2)loop. All 25 project tests pass (including the 12 EST tests, which exercise the shared origin-builder refactor); build is warning-clean.