Skip to content

SCEP gap-filling#7

Merged
philljj merged 5 commits into
wolfSSL:mainfrom
Frauschi:scep_gaps
Jul 20, 2026
Merged

SCEP gap-filling#7
philljj merged 5 commits into
wolfSSL:mainfrom
Frauschi:scep_gaps

Conversation

@Frauschi

@Frauschi Frauschi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

SCEP (RFC 8894) gap-filling: GET PKIOperation, CA-fingerprint bootstrap, failInfo, and hardening

Summary

This branch closes several RFC 8894 (SCEP) conformance and robustness gaps in the client and the in-tree test server, and finishes with a small cross-cutting cleanup. It adds the HTTP GET PKIOperation fallback, an out-of-band CA-fingerprint trust-bootstrap helper, surfaces the CertRep failInfo code, and moves the server's request handling off the stack. No functional behaviour of the existing POST-based flow changes.

Five self-contained commits, each green on its own:

Commit Area
scep: surface CertRep failInfo and drop unused renewal new_key param client diagnostics + API cleanup
scep: add wolfcert_scep_verify_ca_fingerprint trust-bootstrap helper new public API
scep: add base64 HTTP GET PKIOperation fallback (RFC 8894 4.1) protocol feature + server hardening
scep,est: use wc_ConstantCompare for constant-time comparisons cleanup
ci,interop: enable DES3 and make micromdm SCEP interop strict CI + interop

What's included

1. CertRep failInfo surfaced; renewal API cleanup

  • The client now parses the RFC 8894 §3.2.1.4 failInfo attribute from a FAILURE CertRep and exposes the code (0–4) instead of discarding it, so a caller can tell why an enrollment was rejected.
  • Drops the long-dead new_key parameter from wolfcert_scep_renewal_req[_ex] (it was (void)new_key; — the renewed key already travels inside the CSR).

2. CA-fingerprint trust-bootstrap helper

  • New wolfcert_scep_verify_ca_fingerprint() verifies a downloaded CA/RA certificate against a fingerprint obtained out of band — the standard SCEP trust-on-first-use bootstrap.
  • Supports SHA-256 (always), plus SHA-1 and SHA-512 when compiled in; WOLFCERT_SCEP_FP_AUTO selects the algorithm from the fingerprint length (20 / 32 / 64).

3. HTTP GET PKIOperation fallback (RFC 8894 §4.1) + server hardening

  • Client: when a peer does not advertise POSTPKIOperation, the client carries the base64/percent-encoded pkiMessage in a GET query, capped by WOLFCERT_SCEP_MAX_GET_URL.
  • Server: decodes the GET PKIOperation and dispatches it exactly like a POST.
  • Hardening on the server request path (all in the test server):
    • The request read buffer is now heap-allocated and path/query point into it, dropping peak request-handling stack from ~27 KB to under 1 KB (the GET message no longer lands on the stack). Buffer sizes are unchanged; only their storage moved. The EST server and client are untouched.
    • Fixed a memory leak: a GET carrying a spurious Content-Length body no longer leaks the body buffer when the decoded pkiMessage is installed.
    • Strict query-param matching: operation=/message= are matched as whole parameters (start-of-query or after &) rather than as substrings.
    • The percent-decoder's allocation-failure path now returns a 500, matching its sibling error branches.

4. wc_ConstantCompare adoption

  • wolfSSL now exposes wc_ConstantCompare as a public API (alongside wc_ForceZero). Replaces the three hand-rolled XOR-accumulate constant-time comparisons (CA fingerprint, challenge password, HTTP Basic-auth) with it. Net −16 lines; behaviour is identical (returns 0 iff equal).

