README.md
11.2 KB · 212 lines · markdown Raw
1 # PQC Hypervisor Attestation
2
3 ![PQC Native](https://img.shields.io/badge/PQC-Native-blue)
4 ![ML-DSA-65](https://img.shields.io/badge/ML--DSA--65-FIPS%20204-green)
5 ![Backends](https://img.shields.io/badge/backends-SEV--SNP%20%7C%20TDX%20ready-purple)
6 ![License](https://img.shields.io/badge/License-Apache%202.0-orange)
7 ![Version](https://img.shields.io/badge/version-0.1.0-lightgrey)
8
9 **Sigstore for hypervisor memory attestation.** When an AI workload runs inside a cloud VM, the hypervisor (KVM, QEMU, Hyper-V) is transparent: nothing cryptographically proves to the tenant that model weights in RAM have not been read or rewritten by a malicious host. This library is the **post-quantum cryptographic envelope** — `MemoryRegion`, `AttestationClaim`, `AttestationReport`, ML-DSA-signed, independently verifiable — that a hypervisor project, TEE runtime, or confidential-computing framework can plug its memory-reading primitives into. The library ships pluggable backends for **AMD SEV-SNP** and **Intel TDX** as stubs, plus a reference `InMemoryBackend` for tests.
10
11 ## The Problem
12
13 Cloud AI inference workloads place multi-hundred-megabyte model weights in guest memory. The hypervisor can read or rewrite every page. Today's tenants rely on contractual trust ("the cloud provider said they wouldn't") plus coarse platform-level attestation (MRTD, launch digest) that only covers VM boot — not the *runtime* state of the pages you actually care about. An attacker with host compromise, or a curious operator, can:
14
15 - Silently exfiltrate proprietary model weights.
16 - Swap in backdoored weights for a single request.
17 - Corrupt the KV cache to influence in-flight generations.
18
19 None of this is visible to the guest without a runtime memory attestation protocol. And any attestation that signs memory state with RSA or ECDSA today is retroactively forgeable once a CRQC exists — which is why the cryptographic envelope must be post-quantum from day one.
20
21 ## The Solution
22
23 Every attestation is a post-quantum signed, freshness-bounded claim about specific memory regions:
24
25 - **SHA3-256** per-region content hashes — deterministic snapshot of what lives at those pages.
26 - **ML-DSA (FIPS 204)** signature over the canonical report bytes, by a known attester DID.
27 - **Expiry and nonce** for replay resistance.
28 - **Expected-hash pinning** so a remote verifier can detect drift from trusted state.
29 - **Pluggable backends** — the library never reads memory itself; backends do, using `/dev/sev-guest`, `/dev/tdx-guest`, or whatever primitive the real TEE exposes.
30
31 ## Installation
32
33 ```bash
34 pip install pqc-hypervisor-attestation
35 ```
36
37 Development:
38
39 ```bash
40 pip install -e ".[dev]"
41 ```
42
43 ## Quick Start
44
45 ```python
46 from quantumshield.identity.agent import AgentIdentity
47
48 from pqc_hypervisor_attestation import (
49 AttestationVerifier,
50 Attester,
51 ContinuousAttester,
52 InMemoryBackend,
53 MemoryRegion,
54 RegionSnapshot,
55 )
56
57 # 1. Identity + signer.
58 identity = AgentIdentity.create("llama-host-attester", capabilities=["attest"])
59 attester = Attester(identity)
60
61 # 2. Backend with a pinned region.
62 backend = InMemoryBackend()
63 weights = MemoryRegion(
64 region_id="model-weights-0",
65 description="Llama weight shard 0",
66 address=0x1000,
67 size=128,
68 protection="RO",
69 )
70 content = b"\xaa" * 128
71 backend.register("model-serving-1", weights, content)
72
73 # 3. Continuous attester with pinned expected hash.
74 loop = ContinuousAttester(
75 attester=attester,
76 backend=backend,
77 workload_id="model-serving-1",
78 expected_hashes={weights.region_id: RegionSnapshot.hash_bytes(content)},
79 )
80
81 report = loop.attest_once()
82 result = AttestationVerifier.verify(report, strict=True)
83 assert result.valid
84 ```
85
86 ## Architecture
87
88 ```
89 +-----------------------------+ +------------------------------+
90 | AI workload (guest VM) | | Remote verifier |
91 | | | |
92 | ContinuousAttester | | AttestationVerifier |
93 | | | | | |
94 | v | | v |
95 | AttestationBackend | | - ML-DSA signature check |
96 | (SEV-SNP | TDX | memory) | | - expiry check |
97 | | | | - expected_hash pinning |
98 | v | | |
99 | MemoryRegion ---> RegionSnapshot -> AttestationClaim |
100 | | |
101 | v |
102 | AttestationReport (bundle) |
103 | | |
104 | v |
105 | ML-DSA sign (quantumshield) |
106 +-----------------------------+ +------------------------------+
107 | ^
108 | signed AttestationReport |
109 +---------------------------------+
110 ```
111
112 ## Cryptography
113
114 | Primitive | Purpose | Algorithm |
115 | ------------------------- | ---------------------------------------- | ------------- |
116 | Region content hash | Fingerprint memory bytes | SHA3-256 |
117 | Report canonical digest | Input to signer | SHA3-256 |
118 | Attestation signature | Bind report to attester DID | ML-DSA-65 |
119 | Verifier trust anchor | Attester public key (from DID / keystore)| ML-DSA public |
120
121 All signing is delegated to [`quantumshield`](https://github.com/dyber-pqc/quantumshield), which prefers real `liboqs` ML-DSA when available and falls back to transitional Ed25519.
122
123 ## Threat Model
124
125 | Adversary capability | Coverage |
126 | ------------------------------------------------- | ---------------------------------------------------------------- |
127 | Forges an attestation report from scratch | Blocked — requires the attester's ML-DSA private key. |
128 | Replays an old valid report to hide drift | Blocked — `expires_at` bound + per-report nonce in each claim. |
129 | Rewrites model weights in guest memory | Detected — region snapshot hash diverges from `expected_hash`. |
130 | Substitutes a newer signed report from same key | Mitigated — verifier pins expected hashes independently. |
131 | Tampers with report fields in transit | Blocked — canonical-bytes + ML-DSA binds every field. |
132 | Q-day adversary with CRQC | Out of scope for ECDSA/RSA signers; covered here by ML-DSA. |
133 | Compromises the attester key via guest escape | Out of scope — mitigate with SEV-SNP / TDX sealed key storage. |
134
135 ## Backend Integration Guide
136
137 The library defines a single abstract interface:
138
139 ```python
140 class AttestationBackend(ABC):
141 name: str
142 platform: str
143
144 def list_regions(self, workload_id: str) -> list[MemoryRegion]: ...
145 def snapshot(self, region: MemoryRegion) -> RegionSnapshot: ...
146 ```
147
148 Any backend that can enumerate memory ranges and hash their bytes can plug in.
149
150 ### AMD SEV-SNP (`/dev/sev-guest`)
151
152 The shipped `AMDSEVSNPBackend` is a stub that documents the expected behaviour. A real integration:
153
154 1. Reads the SEV-SNP launch digest at VM start and stores per-region base/size in its workload manifest.
155 2. For `list_regions`, returns `MemoryRegion` entries whose `address` is the guest-physical base and `size` is the range length.
156 3. For `snapshot`, issues the `SNP_GET_REPORT` ioctl on `/dev/sev-guest`, reads the backing pages into a buffer, and returns `RegionSnapshot.create(region_id, buffer)`.
157
158 ### Intel TDX (`/dev/tdx-guest`)
159
160 `IntelTDXBackend` follows the same pattern using `TDX_CMD_GET_REPORT0` and the TD's MRTD / RTMR measurements.
161
162 ### Your own backend
163
164 Subclass `AttestationBackend`, implement `list_regions` and `snapshot`, and pass an instance to `ContinuousAttester`. The library handles canonicalisation, signing, and verification uniformly.
165
166 ## API Reference
167
168 ### Data types
169
170 | Class | Description |
171 | ------------------ | ----------------------------------------------------------------- |
172 | `MemoryRegion` | Addressable range `(region_id, address, size, protection)`. |
173 | `RegionSnapshot` | `(region_id, content_hash, size, taken_at)` SHA3-256 fingerprint. |
174 | `AttestationClaim` | One signed statement about one region at one point in time. |
175 | `AttestationReport`| Bundle of claims + ML-DSA envelope + expiry. |
176 | `VerificationResult`| Breakdown of signature / expiry / drift checks. |
177
178 ### Signers and verifiers
179
180 | Symbol | Purpose |
181 | ------------------------ | ----------------------------------------------------------- |
182 | `Attester` | Wraps an `AgentIdentity`; signs reports. |
183 | `AttestationVerifier` | Static verifier; returns `VerificationResult`. |
184 | `AttestationVerifier.verify_or_raise` | Raises `RegionDriftError` / `AttestationVerificationError`. |
185 | `ContinuousAttester` | Periodic loop: enumerate regions, snapshot, sign. |
186
187 ### Backends
188
189 | Class | Use |
190 | ------------------ | --------------------------------------------------------- |
191 | `InMemoryBackend` | Reference / tests / tutorials. |
192 | `AMDSEVSNPBackend` | Stub for AMD SEV-SNP (plug into `/dev/sev-guest`). |
193 | `IntelTDXBackend` | Stub for Intel TDX (plug into `/dev/tdx-guest`). |
194
195 ### Exceptions
196
197 `HypervisorAttestationError` -> `InvalidRegionError`, `AttestationVerificationError` (-> `RegionDriftError`), `BackendError` (-> `UnknownBackendError`).
198
199 ## Why PQC Matters for Hypervisor Attestation
200
201 Cloud confidential-computing deployments live for a decade or more. A VM image spun up in 2026 may still be attested by the same kind of signature in 2036 — long enough for a CRQC to exist. An attestation signed today with ECDSA or RSA can be **retroactively forged** by any adversary holding a recording of the public key and the signed payload: in 2035 they can manufacture a fresh, valid-looking "clean" attestation for a workload they actually tampered with in 2026, and no auditor can distinguish it from a real one. Post-quantum signatures break that chain: even with Shor's algorithm, ML-DSA remains unbroken, so historical attestations continue to bind the host to its claim. Hypervisor attestation is exactly the place that non-repudiation must survive the cryptographic transition.
202
203 ## Examples
204
205 * [`examples/basic_attestation.py`](examples/basic_attestation.py) — end-to-end sign-and-verify over two in-memory regions.
206 * [`examples/detect_memory_tampering.py`](examples/detect_memory_tampering.py) — mutate a region between attestations and watch drift surface.
207 * [`examples/continuous_loop_demo.py`](examples/continuous_loop_demo.py) — run the periodic attestation loop and stream reports.
208
209 ## License
210
211 Apache 2.0. See [LICENSE](LICENSE).
212