scep: require transactionID and senderNonce before dispatch - #22
Conversation
There was a problem hiding this comment.
Pull request overview
This PR enforces mandatory SCEP transactionID and senderNonce attributes before dispatch and adds regression coverage.
Changes:
- Rejects missing or empty required attributes.
- Adds raw POST handling and malformed-request tests.
- Covers valid and invalid SCEP message cases.
Review findings:
src/scep/scep_server.c:798— wrap protocol errors to update thread-local diagnostics (moderate, 2 votes).tests/integration/test_scep_roundtrip.c:125— handle short socket writes (moderate, 2 votes).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Summary |
|---|---|
src/scep/scep_server.c |
Validates required SCEP attributes before processing. |
tests/integration/test_scep_roundtrip.c |
Adds raw HTTP handling and required-attribute tests. |
Suppressed comments (2)
src/scep/scep_server.c:802
- The new
snonce_len == 0path is not exercised: the added malformed round atcheck_required_attrs()omitssender_nonceentirely, while onlytransactionIDgets a dedicated zero-length case. Please add an analogous non-NULLsender_noncewith length 0 and assert the same signed FAILURE response, otherwise the empty-senderNonce regression remains untested.
if (snonce == NULL || snonce_len == 0) {
tests/integration/test_scep_roundtrip.c:780
- The new check also rejects a non-NULL senderNonce with
snonce_len == 0, but this round only exercises an absent senderNonce (a.sender_nonce == NULL). A regression that drops the length check would still pass the suite; add a hand-built empty OCTET STRING senderNonce round and assert the same failure CertRep/no-recipientNonce behavior.
else if (i == 2) { /* no senderNonce */
a.transaction_id = tid; a.transaction_id_len = sizeof(tid);
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
c5e89da to
0496c44
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #22
Scan targets checked: wolfcert-bugs, wolfcert-src
No new issues found in the changed files. ✅
Frauschi
left a comment
There was a problem hiding this comment.
Two comments. The senderNonce guard is the substantive one - I think it should return 400 like the transactionID guard directly above it, since no conforming CertRep exists without a senderNonce to echo. The other is a nit on the test harness. The fix itself is right, and both guards correctly land before the deenvelop.
0496c44 to
e049305
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #22
Scan targets checked: wolfcert-bugs, wolfcert-src
Fenrir result: Approved ✅
No new issues found in the changed files.
Advisory only — this automated result does not count as a GitHub approval.
|
Hello @Frauschi , |
- handle_pki_op() answers a pkiMessage whose transactionID or senderNonce is absent or empty with HTTP 400, clearing keep_alive and returning WOLFCERT_ERR_PROTOCOL, ahead of wolfcert_scep_deenvelop(). - raw_http_req() replaces the body of raw_http_status() in the SCEP roundtrip test, taking a method, content type, binary body and persistent flag, and returning the response body; a read error, timeout or failed grow returns -1. write_all_fd() loops over short writes. raw_http_status() wraps it for GET. - check_required_attrs() POSTs five pkiMessages -- no transactionID, zero-length transactionID, no senderNonce, zero-length senderNonce, both present -- expecting HTTP 400 on the first four and a full CertRep on the last, then one over a keep-alive connection to confirm the reject closes it. Issue: F-8042
e049305 to
7f34c6e
Compare
Problem
The SCEP server validated only
messageTypebefore dispatch.wolfcert_scep_parse_pki_message()leavestransactionID/senderNonceNULL when the attribute is absent rather than failing, andbuild_signed_attribs()silently omits any NULL field. A CMS-valid PKCSReq/RenewalReq omitting either therefore reached issuance, and the server signed and returned a CertRep missingtransactionIDand/orrecipientNonceinstead of refusing.RFC 8894 §3.2.1 makes both mandatory in every pkiMessage; §3.2.1.1 and §3.2.1.5 require the response to echo them. Closes f-8042 (High). Also removes a zero-length-
transactionIDcollision in the pending queue and a NULL-to-memcmp()inpending_find().Fix (
src/scep/scep_server.c)handle_pki_op()rejects a pkiMessage missing either attribute with HTTP 400:recipientNonceis copied from the request'ssenderNonce, so a reply to a request lacking one cannot carry it — that CertRep would be non-conforming in exactly the way this PR exists to stop, and our own client rejects it outright (scep_client.c:882), so thefailInfonever reaches the caller. This matches the file's split: a malformed request gets a plain 400 (:815decrypt failure,:828unknown messageType), while a PKI decision on a well-formed request gets a signed CertRep. Nosend_cert_rep()call now passesrecipient_nonce = NULL.messageType-missing andwolfcert_scep_deenvelop(). Placing it first makes the invariant structural rather than dependent on where the branches sit, and costs a rejected request neither an RSA private-key operation nor a CA signature.len == 0matters. An empty attribute on the wire yields a non-NULL zero-length buffer, which a NULL-only check would miss.Tests (
tests/integration/test_scep_roundtrip.c)raw_http_status()is generalized toraw_http_req()(method, content type, binary body, persistent flag, returns the response body); a GET wrapper leaves the existing call sites unchanged.write_all_fd()loops over short writes, and the read loop returns-1on a grow failure or timeout rather than parsing a status out of a truncated response.check_required_attrs()POSTs five hand-built pkiMessages:transactionIDtransactionIDsenderNoncesenderNoncepkiStatus0, envelope, echoedtransactionIDandrecipientNonceA sixth request —
transactionIDpresent, nosenderNonce, nomessageType, over a keep-alive connection — must come back 400, which holds only if the check runs ahead of themessageTypebranch and closes the socket.Verification
tid_len == 0fails round 1 andsnonce_len == 0fails round 3; dropping bothtransactionIDterms fails round 0 and bothsenderNonceterms fails round 2. The two NULL terms are not individually covered — the parser zeroes pointer and length together when an attribute is absent, so the length terms carry all four rounds and the NULL terms are belt and braces. Reverting the block order fails the keep-alive round, and the control passes with and without the fix.Not in this PR
The
400 "Cannot Decrypt"reply is a decryption oracle against PKCS#1 v1.5 key transport; this change narrows reachability but does not close it. PR #23 reworks the remaining rejection paths; with the check placed first here, itssend_pki_failure()needs nosenderNonceguard of its own.