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