基于 VirTEE/SEV 的 SEV-SNP 平台证明
刊号 58217,版本 v1.2,发布于 2023.7
1. 介绍
VirTEE/sev 工具箱提供了一套基于 rust 语言的简单易用的 API 来访问 AMD EPYC 处理器内的安全处理器,这个库已经早已经支持传统的 SEV 固件,最近在 VirTEE 社区中又增加了新的 SEV-SNP 固件的支持,主要是第三代和新上市的 AMD EPYC 处理器。用户手册中包含了使用 AMD SEV-SNP 技术对可信执行环境 TEE 进行平台证明的一些潜在的解决方案,主要针对以下用户:
- 平台所有者:管理用来部署虚拟机、容器系统软件,比如主机或者云服务商。这些系软件主要指的是运行机密虚拟机、机器容器的虚拟机监视器 hypervisor。
- 来宾所有者:指的是部署工作负载者
这篇用户指南探讨了 VirTEE/sev 工具箱中 SEV-SNP 技术更新带给平台所有者和来宾所有者带来的一些新的能力,同时提供了一些用例以及解释了一些用户关心的配置选项。
2. 平台所有者
平台所有者是包括 hypervisor 在内的系统软件,用来部署机密虚拟机或者容器。
2.1 系统要求
- 硬件:系统必须是第三代或者更新的 AMD EPYC 处理器
- 固件:必须使用支持最新 SEV 固件的 BIOS 版本,保证具有对应的安全保护功能。VirTEE/sev SEV-SNP 特性在版本1.54.01 中兼容
- 内核:SEV-SNP 正处在开发中,ADM 建议使用上游最新的内核补丁
- 软件:通常来讲 AMD 建议使用上游的 OVMF/EDK II 和 QEMU 7.1,不过对于平台所有者或者来宾所有者有以下需求的,建议使用 OVMF/EDK II 最新和 QEMU 最新
- 证明报告中需要包含验证来宾身份
- OVMF/EDK II 需要验证内核、initrd、cmdline 等参数
2.2 API 能力
平台所有者应该:
- 请求 AMD 安全处理器状态
- 加载新扩展配置
- 请求既存的扩展配置
2.2.1 请求 AMD 安全处理器状态
在配置主机平台来缓存证书前,AMD 建议检查 AMD 安全处理器的状态,包括以下步骤:
1.包含 VirTEE/sev 到用户 rust 工程里
// Import library
use sev::firmware::host::*;
2.连接到固件并请求 AMD 安全处理 snp 的状态
// Open a connection to the firmware.
let mut firmware: Firmware = Firmware::open().unwrap();
// Request the current snp status of the AMD Secure Processor.
let snp_status: SnpPlatformStatus = firmware.snp_platform_status().unwrap();
2.2.2 加载新的扩展配置
hypervisor 内存里的存储策略和证书链使得平台所有者提升了来宾所有者的使用体验,避免了向 AMD 密钥发布服务器 KDS 请求证书的麻烦,同时也减少了 CSP 对于 AMD KDS 的依赖,更重要的,这避免了在大规模部署虚拟机/容器时手动请求证书链的麻烦。
1.包含 VirTEE/sev 到 Rust 工程中
// Import library
use sev::firmware::host::*;
2.读取证书
// Read certificate bytes.
pub const ARK: &[u8] = include_bytes!("ark.pem");
pub const ASK: &[u8] = include_bytes!("ask.pem");
pub const VCEK: &[u8] = include_bytes!("vcek.pem");
3.创建来扩展报告的配置
- 仅证书:允许 hypervisor 在不修改报告的 TCB 的前提下存储证书链
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec<CertTableEntry> = vec![CertTableEntry::new(CertType::ARK, ARK.to_vec()),CertTableEntry::new(CertType::ASK, ASK.to_vec()),CertTableEntry::new(CertType::VCEK, VCEK.to_vec()),
];
// Call the `new_certs_only` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new_certs_only(certificates
);
- 仅配置:不存储证书链的前提下配置和更新报告的 TCB 内容
// Specify the desired configuration
let configuration: Config = Config::new(TcbVersion::new(3, 0, 10, 169),0,
);
// Call the `new_config_only` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new_config_only(configuration
);
- 配置和证书:更改 TCB 报告并存储证书链
// Specify the desired configuration
let configuration: Config = Config::new(TcbVersion::new(3, 0, 10, 169),0,
);
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec<CertTableEntry> = vec![CertTableEntry::new(CertType::ARK, ARK.to_vec()),CertTableEntry::new(CertType::ASK, ASK.to_vec()),CertTableEntry::new(CertType::VCEK, VCEK.to_vec()),
];
// Call the `new` constructor to generate the extended configuration.
let ext_config: ExtConfig = ExtConfig::new(configuration,certificates
);
4.连接固件并将扩展请求转发给 AMD 安全处理器
// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open().unwrap();
// Forward the certificates to the AMD Secure Processor to be loaded.
if let Err(error) = fw.snp_reset_config(&ext_config) {// Handle an error if one is encountered....
}
2.2.3 请求既存扩展配置
请求既存扩展配置包含以下步骤:
1.包含 VirTEE/sev 到 Rust 工程中
// Import library
use sev::firmware::host::*;
2.连接到固件并请求当前配置
// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open().unwrap();
// Request the current configuration.
let current_configuration: ExtConfig = fw.snp_get_ext_config().unwrap();
3.来宾所有者
来宾所有者是虚拟化提供商的租户,它可能拥有一个或者多个机密虚拟机或者容器不属于平台所有者的环境中。
3.1 系统要求
来宾级的支持已经全部完成,使用 Linux Kernel 5.19 或者更新的内核来访问来宾驱动。
3.2 API 能力
来宾所有者可以:
- 请求标准的证明报告
- 请求扩展证明报告
- 请求通过硬件密钥派生的唯一密码学密钥
3.2.1 标准证明保证请求和证明
1.从工具箱导入需要的代码模块
// Import the modules
use sev::firmware::guest::* ;
2.创建用于证明报告的 64 字节唯一数据
// This could be a unique message, a public key, etc.
let unique_data: [u8; 64] = [65, 77, 68, 32, 105, 115, 32, 101, 120, 116, 114, 101, 109, 101, 108, 121, 32, 97,
119,101, 115, 111, 109, 101, 33, 32, 87, 101, 32, 109, 97, 107, 101, 32, 116, 104, 101,
32,98, 101, 115, 116, 32, 67, 80, 85, 115, 33, 32, 65, 77, 68, 32, 82, 111, 99, 107,
115,33, 33, 33, 33, 33, 33,
];
3.连接到固件并请求 SEV-SNP 证明报告
// Open a connection to the firmware.
let mut fw: Firmware = Firmware::open()?;
// Request a standard attestation report.
let attestation_report: AttestationReport = fw.get_report(None, Some(unique_data),
None);
4.验证信任根。验证信任根是整个证明过程中最重要的一步,opehttps://crates.io/crates/opensslnssl 工具箱提供了所有用来验证证书链签名的工具,VirTEE/sev 库也包含了一些简化证书处理的结构和函数。当前 AMD 信任根是:
- AMD 根密钥 ARK 是自签名的
- ARK 给 ADM 签名密钥 ASK 签名
- ASK 给芯片版本背书密钥 VCEK 签名
比如:
- 导入必要的证书处理的逻辑
use sev::{certs::snp::{ca, Certificate, Chain},firmware::host::CertType,
};
- 导入相关 openssl 库
ecdsa::EcdsaSig,pkey::{PKey, Public},sha::Sha384,x509::X509,
- 从 AMD 密钥分发系统 AKS 拉取证书链,参考 VCEK 规范。所有域都至少是 2 字节长度,并且用 0 补全。证明报告中 hwid 和 chip_id 是匹配的
const KDS_CERT_SITE: &str = "https://kdsintf.amd.com";
const KDS_VCEK: &str = "/vcek/v1";
const KDS_CERT_CHAIN: &str = "cert_chain";
/// Requests the certificate-chain (AMD ASK + AMD ARK)
/// These may be used to verify the downloaded VCEK is authentic.
pub fn request_cert_chain (sev_prod_name: &str) -> (ask, ark) {// Should make -> https://kdsintf.amd.com/vcek/v1/{SEV_PROD_NAME}/cert_chainlet url: String = format!("{KDS_CERT_SITE}{KDS_VCEK}/{sev_prod_name}/
{KDS_CERT_CHAIN}");println!("Requesting AMD certificate-chain from: {url}");let rsp: Response = get(&url).unwrap();
let body: Vec<u8> = rsp.bytes().unwrap().to_vec();
let chain: Vec<x509> = X509::stack_from_pem(&body).unwrap();
// Create a ca chain with ark and ask
let ca_chain: ca::Chain = ca::Chain::from_pem(&chain[1].to_pem, &chain[0].to_pem);
ca_chain
};
/// Requests the VCEK for the specified chip and TCP
pub fn request_vcek(chip_id: [u8; 64], reported_tcb: TcbVersion) -> X509 {let hw_id: String = hexify(&chip_id);let url: String = format!("{KDS_CERT_SITE}{KDS_VCEK}/{SEV_PROD_NAME}/\{hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",reported_tcb.boot_loader,reported_tcb.microcode); println!("Requesting VCEK from: {url}\n");let rsp_bytes = get(&url).unwrap().bytes().unwrap().to_vec();Certificate::from_der(&rsp_bytes)
};
- 验证信任根
let ca_chain: ca::Chain = request_cert_chain("milan");
// chip_id and reported_tcb should be pulled from the host machine,
// or an attestation report. let vcek: Certificate = request_vcek(
chip_id,
reported_tcb
);
// Create a full-chain with the certificates:
Let cert_chain = Chain{ca: ca_chain, vcek: vcek};
//Now you can simply verify the whole chain in one command.
cert_chain.verify().unwrap();
//Or you can verify each certificate individually
let ark = cert_chain.ca.ark;
let ask = cert_chain.ca.ask;
if (&ark,&ark).verify().unwrap() {println!("The AMD ARK was self-signed...");if (&ark,&ask).verify().unwrap() {iprintln!("The AMD ASK was signed by the AMD ARK...");f (&ask,&vcek).verify().unwrap() {println!("The VCEK was signed by the AMD ASK...");
} else {eprintln!("The VCEK was not signed by the AMD ASK!");}
} else {eprintln!("The AMD ASK was not signed by the AMD ARK!");}
} else {eprintln!("The AMD ARK is not self-signed!");
}
5.通过验证证明报告中以下域和 VCEK 来验证来宾可信计算机 TCB
- Bootloader
- TEE
- SNP
- Microcode
- Chip ID
openssl 和 VirTEE/sev 工具箱都不支持 x509v3 扩展验证,一个可信的解决方案是使用 x509解析器工具箱的 asn1 rs。下面示例就是基于这些定义构建起来的:
/
******************************************************************************************* RELEVANT X509v3 EXTENSION OIDS
******************************************************************************************
/
use asn1_rs::{oid, Oid};
use x509_parser::{self,certificate::X509Certificate,pem::{parse_x509_pem, Pem},prelude::X509Extension,
};
enum SnpOid {BootLoader,Tee,Snp,Ucode,HwId,
}
impl SnpOid {fn oid(&self) -> Oid {match self {SnpOid::BootLoader => oid!(1.3.6.1.4.1.3704.1.3.1),SnpOid::Tee => oid!(1.3.6.1.4.1.3704.1.3.2),SnpOid::Snp => oid!(1.3.6.1.4.1.3704.1.3.3),SnpOid::Ucode => oid!(1.3.6.1.4.1.3704.1.3.8),SnpOid::HwId => oid!(1.3.6.1.4.1.3704.1.4),}}
}
impl std::fmt::Display for SnpOid {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {write!(f, "{}", self.oid().to_id_string())}
}/
******************************************************************************************* HELPER FUNCTIONS
******************************************************************************************
/
fn check_cert_ext_byte(ext: &X509Extension, val: u8) -> bool {if ext.value[0] != 0x2 {panic!("Invalid type encountered!");}if ext.value[1] != 0x1 && ext.value[1] != 0x2 {panic!("Invalid octet length encountered");}if let Some(byte_value) = ext.value.last() {*byte_value == val} else {false}
}
fn check_cert_ext_bytes(ext: &X509Extension, val: &[u8]) -> bool {ext.value == val
}
/
******************************************************************************************* EXAMPLE ATTESTATION FUNCTION:
******************************************************************************************
/
fn validate_cert_metadata(cert: &X509Certificate,report: &AttestationReport,
) -> bool {let extensions: HashMap<Oid, &X509Extension> = cert.extensions_map().unwrap();if let Some(cert_bl) = extensions.get(&SnpOid::BootLoader.oid()) {if !check_cert_ext_byte(cert_bl, report.reported_tcb.boot_loader) {eprintln!("Report TCB Boot Loader and Certificate Boot Loader mismatch
encountered.");return false;}println!("Reported TCB Boot Loader from certificate matches the attestation
report.");}if let Some(cert_tee) = extensions.get(&SnpOid::Tee.oid()) {if !check_cert_ext_byte(cert_tee, report.reported_tcb.tee) {eprintln!("Report TCB TEE and Certificate TEE mismatch encountered.");return false;}println!("Reported TCB TEE from certificate matches the attestation report.");}if let Some(cert_snp) = extensions.get(&SnpOid::Snp.oid()) {if !check_cert_ext_byte(cert_snp, report.reported_tcb.snp) {eprintln!("Report TCB SNP and Certificate SNP mismatch encountered.");return false;}println!("Reported TCB SNP from certificate matches the attestation report.");}if let Some(cert_ucode) = extensions.get(&SnpOid::Ucode.oid()) {if !check_cert_ext_byte(cert_ucode, report.reported_tcb.microcode) {eprintln!("Report TCB Microcode and Certificate Microcode mismatch
encountered.");return false;}println!("Reported TCB Microcode from certificate matches the attestation
report.");}if let Some(cert_hwid) = extensions.get(&SnpOid::HwId.oid()) {if !check_cert_ext_bytes(cert_hwid, &report.chip_id) {eprintln!("Report TCB Microcode and Certificate Microcode mismatch
encountered.");return false;}println!("Chip ID from certificate matches the attestation report.");}true
}
6.验证报告中的签名确实是由 VCEK 签发的
let ar_signature: EcdsaSig = EcdsaSig::try_from(&report.signature).unwrap();
let signed_bytes: &[u8] = &bincode::serialize(&report).unwrap()[0x0..0x2A0];
let amd_vcek_pubkey: EcKey<Public> = vcek.public_key().unwrap().ec_key().unwrap();
let mut hasher: Sha384 = Sha384::new()
hasher.update(signed_bytes);
let base_message_digest: [u8; 48] = hasher.finish();
if ar_signature.verify(base_message_digest.as_ref(), vcek_pubkey.as_ref()).unwrap() {println!("VCEK signed the Attestation Report!");
} else {eprintln!("VCEK did NOT sign the Attestation Report!");
}
// Or you can use a complete certificate chain to verify the attestation report
(&report, &certificate_chain).verify().unwrap()
3.2.2 扩展证明报告的请求和证明
1.创建 64 字节唯一数据用来证明报告中
// This could be a unique message, a public key, etc.
let unique_data: [u8; 64] = [65, 77, 68, 32, 105, 115, 32, 101, 120, 116, 114, 101, 109, 101, 108, 121, 32, 97,
119,101, 115, 111, 109, 101, 33, 32, 87, 101, 32, 109, 97, 107, 101, 32, 116, 104, 101,
32,98, 101, 115, 116, 32, 67, 80, 85, 115, 33, 32, 65, 77, 68, 32, 82, 111, 99, 107,
115,33, 33, 33, 33, 33, 33,
];
2. 连接到固件并请求扩展报告
let mut fw: Firmware = Firmware::open().unwrap();
let (extended_report, certificates): (AttestationReport, Vec<CertTableEntry>) =
fw.get_ext_report(None, Some(unique_data), 0)
3.使用 VirTEE/SEV 库从 AMD 安全处理器中解析 ARK、ASK、VCEK
// Assumes all certificates are in PEM format (for simplicity).
let certs: Chain = Chain::from_cert_table_pem(certificates).unwrap();
4.使用标准证明报告信任根进行验证,跳过连接到 AMD 密钥发布服务器。
3.2.3 请求一把派生密钥
来宾所有者根据硬件信任根衍生唯一的加密密钥有很多使用场景。这个派生的密钥可能依赖多个 TCB 部件参数,只有相同的参数才能派生出相同的密钥。
1.根据规范勾走一个 DerivedKey
let request: DerivedKey = DerivedKey::new(false, GuestFieldSelect(1), 0, 0, 0);
2.连接到固件并请求一把派生密钥
let mut fw: Firmware = Firmware::open().unwrap();
let derived_key: [u8; 32]= fw.get_derived_key(None, request).unwrap();