1 个稳定版本

1.4.4 2023 年 10 月 16 日

#1251硬件支持

Apache-2.0

210KB
3.5K SLoC

sev-snp-utils

AMD SEV-SNP Rust 工具和原语。

测试

而不是运行

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_utils::{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_utils::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_utils::{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_utils::{
    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_utils::{
    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");
}

身份

准备

在您可以生成 IdBlockIdAuthInfo 之前,您首先需要创建一些 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_utils::{
    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_utils::{
    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_utils::{
    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_utils::guest::derived_key::get_derived_key::{DerivedKeyRequester, DerivedKeyRequestBuilder};
use sev_snp_utils::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);
}

杂项

依赖项

~15–30MB
~499K SLoC