1use std::convert::TryFrom;
9use std::marker::PhantomData;
10use std::path::PathBuf;
11
12use chrono::{DateTime, Utc};
13use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
14use serde_json::value::RawValue;
15#[cfg(feature = "verify")]
16use {
17 mbedtls::alloc::List as MbedtlsList, mbedtls::x509::certificate::Certificate, mbedtls::error::{codes, Error as ErrMbed}, pkix::oid,
18 pkix::pem::PEM_CERTIFICATE, pkix::x509::GenericCertificate, pkix::FromBer, std::ops::Deref,
19};
20
21use crate::pckcrt::{TcbComponent, TcbComponents};
22use crate::{io, CpuSvn, Error, PceIsvsvn, Platform, TcbStatus, Unverified, VerificationType, Verified};
23
24#[derive(Debug, Clone, Hash, PartialEq, Eq)]
25pub struct Fmspc([u8; 6]);
26
27#[derive(Debug)]
28pub enum FmspcDecodeError {
29 InvalidHex,
30 InvalidFmspcLength,
31}
32
33impl From<base16::DecodeError> for FmspcDecodeError {
34 fn from(_value: base16::DecodeError) -> FmspcDecodeError {
35 FmspcDecodeError::InvalidHex
36 }
37}
38
39impl Fmspc {
40 pub const fn new(value: [u8; 6]) -> Self {
41 Fmspc(value)
42 }
43
44 pub fn as_bytes(&self) -> &[u8; 6] {
45 &self.0
46 }
47}
48
49impl From<[u8; 6]> for Fmspc {
50 fn from(value: [u8; 6]) -> Fmspc {
51 Fmspc::new(value)
52 }
53}
54
55impl TryFrom<&[u8]> for Fmspc {
56 type Error = FmspcDecodeError;
57
58 fn try_from(value: &[u8]) -> Result<Fmspc, FmspcDecodeError> {
59 let value = <[u8; 6]>::try_from(value).map_err(|_| FmspcDecodeError::InvalidFmspcLength)?;
60 Ok(Fmspc::new(value))
61 }
62}
63
64impl TryFrom<&Vec<u8>> for Fmspc {
65 type Error = FmspcDecodeError;
66
67 fn try_from(value: &Vec<u8>) -> Result<Fmspc, FmspcDecodeError> {
68 Fmspc::try_from(value.as_slice())
69 }
70}
71
72impl TryFrom<&str> for Fmspc {
73 type Error = FmspcDecodeError;
74
75 fn try_from(value: &str) -> Result<Fmspc, FmspcDecodeError> {
76 let value = base16::decode(value)?;
77 Fmspc::try_from(value.as_slice())
78 }
79}
80
81impl TryFrom<&String> for Fmspc {
82 type Error = FmspcDecodeError;
83
84 fn try_from(value: &String) -> Result<Fmspc, FmspcDecodeError> {
85 Fmspc::try_from(value.as_str())
86 }
87}
88
89impl ToString for Fmspc {
90 fn to_string(&self) -> String {
91 base16::encode_lower(&self.0)
92 }
93}
94
95impl Serialize for Fmspc {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: Serializer,
99 {
100 self.to_string().serialize(serializer)
101 }
102}
103
104impl<'de> Deserialize<'de> for Fmspc {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: Deserializer<'de>,
108 {
109 let fmspc = <&str>::deserialize(deserializer)?;
110 Fmspc::try_from(fmspc).map_err(|_| de::Error::custom("Bad fmspc format"))
111 }
112}
113
114#[derive(Serialize, Deserialize, Clone, Debug)]
115#[serde(rename_all = "camelCase")]
116pub struct TcbLevel {
117 pub(crate) tcb: TcbComponents,
118 #[serde(with = "crate::iso8601")]
119 tcb_date: DateTime<Utc>,
120 pub(crate) tcb_status: TcbStatus,
121 #[serde(default, rename = "advisoryIDs", skip_serializing_if = "Vec::is_empty")]
122 advisory_ids: Vec<AdvisoryID>,
123}
124
125impl TcbLevel {
126 pub fn components(&self) -> &TcbComponents {
127 &self.tcb
128 }
129
130 pub fn tcb_status(&self) -> TcbStatus {
131 self.tcb_status
132 }
133
134 pub fn advisory_ids(&self) -> &Vec<AdvisoryID> {
135 &self.advisory_ids
136 }
137
138 pub fn tcb_date(&self) -> &DateTime<Utc> {
139 &self.tcb_date
140 }
141}
142
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub enum AdvisoryID {
145 Security(u32),
150 Documentation(u32)
155}
156
157impl ToString for AdvisoryID {
158 fn to_string(&self) -> String {
159 match self {
160 AdvisoryID::Security(v) => format!("INTEL-SA-{:05}", v),
161 AdvisoryID::Documentation(v) => format!("INTEL-DOC-{:05}", v),
162 }
163 }
164}
165
166impl TryFrom<&str> for AdvisoryID {
167 type Error = &'static str;
168
169 fn try_from(s: &str) -> Result<AdvisoryID, &'static str> {
170 fn tokenize(s: &str) -> Result<(String, String, u32), &'static str> {
171 let mut chunks = s.trim().split('-');
172
173 let intel = chunks.next().ok_or("Couldn't parse INTEL part of advisory ID number")?;
174 let typ = chunks.next().ok_or("Couldn't parse type of advisory ID number")?;
175 let value = chunks.next().ok_or("Couldn't parse value of advisory ID number")?;
176 let value = u32::from_str_radix(value, 10).map_err(|_| "Couldn't parse advisory ID number")?;
177
178 if chunks.next().is_some() {
179 return Err("Failed to parse Advisory ID");
180 }
181
182 Ok((intel.to_string(), typ.to_string(), value))
183 }
184
185 let (intel, typ, value) = tokenize(s)?;
186 if intel.to_uppercase() != "INTEL" {
187 return Err("Advisory IDs must start with INTEL");
188 }
189
190 match typ.to_uppercase().as_str() {
191 "SA" => Ok(AdvisoryID::Security(value)),
192 "DOC" => Ok(AdvisoryID::Documentation(value)),
193 _ => Err("Not a security nor document advisory ID"),
194 }
195 }
196}
197
198impl Serialize for AdvisoryID {
199 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
200 where
201 S: Serializer,
202 {
203 self.to_string().serialize(serializer)
204 }
205}
206
207impl<'de> Deserialize<'de> for AdvisoryID {
208 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
209 where
210 D: Deserializer<'de>,
211 {
212 let advisory = <&str>::deserialize(deserializer)?;
213 AdvisoryID::try_from(advisory).map_err(|e| de::Error::custom(format!("Bad AdvisoryID format: {e}")))
214 }
215}
216
217#[allow(dead_code)]
218#[derive(Clone, Debug)]
219pub struct TcbData<V: VerificationType = Verified> {
220 id: Platform,
221 version: u16,
222 issue_date: DateTime<Utc>,
223 next_update: DateTime<Utc>,
224 fmspc: Fmspc,
225 pce_id: String,
226 tcb_type: u16,
227 tcb_evaluation_data_number: u64,
228 tcb_levels: Vec<TcbLevel>,
229 type_: PhantomData<V>,
230}
231
232impl<'de> Deserialize<'de> for TcbData<Unverified> {
233 fn deserialize<D>(deserializer: D) -> Result<TcbData<Unverified>, D::Error>
234 where
235 D: Deserializer<'de>,
236 {
237 #[derive(Deserialize)]
238 #[serde(rename_all = "camelCase")]
239 struct Dummy {
240 #[serde(default = "crate::sgx_platform")]
241 id: Platform,
242 version: u16,
243 #[serde(with = "crate::iso8601")]
244 issue_date: DateTime<Utc>,
245 #[serde(with = "crate::iso8601")]
246 next_update: DateTime<Utc>,
247 fmspc: Fmspc,
248 pce_id: String,
249 tcb_type: u16,
250 tcb_evaluation_data_number: u64,
251 #[serde(rename = "tcbLevels")]
252 tcb_levels: Vec<TcbLevel>,
253 }
254
255 let Dummy {
256 id,
257 version,
258 issue_date,
259 next_update,
260 fmspc,
261 pce_id,
262 tcb_type,
263 tcb_evaluation_data_number,
264 tcb_levels,
265 } = Dummy::deserialize(deserializer)?;
266 Ok(TcbData {
267 id,
268 version,
269 issue_date,
270 next_update,
271 fmspc,
272 pce_id,
273 tcb_type,
274 tcb_evaluation_data_number,
275 tcb_levels,
276 type_: PhantomData,
277 })
278 }
279}
280
281impl TcbData<Verified> {
282 pub fn version(&self) -> u16 {
283 self.version
284 }
285
286 pub fn fmspc(&self) -> &Fmspc {
287 &self.fmspc
288 }
289}
290
291impl TcbData<Unverified> {
292 pub(crate) fn parse(raw_tcb_data: &String) -> Result<TcbData<Unverified>, Error> {
293 let data: TcbData<Unverified> = serde_json::from_str(&raw_tcb_data).map_err(|e| Error::ParseError(e))?;
294 if data.version != 2 && data.version != 3 {
295 return Err(Error::UnknownTcbInfoVersion(data.version));
296 }
297
298 if data.tcb_type != 0 {
300 return Err(Error::UnknownTcbType(data.tcb_type));
301 }
302 Ok(data)
303 }
304
305 pub fn tcb_component_index(&self, comp: TcbComponent) -> Option<usize> {
307 if let Some(c) = self.tcb_levels.first() {
308 c.components().tcb_component_index(comp)
309 } else {
310 None
311 }
312 }
313
314}
315
316impl<V: VerificationType> TcbData<V> {
317 pub fn tcb_evaluation_data_number(&self) -> u64 {
318 self.tcb_evaluation_data_number
319 }
320
321 pub(crate) fn tcb_levels(&self) -> &Vec<TcbLevel> {
324 &self.tcb_levels
325 }
326
327 pub(crate) fn decompose_raw_cpusvn(&self, raw_cpusvn: &[u8; 16], pce_svn: u16) -> Result<TcbComponents, Error> {
328 if self.tcb_type != 0 {
329 return Err(Error::UnknownTcbType(self.tcb_type));
330 }
331
332 Ok(TcbComponents::from_raw(*raw_cpusvn, pce_svn))
334 }
335
336 pub fn iter_tcb_components(&self) -> impl Iterator<Item = (CpuSvn, PceIsvsvn)> + '_ {
337 self.tcb_levels.iter().map(|tcb_level| (tcb_level.tcb.cpu_svn(), tcb_level.tcb.pce_svn()))
338 }
339}
340
341#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
342pub struct TcbInfo {
343 raw_tcb_info: String,
344 signature: Vec<u8>,
345 ca_chain: Vec<String>,
346}
347
348impl TcbInfo {
349 const FILENAME_EXTENSION: &'static str = ".tcb";
350
351 pub fn new(raw_tcb_info: String, signature: Vec<u8>, ca_chain: Vec<String>) -> Self {
352 TcbInfo {
353 raw_tcb_info,
354 signature,
355 ca_chain,
356 }
357 }
358
359 pub fn parse(body: &String, ca_chain: Vec<String>) -> Result<Self, Error> {
360 #[derive(Deserialize)]
361 #[serde(rename_all = "camelCase")]
362 struct IntelTcbInfoSigned<'a> {
363 #[serde(borrow, rename = "tcbInfo")]
364 raw_tcb_info: &'a RawValue,
365 #[serde(deserialize_with = "crate::intel_signature_deserializer")]
366 signature: Vec<u8>,
367 }
368 let IntelTcbInfoSigned { raw_tcb_info, signature } = serde_json::from_str(&body)?;
369
370 Ok(TcbInfo::new(raw_tcb_info.to_string(), signature, ca_chain))
371 }
372
373 pub fn create_filename(fmspc: &str, evaluation_data_number: Option<u64>) -> String {
374 io::compose_filename(fmspc, Self::FILENAME_EXTENSION, evaluation_data_number)
375 }
376
377 pub fn store(&self, output_dir: &str) -> Result<String, Error> {
378 let data = TcbData::<Unverified>::parse(&self.raw_tcb_info)?;
379 let filename = Self::create_filename(&data.fmspc.to_string(), Some(data.tcb_evaluation_data_number));
380 io::write_to_file(&self, output_dir, &filename)?;
381 Ok(filename)
382 }
383
384 pub fn store_if_not_exist(&self, output_dir: &str) -> Result<Option<PathBuf>, Error> {
385 let data = TcbData::<Unverified>::parse(&self.raw_tcb_info)?;
386 let filename = Self::create_filename(&data.fmspc.to_string(), Some(data.tcb_evaluation_data_number));
387 io::write_to_file_if_not_exist(&self, output_dir, &filename)
388 }
389
390 pub fn restore(input_dir: &str, fmspc: &Fmspc, evaluation_data_number: Option<u64>) -> Result<Self, Error> {
391 let filename = TcbInfo::create_filename(&fmspc.to_string(), evaluation_data_number);
392 let info: TcbInfo = io::read_from_file(input_dir, &filename)?;
393 Ok(info)
394 }
395
396 pub fn read_all<'a>(input_dir: &'a str, fmspc: &'a Fmspc) -> impl Iterator<Item = Result<Self, Error>> + 'a {
397 io::all_files(input_dir, fmspc.to_string(), Self::FILENAME_EXTENSION)
398 .map(move |i| i.and_then(|entry| io::read_from_file(input_dir, entry.file_name())) )
399 }
400
401 pub fn raw_tcb_info(&self) -> &String {
402 &self.raw_tcb_info
403 }
404
405 pub fn signature(&self) -> &Vec<u8> {
406 &self.signature
407 }
408
409 pub fn certificate_chain(&self) -> &Vec<String> {
410 &self.ca_chain
411 }
412
413 #[cfg(feature = "verify")]
414 pub fn verify<B: Deref<Target = [u8]>>(&self, trusted_root_certs: &[B], platform: Platform, min_version: u16) -> Result<TcbData<Verified>, Error> {
415 let now = Utc::now();
416 self.verify_ex(trusted_root_certs, platform, min_version, &now)
417 }
418
419 #[cfg(feature = "verify")]
420 fn verify_ex<B: Deref<Target = [u8]>>(&self, trusted_root_certs: &[B], platform: Platform, min_version: u16, now: &DateTime<Utc>) -> Result<TcbData<Verified>, Error> {
421 let (chain, root) = crate::create_cert_chain(&self.ca_chain)?;
423 let mut leaf = chain.first().unwrap_or(&root).clone();
424 let root_list = std::iter::once(root).collect();
425 if 0 < chain.len() {
426 let trust_ca: MbedtlsList<Certificate> = chain.into_iter().collect();
427 let mut err = String::default();
428 Certificate::verify(&trust_ca, &root_list, None, Some(&mut err))
429 .map_err(|_| Error::InvalidTcbInfo(format!("Invalid TcbInfo: {}", err)))?;
430 }
431
432 let mut hash = [0u8; 32];
434 mbedtls::hash::Md::hash(mbedtls::hash::Type::Sha256, self.raw_tcb_info.as_bytes(), &mut hash).unwrap();
435 leaf.public_key_mut()
436 .verify(mbedtls::hash::Type::Sha256, &hash, self.signature())
437 .map_err(|_| Error::InvalidTcbInfo("Signature verification failed".into()))?;
438
439 let leaf = self.ca_chain.first().ok_or(Error::IncorrectCA)?;
441 let tcb =
442 &pkix::pem::pem_to_der(&leaf, Some(PEM_CERTIFICATE)).ok_or(Error::InvalidQe3Id(ErrMbed::HighLevel(codes::X509BadInputData)))?;
443 let tcb = GenericCertificate::from_ber(&tcb).map_err(|_| Error::InvalidQe3Id(ErrMbed::HighLevel(codes::X509BadInputData)))?;
444 let name = tcb
445 .tbscert
446 .subject
447 .get(&*oid::commonName)
448 .ok_or(Error::InvalidQe3Id(ErrMbed::HighLevel(codes::X509BadInputData)))?;
449 if String::from_utf8_lossy(&name.value()) != "Intel SGX TCB Signing" {
450 return Err(Error::IncorrectCA);
451 }
452
453 crate::check_root_ca(trusted_root_certs, &root_list)?;
454
455 let TcbData {
456 id,
457 version,
458 issue_date,
459 next_update,
460 fmspc,
461 pce_id,
462 tcb_type,
463 tcb_evaluation_data_number,
464 tcb_levels,
465 ..
466 } = TcbData::parse(&self.raw_tcb_info)?;
467
468 if id != platform {
469 return Err(Error::InvalidTcbInfo(format!("TCB Info belongs to the {id} platform, expected one for the {platform} platform")))
470 }
471
472 if min_version > version {
473 return Err(Error::UntrustedTcbInfoVersion(version, min_version))
474 }
475
476 if *now < issue_date {
477 return Err(Error::InvalidTcbInfo(format!("TCB Info only valid from {}", issue_date)))
478 }
479 if next_update < *now {
480 return Err(Error::InvalidTcbInfo(format!("TCB Info expired on {}", next_update)))
481 }
482
483 Ok(TcbData::<Verified> {
484 id,
485 version,
486 issue_date,
487 next_update,
488 fmspc,
489 pce_id,
490 tcb_type,
491 tcb_evaluation_data_number,
492 tcb_levels,
493 type_: PhantomData,
494 })
495 }
496
497 pub fn data(&self) -> Result<TcbData<Unverified>, Error> {
498 TcbData::parse(&self.raw_tcb_info)
499 }
500}
501
502#[cfg(feature = "verify")]
503#[cfg(test)]
504mod tests {
505 #[cfg(not(target_env = "sgx"))]
506 use {
507 chrono::{Utc, TimeZone},
508 crate::Error,
509 crate::tcb_info::{Fmspc, Platform, TcbInfo},
510 tempdir::TempDir,
511 };
512 use std::convert::TryFrom;
513 use super::AdvisoryID;
514
515 #[test]
516 #[cfg(not(target_env = "sgx"))]
517 fn read_tcb_info() {
518 let info =
519 TcbInfo::restore("./tests/data/", &Fmspc::try_from("00906ea10000").expect("static fmspc"), None).expect("validated");
520 let root_certificate = include_bytes!("../tests/data/root_SGX_CA_der.cert");
521 let root_certificates = [&root_certificate[..]];
522 match info.verify(&root_certificates, Platform::SGX, 2) {
523 Err(Error::InvalidTcbInfo(msg)) => assert_eq!(msg, String::from("TCB Info expired on 2020-06-17 17:49:24 UTC")),
524 e => assert!(false, "wrong result: {:?}", e),
525 }
526 let juni_5_2020 = Utc.with_ymd_and_hms(2020, 6, 5, 12, 0, 0).unwrap();
527 assert!(info.verify_ex(&root_certificates, Platform::SGX, 2, &juni_5_2020).is_ok());
528
529 match info.verify(&root_certificates, Platform::TDX, 2) {
530 Err(Error::InvalidTcbInfo(msg)) => assert_eq!(msg, String::from("TCB Info belongs to the SGX platform, expected one for the TDX platform")),
531 e => assert!(false, "wrong result: {:?}", e),
532 }
533
534 let temp_dir = TempDir::new("tempdir").unwrap();
536 let path = temp_dir.path().as_os_str().to_str().unwrap();
537 info.store(&path).unwrap();
538 let info2 = TcbInfo::restore(&path, &Fmspc::try_from("00906ea10000").expect("static fmspc"), Some(8)).unwrap();
539 assert_eq!(info, info2);
540 }
541
542 #[test]
543 #[cfg(not(target_env = "sgx"))]
544 fn read_corrupt_tcb_info() {
545 let tcb_info = TcbInfo::restore("./tests/data/corrupted", &Fmspc::try_from("00906ea10000").unwrap(), None).unwrap();
546 let root_certificate = include_bytes!("../tests/data/root_SGX_CA_der.cert");
547 let root_certificates = [&root_certificate[..]];
548 assert!(tcb_info.verify(&root_certificates, Platform::SGX, 2).is_err());
549 }
550
551 #[test]
552 #[cfg(not(target_env = "sgx"))]
553 fn read_tcb_info_v3() {
554 let tcb_info = TcbInfo::restore("./tests/data/", &Fmspc::try_from("00906ed50000").unwrap(), Some(19)).unwrap();
555 let root_certificate = include_bytes!("../tests/data/root_SGX_CA_der.cert");
556 let root_certificates = [&root_certificate[..]];
557 let june_5_2025 = Utc.with_ymd_and_hms(2025, 6, 5, 12, 0, 0).unwrap();
558 assert!(tcb_info.verify_ex(&root_certificates, Platform::SGX, 3, &june_5_2025).is_ok());
559 assert!(tcb_info.verify_ex(&root_certificates, Platform::SGX, 4, &june_5_2025).is_err());
560 }
561
562 #[test]
563 fn parse_advisory_ids() {
564 assert_eq!(AdvisoryID::try_from("INTEL-SA-00123"), Ok(AdvisoryID::Security(123)));
565 assert_eq!(AdvisoryID::try_from("INTEL-SA-10123"), Ok(AdvisoryID::Security(10123)));
566 assert_eq!(AdvisoryID::try_from("INTEL-SA-00001"), Ok(AdvisoryID::Security(1)));
567 assert_eq!(AdvisoryID::try_from("INTEL-DOC-00123"), Ok(AdvisoryID::Documentation(123)));
568 assert_eq!(AdvisoryID::try_from("INTEL-DOC-10123"), Ok(AdvisoryID::Documentation(10123)));
569 assert_eq!(AdvisoryID::try_from("INTEL-DOC-00001"), Ok(AdvisoryID::Documentation(1)));
570 }
571
572 #[test]
573 fn advisory_ids_to_string() {
574 assert_eq!(AdvisoryID::Security(123).to_string(), "INTEL-SA-00123");
575 assert_eq!(AdvisoryID::Security(1).to_string(), "INTEL-SA-00001");
576 assert_eq!(AdvisoryID::Security(99999).to_string(), "INTEL-SA-99999");
577 assert_eq!(AdvisoryID::Documentation(123).to_string(), "INTEL-DOC-00123");
578 assert_eq!(AdvisoryID::Documentation(1).to_string(), "INTEL-DOC-00001");
579 assert_eq!(AdvisoryID::Documentation(99999).to_string(), "INTEL-DOC-99999");
580 }
581}