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,
12 utils::x509_custom_ts_verify_cb, Error,
13};
14
15#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
17pub struct TCBBundle {
18 #[cbor(rename = "tcb_info")]
19 pub tcb_info: SignedTCBInfo,
20
21 #[cbor(rename = "qe_id")]
22 pub qe_identity: SignedQEIdentity,
23
24 #[cbor(rename = "certs")]
25 pub certificates: Vec<u8>,
26}
27
28impl TCBBundle {
29 pub(super) fn verify_certificates(
30 &self,
31 ts: DateTime<Utc>,
32 ) -> Result<MbedtlsBox<Certificate>, Error> {
33 let raw_certs =
34 CString::new(&*self.certificates).map_err(|err| Error::TCBParseError(err.into()))?;
35 let mut cert_chain = Certificate::from_pem_multiple(raw_certs.as_bytes_with_nul())
36 .map_err(|err| Error::TCBParseError(err.into()))?;
37 if cert_chain.iter().count() != 2 {
38 return Err(Error::UnexpectedCertificateChain);
39 }
40
41 Certificate::verify_with_callback(
42 &cert_chain,
43 &PCS_TRUST_ROOT,
44 None,
45 None,
46 x509_custom_ts_verify_cb(ts),
47 )
48 .map_err(|_| Error::TCBVerificationFailed)?;
49
50 Ok(cert_chain.pop_front().unwrap())
51 }
52}
53
54#[inline]
55fn encode_raw_value(value: &RawValue) -> Vec<u8> {
56 value.get().as_bytes().to_owned()
57}
58
59#[inline]
60fn decode_raw_value(value: Vec<u8>) -> Result<Box<RawValue>, cbor::DecodeError> {
61 RawValue::from_string(String::from_utf8(value).map_err(|_| cbor::DecodeError::UnexpectedType)?)
62 .map_err(|_| cbor::DecodeError::UnexpectedType)
63}
64
65#[derive(Clone, Debug, Default, serde::Deserialize, cbor::Encode, cbor::Decode)]
67pub struct SignedTCBInfo {
68 #[cbor(
69 rename = "tcb_info",
70 serialize_with = "encode_raw_value",
71 deserialize_with = "decode_raw_value"
72 )]
73 #[serde(rename = "tcbInfo")]
74 pub tcb_info: Box<RawValue>,
75
76 #[cbor(rename = "signature")]
77 #[serde(rename = "signature")]
78 pub signature: String,
79}
80
81impl PartialEq for SignedTCBInfo {
82 fn eq(&self, other: &SignedTCBInfo) -> bool {
83 self.tcb_info.get() == other.tcb_info.get() && self.signature == other.signature
84 }
85}
86
87impl Eq for SignedTCBInfo {}
88
89fn open_signed_tcb<'a, T: serde::Deserialize<'a>>(
90 data: &'a str,
91 signature: &str,
92 pk: &mut mbedtls::pk::Pk,
93) -> Result<T, Error> {
94 let mut hash = [0u8; 32];
95 mbedtls::hash::Md::hash(mbedtls::hash::Type::Sha256, data.as_bytes(), &mut hash)
96 .map_err(|_| Error::TCBVerificationFailed)?;
97 let sig: Vec<u8> = signature
98 .from_hex()
99 .map_err(|_| Error::TCBVerificationFailed)?;
100
101 if !sig.len().is_multiple_of(2) {
103 return Err(Error::TCBVerificationFailed);
104 }
105
106 let (r_bytes, s_bytes) = sig.split_at(sig.len() / 2);
107 let r = num_bigint::BigUint::from_bytes_be(r_bytes);
108 let s = num_bigint::BigUint::from_bytes_be(s_bytes);
109
110 let sig = yasna::construct_der(|writer| {
111 writer.write_sequence(|writer| {
112 writer.next().write_biguint(&r);
113 writer.next().write_biguint(&s);
114 })
115 });
116
117 pk.verify(mbedtls::hash::Type::Sha256, &hash, &sig)
118 .map_err(|_| Error::TCBVerificationFailed)?;
119
120 serde_json::from_str(data).map_err(|err| Error::TCBParseError(err.into()))
121}
122
123impl SignedTCBInfo {
124 pub fn open(
125 &self,
126 tee_type: TeeType,
127 ts: DateTime<Utc>,
128 policy: &QuotePolicy,
129 pk: &mut mbedtls::pk::Pk,
130 ) -> Result<TCBInfo, Error> {
131 let ti: TCBInfo = open_signed_tcb(self.tcb_info.get(), &self.signature, pk)?;
132 ti.validate(tee_type, ts, policy)?;
133
134 Ok(ti)
135 }
136}
137
138#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
140pub enum TCBInfoID {
141 #[serde(rename = "SGX")]
142 Sgx,
143 #[serde(rename = "TDX")]
144 Tdx,
145 #[serde(other)]
146 #[default]
147 Invalid,
148}
149
150#[derive(Clone, Debug, Default, serde::Deserialize)]
152pub struct TCBInfo {
153 #[serde(rename = "id")]
154 pub id: TCBInfoID,
155
156 #[serde(rename = "version")]
157 pub version: u32,
158
159 #[serde(rename = "issueDate")]
160 pub issue_date: String,
161
162 #[serde(rename = "nextUpdate")]
163 pub next_update: String,
164
165 #[serde(rename = "fmspc")]
166 pub fmspc: String,
167
168 #[serde(rename = "pceId")]
169 pub pceid: String,
170
171 #[serde(rename = "tcbType")]
172 pub tcb_type: u32,
173
174 #[serde(rename = "tcbEvaluationDataNumber")]
175 pub tcb_evaluation_data_number: u32,
176
177 #[serde(default, rename = "tdxModule")]
178 pub tdx_module: TDXModule,
179
180 #[serde(default, rename = "tdxModuleIdentities")]
181 pub tdx_module_identities: Vec<TDXModuleIdentity>,
182
183 #[serde(rename = "tcbLevels")]
184 pub tcb_levels: Vec<TCBLevel>,
185}
186
187impl TCBInfo {
188 pub fn validate(
190 &self,
191 tee_type: TeeType,
192 ts: DateTime<Utc>,
193 policy: &QuotePolicy,
194 ) -> Result<(), Error> {
195 match (self.id, tee_type) {
196 (TCBInfoID::Sgx, TeeType::Sgx) => {}
197 (TCBInfoID::Tdx, TeeType::Tdx) => {}
198 _ => {
199 return Err(Error::TCBParseError(anyhow::anyhow!(
200 "unexpected TCB info identifier"
201 )))
202 }
203 }
204
205 if self.version != REQUIRED_TCB_INFO_VERSION {
206 return Err(Error::TCBParseError(anyhow::anyhow!(
207 "unexpected TCB info version"
208 )));
209 }
210
211 let issue_date = NaiveDateTime::parse_from_str(&self.issue_date, PCS_TS_FMT)
213 .map_err(|err| Error::TCBParseError(err.into()))?
214 .and_utc();
215 let _next_update = NaiveDateTime::parse_from_str(&self.next_update, PCS_TS_FMT)
216 .map_err(|err| Error::TCBParseError(err.into()))?
217 .and_utc();
218 if issue_date > ts {
219 return Err(Error::TCBExpired);
220 }
221 if ts - issue_date
222 > Duration::try_days(policy.tcb_validity_period.into())
223 .unwrap_or(DEFAULT_TCB_VALIDITY_PERIOD)
224 {
225 return Err(Error::TCBExpired);
226 }
227
228 if self.tcb_evaluation_data_number < policy.min_tcb_evaluation_data_number {
229 return Err(Error::TCBEvaluationDataNumberInvalid);
230 }
231
232 if !policy.fmspc_whitelist.is_empty() && !policy.fmspc_whitelist.contains(&self.fmspc) {
234 return Err(Error::NotWhitelistedFMSPC);
235 }
236
237 if policy.fmspc_blacklist.contains(&self.fmspc) {
239 return Err(Error::BlacklistedFMSPC);
240 }
241
242 Ok(())
243 }
244
245 pub fn verify(
247 &self,
248 fmspc: &[u8],
249 sgx_comp_svn: &[u32; 16],
250 tdx_comp_svn: Option<&[u32; 16]>,
251 pcesvn: u32,
252 ) -> Result<TCBLevel, Error> {
253 let expected_fmspc: Vec<u8> = self
255 .fmspc
256 .from_hex()
257 .map_err(|err| Error::TCBParseError(err.into()))?;
258 if fmspc != expected_fmspc {
259 return Err(Error::TCBMismatch);
260 }
261
262 let level = self
264 .tcb_levels
265 .iter()
266 .find(|level| level.matches(sgx_comp_svn, tdx_comp_svn, pcesvn))
267 .ok_or(Error::TCBOutOfDate)?
268 .clone();
269
270 if self.id == TCBInfoID::Tdx {
271 let tdx_comp_svn = tdx_comp_svn.ok_or(Error::TCBMismatch)?;
274 let tdx_module_version = tdx_comp_svn[1];
275 if tdx_module_version >= 1 {
276 let tdx_module_id = format!("TDX_{tdx_module_version:02}");
281 let tdx_module = self
282 .tdx_module_identities
283 .iter()
284 .find(|tm| tm.id == tdx_module_id)
285 .ok_or(Error::TCBOutOfDate)?;
286
287 let tdx_module_level = tdx_module
293 .tcb_levels
294 .iter()
295 .find(|level| level.tcb.isv_svn as u32 <= tdx_comp_svn[0])
296 .ok_or(Error::TCBOutOfDate)?;
297 if tdx_module_level.status != TCBStatus::UpToDate {
298 return Err(Error::TCBOutOfDate);
299 }
300 }
301 }
302
303 Ok(level)
304 }
305}
306
307#[derive(Clone, Debug, Default, serde::Deserialize)]
309pub struct TDXModule {
310 #[serde(rename = "mrsigner")]
311 pub mr_signer: String,
312
313 #[serde(rename = "attributes")]
314 pub attributes: String,
315
316 #[serde(rename = "attributesMask")]
317 pub attributes_mask: String,
318}
319
320#[derive(Clone, Debug, Default, serde::Deserialize)]
323pub struct TDXModuleIdentity {
324 #[serde(rename = "id")]
325 pub id: String,
326
327 #[serde(flatten)]
328 pub module: TDXModule,
329
330 #[serde(rename = "tcbLevels")]
331 pub tcb_levels: Vec<EnclaveTCBLevel>,
332}
333
334#[derive(Clone, Debug, Default, serde::Deserialize)]
336pub struct TCBLevel {
337 #[serde(rename = "tcb")]
338 pub tcb: TCBVersions,
339
340 #[serde(rename = "tcbDate")]
341 pub date: String,
342
343 #[serde(rename = "tcbStatus")]
344 pub status: TCBStatus,
345
346 #[serde(default, rename = "advisoryIDs")]
347 pub advisory_ids: Vec<String>,
348}
349
350impl TCBLevel {
351 pub fn matches(
353 &self,
354 sgx_comp_svn: &[u32],
355 tdx_comp_svn: Option<&[u32; 16]>,
356 pcesvn: u32,
357 ) -> bool {
358 for (i, comp) in self.tcb.sgx_components.iter().enumerate() {
363 if sgx_comp_svn[i] < comp.svn {
365 return false;
366 }
367 }
368
369 if pcesvn < self.tcb.pcesvn {
374 return false;
375 }
376
377 if let Some(tdx_comp_svn) = tdx_comp_svn {
378 let comps = self.tcb.tdx_components.iter().enumerate();
385 let offset = if tdx_comp_svn[1] != 0 { 2 } else { 0 };
386
387 for (i, comp) in comps.skip(offset) {
388 if tdx_comp_svn[i] < comp.svn {
390 return false;
391 }
392 }
393 }
394
395 true
397 }
398}
399
400#[derive(Clone, Debug, Default, serde::Deserialize)]
402pub struct TCBVersions {
403 #[serde(rename = "pcesvn")]
404 pub pcesvn: u32,
405
406 #[serde(rename = "sgxtcbcomponents")]
407 pub sgx_components: [TCBComponent; 16],
408
409 #[serde(default, rename = "tdxtcbcomponents")]
410 pub tdx_components: [TCBComponent; 16],
411}
412
413#[derive(Clone, Debug, Default, serde::Deserialize)]
415pub struct TCBComponent {
416 #[serde(rename = "svn")]
417 pub svn: u32,
418
419 #[serde(default, rename = "category")]
420 pub category: String,
421
422 #[serde(default, rename = "type")]
423 pub tcb_comp_type: String,
424}
425
426#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
428pub enum TCBStatus {
429 UpToDate,
430 SWHardeningNeeded,
431 ConfigurationNeeded,
432 ConfigurationAndSWHardeningNeeded,
433 OutOfDate,
434 OutOfDateConfigurationNeeded,
435 Revoked,
436 #[serde(other)]
437 #[default]
438 Invalid,
439}
440
441#[derive(Clone, Debug, Default, serde::Deserialize, cbor::Encode, cbor::Decode)]
443pub struct SignedQEIdentity {
444 #[cbor(
445 rename = "enclave_identity",
446 serialize_with = "encode_raw_value",
447 deserialize_with = "decode_raw_value"
448 )]
449 #[serde(rename = "enclaveIdentity")]
450 pub enclave_identity: Box<RawValue>,
451
452 #[cbor(rename = "signature")]
453 #[serde(rename = "signature")]
454 pub signature: String,
455}
456
457impl PartialEq for SignedQEIdentity {
458 fn eq(&self, other: &SignedQEIdentity) -> bool {
459 self.enclave_identity.get() == other.enclave_identity.get()
460 && self.signature == other.signature
461 }
462}
463
464impl Eq for SignedQEIdentity {}
465
466impl SignedQEIdentity {
467 pub fn open(
468 &self,
469 tee_type: TeeType,
470 ts: DateTime<Utc>,
471 policy: &QuotePolicy,
472 pk: &mut mbedtls::pk::Pk,
473 ) -> Result<QEIdentity, Error> {
474 let qe: QEIdentity = open_signed_tcb(self.enclave_identity.get(), &self.signature, pk)?;
475 qe.validate(tee_type, ts, policy)?;
476
477 Ok(qe)
478 }
479}
480
481#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
483#[allow(non_camel_case_types)]
484pub enum QEIdentityID {
485 QE,
486 TD_QE,
487 #[serde(other)]
488 #[default]
489 Invalid,
490}
491
492#[derive(Clone, Debug, Default, serde::Deserialize)]
494pub struct QEIdentity {
495 #[serde(rename = "id")]
496 pub id: QEIdentityID,
497
498 #[serde(rename = "version")]
499 pub version: u32,
500
501 #[serde(rename = "issueDate")]
502 pub issue_date: String,
503
504 #[serde(rename = "nextUpdate")]
505 pub next_update: String,
506
507 #[serde(rename = "tcbEvaluationDataNumber")]
508 pub tcb_evaluation_data_number: u32,
509
510 #[serde(rename = "miscselect")]
511 pub miscselect: String,
512
513 #[serde(rename = "miscselectMask")]
514 pub miscselect_mask: String,
515
516 #[serde(rename = "attributes")]
517 pub attributes: String,
518
519 #[serde(rename = "attributesMask")]
520 pub attributes_mask: String,
521
522 #[serde(rename = "mrsigner")]
523 pub mr_signer: String,
524
525 #[serde(rename = "isvprodid")]
526 pub isv_prod_id: u16,
527
528 #[serde(rename = "tcbLevels")]
529 pub tcb_levels: Vec<EnclaveTCBLevel>,
530
531 #[serde(default, rename = "advisoryIDs")]
532 pub advisory_ids: Vec<String>,
533}
534
535impl QEIdentity {
536 pub fn validate(
538 &self,
539 tee_type: TeeType,
540 ts: DateTime<Utc>,
541 policy: &QuotePolicy,
542 ) -> Result<(), Error> {
543 match (self.id, tee_type) {
544 (QEIdentityID::QE, TeeType::Sgx) => {}
545 (QEIdentityID::TD_QE, TeeType::Tdx) => {}
546 _ => return Err(Error::TCBParseError(anyhow::anyhow!("unexpected QE ID"))),
547 }
548
549 if self.version != REQUIRED_QE_IDENTITY_VERSION {
550 return Err(Error::TCBParseError(anyhow::anyhow!(
551 "unexpected QE identity version"
552 )));
553 }
554
555 let issue_date = NaiveDateTime::parse_from_str(&self.issue_date, PCS_TS_FMT)
557 .map_err(|err| Error::TCBParseError(err.into()))?
558 .and_utc();
559 let _next_update = NaiveDateTime::parse_from_str(&self.next_update, PCS_TS_FMT)
560 .map_err(|err| Error::TCBParseError(err.into()))?
561 .and_utc();
562 if issue_date > ts {
563 return Err(Error::TCBExpired);
564 }
565 if ts - issue_date
566 > Duration::try_days(policy.tcb_validity_period.into())
567 .unwrap_or(DEFAULT_TCB_VALIDITY_PERIOD)
568 {
569 return Err(Error::TCBExpired);
570 }
571
572 if self.tcb_evaluation_data_number < policy.min_tcb_evaluation_data_number {
573 return Err(Error::TCBEvaluationDataNumberInvalid);
574 }
575
576 Ok(())
577 }
578
579 pub fn verify(&self, report: &Report) -> Result<(), Error> {
581 let expected_mr_signer: Vec<u8> = self
584 .mr_signer
585 .from_hex()
586 .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE MRSIGNER")))?;
587 if expected_mr_signer != report.mrsigner {
588 return Err(Error::TCBVerificationFailed);
589 }
590
591 if self.isv_prod_id != report.isvprodid {
594 return Err(Error::TCBVerificationFailed);
595 }
596
597 let raw_miscselect: Vec<u8> = self
601 .miscselect
602 .from_hex()
603 .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE miscselect")))?;
604 if raw_miscselect.len() != 4 {
605 return Err(Error::TCBParseError(anyhow::anyhow!(
606 "malformed QE miscselect"
607 )));
608 }
609 let raw_miscselect_mask: Vec<u8> = self
610 .miscselect_mask
611 .from_hex()
612 .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE miscselect mask")))?;
613 if raw_miscselect_mask.len() != 4 {
614 return Err(Error::TCBParseError(anyhow::anyhow!(
615 "malformed QE miscselect"
616 )));
617 }
618 let expected_miscselect = LittleEndian::read_u32(&raw_miscselect);
619 let miscselect_mask = LittleEndian::read_u32(&raw_miscselect_mask);
620 if report.miscselect.bits() & miscselect_mask != expected_miscselect {
621 return Err(Error::TCBVerificationFailed);
622 }
623
624 let raw_attributes: Vec<u8> = self
628 .attributes
629 .from_hex()
630 .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE attributes")))?;
631 if raw_attributes.len() != 16 {
632 return Err(Error::TCBParseError(anyhow::anyhow!(
633 "malformed QE attributes"
634 )));
635 }
636 let raw_attributes_mask: Vec<u8> = self
637 .attributes_mask
638 .from_hex()
639 .map_err(|_| Error::TCBParseError(anyhow::anyhow!("malformed QE attributes mask")))?;
640 if raw_attributes_mask.len() != 16 {
641 return Err(Error::TCBParseError(anyhow::anyhow!(
642 "malformed QE attributes"
643 )));
644 }
645 let expected_flags = LittleEndian::read_u64(&raw_attributes[..8]);
646 let expected_xfrm = LittleEndian::read_u64(&raw_attributes[8..]);
647 let flags_mask = LittleEndian::read_u64(&raw_attributes_mask[..8]);
648 let xfrm_mask = LittleEndian::read_u64(&raw_attributes_mask[8..]);
649 if report.attributes.flags.bits() & flags_mask != expected_flags {
650 return Err(Error::TCBVerificationFailed);
651 }
652 if report.attributes.xfrm & xfrm_mask != expected_xfrm {
653 return Err(Error::TCBVerificationFailed);
654 }
655
656 if let Some(level) = self
661 .tcb_levels
662 .iter()
663 .find(|level| level.tcb.isv_svn <= report.isvsvn)
664 {
665 if level.status == TCBStatus::UpToDate {
667 return Ok(());
668 }
669 }
670
671 Err(Error::TCBOutOfDate)
672 }
673}
674
675#[derive(Clone, Debug, Default, serde::Deserialize)]
677pub struct EnclaveTCBLevel {
678 #[serde(rename = "tcb")]
679 pub tcb: EnclaveTCBVersions,
680
681 #[serde(rename = "tcbDate")]
682 pub date: String,
683
684 #[serde(rename = "tcbStatus")]
685 pub status: TCBStatus,
686
687 #[serde(default, rename = "advisoryIDs")]
688 pub advisory_ids: Vec<String>,
689}
690
691#[derive(Clone, Debug, Default, serde::Deserialize)]
693pub struct EnclaveTCBVersions {
694 #[serde(rename = "isvsvn")]
695 pub isv_svn: u16,
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 #[test]
703 fn test_tcb_level_matches() {
704 let pcesvn: u32 = 10;
705 let sgx_svn: [u32; 16] = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30];
706 let tdx_svn: [u32; 16] = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31];
707
708 let mut tl = TCBLevel::default();
709 tl.tcb.pcesvn = pcesvn;
710
711 for i in 0..16 {
712 tl.tcb.sgx_components[i].svn = sgx_svn[i];
713 tl.tcb.tdx_components[i].svn = tdx_svn[i];
714 }
715
716 struct TestCase {
717 name: &'static str,
718 pcesvn: u32,
719 sgx_svn: [u32; 16],
720 tdx_svn: Option<[u32; 16]>,
721 matches: bool,
722 }
723
724 let tcs = vec![
725 TestCase {
726 name: "same values",
727 pcesvn,
728 sgx_svn,
729 tdx_svn: Some(tdx_svn),
730 matches: true,
731 },
732 TestCase {
733 name: "higher pcesvn",
734 pcesvn: pcesvn + 1,
735 sgx_svn,
736 tdx_svn: Some(tdx_svn),
737 matches: true,
738 },
739 TestCase {
740 name: "lower pcesvn",
741 pcesvn: pcesvn - 1,
742 sgx_svn,
743 tdx_svn: Some(tdx_svn),
744 matches: false,
745 },
746 TestCase {
747 name: "higher sgx svn",
748 pcesvn,
749 sgx_svn: {
750 let mut ss = sgx_svn;
751 ss[5] += 1;
752 ss
753 },
754 tdx_svn: Some(tdx_svn),
755 matches: true,
756 },
757 TestCase {
758 name: "lower sgx svn",
759 pcesvn,
760 sgx_svn: {
761 let mut ss = sgx_svn;
762 ss[5] -= 1;
763 ss
764 },
765 tdx_svn: Some(tdx_svn),
766 matches: false,
767 },
768 TestCase {
769 name: "higher tdx svn",
770 pcesvn,
771 sgx_svn,
772 tdx_svn: {
773 let mut ts = tdx_svn;
774 ts[5] += 1;
775 Some(ts)
776 },
777 matches: true,
778 },
779 TestCase {
780 name: "lower tdx svn",
781 pcesvn,
782 sgx_svn,
783 tdx_svn: {
784 let mut ts = tdx_svn;
785 ts[5] -= 1;
786 Some(ts)
787 },
788 matches: false,
789 },
790 TestCase {
791 name: "no tdx svn",
792 pcesvn,
793 sgx_svn,
794 tdx_svn: None,
795 matches: true,
796 },
797 ];
798
799 for tc in tcs {
800 let result = tl.matches(&tc.sgx_svn, tc.tdx_svn.as_ref(), tc.pcesvn);
801 if tc.matches {
802 assert!(result, "tcb level should match when {}", tc.name);
803 } else {
804 assert!(!result, "tcb level should not match when {}", tc.name);
805 }
806 }
807 }
808}