From 1b59d26ab4937abeaf589af89e04a786186a8ede Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 27 Jul 2026 13:31:52 -0700 Subject: [PATCH 1/3] test(tpm): validate the TPM verifier against a real Azure vTPM quote Captured a genuine TPM 2.0 quote from an Azure Trusted Launch VM (vTPM, secure boot, PCRs 0-7 under a fresh nonce). The verifier's parse, magic and type checks, qualifying-data binding, PCR digest and AK RSA PKCS#1 v1.5 SHA-256 signature all hold on it, and a flipped bit in the attest blob is rejected. Env-gated on CA2A_TPM_FIXTURE_DIR; the capture is not committed. What this does not close, stated in docs/hardware-validation.md rather than glossed: the AK certificate chain. Azure's pre-provisioned AK certificate certifies a different key than an in-guest tpm2_createak AK, so it does not verify against this quote, and it carries no AIA extension, so its issuing intermediate is not fetchable from it. Chain-to-vendor-root stays unexercised for TPM, which is why this verifier takes caller-supplied roots rather than pinning one. This is also a Hyper-V vTPM, not a discrete TPM chip. Co-Authored-By: Claude Opus 5 (1M context) --- docs/hardware-validation.md | 38 ++++++++++++++++++++++++++++++--- tests/unit/test_tpm.py | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/hardware-validation.md b/docs/hardware-validation.md index ac6b26d..f3c6783 100644 --- a/docs/hardware-validation.md +++ b/docs/hardware-validation.md @@ -14,7 +14,7 @@ and the run is recorded below. |---|---|---|---|---| | AMD SEV-SNP (Azure CVM, vTPM-rooted) | Yes | Yes, to the real AMD ARK-Milan root | Yes | **Yes**, 2026-07-27, capture of 2026-07-20 | | Intel TDX (GCP C3, non-paravisor) | Yes | Yes, to the real Intel SGX Root CA | Yes | **Yes**, 2026-07-27, capture of 2026-07-21 | -| TPM 2.0 | Yes | Yes, to a caller-supplied vendor root | Yes | No. Synthetic vectors only | +| TPM 2.0 (Azure vTPM, Trusted Launch) | Yes | Yes, to a caller-supplied vendor root | Yes | **Partly**, 2026-07-27. Parse, bindings and AK signature yes; certificate chain no, see below | ## What these runs do and do not establish @@ -70,10 +70,42 @@ flat layout, so the tests validated the defect. Failure was closed, so this was false negative rather than an unsound accept, but the TDX path had never worked against real evidence. Synthetic self-consistency is not validation. +## TPM 2.0, Azure Trusted Launch vTPM + +Evidence: a `TPMS_ATTEST` quote over PCRs 0-7 (SHA-256) from a `Standard_D2s_v7` +Ubuntu 24.04 VM with Trusted Launch, vTPM and secure boot enabled, with a fresh +32-byte nonce as qualifying data. The AK was created in-guest with +`tpm2_createak` (RSA, RSASSA, SHA-256). + +What the run checks: `TPMS_ATTEST` parsing against a real blob (magic +`0xFF544347`, type `0x8018`), the qualifying-data binding equalling the nonce +byte for byte, a non-zero PCR digest matching what the TPM reported at quote +time, the AK's RSA PKCS#1 v1.5 SHA-256 signature over the attest blob, and +rejection of a single flipped bit. + +``` +CA2A_TPM_FIXTURE_DIR= pytest tests/unit/test_tpm.py +``` + +What it does **not** check, which matters: the AK certificate chain. Azure's +pre-provisioned AK certificate (read from vTPM NV index `0x01C101D0`, subject +`CN=.TrustedVM.Azure.windows.net`, issuer `CN=Global Virtual TPM CA - 03`) +certifies a *different* key than the one that signed this quote, so it does not +verify against it, and the certificate carries no AIA extension, so its issuing +intermediate is not fetchable from it. Chain-to-vendor-root therefore remains +unexercised for TPM. This is exactly why the TPM verifier takes caller-supplied +trust roots rather than pinning one, unlike SEV-SNP (AMD ARK) and TDX (Intel SGX +Root CA); the shared `verify_cert_chain` those two use is the same code path and +is exercised against real vendor roots there. + +Note also that this is a Hyper-V vTPM, which is what Azure confidential and +Trusted Launch VMs actually present, not a discrete TPM chip. + ## Not yet validated -- **TPM 2.0**: implemented and synthetic-vector validated; needs a real AK-signed - quote plus a vendor AK certificate chain. +- **TPM certificate chain**: needs a quote signed by Azure's pre-provisioned AK + (the one its NV certificate covers) plus Microsoft's `Global Virtual TPM CA` + intermediate, which is not distributed with the certificate. - **Live attested peer binding**: the handshake gating the sealed channel on a verified channel key runs in software mode. Driving it off a real quote on a confidential VM is the remaining hardware property, and the precondition for diff --git a/tests/unit/test_tpm.py b/tests/unit/test_tpm.py index 58ae2f6..da248f5 100644 --- a/tests/unit/test_tpm.py +++ b/tests/unit/test_tpm.py @@ -7,6 +7,7 @@ from __future__ import annotations +import os import struct import pytest @@ -118,3 +119,44 @@ def test_provider_detect_and_attest() -> None: assert TpmProvider.detect() is False with pytest.raises(AttestationUnsupported): TpmProvider().attest("deadbeef", "nonce") + + +@pytest.mark.skipif( + not os.environ.get("CA2A_TPM_FIXTURE_DIR"), + reason="set CA2A_TPM_FIXTURE_DIR to a dir with quote.msg + quote.sig + ak.pub + " + "nonce.hex (a real TPM capture) to run the hardware test", +) +def test_real_tpm_quote_parses_and_verifies() -> None: + """A genuine vTPM quote, end to end (see docs/hardware-validation.md). + + Covers the parse, the magic/type checks, the qualifying-data binding and the + AK signature. It does NOT cover the AK certificate chain: Azure's AK cert + certifies its pre-provisioned AK rather than the one that signed this quote, + and carries no AIA extension to fetch its issuer. That is why this verifier + takes caller-supplied roots instead of pinning one. + """ + import pathlib + + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding + + d = pathlib.Path(os.environ["CA2A_TPM_FIXTURE_DIR"]) + attest = (d / "quote.msg").read_bytes() + signature = (d / "quote.sig").read_bytes() + nonce = bytes.fromhex((d / "nonce.hex").read_text().strip()) + ak_pub = serialization.load_pem_public_key((d / "ak.pub").read_bytes()) + + quote = TpmQuote.parse(attest) + assert quote.magic == 0xFF544347 # TPM_GENERATED + assert quote.attest_type == 0x8018 # TPM_ST_ATTEST_QUOTE + assert quote.qualifying_data == nonce # freshness binding holds on real evidence + assert len(quote.pcr_digest) == 32 + assert quote.pcr_digest != bytes(32) + + ak_pub.verify(signature, attest, padding.PKCS1v15(), hashes.SHA256()) + + tampered = bytearray(attest) + tampered[100] ^= 0x01 + with pytest.raises(InvalidSignature): + ak_pub.verify(signature, bytes(tampered), padding.PKCS1v15(), hashes.SHA256()) From 59d1cf5c8367260812d8367e3399e15085b5ae25 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 27 Jul 2026 13:49:38 -0700 Subject: [PATCH 2/3] docs: the attestation seam ran off a real SEV-SNP quote; assurance=hardware Drove ca2a_runtime.attestation's verifier seam off a live SEV-SNP quote on an Azure DC2ads_v5 confidential VM. verify_offer returned assurance="hardware" rather than "none" for the first time, a payload was sealed to a channel key a hardware-verified measurement vouches for and opened inside the enclave, and both a measurement mismatch and a stale nonce were rejected. Azure SEV-SNP is paravisor-mediated, so the binding is one hop longer than bare-metal: the caller's nonce rides in the AK-signed TPM quote, the paravisor binds that AK into the SNP report's REPORT_DATA, and the report chains VCEK to ASK to ARK-Milan against AMD KDS. docs/hardware-validation.md spells that out. What this does not close is said in the same breath, in LIMITATIONS and the ROADMAP: the run had one attested peer with the caller on the same host, so cA2A is still not demonstrated attested across trust domains. That needs two independently operated attested peers. Software mode also remains the default for the reference transport; the hardware path is a validated capability, not the shipped default. Co-Authored-By: Claude Opus 5 (1M context) --- LIMITATIONS.md | 2 +- ROADMAP.md | 3 +- docs/hardware-validation.md | 60 ++++++++++++++++++++++++++++++------- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/LIMITATIONS.md b/LIMITATIONS.md index b412069..b726d25 100644 --- a/LIMITATIONS.md +++ b/LIMITATIONS.md @@ -10,7 +10,7 @@ cA2A is a pre-release profile in active design. This document states plainly wha ## What is stubbed or not yet implemented -- **Hardware-attested live binding.** The live transport and handshake run today in **software mode only** (the reference server/client above, `assurance="none"`). The remaining gap is hardware: driving the `verifier` seam in `ca2a_runtime.attestation` off a real SEV-SNP, TDX, or TPM quote (wrapping `ca2a_verify`) so the peer's channel key is bound to a hardware-verified enclave measurement, and relying on the enclave to hold the channel private key. There is also no CLI listener (`ca2a start`); serving is via `ca2a_runtime.transport.server.serve`. A software-mode live call is Tier 2 progress, not a claim that cA2A is attested across trust domains. +- **Hardware-attested live binding.** The `verifier` seam in `ca2a_runtime.attestation` has now been driven off a real SEV-SNP quote on an Azure confidential VM: `verify_offer` returned `assurance="hardware"`, a payload was sealed to a channel key a hardware-verified measurement vouches for, and both a measurement mismatch and a stale nonce were rejected. See [docs/hardware-validation.md](docs/hardware-validation.md). Two gaps remain. First, the reference server/client still run in **software mode** by default (`assurance="none"`); the hardware path is a validated capability, not the default configuration, and there is no CLI listener (`ca2a start`) since serving is via `ca2a_runtime.transport.server.serve`. Second, the run had a single attested peer with the caller on the same host, so **cA2A is still not demonstrated attested across trust domains**: that needs two independently operated attested peers, which is what the cross-operator claim rests on. - **Sealed peer channel (hardware property).** The channel is implemented: a payload is sealed to the peer's attested X25519 key (X25519 ECDH, HKDF-SHA256, ChaCha20-Poly1305), and only the holder of the peer's private key can open it. On a live call the handshake now gates the seal on a channel key the caller has appraised, but in software mode that appraisal is `assurance="none"`. Until the seal is bound to a hardware-verified measurement (above), do not assume a payload is confined to a specific attested measurement. Adapter-decoded `sealed_payload` bytes are opaque ciphertext only. - **Real hardware attestation.** The **SEV-SNP and Intel TDX verifiers now appraise genuine hardware evidence end to end**: a real Azure CVM SEV-SNP report (VCEK chain to the AMD ARK-Milan root, ECDSA-P384 report signature, measurement binding) and a real GCP C3 DCAP v4 TDX quote (PCK chain to the Intel SGX Root CA, QE binding, quote signature, MRTD binding), both fail-closed and both rejecting a tampered copy. Runs are recorded in [docs/hardware-validation.md](docs/hardware-validation.md). The **TPM 2.0 verifier** (AK chain to a caller-supplied vendor root, AK signature, magic/type, qualifying-data and PCR-digest binding) is implemented but synthetic-vector validated only. Quote *generation* still requires the respective hardware for all three. This validates the verifier, not a running attested peer: until the `verifier` seam in `ca2a_runtime.attestation` is driven off a live quote on a confidential VM, cA2A must not be described as attested across trust domains. diff --git a/ROADMAP.md b/ROADMAP.md index 42d0dc5..0fe7469 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -34,7 +34,8 @@ Real hardware attestation verification (SEV-SNP VCEK chain, Intel TDX quote via - **TDX verifier: landed and validated on real evidence.** DCAP Quote v4 parsing (including the nested type-6 QE certification data), PCK chain to the genuine Intel SGX Root CA, QE report signature, attestation-key binding, quote signature, and MRTD binding, all fail-closed, run against a genuine GCP C3 quote. Quote generation requires a real TDX guest. See `ca2a_verify.tdx`. - **TPM 2.0 verifier: landed.** TPMS_ATTEST parsing, AK chain to a caller-supplied vendor root, AK signature (ECDSA or RSA), magic/type checks, and qualifying-data/PCR-digest binding, all fail-closed. Quote generation requires a real TPM. See `ca2a_verify.tpm`. - **Cross-operator attestation (C6): validated in software.** A two-operator harness (SEV-SNP verifier + measurement pinning + sealed channel) shows independent keys, mutual attestation, confidential cross-operator delegation, and binary-swap detection. All six claims (C1-C6) are now validated experiments. -- **Pending:** the TPM signature path against a real quote, and a live attested peer (the `verifier` seam driven off a hardware quote on a running confidential VM). SEV-SNP and TDX appraisal of real evidence is done. The transport that parses A2A messages into a `PeerRequest` has **landed** (`ca2a_runtime.transport.a2a`), running in software mode; the hardware seam is the `verifier` callable in `ca2a_runtime.attestation`. +- **Live attested peer: landed.** The `verifier` seam has been driven off a real SEV-SNP quote on an Azure confidential VM, so `verify_offer` returned `assurance="hardware"` and a payload was sealed to a hardware-vouched channel key; measurement mismatch and stale nonce both rejected. See [docs/hardware-validation.md](docs/hardware-validation.md). +- **Pending:** a two-operator run on hardware (two independently operated attested peers, ideally across TEE families), which is what the cross-operator claim needs, and the TPM certificate-chain path. TPM parsing, bindings and the AK signature are validated against a real Azure vTPM quote; SEV-SNP and TDX appraisal of real evidence is done. The transport that parses A2A messages into a `PeerRequest` has **landed** (`ca2a_runtime.transport.a2a`), running in software mode; the hardware seam is the `verifier` callable in `ca2a_runtime.attestation`. ## v1.0: Stable profile diff --git a/docs/hardware-validation.md b/docs/hardware-validation.md index f3c6783..c1b479a 100644 --- a/docs/hardware-validation.md +++ b/docs/hardware-validation.md @@ -19,17 +19,21 @@ and the run is recorded below. ## What these runs do and do not establish They establish that the appraisal path in `ca2a_verify` accepts genuine evidence -from that silicon and fails closed on a tampered copy. They do **not** establish -that cA2A runs inside a TEE: quote *generation* still requires the hardware, and -the `verifier` seam in `ca2a_runtime.attestation` is not yet driven off a live -quote on a running peer. A live cA2A call is still software mode -(`assurance="none"`). - -So the claim that is now true is "the cA2A verifier appraises real SEV-SNP and -TDX evidence." The claim that is still not true is "cA2A is attested across trust -domains." The second needs a peer whose channel key is bound to a hardware -measurement on a live confidential VM, which stays on the critical path in -[ROADMAP.md](../ROADMAP.md). +from that silicon and fails closed on a tampered copy. + +As of 2026-07-27 the `verifier` seam in `ca2a_runtime.attestation` has also been +driven off a live SEV-SNP quote on a running confidential VM, so `verify_offer` +returned `assurance="hardware"` instead of `"none"` for the first time, and a +payload was sealed to a channel key that a hardware-verified measurement vouches +for. See the live-peer section below. + +What is still not established is a two-operator deployment on hardware. The live +run had one attested peer; the cross-operator harness in +`examples/cross-operator-delegation` is still software-attested with synthetic +vectors. Quote generation also still requires the platform, so nothing running +off a CVM is attested. Until a cross-operator run lands, "attested across trust +domains" describes a demonstrated single-peer capability, not a demonstrated +cross-domain deployment; see [ROADMAP.md](../ROADMAP.md). Verification is also bounded to a remote or rogue-admin adversary, not to one with physical access: [TEE.fail](https://tee.fail) extracts attestation keys from @@ -70,6 +74,40 @@ flat layout, so the tests validated the defect. Failure was closed, so this was false negative rather than an unsound accept, but the TDX path had never worked against real evidence. Synthetic self-consistency is not validation. +## Live attested peer on a SEV-SNP confidential VM + +Run 2026-07-27 on a `Standard_DC2ads_v5` Azure CVM (Ubuntu 24.04 CVM image, +eastus). The guest reports `Detected confidential virtualization sev-snp` with +`SEV: Status: vTom` and no `/dev/sev-guest`, the expected paravisor shape. + +Azure SEV-SNP is paravisor-mediated, so the guest cannot write `REPORT_DATA` +directly. The binding is therefore one hop longer than bare-metal SNP: + +``` +channel key + caller nonce -> AK-signed TPM quote (extraData) +vTPM AK -> SNP REPORT_DATA == sha256(runtime_data) +SNP report -> VCEK -> ASK -> ARK-Milan (AMD KDS) +``` + +Driving `ca2a_runtime.attestation`'s `verifier` seam off that chain: + +| Result | Value | +|---|---| +| `assurance` | `hardware` (previously always `none`) | +| Verified measurement | matches the launch measurement in the live SNP report | +| Sealed payload round trip | opened inside the enclave, 102 bytes of ciphertext | +| Binary swap | rejected, "offered measurement does not match the verified report" | +| Stale nonce | rejected before any hardware work | + +This is the item that gated dropping the software-mode caveat on the sealed +channel: the payload was sealed to a key that a hardware-verified measurement +vouches for, not to an unappraised key. The transcript and the bridge script are +kept out of the repository with the other captures. + +What it is not: a two-operator run. One peer was attested, by a caller on the +same host. Two independently operated attested peers, ideally on different TEE +families, is the next step and is what the cross-operator claim needs. + ## TPM 2.0, Azure Trusted Launch vTPM Evidence: a `TPMS_ATTEST` quote over PCRs 0-7 (SHA-256) from a `Standard_D2s_v7` From c7cf1bd6e807c6e26cfc3ac5f9f63e31977aef00 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 27 Jul 2026 16:46:37 -0700 Subject: [PATCH 3/3] docs: record the cross-operator, cross-TEE run (Azure SNP to GCP TDX) An AMD SEV-SNP peer on Azure called an Intel TDX peer on GCP: different clouds, different operators, unrelated attestation formats. The caller appraised the callee's genuine 8KB TDX quote against the pinned Intel SGX Root CA, confirmed the quote's REPORTDATA bound the callee's channel key under a nonce the caller chose, and only then sealed a delegated task. The TDX enclave opened it, enforced a scope narrowed by a chain it did not issue, allowed tool:search, and refused tool:purchase with a denial record carrying the requested capability, the effective scope and the reason back across the trust boundary. A stale nonce was rejected. That is the four primitives holding at once across a real trust boundary, which is what the cross-operator claim rested on. Two honest limits are recorded with it: the attestation was one-directional, so the callee never appraised the caller, and both peers ran from one operator's harness. Mutual simultaneous attestation is the remaining step. The committed cross-operator example stays software-attested, because real evidence embeds per-CPU identifiers and cannot be shipped as a fixture. Also carries the two commits orphaned when #61 merged: the real Azure vTPM quote test and the assurance="hardware" live-peer run. Co-Authored-By: Claude Opus 5 (1M context) --- LIMITATIONS.md | 2 +- ROADMAP.md | 3 +- docs/hardware-validation.md | 49 ++++++++++++++++++++---- examples/rejection-with-proof/chain.json | 18 ++++----- examples/rejection-with-proof/dag.json | 14 +++---- 5 files changed, 61 insertions(+), 25 deletions(-) diff --git a/LIMITATIONS.md b/LIMITATIONS.md index b726d25..c85b103 100644 --- a/LIMITATIONS.md +++ b/LIMITATIONS.md @@ -10,7 +10,7 @@ cA2A is a pre-release profile in active design. This document states plainly wha ## What is stubbed or not yet implemented -- **Hardware-attested live binding.** The `verifier` seam in `ca2a_runtime.attestation` has now been driven off a real SEV-SNP quote on an Azure confidential VM: `verify_offer` returned `assurance="hardware"`, a payload was sealed to a channel key a hardware-verified measurement vouches for, and both a measurement mismatch and a stale nonce were rejected. See [docs/hardware-validation.md](docs/hardware-validation.md). Two gaps remain. First, the reference server/client still run in **software mode** by default (`assurance="none"`); the hardware path is a validated capability, not the default configuration, and there is no CLI listener (`ca2a start`) since serving is via `ca2a_runtime.transport.server.serve`. Second, the run had a single attested peer with the caller on the same host, so **cA2A is still not demonstrated attested across trust domains**: that needs two independently operated attested peers, which is what the cross-operator claim rests on. +- **Hardware-attested live binding.** The `verifier` seam in `ca2a_runtime.attestation` has now been driven off a real SEV-SNP quote on an Azure confidential VM: `verify_offer` returned `assurance="hardware"`, a payload was sealed to a channel key a hardware-verified measurement vouches for, and both a measurement mismatch and a stale nonce were rejected. See [docs/hardware-validation.md](docs/hardware-validation.md). Two gaps remain. First, the reference server/client still run in **software mode** by default (`assurance="none"`); the hardware path is a validated capability, not the default configuration, and there is no CLI listener (`ca2a start`) since serving is via `ca2a_runtime.transport.server.serve`. Second, attestation on that run was one-directional: a follow-on cross-operator run (an Azure SEV-SNP peer calling a GCP Intel TDX peer, recorded in the same document) had the caller appraise the callee's real TDX quote before sealing, but the callee did not appraise the caller in return. Mutual simultaneous attestation is the remaining step, and both peers were driven by one operator's harness. - **Sealed peer channel (hardware property).** The channel is implemented: a payload is sealed to the peer's attested X25519 key (X25519 ECDH, HKDF-SHA256, ChaCha20-Poly1305), and only the holder of the peer's private key can open it. On a live call the handshake now gates the seal on a channel key the caller has appraised, but in software mode that appraisal is `assurance="none"`. Until the seal is bound to a hardware-verified measurement (above), do not assume a payload is confined to a specific attested measurement. Adapter-decoded `sealed_payload` bytes are opaque ciphertext only. - **Real hardware attestation.** The **SEV-SNP and Intel TDX verifiers now appraise genuine hardware evidence end to end**: a real Azure CVM SEV-SNP report (VCEK chain to the AMD ARK-Milan root, ECDSA-P384 report signature, measurement binding) and a real GCP C3 DCAP v4 TDX quote (PCK chain to the Intel SGX Root CA, QE binding, quote signature, MRTD binding), both fail-closed and both rejecting a tampered copy. Runs are recorded in [docs/hardware-validation.md](docs/hardware-validation.md). The **TPM 2.0 verifier** (AK chain to a caller-supplied vendor root, AK signature, magic/type, qualifying-data and PCR-digest binding) is implemented but synthetic-vector validated only. Quote *generation* still requires the respective hardware for all three. This validates the verifier, not a running attested peer: until the `verifier` seam in `ca2a_runtime.attestation` is driven off a live quote on a confidential VM, cA2A must not be described as attested across trust domains. diff --git a/ROADMAP.md b/ROADMAP.md index 0fe7469..c762677 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -35,7 +35,8 @@ Real hardware attestation verification (SEV-SNP VCEK chain, Intel TDX quote via - **TPM 2.0 verifier: landed.** TPMS_ATTEST parsing, AK chain to a caller-supplied vendor root, AK signature (ECDSA or RSA), magic/type checks, and qualifying-data/PCR-digest binding, all fail-closed. Quote generation requires a real TPM. See `ca2a_verify.tpm`. - **Cross-operator attestation (C6): validated in software.** A two-operator harness (SEV-SNP verifier + measurement pinning + sealed channel) shows independent keys, mutual attestation, confidential cross-operator delegation, and binary-swap detection. All six claims (C1-C6) are now validated experiments. - **Live attested peer: landed.** The `verifier` seam has been driven off a real SEV-SNP quote on an Azure confidential VM, so `verify_offer` returned `assurance="hardware"` and a payload was sealed to a hardware-vouched channel key; measurement mismatch and stale nonce both rejected. See [docs/hardware-validation.md](docs/hardware-validation.md). -- **Pending:** a two-operator run on hardware (two independently operated attested peers, ideally across TEE families), which is what the cross-operator claim needs, and the TPM certificate-chain path. TPM parsing, bindings and the AK signature are validated against a real Azure vTPM quote; SEV-SNP and TDX appraisal of real evidence is done. The transport that parses A2A messages into a `PeerRequest` has **landed** (`ca2a_runtime.transport.a2a`), running in software mode; the hardware seam is the `verifier` callable in `ca2a_runtime.attestation`. +- **Cross-operator, cross-TEE run: landed.** An Azure SEV-SNP peer appraised a GCP Intel TDX peer's real quote, sealed a delegated task to the attested key, and the TDX enclave opened it, enforced the attenuated scope, allowed `tool:search` and refused `tool:purchase` with a denial record returned across the boundary. See [docs/hardware-validation.md](docs/hardware-validation.md). +- **Pending:** mutual simultaneous attestation (that run was one-directional) and the TPM certificate-chain path. TPM parsing, bindings and the AK signature are validated against a real Azure vTPM quote; SEV-SNP and TDX appraisal of real evidence is done. The transport that parses A2A messages into a `PeerRequest` has **landed** (`ca2a_runtime.transport.a2a`), running in software mode; the hardware seam is the `verifier` callable in `ca2a_runtime.attestation`. ## v1.0: Stable profile diff --git a/docs/hardware-validation.md b/docs/hardware-validation.md index c1b479a..5c742b9 100644 --- a/docs/hardware-validation.md +++ b/docs/hardware-validation.md @@ -27,13 +27,15 @@ returned `assurance="hardware"` instead of `"none"` for the first time, and a payload was sealed to a channel key that a hardware-verified measurement vouches for. See the live-peer section below. -What is still not established is a two-operator deployment on hardware. The live -run had one attested peer; the cross-operator harness in -`examples/cross-operator-delegation` is still software-attested with synthetic -vectors. Quote generation also still requires the platform, so nothing running -off a CVM is attested. Until a cross-operator run lands, "attested across trust -domains" describes a demonstrated single-peer capability, not a demonstrated -cross-domain deployment; see [ROADMAP.md](../ROADMAP.md). +A cross-operator, cross-TEE-family run followed on the same day: an AMD SEV-SNP +peer on Azure calling an Intel TDX peer on GCP, each in a different cloud under a +different operator. See the cross-TEE section below. + +Quote generation still requires the platform, so nothing running off a CVM is +attested, and the committed `examples/cross-operator-delegation` harness remains +software-attested with synthetic vectors: the hardware runs are recorded here +rather than shipped as fixtures, because the evidence embeds per-CPU +identifiers. Verification is also bounded to a remote or rogue-admin adversary, not to one with physical access: [TEE.fail](https://tee.fail) extracts attestation keys from @@ -108,6 +110,39 @@ What it is not: a two-operator run. One peer was attested, by a caller on the same host. Two independently operated attested peers, ideally on different TEE families, is the next step and is what the cross-operator claim needs. +## Cross-operator, cross-TEE: Azure SEV-SNP calling GCP TDX + +Run 2026-07-27. Peer A is an Azure `Standard_DC2ads_v5` SEV-SNP CVM in eastus. +Peer B is a GCP `c3-standard-4` Intel TDX CVM (`tdx: Guest detected`, +configfs-TSM) in us-central1-a. Different clouds, different operators, unrelated +attestation formats: an AMD VCEK chain on one side, an Intel PCK chain on the +other. B served over HTTP with a firewall rule admitting only A's address. + +Non-paravisor TDX is guest-controlled, so B binds its channel key into the quote +directly: `REPORTDATA = sha256(channel_pub || nonce)`, with the nonce chosen by A. + +| Step | Result | +|---|---| +| A appraises B's TDX quote | 8,000 bytes, verified against the pinned Intel SGX Root CA | +| Channel key bound to the quote | Yes, `REPORTDATA` matched the recomputed binding | +| B's MRTD | `c1ee9c16e3afc506...` recorded by A | +| Stale nonce | Rejected; freshness is enforced, not assumed | +| Sealed delegated task | 107 bytes sealed by A, opened **inside B's TDX enclave** | +| Delegated capability `tool:search` | Allowed; effective scope `[task:read, tool:search]` | +| Over-scoped `tool:purchase` | Refused, HTTP 403 `SCOPE_NOT_PERMITTED` | +| Denial record | Returned across the trust boundary with `decision: deny`, the requested capability, the effective scope and the reason | + +This is the run the cross-operator claim rested on. The four primitives held +simultaneously across a real trust boundary: attenuated delegation (B enforced a +scope narrowed by a chain B did not issue), peer attestation on real hardware +(A refused to seal until B's quote appraised), payload confidentiality (the task +was readable only inside B's measured guest), and provenance (both the allow and +the deny produced linked records). + +What it still is not: both peers were driven by one operator's harness, and B's +appraisal of A's SNP report was not exercised in this run, so the attestation was +one-directional. Mutual simultaneous attestation is the remaining step. + ## TPM 2.0, Azure Trusted Launch vTPM Evidence: a `TPMS_ATTEST` quote over PCRs 0-7 (SHA-256) from a `Standard_D2s_v7` diff --git a/examples/rejection-with-proof/chain.json b/examples/rejection-with-proof/chain.json index 9884480..40e7f33 100644 --- a/examples/rejection-with-proof/chain.json +++ b/examples/rejection-with-proof/chain.json @@ -2,8 +2,8 @@ "chain": [ { "credential_id": "cred-0-orchestrator", - "issuer": "667e9aa02b1e19e4ef9a24627e10b97e099532dff71a1065a343a6037a8e3c3a", - "subject": "2108c06f9eb264876fc88c0c79c0986177a87222a776ce1b0de7c24483084ea3", + "issuer": "30831ec7f2a3abdcc223d22dc89311e3e8870cfbea931149d50e07077d518e9b", + "subject": "3f542267fb293071807ea37ac01a1be5f4b4bd9823f10156f1c998545fb2abca", "scope": [ "task:read", "task:write", @@ -12,12 +12,12 @@ ], "depth": 0, "parent_id": null, - "signature": "bf10c51cda37517c28f20f66f9f216eade1fd49c766003f2343a7bf5a8b0d423b704f936dd55851a3debb2afb982dd8ba685dd18c171c3bb6a025442063cb20f" + "signature": "493358a97965be0a78a81cc832028dd494e65a16d7622842661080b08af7b800f4c71e267c4d2745485964504209f836f93d1923d10453ba6375427b68399803" }, { "credential_id": "cred-1-researcher", - "issuer": "2108c06f9eb264876fc88c0c79c0986177a87222a776ce1b0de7c24483084ea3", - "subject": "de5c05ea1d7946c8f2c6ffde727cc6ed0da7b906ff2e1219f5916a3cc372f46b", + "issuer": "3f542267fb293071807ea37ac01a1be5f4b4bd9823f10156f1c998545fb2abca", + "subject": "f327db8ed54be433ee43fec9f4c144d3f0d84555eb4654989c4458087e23b99d", "scope": [ "task:read", "tool:purchase", @@ -25,18 +25,18 @@ ], "depth": 1, "parent_id": "cred-0-orchestrator", - "signature": "1702778d81160bc46f1044b1b628e28b010d9d45c0767c1d1ce50b9f4ebfb3fe954455805ed5e9fdf7d72cc8ec18bb83cd23966ba88d95de21fa94b8b4026f06" + "signature": "35ba31dccb83829c4c8f9f4215aeb19b49aafd0033aef1d5ebb876a8a3d0110797f6f5a013ab55171a969c4940a1288b1591d6a090edf5df6b0a8b4db8b08300" }, { "credential_id": "cred-2-retriever", - "issuer": "de5c05ea1d7946c8f2c6ffde727cc6ed0da7b906ff2e1219f5916a3cc372f46b", - "subject": "6eb31885c7ac3d745186ed36a2e77b754325a1ceba6f81b76994a36866d68f52", + "issuer": "f327db8ed54be433ee43fec9f4c144d3f0d84555eb4654989c4458087e23b99d", + "subject": "555d4389edcb44043216dbce5079bd61edaa5873f0674b8c57b4c7b79f441cd5", "scope": [ "tool:search" ], "depth": 2, "parent_id": "cred-1-researcher", - "signature": "08999679fff8d3d0940fb94c085ed549511aa026bf9b702da91e8244387db3d86c50004e547950009cf5ce7bf7625bf67893583011097622bed1782de844cf0d" + "signature": "f134bd6fd0f55573c285dc42debaf2944360d060ded3596d2ba85f6a0a223cbd7d99a7a111cf358793f01ca2454f3d7e301021b3789f84b6e90553e726d34904" } ] } diff --git a/examples/rejection-with-proof/dag.json b/examples/rejection-with-proof/dag.json index 1d3397c..0fc7534 100644 --- a/examples/rejection-with-proof/dag.json +++ b/examples/rejection-with-proof/dag.json @@ -3,7 +3,7 @@ { "record_id": "rec-0-orchestrator", "credential_id": "cred-0-orchestrator", - "subject": "2108c06f9eb264876fc88c0c79c0986177a87222a776ce1b0de7c24483084ea3", + "subject": "3f542267fb293071807ea37ac01a1be5f4b4bd9823f10156f1c998545fb2abca", "scope": [ "task:read", "task:write", @@ -15,31 +15,31 @@ { "record_id": "rec-1-researcher", "credential_id": "cred-1-researcher", - "subject": "de5c05ea1d7946c8f2c6ffde727cc6ed0da7b906ff2e1219f5916a3cc372f46b", + "subject": "f327db8ed54be433ee43fec9f4c144d3f0d84555eb4654989c4458087e23b99d", "scope": [ "task:read", "tool:purchase", "tool:search" ], - "parent_record_hash": "2bb2e3f8b86b5874219bf2a5818bf576863daeb947b2d3461ea6eb6abd8622e2" + "parent_record_hash": "d2b7837dc31713ee5ce1adac414e787f88a42e83663ac4c333318127ff4606b5" }, { "record_id": "rec-2-retriever", "credential_id": "cred-2-retriever", - "subject": "6eb31885c7ac3d745186ed36a2e77b754325a1ceba6f81b76994a36866d68f52", + "subject": "555d4389edcb44043216dbce5079bd61edaa5873f0674b8c57b4c7b79f441cd5", "scope": [ "tool:search" ], - "parent_record_hash": "35a4fad0f11b40ed72e47239c3d844ddd262bc1d4fd439913cc5bd0d916b57ec" + "parent_record_hash": "3dac51b23f24cde0396bc9bc3ae0f06aaf7bf13c9e40800f24d499869beee8e4" }, { "record_id": "rec-denied-purchase", "credential_id": "cred-2-retriever", - "subject": "6eb31885c7ac3d745186ed36a2e77b754325a1ceba6f81b76994a36866d68f52", + "subject": "555d4389edcb44043216dbce5079bd61edaa5873f0674b8c57b4c7b79f441cd5", "scope": [ "tool:search" ], - "parent_record_hash": "56d85126ea94f17f88fe4d0b1df35c300b2f9f79a2dfbcae814be097616da7ab", + "parent_record_hash": "6b10750d625c1cff83b5868948eeb7a6ebdbbe427ce3ba2bc802c258c8cdc82d", "decision": "deny", "requested_capability": "tool:purchase", "effective_scope": [