EC2 AMD SEV-SNP 使用 Ubuntu 24.04 构建可证明的 AMI

本文介绍在 Amazon EC2 共享租户(shared tenancy)上,以 Ubuntu 24.04 为操作系统构建可证明的 AMI,并用 AMD 原生工具链完成 AMD SEV-SNP 证明的完整过程。产出物包括:从源代码复现构建并与官方发布逐字节一致的 OVMF 固件、按 vCPU 数量离线计算的参考启动度量、预装 `snpguest` 证明工具的 Ubuntu 24.04 AMI,以及一套由独立验证方执行的验证脚本。

本文介绍在 Amazon EC2 共享租户(shared tenancy)上,以 Ubuntu 24.04 为操作系统构建可证明的 AMI,并用 AMD 原生工具链完成 AMD SEV-SNP 证明的完整过程。产出物包括:从源代码复现构建并与官方发布逐字节一致的 OVMF 固件、按 vCPU 数量离线计算的参考启动度量、预装 snpguest 证明工具的 Ubuntu 24.04 AMI,以及一套由独立验证方执行的验证脚本。

一、背景

1、为什么需要远程证明

AMD SEV-SNP,全称是 AMD Secure Encrypted Virtualization-Secure Nested Paging,是 AMD EPYC 处理器提供的机密计算(Confidential Computing)特性。开启后,虚拟机内存由 AMD 安全处理器(AMD Secure Processor)管理的密钥加密,并由 RMP(Reverse Map Table,反向映射表)在硬件层面阻止 Hypervisor 读取、重映射或篡改客户机内存。SEV-SNP 的开启方法、支持机型、费用与性能影响,见同目录下的《EC2 AMD SEV-SNP 开启方法与性能测试》。

开启 SEV-SNP 只解决了“内存不被主机软件访问”的问题,还没有解决“如何让别人相信这一点”的问题。设想一个典型场景:数据或密钥的持有方需要把敏感数据交给 EC2 实例处理,它在交付前必须确认三件事:实例确实开启了 SEV-SNP 并运行在真实的 AMD 硬件上;实例启动时载入的固件是预期的版本,没有被替换;回应自己的正是此刻运行的这台实例,而不是攻击者重放的旧数据。

远程证明(Remote Attestation)用硬件签名解决这一矛盾。实例可以向 AMD 安全处理器请求一份证明报告(attestation report),报告中包含启动度量(launch measurement)以及由请求方指定的 64 字节数据,并由 AMD 签发的密钥签名。共享租户上的签名密钥是 VLEK(Versioned Loaded Endorsement Key,AMD 为 AWS 签发的版本化签名密钥),其证书可以追溯到 AMD 根证书。验证方只需要信任 AMD,就能独立判断报告的真伪。

AWS 官方文档给出了基于 snpguest 的证明步骤,但存在两点不足:一是验证方与被证明方是同一台实例,没有拆分开构建和生产两套环境;二是只验证了签名,没有把报告中的启动度量与参考值比对,也没有使用 nonce 防止重放。本文在此基础上做了以下改进:

  • 引入独立的验证方:由一台开发机编译工具、计算参考值,并在证明阶段负责全部验证,被证明实例只负责生成报告。
  • 从源代码推导参考值:用 Nix 复现构建 AWS 公开的 OVMF 固件,再离线计算启动度量,不需要信任 AWS 提供的度量值。
  • 使用 nonce:验证方每次生成随机数,要求实例写入报告,以此确认报告为本次请求新生成。
  • 把证明工具固化进 AMI:被证明实例不需要安装编译工具链,启动后即可响应证明请求。

下面说明启动度量的覆盖范围。

2、启动度量的覆盖范围与证明思路

SEV-SNP 的启动度量对象是 Hypervisor 在实例启动前写入客户机内存的初始内容,以及每个 vCPU 的初始寄存器状态,即 VMSA(Virtual Machine Save Area,虚拟机保存区)。在 EC2 上,写入初始内存的只有 AWS 提供的 OVMF(Open Virtual Machine Firmware)固件。AMI 中的引导程序、内核和根文件系统在 OVMF 运行之后才从 EBS 卷读入,不进入启动度量。因此启动度量只取决于 OVMF 固件版本和 vCPU 数量,与 AMI 无关,AWS 更新 OVMF 固件后启动度量随之变化。

AWS 在 GitHub 的 aws/uefi 仓库公开了 OVMF 固件的源代码补丁,并用 Nix 实现可复现构建(Reproducible Build)。验证方可以自行构建出与官方发布逐字节一致的固件,再用 virtee 社区的 sev-snp-measure 工具计算参考启动度量,不需要信任 AWS 提供的度量值。本文所说的“可证明的 AMI”由两部分配合:开发机上复现构建的固件与参考启动度量作为证明的基准,预装 snpguest 的 Ubuntu 24.04 AMI 作为产生证明报告的载体。

整体流程如下。开发机在构建阶段制作镜像并计算参考值,在证明阶段兼任验证方:

开发机(Ubuntu 24.04,兼任验证方)              被证明实例(Ubuntu 24.04,开启 SEV-SNP)
--------------------------------------------    ------------------------------------------
1. 编译 snpguest
2. Nix 复现构建 OVMF,与官方发布比对 SHA256
3. sev-snp-measure 计算参考启动度量
4. 把 snpguest 写入镜像并注册 AMI   ---------->  以该 AMI 启动(第三章)
5. 生成 nonce                       ---------->  snpguest report 请求证明报告
6. 验证证书链、签名、度量与 nonce   <----------  返回 report.bin 与 vlek.pem

启动度量不覆盖操作系统,这是 SEV-SNP 与 NitroTPM 的主要区别。下面对二者进行对比。

3、AMD SEV-SNP 与 NitroTPM 对比

AMD SEV-SNP 的启动度量在实例启动时一次性确定,证明报告中没有运行期可以继续扩展的度量寄存器。EC2 由 OVMF 从 EBS 卷读取引导程序,不提供把内核哈希写入固件的直接启动方式,因此也就无法证明操作系统。

作为与 AMD SEV-SNP 的对照,Intel TDX 提供 RTMR(Runtime Measurement Register,运行时度量寄存器),固件可以把引导程序和内核的哈希扩展进去。

之前介绍过的 NitroTPM 的 EC2 Attestation 可以覆盖操作系统这一层。它把 UKI(Unified Kernel Image,统一内核镜像)度量进 PCR4(Platform Configuration Register,平台配置寄存器),dm-verity 根哈希位于 UKI 内,PCR4 因而间接代表整个根文件系统。NitroTPM 由 Nitro 系统实现,证明文档由 AWS Nitro Attestation PKI 签名,可信根是 AWS,而不是像 AMD SEV-SNP 的可信根是 AMD。

二者对比如下:

对比 EC2 上的 AMD SEV-SNP NitroTPM
度量范围 OVMF 固件与 vCPU 初始状态(VMSA) 固件之后的启动链,包括 UKI、内核与 initrd,并经 dm-verity 间接覆盖根文件系统
签名方与可信根 VLEK,追溯到 AMD 根证书,需要信任 AMD AWS Nitro Attestation PKI,需要信任 AWS
能否证明操作系统未被篡改 不能 能
能否证明内存不被主机软件读取 能 不能
验证方 用户自建,AWS 不提供托管的验证服务 AWS KMS 代为验证
签名验证 验证方从 AMD KDS 获取证书,自行验证 VLEK 证书链与报告签名 KMS 验证 AWS 签名的证明文档
参考值存放 验证方自行计算,按固件版本和 vCPU 数量维护启动度量清单 写入 KMS 密钥策略的 PCR 条件键
防重放 验证方自行生成并校验 nonce KMS 用证明文档中的实例公钥加密响应,只有该实例的私钥能解密
验证通过后的动作 验证方自行实现发放密钥或建立加密通道的逻辑 KMS 直接返回以实例公钥加密的数据密钥或解密结果