5. CI & interop (micromdm/scep)

  • The nightly interop job now runs a real end-to-end SCEP enrollment against micromdm/scep in both directions (wolfcert-client → scepserver, scepclient → wolfcert-server) as a strict pass/fail, replacing the earlier self-heal / KNOWN-FAIL tolerance.
  • --enable-des3 added to the canonical full wolfSSL CI build. micromdm content-encrypts its pkcsPKIEnvelope with single DES-CBC (1.3.14.3.2.7), which wolfSSL disables by default (NO_DES3) → ALGO_ID_E without it. This also makes the existing no-des3 config a real contrast (previously a no-op). Mirrored into the canonical configure line in README.md / CLAUDE.md.
  • Depends on the wolfSSL pkcs7_fix PR (Fix two PKCS#7/CMS Go-interop gaps: SignedData DigestInfo verify + EnvelopedData definite [0] decrypt wolfssl#10928) — two PKCS#7 fixes: wc_PKCS7_DecodeEnvelopedData accepting a definite-length constructed [0] OCTET STRING for encryptedContent, and wc_PKCS7_VerifySignedData tolerating the SignerInfo digestAlgorithm NULL-params vs DigestInfo mismatch. Do not merge before Fix two PKCS#7/CMS Go-interop gaps: SignedData DigestInfo verify + EnvelopedData definite [0] decrypt wolfssl#10928 reaches wolfSSL master — the strict interop job stays red until it does.

Public API changes

Change Kind
wolfcert_scep_verify_ca_fingerprint() + WolfCertScepFpAlg enum added
CertRep failInfo code surfaced on the response status added
new_key parameter removed from wolfcert_scep_renewal_req[_ex] breaking

Breaking change: the new_key parameter was previously ignored, so
in-tree callers were updated mechanically. Any out-of-tree caller of the
8-argument renewal form must drop that argument.

Build requirement

This adds one implicit dependency: wolfCert now calls wc_ConstantCompare, so the linked wolfSSL must provide it (i.e. not built with WOLFSSL_NO_CONST_CMP). This mirrors the assumption already made for wc_ForceZero, so no new configure/check_config.h gate was added.

Testing

  • Full suite green: 24/24 (ctest), both CMake and autoconf.
  • New coverage: GET decode-failure branches (missing message=, malformed percent-escape, invalid base64) driven over a raw socket; the GET-with-body leak-prevention path; SHA-512 fingerprint (explicit + AUTO).
  • The GET-with-body free path is verified clean under ASan + UBSan (the CI sanitizer job provides leak-regression coverage).
  • Interop: a real micromdm/scep enrollment runs both directions end-to-end (see CI & interop above).
  • Review folds: check_ca_fingerprint also asserts UNSUPPORTED when SHA-1/SHA-512 are absent (explicit + AUTO); test_pki_get_url round-trips the encoded message (percent-decode → base64-decode → compare) instead of only checking it is non-empty.

Notes for reviewers

  • The stack→heap move is invisible to the constrained-config CI rows, which build the SCEP server off and exclude roundtrip; the allocation uses the heap hint, so static-memory pools are still honoured.
  • docs/EMBEDDED.md and the internal.h buffer comments were updated to reflect the SCEP request buffer moving to the heap.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #7

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.

Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/unit/test_scep_msg.c Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #7

Scan targets checked: wolfcert-bugs, wolfcert-src

Findings: 4
4 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread tests/integration/test_scep_roundtrip.c Outdated
Comment thread src/scep/scep_client.c Outdated
Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/unit/test_scep_msg.c
Comment thread tests/integration/test_scep_roundtrip.c Outdated
Comment thread src/scep/scep_client.c Outdated
Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/unit/test_scep_msg.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #7

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.

Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/integration/test_scep_roundtrip.c
Comment thread src/scep/scep_client.c Outdated
Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/integration/test_scep_roundtrip.c
Comment thread src/scep/scep_client.c Outdated
The renewal entry points took a new_key argument that was ignored
((void)new_key): the renewed public key is already carried in the CSR
and the pkiMessage is signed with current_key. Remove the dead
parameter from wolfcert_scep_renewal_req and _renewal_req_ex.

Also parse the RFC 8894 section 3.2.1.4 failInfo attribute out of a
FAILURE CertRep and populate WolfCertScepResult.fail_info, which was
previously always left at -1. wolfcert_scep_parse_pki_message gains an
out_fail_info output; all non-consuming callers pass NULL.

The SCEP poll roundtrip test now asserts the client surfaces
failInfo=4 (badCertId) for an unknown transaction.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #7

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.

Comment thread tests/unit/test_scep_msg.c Outdated
Comment thread tests/unit/test_scep_msg.c
Add a public helper to check a GetCACert response against a fingerprint
obtained out of band before it is trusted as a CA anchor. It hashes the
whole DER certificate with SHA-256 (or SHA-1 / SHA-512) and compares in
constant time; WOLFCERT_SCEP_FP_AUTO selects the digest from the
expected length. Returns WOLFCERT_ERR_AUTH on mismatch,
WOLFCERT_ERR_UNSUPPORTED when the digest is not compiled into wolfSSL.
MD5 is intentionally not offered.

Unit-tested in test_scep_msg for match, single-bit tamper, AUTO
dispatch, and length/argument misuse.
@Frauschi

Copy link
Copy Markdown
Contributor Author

Requires wolfSSL/wolfssl#10928

@Frauschi Frauschi assigned wolfSSL-Bot and unassigned Frauschi Jul 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes multiple RFC 8894 SCEP conformance/robustness gaps across the client, in-tree SCEP test server, and test/CI coverage. It adds a GET PKIOperation fallback, a CA-fingerprint trust-bootstrap helper, surfaces failInfo on failures, adopts wc_ConstantCompare, and updates CI/interop/docs accordingly.

Changes:

  • Add SCEP CA-fingerprint trust-bootstrap API (wolfcert_scep_verify_ca_fingerprint) and surface CertRep failInfo.
  • Implement RFC 8894 §4.1 HTTP GET PKIOperation fallback (client + server) with new tests for negative/malformed branches.
  • Harden/configure ecosystem updates: wc_ConstantCompare usage + config gate, DES3 enabled in CI, interop script made strict, docs updated.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
wolfcert/scep.h Adds fingerprint API + enum; removes unused renewal new_key parameter; documents failInfo field.
wolfcert/check_config.h Adds compile-time check for WOLFSSL_NO_CONST_CMP (requires wc_ConstantCompare).
src/scep/scep_client.c Implements fingerprint verification helper, GET PKIOperation URL builder + GET fallback, parses/surfaces failInfo, removes renewal new_key.
src/scep/scep_msg.c Extends wolfcert_scep_parse_pki_message to optionally extract failInfo.
src/scep/scep_server.c Moves request buffer to heap; adds GET PKIOperation decoder/dispatcher; uses wc_ConstantCompare; stricter query-param parsing.
src/internal.h Introduces WOLFCERT_HTTP_QUERY_SZ and WOLFCERT_SCEP_MAX_GET_URL with compile-time consistency guards; exposes GET-URL builder for white-box testing.
src/http.c Raises/guards client URL path+query ceiling to support SCEP GET fallback.
src/est/est_server.c Replaces manual constant-time compare with wc_ConstantCompare for Basic-auth check.
tests/unit/test_scep_msg.c Adds unit coverage for fingerprint verification and GET-URL construction/round-trip.
tests/integration/test_scep_roundtrip.c Adds integration coverage for GET fallback + malformed GET rejection via raw socket requests.
tests/integration/test_scep_poll_roundtrip.c Verifies failInfo is surfaced (e.g., badCertId=4) in poll failure.
tests/interop/scep_micromdm.sh Makes micromdm SCEP interop strict pass/fail in both directions; notes DES3 requirement.
scripts/ci/build-wolfssl.sh Enables --enable-des3 in the “full” wolfSSL CI build flags.
README.md Updates canonical wolfSSL configure line to include --enable-des3.
docs/EMBEDDED.md Updates embedded guidance for new heap-vs-stack request buffering and new tunables.
docs/ARCHITECTURE.md Documents GET fallback behavior and trust-bootstrap helper at the architecture level.
CLAUDE.md Mirrors canonical wolfSSL configure line update (--enable-des3).
Comments suppressed due to low confidence (1)

src/scep/scep_server.c:907

  • The operation dispatch uses strncmp() without checking that the value ends at '&' or '\0'. A query like operation=PKIOperationX&message=... would be accepted and routed as PKIOperation, which defeats the intent of strict query matching and can lead to unexpected handler selection.
    if (strncmp(op, "GetCACaps", 9) == 0 && strcmp(req.method, "GET") == 0) {
        handle_get_ca_caps(s, fd);
    }
    else if (strncmp(op, "GetNextCACert", 13) == 0 && strcmp(req.method, "GET") == 0) {
        handle_get_next_ca_cert(s, fd);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wolfcert/check_config.h
Comment thread src/scep/scep_server.c
Frauschi added 3 commits July 20, 2026 12:50
When the passed caps show the CA does not advertise POSTPKIOperation,
carry the pkiMessage base64-encoded and percent-escaped in a GET
`message` query parameter instead of POSTing it. run_pki_op chooses
POST (the default, and whenever caps are unknown) or GET from the caps;
a GET URL longer than WOLFCERT_SCEP_MAX_GET_URL (8 KiB) is refused with
WOLFCERT_ERR_UNSUPPORTED so the caller falls back to a POST-capable CA.

The in-tree test server learns to accept GET PKIOperation: it decodes
the message parameter and dispatches it exactly like a POST body. Its
query field gets a dedicated WOLFCERT_HTTP_QUERY_SZ (8 KiB) so the
encoded message fits, and the client HTTP path ceiling is raised to
match.

Tests: test_scep_roundtrip enrolls once over the GET path end to end;
test_scep_msg unit-tests URL construction and the oversize rejection.
docs/ARCHITECTURE.md and the docs/EMBEDDED.md tunables table updated.
wolfSSL now exposes wc_ConstantCompare as a public API (alongside
wc_ForceZero). Replace the three hand-rolled XOR-accumulate constant-time
comparisons with it:

  - the CA fingerprint check (scep_client.c, dropping the local ct_diff),
  - the challenge-password check (scep_server.c), and
  - the HTTP Basic-auth credential check (est_server.c).

Each keeps its existing length-mismatch guard, and wc_ConstantCompare
returns 0 iff equal, so behaviour is unchanged.
micromdm/scep content-encrypts its PKCS#7 pkcsPKIEnvelope with single
DES-CBC (1.3.14.3.2.7), which wolfSSL compiles out by default (NO_DES3),
so wc_PKCS7_DecodeEnvelopedData returns ALGO_ID_E. Add --enable-des3 to
the canonical `full` wolfSSL build (build-wolfssl.sh) so the micromdm
interop job can decode both the CertRep (D2) and the PKCSReq (D1)
envelopes. This also makes the existing `no-des3` config a real contrast
rather than a no-op, since wolfSSL defaults DES off. Mirror the flag into
the canonical configure line in README.md and CLAUDE.md.

Make scep_micromdm.sh require both directions to pass. This wolfCert PR
now depends on the wolfSSL pkcs7_fix PR (EnvelopedData constructed-[0]
OCTET STRING decode + DigestInfo NULL-params signature fix), so the
earlier self-heal / KNOWN-FAIL tolerance is dropped: D2 and D1 are strict
assertions that fail on any interop error and dump the client log. Also
fix the D1 check to accept micromdm's PEM cert (it parsed DER before).
@philljj
philljj merged commit 322cbb0 into wolfSSL:main Jul 20, 2026
21 checks passed
@Frauschi
Frauschi deleted the scep_gaps branch July 21, 2026 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants