Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Peer-call enforcement decision core (Tier 2): `ca2a_runtime.policy.LocalPolicy` and `ca2a_runtime.peer` (`effective_scope`, `enforce_peer_call`). Effective permission is the delegated leaf scope intersected with the callee's local policy; a granted call emits a linked provenance record. New error `SCOPE_NOT_PERMITTED`. Claim C3 (scope-policy intersection) is now a validated experiment. Cedar-engine binding of the local policy and live A2A transport wiring remain open.
- Sealed peer channel (Tier 2): `ca2a_runtime.channel` (`SealedChannel`, `generate_channel_keypair`, `open_sealed`). HPKE-style X25519 -> HKDF-SHA256 -> ChaCha20-Poly1305 sealing a payload to the peer's attested key; only the peer's private key opens it, and a wrong key or tampered ciphertext fails closed. Claim C4 (sealed-payload confidentiality) is now a validated experiment at the cryptographic layer. The enclave-binding of the private key (a hardware property) and live-path wiring remain open.
- Cross-operator attestation (Claim C6) validated in software: a two-operator harness composing the SEV-SNP verifier, measurement pinning, and the sealed channel demonstrates independent keys, mutual attestation, confidential cross-operator delegation, and binary-swap detection. Synthetic report vectors (a genuine report needs SEV-SNP hardware); real hardware end to end remains open. **All six claims (C1-C6) are now validated experiments.**
- Intel TDX attestation backend: `ca2a_runtime.tee.tdx` (DCAP Quote v4 parsing, `TdxProvider`) and `ca2a_verify.tdx.verify_tdx_quote` (PCK chain to a trusted Intel root, QE report signature, attestation-key binding, quote signature, and MRTD/report-data binding), all fail-closed. Chain path validated against the genuine Intel SGX Root CA; multi-level signature path validated with a synthetic self-consistent quote. Quote generation requires a real TDX guest.
- Transport-agnostic inbound peer request handler: `ca2a_runtime.peer.handle_peer_request` with `PeerRequest` / `PeerResult`. Composes the full pipeline (verify chain, intersect scope and enforce, open a sealed payload with the enclave key, emit a linked provenance record) fail-closed. A transport parses its wire format into a `PeerRequest`; cA2A does not define the transport (profile, not protocol).
- RFC 8785 (JSON Canonicalization Scheme) canonicalization: `ca2a_runtime.canonical.canonicalize`. Credential and provenance bodies are now signed over the JCS encoding (UTF-16 key ordering, JCS string escaping, literal non-ASCII, shortest-decimal integers), so cA2A signatures are cross-verifiable with agent-manifest. ASCII credentials are byte-identical to the previous encoding, so existing signatures still verify.
- Repository scaffold: governance, CI/CD, docs framework, and packaging at parity with the agentrust-io house standard
Expand All @@ -26,6 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Live A2A transport wiring for the peer-enforcement decision core, including binding the seal to a verified attestation report (Tier 2)
- Cedar policy engine binding for the local policy (Tier 2)
- Intel TDX and TPM attestation backends (Tier 3); end-to-end SEV-SNP validation against real hardware vectors
- TPM attestation backend (Tier 3); end-to-end SEV-SNP and TDX validation against real hardware quotes

[Unreleased]: https://github.com/agentrust-io/ca2a/commits/main
2 changes: 1 addition & 1 deletion LIMITATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cA2A is a pre-release profile in active design. This document states plainly wha

