pcs/
tcb_evaluation_data_numbers.rs

1use chrono::{DateTime, Duration, Utc};
2use crate::{io, Error, Fmspc, Platform, pckcrt::TcbComponents, QeIdentity, QeIdentitySigned, TcbData, TcbInfo, TcbStatus, Unverified, VerificationType, Verified};
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::value::RawValue;
5use std::marker::PhantomData;
6use std::path::PathBuf;
7use std::slice::Iter;
8
9#[cfg(feature = "verify")]
10use {
11    mbedtls::alloc::List as MbedtlsList, mbedtls::x509::certificate::Certificate, mbedtls::error::{codes, Error as ErrMbed}, pkix::oid,
12    pkix::pem::PEM_CERTIFICATE, pkix::x509::GenericCertificate, pkix::FromBer, std::ops::Deref,
13};
14
15/// Implementation of the TcbEvaluationDataNumbers model
16/// <https://api.portal.grustedservices.intel.com/content/documentation.html#pcs-tcb-eval-data-numbers-model-v1>
17#[derive(Clone, Debug)]
18pub struct TcbEvaluationDataNumbers<V: VerificationType = Verified> {
19    #[allow(unused)]
20    id: Platform,
21    #[allow(unused)]
22    version: u16,
23    #[allow(unused)]
24    issue_date: DateTime<Utc>,
25    #[allow(unused)]
26    next_update: DateTime<Utc>,
27    #[allow(unused)]
28    tcb_eval_numbers: Vec<TcbEvalNumber>,
29    type_: PhantomData<V>,
30}
31
32impl<'de> Deserialize<'de> for TcbEvaluationDataNumbers<Unverified> {
33    fn deserialize<D>(deserializer: D) -> Result<TcbEvaluationDataNumbers<Unverified>, D::Error>
34    where
35        D: Deserializer<'de>,
36    {
37        #[derive(Deserialize)]
38        #[serde(rename_all = "camelCase")]
39        struct Dummy {
40            #[serde(default = "crate::sgx_platform")]
41            id: Platform,
42            version: u16,
43            #[serde(with = "crate::iso8601")]
44            issue_date: DateTime<Utc>,
45            #[serde(with = "crate::iso8601")]
46            next_update: DateTime<Utc>,
47            tcb_eval_numbers: Vec<TcbEvalNumber>
48        }
49
50        let Dummy {
51            id,
52            version,
53            issue_date,
54            next_update,
55            tcb_eval_numbers,
56        } = Dummy::deserialize(deserializer)?;
57        Ok(TcbEvaluationDataNumbers {
58            id,
59            version,
60            issue_date,
61            next_update,
62            tcb_eval_numbers,
63            type_: PhantomData,
64        })
65    }
66}
67
68impl<V: VerificationType> TcbEvaluationDataNumbers<V> {
69    pub fn numbers(&self) -> Iter<'_, TcbEvalNumber> {
70        self.tcb_eval_numbers.iter()
71    }
72}
73
74impl TcbEvaluationDataNumbers<Unverified> {
75    /// Given a particular TCB level, select the best available TCB eval number.
76    /// That is the one that gives the most favorable TCB status, and the higher
77    /// one if there's a tie.
78    pub fn select_best(input_dir: &str, fmspc: &Fmspc, tcb_components: &TcbComponents, qesvn: u16) -> Result<TcbEvalNumber, Error> {
79        let evalnums = RawTcbEvaluationDataNumbers::read_from_file(input_dir)?.evaluation_data_numbers()?;
80        let mut tcb_levels: std::collections::HashMap<_, _> = evalnums.numbers().map(|num| (num.number as u64, (num, None, None))).collect();
81
82        for tcbinfo in TcbInfo::read_all(input_dir, fmspc) {
83            let tcb_data = TcbData::parse(tcbinfo?.raw_tcb_info())?;
84            if let Some(level) = tcb_data.tcb_levels()
85                .iter()
86                .find(|level| level.tcb <= *tcb_components)
87            {
88                if let Some(entry) = tcb_levels.get_mut(&tcb_data.tcb_evaluation_data_number()) {
89                    entry.1 = Some(level.tcb_status);
90                }
91            }
92        };
93
94        for qeid in QeIdentitySigned::read_all(input_dir) {
95            let qeid: QeIdentity::<Unverified> = serde_json::from_str(&qeid?.raw_qe_identity()).map_err(|e| Error::ParseError(e))?;
96            if let Some(level) = qeid.tcb_levels()
97                .iter()
98                .find(|level| level.tcb.isvsvn <= qesvn)
99            {
100                if let Some(entry) = tcb_levels.get_mut(&qeid.tcb_evaluation_data_number()) {
101                    entry.2 = Some(level.tcb_status);
102                }
103            }
104        };
105
106        // NB: QE Identity TCB status can only be UpToDate, OutOfDate, or Revoked
107        fn tcb_total_order(platform_status: Option<TcbStatus>, qe_status: Option<TcbStatus>) -> i8 {
108            use std::ops::Neg;
109            use self::TcbStatus::*;
110            // Since we don't have any information here to judge the enclave
111            // has the needed SW hardening, we assume that it does and we
112            // upgrade SWHardeningNeeded to the next level
113            match (platform_status.map(TcbStatus::drop_sw_hardening_needed), qe_status) {
114                (Some(UpToDate),                          Some(UpToDate))  => 0i8,
115                (Some(UpToDate),                          Some(OutOfDate)) => 1,
116                (Some(UpToDate),                          Some(Revoked))   => 1,
117                (Some(ConfigurationNeeded),               Some(UpToDate))  => 2,
118                (Some(ConfigurationNeeded),               Some(OutOfDate)) => 3,
119                (Some(ConfigurationNeeded),               Some(Revoked))   => 3,
120                (Some(OutOfDate),                         Some(UpToDate))  => 4,
121                (Some(OutOfDateConfigurationNeeded),      Some(UpToDate))  => 4,
122                (Some(Revoked),                           Some(UpToDate))  => 4,
123                (Some(OutOfDate),                         Some(OutOfDate)) => 5,
124                (Some(OutOfDate),                         Some(Revoked))   => 5,
125                (Some(Revoked),                           Some(OutOfDate)) => 5,
126                (Some(Revoked),                           Some(Revoked))   => 5,
127                (Some(OutOfDateConfigurationNeeded),      Some(OutOfDate)) => 5,
128                (Some(OutOfDateConfigurationNeeded),      Some(Revoked))   => 5,
129                (Some(UpToDate),                          None)            => 6,
130                (Some(ConfigurationNeeded),               None)            => 7,
131                (Some(OutOfDate),                         None)            => 8,
132                (Some(OutOfDateConfigurationNeeded),      None)            => 8,
133                (Some(Revoked),                           None)            => 8,
134                (None,                                    Some(UpToDate))  => 9,
135                (None,                                    Some(OutOfDate)) => 10,
136                (None,                                    Some(Revoked))   => 10,
137                _                                                          => 11,
138            }.neg()
139        }
140
141        tcb_levels.into_iter()
142            .max_by(|&(a_num, (_, a_platform_status, a_qe_status)), &(b_num, (_, b_platform_status, b_qe_status))| {
143                tcb_total_order(a_platform_status, a_qe_status)
144                    .cmp(&tcb_total_order(b_platform_status, b_qe_status))
145                    .then_with(|| a_num.cmp(&b_num) )
146            })
147            .map(|(_, (num, _, _))| num.clone())
148            .ok_or(Error::InvalidTcbEvaluationDataNumbers("Empty TCB evaluation data numbers".into()))
149    }
150}
151
152#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct TcbEvalNumber {
155    #[serde(rename = "tcbEvaluationDataNumber")]
156    number: u16,
157    #[serde(with = "crate::iso8601")]
158    tcb_recovery_event_date: DateTime<Utc>,
159    #[serde(with = "crate::iso8601")]
160    tcb_date: DateTime<Utc>,
161}
162
163impl TcbEvalNumber {
164    pub fn number(&self) -> u16 {
165        self.number
166    }
167
168    pub fn tcb_recovery_event_date(&self) -> &DateTime<Utc> {
169        &self.tcb_recovery_event_date
170    }
171
172    pub fn tcb_date(&self) -> &DateTime<Utc> {
173        &self.tcb_date
174    }
175}
176
177#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
178pub struct RawTcbEvaluationDataNumbers {
179    raw_tcb_evaluation_data_numbers: String,
180    signature: Vec<u8>,
181    ca_chain: Vec<String>,
182}
183
184impl RawTcbEvaluationDataNumbers {
185    const DEFAULT_FILENAME: &'static str = "tcb_evaluation_data_numbers.numbers";
186
187    pub fn new(raw_tcb_evaluation_data_numbers: String, signature: Vec<u8>, ca_chain: Vec<String>) -> Self {
188        RawTcbEvaluationDataNumbers {
189            raw_tcb_evaluation_data_numbers,
190            signature,
191            ca_chain,
192        }
193    }
194
195    pub fn filename() -> String {
196        Self::DEFAULT_FILENAME.into()
197    }
198
199    pub fn parse(body: &String, ca_chain: Vec<String>) -> Result<Self, Error> {
200        #[derive(Deserialize)]
201        #[serde(rename_all = "camelCase")]
202        struct IntelTcbEvaluationDataNumbers<'a> {
203            #[serde(borrow, rename = "tcbEvaluationDataNumbers")]
204            raw_tcb_evaluation_data_numbers: &'a RawValue,
205            #[serde(deserialize_with = "crate::intel_signature_deserializer")]
206            signature: Vec<u8>,
207        }
208        let IntelTcbEvaluationDataNumbers { raw_tcb_evaluation_data_numbers, signature } = serde_json::from_str(&body)?;
209
210        Ok(RawTcbEvaluationDataNumbers::new(raw_tcb_evaluation_data_numbers.to_string(), signature, ca_chain))
211    }
212
213    /// Returns the raw TCB evaluation data numbers as signed by Intel
214    pub fn raw_tcb_evaluation_data_numbers(&self) -> &str {
215        &self.raw_tcb_evaluation_data_numbers
216    }
217
218    pub fn signature(&self) -> &Vec<u8> {
219        &self.signature
220    }
221
222    pub fn certificate_chain(&self) -> &Vec<String> {
223        &self.ca_chain
224    }
225
226    pub fn write_to_file(&self, output_dir: &str) -> Result<String, Error> {
227        io::write_to_file(&self, output_dir, Self::DEFAULT_FILENAME)?;
228        Ok(Self::DEFAULT_FILENAME.to_string())
229    }
230
231    pub fn write_to_file_if_not_exist(&self, output_dir: &str) -> Result<Option<PathBuf>, Error> {
232        io::write_to_file_if_not_exist(&self, output_dir, Self::DEFAULT_FILENAME)
233    }
234
235    pub fn read_from_file(input_dir: &str) -> Result<Self, Error> {
236        let identity: Self = io::read_from_file(input_dir, Self::DEFAULT_FILENAME)?;
237        Ok(identity)
238    }
239
240    /// Returns the TCB evaluation data numbers present. Warning: These values should not
241    /// be trusted as there is no guarantee the RawTcbEvaluationDataNumbers is valid. If this
242    /// result must be trustworthy, you need to call `verify`
243    pub fn evaluation_data_numbers(&self) -> Result<TcbEvaluationDataNumbers<Unverified>, Error> {
244        serde_json::from_str(&self.raw_tcb_evaluation_data_numbers).map_err(|e| Error::ParseError(e))
245    }
246
247    #[cfg(feature = "verify")]
248    pub fn verify<B: Deref<Target = [u8]>>(&self, trusted_root_certs: &[B], platform: Platform) -> Result<TcbEvaluationDataNumbers, Error> {
249        self.verify_ex(trusted_root_certs, platform, &Utc::now())
250    }
251
252    #[cfg(feature = "verify")]
253    fn verify_ex<B: Deref<Target = [u8]>>(&self, trusted_root_certs: &[B], platform: Platform, now: &DateTime<Utc>) -> Result<TcbEvaluationDataNumbers, Error> {
254        // Check cert chain
255        let (chain, root) = crate::create_cert_chain(&self.ca_chain)?;
256        let mut leaf = chain.first().unwrap_or(&root).clone();
257        let root_list = std::iter::once(root).collect();
258        if 0 < chain.len() {
259            let trust_ca: MbedtlsList<Certificate> = chain.into_iter().collect();
260            let mut err = String::default();
261            Certificate::verify(&trust_ca, &root_list, None, Some(&mut err))
262                .map_err(|e| Error::UntrustworthyTcbEvaluationDataNumber(e))?;
263        }
264
265        // Check signature on data
266        let mut hash = [0u8; 32];
267        mbedtls::hash::Md::hash(mbedtls::hash::Type::Sha256, self.raw_tcb_evaluation_data_numbers.as_bytes(), &mut hash).unwrap();
268        leaf.public_key_mut()
269            .verify(mbedtls::hash::Type::Sha256, &hash, self.signature())
270            .map_err(|e| Error::UntrustworthyTcbEvaluationDataNumber(e))?;
271
272        // Check common name TCB cert
273        let leaf = self.ca_chain.first().ok_or(Error::IncorrectCA)?;
274        let tcb =
275            &pkix::pem::pem_to_der(&leaf, Some(PEM_CERTIFICATE)).ok_or(Error::UntrustworthyTcbEvaluationDataNumber(ErrMbed::HighLevel(codes::X509BadInputData)))?;
276        let tcb = GenericCertificate::from_ber(&tcb).map_err(|_| Error::UntrustworthyTcbEvaluationDataNumber(ErrMbed::HighLevel(codes::X509BadInputData)))?;
277        let name = tcb
278            .tbscert
279            .subject
280            .get(&*oid::commonName)
281            .ok_or(Error::UntrustworthyTcbEvaluationDataNumber(ErrMbed::HighLevel(codes::X509BadInputData)))?;
282        if String::from_utf8_lossy(&name.value()) != "Intel SGX TCB Signing" {
283            return Err(Error::IncorrectCA);
284        }
285
286        crate::check_root_ca(trusted_root_certs, &root_list)?;
287
288        let TcbEvaluationDataNumbers::<Unverified> {
289            id,
290            version,
291            issue_date,
292            next_update,
293            tcb_eval_numbers,
294            ..
295        } = serde_json::from_str(&self.raw_tcb_evaluation_data_numbers).map_err(|e| Error::ParseError(e))?;
296
297        if version != 1 {
298            return Err(Error::InvalidTcbEvaluationDataNumbers(format!("TCB Evaluation Data Numbers version 1 expected, got {version}")));
299        }
300
301        if id != platform {
302            return Err(Error::InvalidTcbEvaluationDataNumbers(format!("TCB Evaluation Data Numbers only valid for {id}, expected one for {platform}")));
303        }
304
305        if *now < issue_date {
306            return Err(Error::InvalidTcbEvaluationDataNumbers(format!("TCB Evaluation Data Numbers only valid from {issue_date}")));
307        }
308        if next_update < *now {
309            return Err(Error::InvalidTcbEvaluationDataNumbers(format!("TCB Evaluation Data Numbers only valid upto {next_update}")));
310        }
311
312        Ok(TcbEvaluationDataNumbers::<Verified> {
313            id,
314            version,
315            issue_date,
316            next_update,
317            tcb_eval_numbers,
318            type_: PhantomData,
319        })
320    }
321}
322
323#[derive(Debug, Clone, PartialEq, Eq)]
324pub struct TcbPolicy {
325    grace_period: Duration,
326}
327
328impl TcbPolicy {
329    /// A TCB policy that allows for TCB info to be used until `grace_period` after
330    /// a TCB recovery event
331    pub const fn new(grace_period: Duration) -> Self {
332        Self {
333            grace_period
334        }
335    }
336
337    fn needs_to_be_enforced(&self, tcb_eval: &TcbEvalNumber, now: &DateTime<Utc>) -> bool {
338        *tcb_eval.tcb_recovery_event_date() + self.grace_period <= *now
339    }
340
341    fn minimum_tcb_evaluation_data_number_ex<V: VerificationType>(&self, tcb_eval: &TcbEvaluationDataNumbers<V>, now: &DateTime<Utc>) -> Option<TcbEvalNumber> {
342        tcb_eval.numbers().fold(None, |last, number| {
343            match (last, self.needs_to_be_enforced(number, &now)) {
344                (last, false) => last,
345                (Some(last), true) => {
346                    if last.number() <= number.number() {
347                        // We need the highest TCB Eval Data Number that needs to be enforced
348                        Some(number.clone())
349                    } else {
350                        Some(last)
351                    }
352                },
353                (None, true)  => Some(number.clone()),
354            }
355        })
356    }
357
358    pub fn minimum_tcb_evaluation_data_number<V: VerificationType>(&self, tcb_eval: &TcbEvaluationDataNumbers<V>) -> Option<TcbEvalNumber> {
359        self.minimum_tcb_evaluation_data_number_ex(tcb_eval, &Utc::now())
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    #[cfg(not(target_env = "sgx"))]
366    use {
367        super::TcbEvaluationDataNumbers,
368        crate::{Error, Unverified}
369    };
370    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
371    use super::{RawTcbEvaluationDataNumbers, TcbPolicy, TcbEvalNumber};
372    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
373    use crate::Platform;
374    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
375    use chrono::{Duration, TimeZone, Utc};
376
377    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
378    #[test]
379    fn parse_tcb_evaluation_data_numbers() {
380        let numbers = RawTcbEvaluationDataNumbers::read_from_file("./tests/data").unwrap();
381        let root_certificate = include_bytes!("../tests/data/root_SGX_CA_der.cert");
382        let root_certificates = [&root_certificate[..]];
383        numbers.verify_ex(&root_certificates, Platform::SGX, &Utc.with_ymd_and_hms(2025, 6, 4, 12, 0, 0).unwrap()).unwrap();
384    }
385
386    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
387    #[test]
388    fn parse_tcb_evaluation_data_numbers_incorrect_signature() {
389        let numbers = RawTcbEvaluationDataNumbers::read_from_file("./tests/data").unwrap();
390        let root_certificate = include_bytes!("../tests/data/root_SGX_CA_der.cert").to_owned();
391        let root_certificates = [&root_certificate[..]];
392        numbers.verify_ex(&root_certificates, Platform::SGX, &Utc.with_ymd_and_hms(2025, 6, 4, 12, 0, 0).unwrap()).unwrap();
393
394        let mut corrupted = numbers.clone();
395        corrupted.signature[10] = 0x66;
396        if let Err(Error::UntrustworthyTcbEvaluationDataNumber(_s)) = corrupted.verify(&root_certificates, Platform::SGX) {
397            ();
398        } else {
399            assert!(false);
400        }
401    }
402
403    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
404    #[test]
405    fn tcb_eval_number() {
406        let april_8_2025 = Utc.with_ymd_and_hms(2025, 4, 8, 14, 55, 0).unwrap();
407        let april_9_2025 = Utc.with_ymd_and_hms(2025, 4, 9, 14, 55, 0).unwrap();
408        let april_15_2025 = Utc.with_ymd_and_hms(2025, 4, 15, 14, 55, 0).unwrap();
409        let april_16_2025 = Utc.with_ymd_and_hms(2025, 4, 16, 14, 55, 0).unwrap();
410        let april_17_2025 = Utc.with_ymd_and_hms(2025, 4, 17, 14, 55, 0).unwrap();
411        let number = TcbEvalNumber {
412            number: 42,
413            tcb_recovery_event_date: april_9_2025,
414            tcb_date: april_9_2025,
415        };
416
417        let policy = TcbPolicy::new(Duration::days(8));
418        assert!(!policy.needs_to_be_enforced(&number, &april_8_2025));
419        assert!(!policy.needs_to_be_enforced(&number, &april_9_2025));
420        assert!(!policy.needs_to_be_enforced(&number, &april_15_2025));
421        assert!(!policy.needs_to_be_enforced(&number, &april_16_2025));
422        assert!(policy.needs_to_be_enforced(&number, &april_17_2025));
423    }
424
425    #[cfg(all(not(target_env = "sgx"), feature = "verify"))]
426    #[test]
427    fn minimum_tcb_evaluation_data_number() {
428        let numbers = RawTcbEvaluationDataNumbers::read_from_file("./tests/data").unwrap();
429        let numbers: TcbEvaluationDataNumbers<Unverified> = serde_json::from_str(numbers.raw_tcb_evaluation_data_numbers()).unwrap();
430
431        let april_8_2025 = Utc.with_ymd_and_hms(2025, 4, 8, 0, 0, 0).unwrap();
432        let march_12_2024 = Utc.with_ymd_and_hms(2024, 3, 12, 0, 0, 0).unwrap();
433        let march_13_2024 = Utc.with_ymd_and_hms(2024, 3, 13, 0, 0, 0).unwrap();
434        let november_12_2024 = Utc.with_ymd_and_hms(2024, 11, 12, 0, 0, 0).unwrap();
435        let november_13_2024 = Utc.with_ymd_and_hms(2024, 11, 13, 0, 0, 0).unwrap();
436        let november_20_2024 = Utc.with_ymd_and_hms(2024, 11, 20, 0, 0, 0).unwrap();
437        let number_17 = TcbEvalNumber {
438            number: 17,
439            tcb_recovery_event_date: march_12_2024,
440            tcb_date: march_13_2024,
441        };
442        let number_18 = TcbEvalNumber {
443            number: 18,
444            tcb_recovery_event_date: november_12_2024,
445            tcb_date: november_13_2024,
446        };
447        let policy = TcbPolicy::new(Duration::days(10));
448        assert_eq!(policy.minimum_tcb_evaluation_data_number_ex(&numbers, &april_8_2025),
449            Some(number_18));
450        assert_eq!(policy.minimum_tcb_evaluation_data_number_ex(&numbers, &november_13_2024),
451            Some(number_17.clone()));
452        assert_eq!(policy.minimum_tcb_evaluation_data_number_ex(&numbers, &november_20_2024),
453            Some(number_17));
454    }
455
456    #[cfg(not(target_env = "sgx"))]
457    #[test]
458    fn select_best() {
459        use crate::pckcrt::TcbComponents;
460        fn select(tcb_components: &TcbComponents, qesvn: u16) -> Result<u16, Error> {
461            use std::convert::TryInto;
462            TcbEvaluationDataNumbers::<Unverified>::select_best("./tests/data/eval-num-select-best", &"00606a000000".try_into().unwrap(), tcb_components, qesvn)
463                .map(|num| num.number)
464        }
465        // platform and QE are nonsensical: just choose highest
466        assert_eq!(select(&TcbComponents::from_raw([0; 16], 0), 0).unwrap(), 19);
467        // platform is nonsensical: choose eval nums based on QE up-to-date
468        assert_eq!(select(&TcbComponents::from_raw([0; 16], 0), 8).unwrap(), 19);
469        assert_eq!(select(&TcbComponents::from_raw([0; 16], 0), 6).unwrap(), 8);
470        // QE is nonsensical: choose eval nums based on platform up-to-date
471        assert_eq!(select(&TcbComponents::from_raw([16, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 0).unwrap(), 19);
472        assert_eq!(select(&TcbComponents::from_raw([15, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 0).unwrap(), 18);
473        assert_eq!(select(&TcbComponents::from_raw([14, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 0).unwrap(), 17);
474        assert_eq!(select(&TcbComponents::from_raw([7, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 0).unwrap(), 14);
475        // platform and QE are fully up to date: choose highest
476        assert_eq!(select(&TcbComponents::from_raw([16, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 8).unwrap(), 19);
477        // QE is up to date: choose up-to-date eval nums based on platform
478        assert_eq!(select(&TcbComponents::from_raw([15, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 8).unwrap(), 18);
479        assert_eq!(select(&TcbComponents::from_raw([14, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 8).unwrap(), 17);
480        assert_eq!(select(&TcbComponents::from_raw([7, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 8).unwrap(), 8);
481        // platform is up to date: choose up-to-date eval nums based on QE
482        assert_eq!(select(&TcbComponents::from_raw([16, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 6).unwrap(), 8);
483        // neither platform and QE are up to date: choose highest eval nums where they were both up to date
484        assert_eq!(select(&TcbComponents::from_raw([4, 16, 3, 3, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 13), 5).unwrap(), 8);
485    }
486}