两种方案在验证方上的差异较大。对于 NitroTPM 的 EC2 Attestation,AWS KMS API 参考文档明确说明 Recipient 参数支持 AWS Nitro Enclaves 与 NitroTPM 的证明文档。密钥策略中按度量值放行的条件键是 kms:RecipientAttestation:NitroTPMPCR<PCR_ID>。AWS 在 AMD SEV-SNP 证明文档中给出了用 snpguest 获取和验证报告的步骤,但证明报告不能提交给 KMS,验证方须由用户自建。本文第三章的 dev_verify.sh 是自建验证方的最小实现。生产环境中应将其建设为独立的验证服务,部署在被证明实例的管理者无法控制的环境中,负责 nonce 下发、报告验证、参考值清单维护以及验证通过后的密钥发放。

注意:KMS 托管验证只适用于以 KMS 作为验证方的场景。根据 EC2 Instance Attestation 文档,如果 NitroTPM 实例需要向第三方服务证明自身状态,同样需要自建接收、解析和验证证明文档的机制。

由此可见,NitroTPM 能证明操作系统层的完整性,并可直接使用 KMS 作为验证方,EC2 上的 SEV-SNP 不能证明操作系统,且须自建验证方。但不能据此认为 NitroTPM 的安全性更高,二者回答的是不同的问题。请看下一章节讲解。

二、在开发机上构建可证明的 AMI

1、实验环境

AMD SEV-SNP 的共享租户方式只支持美国东部(俄亥俄,us-east-2)和欧洲(爱尔兰,eu-west-1)两个区域,东京等其他区域只能通过 Dedicated Host 使用。本文为控制成本使用共享租户,测试区域为俄亥俄。实验使用两台 Ubuntu 24.04 实例,配置如下:

项目 开发机 sev-snp-dev 被证明实例 sev-snp-attest
用途 编译证明工具、复现构建固件、计算参考启动度量、制作 AMI,并兼任验证方 以可证明的 AMI 启动,生成证明报告
实例 ID <dev-instance-id> <attest-instance-id>
可用区 us-east-2a(use2-az1) us-east-2a(use2-az1)
机型 c6a.2xlarge(8 vCPU,16 GiB) c6a.xlarge(4 vCPU,8 GiB)
AMI Canonical 官方 Ubuntu 24.04(ami-0fc53b217ec1ad816) 本章自建的 <attest-ami-id>
SEV-SNP 未开启 开启

两台实例的共同配置如下:

  • 区域与网络:美国东部(俄亥俄,us-east-2),默认 VPC 的公有子网。
  • 操作系统:Ubuntu 24.04.5 LTS,内核 7.0.0-1014-aws,启动模式 UEFI。
  • 证明工具:snpguest v0.11.0、sev-snp-measure 0.0.13,固件版本为 aws/uefi 20260814。
  • 安全组:不添加任何入站规则,保留新建安全组默认的“允许全部出站流量”规则。出站访问供 SSM Agent 连接 Systems Manager 服务端点,以及开发机从 GitHub、crates.io、Nix 二进制缓存下载源代码和依赖、从 AMD KDS 下载证书。本文以 <security-group-id> 表示该安全组的 ID,与性能测试文档中的实例共用同一个安全组。
  • 实例角色:附加 AmazonSSMManagedInstanceCore 托管策略的实例配置文件,本文使用 Systems Manager 快速设置创建的 AmazonSSMRoleForInstancesQuickSetup。不需要创建密钥对,也不需要开放 22 端口。

脚本通过 SSM Run Command 下发。由于脚本含引号、管道等字符,直接用 --parameters "commands=..." 简写形式容易被 AWS CLI 按逗号拆分而报错,本文使用 remote-attestation-script/ssm_send.py 把整个脚本作为一个字符串写入请求 JSON,再以 --cli-input-json 提交,返回 SSM 命令 ID;随后用 remote-attestation-script/ssm_get.sh 逐字段读取执行结果。本文用到的脚本均保存在该目录下,python3 ssm_send.py 与 zsh ssm_get.sh 命令也在该目录下执行。读取结果的命令如下:

cd remote-attestation-script
zsh ssm_get.sh us-east-2 <command-id> <instance-id>

下面介绍构建所用的 Nix 工具。

2、Nix 构建工具简介

Nix 是由 NixOS 社区维护的函数式包管理器和构建工具。在 Ubuntu 上直接执行 make 时,构建产物取决于宿主机已安装的编译器、汇编器和脚本解释器版本,不同机器的构建结果往往不一致。Nix 用一份 Nix 表达式(通常为 default.nix)声明构建所需的全部输入,包括源代码、依赖库以及编译器本身的确切版本,并通过以下两项机制保证构建输入一致:

  • 每个构建输入与产物都存放在 /nix/store/<哈希>-<名称> 路径下,哈希由全部输入计算得出,任何输入发生变化都会得到不同的路径。
  • 构建在隔离的沙箱中执行,只能访问已声明的依赖,不能使用宿主机 /usr 下的工具和库。

Nix 保证的是构建输入一致。要得到逐字节一致的产物,项目还需要自行消除时间戳、文件顺序等不确定因素,aws/uefi 仓库已完成这部分工作。本章在开发机上安装 Nix,用它复现构建 EC2 的 OVMF 固件。下面启动开发机。

3、启动 Ubuntu 24.04 开发机

开发机只负责编译、构建和验证,不需要开启 SEV-SNP,也不必使用 SEV-SNP 机型,本文选用 c6a.2xlarge。首先获取 Canonical 发布的最新 Ubuntu 24.04 AMI,并查看其属性。执行如下命令:

AMI=$(aws ssm get-parameter --region us-east-2 \
  --name /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id \
  --query Parameter.Value --output text)
aws ec2 describe-images --region us-east-2 --image-ids $AMI \
  --query 'Images[0].[ImageId,Name,BootMode,RootDeviceName]' --output text

返回结果如下:

ami-0fc53b217ec1ad816	ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-20261023	uefi-preferred	/dev/sda1

Ubuntu AMI 的根设备名为 /dev/sda1,--block-device-mappings 中必须使用这个名称。Nix 构建会向 /nix/store 写入大量依赖,根卷扩大到 40 GiB。安全组与实例配置文件使用本章第 1 节所述的资源。执行如下命令:

aws ec2 run-instances --region us-east-2 \
  --image-id ami-0fc53b217ec1ad816 \
  --instance-type c6a.2xlarge \
  --subnet-id <subnet-id-of-us-east-2a> \
  --security-group-ids <security-group-id> \
  --iam-instance-profile Name=AmazonSSMRoleForInstancesQuickSetup \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=40,VolumeType=gp3}' \
  --metadata-options HttpTokens=required \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sev-snp-dev},{Key=Project,Value=AMD-SEV-SNP}]' \
  --query 'Instances[0].[InstanceId,Placement.AvailabilityZone,InstanceType]' --output json

