1use alloc::string::String;
2use alloc::vec::Vec;
3use core::cmp::Ordering;
4use derive_more::Display;
5use serde::{Deserialize, Serialize};
6
7#[cfg(feature = "borsh_schema")]
8use borsh::BorshSchema;
9#[cfg(feature = "borsh")]
10use borsh::{BorshDeserialize, BorshSerialize};
11
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
15#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
16pub struct TcbInfo {
17 pub id: String,
18 pub version: u8,
19 pub issue_date: String,
20 pub next_update: String,
21 pub fmspc: String,
22 pub pce_id: String,
23 pub tcb_type: u32,
24 pub tcb_evaluation_data_number: u32,
25 pub tcb_levels: Vec<TcbLevel>,
26 #[serde(default)]
27 pub tdx_module: Option<TdxModule>,
28 #[serde(rename = "tdxModuleIdentities", default)]
29 pub tdx_module_identities: Vec<TdxModuleIdentity>,
30}
31
32impl TcbInfo {
33 pub(crate) fn canonicalize_tcb_levels(&mut self) {
47 let is_tdx = self.version >= 3 && self.id == "TDX";
48 self.tcb_levels
49 .sort_by(|a, b| compare_tcb_levels(a, b, is_tdx));
50 }
51}
52
53#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
56#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
57pub struct TcbLevel {
58 pub tcb: Tcb,
59 pub tcb_date: String,
60 pub tcb_status: TcbStatus,
61 #[serde(rename = "advisoryIDs", default)]
62 pub advisory_ids: Vec<String>,
63}
64
65fn compare_tcb_levels(a: &TcbLevel, b: &TcbLevel, is_tdx: bool) -> Ordering {
66 b.tcb
68 .sgx_components
69 .cmp(&a.tcb.sgx_components)
70 .then_with(|| b.tcb.pce_svn.cmp(&a.tcb.pce_svn))
72 .then_with(|| {
74 if is_tdx {
75 b.tcb.tdx_components.cmp(&a.tcb.tdx_components)
76 } else {
77 Ordering::Equal
78 }
79 })
80}
81
82#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
85#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
86pub struct Tcb {
87 #[serde(rename = "sgxtcbcomponents")]
88 pub sgx_components: Vec<TcbComponents>,
89 #[serde(rename = "tdxtcbcomponents", default)]
90 pub tdx_components: Vec<TcbComponents>,
91 #[serde(rename = "pcesvn")]
92 pub pce_svn: u16,
93}
94
95#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
98#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
99pub struct TcbComponents {
100 pub svn: u8,
101}
102
103#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
106#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
107pub struct TdxModule {
108 pub mrsigner: String,
110 pub attributes: String,
112 pub attributes_mask: String,
114}
115
116#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
119#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
120pub struct TdxModuleIdentity {
121 pub id: String,
122 pub mrsigner: String,
123 pub attributes: String,
124 pub attributes_mask: String,
125 pub tcb_levels: Vec<TdxModuleTcbLevel>,
126}
127
128#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
131#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
132pub struct TdxModuleTcbLevel {
133 pub tcb: TdxModuleTcb,
134 pub tcb_date: String,
135 pub tcb_status: TcbStatus,
136 #[serde(rename = "advisoryIDs", default)]
137 pub advisory_ids: Vec<String>,
138}
139
140#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
143#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
144pub struct TdxModuleTcb {
145 pub isvsvn: u8,
146}
147
148#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Display)]
149#[display("{_variant}")]
150#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
151#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
152pub enum TcbStatus {
153 UpToDate,
154 OutOfDateConfigurationNeeded,
155 OutOfDate,
156 ConfigurationAndSWHardeningNeeded,
157 ConfigurationNeeded,
158 SWHardeningNeeded,
159 Revoked,
160}
161
162impl TcbStatus {
163 fn severity(&self) -> u8 {
164 match self {
165 Self::UpToDate => 0,
166 Self::SWHardeningNeeded => 1,
167 Self::ConfigurationNeeded => 2,
168 Self::ConfigurationAndSWHardeningNeeded => 3,
169 Self::OutOfDate => 4,
170 Self::OutOfDateConfigurationNeeded => 5,
171 Self::Revoked => 6,
172 }
173 }
174
175 pub(crate) fn converge_with_component(self, component: TcbStatus) -> TcbStatus {
178 use TcbStatus::*;
179 match (component, self) {
180 (OutOfDate, ConfigurationNeeded | ConfigurationAndSWHardeningNeeded) => {
181 OutOfDateConfigurationNeeded
182 }
183 _ => component.max(self),
184 }
185 }
186}
187
188impl Ord for TcbStatus {
189 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
190 self.severity().cmp(&other.severity())
191 }
192}
193
194impl PartialOrd for TcbStatus {
195 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
196 Some(self.cmp(other))
197 }
198}
199
200#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
205#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
206#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
207pub struct TcbStatusWithAdvisory {
208 pub status: TcbStatus,
209 pub advisory_ids: Vec<String>,
210}
211
212impl TcbStatusWithAdvisory {
213 pub fn new(status: TcbStatus, advisory_ids: Vec<String>) -> Self {
215 Self {
216 status,
217 advisory_ids,
218 }
219 }
220
221 pub fn merge(self, other: &TcbStatusWithAdvisory) -> Self {
223 let final_status = self.status.converge_with_component(other.status);
224
225 let mut advisory_ids = self.advisory_ids;
226 for id in &other.advisory_ids {
227 if !advisory_ids.contains(id) {
228 advisory_ids.push(id.clone());
229 }
230 }
231
232 Self {
233 status: final_status,
234 advisory_ids,
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use TcbStatus::*;
243
244 #[test]
245 fn test_tcb_status_merge_both_up_to_date() {
246 let a = TcbStatusWithAdvisory::new(UpToDate, vec![]);
247 let b = TcbStatusWithAdvisory::new(UpToDate, vec![]);
248 let result = a.merge(&b);
249 assert_eq!(result.status, UpToDate);
250 assert!(result.advisory_ids.is_empty());
251 }
252
253 #[test]
254 fn test_tcb_status_merge_takes_worse() {
255 let a = TcbStatusWithAdvisory::new(UpToDate, vec![]);
256 let b = TcbStatusWithAdvisory::new(OutOfDate, vec!["INTEL-SA-00001".into()]);
257 let result = a.merge(&b);
258 assert_eq!(result.status, OutOfDate);
259 assert_eq!(result.advisory_ids, vec!["INTEL-SA-00001"]);
260 }
261
262 #[test]
263 fn qe_out_of_date_converges_configuration_status() {
264 let platform = TcbStatusWithAdvisory::new(TcbStatus::ConfigurationNeeded, vec![]);
265 let qe = TcbStatusWithAdvisory::new(TcbStatus::OutOfDate, vec![]);
266 assert_eq!(
267 platform.merge(&qe).status,
268 TcbStatus::OutOfDateConfigurationNeeded
269 );
270 }
271
272 #[test]
273 fn test_tcb_status_merge_combines_advisories() {
274 let a = TcbStatusWithAdvisory::new(OutOfDate, vec!["INTEL-SA-00001".into()]);
275 let b = TcbStatusWithAdvisory::new(SWHardeningNeeded, vec!["INTEL-SA-00002".into()]);
276 let result = a.merge(&b);
277 assert_eq!(result.status, OutOfDate);
278 assert_eq!(
279 result.advisory_ids,
280 vec!["INTEL-SA-00001", "INTEL-SA-00002"]
281 );
282 }
283
284 #[test]
285 fn test_tcb_status_merge_deduplicates_advisories() {
286 let a = TcbStatusWithAdvisory::new(OutOfDate, vec!["INTEL-SA-00001".into()]);
287 let b = TcbStatusWithAdvisory::new(OutOfDate, vec!["INTEL-SA-00001".into()]);
288 let result = a.merge(&b);
289 assert_eq!(result.advisory_ids, vec!["INTEL-SA-00001"]);
290 }
291
292 fn make_tcb_level(sgx: &[u8], pce_svn: u16, tdx: &[u8], status: TcbStatus) -> TcbLevel {
293 TcbLevel {
294 tcb: Tcb {
295 sgx_components: sgx.iter().map(|&svn| TcbComponents { svn }).collect(),
296 tdx_components: tdx.iter().map(|&svn| TcbComponents { svn }).collect(),
297 pce_svn,
298 },
299 tcb_date: String::new(),
300 tcb_status: status,
301 advisory_ids: vec![],
302 }
303 }
304
305 fn make_tcb_info(id: &str, tcb_levels: Vec<TcbLevel>) -> TcbInfo {
306 TcbInfo {
307 id: id.into(),
308 version: 3,
309 issue_date: String::new(),
310 next_update: String::new(),
311 fmspc: String::new(),
312 pce_id: String::new(),
313 tcb_type: 0,
314 tcb_evaluation_data_number: 0,
315 tcb_levels,
316 tdx_module: None,
317 tdx_module_identities: vec![],
318 }
319 }
320
321 #[allow(clippy::expect_used)]
322 fn first_component_svn(components: &[TcbComponents]) -> u8 {
323 components
324 .first()
325 .expect("expected at least one TCB component")
326 .svn
327 }
328
329 #[test]
330 fn test_canonicalize_sgx_sorts_by_cpu_svn_desc() {
331 let mut info = make_tcb_info(
332 "SGX",
333 vec![
334 make_tcb_level(&[2, 0], 10, &[], UpToDate),
335 make_tcb_level(&[5, 0], 10, &[], UpToDate),
336 make_tcb_level(&[3, 0], 10, &[], UpToDate),
337 ],
338 );
339 info.canonicalize_tcb_levels();
340 let svns: Vec<u8> = info
341 .tcb_levels
342 .iter()
343 .map(|l| first_component_svn(&l.tcb.sgx_components))
344 .collect();
345 assert_eq!(svns, vec![5, 3, 2]);
346 }
347
348 #[test]
349 fn test_canonicalize_sgx_pce_svn_tiebreaker() {
350 let mut info = make_tcb_info(
351 "SGX",
352 vec![
353 make_tcb_level(&[5], 7, &[], UpToDate),
354 make_tcb_level(&[5], 12, &[], UpToDate),
355 make_tcb_level(&[5], 9, &[], UpToDate),
356 ],
357 );
358 info.canonicalize_tcb_levels();
359 let pce_svns: Vec<u16> = info.tcb_levels.iter().map(|l| l.tcb.pce_svn).collect();
360 assert_eq!(pce_svns, vec![12, 9, 7]);
361 }
362
363 #[test]
364 fn test_canonicalize_tdx_components_tiebreaker() {
365 let mut info = make_tcb_info(
366 "TDX",
367 vec![
368 make_tcb_level(&[5], 10, &[1, 0], UpToDate),
369 make_tcb_level(&[5], 10, &[3, 0], UpToDate),
370 make_tcb_level(&[5], 10, &[2, 0], UpToDate),
371 ],
372 );
373 info.canonicalize_tcb_levels();
374 let tdx_svns: Vec<u8> = info
375 .tcb_levels
376 .iter()
377 .map(|l| first_component_svn(&l.tcb.tdx_components))
378 .collect();
379 assert_eq!(tdx_svns, vec![3, 2, 1]);
380 }
381
382 #[test]
383 fn test_canonicalize_sgx_ignores_tdx_components() {
384 let mut info = make_tcb_info(
385 "SGX",
386 vec![
387 make_tcb_level(&[5], 10, &[1], UpToDate),
388 make_tcb_level(&[5], 10, &[9], UpToDate),
389 ],
390 );
391 info.canonicalize_tcb_levels();
392 let tdx_svns: Vec<u8> = info
394 .tcb_levels
395 .iter()
396 .map(|l| first_component_svn(&l.tcb.tdx_components))
397 .collect();
398 assert_eq!(tdx_svns, vec![1, 9]);
399 }
400}