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#[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 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 fn tcb_total_order(platform_status: Option<TcbStatus>, qe_status: Option<TcbStatus>) -> i8 {
108 use std::ops::Neg;
109 use self::TcbStatus::*;
110 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 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 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 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 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 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 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 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 assert_eq!(select(&TcbComponents::from_raw([0; 16], 0), 0).unwrap(), 19);
467 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 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 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 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 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 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}