返回结果如下:

[
    "<dev-instance-id>",
    "us-east-2a",
    "c6a.2xlarge"
]

Ubuntu AMI 以 snap 包预装了 SSM Agent,实例启动约一分钟后即可确认其在线状态。执行如下命令:

aws ssm describe-instance-information --region us-east-2 \
  --filters Key=InstanceIds,Values=<dev-instance-id> \
  --query 'InstanceInformationList[].[PingStatus,PlatformName,PlatformVersion,AgentVersion]' --output text

返回结果如下:

Online	Ubuntu	24.04	3.3.4793.0

后续操作均通过 remote-attestation-script/ssm_send.py 下发脚本、通过 remote-attestation-script/ssm_get.sh 读取结果。Ubuntu 的 /bin/sh 是 dash 而不是 bash,本文脚本均以 #!/bin/bash 开头,并实测确认 SSM Run Command 按 bash 执行。接下来编译证明工具。

4、编译 snpguest

snpguest 是 virtee 社区维护的 SEV-SNP 客户机工具,用于请求证明报告、获取证书和验证报告。AWS 文档中的步骤直接克隆默认分支,为了让构建结果可复现,本文固定使用 v0.11.0 版本。编译脚本 remote-attestation-script/dev_setup.sh 内容如下:

#!/bin/bash
# 开发机(Ubuntu 24.04):安装编译依赖与 Rust,编译 snpguest
export HOME=/root
export DEBIAN_FRONTEND=noninteractive
set -euo pipefail

apt-get update -q
apt-get install -y -q build-essential git pkg-config libssl-dev python3-venv xxd > /dev/null
lsb_release -ds
uname -r

# Ubuntu 24.04 软件源中的 rustc 版本较旧,使用 rustup 安装稳定版
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal
. "$HOME/.cargo/env"
rustc --version
cargo --version

mkdir -p /opt/snp && cd /opt/snp
rm -rf snpguest
git clone -q --branch v0.11.0 https://github.com/virtee/snpguest.git
cd snpguest
git rev-parse HEAD
time cargo build -r -q
ls -l target/release/snpguest
ldd target/release/snpguest
./target/release/snpguest --version
sha256sum target/release/snpguest

执行如下命令下发脚本:

python3 ssm_send.py us-east-2 dev_setup.sh <dev-instance-id>

约两分钟后读取结果。去掉 apt-get 与 rustup 的下载进度信息后,返回结果如下:

Ubuntu 24.04.5 LTS
7.0.0-1014-aws
rustc 1.99.1 (58cf8f9c3 2026-10-13)
cargo 1.99.1 (5f94df478 2026-08-27)
cfedc83d25c2d8387a12276b6b0808c8941ccb98
-rwxr-xr-x 2 root root 13874112 Nov  3 02:30 target/release/snpguest
	linux-vdso.so.1 (0x000076128019d000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x0000761280161000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x000076127f517000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000076127f200000)
	/lib64/ld-linux-x86-64.so.2 (0x000076128019f000)
snpguest 0.11.0
ef0cf0fd09069061b6c6ae80cecd13001b5b8bcdc66b704f0edf47111e64df6b  target/release/snpguest
real	1m6.242s
user	6m37.912s
sys	0m51.658s

编译耗时约 1 分钟。ldd 的结果说明 snpguest 只依赖 glibc 的基础动态库,不依赖 OpenSSL 等额外的系统库,因此可以直接复制到未做任何改动的 Ubuntu 24.04 镜像中运行,不需要在镜像里安装软件包。下面构建固件。

5、复现构建 AWS 的 OVMF 固件

aws/uefi 仓库在 edk2-stable202211 版本的 EDK II 源代码上叠加 EC2 专用补丁,并在 default.nix 中锁定 nixpkgs 22.11 和 EDK II 源代码的哈希值,保证任何环境下的构建输入一致。截至本文测试时,最新发布版本为 20260814,发布说明给出的固件 SHA256 为 67aaae01a6f4245cc071e008859b00ea2b5c05824362c41d165c3b4751372bc9。构建脚本 remote-attestation-script/dev_ovmf.sh 内容如下:

#!/bin/bash
# 开发机(Ubuntu 24.04):安装 Nix,复现构建 AWS 发布的 OVMF 固件,并与官方发布的二进制比对
export HOME=/root
set -euo pipefail

# 1. 以多用户模式安装 Nix(非交互)
if [ ! -x /nix/var/nix/profiles/default/bin/nix-build ]; then
  curl --proto '=https' --tlsv1.2 -sSfL https://nixos.org/nix/install -o /tmp/nix-install
  sh /tmp/nix-install --daemon --yes > /tmp/nix-install.log 2>&1
fi
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
nix-build --version

# 2. 获取 aws/uefi 指定发布版本的源码并构建
cd /opt/snp
rm -rf uefi
git clone -q --branch 20260814 https://github.com/aws/uefi.git
cd uefi
git rev-parse HEAD
time nix-build --pure > /tmp/nix-build.log 2>&1 || { tail -40 /tmp/nix-build.log; exit 1; }
ls -lL result/ovmf_img.fd

# 3. 下载官方发布的二进制,比对 SHA256
curl --proto '=https' --tlsv1.2 -sSfL -o /opt/snp/ovmf_img.release.fd \
  https://github.com/aws/uefi/releases/download/20260814/ovmf_img.fd
sha256sum result/ovmf_img.fd /opt/snp/ovmf_img.release.fd

SSM Run Command 以 root 身份执行,Nix 安装程序的 --daemon --yes 参数以多用户模式非交互安装,安装完成后需要加载 nix-daemon.sh 才能在当前脚本中使用 nix-build。执行如下命令下发脚本:

python3 ssm_send.py us-east-2 dev_ovmf.sh <dev-instance-id>

返回结果如下:

nix-build (Nix) 2.35.4
b79f7b9c5b2426795f8740419aeacbcf5ac7608b
-r--r--r-- 1 root root 4194304 Jan  1  1970 result/ovmf_img.fd
67aaae01a6f4245cc071e008859b00ea2b5c05824362c41d165c3b4751372bc9  result/ovmf_img.fd
67aaae01a6f4245cc071e008859b00ea2b5c05824362c41d165c3b4751372bc9  /opt/snp/ovmf_img.release.fd
real	2m16.058s
user	6m45.849s
sys	1m25.750s

本地构建的 ovmf_img.fd 与官方发布的二进制 SHA256 完全一致,也与发布说明中的值一致,说明 AWS 发布的固件确实由公开的源代码构建而来。文件时间显示为 1970 年 1 月 1 日,是因为 Nix 把存储中所有文件的修改时间统一置为 0,这是可复现构建的一部分。

注意:这里只证明了“官方发布的固件与源代码一致”,EC2 实际运行的是否为这个版本,要到第三章用证明报告中的启动度量比对后才能确认。如果比对不一致,应依次检出 aws/uefi 的更早发布标签(如 20240809)重新构建和计算。下面计算参考启动度量。

6、计算参考启动度量

