diff --git a/CHANGELOG.md b/CHANGELOG.md index c53c446..ce06034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Certificate-chain verification now delegates to agent-manifest's shared generic verifier (`agent-manifest>=0.5`) instead of ca2a's own copy. `ca2a_verify.verify_cert_chain` (used by the SEV-SNP VCEK, TDX PCK, and TPM AK chains alike) is now a thin wrapper over `agent_manifest.verify_cert_chain`, re-raising `CertChainError` as `AttestationFailed` to preserve ca2a's contract. This removes the org's last duplicated cert-chain verifier; ca2a keeps its own report/quote parsers, per-format signature checks, appraisal shapes, trust-root pinning, and `verify_offer` semantics. Behavior unchanged (all 201 tests pass unchanged). - Bumped `agentrust-trace` to `>=0.4`. The previous `<0.4` cap worked around the published 0.3.0 lacking the TRACE A2A `delegation` block that cA2A records carry; 0.4.0 ships that block, so the cap is removed. cA2A still owns the diff --git a/pyproject.toml b/pyproject.toml index eca3b67..7c1afeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,9 @@ dependencies = [ # (see ROADMAP Tier 0). Pin a compatible minor; cross-repo skew is a known # risk documented in LIMITATIONS.md. "agentrust-trace>=0.4", + # Shared hardware-attestation verification (generic cert-chain verifier, + # SNP/TDX/TPM primitives) consumed via PyPI instead of duplicated per repo. + "agent-manifest>=0.5", "rfc8785>=0.1", ] diff --git a/src/ca2a_verify/sev_snp.py b/src/ca2a_verify/sev_snp.py index 259a2e9..e78045d 100644 --- a/src/ca2a_verify/sev_snp.py +++ b/src/ca2a_verify/sev_snp.py @@ -20,7 +20,7 @@ from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature -from cryptography.hazmat.primitives.hashes import SHA256, SHA384 +from cryptography.hazmat.primitives.hashes import SHA384 from ca2a_runtime.errors import AttestationFailed from ca2a_runtime.tee.sev_snp import SEV_GUEST_DEVICE, SIG_ALGO_ECDSA_P384_SHA384, SevSnpReport @@ -33,30 +33,22 @@ def verify_cert_chain( ) -> None: """Verify a leaf-to-root certificate chain against a set of trusted roots. - ``chain`` is ordered leaf first (VCEK), root last (ARK). Each certificate - must be directly issued by the next, and the final certificate must match a - trusted root by fingerprint. Raises AttestationFailed on any failure. + ``chain`` is ordered leaf first (VCEK), root last (ARK). Delegates to + agent-manifest's shared, algorithm-agnostic cert-chain verifier (one + implementation across the org, consumed via PyPI) and re-raises its failure + as AttestationFailed to preserve ca2a's error contract. Used for the SEV-SNP + VCEK chain, the TDX PCK chain, and the TPM AK chain alike. Raises + AttestationFailed on any failure. """ - if not chain: - raise AttestationFailed("empty certificate chain") - - for i in range(len(chain) - 1): - child, issuer = chain[i], chain[i + 1] - try: - child.verify_directly_issued_by(issuer) - except (ValueError, TypeError, InvalidSignature) as exc: - raise AttestationFailed( - f"certificate at position {i} is not validly issued by the next", - detail=str(exc), - ) from exc - - root = chain[-1] - trusted = {c.fingerprint(SHA256()) for c in trusted_roots} - if root.fingerprint(SHA256()) not in trusted: + from agent_manifest import CertChainError + from agent_manifest import verify_cert_chain as _shared_verify_cert_chain + + try: + _shared_verify_cert_chain(chain, trusted_roots) + except CertChainError as exc: raise AttestationFailed( - "chain root is not a trusted AMD root", - detail=root.subject.rfc4514_string(), - ) + "certificate chain verification failed", detail=str(exc) + ) from exc def verify_sev_snp_report(