Attestation & Verification
Attestation is central to what Lunal enables. Every request-response flow can include cryptographic proof of what code is running, on what hardware, with what configuration. This document explains how that works under the hood.
After reading this, you'll understand how measurements are computed at launch, what attestation reports contain, how the certificate chain works, and how to verify a report. This assumes familiarity with the concepts from Documents 1-4, particularly the RMP, VMPLs, and the role of the PSP (Platform Security Processor).
The Problem Attestation Solves
Encryption and integrity protection are only useful if you can prove what's actually running. Consider the situation from a customer's perspective: they want to run sensitive workloads on your infrastructure, but they don't trust you. You tell them "we're running your code in an encrypted VM, we can't see anything." They have no reason to believe you.
Maybe you're running their code unencrypted and just claiming otherwise. Maybe you modified their code before loading it. Maybe you're running a completely different program that just pretends to be theirs. Without cryptographic proof, promises are worthless.
Attestation provides that proof. The hardware itself measures what code was loaded, and AMD's keys (fused into the silicon at manufacturing) sign a statement about that measurement. A customer can verify: "This signature is valid and chains to AMD's root key. The measurement matches what I expected. Therefore, AMD's hardware is asserting that my code is running."
This is the problem Lunal solves. We make attestation accessible such that you don't need to implement the verification flow yourself, but understanding it helps you reason about what guarantees you're actually getting.
What Gets Measured
Before diving into attestation reports, we need to understand what goes into the measurement. The measurement (called the launch digest) captures the initial state of the VM at launch time.
The Launch Sequence
When a hypervisor launches an SNP guest, three commands are involved:
┌──────────────────────────────────────────────────────────────────────────┐
│ SNP Launch Commands │
│ │
│ SNP_LAUNCH_START │
│ ──────────────── │
│ • Creates guest context in PSP memory │
│ • Generates the VM Encryption Key (VEK) │
│ • Records guest policy (what the VM is allowed to do) │
│ • Initializes the launch digest to a starting value │
│ │
│ │ │
│ ▼ │
│ │
│ SNP_LAUNCH_UPDATE (called repeatedly, once per page) │
│ ───────────────── │
│ • For each page of initial VM memory: │
│ - Compute hash of page contents │
│ - Combine with current launch digest, GPA, page type, permissions │
│ - New digest = SHA-384(current_digest || page_info) │
│ - Encrypt page with VEK │
│ - Mark page in RMP as belonging to this guest │
│ │
│ │ │
│ ▼ │
│ │
│ SNP_LAUNCH_FINISH │
│ ───────────────── │
│ • Finalizes the launch digest (no more updates possible) │
│ • Optionally validates Identity Block (more on this later) │
│ • Records HOST_DATA (hypervisor-provided, appears in reports) │
│ • Transitions guest to running state │
│ │
└──────────────────────────────────────────────────────────────────────────┘
The critical insight is that SNP_LAUNCH_UPDATE is called for every page in the initial VM image. Each call extends the digest, creating a chain. The final digest depends on:
- The content of every measured page
- The guest physical address (GPA) where each page is placed
- The type of each page (code, data, VMSA, secrets page, etc.)
- The VMPL permissions assigned to each page
- The order in which pages were installed
What's In the Measurement
In a typical SNP launch with direct boot (passing kernel and initrd to QEMU):
Measured components:
- OVMF firmware (the UEFI implementation)
- Linux kernel
- Initial ramdisk (initramfs/initrd)
- Kernel command line
- Initial CPU state (the VMSA, which sets where execution starts)
Not measured:
- Anything loaded after boot (filesystems mounted from disk, downloaded code)
- Runtime state changes
- Data written to memory during execution
This is important: the measurement only proves initial state. If your application code isn't in the initramfs, it's not in the measurement. Document 6 covers strategies for extending trust to code loaded after boot.
Page Types and Measurement
Different page types are measured differently:
| Page Type | What Gets Measured | Notes |
|---|---|---|
| NORMAL | SHA-384 of page contents | Standard code/data |
| VMSA | SHA-384 of initial CPU state | Where execution starts, initial register values |
| ZERO | Measured as all zeros | Content is encrypted zeros, but measures as zero |
| UNMEASURED | GPA recorded, content not hashed | For pages the guest will initialize itself |
| SECRETS | GPA recorded, content not hashed | PSP fills in keys after measurement |
| CPUID | GPA recorded, content not hashed | Hypervisor-provided CPU feature information |
The SECRETS page is particularly interesting. The measurement commits to where the secrets page will be (the GPA), but not what's in it. After measurement, the PSP fills it with the VMPCKs (the keys for guest-PSP communication). This way, the measurement is reproducible (doesn't depend on random key values) but the guest knows exactly where to find its keys.
The Measurement Chain
Each SNP_LAUNCH_UPDATE constructs a PAGE_INFO structure and hashes it:
┌──────────────────────────────────────────────────────────────────────────┐
│ How the Launch Digest Accumulates │
│ │
│ Start: digest = initial_value │
│ │
│ For each page: │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ PAGE_INFO = { │ │
│ │ DIGEST_CUR: current launch digest (48 bytes) │ │
│ │ CONTENTS: SHA-384 of page contents (or 0 for special types) │ │
│ │ GPA: guest physical address where page will be │ │
│ │ PAGE_TYPE: NORMAL, VMSA, ZERO, etc. │ │
│ │ VMPL_PERMS: permissions for each VMPL (0-3) │ │
│ │ } │ │
│ │ │ │
│ │ new_digest = SHA-384(PAGE_INFO) │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ After all pages: final launch digest = MEASUREMENT in attestation report│
│ │
└──────────────────────────────────────────────────────────────────────────┘
Because each step incorporates the previous digest, the final value depends on the exact sequence of operations. Load the same code at different addresses? Different measurement. Load pages in different order? Different measurement. Change one byte in one page? Different measurement.
Attestation Reports
Once a guest is running, it can ask the PSP for an attestation report. The report is a signed statement from the hardware about the guest's identity and current state.
Requesting a Report
The guest sends a MSG_REPORT_REQ to the PSP through the encrypted communication channel (using the VMPCKs from the secrets page). The request includes:
- REPORT_DATA: 64 bytes of arbitrary data the guest wants to bind to the report
- VMPL: Which privilege level is requesting (must be >= current VMPL)
The PSP constructs the report and signs it, then returns it to the guest.
What's In the Report
The attestation report is a structured data blob (currently version 5) with several categories of information:
┌──────────────────────────────────────────────────────────────────────────┐
│ Attestation Report Contents │
│ │
│ Identity Information │
│ ──────────────────── │
│ VERSION: Report format version (currently 5) │
│ POLICY: Guest policy flags (debug allowed?, SMT?, migration?) │
│ FAMILY_ID: 16-byte family identifier (from launch, if IDB used) │
│ IMAGE_ID: 16-byte image identifier (from launch, if IDB used) │
│ GUEST_SVN: Guest security version number │
│ VMPL: Which VMPL requested this report (or 0xFFFFFFFF if host) │
│ │
│ The Measurement │
│ ─────────────── │
│ MEASUREMENT: 48-byte SHA-384 launch digest │
│ This is what proves which code was loaded at launch │
│ │
│ Custom Data │
│ ─────────── │
│ REPORT_DATA: 64 bytes provided by the guest when requesting │
│ HOST_DATA: 32 bytes provided by hypervisor at launch │
│ │
│ Platform Information │
│ ──────────────────── │
│ CURRENT_TCB: Current firmware versions on this machine │
│ REPORTED_TCB: TCB version used to derive the signing key │
│ COMMITTED_TCB: Minimum TCB version (anti-rollback) │
│ PLATFORM_INFO: Flags about the platform (SMT enabled?, etc.) │
│ │
│ Hardware Identification │
│ ─────────────────────── │
│ CHIP_ID: 64-byte unique identifier for this physical CPU │
│ CPUID info: Family, model, stepping of the processor │
│ │
│ Signing Key Information │
│ ─────────────────────── │
│ ID_KEY_DIGEST: Hash of the ID signing key (if IDB used) │
│ AUTHOR_KEY_DIGEST: Hash of the author signing key (if provided) │
│ REPORT_ID: Random identifier for this specific guest instance │
│ │
│ The Signature │
│ ───────────── │
│ SIGNATURE: 512-byte ECDSA P-384 signature over all the above │
│ Signed by VCEK (chip-unique key) or VLEK (provider key) │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Lunal enables these attestation reports to be attached to any request-response flow. The integration approach depends on your architecture—attestation on every request, on session establishment, or on-demand.
The REPORT_DATA Field
The REPORT_DATA field deserves special attention. The guest provides these 64 bytes, and they're included in the signed report. This enables several important patterns:
Binding a TLS session to attestation:
- Guest generates an ephemeral TLS keypair
- Guest computes SHA-256 of the TLS public key (32 bytes, fits in 64)
- Guest requests attestation report with this hash as REPORT_DATA
- Guest sends both the report and the TLS public key to the verifier
- Verifier checks: does REPORT_DATA equal SHA-256 of this TLS key?
- If yes, the TLS session is provably with the attested VM
Binding a nonce for freshness:
- Verifier sends a random nonce to guest
- Guest includes nonce in REPORT_DATA
- Verifier checks the nonce matches
- Proves the report was generated recently (not replayed)
Binding a manifest of claims:
- Guest creates a JSON/CBOR document with all claims
- Guest puts SHA-384 of the manifest in REPORT_DATA
- Guest sends both report and manifest
- Verifier checks hash matches, then validates manifest contents
The constraint is size: only 64 bytes. For complex attestations, hash a larger document and include the hash.
We use REPORT_DATA to bind attestations to specific sessions and include deployment metadata. For example, we include a hash of the git commit that produced the running code, linking the cryptographic proof back to your source repository.
TCB Version and Security Patches
The report contains multiple TCB (Trusted Computing Base) version fields. The TCB version is a composite of firmware component versions:
┌──────────────────────────────────────────────────────────────────────────┐
│ TCB Version Structure (Milan/Genoa) │
│ │
│ Bits 7:0 BOOT_LOADER - PSP bootloader version │
│ Bits 15:8 TEE - PSP OS version │
│ Bits 55:48 SNP - SNP firmware version │
│ Bits 63:56 MICROCODE - CPU microcode patch level │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Why multiple TCB fields in the report?
- CURRENT_TCB: What's actually running on the machine right now
- REPORTED_TCB: What was used to derive the signing key (can be older)
- COMMITTED_TCB: The minimum version, for anti-rollback protection
- LAUNCH_TCB: What was running when this guest was created
The distinction between CURRENT_TCB and REPORTED_TCB exists because the hypervisor can control which key signs the report. After a firmware update, the hypervisor might continue signing with the old TCB's key until verifiers have fetched the new certificates. This decouples "updating firmware" from "changing what key signs reports."
The Key Hierarchy
The attestation report signature needs to chain back to something the verifier trusts. This is where AMD's key hierarchy comes in.
From Silicon to Signature
┌──────────────────────────────────────────────────────────────────────────┐
│ Key Hierarchy │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ AMD Root Key (ARK) │ │
│ │ • RSA-4096 key │ │
│ │ • Self-signed certificate │ │
│ │ • Lives in AMD's HSMs, never leaves │ │
│ │ • Different ARK per product family (Milan, Genoa, Turin) │ │
│ │ • This is the ultimate trust anchor │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ │ signs │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ AMD SEV Key (ASK) │ │
│ │ • RSA-4096 key │ │
│ │ • Intermediate CA specifically for SEV │ │
│ │ • Also product-family specific │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ │ signs │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ VCEK (Versioned Chip Endorsement Key) │ │
│ │ • ECDSA P-384 key │ │
│ │ • Unique per physical chip AND per TCB version │ │
│ │ • Derived from fused secrets that never leave the chip │ │
│ │ • This is what signs attestation reports │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ │ signs │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Attestation Report │ │
│ │ • Contains measurement, policy, REPORT_DATA, etc. │ │
│ │ • Signature verifiable with VCEK public key │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
The VCEK: Chip-Unique, Version-Specific
The VCEK is the key that signs attestation reports. It has two important properties:
Unique per chip: Each physical AMD processor has secrets fused into it at manufacturing. The VCEK is derived from these secrets. Two different chips have different VCEKs, even with identical firmware.
Unique per TCB version: The derivation also incorporates the firmware versions. The same chip with different firmware produces a different VCEK. This means you can't take a key from an old, vulnerable firmware version and use it with new firmware (or vice versa).
The derivation uses a one-way function: knowing the VCEK for one TCB version doesn't let you compute the VCEK for any other version. And only the chip's fused secrets (inaccessible to software) can compute any VCEK.
VCEK vs VLEK
There's an alternative to VCEK: the VLEK (Versioned Loaded Endorsement Key).
VCEK reveals which specific chip is running (via the CHIP_ID in the report and the certificate). Some cloud providers don't want to expose this. VLEK provides the same security guarantees but with a different trust model:
- AMD maintains VLEK seeds for enrolled cloud providers
- The provider provisions their platforms with VLEK via a secure process
- Reports can be signed with VLEK instead of VCEK
- VLEK certificates are issued by AMD but don't contain CHIP_ID
From a verifier's perspective, both VCEK and VLEK provide the same assurance: "This report was signed by genuine AMD hardware at this TCB version." The difference is operational: VCEK reveals which specific chip, VLEK doesn't.
Depending on the hardware your workload runs on, our attestations are signed by AMD (for CPU TEEs), Intel (for TDX), or NVIDIA (for GPU confidential computing). The verification flow is similar across all three—the key hierarchy and certificate chain structure are analogous.
Verifying a Report
Here's the complete flow for verifying an attestation report.
Step 1: Receive the Report
The guest sends its attestation report to the verifier, typically over TLS. The report is a binary blob (around 1KB).
Step 2: Parse and Extract Metadata
From the report, extract:
- CHIP_ID (64 bytes at offset 0x1A0)
- REPORTED_TCB (8 bytes at offset 0x180)
- CPUID family/model/stepping (bytes at 0x188-0x18A)
These tell you which certificates to fetch.
Step 3: Fetch Certificates from AMD
AMD's Key Distribution Service (KDS) at https://kdsintf.amd.com provides certificates.
Certificate chain (ARK + ASK):
GET /vcek/v1/{product}/cert_chain
product= "Milan", "Genoa", "Turin", etc. (from CPUID)- Returns PEM with both certificates
VCEK certificate:
GET /vcek/v1/{product}/{chip_id}?blSPL=X&teeSPL=X&snpSPL=X&ucodeSPL=X
chip_id= hex encoding of CHIP_ID from report- SPL values from REPORTED_TCB in report
- Returns DER-encoded certificate
Certificate revocation list:
GET /vcek/v1/{product}/crl
- Check this to ensure cert hasn't been revoked
Step 4: Verify the Certificate Chain
-
Verify ARK is self-signed
- ARK signs itself, this is the root
- You must trust AMD's ARK (compare against known-good copy)
-
Verify ASK is signed by ARK
- Check signature
- Check validity period
-
Verify VCEK is signed by ASK
- Check signature
- Check validity period
- Check VCEK's X.509 extensions contain the correct TCB values
- Check VCEK's X.509 extensions contain the correct CHIP_ID
If any step fails, reject the report.
Step 5: Verify the Report Signature
Using the VCEK public key from the certificate:
- Compute SHA-384 of report bytes 0x00 through 0x29F
- Verify the ECDSA P-384 signature at offset 0x2A0
The signature format stores R and S components as zero-extended little-endian 72-byte values (144 bytes total, padded to 512).
Step 6: Check Report Contents
The signature being valid only proves the report came from genuine AMD hardware. You still need to check that the report says what you expect:
Check MEASUREMENT:
- Does it match your expected launch digest?
- This proves the right code was loaded
Check POLICY:
- DEBUG bit should be 0 for production
- Check other policy flags match expectations
Check REPORTED_TCB:
- Is it at or above your minimum required TCB?
- Old TCB versions may have known vulnerabilities
Check REPORT_DATA:
- Does it contain the expected value?
- For TLS binding: does it match hash of the TLS public key?
- For freshness: does it contain your nonce?
Check VMPL:
- Which privilege level requested this report?
- VMPL 0xFFFFFFFF means host-requested (no guest involvement)
Computing Expected Measurements
For verification to work, you need to know what measurement to expect. This requires knowing exactly what will be loaded at launch.
The sev-snp-measure tool computes expected measurements given the launch components:
sev-snp-measure \
--mode snp \
--vcpus 4 \
--ovmf /path/to/OVMF.fd \
--kernel /path/to/vmlinuz \
--initrd /path/to/initrd.img \
--append "console=ttyS0"
This outputs the expected launch digest. Your verifier compares the report's MEASUREMENT against this value.
For this to work, your builds must be reproducible. If OVMF or the kernel aren't byte-for-byte identical to what you measured, the digest won't match.
The Identity Block (IDB)
The Identity Block is an optional mechanism for asserting control over launch validation at the firmware level.
What the IDB Does
The guest owner pre-computes what the measurement should be, signs it, and provides this signed assertion to the hypervisor during launch. The PSP verifies that the actual measurement matches the IDB before the guest can run.
Before launch:
- Guest owner builds VM image, computes expected measurement
- Creates Identity Block containing:
- Expected launch digest
- Expected policy
- FAMILY_ID, IMAGE_ID, GUEST_SVN (metadata)
- Signs IDB with their ID Key
- Provides IDB + signature + public key to hypervisor
During launch:
- PSP performs normal launch, computing measurement
- At SNP_LAUNCH_FINISH, PSP checks:
- Does computed measurement match IDB.LD?
- Does actual policy match IDB.POLICY?
- Is the signature valid?
- If any check fails: launch fails, guest never runs
- If all pass: launch succeeds, key digests appear in future reports
Why Use IDB
Without IDB, the hypervisor could launch a modified VM image, and the verifier would only detect this after the fact (when checking the attestation report). The guest would have already started running.
With IDB, the launch fails immediately if the measurement doesn't match. The guest never executes unauthorized code.
IDB also enables a signing authority model: the guest owner's key appears in attestation reports (as ID_KEY_DIGEST and optionally AUTHOR_KEY_DIGEST). Verifiers can check "this was launched with authorization from this entity" without needing to know the exact expected measurement themselves.
Attestation with SVSM
When an SVSM runs at VMPL0, attestation has additional considerations.
The Measurement Includes SVSM
If SVSM is present, it's loaded and measured during launch, before the guest OS. The final measurement reflects both SVSM and guest firmware. This means:
- Verifiers need to know both what SVSM and what guest firmware to expect
- Different SVSM versions produce different measurements
- You're trusting the SVSM as part of your TCB
VMPCK0 and Attestation Requests
The SVSM zeros VMPCK0 (the VMPL0 communication key) after startup to prevent the guest OS from impersonating VMPL0 to the PSP. This affects attestation:
- The guest OS (at VMPL2/3) uses VMPCK2/3 to request attestation
- Those reports show VMPL=2 or VMPL=3
- Only SVSM can generate VMPL=0 reports
If you need VMPL0 attestation, the SVSM must provide it. The SVSM specification includes attestation protocols for this.
SVSM Attestation Protocol
The SVSM can provide attestation reports that include a services manifest, describing what services (vTPM, etc.) are available and their properties. This is defined in SVSM Protocol 1:
SVSM_ATTEST_SERVICES: Get report with manifest of all servicesSVSM_ATTEST_SINGLE_SERVICE: Get report for one specific service
This allows a verifier to check "not only is the SVSM the expected version, but it's offering the expected services."
What Attestation Does and Doesn't Prove
Attestation Proves
- The report was generated by genuine AMD hardware with SEV-SNP
- The hardware has a specific TCB version (firmware/microcode)
- The guest was launched with specific measured code
- The guest provided specific REPORT_DATA at request time
- (If IDB used) A specific entity authorized this launch
Attestation Does NOT Prove
- The guest code is correct or secure (only that it matches the measurement)
- The guest hasn't been compromised since launch (only initial state)
- The hypervisor is behaving correctly (only that SNP is active)
- Side channels aren't leaking information
- The code does what you think it does (measurement is identity, not behavior)
For closed-source code, attestation proves "this binary is running" but not "this binary does what the vendor claims." You still need to trust the code author.
Security Considerations
Verifier Responsibilities
Pin the ARK: Your verifier should have a known-good copy of AMD's ARK, not fetch it from KDS on every verification. An attacker who compromises the network path could serve a fake ARK.
Check the CRL: Certificates can be revoked. Check AMD's CRL to ensure the VCEK hasn't been revoked.
Enforce TCB minimums: Old firmware versions may have known vulnerabilities. Don't accept reports signed with TCB below your threshold.
Verify REPORT_DATA: If you're using REPORT_DATA for key binding or nonces, actually check it. A report with valid signature but wrong REPORT_DATA might be a replay or misdirection.
What Attacks Remain Possible
Denial of service: The hypervisor can refuse to let the guest request attestation, or refuse to deliver reports to verifiers. Availability is out of scope.
Rollback within committed TCB: The COMMITTED_TCB sets a floor, but if an older (but still above COMMITTED) TCB has vulnerabilities, the hypervisor might run that version.
Pre-attestation attacks: If the guest can be compromised before it requests attestation, the attacker could make the guest lie in REPORT_DATA (put their key instead of the legitimate key).
Post-attestation compromise: Attestation proves initial state. If the guest is exploited after boot, attestation won't detect it.
Practical Verification Summary
Verification Checklist:
- Received attestation report from guest
- Extracted CHIP_ID, REPORTED_TCB, CPUID info
- Fetched certificate chain from AMD KDS
- Verified ARK is self-signed (compare against pinned copy)
- Verified ASK is signed by ARK
- Verified VCEK is signed by ASK
- Verified VCEK extensions match report (TCB, CHIP_ID)
- Checked CRL to ensure VCEK not revoked
- Verified report signature with VCEK public key
- Checked MEASUREMENT matches expected launch digest
- Checked POLICY flags are acceptable (DEBUG=0, etc.)
- Checked REPORTED_TCB meets minimum requirements
- Checked REPORT_DATA contains expected value
- Checked VMPL is expected level
All checks pass → trust that this is the expected code on genuine AMD hardware.
We provide SDKs that handle this verification flow. You don't need to implement certificate fetching, chain validation, or signature verification yourself—though you can if you want to verify independently.
Attestation on Lunal
We enable attestation on every request-response flow. How you integrate is up to you:
- Session-based: Verify attestation once when establishing a connection, then trust subsequent requests on that session
- Per-request: Include attestation with each request for maximum assurance
- On-demand: Request attestation when you need it, skip it when you don't
What our attestations prove:
- The code running matches a specific measurement (tied to your git commit)
- The hardware is genuine AMD/Intel/NVIDIA TEE hardware
- The TCB version meets your security requirements
- The workload is running in a confidential environment
Our server and client SDKs let you:
- Generate attestation reports with custom REPORT_DATA
- Verify attestation reports and certificate chains
- Bind TLS sessions to attestation for end-to-end verification
Manual verification is always possible. Our attestations use standard formats—you can verify them with AMD's tools, OpenSSL, or your own implementation.
Further Reading
- Document 4: Privilege & Communication for VMPLs and SVSM details
- Document 6: Measurement Strategies for extending trust beyond launch