- **Runtime peer-delegation enforcement.** The runtime does not yet accept a delegation credential on a live inbound peer call, verify it in the request path, and intersect the delegated scope with a local Cedar policy. This is Tier 2 on the roadmap.
- **Sealed peer channel.** 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. The remaining gap is the hardware property that the private key never leaves the peer's enclave (established by attestation), and wiring the seal to a verified report on a live inbound call. Until that end-to-end binding lands on hardware, do not assume a payload is confined to a specific attested measurement.
- **Real hardware attestation.** The **SEV-SNP verifier is implemented**: report parsing, VCEK certificate chain verification, ECDSA-P384 report-signature verification, and measurement/report-data binding, all fail-closed. The chain path is validated against the genuine AMD Milan root chain; the report-signature path is validated with synthetic vectors, since a real report plus VCEK pair needs SEV-SNP hardware. Report generation (`SevSnpProvider.attest`) still requires a real SEV-SNP guest. **Intel TDX and TPM backends are not yet implemented (Tier 3).** Until a backend verifies a real quote end to end against a golden measurement on hardware, cA2A must not be described as fully attested across trust domains.
- **Real hardware attestation.** The **SEV-SNP verifier is implemented**: report parsing, VCEK certificate chain verification, ECDSA-P384 report-signature verification, and measurement/report-data binding, all fail-closed. The chain path is validated against the genuine AMD Milan root chain; the report-signature path is validated with synthetic vectors, since a real report plus VCEK pair needs SEV-SNP hardware. Report generation (`SevSnpProvider.attest`) still requires a real SEV-SNP guest. The **Intel TDX verifier is also implemented** (DCAP Quote v4: PCK chain to the genuine Intel SGX Root CA, QE report, attestation-key binding, quote signature, MRTD binding; synthetic-quote validated, quote generation needs a TDX guest). **The TPM backend is not yet implemented (Tier 3).** Until a backend verifies a real quote end to end against a golden measurement on hardware, cA2A must not be described as fully attested across trust domains.

## Out of scope

Expand Down
3 changes: 2 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ Already implemented and tested elsewhere; cA2A depends on it rather than reimple
Real hardware attestation verification (SEV-SNP VCEK chain, Intel TDX quote via QVL/PCS, TPM AK cert + checkquote). This is a dependency for any cross-operator trust claim, single-agent or multi-agent, and is shared with cmcp. At least one real hardware backend must land before cA2A is marketed as attested across trust domains, so the demo matches the claim.

- **SEV-SNP verifier: landed.** Report parsing, VCEK chain verification (validated against the real AMD Milan root), ECDSA-P384 report-signature verification, and measurement/report-data binding, all fail-closed. Report generation still requires a real SEV-SNP guest. See `ca2a_verify.sev_snp` and [docs/spec/attestation.md](docs/spec/attestation.md).
- **TDX verifier: landed.** DCAP Quote v4 parsing, PCK chain to the genuine Intel SGX Root CA, QE report signature, attestation-key binding, quote signature, and MRTD binding, all fail-closed. Quote generation requires a real TDX guest. See `ca2a_verify.tdx`.
- **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:** Intel TDX and TPM backends; end-to-end validation of the report-signature path against real hardware vectors on a confidential VM; and the live A2A transport binding that drives the whole pipeline off a real inbound call.
- **Pending:** the TPM backend; end-to-end validation of the SEV-SNP and TDX signature paths against real hardware quotes on a confidential VM; and a transport that parses real A2A wire messages into a `PeerRequest`.

## v1.0: Stable profile

Expand Down
10 changes: 8 additions & 2 deletions docs/spec/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ An `AttestationReport` carries `platform`, `measurement`, the bound `public_key`
|---|---|---|
| `software-only` | none | Available; for development and CI. Reports `platform: software-only`, never a hardware platform string. |
| `sev-snp` | AMD SEV-SNP | Verifier implemented (see below). Report generation requires a real SEV-SNP guest. |
| `tdx` | Intel TDX | Verifier implemented (see below). Quote generation requires a real TDX guest. |
| `tpm` | TPM 2.0 / vTPM | Tier 3, not yet implemented |
| `tdx` | Intel TDX | Tier 3, not yet implemented |
| `opaque` | OPAQUE Confidential Runtime | Tier 3, explicit opt-in, not auto-selected |

## SEV-SNP verification
Expand All @@ -33,9 +33,15 @@ An `AttestationReport` carries `platform`, `measurement`, the bound `public_key`

**Cross-operator use.** Two operators in separate trust domains each bind their sealed-channel public key into a report and verify the counterparty's report against a pinned golden measurement. This composes into mutual attestation, confidential cross-operator delegation (seal to the attested key), and binary-swap detection (a changed measurement is rejected), validated in software as claim C6. See the [call graph](call-graph.md) and the `claim6-cross-operator-attestation` experiment.

## TDX verification

`ca2a_verify.tdx.verify_tdx_quote` appraises an Intel TDX quote (DCAP, ECDSA-256) offline in four fail-closed steps: the PCK certificate chain is verified up to a trusted Intel root; the Quoting Enclave report is verified against the PCK; the attestation key is confirmed to be the one the QE report data commits to (SHA-256 of the key and the QE auth data); and the attestation key's signature over the quote body is verified, along with the launch measurement (MRTD) and report data.

