diff --git a/docs/spec/attestation.md b/docs/spec/attestation.md index 069d29f..1c1d392 100644 --- a/docs/spec/attestation.md +++ b/docs/spec/attestation.md @@ -29,7 +29,7 @@ An `AttestationReport` carries `platform`, `measurement`, the bound `public_key` 2. Report signature: the ECDSA-P384 signature (stored as little-endian `r` and `s`) is verified against the VCEK public key over the report body (`report[:0x2A0]`). 3. Binding: the launch `measurement` and the `report_data` (which carries the runtime key and nonce) are checked against expected values. -What is validated. The chain-verification path is exercised against the genuine AMD Milan ARK/ASK root chain fetched from AMD KDS (`tests/fixtures/sev_snp/`). The report-signature path is exercised end to end with a synthetic VCEK and report, because a genuine report plus VCEK pair requires real SEV-SNP hardware. Producing a report (`SevSnpProvider.attest`) fails closed off hardware (`AttestationUnsupported`). +What is validated. The chain-verification path is exercised against the genuine AMD Milan ARK/ASK root chain fetched from AMD KDS (`tests/fixtures/sev_snp/`), and the full path (VCEK chain to the ARK, ECDSA-P384 report signature, measurement binding, and rejection of a flipped bit) is exercised against a genuine SEV-SNP report captured from an Azure confidential VM, alongside the synthetic vectors. Producing a report (`SevSnpProvider.attest`) still fails closed off hardware (`AttestationUnsupported`). See [hardware validation](../hardware-validation.md). 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. @@ -37,13 +37,13 @@ Cross-operator use. Two operators in separate trust domains each bind their seal `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. +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, and the multi-level signature path (PCK to QE report to attestation key to quote) is exercised both with a synthetic self-consistent quote and against a genuine DCAP v4 quote captured from a GCP C3 confidential VM. Byte offsets follow the Intel DCAP Quote v4 layout, including the nested type-6 `QE_REPORT_CERTIFICATION_DATA` header that wraps the QE report and the type-5 PCK chain; reading the QE report directly after the attestation key is six bytes early and rejects every genuine quote. Quote *generation* still requires a TDX guest. See [hardware validation](../hardware-validation.md). ## TPM verification `ca2a_verify.tpm.verify_tpm_quote` appraises a TPM 2.0 quote (`TPMS_ATTEST`) offline: the AK certificate chain is verified to a trusted root, the AK signature over the attest blob is verified (ECDSA-SHA256 or RSA PKCS#1 v1.5), the structure is confirmed to be a TPM-generated quote (magic and type), and the qualifying data (the verifier's nonce) and the PCR digest (the platform measurement) are checked against expected values. -What is validated. Unlike SEV-SNP and TDX, TPM attestation keys chain to per-vendor EK roots, so there is no single published root to validate against; the caller supplies the vendor roots it trusts, and the verifier is exercised against synthetic self-consistent vectors. Producing a quote (`TpmProvider.attest`) fails closed off a real TPM. +What is validated. Unlike SEV-SNP and TDX, TPM attestation keys chain to per-vendor EK roots, so there is no single published root to validate against; the caller supplies the vendor roots it trusts, and the verifier is exercised against synthetic self-consistent vectors only. No real AK-signed quote has been run through it, so unlike the other two backends the TPM path is not hardware-validated. See [hardware validation](../hardware-validation.md). ## Fail closed diff --git a/docs/spec/call-graph.md b/docs/spec/call-graph.md index 73b9e8a..287b822 100644 --- a/docs/spec/call-graph.md +++ b/docs/spec/call-graph.md @@ -35,8 +35,8 @@ If any step raises, the call is denied. Absence of evidence is denial, not a war | Step | What it enforces | Function / type | Status | |---|---|---|---| | 1. Chain verification | Signature, continuity, attenuation, depth bound, anti-replay | `verify_chain` | Implemented | -| 2. Peer attestation | Peer measurement matches an expected value | `ca2a_verify.sev_snp.verify_sev_snp_report` | SEV-SNP verifier implemented; not yet wired into the call path; report needs hardware | -| 3. Scope intersection | Delegated scope intersected with local policy | `ca2a_runtime.peer.effective_scope`, `enforce_peer_call` | Implemented (decision core); Cedar engine binding pending (#10) | +| 2. Peer attestation | Peer measurement matches an expected value | `ca2a_verify.sev_snp`, `.tdx`, `.tpm`; `ca2a_runtime.attestation` | Verifiers implemented and wired into the handshake. SEV-SNP and TDX appraise real hardware evidence; on a live call off hardware the appraisal is `assurance="none"` | +| 3. Scope intersection | Delegated scope intersected with local policy | `ca2a_runtime.peer.effective_scope`, `enforce_peer_call` | Implemented, with `LocalPolicy` or the Cedar engine (`CedarPolicy`) | | 4. Payload sealing | Payload sealed to the peer's attested key | `SealedChannel.seal`, `open_sealed` | Implemented (crypto); binding to a verified report on the live path pending | | 5. Provenance record | A `DelegationRecord` emitted and linked to its parent | `enforce_peer_call`, `record_for`, `verify_dag` | Implemented (emitted by the decision core) | | Inbound pipeline handler | Verify, enforce, open sealed payload, emit record off a parsed request | `handle_peer_request`, `PeerRequest` | Implemented (transport-agnostic) | @@ -57,9 +57,11 @@ delegated_scope = leaf.scope # frozenset[str], the authority to enforce below `max_depth` comes from `Ca2aConfig.max_delegation_depth` (default 8). This step is deterministic and offline: it contacts no operator and depends only on the signed bytes. It is the only step that gates an inbound call in this release. -## Step 2: verify peer attestation (pending, Tier 3) +## Step 2: verify peer attestation (implemented; software mode on a live call) -The runtime confirms the peer is running attested, measured code before it is trusted with the task, by checking the peer's report measurement against an expected value under a fresh nonce. A SEV-SNP verifier exists (`ca2a_verify.sev_snp.verify_sev_snp_report`: VCEK chain, report-signature, measurement binding), but it is not yet wired into this call path, producing a report requires real SEV-SNP hardware, and TDX/TPM backends are not implemented. Verification fails closed when evidence is absent. Do not treat this step as active on the live path yet. See [attestation.md](attestation.md). +The runtime confirms the peer is running attested, measured code before it is trusted with the task, by checking the peer's report measurement against an expected value under a fresh nonce. Verifiers exist and are fail-closed for all three platforms: `ca2a_verify.sev_snp` (VCEK chain, ECDSA-P384 report signature, measurement binding), `ca2a_verify.tdx` (DCAP v4 including the nested type-6 QE certification data, PCK chain to the Intel SGX Root CA, QE binding, MRTD) and `ca2a_verify.tpm` (AK chain, AK signature, qualifying-data and PCR-digest binding). The SEV-SNP and TDX verifiers appraise genuine hardware evidence; TPM is synthetic-vector validated only. See [hardware validation](../hardware-validation.md). + +The handshake in `ca2a_runtime.attestation` gates the seal on an appraised channel key, so this step is wired into the call path. What is still missing is hardware on the live path: producing a report requires the platform, so off hardware the appraisal records `assurance="none"` and the `verifier` seam is not driven off a real quote. Do not read a completed software-mode call as an attested one. See [attestation.md](attestation.md). ## Step 3: intersect scope with local policy (implemented as a decision core) diff --git a/docs/spec/component-model.md b/docs/spec/component-model.md index 0494dd4..55cf380 100644 --- a/docs/spec/component-model.md +++ b/docs/spec/component-model.md @@ -18,11 +18,23 @@ The cA2A runtime is a set of small, composable modules under `src/`. Each maps t ### channel -`ca2a_runtime.channel.sealed` defines `SealedChannel`, the measurement-bound peer channel. Instantiation is allowed so the runtime can be wired against the interface, but `seal()` and `open()` fail closed with `SEALED_CHANNEL_ERROR` today. This is Tier 2 and not yet implemented; do not send confidential payloads across a trust boundary and assume they are protected. See [sealed channel](sealed-channel.md) and [LIMITATIONS.md](../../LIMITATIONS.md). +`ca2a_runtime.channel` defines `SealedChannel`, `generate_channel_keypair`, and `open_sealed`: HPKE-style sealing (X25519 ECDH, HKDF-SHA256, ChaCha20-Poly1305) of a payload to the peer's channel key. Only the holder of that key opens it; a wrong key or tampered ciphertext fails closed. The cryptography is implemented. What is *not* yet true is the hardware property: until the key is bound to a hardware-verified measurement, do not assume a payload is confined to a specific attested enclave. See [sealed channel](sealed-channel.md) and [LIMITATIONS.md](../../LIMITATIONS.md). + +### peer, policy and cedar + +`ca2a_runtime.peer` is the inbound decision core. `effective_scope(chain, policy)` verifies the chain and returns the delegated leaf scope intersected with local policy; `enforce_peer_call(...)` enforces a requested capability against it and emits a linked provenance record, raising `SCOPE_NOT_PERMITTED` (carrying a denial record) when the capability falls outside. `handle_peer_request(request, ...)` composes the full transport-agnostic pipeline: verify, enforce, open any sealed payload with the enclave key, emit the record. `ca2a_runtime.policy` defines the `Policy` protocol and `LocalPolicy` (allow set); `ca2a_runtime.cedar.CedarPolicy` is the Cedar-engine implementation, interchangeable with it. Implemented. See [Cedar policy](cedar-policy.md) and [call graph](call-graph.md). ### tee -`ca2a_runtime.tee.base` defines the provider interface and evidence model. `AttestationReport` is a frozen dataclass binding a `public_key` to a `measurement` under a `nonce` on a named `platform`. `BaseProvider` is an ABC with `detect()` and `attest(public_key, nonce)`. Real hardware providers (TPM, SEV-SNP, TDX, OPAQUE) are Tier 3 and not implemented; their `detect()` returns False so they are never auto-selected, and verification fails closed. See [attestation](attestation.md). +`ca2a_runtime.tee.base` defines the provider interface and evidence model. `AttestationReport` is a frozen dataclass binding a `public_key` to a `measurement` under a `nonce` on a named `platform`. `BaseProvider` is an ABC with `detect()` and `attest(public_key, nonce)`. Verifiers for SEV-SNP, Intel TDX and TPM 2.0 are implemented in `ca2a_verify` and appraise real evidence, all fail-closed; `ca2a_runtime.tee.software.SoftwareProvider` supplies a no-hardware provider that is never auto-selected. Quote *generation* requires the corresponding hardware, so each hardware provider's `detect()` returns False off that platform. The OPAQUE provider is not implemented. See [attestation](attestation.md) and [hardware validation](../hardware-validation.md). + +### transport, node and attestation handshake + +`ca2a_runtime.transport.a2a_adapter` parses cA2A extension metadata on an A2A message into a `PeerRequest` and attaches it in the reverse direction, failing closed on malformed metadata (`TRANSPORT_ERROR`) and returning `None` when no cA2A keys are present. `ca2a_runtime.transport.server`/`client` are a standard-library reference HTTP transport, and `ca2a_runtime.node.PeerNode` composes provider, policy, adapter and handler. `ca2a_runtime.attestation` (offer, verify, seal) gates sealing on a channel key the caller has appraised under a fresh nonce. All implemented and exercised end to end in software mode; the reference transport is a convenience, not part of the profile. See [transport](transport.md). + +### trace_binding and canonical + +`ca2a_runtime.trace_binding` lifts each hop into a signed TRACE Trust Record carrying the A2A `delegation` block (`build_trace_record`, `sign_trace_record`, `emit_dag`, `trace_record_hash`, `HopContext`), built on `agentrust-trace`. `ca2a_verify.verify_trace_dag` verifies a signed DAG offline and `cross_check_trace_dag` ties it to the chain. `ca2a_runtime.canonical` is the RFC 8785 JCS canonicalizer the signatures are computed over. Implemented. See [TRACE A2A profile](trace-a2a-profile.md). ### config @@ -30,11 +42,11 @@ The cA2A runtime is a set of small, composable modules under `src/`. Each maps t ### errors -`ca2a_runtime.errors` is the central registry. Every error is a `CA2AError` subclass carrying a stable `code` and an `http_status`: `CONFIG_ERROR`, `INVALID_CREDENTIAL`, `SCOPE_ESCALATION`, `BROKEN_DELEGATION_LINK`, `DELEGATION_DEPTH_EXCEEDED`, `CREDENTIAL_REPLAY`, `ATTESTATION_UNSUPPORTED`, `ATTESTATION_FAILED`, `SEALED_CHANNEL_ERROR`, `PROVENANCE_LINK_BROKEN`. See [error codes](error-codes.md). +`ca2a_runtime.errors` is the central registry. Every error is a `CA2AError` subclass carrying a stable `code` and an `http_status`: `CONFIG_ERROR`, `INVALID_CREDENTIAL`, `SCOPE_ESCALATION`, `BROKEN_DELEGATION_LINK`, `DELEGATION_DEPTH_EXCEEDED`, `CREDENTIAL_REPLAY`, `ATTESTATION_UNSUPPORTED`, `ATTESTATION_FAILED`, `SEALED_CHANNEL_ERROR`, `PROVENANCE_LINK_BROKEN`, `SCOPE_NOT_PERMITTED`, `TRACE_RECORD_INVALID`, `TRANSPORT_ERROR`. See [error codes](error-codes.md). ### cli -`ca2a_runtime.cli` exposes the `ca2a` command with two subcommands: `validate-config --config` (loads and validates a `Ca2aConfig`) and `verify-chain --chain [--max-depth]` (calls `verify_chain_file` and prints a JSON result). Both are implemented and operate offline. +`ca2a_runtime.cli` exposes the `ca2a` command with three subcommands: `validate-config --config` (loads and validates a `Ca2aConfig`), `verify-chain --chain [--max-depth]` (calls `verify_chain_file` and prints a JSON result), and `verify-dag --dag [--chain] [--max-depth]` (verifies a provenance DAG, optionally cross-checking it against the chain, and reports `outcome: denied` when the leaf documents a refusal). All implemented and offline. ## Component map @@ -43,20 +55,24 @@ The cA2A runtime is a set of small, composable modules under `src/`. Each maps t | delegation | `ca2a_runtime.delegation.credential` | `DelegationCredential`, `new_keypair`, `verify_chain` | Implemented | | provenance | `ca2a_runtime.provenance` | `DelegationRecord`, `record_for`, `verify_dag`, `cross_check_chain` | Implemented | | verify | `ca2a_verify.verify` | `verify_delegation_chain`, `verify_chain_file`, `ChainResult` | Implemented | -| config | `ca2a_runtime.config` | `Ca2aConfig` | Surface implemented; peer path pending (Tier 2) | +| config | `ca2a_runtime.config` | `Ca2aConfig` | Implemented | | errors | `ca2a_runtime.errors` | `CA2AError` and subclasses | Implemented | -| cli | `ca2a_runtime.cli` | `ca2a validate-config`, `ca2a verify-chain` | Implemented | -| channel | `ca2a_runtime.channel.sealed` | `SealedChannel` | Placeholder, fails closed (Tier 2) | -| tee | `ca2a_runtime.tee.base` | `BaseProvider`, `AttestationReport` | Interface only; hardware backends pending (Tier 3) | +| cli | `ca2a_runtime.cli` | `validate-config`, `verify-chain`, `verify-dag` | Implemented | +| channel | `ca2a_runtime.channel` | `SealedChannel`, `generate_channel_keypair`, `open_sealed` | Crypto implemented; binding to a hardware-verified measurement pending | +| peer | `ca2a_runtime.peer` | `effective_scope`, `enforce_peer_call`, `handle_peer_request` | Implemented | +| policy | `ca2a_runtime.policy`, `ca2a_runtime.cedar` | `Policy`, `LocalPolicy`, `CedarPolicy` | Implemented | +| transport | `ca2a_runtime.transport`, `ca2a_runtime.node` | `parse_peer_request`, `serve`, `PeerNode` | Implemented (reference transport, software mode) | +| attestation | `ca2a_runtime.attestation` | offer, verify, seal | Implemented; appraisal is `assurance="none"` off hardware | +| tee | `ca2a_runtime.tee` | `BaseProvider`, `AttestationReport`, `SoftwareProvider` | Interface and software provider implemented; quote generation needs the platform | +| verifiers | `ca2a_verify.sev_snp`, `.tdx`, `.tpm` | `verify_sev_snp_report`, `verify_tdx_quote`, `verify_tpm_quote` | Implemented and fail-closed. SEV-SNP and TDX appraise real hardware evidence; TPM is synthetic-vector validated only | +| trace_binding | `ca2a_runtime.trace_binding`, `ca2a_verify.dag` | `emit_dag`, `verify_trace_dag`, `cross_check_trace_dag` | Implemented | ## How they compose on an inbound peer call -The intended peer path threads these components together. Steps 2 through 5 below are the target composition; the implemented parts today are the chain and provenance verification an offline verifier can run over signed evidence. - -1. A hands B a child credential with `scope ⊆` A's scope. This is the [delegation](delegation-chain.md) model, implemented. -2. Before B accepts, the runtime verifies the chain with `verify_chain` and intersects the delegated scope with a local Cedar policy under B's `enforcement_mode`. Chain verification is implemented; runtime enforcement and Cedar scope intersection are Tier 2 and not yet built. See [Cedar policy](cedar-policy.md). -3. B's `tee` provider produces an `AttestationReport`; the runtime checks the measurement. The interface exists, but no hardware backend verifies a quote yet (Tier 3), so this fails closed. See [attestation](attestation.md). -4. The task payload is sealed to B's measurement through `SealedChannel`. Tier 2, fails closed today. See [sealed channel](sealed-channel.md). -5. B emits a `DelegationRecord` linking to A's record via `record_for`, and any verifier can later run `verify_dag` and `cross_check_chain` offline. Implemented. See [TRACE A2A profile](trace-a2a-profile.md). +1. A hands B a child credential with `scope ⊆` A's scope. This is the [delegation](delegation-chain.md) model. Implemented. +2. B appraises the peer's attestation evidence before sealing anything to its channel key. The handshake and the verifiers are implemented, and the SEV-SNP and TDX verifiers appraise genuine hardware evidence, but on a live call the appraisal runs in software mode at `assurance="none"`. See [attestation](attestation.md) and [hardware validation](../hardware-validation.md). +3. The runtime verifies the chain with `verify_chain` and intersects the delegated scope with B's local policy (`LocalPolicy` or `CedarPolicy`), enforcing the requested capability. Implemented. See [Cedar policy](cedar-policy.md). +4. The task payload is sealed to B's channel key through `SealedChannel` and opened with B's enclave-bound key. The cryptography is implemented; binding the seal to a hardware-verified measurement is the remaining hardware step. +5. B emits a `DelegationRecord` linking to A's record, or a denial record if the call was refused, and any verifier runs `verify_dag` and `cross_check_chain` offline. Implemented. See [TRACE A2A profile](trace-a2a-profile.md). -What ships today is the offline path: given signed credentials and records, `ca2a_verify` and `provenance` reconstruct and check the delegation tree without trusting the operators that produced it. The runtime peer enforcement, sealed channel, Cedar intersection, and hardware attestation that would gate a live call are pending. See [failure modes](failure-modes.md), [ROADMAP.md](../../ROADMAP.md), and [LIMITATIONS.md](../../LIMITATIONS.md). +What ships today runs the whole path live in software mode, and everything except steps 2 and 4's hardware property is verifiable offline by a party trusting neither operator. What does not ship is a peer whose channel key is bound to a hardware-verified measurement on a live call, which is why cA2A is not described as attested across trust domains. See [failure modes](failure-modes.md), [ROADMAP.md](../../ROADMAP.md), and [LIMITATIONS.md](../../LIMITATIONS.md).