启动度量由两类输入决定:一是 OVMF 固件镜像,二是每个 vCPU 的初始寄存器状态(VMSA)。固件版本确定后,唯一的变量是 vCPU 数量,每增加一个 vCPU,度量中就多一个 VMSA 页,结果随之改变。验证方事先并不知道被证明实例使用哪种规格,因此需要按 vCPU 数量分别计算并保存参考值。本节计算 2、4、8 个 vCPU 三个值,其用途如下:

  • vcpus=4:用于第三章的正例,被证明实例 c6a.xlarge 有 4 个 vCPU。
  • vcpus=8:用于第三章第 6 节的反例,确认使用错误的参考值时验证会被拒绝。
  • vcpus=2:本文未使用,只用于展示启动度量随 vCPU 数量变化。

首先安装 Python 包。由于 Ubuntu 24.04 的系统 Python 受到保护,直接执行 pip install 会返回 externally-managed-environment 错误而无法安装,因此需要在 venv 虚拟环境中安装。计算脚本 remote-attestation-script/dev_measure.sh 内容如下:

#!/bin/bash
# 开发机(Ubuntu 24.04):用 sev-snp-measure 离线计算参考启动度量
export HOME=/root
set -euo pipefail

python3 -m venv /opt/snp/venv
/opt/snp/venv/bin/pip install -q sev-snp-measure==0.0.13
/opt/snp/venv/bin/sev-snp-measure --version

# 启动度量与 vCPU 数量相关,按 c6a.large / xlarge / 2xlarge 的 vCPU 数分别计算
for n in 2 4 8; do
  m=$(/opt/snp/venv/bin/sev-snp-measure --mode snp --vcpus=$n --vmm-type=ec2 \
      --ovmf=/opt/snp/uefi/result/ovmf_img.fd)
  echo "vcpus=$n measurement=$m"
done

执行如下命令下发脚本:

python3 ssm_send.py us-east-2 dev_measure.sh <dev-instance-id>

返回结果如下:

sev-snp-measure 0.0.13
vcpus=2 measurement=7a89cceaaba0bbdddc4f775acca3ef15d1f9bf8abccdc6115cfad7fb8eae113df3ee0bb50efa34db9c53ae065b1dc766
vcpus=4 measurement=1d8ee9bae96e2b88e7dab179eca3229827f658cb04fbe814f59b5f4312edcb07a7e9d98141b152db1063379046c1bf5b
vcpus=8 measurement=c7b48b97c5fa830743ac09d1076097c1790d7818daedfd7f915e6c50b42f958a881539b22a5cdfbea2b301ef7259e91a

计算参数中只有 OVMF 固件和 vCPU 数量,没有机型系列和内存大小,因此同一尺寸的 C6a、M6a、R6a 会得到相同的启动度量。这一结果符合 SEV-SNP 的度量机制,并非计算错误,原因如下:

  • 同尺寸的 C6a、M6a、R6a 只有内存容量不同,例如 xlarge 分别为 8、16、32 GiB。启动度量只覆盖 Hypervisor 在启动前写入并度量的页,即 OVMF 镜像、固件元数据页和各 vCPU 的 VMSA,客户机内存的大小不在度量范围内。
  • 三个系列均使用第三代 AMD EPYC(Milan,型号 7R13)处理器,VMSA 中记录的 CPU 型号信息也相同。

因此,只要 OVMF 固件版本和 vCPU 数量相同,启动度量就相同。整理为参考值表如下:

机型 vCPU 数 参考启动度量(OVMF 20260814) 是否实测核对
c6a.large、m6a.large、r6a.large 2 7a89ccea…5b1dc766 未实测,按原理推导
c6a.xlarge、m6a.xlarge、r6a.xlarge 4 1d8ee9ba…46c1bf5b c6a.xlarge 已用实测报告核对,m6a、r6a 按原理推导
c6a.2xlarge、m6a.2xlarge、r6a.2xlarge 8 c7b48b97…7259e91a 未实测,按原理推导

第三章的被证明实例使用 c6a.xlarge,对应 4 个 vCPU 的参考值。参考值必须由验证方自行计算并保存,不能采信被证明实例自己声称的度量值。生产环境中,验证方应为每个 aws/uefi 发布版本和每种 vCPU 数量维护一份参考值清单,AWS 发布新固件后及时补充。下面制作镜像。

7、将 snpguest 写入 Ubuntu 镜像并注册 AMI

制作镜像有两种做法。一种是在开发机上直接执行 create-image,但开发机中包含 Rust 工具链、Nix 存储和验证方的数据,不适合作为被证明实例的镜像。本文采用另一种做法:用 Canonical 官方 AMI 的根快照创建一块新卷,挂载到开发机上,只写入 snpguest 和证明脚本,再对这块卷创建快照并注册 AMI。AWS 不允许直接用其他账号拥有的快照注册 AMI,因此必须先创建属于本账号的卷和快照。

首先查看官方 AMI 的根快照与网络、元数据相关属性,注册新 AMI 时需要保持一致。执行如下命令:

aws ec2 describe-images --region us-east-2 --image-ids ami-0fc53b217ec1ad816 \
  --query 'Images[0].[BlockDeviceMappings[0].Ebs.SnapshotId,EnaSupport,SriovNetSupport,ImdsSupport]' --output text

返回结果如下:

snap-09432d719e570b744	True	simple	v2.0

用该快照在开发机所在的可用区创建卷,并挂载到开发机。执行如下命令:

aws ec2 create-volume --region us-east-2 \
  --availability-zone us-east-2a \
  --snapshot-id snap-09432d719e570b744 --volume-type gp3 \
  --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=sev-snp-ubuntu-image},{Key=Project,Value=AMD-SEV-SNP}]' \
  --query VolumeId --output text

返回结果如下:

<image-volume-id>

执行如下命令:

aws ec2 wait volume-available --region us-east-2 --volume-ids <image-volume-id>
aws ec2 attach-volume --region us-east-2 \
  --volume-id <image-volume-id> --instance-id <dev-instance-id> --device /dev/sdf \
  --query State --output text

返回结果如下:

attaching

Nitro 实例上 EBS 卷以 NVMe 设备呈现,设备名不一定是 /dev/sdf,脚本通过 /dev/disk/by-id/ 下带卷 ID 的路径定位这块卷。写入脚本 remote-attestation-script/dev_bake.sh 内容如下,其中 snp-attest-report 是写入镜像的证明脚本:

#!/bin/bash
# 开发机(Ubuntu 24.04):把 snpguest 与证明脚本写入由 Ubuntu 官方快照创建的 EBS 卷
export HOME=/root
set -euo pipefail

VOL_ID=vol-xxxxxxxxxxxxxxxxx  # 替换为 create-volume 返回的卷 ID
DEV=/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_${VOL_ID/-/}
lsblk -o NAME,SIZE,FSTYPE,LABEL,PARTLABEL "$(readlink -f $DEV)"

# 根分区为第 1 分区。该卷的文件系统标签与开发机自身根卷相同,因此按设备路径挂载,不按标签挂载
mkdir -p /mnt/img
mount "${DEV}-part1" /mnt/img
grep -E 'PRETTY_NAME' /mnt/img/etc/os-release

install -m 0755 /opt/snp/snpguest/target/release/snpguest /mnt/img/usr/local/bin/snpguest
cat > /mnt/img/usr/local/bin/snp-attest-report <<'EOF'
#!/bin/bash
# 被证明实例:用验证方提供的 nonce 请求 SEV-SNP 证明报告,并输出报告与 VLEK 证书
# 用法:snp-attest-report <128 个十六进制字符的 nonce>
set -euo pipefail

