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
18 changes: 9 additions & 9 deletions examples/rejection-with-proof/chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"chain": [
{
"credential_id": "cred-0-orchestrator",
"issuer": "4b6b80f73e41b5401523db167775027ee5d41a1d83954557c905b00962cd5c8e",
"subject": "f5d7ff727931d0acb4393c9012d9a4fe9c3a0837a1c191a31e5ad1c35fa2dd07",
"issuer": "5d950cd9fea67de730ac35d540c48d62a22dada5618db0c8474b5eae5562a7cf",
"subject": "2f35acc0747fb45e739288d02bbd18e54e5283f770e30391b1c272fc1285e3bf",
"scope": [
"task:read",
"task:write",
Expand All @@ -12,31 +12,31 @@
],
"depth": 0,
"parent_id": null,
"signature": "749c4a53497b1bb6ca17f4aafe6bad30674484baa4f8f3083581cf9bbbb950d5cb9e3a2ab0453b7cb9c24b339496f4eaa6a6d989960d356633aeb53c58251007"
"signature": "21056267f494e8ac773bf9aa76b23815baded9b17605c265296f719241050a76b991cbd0162385359302607f19483d81cacc971f8d3cdbf1bc3d8c4cca778d0e"
},
{
"credential_id": "cred-1-researcher",
"issuer": "f5d7ff727931d0acb4393c9012d9a4fe9c3a0837a1c191a31e5ad1c35fa2dd07",
"subject": "747e897486d4d32abd45d3a9e04e5357f6a88a1bad8d0ce9dc6084cd10c7b121",
"issuer": "2f35acc0747fb45e739288d02bbd18e54e5283f770e30391b1c272fc1285e3bf",
"subject": "b12a13df30c7123fe6ab8e03551d7c7465cb1acfa04701e0efb05eeba9a2b7d6",
"scope": [
"task:read",
"tool:purchase",
"tool:search"
],
"depth": 1,
"parent_id": "cred-0-orchestrator",
"signature": "0a5d95fded81c149bc652bfa5903c7a02e3caa9e0f5a41761ffef19d2dad9fd3e4ac5e1f2ef921bc92a6ed146aad0b3765709491447e7f87d9331df502e3af02"
"signature": "308b8c1cab0b49bb1993f185b67515adf832bf50a8cc9ddbf7e9e4d785cac9f3ab20a73be8cef332e28b409fc67a904eef4955ac0b1fcded39734303f0e0bf01"
},
{
"credential_id": "cred-2-retriever",
"issuer": "747e897486d4d32abd45d3a9e04e5357f6a88a1bad8d0ce9dc6084cd10c7b121",
"subject": "b05e7f85927cc4a5886ec097da0200c4a02d0d944dd2d73c9a10f5662041074c",
"issuer": "b12a13df30c7123fe6ab8e03551d7c7465cb1acfa04701e0efb05eeba9a2b7d6",
"subject": "2da412a4c5cd6d9f59cf6bf84fc8d78cb9ba3352c515f85031592cd6a59fb9a2",
"scope": [
"tool:search"
],
"depth": 2,
"parent_id": "cred-1-researcher",
"signature": "a6ec5c6c8add5923b9bf3f9fc9cb8cd0c10cf6faf53a3e04571a418f199d8191bbf9ee4f0b7ddd06f75eaeaa4e25e0a761e62ca59b09d31d6544b4bc90ce1e0d"
"signature": "6307eb2fb085b2dcca5d5cbdc21a6762c18019db5e8c29714bc7e99837d696d7f18ddbbe60ebfb3c6815f4a44e61020f71a4b30da5d387a3b9afc22ef1b49204"
}
]
}
14 changes: 7 additions & 7 deletions examples/rejection-with-proof/dag.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"record_id": "rec-0-orchestrator",
"credential_id": "cred-0-orchestrator",
"subject": "f5d7ff727931d0acb4393c9012d9a4fe9c3a0837a1c191a31e5ad1c35fa2dd07",
"subject": "2f35acc0747fb45e739288d02bbd18e54e5283f770e30391b1c272fc1285e3bf",
"scope": [
"task:read",
"task:write",
Expand All @@ -15,31 +15,31 @@
{
"record_id": "rec-1-researcher",
"credential_id": "cred-1-researcher",
"subject": "747e897486d4d32abd45d3a9e04e5357f6a88a1bad8d0ce9dc6084cd10c7b121",
"subject": "b12a13df30c7123fe6ab8e03551d7c7465cb1acfa04701e0efb05eeba9a2b7d6",
"scope": [
"task:read",
"tool:purchase",
"tool:search"
],
"parent_record_hash": "f8fd5e5073d51406bdc9fe7e79b90cd3c70afbee1ac47d5763996d4af20af5a7"
"parent_record_hash": "3655a4752c7d0841cf4cc2adb80523ea988469a548d4b05636707132ab25c1b1"
},
{
"record_id": "rec-2-retriever",
"credential_id": "cred-2-retriever",
"subject": "b05e7f85927cc4a5886ec097da0200c4a02d0d944dd2d73c9a10f5662041074c",
"subject": "2da412a4c5cd6d9f59cf6bf84fc8d78cb9ba3352c515f85031592cd6a59fb9a2",
"scope": [
"tool:search"
],
"parent_record_hash": "f940a2e460960035bbe39f93b7c76ac965d914857e10bce66bffce871fbc102e"
"parent_record_hash": "1b5cc71b77a37400961d71ccc75c4292f5ed122bd77ef854137dda059540faf9"
},
{
"record_id": "rec-denied-purchase",
"credential_id": "cred-2-retriever",
"subject": "b05e7f85927cc4a5886ec097da0200c4a02d0d944dd2d73c9a10f5662041074c",
"subject": "2da412a4c5cd6d9f59cf6bf84fc8d78cb9ba3352c515f85031592cd6a59fb9a2",
"scope": [
"tool:search"
],
"parent_record_hash": "3867abdd0574dce6cd20cae0b1e4a22afa5ab0a22778cda449c82c12dca4f127",
"parent_record_hash": "169191a74d471318788dab7619b3914121763baa8a34b2f4465b7e16ba10398e",
"decision": "deny",
"requested_capability": "tool:purchase",
"effective_scope": [
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies = [
"agentrust-trace>=0.5",
# Shared hardware-attestation verification (generic cert-chain verifier,
# SNP/TDX/TPM primitives) consumed via PyPI instead of duplicated per repo.
"agent-manifest>=0.8",
"agent-manifest>=0.10",
"rfc8785>=0.1",
]

Expand Down
65 changes: 35 additions & 30 deletions src/ca2a_runtime/tee/sev_snp.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""AMD SEV-SNP attestation report parsing and the SEV-SNP provider.

This module parses the SEV-SNP ``ATTESTATION_REPORT`` structure (1184 bytes,
AMD SEV-SNP ABI) and exposes the fields the cA2A verifier appraises: the launch
measurement, the report data (which binds the runtime key and nonce), and the
ECDSA-P384 signature over the report body. The verification and certificate
chain appraisal live in :mod:`ca2a_verify.sev_snp`.
This module exposes the SEV-SNP ``ATTESTATION_REPORT`` fields the cA2A verifier
appraises: the launch measurement, the report data (which binds the runtime key
and nonce), and the ECDSA-P384 signature over the report body. The verification
and certificate chain appraisal live in :mod:`ca2a_verify.sev_snp`.

The layout itself is **not** defined here. Offsets and parsing come from
``agent_manifest`` (>=0.10), which cmcp also consumes; between them the org
carried four mirrors of one ABI table, two inside cmcp alone. :class:`SevSnpReport`
stays as cA2A's surface, keeping its error contract and the ``signature_rs``
helper, but the bytes are read by the shared parser.

Producing a report requires a real SEV-SNP guest (``/dev/sev-guest``), so
:meth:`SevSnpProvider.attest` fails closed off hardware. The verifier does not
Expand All @@ -14,18 +19,20 @@

from __future__ import annotations

import struct
from dataclasses import dataclass

from agent_manifest import SNP_OFFSETS, SNP_REPORT_LEN, SnpVerificationError, parse_snp_report

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

# Layout of the SEV-SNP ATTESTATION_REPORT (offsets in bytes).
REPORT_SIZE = 0x4A0 # 1184
SIG_OFFSET = 0x2A0 # signature covers report[:SIG_OFFSET]
REPORT_DATA_OFFSET = 0x50
# Layout of the SEV-SNP ATTESTATION_REPORT, re-exported from agent-manifest's
# shared ABI table so cA2A and cmcp cannot disagree about where a field sits.
REPORT_SIZE = SNP_REPORT_LEN
SIG_OFFSET = SNP_OFFSETS["signature"] # signature covers report[:SIG_OFFSET]
REPORT_DATA_OFFSET = SNP_OFFSETS["report_data"]
REPORT_DATA_LEN = 64
MEASUREMENT_OFFSET = 0x90
MEASUREMENT_OFFSET = SNP_OFFSETS["measurement"]
MEASUREMENT_LEN = 48
# ECDSA-P384 signature: r then s, each in a 72-byte little-endian field.
SIG_COMPONENT_LEN = 72
Expand Down Expand Up @@ -64,26 +71,24 @@ def signature_rs(self) -> tuple[int, int]:

@classmethod
def parse(cls, blob: bytes) -> SevSnpReport:
"""Parse a raw report, raising AttestationFailed on any malformed input."""
if len(blob) < REPORT_SIZE:
raise AttestationFailed(
"SEV-SNP report too short",
detail=f"got {len(blob)} bytes, need at least {REPORT_SIZE}",
)
version, guest_svn, policy = struct.unpack_from("<IIQ", blob, 0)
(vmpl,) = struct.unpack_from("<I", blob, 0x30)
(signature_algo,) = struct.unpack_from("<I", blob, 0x34)
report_data = blob[REPORT_DATA_OFFSET : REPORT_DATA_OFFSET + REPORT_DATA_LEN]
measurement = blob[MEASUREMENT_OFFSET : MEASUREMENT_OFFSET + MEASUREMENT_LEN]
"""Parse a raw report, raising AttestationFailed on any malformed input.

The bytes are read by ``agent_manifest.parse_snp_report``; this keeps
cA2A's error contract so callers still catch :class:`AttestationFailed`.
"""
try:
shared = parse_snp_report(blob)
except SnpVerificationError as exc:
raise AttestationFailed("SEV-SNP report is malformed", detail=str(exc)) from exc
return cls(
version=version,
guest_svn=guest_svn,
policy=policy,
vmpl=vmpl,
signature_algo=signature_algo,
measurement=measurement,
report_data=report_data,
raw=bytes(blob[:REPORT_SIZE]),
version=shared.version,
guest_svn=shared.guest_svn,
policy=shared.policy,
vmpl=shared.vmpl,
signature_algo=shared.signature_algo,
measurement=shared.measurement,
report_data=shared.report_data,
raw=shared.raw,
)


Expand Down