刊号 58217,版本 v1.2,发布于 2023.7
VirTEE/sev 工具箱提供了一套基于 rust 语言的简单易用的 API 来访问 AMD EPYC 处理器内的安全处理器,这个库已经早已经支持传统的 SEV 固件,最近在 VirTEE 社区中又增加了新的 SEV-SNP 固件的支持,主要是第三代和新上市的 AMD EPYC 处理器。用户手册中包含了使用 AMD SEV-SNP 技术对可信执行环境 TEE 进行平台证明的一些潜在的解决方案,主要针对以下用户:
这篇用户指南探讨了 VirTEE/sev 工具箱中 SEV-SNP 技术更新带给平台所有者和来宾所有者带来的一些新的能力,同时提供了一些用例以及解释了一些用户关心的配置选项。
平台所有者是包括 hypervisor 在内的系统软件,用来部署机密虚拟机或者容器。
平台所有者应该:
在配置主机平台来缓存证书前,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();
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.创建来扩展报告的配置
// Generate a vector of certificates to store in hypervisor memory.
let certificates: Vec = 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
);
// 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
);
// 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 = 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.
...
}
请求既存扩展配置包含以下步骤:
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();
来宾所有者是虚拟化提供商的租户,它可能拥有一个或者多个机密虚拟机或者容器不属于平台所有者的环境中。
来宾级的支持已经全部完成,使用 Linux Kernel 5.19 或者更新的内核来访问来宾驱动。
来宾所有者可以:
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 信任根是:
比如:
use sev::{
certs::snp::{ca, Certificate, Chain},
firmware::host::CertType,
};
ecdsa::EcdsaSig,
pkey::{PKey, Public},
sha::Sha384,
x509::X509,
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_chain
let 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 = rsp.bytes().unwrap().to_vec();
let chain: Vec = 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
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 = 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 = 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()
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) =
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 密钥发布服务器。
来宾所有者根据硬件信任根衍生唯一的加密密钥有很多使用场景。这个派生的密钥可能依赖多个 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();