NONCE=${1:-}
if [[ ! $NONCE =~ ^[0-9a-fA-F]{128}$ ]]; then
  echo "用法:snp-attest-report <128 个十六进制字符(64 字节)的 nonce>" >&2
  exit 2
fi

WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
cd "$WORK"

# nonce 写入 64 字节的请求数据,处理器会将其原样放入报告的 REPORT_DATA 字段
python3 -c 'import sys; sys.stdout.buffer.write(bytes.fromhex(sys.argv[1]))' "$NONCE" > request.bin
/usr/local/bin/snpguest report report.bin request.bin
# 共享租户由主机内存提供 VLEK 证书
/usr/local/bin/snpguest certificates PEM ./certs > /dev/null

echo "--- report.bin (base64)"
base64 -w0 report.bin
echo
echo "--- vlek.pem"
cat certs/vlek.pem
EOF
chmod 0755 /mnt/img/usr/local/bin/snp-attest-report

ls -l /mnt/img/usr/local/bin/
sha256sum /mnt/img/usr/local/bin/snpguest
umount /mnt/img
sync
echo "unmounted"

snp-attest-report 把验证方给出的 nonce 写成 64 字节的请求数据,调用 snpguest report 请求证明报告,再调用 snpguest certificates 从主机内存取出 VLEK 证书,最后把二者以文本形式输出,便于经 SSM 等通道回传给验证方。十六进制转换使用镜像自带的 python3,不依赖 xxd 等额外软件包。执行如下命令下发脚本:

python3 ssm_send.py us-east-2 dev_bake.sh <dev-instance-id>

返回结果如下:

NAME          SIZE FSTYPE LABEL           PARTLABEL
nvme1n1         8G                        
├─nvme1n1p1     7G ext4   cloudimg-rootfs 
├─nvme1n1p14    4M                        
├─nvme1n1p15  106M vfat   UEFI            
└─nvme1n1p16  913M ext4   BOOT            
PRETTY_NAME="Ubuntu 24.04.5 LTS"
total 13544
-rwxr-xr-x 1 root root      889 Nov  3 02:32 snp-attest-report
-rwxr-xr-x 1 root root 13874112 Nov  3 02:32 snpguest
ef0cf0fd09069061b6c6ae80cecd13001b5b8bcdc66b704f0edf47111e64df6b  /mnt/img/usr/local/bin/snpguest
unmounted

镜像中 snpguest 的 SHA256 与第 4 节编译产物一致。

注意:这块卷与开发机根卷来自同一个快照,文件系统标签、UUID 和分区 PARTUUID 完全相同。卷挂载在开发机上期间不要重启开发机,否则引导程序可能从这块卷启动。写入完成后应立即卸离。

卸离卷并创建快照。执行如下命令:

aws ec2 detach-volume --region us-east-2 --volume-id <image-volume-id> --query State --output text
aws ec2 wait volume-available --region us-east-2 --volume-ids <image-volume-id>
aws ec2 create-snapshot --region us-east-2 --volume-id <image-volume-id> \
  --description "Ubuntu 24.04 with snpguest for SEV-SNP attestation" \
  --tag-specifications 'ResourceType=snapshot,Tags=[{Key=Name,Value=sev-snp-ubuntu-image},{Key=Project,Value=AMD-SEV-SNP}]' \
  --query SnapshotId --output text

返回结果如下:

detaching
<image-snapshot-id>

同一块卷短时间内重复调用 create-snapshot 会返回 SnapshotCreationPerVolumeRateExceeded。如果命令因终端超时没有显示结果,应先用 describe-snapshots 按标签查询是否已经创建,不要立即重试。等待快照完成。执行如下命令:

aws ec2 wait snapshot-completed --region us-east-2 --snapshot-ids <image-snapshot-id>

8 GiB 的快照约 2 分钟完成。用该快照注册 AMI,启动模式指定为 uefi,满足 SEV-SNP 对启动模式的要求;根设备名、ENA、SR-IOV 与 IMDSv2 设置与官方 AMI 保持一致。执行如下命令:

aws ec2 register-image --region us-east-2 \
  --name ubuntu-2404-sev-snp-attest-v1 \
  --description "Ubuntu 24.04 with snpguest 0.11.0 for AMD SEV-SNP attestation" \
  --architecture x86_64 --virtualization-type hvm \
  --boot-mode uefi --ena-support --sriov-net-support simple --imds-support v2.0 \
  --root-device-name /dev/sda1 \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={SnapshotId=<image-snapshot-id>,VolumeSize=8,VolumeType=gp3,DeleteOnTermination=true}' \
  --tag-specifications 'ResourceType=image,Tags=[{Key=Name,Value=ubuntu-2404-sev-snp-attest-v1},{Key=Project,Value=AMD-SEV-SNP}]' \
  --query ImageId --output text

返回结果如下:

<attest-ami-id>

确认 AMI 状态。执行如下命令:

aws ec2 describe-images --region us-east-2 --image-ids <attest-ami-id> \
  --query 'Images[0].[ImageId,Name,State,BootMode,ImdsSupport,EnaSupport]' --output text

返回结果如下:

<attest-ami-id>	ubuntu-2404-sev-snp-attest-v1	available	uefi	v2.0	True

至此,开发机上已具备证明所需的全部基准:snpguest 验证工具、与官方一致的 OVMF 固件、按 vCPU 数量计算的参考启动度量,以及预装证明工具的 AMI。请看下一章节讲解。

三、以可证明的 AMI 启动并完成远程证明

1、以开启 AMD SEV-SNP 的方式启动实例

用第二章注册的 AMI 启动 c6a.xlarge 实例,并增加 --cpu-options AmdSevSnp=enabled。根卷直接沿用 AMI 中定义的 8 GiB gp3 卷,不再指定 --block-device-mappings。执行如下命令:

aws ec2 run-instances --region us-east-2 \
  --image-id <attest-ami-id> \
  --instance-type c6a.xlarge \
  --cpu-options AmdSevSnp=enabled \
  --subnet-id <subnet-id-of-us-east-2a> \
  --security-group-ids <security-group-id> \
  --iam-instance-profile Name=AmazonSSMRoleForInstancesQuickSetup \
  --metadata-options HttpTokens=required \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sev-snp-attest},{Key=Project,Value=AMD-SEV-SNP}]' \
  --query 'Instances[0].[InstanceId,Placement.AvailabilityZone,CpuOptions]' --output json

返回结果如下:

[
    "<attest-instance-id>",
    "us-east-2a",
    {
        "CoreCount": 2,
        "ThreadsPerCore": 2,
        "AmdSevSnp": "enabled"
    }
]

c6a.xlarge 为 2 个物理核、每核 2 个线程,共 4 个 vCPU,对应第二章第 6 节中 vcpus=4 的参考启动度量。下面确认实例状态。

2、确认 SEV-SNP 已开启与证明工具就绪

实例启动约一分钟后 SSM Agent 变为 Online。用 remote-attestation-script/attest_sysinfo.sh 确认系统版本、SEV-SNP 状态与镜像中的证明工具,脚本内容如下:

#!/bin/bash
# 被证明实例(Ubuntu 24.04):确认系统版本、SEV-SNP 状态与镜像内置的证明工具
set -uo pipefail
lsb_release -ds
uname -r
nproc
dmesg | grep -i -E 'Memory Encryption|SEV: (Status|SNP running)|sev-guest'
ls -l /dev/sev-guest
snpguest --version
ls -l /usr/local/bin/snpguest /usr/local/bin/snp-attest-report