**What is validated.** The chain-verification path accepts the genuine self-signed Intel SGX Root CA fetched from Intel (`tests/fixtures/tdx/`) and rejects an untrusted root. The multi-level signature path (PCK to QE report to attestation key to quote) is exercised end to end with a synthetic self-consistent quote, because a genuine quote requires a TDX guest. Byte offsets follow the Intel DCAP Quote v4 layout; end-to-end validation against a real hardware quote requires a TDX guest and remains open.

## Fail closed

Providers without a backend `detect()` to False, so they are never selected automatically, and verification fails closed when evidence is absent or invalid. This is deliberate: cA2A must not be described as attested across trust domains until a real hardware backend verifies a quote against a golden measurement. TDX and TPM backends remain Tier 3. See [LIMITATIONS.md](../../LIMITATIONS.md).
Providers without a backend `detect()` to False, so they are never selected automatically, and verification fails closed when evidence is absent or invalid. This is deliberate: cA2A must not be described as attested across trust domains until a real hardware backend verifies a quote against a golden measurement on hardware. The TPM backend remains Tier 3. See [LIMITATIONS.md](../../LIMITATIONS.md).

## Why this is the critical path

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,5 @@ warn_return_any = false
files = ["src/ca2a_runtime", "src/ca2a_verify"]

[[tool.mypy.overrides]]
module = ["ca2a_runtime.tee.sev_snp", "ca2a_runtime.tee.tdx", "ca2a_runtime.channel.sealed"]
module = ["ca2a_runtime.tee.sev_snp", "ca2a_runtime.tee.tdx", "ca2a_verify.tdx", "ca2a_runtime.channel.sealed"]
warn_unused_ignores = false
30 changes: 19 additions & 11 deletions src/ca2a_runtime/tee/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
"""TEE provider abstraction for peer attestation.

cA2A reuses the pluggable provider model from cmcp: a provider produces an
attestation report that binds a public key to a hardware measurement. Real
hardware backends are Tier 3 (see ROADMAP.md) and fail closed until implemented.
"""

from ca2a_runtime.tee.base import AttestationReport, BaseProvider
from ca2a_runtime.tee.sev_snp import SevSnpProvider, SevSnpReport

__all__ = ["AttestationReport", "BaseProvider", "SevSnpProvider", "SevSnpReport"]
"""TEE provider abstraction for peer attestation.

cA2A reuses the pluggable provider model from cmcp: a provider produces an
attestation report that binds a public key to a hardware measurement. Real
hardware backends are Tier 3 (see ROADMAP.md) and fail closed until implemented.
"""

from ca2a_runtime.tee.base import AttestationReport, BaseProvider
from ca2a_runtime.tee.sev_snp import SevSnpProvider, SevSnpReport
from ca2a_runtime.tee.tdx import TdxProvider, TdxQuote

__all__ = [
"AttestationReport",
"BaseProvider",
"SevSnpProvider",
"SevSnpReport",
"TdxProvider",
"TdxQuote",
]
133 changes: 133 additions & 0 deletions src/ca2a_runtime/tee/tdx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Intel TDX quote (DCAP, ECDSA-256) parsing and the TDX provider.

Parses a TDX v4 quote: the header, the TD report body (from which the launch
measurement MRTD and the report data are read), and the ECDSA signature section
(the quote signature, the attestation key, the Quoting Enclave report and its
PCK signature, and the PCK certificate chain). Verification lives in
:mod:`ca2a_verify.tdx`.

