Skip to content

prototype: tee-attestation package for TMP router attestation RFC (backs adcontextprotocol/adcp#5770)#395

Draft
ohalushchak-exadel wants to merge 1 commit into
mainfrom
ohalushchak-exadel/tee-attestation-prototype
Draft

prototype: tee-attestation package for TMP router attestation RFC (backs adcontextprotocol/adcp#5770)#395
ohalushchak-exadel wants to merge 1 commit into
mainfrom
ohalushchak-exadel/tee-attestation-prototype

Conversation

@ohalushchak-exadel

Copy link
Copy Markdown
Collaborator

Draft prototype. Not for merge. This exists to answer the byte-layout question bokelley raised on adcontextprotocol/adcp#5770 — his sequencing recommendation was "prototype the Nitro emit-path + one verifier kit in adcp-go first, then promote the proven envelope." This is that.

What this is

A stand-alone tee-attestation/ Go package that implements both sides of the wire shape proposed in the spec RFC:

  • Emit pathNsm interface matching 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 docs/trusted-match/router-attestation.mdx: 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 from the spec's failure-mode table.

Primary deliverable: the byte-layout finding

The whole point of building this is tee-attestation/nitro/PROJECTION.md. Short version:

  1. The spec's "same user-data slot" wording is a category error for Nitro. A real Nitro attestation document has three separate dedicated fields (nonce, public_key, user_data), not one shared slot. This prototype uses them directly:
    • Envelope nonce (raw bytes) → Nitro nonce field
    • Envelope signing_key (raw Ed25519 pubkey bytes) → Nitro public_key field
    • Nitro user_data field → intentionally unused, reserved for extension
  2. Raw pubkey bytes beat encoded-JWK-in-slot for Nitro. Nitro's public_key field is documented as "the public key that the enclave wants to have attested" — exactly what the binding rule anchors against. Raw bytes are simpler than JCS-encoded JWK, and the verifier reconstructs the JWK deterministically to run the thumbprint check.
  3. RFC 7638 thumbprint stays in the verification recipe but is no longer load-bearing for Nitro — byte comparison on the raw pubkey is sufficient; thumbprint agreement is a redundant sanity check that guards against future canonicalization drift.

The spec's slot-projection registry item needs one normative entry per format (Nitro / TDX / SEV-SNP / GCP), not a single "slot" description. That's the payload back into the spec PR.

Round-trip evidence

roundtrip_test.go — 11 tests covering the happy path plus every failure mode from the spec's failure-mode table:

Test Spec failure mode
TestNitroRoundTrip (happy path)
TestVerifyRejectsNonceMismatch nonce_mismatch
TestVerifyRejectsExpiredEnvelope envelope_expired
TestVerifyRejectsUnsupportedFormat unsupported_format
TestVerifyRejectsTamperedDocument platform_verification_failed
TestVerifyRejectsSwappedSigningKey signing_key_not_bound
TestVerifyRejectsWrongRoot platform_verification_failed
TestVerifyRejectsPolicyDisallow measurement_disallowed
TestVerifyAcceptsPerRequestPathWithoutNonceEcho (per-request X-TMP-Attestation code path)
TestEnvelopeJSONRoundTrip (wire format stability)
TestJWKThumbprintStable (RFC 7638 canonicalization)

All pass. Run locally with go test ./tee-attestation/... — no Nitro tooling required.

Explicitly not in this PR

  • Real Nitro NSM. nsm_real.go is a stub behind a nitro build tag. Running against a real Nitro instance is the follow-up.
  • Router integration. cmd/router and router/ do not use this package. Wiring lands once the spec's byte layout is settled — the whole point of building this prototype was that adopting the wire before it's proven ripples every spec change into production code.
  • TDX / SEV-SNP / GCP Confidential Space. Nitro-only for the initial finding.
  • X-TMP-Attestation per-request header carrier. Envelope + verifier first.
  • KMS-bound in-enclave key custody. Mock generates its own keypair.

Dependencies added

  • github.com/veraison/go-cose — COSE_Sign1 sign/verify. Pure Go, actively maintained by the Veraison working group (IETF RATS).
  • github.com/fxamacker/cbor/v2 — CBOR encode/decode with Core Deterministic Encoding option. Pure Go.

Both are pure Go — no CGO added.

Test plan

  • go test ./tee-attestation/... — 11 tests pass.
  • go vet ./tee-attestation/... — clean.
  • gofmt -l tee-attestation/ — clean.
  • go build ./... — full-repo build unchanged.
  • Wire against a real Nitro instance and confirm the mock's wire shape matches (follow-up PR).
  • Reviewer confirms the Nitro projection choice (raw pubkey in public_key field, raw nonce in nonce field) is the right factoring for the spec's slot-projection registry item.

🤖 Generated with Claude Code

…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.
@ohalushchak-exadel
ohalushchak-exadel force-pushed the ohalushchak-exadel/tee-attestation-prototype branch from b68f712 to efde51d Compare July 2, 2026 11:25
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.

1 participant