执行如下命令下发脚本:

python3 ssm_send.py us-east-2 attest_sysinfo.sh <attest-instance-id>

返回结果如下:

Ubuntu 24.04.5 LTS
7.0.0-1014-aws
4
[    3.223671] Memory Encryption Features active: AMD SEV SEV-ES SEV-SNP
[    3.224619] SEV: Status: SEV SEV-ES SEV-SNP 
[    3.655843] SEV: SNP running at VMPL0.
[    4.416067] software IO TLB: Memory encryption is active and system is using DMA bounce buffers
[   10.181916] sev-guest sev-guest: Initialized SEV guest driver (using VMPCK0 communication key)
crw------- 1 root root 10, 263 Nov  3 02:35 /dev/sev-guest
snpguest 0.11.0
-rwxr-xr-x 1 root root      889 Nov  3 02:32 /usr/local/bin/snp-attest-report
-rwxr-xr-x 1 root root 13874112 Nov  3 02:32 /usr/local/bin/snpguest

Ubuntu 24.04 的 AWS 内核同样识别出 SEV、SEV-ES、SEV-SNP 三项特性,并加载了 sev-guest 驱动,/dev/sev-guest 设备存在,镜像中的 snpguest 与证明脚本均已就绪。接下来由验证方发起证明。

3、验证方生成 nonce

如果验证方只检查报告签名和启动度量,攻击者可以把某台合法实例过去生成的报告保存下来,在之后任意时刻重放。因此验证方每次发起证明时都要生成一个随机数(nonce),要求实例把它放入报告。SEV-SNP 报告的 REPORT_DATA 字段长 64 字节,内容完全由客户机在请求报告时指定,并被签名覆盖。生产环境中,REPORT_DATA 也常用于放置实例临时公钥的哈希,使后续建立的加密通道与这份报告绑定。

在验证方(开发机)上生成 64 字节的 nonce,并保存 4 个 vCPU 的参考启动度量,以及第 6 节反例所用的 8 个 vCPU 的参考启动度量。执行如下命令:

mkdir -p /opt/snp/verify/certs && cd /opt/snp/verify
openssl rand -hex 64 | tee nonce.hex
echo 1d8ee9bae96e2b88e7dab179eca3229827f658cb04fbe814f59b5f4312edcb07a7e9d98141b152db1063379046c1bf5b > expected.hex
echo c7b48b97c5fa830743ac09d1076097c1790d7818daedfd7f915e6c50b42f958a881539b22a5cdfbea2b301ef7259e91a > expected-8vcpu.hex

返回结果如下:

ee7009b0818d581f0e7757979e2bab18b104f55c116a167fb21ec5bfea54e7e5cd6f4aa2db627611799ad552db2494451bad6962d3f2c20eb434e8f16ffe61dc

下面把 nonce 交给被证明实例。

4、在实例上获取证明报告

在被证明实例上以该 nonce 调用镜像内置的证明脚本。执行如下命令:

aws ssm send-command --region us-east-2 \
  --instance-ids <attest-instance-id> \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["snp-attest-report ee7009b0818d581f0e7757979e2bab18b104f55c116a167fb21ec5bfea54e7e5cd6f4aa2db627611799ad552db2494451bad6962d3f2c20eb434e8f16ffe61dc"]' \
  --query Command.CommandId --output text

返回的命令 ID 用 ssm_get.sh 读取。报告的 Base64 编码长 1580 个字符,VLEK 证书为 30 行的 PEM 文本,以下只保留首尾部分,返回结果如下:

--- report.bin (base64)
BQAAAAAAAAAAAAMCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA……AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
--- vlek.pem
-----BEGIN CERTIFICATE-----
MIIFIzCCAtegAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAgUA
oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATAwgYAxFDASBgNVBAsM
……
4ZIV9Kk4FusZXzt+O9CzhjiA0rB2LMPtFVbfskmIeEhTwleoY1utv9CK9ky9Uq09
jofUd/uxMdMA9+ftczyrsLWMjSxPGAQ=
-----END CERTIFICATE-----

证明报告解码后为 1184 字节。报告与证书的完整性由 AMD 的签名保证,传输通道本身不需要可信。本文借用 SSM 命令输出回传,再用一条 SSM 命令以 heredoc 方式把两者写入验证方的 /opt/snp/verify/report.bin 与 /opt/snp/verify/certs/vlek.pem,写入后返回结果如下:

-rw-r--r-- 1 root root 1842 Nov  3 02:37 certs/vlek.pem
-rw-r--r-- 1 root root 1184 Nov  3 02:37 report.bin

生产环境中,这一步通常由业务协议承载,例如实例在 TLS 握手或 API 请求中附带报告与证书。下面在验证方完成验证。

5、验证证书链、报告签名、启动度量与 nonce

验证分为四步:从 AMD 密钥分发服务(KDS,Key Distribution Service)获取根证书并验证 VLEK 证书链;用 openssl 独立复核 VLEK 证书;查看报告关键字段;验证报告签名并比对启动度量与 nonce。验证脚本 remote-attestation-script/dev_verify.sh 内容如下,其中第 5、6 步为第 6 节的反例:

#!/bin/bash
# 验证方(开发机):验证 VLEK 证书链、报告签名、启动度量与 nonce
# 输入:/opt/snp/verify 目录下的 report.bin、certs/vlek.pem、nonce.hex、expected.hex
export HOME=/root
set -uo pipefail
S=/opt/snp/snpguest/target/release/snpguest
cd /opt/snp/verify
NONCE=$(cat nonce.hex)
EXPECTED=$(cat expected.hex)

echo "=== 1. 从 AMD KDS 获取 VLEK 的 ARK 与 ASK 证书,验证证书链"
$S fetch ca pem ./certs milan --endorser vlek
ls certs
$S verify certs ./certs

echo "=== 2. 用 openssl 独立验证 VLEK 证书"
curl --proto '=https' --tlsv1.2 -sSf https://kdsintf.amd.com/vlek/v1/Milan/cert_chain -o cert_chain.pem
openssl verify -CAfile cert_chain.pem certs/vlek.pem

echo "=== 3. 报告中的关键字段"
$S display report report.bin | grep -A4 -E '^(Version|Guest SVN|VMPL|Report Data|Measurement|Signing Key|Platform Info)' | head -60

echo "=== 4. 验证报告签名、启动度量与 nonce(十六进制参数须带 0x 前缀)"
$S verify attestation ./certs report.bin --measurement "0x$EXPECTED" --report-data "0x$NONCE"
echo "exit=$?"

echo "=== 5. 反例:用 8 个 vCPU 的参考启动度量验证"
$S verify attestation ./certs report.bin --signature --measurement "0x$(cat expected-8vcpu.hex)"
echo "exit=$?"

echo "=== 6. 反例:用另一个 nonce 验证"
$S verify attestation ./certs report.bin --signature --report-data "0x$(openssl rand -hex 64)"
echo "exit=$?"

执行如下命令下发脚本:

python3 ssm_send.py us-east-2 dev_verify.sh <dev-instance-id>

第 1 至 4 步返回结果如下:

