use std::{
collections::HashMap,
io::{BufReader, Read, Seek},
};
use ecdsa::signature::Verifier as EcdsaVerifier;
use rsa::pkcs8::DecodePublicKey;
use rsa::sha2::Sha384;
use rsa::signature::Verifier as RsaVerifier;
use sev::certs::snp::ecdsa::Signature;
use sev::firmware::guest::{AttestationReport, GuestPolicy, KeyInfo, PlatformInfo, Version};
use sev::firmware::host::TcbVersion;
use x509_parser::{
certificate::X509Certificate, error::PEMError, num_bigint::BigUint, pem::Pem,
prelude::parse_x509_pem,
};
use crate::ReportSignature;
mod vcek {
use der::{asn1::Ia5String, Decode};
use oid_registry::{asn1_rs::oid, Oid, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_PKCS1_RSASSAPSS};
use p384::pkcs8::DecodePublicKey;
use sev::firmware::host::TcbVersion;
use x509_parser::{
num_bigint::BigUint,
prelude::{PEMError, Pem},
};
use super::{Error, RootStore};
const OID_PRODUCT_NAME: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .2);
const OID_BOOT_LOADER: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .1);
const OID_TEE: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .2);
const OID_SNP: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .3);
const OID_UCODE: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .8);
const OID_FMC: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .9);
#[derive(Debug, Clone, Copy)]
pub enum Product {
Milan,
Genoa,
Turin,
}
struct VcekExpected {}
struct LooksLikeVcek {
possible_verifying_key: p384::ecdsa::VerifyingKey,
product_in_cert: Product,
cert_data: Vec<u8>,
signature: Vec<u8>,
tcb_version: TcbVersion,
}
struct SignedByKnownAmdRoot {
product: Product,
verifying_key: p384::ecdsa::VerifyingKey,
}
trait Chain: Iterator<Item = Result<Pem, PEMError>> {}
impl<I: Iterator<Item = Result<Pem, PEMError>>> Chain for I {}
struct VcekChainVerifier<I: Chain, S> {
chain: I,
state: S,
}
impl<I: Chain, S> VcekChainVerifier<I, S> {
fn next_pem(&mut self) -> Result<Pem, Error> {
self.chain
.next()
.map(|result| result.map_err(Error::DecodeError))
.ok_or(Error::ChainBroken)?
}
}
impl<I: Chain> VcekChainVerifier<I, VcekExpected> {
fn new(chain: I) -> Self {
Self {
chain,
state: VcekExpected {},
}
}
}
impl<I: Chain> VcekChainVerifier<I, LooksLikeVcek> {
fn from(mut current: VcekChainVerifier<I, VcekExpected>) -> Result<Self, Error> {
let pem = current.next_pem()?;
if pem.label != "CERTIFICATE" {
return Err(Error::NotACertificate);
}
let cert = pem.parse_x509().map_err(|_| Error::ParseError)?;
if cert.is_ca() {
return Err(Error::WrongBasicConstraints);
}
if cert.serial != BigUint::from(0usize) {
return Err(Error::ChainBroken);
}
if cert.signature_algorithm.algorithm != OID_PKCS1_RSASSAPSS {
return Err(Error::ChainBroken);
}
let Some(product_name) = cert
.get_extension_unique(&OID_PRODUCT_NAME)
.ok()
.flatten()
.and_then(|ext| Ia5String::from_der(ext.value).ok())
else {
return Err(Error::ChainBroken);
};
let product = match product_name.as_str() {
s if s.starts_with("Genoa") => Product::Genoa,
s if s.starts_with("Milan") => Product::Milan,
s if s.starts_with("Turin") => Product::Turin,
_ => return Err(Error::UnknownProductName(product_name.to_string())),
};
let get_u8 = |oid: Oid| {
cert.get_extension_unique(&oid)
.ok()
.flatten()
.and_then(|ext| u8::from_der(ext.value).ok())
.ok_or(Error::ChainBroken)
};
let bootloader = get_u8(OID_BOOT_LOADER)?;
let tee = get_u8(OID_TEE)?;
let snp = get_u8(OID_SNP)?;
let microcode = get_u8(OID_UCODE)?;
let tcb_version = match product {
Product::Milan | Product::Genoa => TcbVersion {
fmc: None,
bootloader,
tee,
snp,
microcode,
},
Product::Turin => {
let fmc = get_u8(OID_FMC)?;
TcbVersion {
fmc: Some(fmc),
bootloader,
tee,
snp,
microcode,
}
}
};
if cert
.subject
.iter_common_name()
.next()
.map(|cn| cn.as_str().map_err(|_| Error::ChainBroken))
.ok_or(Error::ChainBroken)??
!= "SEV-VCEK"
{
return Err(Error::ChainBroken);
}
if cert.public_key().algorithm.algorithm != OID_KEY_TYPE_EC_PUBLIC_KEY {
return Err(Error::ChainBroken);
}
let possible_verifying_key =
p384::ecdsa::VerifyingKey::from_public_key_der(cert.public_key().raw)
.map_err(|_| Error::ChainBroken)?;
Ok(Self {
chain: current.chain,
state: LooksLikeVcek {
possible_verifying_key,
product_in_cert: product,
cert_data: cert.tbs_certificate.as_ref().to_vec(),
signature: cert.signature_value.data.to_vec(),
tcb_version,
},
})
}
}
impl<I: Chain> VcekChainVerifier<I, SignedByKnownAmdRoot> {
fn from(
mut current: VcekChainVerifier<I, LooksLikeVcek>,
root_store: &RootStore,
) -> Result<Self, Error> {
let pem = current.next_pem()?;
if pem.label != "CERTIFICATE" {
return Err(Error::NotACertificate);
}
let cert = pem.parse_x509().map_err(|_| Error::ParseError)?;
if root_store
.find_anchor_by_serial(&cert.serial)
.is_some_and(|anchor| {
anchor.has_signed(¤t.state.cert_data, ¤t.state.signature)
})
{
return Ok(Self {
chain: current.chain,
state: SignedByKnownAmdRoot {
product: current.state.product_in_cert,
verifying_key: current.state.possible_verifying_key,
},
});
}
Err(Error::ChainBroken)
}
}
#[allow(clippy::missing_errors_doc)]
pub fn verify_vcek_chain<I: Iterator<Item = Result<Pem, PEMError>>>(
root_store: &RootStore,
chain: I,
) -> Result<(Product, p384::ecdsa::VerifyingKey, TcbVersion), Error> {
let vcek_expected = VcekChainVerifier::new(chain);
let looks_like_vcek = VcekChainVerifier::<I, LooksLikeVcek>::from(vcek_expected)?;
let tcb_version = looks_like_vcek.state.tcb_version;
let signed_by_known_amd_root =
VcekChainVerifier::<I, SignedByKnownAmdRoot>::from(looks_like_vcek, root_store)?;
Ok((
signed_by_known_amd_root.state.product,
signed_by_known_amd_root.state.verifying_key,
tcb_version,
))
}
}
static GENOA_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Genoa.pem");
static MILAN_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Milan.pem");
static TURIN_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Turin.pem");
#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
pub enum Error {
DecodeError(PEMError),
ParseError,
NotACertificate,
WrongKeyUsage,
WrongBasicConstraints,
UnsupportedSignatureAlgorithm,
ChainBroken,
UnknownProductName(String),
ReportParseError,
ReportSignatureParseError,
ReportSignatureMismatch,
ReportTcbVersionMismatch,
RequirementsNotSatisfied(&'static str),
}
#[derive(Debug)]
struct TrustAnchor {
verifying_key: rsa::pss::VerifyingKey<Sha384>,
}
impl TrustAnchor {
pub fn has_signed(&self, cert: &[u8], signature: &[u8]) -> bool {
let Ok(sig) = signature.try_into() else {
return false;
};
self.verifying_key.verify(cert, &sig).is_ok()
}
}
impl<'a> TryFrom<&X509Certificate<'a>> for TrustAnchor {
type Error = String;
fn try_from(value: &X509Certificate<'a>) -> Result<Self, Self::Error> {
let public_key = rsa::RsaPublicKey::from_public_key_der(value.public_key().raw)
.map_err(|_| "Could not create RSA public key from certificat public key")?;
Ok(Self {
verifying_key: public_key.into(),
})
}
}
pub struct RootStore {
anchors: HashMap<BigUint, TrustAnchor>,
}
impl RootStore {
#[must_use]
pub fn new() -> Self {
Self {
anchors: HashMap::new(),
}
}
#[allow(clippy::missing_errors_doc)]
pub fn add_chain(&mut self, contents: &[u8]) -> Result<(), String> {
let (_, pem) = parse_x509_pem(contents).map_err(|_| "Could not decode entry")?;
let cert = pem
.parse_x509()
.map_err(|_| "Could not parse certificate")?;
if !cert.is_ca() {
return Err("Certificates in root store should be CA certificates".to_string());
}
if cert
.basic_constraints()
.unwrap_or(None)
.and_then(|ext| ext.value.path_len_constraint)
!= Some(0)
{
return Err("Leaf in root chain should have pathlen of 0".to_string());
}
let anchor = TrustAnchor::try_from(&cert)?;
if self.anchors.insert(cert.serial.clone(), anchor).is_some() {
return Err("Multiple certs with same serial".to_string());
}
Ok(())
}
fn find_anchor_by_serial(&self, serial: &BigUint) -> Option<&TrustAnchor> {
self.anchors.get(serial)
}
}
impl Default for RootStore {
fn default() -> Self {
let mut store = Self::new();
store
.add_chain(MILAN_ROOT_CHAIN)
.expect("Root chain should be valid");
store
.add_chain(GENOA_ROOT_CHAIN)
.expect("Root chain should be valid");
store
.add_chain(TURIN_ROOT_CHAIN)
.expect("Root chain should be valid");
store
}
}
type FnMicrocode = fn(vcek::Product, &AttestationReport) -> Result<(), &'static str>;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct RequirementsBuilder {
genoa: ProductRequirements,
milan: ProductRequirements,
turin: ProductRequirements,
require_alias_check: bool,
check_microcode: Option<FnMicrocode>,
}
impl RequirementsBuilder {
#[must_use]
pub fn build(self) -> Requirements {
Requirements {
genoa: self.genoa,
milan: self.milan,
turin: self.turin,
require_alias_check: self.require_alias_check,
check_microcode: self.check_microcode,
}
}
pub fn require_alias_check(mut self) -> Self {
self.require_alias_check = true;
if self.genoa.min_committed_version.unwrap_or_default()
< Requirements::SB_3015_MIN_GENOA_VERSION
{
self.genoa.min_committed_version = Some(Requirements::SB_3015_MIN_GENOA_VERSION);
}
if self.genoa.min_committed_tcb_snp.unwrap_or_default()
< Requirements::SB_3015_MIN_GENOA_SNP_SVN
{
self.genoa.min_committed_tcb_snp = Some(Requirements::SB_3015_MIN_GENOA_SNP_SVN);
}
if self.milan.min_committed_version.unwrap_or_default()
< Requirements::SB_3015_MIN_MILAN_VERSION
{
self.milan.min_committed_version = Some(Requirements::SB_3015_MIN_MILAN_VERSION);
}
if self.milan.min_committed_tcb_snp.unwrap_or_default()
< Requirements::SB_3015_MIN_MILAN_SNP_SVN
{
self.milan.min_committed_tcb_snp = Some(Requirements::SB_3015_MIN_MILAN_SNP_SVN);
}
self
}
pub fn min_committed_version_for_genoa(mut self, version: (u8, u8, u8)) -> Self {
if version > self.genoa.min_committed_version.unwrap_or_default() {
self.genoa.min_committed_version = Some(version);
}
self
}
pub fn min_committed_version_for_milan(mut self, version: (u8, u8, u8)) -> Self {
if version > self.milan.min_committed_version.unwrap_or_default() {
self.milan.min_committed_version = Some(version);
}
self
}
pub fn min_committed_version_for_turin(mut self, version: (u8, u8, u8)) -> Self {
if version > self.turin.min_committed_version.unwrap_or_default() {
self.turin.min_committed_version = Some(version);
}
self
}
pub fn min_committed_snp_svn_for_milan(mut self, snp: u8) -> Self {
if snp > self.milan.min_committed_tcb_snp.unwrap_or_default() {
self.milan.min_committed_tcb_snp = Some(snp);
}
self
}
pub fn min_committed_snp_svn_for_genoa(mut self, snp: u8) -> Self {
if snp > self.genoa.min_committed_tcb_snp.unwrap_or_default() {
self.genoa.min_committed_tcb_snp = Some(snp);
}
self
}
pub fn min_committed_snp_svn_for_turin(mut self, snp: u8) -> Self {
if snp > self.turin.min_committed_tcb_snp.unwrap_or_default() {
self.turin.min_committed_tcb_snp = Some(snp);
}
self
}
pub fn require_sb_7033_mitigations(mut self) -> Self {
self.check_microcode = Some(Requirements::sb_7033_microcode_check);
self
}
}
#[derive(Debug, Clone, Default)]
#[allow(clippy::struct_field_names)]
struct ProductRequirements {
min_committed_version: Option<(u8, u8, u8)>,
min_committed_tcb_snp: Option<u8>,
min_mit_vector: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct Requirements {
genoa: ProductRequirements,
milan: ProductRequirements,
turin: ProductRequirements,
require_alias_check: bool,
check_microcode: Option<FnMicrocode>,
}
impl Default for Requirements {
fn default() -> Self {
Self::sb_3023_mitigations()
}
}
macro_rules! microcode_check {
($name:ident, $(($product:pat, $cpu_family:pat) => $min_rev:expr),+ $(,)?) => {
fn $name(
product: vcek::Product,
report: &AttestationReport,
) -> Result<(), &'static str> {
if let Some(cpu_family) = report.cpuid_mod_id.zip(report.cpuid_step) {
let min_rev = match (product, cpu_family) {
$(($product, $cpu_family) => $min_rev,)+
_ => return Err("Report doesn't match any known CPU family"),
};
if report.committed_tcb.microcode < min_rev {
return Err("Committed TCB: Microcode version too small");
}
} else {
return Err("Could not verify microcode version: Missing values for CPU family");
}
Ok(())
}
};
}
impl Requirements {
const SB_3023_MIN_GENOA_MIT_VEC: u64 = (1 << 0) | (1 << 1);
const SB_3023_MIN_TURIN_MIT_VEC: u64 = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5);
const SB_3020_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x31);
const SB_3020_MIN_GENOA_SNP_SVN: u8 = 0x1B;
const SB_3020_MIN_GENOA_MIT_VEC: u64 = 1 << 1;
const SB_3020_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x23);
const SB_3020_MIN_MILAN_SNP_SVN: u8 = 0x1B;
const SB_3020_MIN_MILAN_MIT_VEC: u64 = 1 << 1;
const SB_3020_MIN_TURIN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x41);
const SB_3020_MIN_TURIN_SNP_SVN: u8 = 0x04;
const SB_3020_MIN_TURIN_MIT_VEC: u64 = 1 << 0;
const SB_3019_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x28);
const SB_3019_MIN_GENOA_SNP_SVN: u8 = 0x17;
const SB_3019_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x18);
const SB_3019_MIN_MILAN_SNP_SVN: u8 = 0x18;
const SB_3019_MIN_TURIN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x3b);
const SB_3015_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x26);
const SB_3015_MIN_GENOA_SNP_SVN: u8 = 0x16;
const SB_3015_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x16);
const SB_3015_MIN_MILAN_SNP_SVN: u8 = 0x17;
#[must_use]
pub fn sb_3023_mitigations() -> Self {
let sb_3020 = Self::sb_3020_mitigations();
Self {
genoa: ProductRequirements {
min_mit_vector: Some(Self::SB_3023_MIN_GENOA_MIT_VEC),
..sb_3020.genoa
},
turin: ProductRequirements {
min_mit_vector: Some(Self::SB_3023_MIN_TURIN_MIT_VEC),
..sb_3020.turin
},
check_microcode: Some(Self::sb_3023_microcode_check),
..sb_3020
}
}
#[must_use]
pub fn sb_3020_mitigations() -> Self {
Self {
genoa: ProductRequirements {
min_committed_version: Some(Self::SB_3020_MIN_GENOA_VERSION),
min_committed_tcb_snp: Some(Self::SB_3020_MIN_GENOA_SNP_SVN),
min_mit_vector: Some(Self::SB_3020_MIN_GENOA_MIT_VEC),
},
milan: ProductRequirements {
min_committed_version: Some(Self::SB_3020_MIN_MILAN_VERSION),
min_committed_tcb_snp: Some(Self::SB_3020_MIN_MILAN_SNP_SVN),
min_mit_vector: Some(Self::SB_3020_MIN_MILAN_MIT_VEC),
},
turin: ProductRequirements {
min_committed_version: Some(Self::SB_3020_MIN_TURIN_VERSION),
min_committed_tcb_snp: Some(Self::SB_3020_MIN_TURIN_SNP_SVN),
min_mit_vector: Some(Self::SB_3020_MIN_TURIN_MIT_VEC),
},
require_alias_check: true,
check_microcode: Some(Self::sb_3020_microcode_check),
}
}
#[must_use]
pub fn sb_3019_mitigations() -> Self {
Self {
genoa: ProductRequirements {
min_committed_version: Some(Self::SB_3019_MIN_GENOA_VERSION),
min_committed_tcb_snp: Some(Self::SB_3019_MIN_GENOA_SNP_SVN),
..Default::default()
},
milan: ProductRequirements {
min_committed_version: Some(Self::SB_3019_MIN_MILAN_VERSION),
min_committed_tcb_snp: Some(Self::SB_3019_MIN_MILAN_SNP_SVN),
..Default::default()
},
turin: ProductRequirements {
min_committed_version: Some(Self::SB_3019_MIN_TURIN_VERSION),
..Default::default()
},
require_alias_check: true,
check_microcode: None,
}
}
#[must_use]
pub fn sb_3015_mitigations() -> Self {
Self {
genoa: ProductRequirements {
min_committed_version: Some(Self::SB_3015_MIN_GENOA_VERSION),
min_committed_tcb_snp: Some(Self::SB_3015_MIN_GENOA_SNP_SVN),
..Default::default()
},
milan: ProductRequirements {
min_committed_version: Some(Self::SB_3015_MIN_MILAN_VERSION),
min_committed_tcb_snp: Some(Self::SB_3015_MIN_MILAN_SNP_SVN),
..Default::default()
},
turin: ProductRequirements::default(),
require_alias_check: true,
check_microcode: None,
}
}
#[must_use]
pub fn sb_3011_mitigations() -> Self {
Self {
genoa: ProductRequirements {
min_committed_version: Some((0x1, 0x37, 0x24)),
min_committed_tcb_snp: Some(0x16),
..Default::default()
},
milan: ProductRequirements {
min_committed_version: Some((0x1, 0x37, 0x14)),
min_committed_tcb_snp: Some(0x17),
..Default::default()
},
turin: ProductRequirements::default(),
require_alias_check: false,
check_microcode: None,
}
}
microcode_check!(sb_3023_microcode_check,
(vcek::Product::Milan, (1, 1)) => 0xDE,
(vcek::Product::Milan, (1, 2)) => 0x47,
(vcek::Product::Genoa, (0x11, 1)) => 0x56,
(vcek::Product::Genoa, (0x11, 2)) => 0x51,
(vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
(vcek::Product::Turin, (2, 1)) => 0x51,
(vcek::Product::Turin, (0x11, 0)) => 0x4E,
);
microcode_check!(sb_3020_microcode_check,
(vcek::Product::Milan, (1, 1)) => 0xDE,
(vcek::Product::Milan, (1, 2)) => 0x45,
(vcek::Product::Genoa, (0x11, 1)) => 0x56,
(vcek::Product::Genoa, (0x11, 2)) => 0x51,
(vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
(vcek::Product::Turin, (2, 1)) => 0x50,
(vcek::Product::Turin, (0x11, 0)) => 0x4D,
);
microcode_check!(sb_7033_microcode_check,
(vcek::Product::Milan, (1, 1)) => 0xDB,
(vcek::Product::Milan, (1, 2)) => 0x44,
(vcek::Product::Genoa, (0x11, 1)) => 0x54,
(vcek::Product::Genoa, (0x11, 2)) => 0x4F,
(vcek::Product::Genoa, (0xA0, 2)) => 0x19,
(vcek::Product::Turin, (2, 1)) => 0x47,
);
fn verify(
&self,
product: vcek::Product,
report: &AttestationReport,
) -> Result<(), &'static str> {
if report.policy.debug_allowed() {
return Err("Debug is enabled");
}
if report.policy.migrate_ma_allowed() {
return Err("Guest policy allows a migration agent");
}
if report.vmpl > 3 {
return Err("VMPL is not <= 3");
}
if report.key_info.signing_key() != 0 {
return Err("Signing key is not VCEK");
}
if self.require_alias_check && !report.plat_info.alias_check_complete() {
return Err("Alias check complete is false");
}
let requirements = match product {
vcek::Product::Milan => &self.milan,
vcek::Product::Genoa => &self.genoa,
vcek::Product::Turin => &self.turin,
};
if let Some(min_wanted) = requirements.min_committed_version {
if (
report.committed.major,
report.committed.minor,
report.committed.build,
) < min_wanted
{
return Err("Firmware version too small");
}
}
if let Some(min_tcb_snp) = requirements.min_committed_tcb_snp {
if report.committed_tcb.snp < min_tcb_snp {
return Err("Committed TCB: SNP patch level too small");
}
}
if let Some(min_mit_vec) = requirements.min_mit_vector {
match report.current_mit_vector {
None => return Err("Current mitigation vector: not present in report"),
Some(v) if v & min_mit_vec != min_mit_vec => {
return Err("Current mitigation vector: required mitigation bits not set")
}
Some(_) => {}
}
match report.launch_mit_vector {
None => return Err("Launch mitigation vector: not present in report"),
Some(v) if v & min_mit_vec != min_mit_vec => {
return Err("Launch mitigation vector: required mitigation bits not set")
}
Some(_) => {}
}
}
if let Some(check) = self.check_microcode {
check(product, report)?;
}
Ok(())
}
}
fn parse_report(bytes: &[u8]) -> Option<AttestationReport> {
if bytes.len() < 1184 {
return None;
}
let read_u32 = |off: usize| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
let read_u64 = |off: usize| u64::from_le_bytes(bytes[off..off + 8].try_into().unwrap());
let version = read_u32(0x00);
let turin_like = if version >= 3 {
bytes[0x188] != 0x19
} else {
let chip_id = &bytes[0x1A0..0x1E0];
if chip_id == [0u8; 64] {
return None;
}
chip_id[8..] == [0u8; 56]
};
let parse_tcb = |off: usize| -> TcbVersion {
let b = &bytes[off..off + 8];
if turin_like {
TcbVersion {
fmc: Some(b[0]),
bootloader: b[1],
tee: b[2],
snp: b[3],
microcode: b[7],
}
} else {
TcbVersion {
fmc: None,
bootloader: b[0],
tee: b[1],
snp: b[6],
microcode: b[7],
}
}
};
let (cpuid_fam_id, cpuid_mod_id, cpuid_step) = if version >= 3 {
(Some(bytes[0x188]), Some(bytes[0x189]), Some(bytes[0x18A]))
} else {
(None, None, None)
};
let parse_version = |off: usize| Version {
build: bytes[off],
minor: bytes[off + 1],
major: bytes[off + 2],
};
let r: [u8; 72] = bytes[0x2A0..0x2E8].try_into().ok()?;
let s: [u8; 72] = bytes[0x2E8..0x330].try_into().ok()?;
Some(AttestationReport {
version,
guest_svn: read_u32(0x04),
policy: GuestPolicy(read_u64(0x08)),
family_id: bytes[0x10..0x20].try_into().ok()?,
image_id: bytes[0x20..0x30].try_into().ok()?,
vmpl: read_u32(0x30),
sig_algo: read_u32(0x34),
current_tcb: parse_tcb(0x38),
plat_info: PlatformInfo(read_u64(0x40)),
key_info: KeyInfo(read_u32(0x48)),
report_data: bytes[0x50..0x90].try_into().ok()?,
measurement: bytes[0x90..0xC0].try_into().ok()?,
host_data: bytes[0xC0..0xE0].try_into().ok()?,
id_key_digest: bytes[0xE0..0x110].try_into().ok()?,
author_key_digest: bytes[0x110..0x140].try_into().ok()?,
report_id: bytes[0x140..0x160].try_into().ok()?,
report_id_ma: bytes[0x160..0x180].try_into().ok()?,
reported_tcb: parse_tcb(0x180),
cpuid_fam_id,
cpuid_mod_id,
cpuid_step,
chip_id: bytes[0x1A0..0x1E0].try_into().ok()?,
committed_tcb: parse_tcb(0x1E0),
current: parse_version(0x1E8),
committed: parse_version(0x1EC),
launch_tcb: parse_tcb(0x1F0),
launch_mit_vector: (version >= 5).then(|| read_u64(0x1F8)),
current_mit_vector: (version >= 5).then(|| read_u64(0x200)),
signature: Signature::new(r, s),
})
}
#[allow(clippy::missing_errors_doc)]
pub fn verify_report<R: Read + Seek>(
report_contents: &[u8],
vcek_chain: R,
root_store: &RootStore,
verifications: &Requirements,
) -> Result<AttestationReport, Error> {
if report_contents.len() < 1184 {
return Err(Error::ReportParseError);
}
let report = parse_report(report_contents).ok_or(Error::ReportParseError)?;
let sig = ReportSignature::try_from(&report).map_err(|_| Error::ReportParseError)?;
let ecdsa_sig =
p384::ecdsa::Signature::try_from(&sig).map_err(|_| Error::ReportSignatureParseError)?;
let (product, key, tcb_version) = vcek::verify_vcek_chain(
root_store,
Pem::iter_from_reader(BufReader::new(vcek_chain)),
)?;
if report.reported_tcb != tcb_version {
return Err(Error::ReportTcbVersionMismatch);
}
if key.verify(&report_contents[..0x2a0], &ecdsa_sig).is_ok() {
verifications
.verify(product, &report)
.map(|()| report)
.map_err(Error::RequirementsNotSatisfied)
} else {
Err(Error::ReportSignatureMismatch)
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::Cursor, path::PathBuf};
use ecdsa::{elliptic_curve::Generate, signature::Signer};
use p384::{ecdsa::SigningKey, NistP384};
use rand::{rand_core::UnwrapErr, rngs::SysRng};
use sev::{
certs::snp::ecdsa::Signature,
firmware::guest::{AttestationReport, GuestPolicy},
parser::ByteParser,
};
use x509_parser::{num_bigint::BigUint, prelude::Pem};
use crate::verification::{Error, Requirements, RootStore};
use super::{vcek, vcek::verify_vcek_chain, verify_report, RequirementsBuilder};
macro_rules! assert_m {
($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
match $left {
$( $pattern )|+ $( if $guard )? => {}
ref left_val => {
panic!(
"Expected match where there is none\nleft: {left_val:?}\nright: {}",
stringify!($($pattern)|+ $(if $guard)?),
);
}
}
};
}
#[test]
fn verify_report_returns_ok_for_valid_report_and_chain() {
let result = verify_report(
&hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap(),
File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
&RootStore::default(),
&RequirementsBuilder::default().build(),
);
assert!(result.is_ok());
}
#[test]
fn verify_report_returns_ok_for_valid_report_and_chain_with_sb_3015_mitigations() {
let result = verify_report(
&genoa_report_alias_check_fixture_bytes(),
genoa_vcek_chain_alias_check_fixture(),
&RootStore::default(),
&Requirements::sb_3015_mitigations(),
);
assert!(result.is_ok());
}
#[test]
fn default_requirements_require_sb_3023_mitigations() {
let result = verify_report(
&hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap(),
File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
&RootStore::default(),
&Requirements::default(),
);
assert_m!(
result,
Err(Error::RequirementsNotSatisfied(
"Alias check complete is false"
))
);
}
#[test]
fn verify_vcek_chain_returns_ok_for_valid_chain() {
let result = verify_vcek_chain(
&RootStore::default(),
Pem::iter_from_buffer(include_bytes!("../fixtures/tests/valid_vcek_chain.data")),
);
assert!(result.is_ok());
}
#[test]
fn verify_vcek_chain_returns_err_for_selfsigned_chain() {
let result = verify_vcek_chain(
&RootStore::default(),
Pem::iter_from_buffer(include_bytes!(
"../fixtures/tests/selfsigned_vcek_chain.data"
)),
);
assert!(result.is_err());
}
#[test]
fn verify_vcek_chain_returns_err_for_invalid_chain() {
let result = verify_vcek_chain(
&RootStore::default(),
Pem::iter_from_buffer(include_bytes!("../fixtures/tests/vcek_wrong_order.data")),
);
assert!(result.is_err());
}
#[test]
fn default_root_store_finds_milan_serial() {
let root_store = RootStore::default();
assert!(root_store
.find_anchor_by_serial(&BigUint::from(0x10001u32))
.is_some());
}
#[test]
fn root_store_returns_none_for_unknown_serial() {
let root_store = RootStore::new();
assert!(root_store
.find_anchor_by_serial(&BigUint::from(0x1234u32))
.is_none());
}
#[test]
fn invalid_report_signature_returns_err() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.signature = Signature::new([0u8; 72], [0u8; 72]);
let result = verify_report(
&report.to_bytes().unwrap(),
File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
&RootStore::default(),
&Requirements::default(),
);
assert_m!(result, Err(Error::ReportSignatureParseError));
}
#[test]
fn wrong_report_signature_returns_err() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
let signing_key = SigningKey::generate_from_rng(&mut UnwrapErr(SysRng));
let signature = signing_key.sign(b"wurzelpfropf");
report.signature = create_signature_from_signature(&signature);
let result = verify_report(
&report.to_bytes().unwrap(),
vcek_chain_fixture(),
&RootStore::default(),
&Requirements::default(),
);
assert_m!(result, Err(Error::ReportSignatureMismatch));
}
#[test]
fn wrong_tcb_version_returns_err() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
let signing_key = SigningKey::generate_from_rng(&mut UnwrapErr(SysRng));
let signature = signing_key.sign(b"wurzelpfropf");
report.signature = create_signature_from_signature(&signature);
let result = verify_report(
&report.to_bytes().unwrap(),
File::open(PathBuf::from(
"./fixtures/tests/valid_vcek_wrong_tcb_version.data",
))
.unwrap(),
&RootStore::default(),
&Requirements::default(),
);
assert_m!(result, Err(Error::ReportTcbVersionMismatch));
}
#[test]
fn debug_enabled_returns_err() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.policy.set_debug_allowed(true);
assert_m!(
RequirementsBuilder::default()
.build()
.verify(vcek::Product::Milan, &report),
Err("Debug is enabled")
);
}
fn default_guest_policy() -> GuestPolicy {
GuestPolicy(0x3_0000)
}
#[test]
fn default_guest_policy_is_accepted() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.policy = default_guest_policy();
assert_m!(
RequirementsBuilder::default()
.build()
.verify(vcek::Product::Milan, &report),
Ok(())
);
}
#[test]
fn migration_agent_allowed_returns_err() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.policy = default_guest_policy();
report.policy.set_migrate_ma_allowed(true);
assert_m!(
RequirementsBuilder::default()
.build()
.verify(vcek::Product::Milan, &report),
Err("Guest policy allows a migration agent")
);
}
#[test]
fn non_guest_vmpl_returns_err() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.vmpl = 4;
assert_m!(
RequirementsBuilder::default()
.build()
.verify(vcek::Product::Milan, &report),
Err("VMPL is not <= 3")
);
}
#[test]
fn signing_key_not_vcek_returns_err() {
let mut report_bytes = report_fixture_bytes();
report_bytes[0x48] |= 1 << 2;
let report = AttestationReport::from_bytes(&report_bytes).unwrap();
assert_m!(
RequirementsBuilder::default()
.build()
.verify(vcek::Product::Milan, &report),
Err("Signing key is not VCEK")
);
}
#[test]
fn requirements_check_for_minimal_committed_snp_version_milan() {
fn verify(version: (u8, u8, u8)) -> Result<AttestationReport, Error> {
verify_report(
&report_fixture_bytes(),
vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default()
.min_committed_version_for_milan(version)
.build(),
)
}
fn version_too_small(version: (u8, u8, u8)) {
assert_m!(
verify(version),
Err(Error::RequirementsNotSatisfied(
"Firmware version too small"
))
);
}
version_too_small((1, 52, 5));
version_too_small((1, 53, 4));
version_too_small((2, 0, 0));
assert_m!(verify((1, 52, 4)), Ok(_));
assert_m!(verify((1, 50, 0)), Ok(_));
}
#[test]
fn requirements_check_for_minimal_committed_snp_version_genoa() {
fn verify(version: (u8, u8, u8)) -> Result<AttestationReport, Error> {
verify_report(
&genoa_report_fixture_bytes(),
genoa_vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default()
.min_committed_version_for_genoa(version)
.build(),
)
}
fn version_too_small(version: (u8, u8, u8)) {
assert_m!(
verify(version),
Err(Error::RequirementsNotSatisfied(
"Firmware version too small"
))
);
}
version_too_small((1, 55, 22));
version_too_small((1, 56, 21));
version_too_small((2, 0, 0));
assert_m!(verify((1, 55, 21)), Ok(_));
assert_m!(verify((1, 54, 0)), Ok(_));
}
#[test]
fn requirements_check_for_minimal_committed_snp_svn_milan() {
fn verify(svn: u8) -> Result<AttestationReport, Error> {
verify_report(
&report_fixture_bytes(),
vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default()
.min_committed_snp_svn_for_milan(svn)
.build(),
)
}
assert_m!(verify(8), Ok(_));
assert_m!(
verify(9),
Err(Error::RequirementsNotSatisfied(
"Committed TCB: SNP patch level too small"
))
);
}
#[test]
fn requirements_check_for_minimal_committed_snp_svn_genoa() {
fn verify(svn: u8) -> Result<AttestationReport, Error> {
verify_report(
&genoa_report_fixture_bytes(),
genoa_vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default()
.min_committed_snp_svn_for_genoa(svn)
.build(),
)
}
assert_m!(verify(14), Ok(_));
assert_m!(
verify(15),
Err(Error::RequirementsNotSatisfied(
"Committed TCB: SNP patch level too small"
))
);
}
#[test]
fn requirements_check_for_alias_check_milan() {
let result = verify_report(
&report_fixture_bytes(),
vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default().require_alias_check().build(),
);
assert_m!(
result,
Err(Error::RequirementsNotSatisfied(
"Alias check complete is false"
))
);
}
#[test]
fn requirements_check_for_alias_check_genoa() {
let result = verify_report(
&genoa_report_fixture_bytes(),
genoa_vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default().require_alias_check().build(),
);
assert_m!(
result,
Err(Error::RequirementsNotSatisfied(
"Alias check complete is false"
))
);
}
#[test]
fn requirements_check_for_alias_check_genoa_ok() {
let result = verify_report(
&genoa_report_alias_check_fixture_bytes(),
genoa_vcek_chain_alias_check_fixture(),
&RootStore::default(),
&RequirementsBuilder::default().require_alias_check().build(),
);
assert!(result.is_ok());
}
#[test]
fn requirements_check_microcode_missing_cpu_fields() {
let result = verify_report(
&report_fixture_bytes(),
vcek_chain_fixture(),
&RootStore::default(),
&RequirementsBuilder::default()
.require_sb_7033_mitigations()
.build(),
);
assert_m!(
result,
Err(Error::RequirementsNotSatisfied(
"Could not verify microcode version: Missing values for CPU family"
)),
);
}
#[test]
fn verify_report_passes_for_authentic_genoa_v5_report() {
let result = verify_report(
&hex::decode(include_str!("../fixtures/tests/report_v5.data").trim()).unwrap(),
File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain_v5.data")).unwrap(),
&RootStore::default(),
&Requirements::default(),
);
assert!(result.is_ok());
}
#[test]
fn verify_vcek_chain_returns_ok_for_milan_with_microcode_above_128() {
let (product, _key, tcb_version) = verify_vcek_chain(
&RootStore::default(),
Pem::iter_from_buffer(include_bytes!(
"../fixtures/tests/valid_vcek_chain_milan.data"
)),
)
.unwrap();
assert_m!(product, vcek::Product::Milan);
assert_eq!(tcb_version.microcode, 0xDB);
}
#[test]
fn verify_report_returns_ok_for_milan_with_microcode_above_128() {
let result = verify_report(
&hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap(),
Cursor::new(include_str!(
"../fixtures/tests/valid_vcek_chain_milan.data"
)),
&RootStore::default(),
&RequirementsBuilder::default().build(),
);
assert!(result.is_ok());
}
#[test]
fn verify_vcek_chain_returns_ok_for_turin() {
let (product, _key, tcb_version) = verify_vcek_chain(
&RootStore::default(),
Pem::iter_from_buffer(include_bytes!(
"../fixtures/tests/valid_vcek_chain_turin.data"
)),
)
.unwrap();
assert_m!(product, vcek::Product::Turin);
assert_eq!(tcb_version.microcode, 0x47);
}
#[test]
fn verify_report_returns_ok_for_turin() {
let result = verify_report(
&hex::decode(include_str!("../fixtures/tests/report_turin.data").trim()).unwrap(),
Cursor::new(include_str!(
"../fixtures/tests/valid_vcek_chain_turin.data"
)),
&RootStore::default(),
&RequirementsBuilder::default().build(),
);
assert!(result.is_ok());
}
#[test]
fn requirements_builder_doesnt_lower_version() {
let requirements = RequirementsBuilder::default()
.min_committed_version_for_genoa((1, 52, 1))
.min_committed_version_for_genoa((1, 50, 0))
.min_committed_version_for_milan((1, 52, 1))
.min_committed_version_for_milan((1, 50, 0))
.min_committed_version_for_turin((1, 52, 1))
.min_committed_version_for_turin((1, 50, 0))
.build();
assert_eq!(requirements.genoa.min_committed_version, Some((1, 52, 1)));
assert_eq!(requirements.milan.min_committed_version, Some((1, 52, 1)));
assert_eq!(requirements.turin.min_committed_version, Some((1, 52, 1)));
}
#[test]
fn requirements_builder_sets_higher_version() {
let requirements = RequirementsBuilder::default()
.min_committed_version_for_genoa((1, 52, 1))
.min_committed_version_for_genoa((1, 52, 2))
.min_committed_version_for_milan((1, 52, 1))
.min_committed_version_for_milan((1, 52, 2))
.min_committed_version_for_turin((1, 52, 1))
.min_committed_version_for_turin((1, 52, 2))
.build();
assert_eq!(requirements.genoa.min_committed_version, Some((1, 52, 2)));
assert_eq!(requirements.milan.min_committed_version, Some((1, 52, 2)));
assert_eq!(requirements.turin.min_committed_version, Some((1, 52, 2)));
}
fn create_signature_from_signature(signature: &ecdsa::Signature<NistP384>) -> Signature {
let mut r: [u8; 72] = [0u8; 72];
r[24..].copy_from_slice(&signature.r().to_bytes());
r.reverse();
let mut s: [u8; 72] = [0u8; 72];
s[24..].copy_from_slice(&signature.s().to_bytes());
s.reverse();
Signature::new(r, s)
}
fn genoa_report_fixture_bytes() -> Vec<u8> {
hex::decode(include_bytes!("../fixtures/tests/report_genoa.data")).unwrap()
}
fn genoa_report_alias_check_fixture_bytes() -> Vec<u8> {
hex::decode(include_bytes!(
"../fixtures/tests/report_genoa_alias_check.data"
))
.unwrap()
}
fn report_fixture_bytes() -> Vec<u8> {
hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap()
}
fn genoa_vcek_chain_fixture() -> impl std::io::Read + std::io::Seek {
File::open(PathBuf::from(
"./fixtures/tests/valid_vcek_chain_genoa.data",
))
.unwrap()
}
fn genoa_vcek_chain_alias_check_fixture() -> impl std::io::Read + std::io::Seek {
File::open(PathBuf::from(
"./fixtures/tests/valid_vcek_chain_genoa_alias_check.data",
))
.unwrap()
}
fn vcek_chain_fixture() -> impl std::io::Read + std::io::Seek {
File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap()
}
#[test]
fn parser_matches_sev_parser_milan() {
let bytes =
hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
let expected = AttestationReport::from_bytes(&bytes).unwrap();
let actual = super::parse_report(&bytes).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn parser_matches_sev_parser_genoa() {
let bytes = genoa_report_fixture_bytes();
let expected = AttestationReport::from_bytes(&bytes).unwrap();
let actual = super::parse_report(&bytes).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn parser_matches_sev_parser_v5() {
let bytes = hex::decode(include_str!("../fixtures/tests/report_v5.data").trim()).unwrap();
let expected = AttestationReport::from_bytes(&bytes).unwrap();
let actual = super::parse_report(&bytes).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn parser_matches_sev_parser_turin() {
let bytes =
hex::decode(include_str!("../fixtures/tests/report_turin.data").trim()).unwrap();
let expected = AttestationReport::from_bytes(&bytes).unwrap();
let actual = super::parse_report(&bytes).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn parse_report_ignores_reserved_bytes_for_unknown_version() {
let mut bytes =
hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
bytes[0..4].copy_from_slice(&42u32.to_le_bytes());
bytes[0x208..0x2A0].fill(0xAB);
let report = super::parse_report(&bytes).unwrap();
assert_eq!(report.version, 42);
assert!(report.current_mit_vector.is_some());
}
#[test]
fn parse_report_truncated() {
let bytes =
hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
assert!(super::parse_report(&bytes[..1183]).is_none());
}
#[test]
fn microcode_check_macro_accepts_above_minimum() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.cpuid_mod_id = Some(1);
report.cpuid_step = Some(1);
report.committed_tcb.microcode = 0xDE;
assert!(Requirements::sb_3023_microcode_check(vcek::Product::Milan, &report).is_ok());
assert!(Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report).is_ok());
assert!(Requirements::sb_7033_microcode_check(vcek::Product::Milan, &report).is_ok());
}
#[test]
fn microcode_check_macro_rejects_below_minimum() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.cpuid_mod_id = Some(1);
report.cpuid_step = Some(1);
report.committed_tcb.microcode = 0xDD;
assert_eq!(
Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
Err("Committed TCB: Microcode version too small"),
);
}
#[test]
fn microcode_check_macro_rejects_unknown_cpu_family() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.cpuid_mod_id = Some(0xFF);
report.cpuid_step = Some(0xFF);
assert_eq!(
Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
Err("Report doesn't match any known CPU family"),
);
}
#[test]
fn microcode_check_macro_rejects_missing_cpu_fields() {
let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
report.cpuid_mod_id = None;
report.cpuid_step = None;
assert_eq!(
Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
Err("Could not verify microcode version: Missing values for CPU family"),
);
}
fn sb_3020_passing_genoa_report() -> AttestationReport {
use sev::firmware::{
guest::{PlatformInfo, Version},
host::TcbVersion,
};
AttestationReport {
policy: default_guest_policy(),
plat_info: PlatformInfo(1 << 5), committed: Version::new(1, 0x37, 0x31),
committed_tcb: TcbVersion {
snp: 0x1b,
microcode: 0x56,
..Default::default()
},
cpuid_mod_id: Some(0x11),
cpuid_step: Some(1),
current_mit_vector: Some(Requirements::SB_3020_MIN_GENOA_MIT_VEC),
launch_mit_vector: Some(Requirements::SB_3020_MIN_GENOA_MIT_VEC),
..Default::default()
}
}
#[test]
fn requirements_check_mit_vector_missing() {
let mut report = sb_3020_passing_genoa_report();
report.current_mit_vector = None;
assert_eq!(
Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
Err("Current mitigation vector: not present in report"),
);
}
#[test]
fn requirements_check_mit_vector_insufficient() {
let mut report = sb_3020_passing_genoa_report();
report.current_mit_vector = Some(0);
assert_eq!(
Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
Err("Current mitigation vector: required mitigation bits not set"),
);
}
#[test]
fn requirements_check_mit_vector_valid() {
let report = sb_3020_passing_genoa_report();
assert!(Requirements::sb_3020_mitigations()
.verify(vcek::Product::Genoa, &report)
.is_ok());
}
#[test]
fn requirements_check_launch_mit_vector_missing() {
let mut report = sb_3020_passing_genoa_report();
report.launch_mit_vector = None;
assert_eq!(
Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
Err("Launch mitigation vector: not present in report"),
);
}
#[test]
fn requirements_check_launch_mit_vector_insufficient() {
let mut report = sb_3020_passing_genoa_report();
report.launch_mit_vector = Some(0);
assert_eq!(
Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
Err("Launch mitigation vector: required mitigation bits not set"),
);
}
#[test]
fn requirements_check_sb_3023_mit_vector_requires_extra_genoa_bits() {
let mut report = sb_3020_passing_genoa_report();
report.committed_tcb.microcode = 0x58;
assert_eq!(
Requirements::sb_3023_mitigations().verify(vcek::Product::Genoa, &report),
Err("Current mitigation vector: required mitigation bits not set"),
);
report.current_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
report.launch_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
assert!(Requirements::sb_3023_mitigations()
.verify(vcek::Product::Genoa, &report)
.is_ok());
}
#[test]
fn requirements_check_sb_3023_launch_mit_vector_checked_independently_of_current() {
let mut report = sb_3020_passing_genoa_report();
report.committed_tcb.microcode = 0x58;
report.current_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
assert_eq!(
Requirements::sb_3023_mitigations().verify(vcek::Product::Genoa, &report),
Err("Launch mitigation vector: required mitigation bits not set"),
);
}
}