6 个稳定版本
1.4.9 | 2024年7月5日 |
---|---|
1.4.8 | 2024年5月13日 |
1.4.7 | 2024年2月1日 |
1.4.6 | 2023年12月8日 |
1.4.4 | 2023年8月31日 |
#80 在 硬件支持
970 每月下载
220KB
4K SLoC
sev-snp-utilities
AMD SEV-SNP rust 工具和原语。
测试
代替 cargo test
,运行
make test
环境
变量 | 默认 | 描述 |
---|---|---|
SEV_SNP_CACHE_PATH | /var/cache/sev-snp | 存储已下载证书的路径。 |
SEV_SNP_CACHE_ENTRIES_VCEK | 100 | VCEK证书的最大缓存条目(内存中)。 |
证明
报告
请求
从SEV-SNP功能的CPU请求报告(与sev-guest-get-report
相同的功能)
use sev_snp_utilities::{AttestationReport, Requester};
fn main() {
let report = AttestationReport::request()
.expect("failed to request guest report");
println!("version: {:?}", report.version);
// Or raw bytes
let report_bytes = AttestationReport::request_raw()
.expect("failed to request guest report");
println!("bytes len: {:?}", report_bytes.len());
}
解析
解析来自sev-guest-get-report
的guest_report.bin文件(或从AttestationReport::request_raw()
保存的文件)
use sev_snp_utilities::AttestationReport;
fn main() {
let report = AttestationReport::from_file("./guest_report.bin")
.expect("failed to parse guest report");
println!("version: {:?}", report.version);
println!("guest_svn: {:?}", report.guest_svn);
println!("policy: {:?}", report.policy);
println!("platform_version: {:?}", report.platform_version.raw_decimal());
println!("measurement: {}", report.measurement_hex());
println!("report data: {}", report.report_data_hex());
println!("id key digest: {}", report.id_key_digest_hex());
println!("author key digest: {}", report.author_key_digest_hex());
println!("chip id: {}", report.chip_id_hex());
println!("hash: {}", report.sha384_hex());
println!("signature:");
println!(" r: {}", report.signature.r_hex());
println!(" s: {}", report.signature.s_hex());
}
验证
验证过程
- 从AMD下载ARK、ASK和VCEK DER文件并存储在磁盘上(同时存储内存缓存)。
- 由于ARK端点限制,尝试多次下载(10次,每次4秒睡眠)。
- 验证ARK是否自签名,ASK(AMD SEV中间证书)由ARK签名,以及VCEK(CPU证书)由ASK签名。
- 对报告.bin文件的第一部分(签名之前)计算SHA384哈希值。
- 将哈希值与证书链上的文件签名进行验证。
- 根据您提供的策略,可选地验证其他一些内容。
验证guest_report.bin文件
use sev_snp_utilities::{AttestationReport, Verification, Policy};
async fn verify_guest() {
let report = AttestationReport::from_file("./guest_report.bin")
.expect("failed to parse guest report");
let res = report.verify(Some(Policy::permissive())).await
.expect("failed to call verify");
if !res {
panic!("verification failed");
}
}
您还可以使用Policy::strict()
或创建自己的策略
let policy = Policy::new(
true, // require_no_debug
true, // require_no_ma
true, // require_no_smt
true, // require_id_key
true // require_author_key
);
证书
您还可以获取证书以直接使用它们
use sev_snp_utilities::{
AttestationReport, KdsCertificates, CertFormat,
get_kds_ark_ask_certs_bytes, get_kds_ark_ask_certs,
get_kds_ark_ask_certs_and_validate, validate_ark_ask_vcek_certs,
PRODUCT_NAME_MILAN
};
async fn get_certs() {
let report = AttestationReport::from_file("./guest_report.bin")
.expect("failed to parse guest report");
// VCEK
// Raw bytes as PEM or DER (cached only on disk)
let pem_bytes = report.get_kds_vcek_cert_bytes(CertFormat::PEM).await
.expect("failed to get VCEK PEM");
let der_bytes = report.get_kds_vcek_cert_bytes(CertFormat::DER).await
.expect("failed to get VCEK DER");
// X509 (cached in-memory, prefer this method)
let cert = report.get_kds_vcek_cert().await
.expect("failed to get VCEK cert");
// ARK & ASK
// Raw bytes as PEM or DER (cached only on disk)
let (ark_pem, ask_pem) = get_kds_ark_ask_certs_bytes(PRODUCT_NAME_MILAN, CertFormat::PEM).await
.expect("failed to get ARK/ASK PEMs");
// X509 (cached in-memory, prefer this method)
let (ark_cert, ask_cert) = get_kds_ark_ask_certs(PRODUCT_NAME_MILAN).await
.expect("failed to get ARK/ASK certs");
// X509 validated (cached in-memory, prefer this method)
let (ark_cert, ask_cert) = get_kds_ark_ask_certs_and_validate(PRODUCT_NAME_MILAN).await
.expect("failed to get ARK/ASK certs");
// Validate
validate_ark_ask_vcek_certs(&ark_cert, &ask_cert, Some(&cert))
.expect("failed to validate certs");
}
测量
计算启动摘要
use std::fs;
use std::path::PathBuf;
use sev_snp_utilities::{
calc_launch_digest, SevMode, CpuType
};
fn main() {
let ovmf_path = PathBuf::from("./OVMF_CODE.fd");
let kernel_path = PathBuf::from("./vmlinuz");
let append_path = PathBuf::from("./vmlinuz.cmdline");
let initrd_path = PathBuf::from("./initrd.img");
let append = fs::read_to_string(&append_path)
.expect(format!("failed to read '{:?}'", &append_path).as_str());
let digest = calc_launch_digest(SevMode::SevSnp, 64, ovmf_path.as_path(),
Some(kernel_path.as_path()), Some(initrd_path.as_path()),
Some(append.as_str()))
.expect("failed to calculate launch digest");
}
身份
准备
在您能够生成IdBlock
和IdAuthInfo
之前,您首先需要创建一些ECDSA密钥(pem文件)。
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:"P-384" -out id-key.pem
# Author key is optional.
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:"P-384" -out author-key.pem
生成
方法接口
use std::path::PathBuf;
use sev_snp_utilities::{
create_identity_block, LaunchDigest, FamilyId, ImageId, ToBase64
};
fn main() {
let id_key_pem = PathBuf::from("./id-key.pem");
let author_key_pem = PathBuf::from("./author-key.pem");
let measurement = LaunchDigest::from_str("ffb0cb7f01a5d5b122430d66f211326ab5cf11a9a5d3189ec53adf9a60730bc63d9856fe9fe602abd662861d0ee36007");
let family_id = FamilyId::zeroes();
let image_id = ImageId::from_str("ffb0cb7f01a5d5b122430d66f211326a");
let guest_svn = 0;
let policy = 0x30000;
let (id_block, id_auth_info) = create_identity_block(measurement, family_id, image_id,
guest_svn, policy, id_key_pem.as_path(),
Some(author_key_pem.as_path()))
.expect("failed to create identity block");
println!("id_block: {}", id_block.to_base64().unwrap()); // Or call save_base64().
println!("id_auth_info: {}", id_auth_info.to_base64().unwrap());
}
对象接口
use std::path::PathBuf;
use sev_snp_utilities::{
IdBlock, LaunchDigest, FamilyId, ImageId, BlockSigner, ToBase64
};
fn main() {
let id_key_pem = PathBuf::from("./id-key.pem");
let author_key_pem = PathBuf::from("./author-key.pem");
let id_block = IdBlock::default()
.with_ld(LaunchDigest::from_str("ffb0cb7f01a5d5b122430d66f211326ab5cf11a9a5d3189ec53adf9a60730bc63d9856fe9fe602abd662861d0ee36007"))
.with_family_id(FamilyId::zeroes())
.with_image_id(ImageId::from_str("ffb0cb7f01a5d5b122430d66f211326a"))
.with_guest_svn(0)
.with_policy(0x30000);
let id_auth_info = id_block.sign(id_key_pem.as_path(), Some(author_key_pem.as_path()))
.expect("failed to sign id block");
println!("id_block: {}", id_block.to_base64().unwrap()); // Or call save_base64().
println!("id_auth_info: {}", id_auth_info.to_base64().unwrap());
}
指纹
use std::path::PathBuf;
use sev_snp_utilities::{
fingerprint_id_key_as_hex
};
fn main() {
let id_key_pem = PathBuf::from("./id-key.pem");
let author_key_pem = PathBuf::from("./author-key.pem");
let id_fingerprint = fingerprint_id_key_as_hex(id_key_pem.as_path()) // or fingerprint_id_key()
.expect("failed to fingerprint");
let author_fingerprint = fingerprint_id_key_as_hex(author_key_pem.as_path())
.expect("failed to fingerprint");
println!("id_fingerprint: {}", id_fingerprint);
println!("author_fingerprint: {}", author_fingerprint);
}
密钥派生
访客可以向固件请求从AMD SEV-SNP PSP中包含的根密钥派生的密钥。此密钥可以由访客用于其选择的任何目的,例如密封密钥(即磁盘加密)或与外部实体通信。通常的意图是,这可以用于创建只有访客知道的密钥。
准备
使用DerivedKeyRequestedBuilder
准备请求,如下所示
let options = DerivedKeyRequestBuilder::new()
.with_tcb_version()
.with_image_id()
.build();
上述示例将访客提供的TCB版本和启动时提供的映像ID混合到派生密钥中。
以下是您可以用于将不同数据混合到派生密钥中的完整构建器方法列表
with_tcb_version
:混合访客提供的TCB版本。with_svn
:混合访客的SVN。with_launch_measurement
:混合启动时访客的测量值。with_family_id
:混合启动时的家族ID。with_image_id
:混合启动时的映像ID。with_policy
:混合启动时的访客策略。
请求
将DerivedKeyRequestOptions
结构体传递给DerivedKey::request
方法,如下所示
let derived_key = DerivedKey::request(options).unwrap();
示例
以下是请求从固件请求派生密钥的MCVE
use sev_snp_utilities::guest::derived_key::get_derived_key::{DerivedKeyRequester, DerivedKeyRequestBuilder};
use sev_snp_utilities::guest::derived_key::derived_key::DerivedKey;
fn main() {
let options = DerivedKeyRequestBuilder::new()
.with_tcb_version()
.with_image_id()
.build();
println!("Options: {:?}", options);
let derived_key = DerivedKey::request(options).unwrap();
println!("Derived Key: {:?}", derived_key);
}
其他
- AMD SEV-SNP 固件ABI规范:[链接](https://www.amd.com/system/files/TechDocs/56860.pdf)
依赖关系
~15-29MB
~485K SLoC