=== 1. 从 AMD KDS 获取 VLEK 的 ARK 与 ASK 证书,验证证书链
ark.pem
asvk.pem
vlek.pem
The AMD ARK was self-signed!
The AMD ASVK was signed by the AMD ARK!
The VLEK was signed by the AMD ASVK!
=== 2. 用 openssl 独立验证 VLEK 证书
certs/vlek.pem: OK
=== 3. 报告中的关键字段
Version:                      5
Guest SVN:                    0
Guest Policy (0x2030000):
  ABI Major:     0
  ABI Minor:     0
--
VMPL:                         1
Signature Algorithm:          1
Current TCB:
--
Platform Info (39):
  SMT Enabled:               true
  TSME Enabled:              true
  ECC Enabled:               true
  RAPL Disabled:             false
--
Report Data:
EE 70 09 B0 81 8D 58 1F 0E 77 57 97 9E 2B AB 18
B1 04 F5 5C 11 6A 16 7F B2 1E C5 BF EA 54 E7 E5
CD 6F 4A A2 DB 62 76 11 79 9A D5 52 DB 24 94 45
1B AD 69 62 D3 F2 C2 0E B4 34 E8 F1 6F FE 61 DC
--
Measurement:
1D 8E E9 BA E9 6E 2B 88 E7 DA B1 79 EC A3 22 98
27 F6 58 CB 04 FB E8 14 F5 9B 5F 43 12 ED CB 07
A7 E9 D9 81 41 B1 52 DB 10 63 37 90 46 C1 BF 5B
=== 4. 验证报告签名、启动度量与 nonce(十六进制参数须带 0x 前缀)
Reported TCB Boot Loader from certificate matches the attestation report.
Reported TCB TEE from certificate matches the attestation report.
Reported TCB SNP from certificate matches the attestation report.
Reported TCB Microcode from certificate matches the attestation report.
VEK signed the Attestation Report!
Measurement verified successfully.
Report Data verified successfully.
exit=0

各步结果的含义如下:

  • 证书链:VLEK 证书链的中间证书是 ASVK(AMD SEV VLEK Key),而不是 VCEK 证书链中的 ASK(AMD SEV Key)。snpguest 先确认 ARK(AMD Root Key)自签名,再确认 ARK 签发了 ASVK、ASVK 签发了 VLEK。openssl verify 使用从 AMD KDS 直接下载的证书链,得到相同结论。VLEK 证书虽然由被证明实例所在的主机提供,但它必须能用 AMD 根证书验证通过,因此验证方不需要信任 AWS 主机。
  • 报告签名与 TCB:VLEK 证书中记录的引导加载程序、TEE、SNP 固件与微码的 TCB(Trusted Computing Base,可信计算基)版本与报告一致,且报告由 VLEK 签名。
  • 启动度量:报告中的 Measurement 与第二章第 6 节离线计算的 vcpus=4 参考值逐字节一致。这同时说明 EC2 当前运行的 OVMF 正是 aws/uefi 的 20260814 版本。
  • nonce:报告中的 Report Data 与验证方本次生成的 nonce 一致,证明这份报告是本次请求新生成的,而不是重放的旧报告。

snpguest display report 输出的完整报告中,还有以下字段值得验证方关注:

  • Guest Policy 中 Debug Allowed: false,表示 Hypervisor 不能用调试接口解密客户机内存。验证方应把该字段为 false 作为通过条件之一,snpguest verify attestation 不检查这一项,需要业务方自行判断。
  • VMPL: 1 是请求报告时指定的特权级。snpguest report 的 --vmpl 参数默认为 1,客户机内核本身运行在 VMPL0(见第 2 节 dmesg)。验证方如需要求报告来自最高特权级,应在请求时指定 --vmpl 0 并检查该字段。
  • signing key: vlek 表示报告由 VLEK 签名,与共享租户一致。实测报告中的 Chip ID 全为 0,验证方不能依赖芯片标识区分主机。
  • Host Data、ID Key Digest、Author Key Digest 全为 0,说明 EC2 启动实例时没有提供 ID Block 等由客户自定义的启动身份信息,验证只能依赖启动度量本身。

注意:snpguest v0.11.0 的 --measurement 与 --report-data 参数必须带 0x 前缀,否则会把十六进制字符串按 ASCII 字节处理。实测不带前缀时返回 ERROR: Expected 48 characters for Measurement, got 96,签名验证已经通过,但退出码为 1。下面验证失败场景。

6、验证失败的反例

为确认验证逻辑确实生效,第 5 节脚本的第 5、6 步分别用 8 个 vCPU 的参考启动度量和一个新生成的 nonce 验证同一份报告,返回结果如下:

=== 5. 反例:用 8 个 vCPU 的参考启动度量验证
VEK signed the Attestation Report!
exit=1
=== 6. 反例:用另一个 nonce 验证
VEK signed the Attestation Report!
exit=1

标准错误输出如下:

ERROR: Measurement did not match:
 expected 1d8ee9bae96e2b88e7dab179eca3229827f658cb04fbe814f59b5f4312edcb07a7e9d98141b152db1063379046c1bf5b
 got c7b48b97c5fa830743ac09d1076097c1790d7818daedfd7f915e6c50b42f958a881539b22a5cdfbea2b301ef7259e91a
Error: Measurement did not match:
 expected 1d8ee9bae96e2b88e7dab179eca3229827f658cb04fbe814f59b5f4312edcb07a7e9d98141b152db1063379046c1bf5b
 got c7b48b97c5fa830743ac09d1076097c1790d7818daedfd7f915e6c50b42f958a881539b22a5cdfbea2b301ef7259e91a
ERROR: Report Data did not match:
 expected ee7009b0818d581f0e7757979e2bab18b104f55c116a167fb21ec5bfea54e7e5cd6f4aa2db627611799ad552db2494451bad6962d3f2c20eb434e8f16ffe61dc
 got ffd74ba884224cfd4681bfe0649eaca702bff2a492814b67c058346d6d6c8bc1fc0b93cb28a88e29450a5f719b1b8fc4243744583e2f86c28d32bc1c405d7404
Error: Report Data did not match:
 expected ee7009b0818d581f0e7757979e2bab18b104f55c116a167fb21ec5bfea54e7e5cd6f4aa2db627611799ad552db2494451bad6962d3f2c20eb434e8f16ffe61dc
 got ffd74ba884224cfd4681bfe0649eaca702bff2a492814b67c058346d6d6c8bc1fc0b93cb28a88e29450a5f719b1b8fc4243744583e2f86c28d32bc1c405d7404

两个反例中报告签名仍然有效,但启动度量或 nonce 不匹配,snpguest 以退出码 1 拒绝。需要注意 snpguest v0.11.0 错误信息中的措辞:expected 后面是报告中的实际值,got 后面是命令行传入的参考值,与通常的理解相反,排查时不要看反。

第一个反例也说明了启动度量与 vCPU 数量的关系:如果把实例从 c6a.xlarge 改为 c6a.2xlarge,同一个 AMI 的启动度量会变为 vcpus=8 的值,验证方必须按实例的实际规格选取参考值。

至此,远程证明完成,验证方确认了被证明实例运行在真实的 AMD SEV-SNP 硬件上,启动固件与 aws/uefi 公开源代码一致,报告为本次请求新生成。如前文所述,这一结论只覆盖到固件层,不覆盖 AMI 中的操作系统。下面清理测试资源。

