Skip to main content

clerk_report/
verification.rs

1use std::{
2    collections::HashMap,
3    io::{BufReader, Read, Seek},
4};
5
6use ecdsa::signature::Verifier as EcdsaVerifier;
7use rsa::pkcs8::DecodePublicKey;
8use rsa::sha2::Sha384;
9use rsa::signature::Verifier as RsaVerifier;
10use sev::certs::snp::ecdsa::Signature;
11use sev::firmware::guest::{AttestationReport, GuestPolicy, KeyInfo, PlatformInfo, Version};
12use sev::firmware::host::TcbVersion;
13use x509_parser::{
14    certificate::X509Certificate, error::PEMError, num_bigint::BigUint, pem::Pem,
15    prelude::parse_x509_pem,
16};
17
18use crate::ReportSignature;
19
20mod vcek {
21    //! This module contains the code for verifying a Versioned Chip Endorsement Key's certificate
22    //! chain (VCEK in short).
23    //!
24    //! Our verification code can make assumptions that a general web PKI can't do:
25    //!
26    //! - According to <https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf> 2.1, we always have a
27    //!   certificate chain of length 3: AMD Root Key (ARK), AMD SEV Signing Key (ASK), VCEK
28    //! - AMD gives us the first two certs from the chain, so we always only have to deal with
29    //!   one unknown cert
30    //! - We know some things about the VCEK (e.g. uses P-384 with SHA-384)
31
32    use der::{asn1::Ia5String, Decode};
33    use oid_registry::{asn1_rs::oid, Oid, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_PKCS1_RSASSAPSS};
34    use p384::pkcs8::DecodePublicKey;
35    use sev::firmware::host::TcbVersion;
36    use x509_parser::{
37        num_bigint::BigUint,
38        prelude::{PEMError, Pem},
39    };
40
41    use super::{Error, RootStore};
42
43    const OID_PRODUCT_NAME: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .2);
44    const OID_BOOT_LOADER: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .1);
45    const OID_TEE: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .2);
46    const OID_SNP: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .3);
47    const OID_UCODE: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .8);
48    const OID_FMC: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .9);
49
50    #[derive(Debug, Clone, Copy)]
51    pub enum Product {
52        Milan,
53        Genoa,
54        Turin,
55    }
56
57    struct VcekExpected {}
58    struct LooksLikeVcek {
59        possible_verifying_key: p384::ecdsa::VerifyingKey,
60        product_in_cert: Product,
61        cert_data: Vec<u8>,
62        signature: Vec<u8>,
63        tcb_version: TcbVersion,
64    }
65
66    struct SignedByKnownAmdRoot {
67        product: Product,
68        verifying_key: p384::ecdsa::VerifyingKey,
69    }
70
71    // Handy alias so we don't have to write out the iterator constraint all the time
72    trait Chain: Iterator<Item = Result<Pem, PEMError>> {}
73    impl<I: Iterator<Item = Result<Pem, PEMError>>> Chain for I {}
74
75    struct VcekChainVerifier<I: Chain, S> {
76        chain: I,
77        state: S,
78    }
79
80    impl<I: Chain, S> VcekChainVerifier<I, S> {
81        fn next_pem(&mut self) -> Result<Pem, Error> {
82            self.chain
83                .next()
84                .map(|result| result.map_err(Error::DecodeError))
85                .ok_or(Error::ChainBroken)?
86        }
87    }
88
89    impl<I: Chain> VcekChainVerifier<I, VcekExpected> {
90        fn new(chain: I) -> Self {
91            Self {
92                chain,
93                state: VcekExpected {},
94            }
95        }
96    }
97
98    impl<I: Chain> VcekChainVerifier<I, LooksLikeVcek> {
99        fn from(mut current: VcekChainVerifier<I, VcekExpected>) -> Result<Self, Error> {
100            let pem = current.next_pem()?;
101            if pem.label != "CERTIFICATE" {
102                return Err(Error::NotACertificate);
103            }
104            let cert = pem.parse_x509().map_err(|_| Error::ParseError)?;
105
106            // See also https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf chapter 3 ("VCEK Certificate Format")
107
108            // VCEK should not be a CA certificate
109            if cert.is_ca() {
110                return Err(Error::WrongBasicConstraints);
111            }
112
113            // Should have serial number 0
114            if cert.serial != BigUint::from(0usize) {
115                return Err(Error::ChainBroken);
116            }
117
118            if cert.signature_algorithm.algorithm != OID_PKCS1_RSASSAPSS {
119                return Err(Error::ChainBroken);
120            }
121
122            let Some(product_name) = cert
123                .get_extension_unique(&OID_PRODUCT_NAME)
124                .ok()
125                .flatten()
126                .and_then(|ext| Ia5String::from_der(ext.value).ok())
127            else {
128                return Err(Error::ChainBroken);
129            };
130
131            // `product_name` includes silicon stepping, hence the prefix match (N.B. but only
132            // seems to be the case for Milan)
133            let product = match product_name.as_str() {
134                s if s.starts_with("Genoa") => Product::Genoa,
135                s if s.starts_with("Milan") => Product::Milan,
136                s if s.starts_with("Turin") => Product::Turin,
137                _ => return Err(Error::UnknownProductName(product_name.to_string())),
138            };
139
140            let get_u8 = |oid: Oid| {
141                cert.get_extension_unique(&oid)
142                    .ok()
143                    .flatten()
144                    .and_then(|ext| u8::from_der(ext.value).ok())
145                    .ok_or(Error::ChainBroken)
146            };
147
148            let bootloader = get_u8(OID_BOOT_LOADER)?;
149            let tee = get_u8(OID_TEE)?;
150            let snp = get_u8(OID_SNP)?;
151            let microcode = get_u8(OID_UCODE)?;
152
153            let tcb_version = match product {
154                Product::Milan | Product::Genoa => TcbVersion {
155                    fmc: None,
156                    bootloader,
157                    tee,
158                    snp,
159                    microcode,
160                },
161                Product::Turin => {
162                    let fmc = get_u8(OID_FMC)?;
163                    TcbVersion {
164                        fmc: Some(fmc),
165                        bootloader,
166                        tee,
167                        snp,
168                        microcode,
169                    }
170                }
171            };
172
173            if cert
174                .subject
175                .iter_common_name()
176                .next()
177                .map(|cn| cn.as_str().map_err(|_| Error::ChainBroken))
178                .ok_or(Error::ChainBroken)??
179                != "SEV-VCEK"
180            {
181                return Err(Error::ChainBroken);
182            }
183
184            // An EC key is expected
185            if cert.public_key().algorithm.algorithm != OID_KEY_TYPE_EC_PUBLIC_KEY {
186                return Err(Error::ChainBroken);
187            }
188            let possible_verifying_key =
189                p384::ecdsa::VerifyingKey::from_public_key_der(cert.public_key().raw)
190                    .map_err(|_| Error::ChainBroken)?;
191
192            Ok(Self {
193                chain: current.chain,
194                state: LooksLikeVcek {
195                    possible_verifying_key,
196                    product_in_cert: product,
197                    cert_data: cert.tbs_certificate.as_ref().to_vec(),
198                    signature: cert.signature_value.data.to_vec(),
199                    tcb_version,
200                },
201            })
202        }
203    }
204
205    impl<I: Chain> VcekChainVerifier<I, SignedByKnownAmdRoot> {
206        fn from(
207            mut current: VcekChainVerifier<I, LooksLikeVcek>,
208            root_store: &RootStore,
209        ) -> Result<Self, Error> {
210            let pem = current.next_pem()?;
211            if pem.label != "CERTIFICATE" {
212                return Err(Error::NotACertificate);
213            }
214            let cert = pem.parse_x509().map_err(|_| Error::ParseError)?;
215
216            if root_store
217                .find_anchor_by_serial(&cert.serial)
218                .is_some_and(|anchor| {
219                    anchor.has_signed(&current.state.cert_data, &current.state.signature)
220                })
221            {
222                return Ok(Self {
223                    chain: current.chain,
224                    state: SignedByKnownAmdRoot {
225                        product: current.state.product_in_cert,
226                        verifying_key: current.state.possible_verifying_key,
227                    },
228                });
229            }
230
231            Err(Error::ChainBroken)
232        }
233    }
234
235    #[allow(clippy::missing_errors_doc)]
236    pub fn verify_vcek_chain<I: Iterator<Item = Result<Pem, PEMError>>>(
237        root_store: &RootStore,
238        chain: I,
239    ) -> Result<(Product, p384::ecdsa::VerifyingKey, TcbVersion), Error> {
240        let vcek_expected = VcekChainVerifier::new(chain);
241        let looks_like_vcek = VcekChainVerifier::<I, LooksLikeVcek>::from(vcek_expected)?;
242        let tcb_version = looks_like_vcek.state.tcb_version;
243
244        let signed_by_known_amd_root =
245            VcekChainVerifier::<I, SignedByKnownAmdRoot>::from(looks_like_vcek, root_store)?;
246
247        Ok((
248            signed_by_known_amd_root.state.product,
249            signed_by_known_amd_root.state.verifying_key,
250            tcb_version,
251        ))
252    }
253}
254
255// Siena and Bergamo use the same root chain as Genoa
256static GENOA_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Genoa.pem");
257static MILAN_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Milan.pem");
258static TURIN_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Turin.pem");
259
260#[allow(clippy::enum_variant_names)]
261#[derive(Debug)]
262pub enum Error {
263    // VCEK validation errors
264    DecodeError(PEMError),
265    ParseError,
266    /// Something else than a certificate was seen
267    NotACertificate,
268    WrongKeyUsage,
269    WrongBasicConstraints,
270    UnsupportedSignatureAlgorithm,
271    ChainBroken,
272    UnknownProductName(String),
273
274    // Other report validation errors
275    ReportParseError,
276    ReportSignatureParseError,
277    ReportSignatureMismatch,
278    ReportTcbVersionMismatch,
279    /// The report didn't satisfy the requested verifications
280    RequirementsNotSatisfied(&'static str),
281}
282
283/// A trusted certificate. Every certificate in a chain above this one is considered trusted.
284#[derive(Debug)]
285struct TrustAnchor {
286    verifying_key: rsa::pss::VerifyingKey<Sha384>,
287}
288
289impl TrustAnchor {
290    pub fn has_signed(&self, cert: &[u8], signature: &[u8]) -> bool {
291        let Ok(sig) = signature.try_into() else {
292            return false;
293        };
294        self.verifying_key.verify(cert, &sig).is_ok()
295    }
296}
297
298impl<'a> TryFrom<&X509Certificate<'a>> for TrustAnchor {
299    type Error = String;
300
301    fn try_from(value: &X509Certificate<'a>) -> Result<Self, Self::Error> {
302        let public_key = rsa::RsaPublicKey::from_public_key_der(value.public_key().raw)
303            .map_err(|_| "Could not create RSA public key from certificat public key")?;
304        Ok(Self {
305            verifying_key: public_key.into(),
306        })
307    }
308}
309
310pub struct RootStore {
311    anchors: HashMap<BigUint, TrustAnchor>,
312}
313
314impl RootStore {
315    /// Creates a new, empty [`RootStore`].
316    #[must_use]
317    pub fn new() -> Self {
318        Self {
319            anchors: HashMap::new(),
320        }
321    }
322
323    #[allow(clippy::missing_errors_doc)]
324    pub fn add_chain(&mut self, contents: &[u8]) -> Result<(), String> {
325        // N.B. only the leaf of the chain is stored as trust anchor
326        let (_, pem) = parse_x509_pem(contents).map_err(|_| "Could not decode entry")?;
327        let cert = pem
328            .parse_x509()
329            .map_err(|_| "Could not parse certificate")?;
330
331        if !cert.is_ca() {
332            return Err("Certificates in root store should be CA certificates".to_string());
333        }
334
335        // Leaf should have a pathlen of 0
336        if cert
337            .basic_constraints()
338            .unwrap_or(None)
339            .and_then(|ext| ext.value.path_len_constraint)
340            != Some(0)
341        {
342            return Err("Leaf in root chain should have pathlen of 0".to_string());
343        }
344
345        let anchor = TrustAnchor::try_from(&cert)?;
346        if self.anchors.insert(cert.serial.clone(), anchor).is_some() {
347            return Err("Multiple certs with same serial".to_string());
348        }
349
350        Ok(())
351    }
352
353    fn find_anchor_by_serial(&self, serial: &BigUint) -> Option<&TrustAnchor> {
354        self.anchors.get(serial)
355    }
356}
357
358impl Default for RootStore {
359    fn default() -> Self {
360        let mut store = Self::new();
361        store
362            .add_chain(MILAN_ROOT_CHAIN)
363            .expect("Root chain should be valid");
364        store
365            .add_chain(GENOA_ROOT_CHAIN)
366            .expect("Root chain should be valid");
367        store
368            .add_chain(TURIN_ROOT_CHAIN)
369            .expect("Root chain should be valid");
370        store
371    }
372}
373
374type FnMicrocode = fn(vcek::Product, &AttestationReport) -> Result<(), &'static str>;
375
376/// A builder for [`Requirements`].
377///
378/// # [`std::default::Default`] implementation
379///
380/// Note that the builder starts without any requirements. In particular,
381/// `RequirementsBuilder::default().build()` is different to `Requirements::default()`.
382#[derive(Debug, Clone, Default)]
383#[must_use]
384pub struct RequirementsBuilder {
385    genoa: ProductRequirements,
386    milan: ProductRequirements,
387    turin: ProductRequirements,
388    require_alias_check: bool,
389    check_microcode: Option<FnMicrocode>,
390}
391
392impl RequirementsBuilder {
393    /// Consumes this builder and returns corresponding [`Requirements`].
394    #[must_use]
395    pub fn build(self) -> Requirements {
396        Requirements {
397            genoa: self.genoa,
398            milan: self.milan,
399            turin: self.turin,
400            require_alias_check: self.require_alias_check,
401            check_microcode: self.check_microcode,
402        }
403    }
404
405    /// Requires that the report contains that alias detection has completed and that there are no
406    /// aliasing addresses. Also sets the required SNP firmware versions for Milan and Genoa as
407    /// well as SNP security version number if no higher versions are already set.
408    pub fn require_alias_check(mut self) -> Self {
409        self.require_alias_check = true;
410        if self.genoa.min_committed_version.unwrap_or_default()
411            < Requirements::SB_3015_MIN_GENOA_VERSION
412        {
413            self.genoa.min_committed_version = Some(Requirements::SB_3015_MIN_GENOA_VERSION);
414        }
415        if self.genoa.min_committed_tcb_snp.unwrap_or_default()
416            < Requirements::SB_3015_MIN_GENOA_SNP_SVN
417        {
418            self.genoa.min_committed_tcb_snp = Some(Requirements::SB_3015_MIN_GENOA_SNP_SVN);
419        }
420
421        if self.milan.min_committed_version.unwrap_or_default()
422            < Requirements::SB_3015_MIN_MILAN_VERSION
423        {
424            self.milan.min_committed_version = Some(Requirements::SB_3015_MIN_MILAN_VERSION);
425        }
426        if self.milan.min_committed_tcb_snp.unwrap_or_default()
427            < Requirements::SB_3015_MIN_MILAN_SNP_SVN
428        {
429            self.milan.min_committed_tcb_snp = Some(Requirements::SB_3015_MIN_MILAN_SNP_SVN);
430        }
431
432        self
433    }
434
435    /// Requires that the report's committed SNP firmware version is at least the given version for
436    /// Genoa products. Does not lower the required version if a higher version is already
437    /// required.
438    pub fn min_committed_version_for_genoa(mut self, version: (u8, u8, u8)) -> Self {
439        if version > self.genoa.min_committed_version.unwrap_or_default() {
440            self.genoa.min_committed_version = Some(version);
441        }
442        self
443    }
444
445    /// Requires that the report's committed SNP firmware version is at least the given version for
446    /// Milan products. Does not lower the required version if a higher version is already
447    /// required.
448    pub fn min_committed_version_for_milan(mut self, version: (u8, u8, u8)) -> Self {
449        if version > self.milan.min_committed_version.unwrap_or_default() {
450            self.milan.min_committed_version = Some(version);
451        }
452        self
453    }
454
455    /// Requires that the report's committed SNP firmware version is at least the given version for
456    /// Turin products. Does not lower the required version if a higher version is already
457    /// required.
458    pub fn min_committed_version_for_turin(mut self, version: (u8, u8, u8)) -> Self {
459        if version > self.turin.min_committed_version.unwrap_or_default() {
460            self.turin.min_committed_version = Some(version);
461        }
462        self
463    }
464
465    /// Requires that the report's committed SNP firmware security version number (SVN) is at least
466    /// the given version number for Milan products. Does not lower the required version number if
467    /// a higher version number is already required.
468    pub fn min_committed_snp_svn_for_milan(mut self, snp: u8) -> Self {
469        if snp > self.milan.min_committed_tcb_snp.unwrap_or_default() {
470            self.milan.min_committed_tcb_snp = Some(snp);
471        }
472        self
473    }
474
475    /// Requires that the report's committed SNP firmware security version number (SVN) is at least
476    /// the given version number for Genoa products. Does not lower the required version number if
477    /// a higher version number is already required.
478    pub fn min_committed_snp_svn_for_genoa(mut self, snp: u8) -> Self {
479        if snp > self.genoa.min_committed_tcb_snp.unwrap_or_default() {
480            self.genoa.min_committed_tcb_snp = Some(snp);
481        }
482        self
483    }
484
485    /// Requires that the report's committed SNP firmware security version number (SVN) is at least
486    /// the given version number for Turin products. Does not lower the required version number if
487    /// a higher version number is already required.
488    pub fn min_committed_snp_svn_for_turin(mut self, snp: u8) -> Self {
489        if snp > self.turin.min_committed_tcb_snp.unwrap_or_default() {
490            self.turin.min_committed_tcb_snp = Some(snp);
491        }
492        self
493    }
494
495    pub fn require_sb_7033_mitigations(mut self) -> Self {
496        self.check_microcode = Some(Requirements::sb_7033_microcode_check);
497        self
498    }
499}
500
501#[derive(Debug, Clone, Default)]
502#[allow(clippy::struct_field_names)]
503struct ProductRequirements {
504    // Inclusive
505    min_committed_version: Option<(u8, u8, u8)>,
506    // Inclusive
507    min_committed_tcb_snp: Option<u8>,
508    // Bitmask: all listed bits must be set in both current_mit_vector and launch_mit_vector
509    min_mit_vector: Option<u64>,
510}
511
512/// Additional requirements for [`AttestationReport`] verification.
513///
514/// See also: [`RequirementsBuilder`], [`verify_report`]
515#[derive(Debug, Clone)]
516pub struct Requirements {
517    genoa: ProductRequirements,
518    milan: ProductRequirements,
519    turin: ProductRequirements,
520    require_alias_check: bool,
521    check_microcode: Option<FnMicrocode>,
522}
523
524impl Default for Requirements {
525    fn default() -> Self {
526        Self::sb_3023_mitigations()
527    }
528}
529
530macro_rules! microcode_check {
531    ($name:ident, $(($product:pat, $cpu_family:pat) => $min_rev:expr),+ $(,)?) => {
532        fn $name(
533            product: vcek::Product,
534            report: &AttestationReport,
535        ) -> Result<(), &'static str> {
536            if let Some(cpu_family) = report.cpuid_mod_id.zip(report.cpuid_step) {
537                let min_rev = match (product, cpu_family) {
538                    $(($product, $cpu_family) => $min_rev,)+
539                    _ => return Err("Report doesn't match any known CPU family"),
540                };
541                if report.committed_tcb.microcode < min_rev {
542                    return Err("Committed TCB: Microcode version too small");
543                }
544            } else {
545                return Err("Could not verify microcode version: Missing values for CPU family");
546            }
547            Ok(())
548        }
549    };
550}
551
552impl Requirements {
553    const SB_3023_MIN_GENOA_MIT_VEC: u64 = (1 << 0) | (1 << 1);
554    const SB_3023_MIN_TURIN_MIT_VEC: u64 = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5);
555
556    const SB_3020_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x31);
557    const SB_3020_MIN_GENOA_SNP_SVN: u8 = 0x1B;
558    const SB_3020_MIN_GENOA_MIT_VEC: u64 = 1 << 1;
559    const SB_3020_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x23);
560    const SB_3020_MIN_MILAN_SNP_SVN: u8 = 0x1B;
561    const SB_3020_MIN_MILAN_MIT_VEC: u64 = 1 << 1;
562    const SB_3020_MIN_TURIN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x41);
563    const SB_3020_MIN_TURIN_SNP_SVN: u8 = 0x04;
564    const SB_3020_MIN_TURIN_MIT_VEC: u64 = 1 << 0;
565
566    const SB_3019_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x28);
567    const SB_3019_MIN_GENOA_SNP_SVN: u8 = 0x17;
568    const SB_3019_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x18);
569    const SB_3019_MIN_MILAN_SNP_SVN: u8 = 0x18;
570    const SB_3019_MIN_TURIN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x3b);
571
572    const SB_3015_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x26);
573    const SB_3015_MIN_GENOA_SNP_SVN: u8 = 0x16;
574    const SB_3015_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x16);
575    const SB_3015_MIN_MILAN_SNP_SVN: u8 = 0x17;
576
577    /// Checks for mitigations against [AMD-SB-3023](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3023.html)
578    #[must_use]
579    pub fn sb_3023_mitigations() -> Self {
580        let sb_3020 = Self::sb_3020_mitigations();
581        Self {
582            genoa: ProductRequirements {
583                min_mit_vector: Some(Self::SB_3023_MIN_GENOA_MIT_VEC),
584                ..sb_3020.genoa
585            },
586            turin: ProductRequirements {
587                min_mit_vector: Some(Self::SB_3023_MIN_TURIN_MIT_VEC),
588                ..sb_3020.turin
589            },
590            check_microcode: Some(Self::sb_3023_microcode_check),
591            ..sb_3020
592        }
593    }
594
595    /// Checks for mitigations against [AMD-SB-3020](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3020.html)
596    #[must_use]
597    pub fn sb_3020_mitigations() -> Self {
598        Self {
599            genoa: ProductRequirements {
600                min_committed_version: Some(Self::SB_3020_MIN_GENOA_VERSION),
601                min_committed_tcb_snp: Some(Self::SB_3020_MIN_GENOA_SNP_SVN),
602                min_mit_vector: Some(Self::SB_3020_MIN_GENOA_MIT_VEC),
603            },
604            milan: ProductRequirements {
605                min_committed_version: Some(Self::SB_3020_MIN_MILAN_VERSION),
606                min_committed_tcb_snp: Some(Self::SB_3020_MIN_MILAN_SNP_SVN),
607                min_mit_vector: Some(Self::SB_3020_MIN_MILAN_MIT_VEC),
608            },
609            turin: ProductRequirements {
610                min_committed_version: Some(Self::SB_3020_MIN_TURIN_VERSION),
611                min_committed_tcb_snp: Some(Self::SB_3020_MIN_TURIN_SNP_SVN),
612                min_mit_vector: Some(Self::SB_3020_MIN_TURIN_MIT_VEC),
613            },
614            require_alias_check: true,
615            check_microcode: Some(Self::sb_3020_microcode_check),
616        }
617    }
618
619    /// Checks for mitigations against [AMD-SB-3019](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html)
620    #[must_use]
621    pub fn sb_3019_mitigations() -> Self {
622        Self {
623            genoa: ProductRequirements {
624                min_committed_version: Some(Self::SB_3019_MIN_GENOA_VERSION),
625                min_committed_tcb_snp: Some(Self::SB_3019_MIN_GENOA_SNP_SVN),
626                ..Default::default()
627            },
628            milan: ProductRequirements {
629                min_committed_version: Some(Self::SB_3019_MIN_MILAN_VERSION),
630                min_committed_tcb_snp: Some(Self::SB_3019_MIN_MILAN_SNP_SVN),
631                ..Default::default()
632            },
633            turin: ProductRequirements {
634                min_committed_version: Some(Self::SB_3019_MIN_TURIN_VERSION),
635                ..Default::default()
636            },
637            require_alias_check: true,
638            check_microcode: None,
639        }
640    }
641
642    /// Checks for mitigations against [AMD-SB-3015](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3015.html)
643    #[must_use]
644    pub fn sb_3015_mitigations() -> Self {
645        Self {
646            genoa: ProductRequirements {
647                min_committed_version: Some(Self::SB_3015_MIN_GENOA_VERSION),
648                min_committed_tcb_snp: Some(Self::SB_3015_MIN_GENOA_SNP_SVN),
649                ..Default::default()
650            },
651            milan: ProductRequirements {
652                min_committed_version: Some(Self::SB_3015_MIN_MILAN_VERSION),
653                min_committed_tcb_snp: Some(Self::SB_3015_MIN_MILAN_SNP_SVN),
654                ..Default::default()
655            },
656            // Turin not affected by SB-3015
657            turin: ProductRequirements::default(),
658            require_alias_check: true,
659            check_microcode: None,
660        }
661    }
662
663    /// Checks for mitigations against [AMD-SB-3011](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3011.html)
664    #[must_use]
665    pub fn sb_3011_mitigations() -> Self {
666        Self {
667            genoa: ProductRequirements {
668                min_committed_version: Some((0x1, 0x37, 0x24)),
669                min_committed_tcb_snp: Some(0x16),
670                ..Default::default()
671            },
672            milan: ProductRequirements {
673                min_committed_version: Some((0x1, 0x37, 0x14)),
674                min_committed_tcb_snp: Some(0x17),
675                ..Default::default()
676            },
677            // Turin not affected by SB-3011
678            turin: ProductRequirements::default(),
679            require_alias_check: false,
680            check_microcode: None,
681        }
682    }
683
684    microcode_check!(sb_3023_microcode_check,
685        // Milan
686        (vcek::Product::Milan, (1, 1)) => 0xDE,
687        // Milan-X
688        (vcek::Product::Milan, (1, 2)) => 0x47,
689        // Genoa
690        (vcek::Product::Genoa, (0x11, 1)) => 0x56,
691        // Genoa-X
692        (vcek::Product::Genoa, (0x11, 2)) => 0x51,
693        // Bergamo/Siena
694        (vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
695        // Turin Classic
696        (vcek::Product::Turin, (2, 1)) => 0x51,
697        // Turin Dense
698        (vcek::Product::Turin, (0x11, 0)) => 0x4E,
699    );
700
701    microcode_check!(sb_3020_microcode_check,
702        // Milan
703        (vcek::Product::Milan, (1, 1)) => 0xDE,
704        // Milan-X
705        (vcek::Product::Milan, (1, 2)) => 0x45,
706        // Genoa
707        (vcek::Product::Genoa, (0x11, 1)) => 0x56,
708        // Genoa-X
709        (vcek::Product::Genoa, (0x11, 2)) => 0x51,
710        // Bergamo/Siena
711        (vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
712        // Turin Classic
713        (vcek::Product::Turin, (2, 1)) => 0x50,
714        // Turin Dense
715        (vcek::Product::Turin, (0x11, 0)) => 0x4D,
716    );
717
718    microcode_check!(sb_7033_microcode_check,
719        // Milan
720        (vcek::Product::Milan, (1, 1)) => 0xDB,
721        // Milan-X
722        (vcek::Product::Milan, (1, 2)) => 0x44,
723        // Genoa
724        (vcek::Product::Genoa, (0x11, 1)) => 0x54,
725        // Genoa-X
726        (vcek::Product::Genoa, (0x11, 2)) => 0x4F,
727        // Bergamo/Siena
728        (vcek::Product::Genoa, (0xA0, 2)) => 0x19,
729        // Turin Classic
730        (vcek::Product::Turin, (2, 1)) => 0x47,
731    );
732
733    fn verify(
734        &self,
735        product: vcek::Product,
736        report: &AttestationReport,
737    ) -> Result<(), &'static str> {
738        if report.policy.debug_allowed() {
739            return Err("Debug is enabled");
740        }
741
742        if report.policy.migrate_ma_allowed() {
743            return Err("Guest policy allows a migration agent");
744        }
745
746        if report.vmpl > 3 {
747            return Err("VMPL is not <= 3");
748        }
749
750        if report.key_info.signing_key() != 0 {
751            return Err("Signing key is not VCEK");
752        }
753
754        if self.require_alias_check && !report.plat_info.alias_check_complete() {
755            return Err("Alias check complete is false");
756        }
757
758        let requirements = match product {
759            vcek::Product::Milan => &self.milan,
760            vcek::Product::Genoa => &self.genoa,
761            vcek::Product::Turin => &self.turin,
762        };
763
764        if let Some(min_wanted) = requirements.min_committed_version {
765            if (
766                report.committed.major,
767                report.committed.minor,
768                report.committed.build,
769            ) < min_wanted
770            {
771                return Err("Firmware version too small");
772            }
773        }
774
775        if let Some(min_tcb_snp) = requirements.min_committed_tcb_snp {
776            if report.committed_tcb.snp < min_tcb_snp {
777                return Err("Committed TCB: SNP patch level too small");
778            }
779        }
780
781        if let Some(min_mit_vec) = requirements.min_mit_vector {
782            match report.current_mit_vector {
783                None => return Err("Current mitigation vector: not present in report"),
784                Some(v) if v & min_mit_vec != min_mit_vec => {
785                    return Err("Current mitigation vector: required mitigation bits not set")
786                }
787                Some(_) => {}
788            }
789            match report.launch_mit_vector {
790                None => return Err("Launch mitigation vector: not present in report"),
791                Some(v) if v & min_mit_vec != min_mit_vec => {
792                    return Err("Launch mitigation vector: required mitigation bits not set")
793                }
794                Some(_) => {}
795            }
796        }
797
798        if let Some(check) = self.check_microcode {
799            check(product, report)?;
800        }
801
802        Ok(())
803    }
804}
805
806fn parse_report(bytes: &[u8]) -> Option<AttestationReport> {
807    if bytes.len() < 1184 {
808        return None;
809    }
810
811    let read_u32 = |off: usize| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
812    let read_u64 = |off: usize| u64::from_le_bytes(bytes[off..off + 8].try_into().unwrap());
813
814    let version = read_u32(0x00);
815
816    let turin_like = if version >= 3 {
817        // Turin-like if CPU_FAM_ID is not the one for Genoa and Milan
818        bytes[0x188] != 0x19
819    } else {
820        let chip_id = &bytes[0x1A0..0x1E0];
821        if chip_id == [0u8; 64] {
822            return None;
823        }
824
825        // Turin-like if the last 8 bytes of CHIP_ID are zero
826        chip_id[8..] == [0u8; 56]
827    };
828
829    let parse_tcb = |off: usize| -> TcbVersion {
830        let b = &bytes[off..off + 8];
831        if turin_like {
832            TcbVersion {
833                fmc: Some(b[0]),
834                bootloader: b[1],
835                tee: b[2],
836                snp: b[3],
837                microcode: b[7],
838            }
839        } else {
840            TcbVersion {
841                fmc: None,
842                bootloader: b[0],
843                tee: b[1],
844                snp: b[6],
845                microcode: b[7],
846            }
847        }
848    };
849
850    let (cpuid_fam_id, cpuid_mod_id, cpuid_step) = if version >= 3 {
851        (Some(bytes[0x188]), Some(bytes[0x189]), Some(bytes[0x18A]))
852    } else {
853        (None, None, None)
854    };
855
856    let parse_version = |off: usize| Version {
857        build: bytes[off],
858        minor: bytes[off + 1],
859        major: bytes[off + 2],
860    };
861
862    let r: [u8; 72] = bytes[0x2A0..0x2E8].try_into().ok()?;
863    let s: [u8; 72] = bytes[0x2E8..0x330].try_into().ok()?;
864
865    Some(AttestationReport {
866        version,
867        guest_svn: read_u32(0x04),
868        policy: GuestPolicy(read_u64(0x08)),
869        family_id: bytes[0x10..0x20].try_into().ok()?,
870        image_id: bytes[0x20..0x30].try_into().ok()?,
871        vmpl: read_u32(0x30),
872        sig_algo: read_u32(0x34),
873        current_tcb: parse_tcb(0x38),
874        plat_info: PlatformInfo(read_u64(0x40)),
875        key_info: KeyInfo(read_u32(0x48)),
876        report_data: bytes[0x50..0x90].try_into().ok()?,
877        measurement: bytes[0x90..0xC0].try_into().ok()?,
878        host_data: bytes[0xC0..0xE0].try_into().ok()?,
879        id_key_digest: bytes[0xE0..0x110].try_into().ok()?,
880        author_key_digest: bytes[0x110..0x140].try_into().ok()?,
881        report_id: bytes[0x140..0x160].try_into().ok()?,
882        report_id_ma: bytes[0x160..0x180].try_into().ok()?,
883        reported_tcb: parse_tcb(0x180),
884        cpuid_fam_id,
885        cpuid_mod_id,
886        cpuid_step,
887        chip_id: bytes[0x1A0..0x1E0].try_into().ok()?,
888        committed_tcb: parse_tcb(0x1E0),
889        current: parse_version(0x1E8),
890        committed: parse_version(0x1EC),
891        launch_tcb: parse_tcb(0x1F0),
892        launch_mit_vector: (version >= 5).then(|| read_u64(0x1F8)),
893        current_mit_vector: (version >= 5).then(|| read_u64(0x200)),
894        signature: Signature::new(r, s),
895    })
896}
897
898#[allow(clippy::missing_errors_doc)]
899pub fn verify_report<R: Read + Seek>(
900    report_contents: &[u8],
901    vcek_chain: R,
902    root_store: &RootStore,
903    verifications: &Requirements,
904) -> Result<AttestationReport, Error> {
905    if report_contents.len() < 1184 {
906        return Err(Error::ReportParseError);
907    }
908
909    let report = parse_report(report_contents).ok_or(Error::ReportParseError)?;
910    let sig = ReportSignature::try_from(&report).map_err(|_| Error::ReportParseError)?;
911    let ecdsa_sig =
912        p384::ecdsa::Signature::try_from(&sig).map_err(|_| Error::ReportSignatureParseError)?;
913    let (product, key, tcb_version) = vcek::verify_vcek_chain(
914        root_store,
915        Pem::iter_from_reader(BufReader::new(vcek_chain)),
916    )?;
917
918    if report.reported_tcb != tcb_version {
919        return Err(Error::ReportTcbVersionMismatch);
920    }
921
922    if key.verify(&report_contents[..0x2a0], &ecdsa_sig).is_ok() {
923        verifications
924            .verify(product, &report)
925            .map(|()| report)
926            .map_err(Error::RequirementsNotSatisfied)
927    } else {
928        Err(Error::ReportSignatureMismatch)
929    }
930}
931
932#[cfg(test)]
933mod tests {
934    use std::{fs::File, io::Cursor, path::PathBuf};
935
936    use ecdsa::{elliptic_curve::Generate, signature::Signer};
937    use p384::{ecdsa::SigningKey, NistP384};
938    use rand::{rand_core::UnwrapErr, rngs::SysRng};
939    use sev::{
940        certs::snp::ecdsa::Signature,
941        firmware::guest::{AttestationReport, GuestPolicy},
942        parser::ByteParser,
943    };
944    use x509_parser::{num_bigint::BigUint, prelude::Pem};
945
946    use crate::verification::{Error, Requirements, RootStore};
947
948    use super::{vcek, vcek::verify_vcek_chain, verify_report, RequirementsBuilder};
949
950    // Re-implementation of std's `assert_matches!` which is unfortunately still unstable
951    macro_rules! assert_m {
952        ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
953            match $left {
954                $( $pattern )|+ $( if $guard )? => {}
955                ref left_val => {
956                    panic!(
957                        "Expected match where there is none\nleft: {left_val:?}\nright: {}",
958                        stringify!($($pattern)|+ $(if $guard)?),
959                    );
960                }
961            }
962        };
963    }
964
965    #[test]
966    fn verify_report_returns_ok_for_valid_report_and_chain() {
967        let result = verify_report(
968            &hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap(),
969            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
970            &RootStore::default(),
971            // N.B. doesn't use `Requirements::default` because the fixtures are reports without
972            // the required mitigations
973            &RequirementsBuilder::default().build(),
974        );
975
976        assert!(result.is_ok());
977    }
978
979    #[test]
980    fn verify_report_returns_ok_for_valid_report_and_chain_with_sb_3015_mitigations() {
981        let result = verify_report(
982            &genoa_report_alias_check_fixture_bytes(),
983            genoa_vcek_chain_alias_check_fixture(),
984            &RootStore::default(),
985            &Requirements::sb_3015_mitigations(),
986        );
987        assert!(result.is_ok());
988    }
989
990    #[test]
991    fn default_requirements_require_sb_3023_mitigations() {
992        let result = verify_report(
993            &hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap(),
994            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
995            &RootStore::default(),
996            &Requirements::default(),
997        );
998
999        assert_m!(
1000            result,
1001            Err(Error::RequirementsNotSatisfied(
1002                "Alias check complete is false"
1003            ))
1004        );
1005    }
1006
1007    #[test]
1008    fn verify_vcek_chain_returns_ok_for_valid_chain() {
1009        let result = verify_vcek_chain(
1010            &RootStore::default(),
1011            Pem::iter_from_buffer(include_bytes!("../fixtures/tests/valid_vcek_chain.data")),
1012        );
1013
1014        assert!(result.is_ok());
1015    }
1016
1017    #[test]
1018    fn verify_vcek_chain_returns_err_for_selfsigned_chain() {
1019        let result = verify_vcek_chain(
1020            &RootStore::default(),
1021            Pem::iter_from_buffer(include_bytes!(
1022                "../fixtures/tests/selfsigned_vcek_chain.data"
1023            )),
1024        );
1025
1026        assert!(result.is_err());
1027    }
1028
1029    #[test]
1030    fn verify_vcek_chain_returns_err_for_invalid_chain() {
1031        let result = verify_vcek_chain(
1032            &RootStore::default(),
1033            Pem::iter_from_buffer(include_bytes!("../fixtures/tests/vcek_wrong_order.data")),
1034        );
1035
1036        assert!(result.is_err());
1037    }
1038
1039    #[test]
1040    fn default_root_store_finds_milan_serial() {
1041        let root_store = RootStore::default();
1042
1043        assert!(root_store
1044            .find_anchor_by_serial(&BigUint::from(0x10001u32))
1045            .is_some());
1046    }
1047
1048    #[test]
1049    fn root_store_returns_none_for_unknown_serial() {
1050        let root_store = RootStore::new();
1051
1052        assert!(root_store
1053            .find_anchor_by_serial(&BigUint::from(0x1234u32))
1054            .is_none());
1055    }
1056
1057    #[test]
1058    fn invalid_report_signature_returns_err() {
1059        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1060        // 0 values result in a signature error
1061        report.signature = Signature::new([0u8; 72], [0u8; 72]);
1062
1063        let result = verify_report(
1064            &report.to_bytes().unwrap(),
1065            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
1066            &RootStore::default(),
1067            &Requirements::default(),
1068        );
1069
1070        assert_m!(result, Err(Error::ReportSignatureParseError));
1071    }
1072
1073    #[test]
1074    fn wrong_report_signature_returns_err() {
1075        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1076        let signing_key = SigningKey::generate_from_rng(&mut UnwrapErr(SysRng));
1077        let signature = signing_key.sign(b"wurzelpfropf");
1078        report.signature = create_signature_from_signature(&signature);
1079
1080        let result = verify_report(
1081            &report.to_bytes().unwrap(),
1082            vcek_chain_fixture(),
1083            &RootStore::default(),
1084            &Requirements::default(),
1085        );
1086
1087        assert_m!(result, Err(Error::ReportSignatureMismatch));
1088    }
1089
1090    #[test]
1091    fn wrong_tcb_version_returns_err() {
1092        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1093        let signing_key = SigningKey::generate_from_rng(&mut UnwrapErr(SysRng));
1094        let signature = signing_key.sign(b"wurzelpfropf");
1095        report.signature = create_signature_from_signature(&signature);
1096
1097        let result = verify_report(
1098            &report.to_bytes().unwrap(),
1099            File::open(PathBuf::from(
1100                "./fixtures/tests/valid_vcek_wrong_tcb_version.data",
1101            ))
1102            .unwrap(),
1103            &RootStore::default(),
1104            &Requirements::default(),
1105        );
1106
1107        assert_m!(result, Err(Error::ReportTcbVersionMismatch));
1108    }
1109
1110    #[test]
1111    fn debug_enabled_returns_err() {
1112        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1113        report.policy.set_debug_allowed(true);
1114
1115        assert_m!(
1116            RequirementsBuilder::default()
1117                .build()
1118                .verify(vcek::Product::Milan, &report),
1119            Err("Debug is enabled")
1120        );
1121    }
1122
1123    /// The guest policy a stock hypervisor launches with: reserved bit 17 and SMT.
1124    fn default_guest_policy() -> GuestPolicy {
1125        GuestPolicy(0x3_0000)
1126    }
1127
1128    #[test]
1129    fn default_guest_policy_is_accepted() {
1130        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1131        report.policy = default_guest_policy();
1132
1133        assert_m!(
1134            RequirementsBuilder::default()
1135                .build()
1136                .verify(vcek::Product::Milan, &report),
1137            Ok(())
1138        );
1139    }
1140
1141    #[test]
1142    fn migration_agent_allowed_returns_err() {
1143        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1144        report.policy = default_guest_policy();
1145        report.policy.set_migrate_ma_allowed(true);
1146
1147        assert_m!(
1148            RequirementsBuilder::default()
1149                .build()
1150                .verify(vcek::Product::Milan, &report),
1151            Err("Guest policy allows a migration agent")
1152        );
1153    }
1154
1155    #[test]
1156    fn non_guest_vmpl_returns_err() {
1157        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1158        report.vmpl = 4;
1159
1160        assert_m!(
1161            RequirementsBuilder::default()
1162                .build()
1163                .verify(vcek::Product::Milan, &report),
1164            Err("VMPL is not <= 3")
1165        );
1166    }
1167
1168    #[test]
1169    fn signing_key_not_vcek_returns_err() {
1170        let mut report_bytes = report_fixture_bytes();
1171        // KEY_INFO is at offset 0x48 in the attestation report structure.
1172        // SIGNING_KEY is bits 4:2; set to 1 (VLEK).
1173        report_bytes[0x48] |= 1 << 2;
1174        let report = AttestationReport::from_bytes(&report_bytes).unwrap();
1175
1176        assert_m!(
1177            RequirementsBuilder::default()
1178                .build()
1179                .verify(vcek::Product::Milan, &report),
1180            Err("Signing key is not VCEK")
1181        );
1182    }
1183
1184    #[test]
1185    fn requirements_check_for_minimal_committed_snp_version_milan() {
1186        fn verify(version: (u8, u8, u8)) -> Result<AttestationReport, Error> {
1187            verify_report(
1188                &report_fixture_bytes(),
1189                vcek_chain_fixture(),
1190                &RootStore::default(),
1191                &RequirementsBuilder::default()
1192                    .min_committed_version_for_milan(version)
1193                    .build(),
1194            )
1195        }
1196
1197        fn version_too_small(version: (u8, u8, u8)) {
1198            assert_m!(
1199                verify(version),
1200                Err(Error::RequirementsNotSatisfied(
1201                    "Firmware version too small"
1202                ))
1203            );
1204        }
1205
1206        version_too_small((1, 52, 5));
1207        version_too_small((1, 53, 4));
1208        version_too_small((2, 0, 0));
1209
1210        assert_m!(verify((1, 52, 4)), Ok(_));
1211        assert_m!(verify((1, 50, 0)), Ok(_));
1212    }
1213
1214    #[test]
1215    fn requirements_check_for_minimal_committed_snp_version_genoa() {
1216        fn verify(version: (u8, u8, u8)) -> Result<AttestationReport, Error> {
1217            verify_report(
1218                &genoa_report_fixture_bytes(),
1219                genoa_vcek_chain_fixture(),
1220                &RootStore::default(),
1221                &RequirementsBuilder::default()
1222                    .min_committed_version_for_genoa(version)
1223                    .build(),
1224            )
1225        }
1226
1227        fn version_too_small(version: (u8, u8, u8)) {
1228            assert_m!(
1229                verify(version),
1230                Err(Error::RequirementsNotSatisfied(
1231                    "Firmware version too small"
1232                ))
1233            );
1234        }
1235
1236        version_too_small((1, 55, 22));
1237        version_too_small((1, 56, 21));
1238        version_too_small((2, 0, 0));
1239
1240        assert_m!(verify((1, 55, 21)), Ok(_));
1241        assert_m!(verify((1, 54, 0)), Ok(_));
1242    }
1243
1244    #[test]
1245    fn requirements_check_for_minimal_committed_snp_svn_milan() {
1246        fn verify(svn: u8) -> Result<AttestationReport, Error> {
1247            verify_report(
1248                &report_fixture_bytes(),
1249                vcek_chain_fixture(),
1250                &RootStore::default(),
1251                &RequirementsBuilder::default()
1252                    .min_committed_snp_svn_for_milan(svn)
1253                    .build(),
1254            )
1255        }
1256
1257        assert_m!(verify(8), Ok(_));
1258        assert_m!(
1259            verify(9),
1260            Err(Error::RequirementsNotSatisfied(
1261                "Committed TCB: SNP patch level too small"
1262            ))
1263        );
1264    }
1265
1266    #[test]
1267    fn requirements_check_for_minimal_committed_snp_svn_genoa() {
1268        fn verify(svn: u8) -> Result<AttestationReport, Error> {
1269            verify_report(
1270                &genoa_report_fixture_bytes(),
1271                genoa_vcek_chain_fixture(),
1272                &RootStore::default(),
1273                &RequirementsBuilder::default()
1274                    .min_committed_snp_svn_for_genoa(svn)
1275                    .build(),
1276            )
1277        }
1278
1279        assert_m!(verify(14), Ok(_));
1280        assert_m!(
1281            verify(15),
1282            Err(Error::RequirementsNotSatisfied(
1283                "Committed TCB: SNP patch level too small"
1284            ))
1285        );
1286    }
1287
1288    #[test]
1289    fn requirements_check_for_alias_check_milan() {
1290        let result = verify_report(
1291            &report_fixture_bytes(),
1292            vcek_chain_fixture(),
1293            &RootStore::default(),
1294            &RequirementsBuilder::default().require_alias_check().build(),
1295        );
1296        assert_m!(
1297            result,
1298            Err(Error::RequirementsNotSatisfied(
1299                "Alias check complete is false"
1300            ))
1301        );
1302    }
1303
1304    #[test]
1305    fn requirements_check_for_alias_check_genoa() {
1306        let result = verify_report(
1307            &genoa_report_fixture_bytes(),
1308            genoa_vcek_chain_fixture(),
1309            &RootStore::default(),
1310            &RequirementsBuilder::default().require_alias_check().build(),
1311        );
1312        assert_m!(
1313            result,
1314            Err(Error::RequirementsNotSatisfied(
1315                "Alias check complete is false"
1316            ))
1317        );
1318    }
1319
1320    #[test]
1321    fn requirements_check_for_alias_check_genoa_ok() {
1322        let result = verify_report(
1323            &genoa_report_alias_check_fixture_bytes(),
1324            genoa_vcek_chain_alias_check_fixture(),
1325            &RootStore::default(),
1326            &RequirementsBuilder::default().require_alias_check().build(),
1327        );
1328        assert!(result.is_ok());
1329    }
1330
1331    #[test]
1332    fn requirements_check_microcode_missing_cpu_fields() {
1333        let result = verify_report(
1334            &report_fixture_bytes(),
1335            vcek_chain_fixture(),
1336            &RootStore::default(),
1337            &RequirementsBuilder::default()
1338                .require_sb_7033_mitigations()
1339                .build(),
1340        );
1341        assert_m!(
1342            result,
1343            Err(Error::RequirementsNotSatisfied(
1344                "Could not verify microcode version: Missing values for CPU family"
1345            )),
1346        );
1347    }
1348
1349    #[test]
1350    fn verify_report_passes_for_authentic_genoa_v5_report() {
1351        let result = verify_report(
1352            &hex::decode(include_str!("../fixtures/tests/report_v5.data").trim()).unwrap(),
1353            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain_v5.data")).unwrap(),
1354            &RootStore::default(),
1355            &Requirements::default(),
1356        );
1357        assert!(result.is_ok());
1358    }
1359
1360    #[test]
1361    fn verify_vcek_chain_returns_ok_for_milan_with_microcode_above_128() {
1362        let (product, _key, tcb_version) = verify_vcek_chain(
1363            &RootStore::default(),
1364            Pem::iter_from_buffer(include_bytes!(
1365                "../fixtures/tests/valid_vcek_chain_milan.data"
1366            )),
1367        )
1368        .unwrap();
1369
1370        assert_m!(product, vcek::Product::Milan);
1371        assert_eq!(tcb_version.microcode, 0xDB);
1372    }
1373
1374    #[test]
1375    fn verify_report_returns_ok_for_milan_with_microcode_above_128() {
1376        let result = verify_report(
1377            &hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap(),
1378            Cursor::new(include_str!(
1379                "../fixtures/tests/valid_vcek_chain_milan.data"
1380            )),
1381            &RootStore::default(),
1382            &RequirementsBuilder::default().build(),
1383        );
1384
1385        assert!(result.is_ok());
1386    }
1387
1388    #[test]
1389    fn verify_vcek_chain_returns_ok_for_turin() {
1390        let (product, _key, tcb_version) = verify_vcek_chain(
1391            &RootStore::default(),
1392            Pem::iter_from_buffer(include_bytes!(
1393                "../fixtures/tests/valid_vcek_chain_turin.data"
1394            )),
1395        )
1396        .unwrap();
1397
1398        assert_m!(product, vcek::Product::Turin);
1399        assert_eq!(tcb_version.microcode, 0x47);
1400    }
1401
1402    #[test]
1403    fn verify_report_returns_ok_for_turin() {
1404        let result = verify_report(
1405            &hex::decode(include_str!("../fixtures/tests/report_turin.data").trim()).unwrap(),
1406            Cursor::new(include_str!(
1407                "../fixtures/tests/valid_vcek_chain_turin.data"
1408            )),
1409            &RootStore::default(),
1410            &RequirementsBuilder::default().build(),
1411        );
1412
1413        assert!(result.is_ok());
1414    }
1415
1416    #[test]
1417    fn requirements_builder_doesnt_lower_version() {
1418        let requirements = RequirementsBuilder::default()
1419            .min_committed_version_for_genoa((1, 52, 1))
1420            .min_committed_version_for_genoa((1, 50, 0))
1421            .min_committed_version_for_milan((1, 52, 1))
1422            .min_committed_version_for_milan((1, 50, 0))
1423            .min_committed_version_for_turin((1, 52, 1))
1424            .min_committed_version_for_turin((1, 50, 0))
1425            .build();
1426
1427        assert_eq!(requirements.genoa.min_committed_version, Some((1, 52, 1)));
1428        assert_eq!(requirements.milan.min_committed_version, Some((1, 52, 1)));
1429        assert_eq!(requirements.turin.min_committed_version, Some((1, 52, 1)));
1430    }
1431
1432    #[test]
1433    fn requirements_builder_sets_higher_version() {
1434        let requirements = RequirementsBuilder::default()
1435            .min_committed_version_for_genoa((1, 52, 1))
1436            .min_committed_version_for_genoa((1, 52, 2))
1437            .min_committed_version_for_milan((1, 52, 1))
1438            .min_committed_version_for_milan((1, 52, 2))
1439            .min_committed_version_for_turin((1, 52, 1))
1440            .min_committed_version_for_turin((1, 52, 2))
1441            .build();
1442
1443        assert_eq!(requirements.genoa.min_committed_version, Some((1, 52, 2)));
1444        assert_eq!(requirements.milan.min_committed_version, Some((1, 52, 2)));
1445        assert_eq!(requirements.turin.min_committed_version, Some((1, 52, 2)));
1446    }
1447
1448    fn create_signature_from_signature(signature: &ecdsa::Signature<NistP384>) -> Signature {
1449        // p384 uses big endian, but SNP firmware uses little endian for the signature,
1450        // hence reverse to match endianess. Additionally, the scalars in p384 ECDSA signatures are
1451        // 48 bytes, but the report's fields are 72 bytes, so the remaining bytes are filled with
1452        // zeros.
1453        // See table 115: "Format for an ECDSA P-384 with SHA-384 Signature" in
1454        // "SEV Secure Nested Paging Firmware ABI Specification" (publication #56860)
1455        // https://www.amd.com/system/files/TechDocs/56860.pdf
1456        let mut r: [u8; 72] = [0u8; 72];
1457        r[24..].copy_from_slice(&signature.r().to_bytes());
1458        r.reverse();
1459        let mut s: [u8; 72] = [0u8; 72];
1460        s[24..].copy_from_slice(&signature.s().to_bytes());
1461        s.reverse();
1462
1463        Signature::new(r, s)
1464    }
1465
1466    fn genoa_report_fixture_bytes() -> Vec<u8> {
1467        hex::decode(include_bytes!("../fixtures/tests/report_genoa.data")).unwrap()
1468    }
1469
1470    fn genoa_report_alias_check_fixture_bytes() -> Vec<u8> {
1471        hex::decode(include_bytes!(
1472            "../fixtures/tests/report_genoa_alias_check.data"
1473        ))
1474        .unwrap()
1475    }
1476
1477    fn report_fixture_bytes() -> Vec<u8> {
1478        hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap()
1479    }
1480
1481    fn genoa_vcek_chain_fixture() -> impl std::io::Read + std::io::Seek {
1482        File::open(PathBuf::from(
1483            "./fixtures/tests/valid_vcek_chain_genoa.data",
1484        ))
1485        .unwrap()
1486    }
1487
1488    fn genoa_vcek_chain_alias_check_fixture() -> impl std::io::Read + std::io::Seek {
1489        File::open(PathBuf::from(
1490            "./fixtures/tests/valid_vcek_chain_genoa_alias_check.data",
1491        ))
1492        .unwrap()
1493    }
1494
1495    fn vcek_chain_fixture() -> impl std::io::Read + std::io::Seek {
1496        File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap()
1497    }
1498
1499    #[test]
1500    fn parser_matches_sev_parser_milan() {
1501        let bytes =
1502            hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
1503        let expected = AttestationReport::from_bytes(&bytes).unwrap();
1504        let actual = super::parse_report(&bytes).unwrap();
1505        assert_eq!(actual, expected);
1506    }
1507
1508    #[test]
1509    fn parser_matches_sev_parser_genoa() {
1510        let bytes = genoa_report_fixture_bytes();
1511        let expected = AttestationReport::from_bytes(&bytes).unwrap();
1512        let actual = super::parse_report(&bytes).unwrap();
1513        assert_eq!(actual, expected);
1514    }
1515
1516    #[test]
1517    fn parser_matches_sev_parser_v5() {
1518        let bytes = hex::decode(include_str!("../fixtures/tests/report_v5.data").trim()).unwrap();
1519        let expected = AttestationReport::from_bytes(&bytes).unwrap();
1520        let actual = super::parse_report(&bytes).unwrap();
1521        assert_eq!(actual, expected);
1522    }
1523
1524    #[test]
1525    fn parser_matches_sev_parser_turin() {
1526        let bytes =
1527            hex::decode(include_str!("../fixtures/tests/report_turin.data").trim()).unwrap();
1528        let expected = AttestationReport::from_bytes(&bytes).unwrap();
1529        let actual = super::parse_report(&bytes).unwrap();
1530        assert_eq!(actual, expected);
1531    }
1532
1533    #[test]
1534    fn parse_report_ignores_reserved_bytes_for_unknown_version() {
1535        // Simulate a hypothetical future version that uses the currently reserved
1536        // area between current_mit_vector and the signature (0x208..0x2A0).
1537        let mut bytes =
1538            hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
1539        bytes[0..4].copy_from_slice(&42u32.to_le_bytes());
1540        bytes[0x208..0x2A0].fill(0xAB);
1541
1542        let report = super::parse_report(&bytes).unwrap();
1543        assert_eq!(report.version, 42);
1544        // Fields that precede the reserved area must be unaffected.
1545        assert!(report.current_mit_vector.is_some());
1546    }
1547
1548    #[test]
1549    fn parse_report_truncated() {
1550        let bytes =
1551            hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
1552        assert!(super::parse_report(&bytes[..1183]).is_none());
1553    }
1554
1555    #[test]
1556    fn microcode_check_macro_accepts_above_minimum() {
1557        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1558        report.cpuid_mod_id = Some(1);
1559        report.cpuid_step = Some(1);
1560        // All three checks for Milan (1,1) require at most 0xDE
1561        report.committed_tcb.microcode = 0xDE;
1562        assert!(Requirements::sb_3023_microcode_check(vcek::Product::Milan, &report).is_ok());
1563        assert!(Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report).is_ok());
1564        assert!(Requirements::sb_7033_microcode_check(vcek::Product::Milan, &report).is_ok());
1565    }
1566
1567    #[test]
1568    fn microcode_check_macro_rejects_below_minimum() {
1569        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1570        report.cpuid_mod_id = Some(1);
1571        report.cpuid_step = Some(1);
1572        // SB-3020 Milan (1,1) minimum is 0xDE
1573        report.committed_tcb.microcode = 0xDD;
1574        assert_eq!(
1575            Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
1576            Err("Committed TCB: Microcode version too small"),
1577        );
1578    }
1579
1580    #[test]
1581    fn microcode_check_macro_rejects_unknown_cpu_family() {
1582        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1583        report.cpuid_mod_id = Some(0xFF);
1584        report.cpuid_step = Some(0xFF);
1585        assert_eq!(
1586            Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
1587            Err("Report doesn't match any known CPU family"),
1588        );
1589    }
1590
1591    #[test]
1592    fn microcode_check_macro_rejects_missing_cpu_fields() {
1593        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
1594        report.cpuid_mod_id = None;
1595        report.cpuid_step = None;
1596        assert_eq!(
1597            Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
1598            Err("Could not verify microcode version: Missing values for CPU family"),
1599        );
1600    }
1601
1602    fn sb_3020_passing_genoa_report() -> AttestationReport {
1603        use sev::firmware::{
1604            guest::{PlatformInfo, Version},
1605            host::TcbVersion,
1606        };
1607        AttestationReport {
1608            policy: default_guest_policy(),
1609            plat_info: PlatformInfo(1 << 5), // alias_check_complete
1610            committed: Version::new(1, 0x37, 0x31),
1611            committed_tcb: TcbVersion {
1612                snp: 0x1b,
1613                microcode: 0x56,
1614                ..Default::default()
1615            },
1616            cpuid_mod_id: Some(0x11),
1617            cpuid_step: Some(1),
1618            current_mit_vector: Some(Requirements::SB_3020_MIN_GENOA_MIT_VEC),
1619            launch_mit_vector: Some(Requirements::SB_3020_MIN_GENOA_MIT_VEC),
1620            ..Default::default()
1621        }
1622    }
1623
1624    #[test]
1625    fn requirements_check_mit_vector_missing() {
1626        let mut report = sb_3020_passing_genoa_report();
1627        report.current_mit_vector = None;
1628        assert_eq!(
1629            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
1630            Err("Current mitigation vector: not present in report"),
1631        );
1632    }
1633
1634    #[test]
1635    fn requirements_check_mit_vector_insufficient() {
1636        let mut report = sb_3020_passing_genoa_report();
1637        report.current_mit_vector = Some(0);
1638        assert_eq!(
1639            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
1640            Err("Current mitigation vector: required mitigation bits not set"),
1641        );
1642    }
1643
1644    #[test]
1645    fn requirements_check_mit_vector_valid() {
1646        let report = sb_3020_passing_genoa_report();
1647        assert!(Requirements::sb_3020_mitigations()
1648            .verify(vcek::Product::Genoa, &report)
1649            .is_ok());
1650    }
1651
1652    #[test]
1653    fn requirements_check_launch_mit_vector_missing() {
1654        let mut report = sb_3020_passing_genoa_report();
1655        report.launch_mit_vector = None;
1656        assert_eq!(
1657            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
1658            Err("Launch mitigation vector: not present in report"),
1659        );
1660    }
1661
1662    #[test]
1663    fn requirements_check_launch_mit_vector_insufficient() {
1664        let mut report = sb_3020_passing_genoa_report();
1665        report.launch_mit_vector = Some(0);
1666        assert_eq!(
1667            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
1668            Err("Launch mitigation vector: required mitigation bits not set"),
1669        );
1670    }
1671
1672    #[test]
1673    fn requirements_check_sb_3023_mit_vector_requires_extra_genoa_bits() {
1674        let mut report = sb_3020_passing_genoa_report();
1675        report.committed_tcb.microcode = 0x58;
1676        assert_eq!(
1677            Requirements::sb_3023_mitigations().verify(vcek::Product::Genoa, &report),
1678            Err("Current mitigation vector: required mitigation bits not set"),
1679        );
1680        report.current_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
1681        report.launch_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
1682        assert!(Requirements::sb_3023_mitigations()
1683            .verify(vcek::Product::Genoa, &report)
1684            .is_ok());
1685    }
1686
1687    #[test]
1688    fn requirements_check_sb_3023_launch_mit_vector_checked_independently_of_current() {
1689        let mut report = sb_3020_passing_genoa_report();
1690        report.committed_tcb.microcode = 0x58;
1691        report.current_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
1692        assert_eq!(
1693            Requirements::sb_3023_mitigations().verify(vcek::Product::Genoa, &report),
1694            Err("Launch mitigation vector: required mitigation bits not set"),
1695        );
1696    }
1697}