Skip to content

feat!: signing for UCP requests & responses#156

Draft
igrigorik wants to merge 2 commits intomainfrom
feat/signatures
Draft

feat!: signing for UCP requests & responses#156
igrigorik wants to merge 2 commits intomainfrom
feat/signatures

Conversation

@igrigorik
Copy link
Contributor

Context / Closes #135. Defines a layered signature architecture:

  SHARED FOUNDATION
  ├── Canonicalization: JCS (RFC 8785)
  ├── Algorithms: ES256 (required), ES384, ES512
  ├── Key Format: JWK (RFC 7517)
  ├── Key Discovery: signing_keys[] in /.well-known/ucp
  └── Replay Protection: idempotency-key (business layer)
          │
          ├── REST BINDING (RFC 9421)
          │   Headers: Signature, Signature-Input, UCP-Content-Digest-JCS
          │
          └── MCP BINDING (RFC 7515 Appendix F)
              Fields: meta.signature, meta.idempotency-key, meta.ucp-agent

JCS canonicalization (RFC 8785):

  • Deterministic JSON serialization before signing
  • Avoids whitespace/key-ordering verification failures
  • Custom header UCP-Content-Digest-JCS (not RFC 9530 Content-Digest)
    because we hash canonicalized JSON, not raw bytes

Transport-specific formats:

  • REST uses RFC 9421 HTTP Message Signatures (modern standard)
  • MCP uses detached JWS (RFC 7515 Appendix F) in meta.signature
  • Both sign the full message; MCP excludes only meta.signature field

Changes to OpenAPI:

  • Request-Signature header → Signature + Signature-Input headers
  • X-Detached-JWT response header → Signature + Signature-Input
  • UCP-Content-Digest-JCS header for body digest

Other updates:

  • checkout-rest.md, checkout-mcp.md: Added Message Signing sections
  • order.md: Rewrote webhook signing to use RFC 9421
  • ap2-mandates.md: Now references signatures.md for crypto primitives
  • openrpc.json: Added signature field to meta, added meta to cart methods
  • mkdocs.yml: Added signatures.md to nav and llmstxt sections

Checklist

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

  Context / Closes #135.

  Defines a layered signature architecture:

  SHARED FOUNDATION
  ├── Canonicalization: JCS (RFC 8785)
  ├── Algorithms: ES256 (required), ES384, ES512
  ├── Key Format: JWK (RFC 7517)
  ├── Key Discovery: signing_keys[] in /.well-known/ucp
  └── Replay Protection: idempotency-key (business layer)
          │
          ├── REST BINDING (RFC 9421)
          │   Headers: Signature, Signature-Input, UCP-Content-Digest-JCS
          │
          └── MCP BINDING (RFC 7515 Appendix F)
              Fields: meta.signature, meta.idempotency-key, meta.ucp-agent

  JCS canonicalization (RFC 8785):
  - Deterministic JSON serialization before signing
  - Avoids whitespace/key-ordering verification failures
  - Custom header `UCP-Content-Digest-JCS` (not RFC 9530 Content-Digest)
    because we hash canonicalized JSON, not raw bytes

  Transport-specific formats:
  - REST uses RFC 9421 HTTP Message Signatures (modern standard)
  - MCP uses detached JWS (RFC 7515 Appendix F) in meta.signature
  - Both sign the full message; MCP excludes only meta.signature field

  Changes to OpenAPI:
  - `Request-Signature` header → `Signature` + `Signature-Input` headers
  - `X-Detached-JWT` response header → `Signature` + `Signature-Input`
  - `UCP-Content-Digest-JCS` header for body digest

  Other updates:
  - checkout-rest.md, checkout-mcp.md: Added Message Signing sections
  - order.md: Rewrote webhook signing to use RFC 9421
  - ap2-mandates.md: Now references signatures.md for crypto primitives
  - openrpc.json: Added signature field to meta, added meta to cart methods
  - mkdocs.yml: Added signatures.md to nav and llmstxt sections
@igrigorik igrigorik changed the title feat!(signatures): signing for UCP requests & responses feat!: signing for UCP requests & responses Feb 4, 2026
Copy link
Collaborator

@drewolson-google drewolson-google left a comment

Choose a reason for hiding this comment

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

Generally looks very good to me. I've included a few small comments.

  - Separate "Implementation requirements" (MUST verify ES256) from
    "Usage guidance" (SHOULD use ES256 for compatibility)
  - Removed "Signers: use newest key" - no way to determine key age
    from JWK spec, and verifiers accept any key in signing_keys[]
For signature computation over JSON payloads, implementations **MUST** use
**JSON Canonicalization Scheme (JCS)** as defined in
[RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785).
All JSON payloads **MUST** be canonicalized using **JSON Canonicalization

Choose a reason for hiding this comment

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

AIUI canonicalized JSON is generally considered much more problematic than signing the body bytes of the serialized JSON. I would highly recommend we avoid doing so.

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.

[Spec] Clarify Request-Signature for Checkout REST binding (define signing/verification; RFC 9421 discussion)

3 participants