Producing a quote requires a real TDX guest, so :meth:`TdxProvider.attest` fails
closed off hardware. Byte offsets follow the Intel DCAP Quote v4 layout; the
verifier is exercised against synthetic self-consistent vectors plus the real
Intel SGX Root CA in the test suite. End-to-end validation against a real
hardware quote requires a TDX guest and remains open.
"""

from __future__ import annotations

import struct
from dataclasses import dataclass

from cryptography import x509

from ca2a_runtime.errors import AttestationFailed, AttestationUnsupported
from ca2a_runtime.tee.base import AttestationReport, BaseProvider

HEADER_LEN = 48
TD_REPORT_LEN = 584
SIGNED_LEN = HEADER_LEN + TD_REPORT_LEN # quote signature covers header + TD report
MRTD_OFFSET = HEADER_LEN + 136
MRTD_LEN = 48
REPORT_DATA_OFFSET = HEADER_LEN + 520
REPORT_DATA_LEN = 64

# Signature section (relative to SIGNED_LEN + 4-byte sig_data_len).
QUOTE_SIG_LEN = 64
ATT_KEY_LEN = 64
QE_REPORT_LEN = 384
QE_REPORT_DATA_OFFSET = 320 # within the QE SGX report

TEE_TYPE_TDX = 0x81
CERT_TYPE_PCK_CHAIN = 5
TDX_GUEST_DEVICE = "/dev/tdx_guest"


@dataclass(frozen=True)
class TdxQuote:
"""The parsed subset of a TDX quote cA2A appraises."""

version: int
tee_type: int
measurement: bytes # MRTD
report_data: bytes
signed_body: bytes # header + TD report body
quote_signature: bytes # 64 bytes, r||s big-endian
attestation_key: bytes # 64 bytes, raw P-256 x||y
qe_report: bytes # 384-byte SGX report
qe_report_signature: bytes # 64 bytes, r||s big-endian
qe_auth_data: bytes
pck_chain: list[x509.Certificate] # leaf (PCK) first, root last

@classmethod
def parse(cls, blob: bytes) -> TdxQuote:
if len(blob) < SIGNED_LEN + 4:
raise AttestationFailed(
"TDX quote too short",
detail=f"got {len(blob)} bytes, need at least {SIGNED_LEN + 4}",
)
version, _att_key_type, tee_type = struct.unpack_from("<HHI", blob, 0)
measurement = blob[MRTD_OFFSET : MRTD_OFFSET + MRTD_LEN]
report_data = blob[REPORT_DATA_OFFSET : REPORT_DATA_OFFSET + REPORT_DATA_LEN]

(sig_len,) = struct.unpack_from("<I", blob, SIGNED_LEN)
pos = SIGNED_LEN + 4
end = pos + sig_len
if end > len(blob):
raise AttestationFailed("TDX quote signature section is truncated")

quote_sig = blob[pos : pos + QUOTE_SIG_LEN]
pos += QUOTE_SIG_LEN
att_key = blob[pos : pos + ATT_KEY_LEN]
pos += ATT_KEY_LEN
qe_report = blob[pos : pos + QE_REPORT_LEN]
pos += QE_REPORT_LEN
qe_report_sig = blob[pos : pos + QUOTE_SIG_LEN]
pos += QUOTE_SIG_LEN
(qe_auth_len,) = struct.unpack_from("<H", blob, pos)
pos += 2
qe_auth = blob[pos : pos + qe_auth_len]
pos += qe_auth_len
cert_type, cert_len = struct.unpack_from("<HI", blob, pos)
pos += 6
cert_bytes = blob[pos : pos + cert_len]
if cert_type != CERT_TYPE_PCK_CHAIN:
raise AttestationFailed(
"unsupported QE certification data type",
detail=f"type={cert_type}, expected {CERT_TYPE_PCK_CHAIN} (PCK chain)",
)
try:
chain = x509.load_pem_x509_certificates(cert_bytes)
except ValueError as exc:
raise AttestationFailed("could not parse PCK certificate chain", detail=str(exc)) from exc

return cls(
version=version,
tee_type=tee_type,
measurement=measurement,
report_data=report_data,
signed_body=blob[:SIGNED_LEN],
quote_signature=quote_sig,
attestation_key=att_key,
qe_report=qe_report,
qe_report_signature=qe_report_sig,
qe_auth_data=qe_auth,
pck_chain=list(chain),
)


class TdxProvider(BaseProvider):
"""Intel TDX provider. Quote generation requires a real TDX guest."""

platform = "tdx"

@classmethod
def detect(cls) -> bool:
import os

return os.path.exists(TDX_GUEST_DEVICE)

def attest(self, public_key: str, nonce: str) -> AttestationReport:
raise AttestationUnsupported(
"TDX quote generation requires a real TDX guest",
detail=f"{TDX_GUEST_DEVICE} not present; run on an Intel TDX confidential VM",
)
Loading
Loading