diff --git a/CHANGELOG.md b/CHANGELOG.md index f74ad1f..d390889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Azure confidential-VM attestation (`cmcp_runtime.tee.azure_cvm.AzureCVMProvider` + `cmcp_verify.azure_cvm`), **hardware-validated on live Azure SEV-SNP silicon**. Azure runs SNP behind a Hyper-V paravisor with no `/dev/sev-guest`; the SNP report is read from the vTPM NV index `0x01400001` and the guest cannot control `REPORT_DATA` (the paravisor binds the vTPM AK there). cMCP's nonce (`jwk_thumbprint || audit-root`) is therefore committed into an AK-signed TPM quote's qualifying data, with the AK rooted in silicon via the SNP report (`REPORT_DATA == sha256(runtime_data)`) and the VCEK→ASK→ARK chain (reusing `cmcp_verify.sev_snp`). Auto-detected first (before TPM/SEV-SNP) since Azure exposes no `/dev/sev-guest`. Maps to the `amd-sev-snp` platform and is distinguished at verify time by its evidence envelope, so no new platform identifier is required. +- Azure confidential-VM attestation (`cmcp_runtime.tee.azure_cvm.AzureCVMProvider` + `cmcp_verify.azure_cvm`), **hardware-validated on live Azure SEV-SNP silicon**. Azure runs SNP behind a Hyper-V paravisor with no `/dev/sev-guest`; the SNP report is read from the vTPM NV index `0x01400001` and the guest cannot control `REPORT_DATA` (the paravisor binds the vTPM AK there). cMCP's nonce (`jwk_thumbprint || audit-root`) is therefore committed into an AK-signed TPM quote's qualifying data, with the AK rooted in silicon via the SNP report (`REPORT_DATA == sha256(runtime_data)`) and the VCEK→ASK→ARK chain (reusing `cmcp_verify.sev_snp`). Auto-detected first (before TPM/SEV-SNP) since Azure exposes no `/dev/sev-guest`. Carries its own `runtime.platform` value `azure-cvm-sev-snp` (requires `agentrust-trace>=0.4`) so a consumer keying on `runtime.platform` knows the root of trust is vTPM-rooted, not a guest-controlled SNP `report_data`. - `tool_transcript.entries`: privacy-preserving per-call view in the TRACE Claim (one entry per tool call with `tool_name`, `data_class` from the catalog, and the policy `decision`), derived from the audit chain so no raw parameters or response bodies are exposed. `tool_transcript.hash` continues to bind the full transcript to the audit-chain tip. Adds `transcript_entries_hash()` for offline recomputation. (#126) ## [0.3.0] - 2026-06-30 diff --git a/pyproject.toml b/pyproject.toml index 235f39b..d9098d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "agentrust-trace>=0.1", + "agentrust-trace>=0.4", "agent-manifest>=0.1.1", "cryptography>=42.0", "pyyaml>=6.0", diff --git a/src/cmcp_runtime/audit/trace_claim.py b/src/cmcp_runtime/audit/trace_claim.py index f9de202..63c1f79 100644 --- a/src/cmcp_runtime/audit/trace_claim.py +++ b/src/cmcp_runtime/audit/trace_claim.py @@ -24,10 +24,11 @@ _PROVIDER_MAP: dict[str, str] = { "sev-snp": "amd-sev-snp", - # Azure CVM is SEV-SNP (vTPM-rooted behind a paravisor); it maps to the same - # canonical platform. The verifier distinguishes it by evidence shape (a JSON - # envelope vs a raw SNP report), so no new platform literal is required. - "azure-cvm-sev-snp": "amd-sev-snp", + # Azure CVM is SEV-SNP behind a Hyper-V paravisor (vTPM-rooted, not + # direct-silicon). It has its own canonical platform value (agentrust-trace + # >=0.4) so a consumer keying on runtime.platform knows the root of trust is + # vTPM-rooted rather than a guest-controlled SNP report_data. + "azure-cvm-sev-snp": "azure-cvm-sev-snp", "tdx": "intel-tdx", "opaque": "intel-tdx", "tpm": "tpm2", diff --git a/src/cmcp_verify/verify.py b/src/cmcp_verify/verify.py index c67c144..c149b28 100644 --- a/src/cmcp_verify/verify.py +++ b/src/cmcp_verify/verify.py @@ -53,6 +53,7 @@ def _jwk_thumbprint_sha256(x_b64url: str) -> bytes: _KNOWN_PLATFORMS = { "amd-sev-snp", + "azure-cvm-sev-snp", "intel-tdx", "tpm2", "nvidia-h100", @@ -64,22 +65,6 @@ def _jwk_thumbprint_sha256(x_b64url: str) -> bytes: } -def _is_azure_cvm_evidence(raw_evidence_b64: str | None) -> bool: - """True if raw_evidence is the Azure CVM JSON envelope (vs a raw SNP report). - - The AzureCVMProvider emits a JSON object ({"v":1,...}); a bare-metal SNP - report is a binary blob starting with its version field. Base64 of a JSON - object begins with 'ey' (i.e. '{"'); this cheap prefix check routes the - amd-sev-snp platform to the vTPM-rooted verifier without a new platform id. - """ - if not raw_evidence_b64: - return False - try: - return base64.b64decode(raw_evidence_b64)[:1] == b"{" - except Exception: # noqa: BLE001 - return False - - def _is_software_only(runtime: dict[str, Any]) -> bool: """True for dev-mode (non-attested) records. @@ -810,14 +795,13 @@ def verify_trace_claim( details["tpm_failure"] = tpm_result.failure_reason unverified.extend(tpm_result.unverified_fields) details.update(tpm_result.details) - elif platform == "amd-sev-snp" and _is_azure_cvm_evidence(_runtime.get("raw_evidence")): + elif platform == "azure-cvm-sev-snp": # Azure confidential VM: SEV-SNP behind a Hyper-V paravisor, vTPM-rooted. # The guest cannot control SNP REPORT_DATA (the paravisor binds the vTPM AK # there), so cMCP's nonce is committed into an AK-signed TPM quote's extraData # and the AK is rooted in silicon by the SNP report (REPORT_DATA == - # sha256(runtime_data)). Distinguished from a bare-metal SNP report by the JSON - # evidence envelope AzureCVMProvider emits. Chain travels in the envelope; the - # ARK is pinned out of band. Hardware-validated on live Azure SEV-SNP. + # sha256(runtime_data)). The VCEK chain travels in the JSON evidence envelope; + # the ARK is pinned out of band. Hardware-validated on live Azure SEV-SNP. from cmcp_verify.azure_cvm import verify_azure_cvm_measurement raw_bytes = base64.b64decode(_runtime["raw_evidence"])