Skip to content

feat: signing for UCP requests & responses#156

Open
igrigorik wants to merge 3 commits intomainfrom
feat/signatures
Open

feat: signing for UCP requests & responses#156
igrigorik wants to merge 3 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[]
@igrigorik
Copy link
Contributor Author

A couple of key takeaways from today's working group call...

1. Do we really need request signatures?

Yes and no.

  • No when alternative auth mechanisms exist: API keys, OAuth client credentials, mTLS. If you've already established trust out-of-band, signatures are redundant for authentication.
  • Yes for the distributed web use case. A self-hosted site can't negotiate API keys with every agent. Signatures with advertised public keys solve the one-to-many scaling problem — merchants can declaratively say "I trust agent A & B" without any out-of-band coordination.

Recommendation: "Businesses SHOULD authenticate agents". The spec should allow multiple mechanisms (API keys, signatures, MTLS) without mandating a single approach. Signatures become valuable when you want permissionless agent onboarding, and we should spec this path.

2. If agent auth is the primary goal, can we simplify what we're signing?

Likely, yes. Current proposal signs the entire request body with JCS canonicalization, which is complex and might be unnecessary — see 3. We could, potentially, skip signing body or consider Content-Digest: sha-256 of the payload bytes, which is a lot simpler. For identity we could lean on RFC 9421 request binding (method, path, idempotency-key, ucp-agent, content-digest?) which would cover request payload & agent attestation.

As a next step, we'll tap security folks to review and help converge on a recommendation.

3. Bonus: can we unify on RFC 9421?

Likely, yes. UCP's MCP transport runs over HTTP, which means we can rely on 9421: single signature mechanism, CDN/edge can validate, no JCS needed. This would be a significant simplification.

  This restructures UCP's signature specification based on working group
  discussion. Key changes:

  Multiple authentication mechanisms:
  - HTTP Message Signatures, API keys, OAuth 2.0, mTLS, choose whichever
  - Businesses choose what fits their security model and integration patterns
  - Implementations SHOULD maintain allowlists of trusted profiles

  RFC 9421 for all HTTP transports:
  - REST and MCP (streamable HTTP) now use the same signing mechanism
  - Signature + Signature-Input headers replace the deprecated Request-Signature
  - Covered components: @method, @path, content-digest, ucp-agent, idempotency-key

  Content-Digest (raw bytes) replaces JCS canonicalization:
  - Request signatures hash raw body bytes per RFC 9530
  - Simpler implementation, no JSON canonicalization complexity
  - JCS retained only for AP2 mandates
@igrigorik
Copy link
Contributor Author

@ACSchil fyi, updated to capture shape we discussed yesterday, ptal.

@igrigorik igrigorik changed the title feat!: signing for UCP requests & responses feat: signing for UCP requests & responses Feb 6, 2026
@igrigorik igrigorik self-assigned this Feb 6, 2026
@igrigorik igrigorik added this to the Working Draft milestone Feb 6, 2026
@igrigorik igrigorik marked this pull request as ready for review February 6, 2026 17:12
@igrigorik igrigorik requested a review from a team February 6, 2026 17:12
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