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:

What's In the Measurement

In a typical SNP launch with direct boot (passing kernel and initrd to QEMU):

Measured components:

Not measured:

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 TypeWhat Gets MeasuredNotes
NORMALSHA-384 of page contentsStandard code/data
VMSASHA-384 of initial CPU stateWhere execution starts, initial register values
ZEROMeasured as all zerosContent is encrypted zeros, but measures as zero
UNMEASUREDGPA recorded, content not hashedFor pages the guest will initialize itself
SECRETSGPA recorded, content not hashedPSP fills in keys after measurement
CPUIDGPA recorded, content not hashedHypervisor-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:

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:

  1. Guest generates an ephemeral TLS keypair
  2. Guest computes SHA-256 of the TLS public key (32 bytes, fits in 64)
  3. Guest requests attestation report with this hash as REPORT_DATA
  4. Guest sends both the report and the TLS public key to the verifier
  5. Verifier checks: does REPORT_DATA equal SHA-256 of this TLS key?
  6. If yes, the TLS session is provably with the attested VM

Binding a nonce for freshness:

  1. Verifier sends a random nonce to guest
  2. Guest includes nonce in REPORT_DATA
  3. Verifier checks the nonce matches
  4. Proves the report was generated recently (not replayed)

Binding a manifest of claims:

  1. Guest creates a JSON/CBOR document with all claims
  2. Guest puts SHA-384 of the manifest in REPORT_DATA
  3. Guest sends both report and manifest
  4. 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?

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:

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:

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

VCEK certificate:

GET /vcek/v1/{product}/{chip_id}?blSPL=X&teeSPL=X&snpSPL=X&ucodeSPL=X

Certificate revocation list:

GET /vcek/v1/{product}/crl

Step 4: Verify the Certificate Chain

  1. Verify ARK is self-signed

    • ARK signs itself, this is the root
    • You must trust AMD's ARK (compare against known-good copy)
  2. Verify ASK is signed by ARK

    • Check signature
    • Check validity period
  3. 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:

  1. Compute SHA-384 of report bytes 0x00 through 0x29F
  2. 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:

Check POLICY:

Check REPORTED_TCB:

Check REPORT_DATA:

Check VMPL:

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:

  1. Guest owner builds VM image, computes expected measurement
  2. Creates Identity Block containing:
    • Expected launch digest
    • Expected policy
    • FAMILY_ID, IMAGE_ID, GUEST_SVN (metadata)
  3. Signs IDB with their ID Key
  4. Provides IDB + signature + public key to hypervisor

During launch:

  1. PSP performs normal launch, computing measurement
  2. At SNP_LAUNCH_FINISH, PSP checks:
    • Does computed measurement match IDB.LD?
    • Does actual policy match IDB.POLICY?
    • Is the signature valid?
  3. If any check fails: launch fails, guest never runs
  4. 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:

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:

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:

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

Attestation Does NOT Prove

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:

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:

What our attestations prove:

Our server and client SDKs let you:

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