7、清理测试资源

测试完成后,终止开发机与被证明实例,注销自建 AMI,删除其快照与制作 AMI 用的卷,避免持续计费。执行如下命令:

aws ec2 terminate-instances --region us-east-2 \
  --instance-ids <dev-instance-id> <attest-instance-id>
aws ec2 deregister-image --region us-east-2 --image-id <attest-ami-id>
aws ec2 delete-snapshot --region us-east-2 --snapshot-id <image-snapshot-id>
aws ec2 delete-volume --region us-east-2 --volume-id <image-volume-id>
aws ec2 wait instance-terminated --region us-east-2 \
  --instance-ids <dev-instance-id> <attest-instance-id>

快照被 AMI 引用期间无法删除,因此须先注销 AMI 再删除快照。安全组 <security-group-id> 与性能测试文档中的实例共用,确认不再有实例使用后再执行 aws ec2 delete-security-group 删除,否则会返回 DependencyViolation。下面对全文进行总结。

四、小结

在 EC2 上选择 AMD SEV-SNP 还是 NitroTPM 做实例证明,判断依据不是“是否需要每台虚拟机有独立的内存加密密钥”,而是“是否要把 AWS(Nitro Hypervisor 与主机特权软件)排除在信任范围之外”。

独立的内存加密密钥只是手段,SEV-SNP 的作用不止于内存加密。EC2 在 AMD EPYC(Milan)及以后的机型上默认加密内存,这种加密主要防御物理层面的内存窃取,对 Hypervisor 透明。SEV-SNP 在此之上提供两项能力:

  • Hypervisor 无法读写虚拟机内存:内存密钥由 AMD 安全处理器管理,RMP 提供内存完整性保护,寄存器状态也被加密。
  • 证明报告由 AMD 签名:VLEK 证书链追溯到 AMD 根证书,验证方不需要信任 AWS。

但 EC2 上 SEV-SNP 的证明内容确实很有限。如第一章第 2 节所述,启动度量只覆盖 OVMF 固件和 vCPU 初始状态,与 AMI 无关。只要 vCPU 数量相同,任何 AMI 启动后得到的启动度量都一样。因此 SEV-SNP 证明报告只能证明三点:实例运行在真实的 AMD 硬件上,已开启 SEV-SNP,固件是 AWS 公开的版本。它不能证明实例运行的是经过批准的镜像。如果目标是“只把密钥发放给特定的工作负载”,单靠 SEV-SNP 无法实现。在这一点上,SEV-SNP 的证明能力不如 NitroTPM。

在接受 AWS 作为可信根的前提下,NitroTPM 更为合适,原因如下:

  • 证明范围更大:NitroTPM 的 PCR4 覆盖 UKI、内核和 initrd,并经 dm-verity 间接覆盖根文件系统。
  • KMS 可直接验证:AWS KMS 原生支持 NitroTPM 证明文档,通过 PCR 条件键控制密钥发放,不需要自建验证服务。
  • 成本与可用性更好:NitroTPM 没有附加费,也不涉及 SEV-SNP 的内存加密开销,支持的机型和区域更多。相比之下,《EC2 AMD SEV-SNP 开启方法与性能测试》实测开启 SEV-SNP 后 ffmpeg 转码性能下降 6.7%–43.7%,另按按需价的 10% 收取附加费。共享租户下 SEV-SNP 只支持 6a 系列的部分规格,且只在美国东部(俄亥俄,us-east-2)和欧洲(爱尔兰,eu-west-1)提供。

仍然需要 SEV-SNP 的场景如下:

  • 合规要求云厂商的主机软件和运维人员也无法访问实例内存,并且必须由硬件强制,不能只依赖 AWS 的设计承诺。
  • 数据持有方(验证方)不接受 AWS 签名的证明,只接受芯片厂商签名的证明。

选择 SEV-SNP 时须注意,在 EC2 上无法在不信任 AWS 的前提下证明操作系统,原因有三:

  • EC2 不提供度量直接启动(kernel-hashes),也没有在 VMPL0 运行的 SVSM vTPM(Azure 机密虚拟机采用这种架构)。
  • SEV-SNP 与 NitroTPM 能否在同一实例上同时开启,AWS 文档没有说明,本文也没有实测。
  • 即使二者能够同时开启,操作系统这一层证明的可信根仍然是 AWS。

综上所述,如果信任体系允许 AWS 作为可信根,EC2 实例证明使用 NitroTPM 即可,而且比 SEV-SNP 更合适。只有在必须排除 AWS 可信根、只允许信任 AMD 签名的场景下才建议 SEV-SNP,此时还须接受上述限制:无法证明操作系统,验证方须自建,并承担性能损失、以及使用本特性额外的 EC2 收费。

五、参考文档

AMD SEV-SNP for Amazon EC2 instances(概念、要求、限制与计费)

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html

Enable AMD SEV-SNP for an EC2 instance(共享租户与 Dedicated Host 的开启方法及状态检查)

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/snp-work-launch.html

Attest an Amazon EC2 instance with AMD SEV-SNP(使用 snpguest 获取并验证证明报告)

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/snp-attestation.html

AWS KMS API Reference:RecipientInfo(Recipient 参数仅支持 Nitro Enclaves 与 NitroTPM 的证明文档)

https://docs.aws.amazon.com/kms/latest/APIReference/API_RecipientInfo.html

AWS KMS Condition keys for NitroTPM(按 PCR 值放行的密钥策略条件键)

https://docs.aws.amazon.com/kms/latest/developerguide/conditions-nitro-tpm.html

Integrating EC2 Instance Attestation with AWS KMS(NitroTPM 证明文档与 KMS 的集成方式)

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/attestation-attest.html

snpguest 证明工具代码仓库

https://github.com/virtee/snpguest

aws/uefi:EC2 所用 OVMF 固件的源代码补丁与 Nix 可复现构建

https://github.com/aws/uefi

aws/uefi 20260814 发布版本(含固件二进制与 SHA256)

https://github.com/aws/uefi/releases/tag/20260814

sev-snp-measure:离线计算 SEV-SNP 启动度量的工具

https://github.com/virtee/sev-snp-measure

AMD SEV Secure Nested Paging Firmware ABI Specification(证明报告结构与字段定义)

https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf

AMD 白皮书:AMD SEV-SNP: Strengthening VM Isolation with Integrity Protection and More

https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/white-papers/SEV-SNP-strengthening-vm-isolation-with-integrity-protection-and-more.pdf

Linux 内核文档:AMD Memory Encryption(SEV-SNP、RMP 与 VMPL 说明)

https://docs.kernel.org/arch/x86/amd-memory-encryption.html

Amazon EC2 instance attestation(NitroTPM 与可证明的 AMI、AWS KMS 集成)

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nitrotpm-attestation.html

Virtual TPMs in Azure confidential VMs(受 SEV-SNP 保护并被度量的 vTPM 架构)

https://learn.microsoft.com/azure/confidential-computing/virtual-tpms-in-azure-confidential-vm

COCONUT-SVSM:运行在 VMPL0 的开源 SVSM 实现

https://github.com/coconut-svsm/svsm

Nix 安装说明

https://nixos.org/download/

Ubuntu on AWS:通过 SSM 参数查找 Ubuntu AMI

https://documentation.ubuntu.com/aws/aws-how-to/instances/find-ubuntu-images/


最后修改于 2026-11-03