server(adcp-security): RFC 9421 webhook verifier (Ed25519 + ECDSA-P256, content-digest, typed errors)#2495
server(adcp-security): RFC 9421 webhook verifier (Ed25519 + ECDSA-P256, content-digest, typed errors)#2495EmmaLouise2018 wants to merge 2 commits into
Conversation
…6, content-digest, typed errors) Reference-server implementation of the AdCP 3.0 webhook-signing profile (test vectors were spec-merged in #2445). Uses http-message-signatures with Ed25519 and ECDSA-P256-SHA256 (P1363 -> DER conversion), verifies content-digest, and surfaces typed error codes. Covered by 10 vitest cases: valid-sig, invalid-sig, stale, expired, algorithm-confusion, content-digest-mismatch, etc. Split from closed #2474.
RFC 9421 Webhook Verifier ReviewSolid MVP verifier with good error taxonomy and clean structure. Strong test scaffolding (real signing with node:crypto + fixture keys). A few concerns worth addressing before this gets wired into a handler — several are security-relevant. Critical / security1.
2. Signature base reconstruction risk (
3. Timing-attack surface on digest compare (
4. No
Correctness5. P1363 → DER conversion edge case (
6.
7.
Questions
Nits
Overall: request changes on #3 (timing-safe compare) and #4 (alg-confusion test coverage). Everything else is discussion / polish. Nice work on the P1363→DER conversion and the typed error taxonomy. |
- Use crypto.timingSafeEqual for content-digest compare (content-digest IS in the signature base, so this compare effectively participates in MAC verification). - Split algSpec into algSpecRfc9421 (Signature-Input) and algSpecJwk (JWK alg). Reject JOSE names (EdDSA/ES256) in Signature-Input per RFC 9421 §6.2 IANA naming. - Surface the `tag` parameter on success VerifyResult so call sites can enforce tag === 'adcp/webhook-signing/v1'. - Validate P1363 signature length strictly (expected 64 bytes for P-256); route length mismatches to webhook_signature_header_malformed instead of webhook_signature_invalid. Reject zero-valued r/s. - Route `expires - created > 300` to webhook_signature_header_malformed (spec violation, not clock-skew expiry). - Harden extractLabelValue: anchor on top-level dictionary members with whitespace-tolerant name split and case-insensitive label match, so `sig1=` substrings inside quoted strings or inner lists can't fool the scan. - Make verifyWebhookSignature synchronous (all branches were sync). - Add algorithm-confusion tests (OKP JWK + ecdsa-p256-sha256 alg, EC JWK + ed25519 alg, JOSE names in Signature-Input), an ES256 happy-path test exercising the DER->P1363->DER round-trip, and a P1363 length-tampering test. - Drop toBase64 helper and \`| unknown\` in BareItemValue. - Fill in the changeset description.
|
Thanks for the thorough review. Addressed in bcf73b9. Critical / security
Correctness
Questions
Nits
|
|
Superseded by
If the adcp site server ever needs webhook verification at runtime, it should depend on |
Summary
Reference-server implementation of the AdCP 3.0 webhook-signing profile (test vectors were spec-merged in #2445).
http-message-signatureslib; supports Ed25519 and ECDSA-P256-SHA256 (with P1363 -> DER conversion for P-256)Content-Digestheader matches payloadFiles
server/src/adcp-security/webhook-verifier.ts(new, 485 lines)server/src/adcp-security/__fixtures__/webhook-signature-keys.ts(new, 30 lines)server/tests/unit/webhook-verifier.test.ts(new, 234 lines, 10/10 passing)package.json— addshttp-message-signatures ^1.0.5Context
Split out from closed #2474 so the server-side verifier can land independently of the other red-team-batch-3 workstreams.
Test plan
npm run typecheckpassesnpx vitest run server/tests/unit/webhook-verifier.test.ts— 10/10 cases pass