spec(trusted-match): TMP router attestation RFC (experimental)#5770
spec(trusted-match): TMP router attestation RFC (experimental)#5770ohalushchak-exadel wants to merge 4 commits into
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Review — right direction, not yet normativeThe instinct here is right, and so is the framing question of "TEE in the spec vs. adcp-go": a TEE isn't a spec object. But there is a legitimate, TEE-agnostic protocol contribution in this PR, and it mostly finds the correct seam. This is a strong DRAFT/RFC; it's not mergeable as a normative minor-bump yet — one structural flaw plus a couple of factual fixes stand in the way. (Verified against the branch.) What's actually being signed — three objects, three signers, three homes
The envelope JSON itself is unsigned, and that's correct — all integrity flows from the vendor-signed document via nonce-echo + slot-bound-nonce + thumbprint-binding. An envelope signature would only add a second key-management problem. The durable invention is the binding rule (object #2 ties #3's key to #1's attested binary). That's the part worth making normative. Two caveats on what this buys:
TEE-in-spec vs adcp-go: the four-layer cutThe spec-able artifact isn't "a TEE" — it's a demand → carry → anchor capability: a verifier demands (
The line between (1) and (2) is drawn one notch too early — this is the finding I'm most confident in. The PR declares the binding rule a normative MUST, but pushes the slot-projection convention (exactly how nonce + key-thumbprint lay into Nitro Objections that don't hold up on close reading (so we don't chase them)
What I'd change before this goes normative
Minor: give the envelope its own minimal public-JWK schema instead of Net: the map is right — binding-rule/demand-carry-anchor in adcp, TEE plumbing in adcp-go, format parsing in verifier kits, allowlists in deploy. Move the one byte-contract the central MUST depends on up into the spec, fix the RFC-9421 and trust-anchor wording, stop overclaiming, and — since there's no interop deadline — prove the binding in adcp-go before freezing the wire. |
…dge attestation overclaim TMP's per-provider request signing is the bespoke X-AdCP-Signature/ X-AdCP-Key-Id Ed25519 envelope (docs/trusted-match/specification.mdx "Request Authentication"), not RFC 9421. Corrects all mislabeled references introduced by this PR across the normative doc, the JSON schema, the changeset, and the design rationale doc. Also hedges the "binary it expects" attestation-proof overclaim to match the existing measurement-allowlist hedge already used in privacy-architecture.mdx. Per maintainer review: #5770 (comment)
|
Thanks for the detailed review. Pushed a follow-up commit (23f4d5d) addressing the two items that are straight factual fixes: RFC 9421 → Attestation overclaim hedge — reworded the intro paragraph in On the remaining items — the three-way trust-anchor equality, the slot-projection registry question, and the adcp-go-first sequencing recommendation, plus the minor schema-dedup note — I'm leaving those to you and the author. They're structural/design calls (one touches the security model directly) rather than mechanical fixes, and outside what this routine should be deciding unilaterally. Generated by Claude Code |
…ed_match.router_attestation`) Promotes the existing one-paragraph "TEE upgrade path" mention in router-architecture into a normative, experimentally-gated wire shape: a `GET /.well-known/tmp-router-attestation?nonce=...` endpoint, a JSON envelope schema, an `X-TMP-Attestation` per-request header carrier, and a new optional `attestation_requirement` block on provider-registration so providers can declare they require an attested router. The load-bearing invariant is the binding rule: the JWK in `signing_key` MUST appear bound in the platform user-data slot of `attestation_document` alongside the nonce. This anchors the router's existing per-provider RFC 9421 signatures to the attested binary rather than to an out-of-band-deployed key. Verifiers reject when the bound key doesn't byte-match the envelope's `signing_key` (RFC 7638 thumbprint comparison is the acceptable form). Scope is wire shape only. Out of scope: reproducible-build pipeline, KMS provisioning, adcp-go implementation, conformance scenarios, verifier-kit implementations, measurement allowlists. The `attestation_format` enum starts with four canonical externally-defined formats — same canonical-external-identifier pattern as `enums/feed-format.json`; entries added to the platform-agnostic lint allowlist with path-qualified justifications. Additive change to an experimental surface: existing TMP behavior is unchanged for participants that do not opt in. Design rationale (Open Questions, Alternatives Considered, Security Analysis) lives in specs/tmp-router-attestation.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auto-fix from `npm run fix:schema-links` — bare `/schemas/...` paths break Mintlify's link checker; the absolute `https://adcontextprotocol.org/schemas/v3/...` form is what users click. No content changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y-rotation rules TMP already defines a cache convention shared by signing keys and TMPX HPKE keys (specification.mdx#key-rotation: 5-minute TTL, eager re-fetch on verification failure, revoked_at semantics). The attestation envelope binds a signing key, so caching the envelope is functionally the same problem as caching the key — the envelope's `kid` IS the signing key's `kid`. Align rather than invent a parallel discipline. Changes: - specs/tmp-router-attestation.md: new Design §6 Caching subsection cross-referencing the existing key-rotation rules. Open Question §3 (attestation caching) removed — it collapses to "follow the existing convention." Remaining open questions renumbered. - docs/trusted-match/router-attestation.mdx: new Caching section before the RFC 9421 signing-flow section; same cross-reference and the eager-re-fetch SHOULD. - provider-registration.json: `attestation_requirement.min_freshness_sec` default lowered from 3600 to 300, matching the 5-minute signing-key TTL. Schema range (60-86400) unchanged — providers with a defensible reason can still raise it. - Updated description references from Open Question §5 → §4 after the renumber. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dge attestation overclaim TMP's per-provider request signing is the bespoke X-AdCP-Signature/ X-AdCP-Key-Id Ed25519 envelope (docs/trusted-match/specification.mdx "Request Authentication"), not RFC 9421. Corrects all mislabeled references introduced by this PR across the normative doc, the JSON schema, the changeset, and the design rationale doc. Also hedges the "binary it expects" attestation-proof overclaim to match the existing measurement-allowlist hedge already used in privacy-architecture.mdx. Per maintainer review: #5770 (comment)
23f4d5d to
45ac575
Compare
…extprotocol/adcp#5770) Adds a stand-alone Go package that implements both sides of the wire shape proposed in the spec RFC: - Emit path: Nsm interface (matches the AWS Nitro NSM AttestationDoc request shape) plus a mock Nsm that generates real COSE_Sign1 documents signed by a caller-owned test CA. Same wire format as production Nitro, so the verifier code path is byte-for-byte identical between mock and prod. - Verify path: full 9-step verification flow from the normative page — nonce echo, expiry, freshness, format support, COSE_Sign1 parse, cert-chain to a caller-supplied root, ECDSA-P384 signature check, slot-bound nonce, binding rule (RFC 7638 thumbprint), measurement-policy hook. Returns typed VerifyError values naming the failure modes the spec defines. Wired against a `nitro` build tag placeholder for a real /dev/nsm impl; real wiring lands in a follow-up. Router integration deferred until the byte layout in the spec is settled — the whole point of building this prototype is bokelley's finding on the spec PR that the slot bytes are what you learn by binding against a real Nitro document. The finding is documented in nitro/PROJECTION.md and is the primary deliverable: the spec's "same user-data slot" wording is a category error for Nitro (which has three separate fields: `nonce`, `public_key`, `user_data`), and the cleanest projection uses those dedicated fields directly rather than inventing a synthetic packing. That feeds back into the spec's slot-projection registry item and, transitively, its GCP-vs-Nitro asymmetry (GCP is a JWT, not a raw slot). Round-trip test covers the happy path plus all 8 failure modes from the spec's failure-mode table: nonce_mismatch, envelope_expired, unsupported_format, platform_verification_failed (tampered document AND wrong root), signing_key_not_bound, measurement_disallowed, per-request path without nonce echo. All 11 tests pass on the mock; no Nitro hardware required. Not in this prototype (explicit non-goals in the README): - Real Nitro NSM wiring - TDX / SEV-SNP / GCP Confidential Space - Router integration (cmd/router, router/) - X-TMP-Attestation per-request header carrier - KMS-bound in-enclave key custody Deps: github.com/veraison/go-cose, github.com/fxamacker/cbor/v2. Both pure Go, no cgo.
Draft / RFC — opening this for review on the wire shape; not a ship-me-now PR.
Summary
Promotes the existing one-paragraph "TEE upgrade path" mention in
docs/trusted-match/router-architecture.mdxinto a normative, experimentally-gated wire shape:GET /.well-known/tmp-router-attestation?nonce=<base64url, 16-32 raw bytes>static/schemas/source/trusted-match/router-attestation.json(envelope returned by the endpoint)X-TMP-Attestationattached to per-provider outbound requests when the provider requires itattestation_requirementonprovider-registration.jsonso providers declare their policytrusted_match.router_attestation(separate fromtrusted_match.coreandtrusted_match.verified_identity)The trust gap this closes (in one sentence)
Today, the TMP Router's privacy claim depends on operational trust that the operator deployed the audited binary; with this RFC, providers can require an attestation envelope whose binding rule cryptographically anchors the router's existing per-provider RFC 9421 signature path to an attested enclave.
The binding rule (the load-bearing piece — read this first)
Without this rule, a router could attest a clean binary and then sign requests with a different (deployment-side) key — every per-provider signature would verify, and no protocol mechanism would detect the swap. With it, every per-provider signature inherits the attestation.
Cache discipline is aligned with existing TMP key-rotation rules
The envelope binds a signing key, so caching the envelope is functionally the same problem as caching the key. Verifier cache discipline therefore reuses the convention already shared by TMP signing keys and TMPX HPKE keys at
specification.mdx#key-rotation— 5-minute default TTL, eager re-fetch onkidchange,revoked_atflow-through.attestation_requirement.min_freshness_secdefault is 300 seconds to match.Design rationale
See
specs/tmp-router-attestation.md— Problem framing, Design (8 numbered decisions including a Caching subsection cross-referencing the existing key-rotation rules), Security Analysis, Alternatives Considered (per-request attestation / no nonce / measurement allowlist in spec / vendor-neutral URN scheme /ext.tee-only), Open Questions, Non-goals.Scope (wire shape only)
In scope: endpoint, envelope schema, nonce mechanic, binding rule, per-request header carrier, provider-registration policy block, experimental capability gate, allowlist entries for the four canonical externally-defined
attestation_formatenum values (Nitro / TDX / SEV-SNP / Confidential Space).Out of scope: reproducible-build pipeline, KMS provisioning,
adcp-goimplementation, conformance scenarios, verifier-kit implementations, measurement allowlists. All called out explicitly in the rationale's Non-goals section.Open questions raised in the rationale
signing_key). Recommendation: one-envelope-per-key in v1.attestation_formatis superseded — dual-emit envelope vs. coordinated rollout.expires_atbut no uniformissued_at; "age" is verifier-kit-derived from the platform document. Recommendation: verifier-kit-derived; surface in parsed result.(A fifth open question — attestation caching by long-lived providers — was removed after the cache discipline was aligned with the existing TMP key-rotation rules; see the new Caching subsection in the design doc.)
Files changed
specs/tmp-router-attestation.mddocs/trusted-match/router-attestation.mdxstatic/schemas/source/trusted-match/router-attestation.json.changeset/tmp-router-attestation-rfc.mdstatic/schemas/source/trusted-match/provider-registration.jsonattestation_requirementblock (min_freshness_secdefault 300, matching signing-key TTL)docs/trusted-match/router-architecture.mdxdocs/trusted-match/privacy-architecture.mdxdocs/protocol/get_adcp_capabilities.mdx+ schematests/check-platform-agnostic.cjsdocs.jsonAdditive change to an experimental surface
Existing TMP behavior is unchanged for any participant that does not opt in: no
attestation_requirementblock on a provider's registration means routers do not attachX-TMP-Attestationand providers do not require it. Sellers and routers that do support this surface MUST declaretrusted_match.router_attestationinexperimental_features— no silent-experimental, matching the verified-identity gating pattern.Test plan
node tests/check-platform-agnostic.cjs— 0 violationsnpm run test:json-schema— 284 examples validated, 19 tests passednpm run test:schemas— all schema validation passednpm run test:docs-nav— 21 tests passednpm run test:schema-utf8— passednpm run test:examples— 55 passednpm run test:extensions/test:extension-schemas— 31 passednpm test+ typecheck) — 4352 tests passed, 30 skippedmin_freshness_sec+ RFC 9421 signature on the request payload)attestation_formatenum values as canonical externally-defined formats (docs/spec-guidelines.md:264-279)/.well-known/registration is the right discovery layer🤖 Generated with Claude Code