Skip to main content

oasis_core_runtime/common/sgx/pcs/
tcb.rs

1use std::ffi::CString;
2
3use byteorder::{ByteOrder, LittleEndian};
4use chrono::{prelude::*, Duration};
5use mbedtls::{alloc::Box as MbedtlsBox, x509::certificate::Certificate};
6use rustc_hex::FromHex;
7use serde_json::value::RawValue;
8use sgx_isa::Report;
9
10use super::{
11    certificates::PCS_TRUST_ROOT, constants::*, policy::QuotePolicy, quote::TeeType,
12    utils::x509_custom_ts_verify_cb, Error,
13};
14
15/// The TCB bundle contains all the required components to verify a quote's TCB.
16#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
17pub struct TCBBundle {
18    #[cbor(rename = "tcb_info")]
19    pub tcb_info: SignedTCBInfo,
20
21    #[cbor(rename = "qe_id")]
22    pub qe_identity: SignedQEIdentity,
23
24    #[cbor(rename = "certs")]
25    pub certificates: Vec<u8>,
26}
27
28impl TCBBundle {
29    pub(super) fn verify_certificates(
30        &self,
31        ts: DateTime<Utc>,
32    ) -> Result<MbedtlsBox<Certificate>, Error> {
33        let raw_certs =
34            CString::new(&*self.certificates).map_err(|err| Error::TCBParseError(err.into()))?;
35        let mut cert_chain = Certificate::from_pem_multiple(raw_certs.as_bytes_with_nul())
36            .map_err(|err| Error::TCBParseError(err.into()))?;
37        if cert_chain.iter().count() != 2 {
38            return Err(Error::UnexpectedCertificateChain);
39        }
40
41        Certificate::verify_with_callback(
42            &cert_chain,
43            &PCS_TRUST_ROOT,
44            None,
45            None,
46            x509_custom_ts_verify_cb(ts),
47        )
48        .map_err(|_| Error::TCBVerificationFailed)?;
49
50        Ok(cert_chain.pop_front().unwrap())
51    }
52}
53
54#[inline]
55fn encode_raw_value(value: &RawValue) -> Vec<u8> {
56    value.get().as_bytes().to_owned()
57}
58
59#[inline]
60fn decode_raw_value(value: Vec<u8>) -> Result<Box<RawValue>, cbor::DecodeError> {
61    RawValue::from_string(String::from_utf8(value).map_err(|_| cbor::DecodeError::UnexpectedType)?)
62        .map_err(|_| cbor::DecodeError::UnexpectedType)
63}
64
65/// A signed TCB info structure.
66#[derive(Clone, Debug, Default, serde::Deserialize, cbor::Encode, cbor::Decode)]
67pub struct SignedTCBInfo {
68    #[cbor(
69        rename = "tcb_info",
70        serialize_with = "encode_raw_value",
71        deserialize_with = "decode_raw_value"
72    )]
73    #[serde(rename = "tcbInfo")]
74    pub tcb_info: Box<RawValue>,
75
76    #[cbor(rename = "signature")]
77    #[serde(rename = "signature")]
78    pub signature: String,
79}
80
81impl PartialEq for SignedTCBInfo {
82    fn eq(&self, other: &SignedTCBInfo) -> bool {
83        self.tcb_info.get() == other.tcb_info.get() && self.signature == other.signature
84    }
85}
86
87impl Eq for SignedTCBInfo {}
88
89fn open_signed_tcb<'a, T: serde::Deserialize<'a>>(
90    data: &'a str,
91    signature: &str,
92    pk: &mut mbedtls::pk::Pk,
93) -> Result<T, Error> {
94    let mut hash = [0u8; 32];
95    mbedtls::hash::Md::hash(mbedtls::hash::Type::Sha256, data.as_bytes(), &mut hash)
96        .map_err(|_| Error::TCBVerificationFailed)?;
97    let sig: Vec<u8> = signature
98        .from_hex()
99        .map_err(|_| Error::TCBVerificationFailed)?;
100
101    // Convert IEEE P1363 ECDSA signature to RFC5480 ASN.1 representation.
102    if !sig.len().is_multiple_of(2) {
103        return Err(Error::TCBVerificationFailed);
104    }
105
106    let (r_bytes, s_bytes) = sig.split_at(sig.len() / 2);
107    let r = num_bigint::BigUint::from_bytes_be(r_bytes);
108    let s = num_bigint::BigUint::from_bytes_be(s_bytes);
109
110    let sig = yasna::construct_der(|writer| {
111        writer.write_sequence(|writer| {
112            writer.next().write_biguint(&r);
113            writer.next().write_biguint(&s);
114        })
115    });
116
117    pk.verify(mbedtls::hash::Type::Sha256, &hash, &sig)
118        .map_err(|_| Error::TCBVerificationFailed)?;
119
120    serde_json::from_str(data).map_err(|err| Error::TCBParseError(err.into()))
121}
122
123impl SignedTCBInfo {
124    pub fn open(
125        &self,
126        tee_type: TeeType,
127        ts: DateTime<Utc>,
128        policy: &QuotePolicy,
129        pk: &mut mbedtls::pk::Pk,
130    ) -> Result<TCBInfo, Error> {
131        let ti: TCBInfo = open_signed_tcb(self.tcb_info.get(), &self.signature, pk)?;
132        ti.validate(tee_type, ts, policy)?;
133
134        Ok(ti)
135    }
136}
137
138/// TCB info identifier.
139#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
140pub enum TCBInfoID {
141    #[serde(rename = "SGX")]
142    Sgx,
143    #[serde(rename = "TDX")]
144    Tdx,
145    #[serde(other)]
146    #[default]
147    Invalid,
148}
149
150/// TCB info body.
151#[derive(Clone, Debug, Default, serde::Deserialize)]
152pub struct TCBInfo {
153    #[serde(rename = "id")]
154    pub id: TCBInfoID,
155
156    #[serde(rename = "version")]
157    pub version: u32,
158
159    #[serde(rename = "issueDate")]
160    pub issue_date: String,
161
162    #[serde(rename = "nextUpdate")]
163    pub next_update: String,
164
165    #[serde(rename = "fmspc")]
166    pub fmspc: String,
167
168    #[serde(rename = "pceId")]
169    pub pceid: String,
170
171    #[serde(rename = "tcbType")]
172    pub tcb_type: u32,
173
174    #[serde(rename = "tcbEvaluationDataNumber")]
175    pub tcb_evaluation_data_number: u32,
176
177    #[serde(default, rename = "tdxModule")]
178    pub tdx_module: TDXModule,
179
180    #[serde(default, rename = "tdxModuleIdentities")]
181    pub tdx_module_identities: Vec<TDXModuleIdentity>,
182
183    #[serde(rename = "tcbLevels")]
184    pub tcb_levels: Vec<TCBLevel>,
185}
186
187impl TCBInfo {
188    /// Validate the TCB info against the quote policy.
189    pub fn validate(
190        &self,
191        tee_type: TeeType,
192        ts: DateTime<Utc>,
193        policy: &QuotePolicy,
194    ) -> Result<(), Error> {
195        match (self.id, tee_type) {
196            (TCBInfoID::Sgx, TeeType::Sgx) => {}
197            (TCBInfoID::Tdx, TeeType::Tdx) => {}
198            _ => {
199                return Err(Error::TCBParseError(anyhow::anyhow!(
200                    "unexpected TCB info identifier"
201                )))
202            }
203        }
204
205        if self.version != REQUIRED_TCB_INFO_VERSION {
206            return Err(Error::TCBParseError(anyhow::anyhow!(
207                "unexpected TCB info version"
208            )));
209        }
210
211        // Validate TCB info is not expired/not yet valid based on current time.
212        let issue_date = NaiveDateTime::parse_from_str(&self.issue_date, PCS_TS_FMT)
213            .map_err(|err| Error::TCBParseError(err.into()))?
214            .and_utc();
215        let _next_update = NaiveDateTime::parse_from_str(&self.next_update, PCS_TS_FMT)
216            .map_err(|err| Error::TCBParseError(err.into()))?
217            .and_utc();
218        if issue_date > ts {
219            return Err(Error::TCBExpired);
220        }
221        if ts - issue_date
222            > Duration::try_days(policy.tcb_validity_period.into())
223                .unwrap_or(DEFAULT_TCB_VALIDITY_PERIOD)
224        {
225            return Err(Error::TCBExpired);
226        }
227
228        if self.tcb_evaluation_data_number < policy.min_tcb_evaluation_data_number {
229            return Err(Error::TCBEvaluationDataNumberInvalid);
230        }
231
232        // Validate FMSPC is whitelisted.
233        if !policy.fmspc_whitelist.is_empty() && !policy.fmspc_whitelist.contains(&self.fmspc) {
234            return Err(Error::NotWhitelistedFMSPC);
235        }
236
237        // Validate FMSPC is not blacklisted.
238        if policy.fmspc_blacklist.contains(&self.fmspc) {
239            return Err(Error::BlacklistedFMSPC);
240        }
241
242        Ok(())
243    }
244
245    /// Verify and return the TCB level matching the given TCB components and PCESVN.
246    pub fn verify(
247        &self,
248        fmspc: &[u8],
249        sgx_comp_svn: &[u32; 16],
250        tdx_comp_svn: Option<&[u32; 16]>,
251        pcesvn: u32,
252    ) -> Result<TCBLevel, Error> {
253        // Validate FMSPC matches.
254        let expected_fmspc: Vec<u8> = self
255            .fmspc
256            .from_hex()
257            .map_err(|err| Error::TCBParseError(err.into()))?;
258        if fmspc != expected_fmspc {
259            return Err(Error::TCBMismatch);
260        }
261
262        // Find first matching TCB level.
263        let level = self
264            .tcb_levels
265            .iter()
266            .find(|level| level.matches(sgx_comp_svn, tdx_comp_svn, pcesvn))
267            .ok_or(Error::TCBOutOfDate)?
268            .clone();
269
270        if self.id == TCBInfoID::Tdx {
271            // Perform additional TCB status evaluation for TDX module in case TEE TCB SVN at index
272            // 1 is greater or equal to 1, otherwise finish the comparison logic.
273            let tdx_comp_svn = tdx_comp_svn.ok_or(Error::TCBMismatch)?;
274            let tdx_module_version = tdx_comp_svn[1];
275            if tdx_module_version >= 1 {
276                // In order to determine TCB status of TDX module, find a matching TDX Module
277                // Identity (in tdxModuleIdentities array of TCB Info) with its id set to
278                // "TDX_<version>" where <version> matches the value of TEE TCB SVN at index 1. If a
279                // matching TDX Module Identity cannot be found, fail.
280                let tdx_module_id = format!("TDX_{tdx_module_version:02}");
281                let tdx_module = self
282                    .tdx_module_identities
283                    .iter()
284                    .find(|tm| tm.id == tdx_module_id)
285                    .ok_or(Error::TCBOutOfDate)?;
286
287                // Otherwise, for the selected TDX Module Identity go over the sorted collection of
288                // TCB Levels starting from the first item on the list and compare its isvsvn value
289                // to the TEE TCB SVN at index 0. If TEE TCB SVN at index 0 is greater or equal to
290                // its value, read tcbStatus assigned to this TCB level, otherwise move to the next
291                // item on TCB levels list.
292                let tdx_module_level = tdx_module
293                    .tcb_levels
294                    .iter()
295                    .find(|level| level.tcb.isv_svn as u32 <= tdx_comp_svn[0])
296                    .ok_or(Error::TCBOutOfDate)?;
297                if tdx_module_level.status != TCBStatus::UpToDate {
298                    return Err(Error::TCBOutOfDate);
299                }
300            }
301        }
302
303        Ok(level)
304    }
305}
306
307/// A representation of the properties of Intel's TDX SEAM module.
308#[derive(Clone, Debug, Default, serde::Deserialize)]
309pub struct TDXModule {
310    #[serde(rename = "mrsigner")]
311    pub mr_signer: String,
312
313    #[serde(rename = "attributes")]
314    pub attributes: String,
315
316    #[serde(rename = "attributesMask")]
317    pub attributes_mask: String,
318}
319
320/// A representation of the identity of the Intel's TDX SEAM module in case the platform supports
321/// more than one TDX SEAM module.
322#[derive(Clone, Debug, Default, serde::Deserialize)]
323pub struct TDXModuleIdentity {
324    #[serde(rename = "id")]
325    pub id: String,
326
327    #[serde(flatten)]
328    pub module: TDXModule,
329
330    #[serde(rename = "tcbLevels")]
331    pub tcb_levels: Vec<EnclaveTCBLevel>,
332}
333
334/// A platform TCB level.
335#[derive(Clone, Debug, Default, serde::Deserialize)]
336pub struct TCBLevel {
337    #[serde(rename = "tcb")]
338    pub tcb: TCBVersions,
339
340    #[serde(rename = "tcbDate")]
341    pub date: String,
342
343    #[serde(rename = "tcbStatus")]
344    pub status: TCBStatus,
345
346    #[serde(default, rename = "advisoryIDs")]
347    pub advisory_ids: Vec<String>,
348}
349
350impl TCBLevel {
351    /// Whether the TCB level matches the given TCB components and PCESVN.
352    pub fn matches(
353        &self,
354        sgx_comp_svn: &[u32],
355        tdx_comp_svn: Option<&[u32; 16]>,
356        pcesvn: u32,
357    ) -> bool {
358        // a) Compare all of the SGX TCB Comp SVNs retrieved from the SGX PCK Certificate (from 01 to
359        //    16) with the corresponding values in the TCB Level. If all SGX TCB Comp SVNs in the
360        //    certificate are greater or equal to the corresponding values in TCB Level, go to b,
361        //    otherwise move to the next item on TCB Levels list.
362        for (i, comp) in self.tcb.sgx_components.iter().enumerate() {
363            // At least one SVN is lower, no match.
364            if sgx_comp_svn[i] < comp.svn {
365                return false;
366            }
367        }
368
369        // b) Compare PCESVN value retrieved from the SGX PCK certificate with the corresponding value
370        //    in the TCB Level. If it is greater or equal to the value in TCB Level, read status
371        //    assigned to this TCB level (in case of SGX) or go to c (in case of TDX). Otherwise,
372        //    move to the next item on TCB Levels list.
373        if pcesvn < self.tcb.pcesvn {
374            return false;
375        }
376
377        if let Some(tdx_comp_svn) = tdx_comp_svn {
378            // c) Compare SVNs in TEE TCB SVN array retrieved from TD Report in Quote (from index 0 to
379            //    15 if TEE TCB SVN at index 1 is set to 0, or from index 2 to 15 otherwise) with the
380            //    corresponding values of SVNs in tdxtcbcomponents array of TCB Level. If all TEE TCB
381            //    SVNs in the TD Report are greater or equal to the corresponding values in TCB Level,
382            //    read tcbStatus assigned to this TCB level. Otherwise, move to the next item on TCB
383            //    Levels list.
384            let comps = self.tcb.tdx_components.iter().enumerate();
385            let offset = if tdx_comp_svn[1] != 0 { 2 } else { 0 };
386
387            for (i, comp) in comps.skip(offset) {
388                // At least one SVN is lower, no match.
389                if tdx_comp_svn[i] < comp.svn {
390                    return false;
391                }
392            }
393        }
394
395        // Match.
396        true
397    }
398}
399
400/// TCB versions.
401#[derive(Clone, Debug, Default, serde::Deserialize)]
402pub struct TCBVersions {
403    #[serde(rename = "pcesvn")]
404    pub pcesvn: u32,
405
406    #[serde(rename = "sgxtcbcomponents")]
407    pub sgx_components: [TCBComponent; 16],
408
409    #[serde(default, rename = "tdxtcbcomponents")]
410    pub tdx_components: [TCBComponent; 16],
411}
412
413/// A TCB component.
414#[derive(Clone, Debug, Default, serde::Deserialize)]
415pub struct TCBComponent {
416    #[serde(rename = "svn")]
417    pub svn: u32,
418
419    #[serde(default, rename = "category")]
420    pub category: String,
421
422    #[serde(default, rename = "type")]
423    pub tcb_comp_type: String,
424}
425
426/// TCB status.
427#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
428pub enum TCBStatus {
429    UpToDate,
430    SWHardeningNeeded,
431    ConfigurationNeeded,
432    ConfigurationAndSWHardeningNeeded,
433    OutOfDate,
434    OutOfDateConfigurationNeeded,
435    Revoked,
436    #[serde(other)]
437    #[default]
438    Invalid,
439}
440
441/// A signed QE identity structure.
442#[derive(Clone, Debug, Default, serde::Deserialize, cbor::Encode, cbor::Decode)]
443pub struct SignedQEIdentity {
444    #[cbor(
445        rename = "enclave_identity",
446        serialize_with = "encode_raw_value",
447        deserialize_with = "decode_raw_value"
448    )]
449    #[serde(rename = "enclaveIdentity")]
450    pub enclave_identity: Box<RawValue>,
451
452    #[cbor(rename = "signature")]
453    #[serde(rename = "signature")]
454    pub signature: String,
455}
456
457impl PartialEq for SignedQEIdentity {
458    fn eq(&self, other: &SignedQEIdentity) -> bool {
459        self.enclave_identity.get() == other.enclave_identity.get()
460            && self.signature == other.signature
461    }
462}
463
464impl Eq for SignedQEIdentity {}
465
466impl SignedQEIdentity {
467    pub fn open(
468        &self,
469        tee_type: TeeType,
470        ts: DateTime<Utc>,
471        policy: &QuotePolicy,
472        pk: &mut mbedtls::pk::Pk,
473    ) -> Result<QEIdentity, Error> {
474        let qe: QEIdentity = open_signed_tcb(self.enclave_identity.get(), &self.signature, pk)?;
475        qe.validate(tee_type, ts, policy)?;
476
477        Ok(qe)
478    }
479}
480
481/// QE identity identifier.
482#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
483#[allow(non_camel_case_types)]
484pub enum QEIdentityID {
485    QE,
486    TD_QE,
487    #[serde(other)]
488    #[default]
489    Invalid,
490}
491
492/// QE identity body.
493#[derive(Clone, Debug, Default, serde::Deserialize)]
494pub struct QEIdentity {
495    #[serde(rename = "id")]
496    pub id: QEIdentityID,
497
498    #[serde(rename = "version")]
499    pub version: u32,
500
501    #[serde(rename = "issueDate")]
502    pub issue_date: String,
503
504    #[serde(rename = "nextUpdate")]
505    pub next_update: String,
506
507    #[serde(rename = "tcbEvaluationDataNumber")]
508    pub tcb_evaluation_data_number: u32,
509
510    #[serde(rename = "miscselect")]
511    pub miscselect: String,
512
513    #[serde(rename = "miscselectMask")]
514    pub miscselect_mask: String,
515
516    #[serde(rename = "attributes")]
517    pub attributes: String,
518
519    #[serde(rename = "attributesMask")]
520    pub attributes_mask: String,
521
522    #[serde(rename = "mrsigner")]
523    pub mr_signer: String,
524
525    #[serde(rename = "isvprodid")]
526    pub isv_prod_id: u16,
527
528    #[serde(rename = "tcbLevels")]
529    pub tcb_levels: Vec<EnclaveTCBLevel>,
530
531    #[serde(default, rename = "advisoryIDs")]
532    pub advisory_ids: Vec<String>,
533}
534
535impl QEIdentity {
536    /// Validate the QE identity against the quote policy.
537    pub fn validate(
538        &self,
539        tee_type: TeeType,
540        ts: DateTime<Utc>,
541        policy: &QuotePolicy,
542    ) -> Result<(), Error> {
543        match (self.id, tee_type) {
544            (QEIdentityID::QE, TeeType::Sgx) => {}
545            (QEIdentityID::TD_QE, TeeType::Tdx) => {}
546            _ => return Err(Error::TCBParseError(anyhow::anyhow!("unexpected QE ID"))),
547        }
548
549        if self.version != REQUIRED_QE_IDENTITY_VERSION {
550            return Err(Error::TCBParseError(anyhow::anyhow!(
551                "unexpected QE identity version"
552            )));
553        }
554
555        // Validate QE identity is not expired/not yet valid based on current time.
556        let issue_date = NaiveDateTime::parse_from_str(&self.issue_date, PCS_TS_FMT)
557            .map_err(|err| Error::TCBParseError(err.into()))?
558            .and_utc();
559        let _next_update = NaiveDateTime::parse_from_str(&self.next_update, PCS_TS_FMT)
560            .map_err(|err| Error::TCBParseError(err.into()))?
561            .and_utc();
562        if issue_date > ts {
563            return Err(Error::TCBExpired);
564        }
565        if ts - issue_date
566            > Duration::try_days(policy.tcb_validity_period.into())
567                .unwrap_or(DEFAULT_TCB_VALIDITY_PERIOD)
568        {
569            return Err(Error::TCBExpired);
570        }
571
572        if self.tcb_evaluation_data_number < policy.min_tcb_evaluation_data_number {
573            return Err(Error::TCBEvaluationDataNumberInvalid);
574        }
575
576        Ok(())
577    }
578
579    /// Verify the QE report against the QE identity.
580    pub fn verify(&self, report: &Report) -> Result<(), Error> {
581        // Verify if MRSIGNER field retrieved from SGX Enclave Report is equal to the value of
582        // mrsigner field in QE Identity.
583        let expected_mr_signer: Vec<u8> = self
584            .mr_signer
585            .from_hex()
586            .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE MRSIGNER")))?;
587        if expected_mr_signer != report.mrsigner {
588            return Err(Error::TCBVerificationFailed);
589        }
590
591        // Verify if ISVPRODID field retrieved from SGX Enclave Report is equal to the value of
592        // isvprodid field in QE Identity.
593        if self.isv_prod_id != report.isvprodid {
594            return Err(Error::TCBVerificationFailed);
595        }
596
597        // Apply miscselectMask (binary mask) from QE Identity to MISCSELECT field retrieved from
598        // SGX Enclave Report. Verify if the outcome (miscselectMask & MISCSELECT) is equal to the
599        // value of miscselect field in QE Identity.
600        let raw_miscselect: Vec<u8> = self
601            .miscselect
602            .from_hex()
603            .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE miscselect")))?;
604        if raw_miscselect.len() != 4 {
605            return Err(Error::TCBParseError(anyhow::anyhow!(
606                "malformed QE miscselect"
607            )));
608        }
609        let raw_miscselect_mask: Vec<u8> = self
610            .miscselect_mask
611            .from_hex()
612            .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE miscselect mask")))?;
613        if raw_miscselect_mask.len() != 4 {
614            return Err(Error::TCBParseError(anyhow::anyhow!(
615                "malformed QE miscselect"
616            )));
617        }
618        let expected_miscselect = LittleEndian::read_u32(&raw_miscselect);
619        let miscselect_mask = LittleEndian::read_u32(&raw_miscselect_mask);
620        if report.miscselect.bits() & miscselect_mask != expected_miscselect {
621            return Err(Error::TCBVerificationFailed);
622        }
623
624        // Apply attributesMask (binary mask) from QE Identity to ATTRIBUTES field retrieved from
625        // SGX Enclave Report. Verify if the outcome (attributesMask & ATTRIBUTES) is equal to the
626        // value of attributes field in QE Identity.
627        let raw_attributes: Vec<u8> = self
628            .attributes
629            .from_hex()
630            .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE attributes")))?;
631        if raw_attributes.len() != 16 {
632            return Err(Error::TCBParseError(anyhow::anyhow!(
633                "malformed QE attributes"
634            )));
635        }
636        let raw_attributes_mask: Vec<u8> = self
637            .attributes_mask
638            .from_hex()
639            .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE attributes mask")))?;
640        if raw_attributes_mask.len() != 16 {
641            return Err(Error::TCBParseError(anyhow::anyhow!(
642                "malformed QE attributes"
643            )));
644        }
645        let expected_flags = LittleEndian::read_u64(&raw_attributes[..8]);
646        let expected_xfrm = LittleEndian::read_u64(&raw_attributes[8..]);
647        let flags_mask = LittleEndian::read_u64(&raw_attributes_mask[..8]);
648        let xfrm_mask = LittleEndian::read_u64(&raw_attributes_mask[8..]);
649        if report.attributes.flags.bits() & flags_mask != expected_flags {
650            return Err(Error::TCBVerificationFailed);
651        }
652        if report.attributes.xfrm & xfrm_mask != expected_xfrm {
653            return Err(Error::TCBVerificationFailed);
654        }
655
656        // Determine a TCB status of the Quoting Enclave.
657        //
658        // Go over the list of TCB Levels (descending order) and find the one that has ISVSVN that
659        // is lower or equal to the ISVSVN value from SGX Enclave Report.
660        if let Some(level) = self
661            .tcb_levels
662            .iter()
663            .find(|level| level.tcb.isv_svn <= report.isvsvn)
664        {
665            // Ensure that the TCB is up to date.
666            if level.status == TCBStatus::UpToDate {
667                return Ok(());
668            }
669        }
670
671        Err(Error::TCBOutOfDate)
672    }
673}
674
675/// An enclave TCB level.
676#[derive(Clone, Debug, Default, serde::Deserialize)]
677pub struct EnclaveTCBLevel {
678    #[serde(rename = "tcb")]
679    pub tcb: EnclaveTCBVersions,
680
681    #[serde(rename = "tcbDate")]
682    pub date: String,
683
684    #[serde(rename = "tcbStatus")]
685    pub status: TCBStatus,
686
687    #[serde(default, rename = "advisoryIDs")]
688    pub advisory_ids: Vec<String>,
689}
690
691/// Enclave TCB versions.
692#[derive(Clone, Debug, Default, serde::Deserialize)]
693pub struct EnclaveTCBVersions {
694    #[serde(rename = "isvsvn")]
695    pub isv_svn: u16,
696}
697
698#[cfg(test)]
699mod tests {
700    use super::*;
701
702    #[test]
703    fn test_tcb_level_matches() {
704        let pcesvn: u32 = 10;
705        let sgx_svn: [u32; 16] = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30];
706        let tdx_svn: [u32; 16] = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31];
707
708        let mut tl = TCBLevel::default();
709        tl.tcb.pcesvn = pcesvn;
710
711        for i in 0..16 {
712            tl.tcb.sgx_components[i].svn = sgx_svn[i];
713            tl.tcb.tdx_components[i].svn = tdx_svn[i];
714        }
715
716        struct TestCase {
717            name: &'static str,
718            pcesvn: u32,
719            sgx_svn: [u32; 16],
720            tdx_svn: Option<[u32; 16]>,
721            matches: bool,
722        }
723
724        let tcs = vec![
725            TestCase {
726                name: "same values",
727                pcesvn,
728                sgx_svn,
729                tdx_svn: Some(tdx_svn),
730                matches: true,
731            },
732            TestCase {
733                name: "higher pcesvn",
734                pcesvn: pcesvn + 1,
735                sgx_svn,
736                tdx_svn: Some(tdx_svn),
737                matches: true,
738            },
739            TestCase {
740                name: "lower pcesvn",
741                pcesvn: pcesvn - 1,
742                sgx_svn,
743                tdx_svn: Some(tdx_svn),
744                matches: false,
745            },
746            TestCase {
747                name: "higher sgx svn",
748                pcesvn,
749                sgx_svn: {
750                    let mut ss = sgx_svn;
751                    ss[5] += 1;
752                    ss
753                },
754                tdx_svn: Some(tdx_svn),
755                matches: true,
756            },
757            TestCase {
758                name: "lower sgx svn",
759                pcesvn,
760                sgx_svn: {
761                    let mut ss = sgx_svn;
762                    ss[5] -= 1;
763                    ss
764                },
765                tdx_svn: Some(tdx_svn),
766                matches: false,
767            },
768            TestCase {
769                name: "higher tdx svn",
770                pcesvn,
771                sgx_svn,
772                tdx_svn: {
773                    let mut ts = tdx_svn;
774                    ts[5] += 1;
775                    Some(ts)
776                },
777                matches: true,
778            },
779            TestCase {
780                name: "lower tdx svn",
781                pcesvn,
782                sgx_svn,
783                tdx_svn: {
784                    let mut ts = tdx_svn;
785                    ts[5] -= 1;
786                    Some(ts)
787                },
788                matches: false,
789            },
790            TestCase {
791                name: "no tdx svn",
792                pcesvn,
793                sgx_svn,
794                tdx_svn: None,
795                matches: true,
796            },
797        ];
798
799        for tc in tcs {
800            let result = tl.matches(&tc.sgx_svn, tc.tdx_svn.as_ref(), tc.pcesvn);
801            if tc.matches {
802                assert!(result, "tcb level should match when {}", tc.name);
803            } else {
804                assert!(!result, "tcb level should not match when {}", tc.name);
805            }
806        }
807    }
808}