Skip to content

scep: require transactionID and senderNonce before dispatch - #22

Merged
Frauschi merged 1 commit into
wolfSSL:mainfrom
yosuke-wolfssl:fix/f_8042
Sep 9, 2026
Merged

scep: require transactionID and senderNonce before dispatch#22
Frauschi merged 1 commit into
wolfSSL:mainfrom
yosuke-wolfssl:fix/f_8042

Conversation

@yosuke-wolfssl

@yosuke-wolfssl yosuke-wolfssl commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

The SCEP server validated only messageType before dispatch.
wolfcert_scep_parse_pki_message() leaves transactionID / senderNonce NULL when the attribute is absent rather than failing, and build_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 missing transactionID and/or recipientNonce instead 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-transactionID collision in the pending queue and a NULL-to-memcmp() in pending_find().

Fix (src/scep/scep_server.c)

handle_pki_op() rejects a pkiMessage missing either attribute with HTTP 400:

if (tid == NULL || tid_len == 0 || snonce == NULL || snonce_len == 0) {
    s->keep_alive = 0;
    send_text(s, fd, 400, "Bad Message", "text/plain", "");
    rc = WOLFCERT_ERR_PROTOCOL;
    goto out;
}
  • Why 400 and not a FAILURE CertRep. recipientNonce is copied from the request's senderNonce, 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 the failInfo never reaches the caller. This matches the file's split: a malformed request gets a plain 400 (:815 decrypt failure, :828 unknown messageType), while a PKI decision on a well-formed request gets a signed CertRep. No send_cert_rep() call now passes recipient_nonce = NULL.
  • The check precedes every branch that echoes an attribute, including messageType-missing and wolfcert_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 == 0 matters. 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 to raw_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 -1 on a grow failure or timeout rather than parsing a status out of a truncated response. check_required_attrs() POSTs five hand-built pkiMessages:

Round Expect
no transactionID HTTP 400
zero-length transactionID HTTP 400
no senderNonce HTTP 400
zero-length senderNonce HTTP 400
both present (control) pkiStatus 0, envelope, echoed transactionID and recipientNonce

A sixth request — transactionID present, no senderNonce, no messageType, over a keep-alive connection — must come back 400, which holds only if the check runs ahead of the messageType branch and closes the socket.

Verification

  • Clean build under both CMake and autoconf, no warnings.
  • 27/27 ctest pass; 27/27 clean under ASan + UBSan.
  • Mutation-tested. Dropping tid_len == 0 fails round 1 and snonce_len == 0 fails round 3; dropping both transactionID terms fails round 0 and both senderNonce terms 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, its send_pki_failure() needs no senderNonce guard of its own.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Aug 21, 2026
Copilot AI lite review requested due to automatic review settings August 21, 2026 05:40

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 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 == 0 path is not exercised: the added malformed round at check_required_attrs() omits sender_nonce entirely, while only transactionID gets a dedicated zero-length case. Please add an analogous non-NULL sender_nonce with 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.

Comment thread src/scep/scep_server.c Outdated
Comment thread tests/integration/test_scep_roundtrip.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 #22

Scan targets checked: wolfcert-bugs, wolfcert-src

No new issues found in the changed files. ✅

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread src/scep/scep_server.c Outdated
Comment thread tests/integration/test_scep_roundtrip.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 #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.

@yosuke-wolfssl

yosuke-wolfssl commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Hello @Frauschi ,
I fixed the issues you mentioned.
Can you check this please ?

Comment thread src/scep/scep_server.c Outdated
- 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
@Frauschi
Frauschi merged commit 7c256ed into wolfSSL:main Sep 9, 2026
23 checks passed
@yosuke-wolfssl
yosuke-wolfssl deleted the fix/f_8042 branch September 9, 2026 22:43
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