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 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 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 if cert.is_ca() {
110 return Err(Error::WrongBasicConstraints);
111 }
112
113 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 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 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(¤t.state.cert_data, ¤t.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
255static 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 DecodeError(PEMError),
265 ParseError,
266 NotACertificate,
268 WrongKeyUsage,
269 WrongBasicConstraints,
270 UnsupportedSignatureAlgorithm,
271 ChainBroken,
272 UnknownProductName(String),
273
274 ReportParseError,
276 ReportSignatureParseError,
277 ReportSignatureMismatch,
278 ReportTcbVersionMismatch,
279 RequirementsNotSatisfied(&'static str),
281}
282
283#[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 #[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 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 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#[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 #[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 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 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 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 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 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 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 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 min_committed_version: Option<(u8, u8, u8)>,
506 min_committed_tcb_snp: Option<u8>,
508 min_mit_vector: Option<u64>,
510}
511
512#[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 #[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 #[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 #[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 #[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: ProductRequirements::default(),
658 require_alias_check: true,
659 check_microcode: None,
660 }
661 }
662
663 #[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: ProductRequirements::default(),
679 require_alias_check: false,
680 check_microcode: None,
681 }
682 }
683
684 microcode_check!(sb_3023_microcode_check,
685 (vcek::Product::Milan, (1, 1)) => 0xDE,
687 (vcek::Product::Milan, (1, 2)) => 0x47,
689 (vcek::Product::Genoa, (0x11, 1)) => 0x56,
691 (vcek::Product::Genoa, (0x11, 2)) => 0x51,
693 (vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
695 (vcek::Product::Turin, (2, 1)) => 0x51,
697 (vcek::Product::Turin, (0x11, 0)) => 0x4E,
699 );
700
701 microcode_check!(sb_3020_microcode_check,
702 (vcek::Product::Milan, (1, 1)) => 0xDE,
704 (vcek::Product::Milan, (1, 2)) => 0x45,
706 (vcek::Product::Genoa, (0x11, 1)) => 0x56,
708 (vcek::Product::Genoa, (0x11, 2)) => 0x51,
710 (vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
712 (vcek::Product::Turin, (2, 1)) => 0x50,
714 (vcek::Product::Turin, (0x11, 0)) => 0x4D,
716 );
717
718 microcode_check!(sb_7033_microcode_check,
719 (vcek::Product::Milan, (1, 1)) => 0xDB,
721 (vcek::Product::Milan, (1, 2)) => 0x44,
723 (vcek::Product::Genoa, (0x11, 1)) => 0x54,
725 (vcek::Product::Genoa, (0x11, 2)) => 0x4F,
727 (vcek::Product::Genoa, (0xA0, 2)) => 0x19,
729 (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 bytes[0x188] != 0x19
819 } else {
820 let chip_id = &bytes[0x1A0..0x1E0];
821 if chip_id == [0u8; 64] {
822 return None;
823 }
824
825 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 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 &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 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 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 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 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 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 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 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 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), 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}