1use core::time::Duration;
2
3use anyhow::{bail, ensure, Context, Result};
4use rustls_pki_types::UnixTime;
5use scale::Decode;
6
7#[cfg(feature = "default-x509")]
8use crate::policy::{PckIdentity, PlatformInfo, Policy, QeInfo, QuoteClaims, TcbVerdict};
9use {
10 crate::constants::*,
11 crate::policy::PckCertFlag,
12 crate::qe_identity::{QeIdentity, QeTcbLevel},
13 crate::tcb_info::{TcbInfo, TcbLevel, TcbStatus, TcbStatusWithAdvisory, TdxModuleTcbLevel},
14 alloc::string::String,
15 alloc::vec::Vec,
16};
17
18pub use crate::quote::{AuthData, EnclaveReport, Quote};
19
20use crate::{
21 config::{Config, CryptoProvider},
22 quote::{Report, TDAttributes},
23 utils::{
24 encode_as_der_with, extract_certs, parse_crls, parse_rfc3339_unix_secs,
25 verify_certificate_chain,
26 },
27};
28use crate::{
29 quote::{TDReport10, TDReport15},
30 QuoteCollateralV3,
31};
32
33use rustls_pki_types::CertificateDer;
34use serde::{Deserialize, Serialize};
35
36pub struct CryptoBackend {
42 pub sig_algo: &'static dyn rustls_pki_types::SignatureVerificationAlgorithm,
44 pub sha256: fn(&[u8]) -> [u8; 32],
46 pub sha384: fn(&[u8]) -> [u8; 48],
48 pub encode_ecdsa: fn(&[u8]) -> Result<Vec<u8>>,
50 pub parse_pck_extension: fn(&[u8]) -> Result<crate::intel::PckExtension>,
52}
53
54fn backend_for<C: Config>() -> CryptoBackend {
55 CryptoBackend {
56 sig_algo: C::Crypto::sig_algo(),
57 sha256: C::Crypto::sha256,
58 sha384: C::Crypto::sha384,
59 encode_ecdsa: encode_as_der_with::<C>,
60 parse_pck_extension: crate::intel::parse_pck_extension_with::<C>,
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65enum TeeType {
66 Sgx,
67 Tdx,
68}
69
70impl TeeType {
71 fn from_u32(value: u32) -> Result<Self> {
72 match value {
73 TEE_TYPE_SGX => Ok(TeeType::Sgx),
74 TEE_TYPE_TDX => Ok(TeeType::Tdx),
75 _ => bail!("Unsupported TEE type: {value}"),
76 }
77 }
78
79 fn is_tdx(&self) -> bool {
80 matches!(self, TeeType::Tdx)
81 }
82}
83
84#[cfg(feature = "js")]
85use wasm_bindgen::prelude::*;
86
87#[cfg(feature = "js")]
88fn format_error_chain(e: &anyhow::Error) -> String {
89 use alloc::format;
90 let mut msg = format!("{}", e);
91 let mut source = e.source();
92 while let Some(err) = source {
93 msg.push_str(&format!("\n Caused by: {}", err));
94 source = err.source();
95 }
96 msg
97}
98
99#[cfg(feature = "borsh_schema")]
100use borsh::BorshSchema;
101#[cfg(feature = "borsh")]
102use borsh::{BorshDeserialize, BorshSerialize};
103use core::marker::PhantomData;
104
105#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
113#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
114#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
115struct QuoteVerificationResult {
116 header: crate::quote::Header,
117 report: Report,
118 collateral: QuoteCollateralV3,
119 #[serde(with = "crate::utils::serde_vec_bytes")]
120 pck_cert_chain_der: Vec<Vec<u8>>,
121 tee_type: u32,
123 tcb_status: TcbStatus,
124 advisory_ids: Vec<String>,
125 platform_tcb_level: TcbLevel,
126 qe_tcb_level: QeTcbLevel,
127 pck_ext: PckCertChainResult,
128 qe_report: EnclaveReport,
129 tcb_eval_data_number: u32,
130 qe_tcb_eval_data_number: u32,
131 #[serde(with = "serde_bytes")]
132 root_key_id: [u8; 48],
133}
134
135impl QuoteVerificationResult {
136 #[cfg(feature = "default-x509")]
141 pub fn claims(&self) -> Result<QuoteClaims> {
142 let tcb_info: TcbInfo = serde_json::from_str(&self.collateral.tcb_info)
144 .context("Failed to parse TcbInfo for claims")?;
145 let qe_identity: QeIdentity = serde_json::from_str(&self.collateral.qe_identity)
146 .context("Failed to parse QeIdentity for claims")?;
147 let pck_certs: Vec<CertificateDer<'_>> = self
148 .pck_cert_chain_der
149 .iter()
150 .map(|cert| CertificateDer::from(cert.as_slice()))
151 .collect();
152
153 let collateral_dates =
154 compute_collateral_time_window(&self.collateral, &pck_certs, &tcb_info, &qe_identity)?;
155
156 let root_key_id = self.root_key_id;
158
159 let root_ca_crl_num = crate::utils::extract_crl_number(&self.collateral.root_ca_crl)
161 .context("Failed to extract root CA CRL number")?;
162 let pck_crl_num = crate::utils::extract_crl_number(&self.collateral.pck_crl)
163 .context("Failed to extract PCK CRL number")?;
164
165 let tcb_date_tag = parse_rfc3339_unix_secs(&self.platform_tcb_level.tcb_date)
167 .context("Failed to parse platform TCB date")?;
168
169 Ok(QuoteClaims {
170 claims_version: 1,
171 header: self.header,
172 tee_type: self.tee_type,
173 tcb: TcbVerdict {
174 status: self.tcb_status,
175 advisory_ids: self.advisory_ids.clone(),
176 eval_data_number: self.tcb_eval_data_number,
177 },
178 platform: PlatformInfo {
179 tcb_level: self.platform_tcb_level.clone(),
180 tcb_date_tag,
181 pck: PckIdentity {
182 ppid: self.pck_ext.ppid.clone(),
183 cpu_svn: self.pck_ext.cpu_svn,
184 pce_svn: self.pck_ext.pce_svn,
185 pce_id: self.pck_ext.pce_id.clone(),
186 fmspc: self.pck_ext.fmspc,
187 sgx_type: self.pck_ext.sgx_type,
188 platform_instance_id: self.pck_ext.platform_instance_id,
189 dynamic_platform: self.pck_ext.dynamic_platform,
190 cached_keys: self.pck_ext.cached_keys,
191 smt_enabled: self.pck_ext.smt_enabled,
192 platform_provider_id: None,
198 },
199 root_key_id: root_key_id.to_vec(),
200 pck_crl_num,
201 root_ca_crl_num,
202 },
203 qe: QeInfo {
204 tcb_level: self.qe_tcb_level.clone(),
205 report: self.qe_report,
206 tcb_eval_data_number: self.qe_tcb_eval_data_number,
207 },
208 report: self.report.clone(),
209 earliest_issue_date: collateral_dates.earliest_issue,
210 latest_issue_date: collateral_dates.latest_issue,
211 earliest_expiration_date: collateral_dates.earliest_expiration,
212 qe_iden_earliest_issue_date: collateral_dates.qe_iden_earliest_issue,
213 qe_iden_latest_issue_date: collateral_dates.qe_iden_latest_issue,
214 qe_iden_earliest_expiration_date: collateral_dates.qe_iden_earliest_expiration,
215 })
216 }
217
218 pub fn into_report_unchecked(self) -> VerifiedReport {
225 let platform_status = TcbStatusWithAdvisory::new(
226 self.platform_tcb_level.tcb_status,
227 self.platform_tcb_level.advisory_ids.clone(),
228 );
229 let qe_status = TcbStatusWithAdvisory::new(
230 self.qe_tcb_level.tcb_status,
231 self.qe_tcb_level.advisory_ids.clone(),
232 );
233 VerifiedReport {
234 status: self.tcb_status.to_string(),
235 advisory_ids: self.advisory_ids,
236 report: self.report,
237 ppid: self.pck_ext.ppid,
238 platform_status,
239 qe_status,
240 }
241 }
242}
243
244#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
245#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
246#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
247pub struct VerifiedReport {
248 pub status: String,
249 pub advisory_ids: Vec<String>,
250 pub report: Report,
251 #[serde(with = "serde_bytes")]
252 pub ppid: Vec<u8>,
253 pub qe_status: TcbStatusWithAdvisory,
254 pub platform_status: TcbStatusWithAdvisory,
255}
256
257pub struct QuoteVerifier<C: Config = crate::configs::DefaultConfig> {
261 root_ca_der: Vec<u8>,
262 allow_service_td: bool,
263 allow_debug: bool,
264 config: PhantomData<C>,
265}
266
267#[cfg(feature = "default-x509")]
268impl QuoteVerifier<crate::configs::DefaultConfig> {
269 pub fn new(root_ca_der: Vec<u8>) -> Self {
271 Self {
272 root_ca_der,
273 allow_service_td: false,
274 allow_debug: false,
275 config: PhantomData,
276 }
277 }
278
279 pub fn new_prod() -> Self {
281 Self::new(TRUSTED_ROOT_CA_DER.to_vec())
282 }
283}
284
285impl<C: Config> QuoteVerifier<C> {
286 pub fn new_with_config(root_ca_der: Vec<u8>) -> Self {
288 Self {
289 root_ca_der,
290 allow_service_td: false,
291 allow_debug: false,
292 config: PhantomData,
293 }
294 }
295
296 pub fn with_config<D: Config>(self) -> QuoteVerifier<D> {
298 QuoteVerifier {
299 root_ca_der: self.root_ca_der,
300 allow_service_td: self.allow_service_td,
301 allow_debug: self.allow_debug,
302 config: PhantomData,
303 }
304 }
305
306 pub fn allow_service_td(mut self, allow: bool) -> Self {
307 self.allow_service_td = allow;
308 self
309 }
310
311 pub fn allow_debug(mut self, allow: bool) -> Self {
312 self.allow_debug = allow;
313 self
314 }
315
316 #[cfg(feature = "default-x509")]
318 pub fn verify_with_policy<P: Policy + ?Sized>(
319 &self,
320 raw_quote: &[u8],
321 collateral: impl Into<QuoteCollateralV3>,
322 now_secs: u64,
323 policy: &P,
324 ) -> Result<QuoteClaims> {
325 let claims = self
326 .verify_result(raw_quote, collateral, now_secs)?
327 .claims()?;
328 policy.validate(&claims)?;
329 Ok(claims)
330 }
331
332 fn verify_result(
333 &self,
334 raw_quote: &[u8],
335 collateral: impl Into<QuoteCollateralV3>,
336 now_secs: u64,
337 ) -> Result<QuoteVerificationResult> {
338 let backend = backend_for::<C>();
339 verify_impl(
340 raw_quote,
341 collateral.into(),
342 now_secs,
343 &self.root_ca_der,
344 &backend,
345 self.allow_service_td,
346 self.allow_debug,
347 #[cfg(feature = "danger-allow-tcb-override")]
348 None::<fn(TcbInfo) -> TcbInfo>,
349 )
350 }
351
352 pub fn verify(
354 &self,
355 raw_quote: &[u8],
356 collateral: &QuoteCollateralV3,
357 now_secs: u64,
358 ) -> Result<VerifiedReport> {
359 self.verify_result(raw_quote, collateral, now_secs)
360 .map(QuoteVerificationResult::into_report_unchecked)
361 }
362
363 #[cfg(all(feature = "danger-allow-tcb-override", feature = "default-x509"))]
368 pub fn dangerous_verify_claims_with_tcb_override(
369 &self,
370 raw_quote: &[u8],
371 collateral: impl Into<QuoteCollateralV3>,
372 now_secs: u64,
373 override_tcb_info: impl FnOnce(TcbInfo) -> TcbInfo,
374 ) -> Result<QuoteClaims> {
375 verify_impl(
376 raw_quote,
377 collateral.into(),
378 now_secs,
379 &self.root_ca_der,
380 &backend_for::<C>(),
381 self.allow_service_td,
382 self.allow_debug,
383 Some(override_tcb_info),
384 )?
385 .claims()
386 }
387
388 #[cfg(feature = "danger-allow-tcb-override")]
389 fn dangerous_verify_result_with_tcb_override<F: FnOnce(TcbInfo) -> TcbInfo>(
390 &self,
391 raw_quote: &[u8],
392 collateral: impl Into<QuoteCollateralV3>,
393 now_secs: u64,
394 override_tcb_info: F,
395 ) -> Result<QuoteVerificationResult> {
396 let backend = backend_for::<C>();
397 verify_impl(
398 raw_quote,
399 collateral.into(),
400 now_secs,
401 &self.root_ca_der,
402 &backend,
403 self.allow_service_td,
404 self.allow_debug,
405 Some(override_tcb_info),
406 )
407 }
408
409 #[cfg(all(feature = "danger-allow-tcb-override", feature = "default-x509"))]
410 pub fn dangerous_verify_with_tcb_override(
411 &self,
412 raw_quote: &[u8],
413 collateral: &QuoteCollateralV3,
414 now_secs: u64,
415 override_tcb_info: impl FnOnce(TcbInfo) -> TcbInfo,
416 ) -> Result<VerifiedReport> {
417 self.dangerous_verify_result_with_tcb_override(
418 raw_quote,
419 collateral,
420 now_secs,
421 override_tcb_info,
422 )
423 .map(QuoteVerificationResult::into_report_unchecked)
424 }
425}
426
427#[cfg(feature = "default-x509")]
429pub fn verify(
430 raw_quote: &[u8],
431 collateral: &QuoteCollateralV3,
432 now_secs: u64,
433) -> Result<VerifiedReport> {
434 QuoteVerifier::<crate::configs::DefaultConfig>::new_prod()
435 .verify(raw_quote, collateral, now_secs)
436}
437
438#[cfg(all(feature = "default-x509", feature = "danger-allow-tcb-override"))]
439pub fn dangerous_verify_with_tcb_override(
440 raw_quote: &[u8],
441 collateral: &QuoteCollateralV3,
442 now_secs: u64,
443 override_tcb_info: impl FnOnce(TcbInfo) -> TcbInfo,
444) -> Result<VerifiedReport> {
445 QuoteVerifier::<crate::configs::DefaultConfig>::new_prod().dangerous_verify_with_tcb_override(
446 raw_quote,
447 collateral,
448 now_secs,
449 override_tcb_info,
450 )
451}
452
453#[cfg(feature = "js")]
462#[wasm_bindgen(js_name = "QuotePolicy")]
463pub struct JsQuotePolicy {
464 inner: crate::policy::QuotePolicy,
465}
466
467#[cfg(feature = "js")]
468fn js_parse_tcb_status(s: &str) -> Result<TcbStatus, JsValue> {
469 match s {
470 "UpToDate" => Ok(TcbStatus::UpToDate),
471 "SWHardeningNeeded" => Ok(TcbStatus::SWHardeningNeeded),
472 "ConfigurationNeeded" => Ok(TcbStatus::ConfigurationNeeded),
473 "ConfigurationAndSWHardeningNeeded" => Ok(TcbStatus::ConfigurationAndSWHardeningNeeded),
474 "OutOfDate" => Ok(TcbStatus::OutOfDate),
475 "OutOfDateConfigurationNeeded" => Ok(TcbStatus::OutOfDateConfigurationNeeded),
476 "Revoked" => Ok(TcbStatus::Revoked),
477 _ => Err(JsValue::from_str(&alloc::format!(
478 "Unknown TCB status: {s}"
479 ))),
480 }
481}
482
483#[cfg(feature = "js")]
484#[wasm_bindgen(js_class = "QuotePolicy")]
485impl JsQuotePolicy {
486 #[wasm_bindgen(constructor)]
488 pub fn strict(now_secs: u64) -> Self {
489 Self {
490 inner: crate::policy::QuotePolicy::strict(now_secs),
491 }
492 }
493
494 #[wasm_bindgen(js_name = "claimsOnly")]
496 pub fn claims_only(now_secs: u64) -> Self {
497 Self {
498 inner: crate::policy::QuotePolicy::claims_only(now_secs),
499 }
500 }
501
502 pub fn allow_status(self, status: &str) -> Result<JsQuotePolicy, JsValue> {
504 let s = js_parse_tcb_status(status)?;
505 Ok(Self {
506 inner: self.inner.allow_status(s),
507 })
508 }
509
510 pub fn reject_advisory(self, id: &str) -> Self {
512 Self {
513 inner: self.inner.reject_advisory(id),
514 }
515 }
516
517 pub fn reject_advisories(self, ids: Vec<String>) -> Self {
519 Self {
520 inner: self.inner.reject_advisories(&ids),
521 }
522 }
523
524 pub fn platform_grace_period(self, secs: u64) -> Self {
526 Self {
527 inner: self.inner.platform_grace_period(Duration::from_secs(secs)),
528 }
529 }
530
531 pub fn qe_grace_period(self, secs: u64) -> Self {
533 Self {
534 inner: self.inner.qe_grace_period(Duration::from_secs(secs)),
535 }
536 }
537
538 pub fn min_tcb_eval_data_number(self, min: u32) -> Self {
540 Self {
541 inner: self.inner.min_tcb_eval_data_number(min),
542 }
543 }
544
545 pub fn allow_dynamic_platform(self, allow: bool) -> Self {
547 Self {
548 inner: self.inner.allow_dynamic_platform(allow),
549 }
550 }
551
552 pub fn allow_cached_keys(self, allow: bool) -> Self {
554 Self {
555 inner: self.inner.allow_cached_keys(allow),
556 }
557 }
558
559 pub fn allow_smt(self, allow: bool) -> Self {
561 Self {
562 inner: self.inner.allow_smt(allow),
563 }
564 }
565
566 pub fn accepted_sgx_types(self, types: Vec<u8>) -> Self {
568 Self {
569 inner: self.inner.accepted_sgx_types(&types),
570 }
571 }
572}
573
574#[cfg(feature = "js")]
582#[wasm_bindgen(js_name = "QuoteVerifier")]
583pub struct JsQuoteVerifier {
584 inner: QuoteVerifier,
585}
586
587#[cfg(feature = "js")]
588#[wasm_bindgen(js_class = "QuoteVerifier")]
589impl JsQuoteVerifier {
590 #[wasm_bindgen(constructor)]
592 pub fn new(root_ca_der: Option<Vec<u8>>) -> Self {
593 let inner = match root_ca_der {
594 Some(der) => QuoteVerifier::new(der),
595 None => QuoteVerifier::new_prod(),
596 };
597 Self { inner }
598 }
599
600 pub fn verify(
602 &self,
603 raw_quote: JsValue,
604 quote_collateral: JsValue,
605 now: u64,
606 ) -> Result<JsValue, JsValue> {
607 let raw_quote: Vec<u8> = serde_wasm_bindgen::from_value(raw_quote)
608 .map_err(|_| JsValue::from_str("Failed to decode raw_quote"))?;
609 let quote_collateral =
610 serde_wasm_bindgen::from_value::<QuoteCollateralV3>(quote_collateral)?;
611 let report = self
612 .inner
613 .verify(&raw_quote, "e_collateral, now)
614 .map_err(|e| JsValue::from_str(&format_error_chain(&e)))?;
615 serde_wasm_bindgen::to_value(&report)
616 .map_err(|_| JsValue::from_str("Failed to encode verified report"))
617 }
618
619 pub fn verify_with_policy(
621 &self,
622 raw_quote: JsValue,
623 quote_collateral: JsValue,
624 now: u64,
625 policy: &JsQuotePolicy,
626 ) -> Result<JsValue, JsValue> {
627 let raw_quote: Vec<u8> = serde_wasm_bindgen::from_value(raw_quote)
628 .map_err(|_| JsValue::from_str("Failed to decode raw_quote"))?;
629 let quote_collateral =
630 serde_wasm_bindgen::from_value::<QuoteCollateralV3>(quote_collateral)?;
631 let claims = self
632 .inner
633 .verify_with_policy(&raw_quote, quote_collateral, now, &policy.inner)
634 .map_err(|e| JsValue::from_str(&format_error_chain(&e)))?;
635 serde_wasm_bindgen::to_value(&claims)
636 .map_err(|_| JsValue::from_str("Failed to encode quote claims"))
637 }
638
639 pub async fn get_collateral(pccs_url: &str, raw_quote: JsValue) -> Result<JsValue, JsValue> {
641 let raw_quote: Vec<u8> = serde_wasm_bindgen::from_value(raw_quote)
642 .map_err(|_| JsValue::from_str("Failed to decode raw_quote"))?;
643
644 let collateral: QuoteCollateralV3 =
645 crate::collateral::CollateralClient::with_default_http(pccs_url)
646 .map_err(|e| JsValue::from_str(&format_error_chain(&e)))?
647 .fetch(&raw_quote)
648 .await
649 .map_err(|e| JsValue::from_str(&format_error_chain(&e)))?;
650 serde_wasm_bindgen::to_value(&collateral)
651 .map_err(|_| JsValue::from_str("Failed to encode collateral"))
652 }
653}
654
655fn verify_tcb_info_signature(
661 collateral: &QuoteCollateralV3,
662 now: UnixTime,
663 crls: &[webpki::CertRevocationList<'_>],
664 trust_anchor: rustls_pki_types::TrustAnchor,
665 backend: &CryptoBackend,
666) -> Result<TcbInfo> {
667 let tcb_info = serde_json::from_str::<TcbInfo>(&collateral.tcb_info)
669 .context("Failed to decode TcbInfo")?;
670
671 let issue_date = parse_rfc3339_unix_secs(&tcb_info.issue_date)
673 .context("Failed to parse TCB Info issue date")?;
674 let next_update = parse_rfc3339_unix_secs(&tcb_info.next_update)
675 .context("Failed to parse TCB Info next update")?;
676 if now.as_secs() < issue_date {
677 bail!("TCBInfo issue date is in the future");
678 }
679 if now.as_secs() > next_update {
680 bail!("TCBInfo expired");
681 }
682
683 let tcb_certs = extract_certs(collateral.tcb_info_issuer_chain.as_bytes())?;
685 let [tcb_leaf, tcb_chain @ ..] = &tcb_certs[..] else {
686 bail!("Certificate chain is too short for TCB Info");
687 };
688 let tcb_leaf_cert = webpki::EndEntityCert::try_from(tcb_leaf)
689 .context("Failed to parse TCB Info leaf certificate")?;
690 verify_certificate_chain(&tcb_leaf_cert, tcb_chain, now, crls, trust_anchor)?;
691
692 let asn1_signature = (backend.encode_ecdsa)(&collateral.tcb_info_signature)?;
694 if tcb_leaf_cert
695 .verify_signature(
696 backend.sig_algo,
697 collateral.tcb_info.as_bytes(),
698 &asn1_signature,
699 )
700 .is_err()
701 {
702 bail!("Signature is invalid for tcb_info in quote_collateral");
703 }
704
705 Ok(tcb_info)
706}
707
708fn verify_qe_identity_signature(
714 collateral: &QuoteCollateralV3,
715 now: UnixTime,
716 crls: &[webpki::CertRevocationList<'_>],
717 trust_anchor: rustls_pki_types::TrustAnchor,
718 backend: &CryptoBackend,
719) -> Result<QeIdentity> {
720 let qe_identity = serde_json::from_str::<QeIdentity>(&collateral.qe_identity)
722 .context("Failed to decode QeIdentity")?;
723
724 let issue_date = parse_rfc3339_unix_secs(&qe_identity.issue_date)
726 .context("Failed to parse QE Identity issue date")?;
727 let next_update = parse_rfc3339_unix_secs(&qe_identity.next_update)
728 .context("Failed to parse QE Identity next update")?;
729 if now.as_secs() < issue_date {
730 bail!("QE Identity issue date is in the future");
731 }
732 if now.as_secs() > next_update {
733 bail!("QE Identity expired");
734 }
735
736 let qe_id_certs = extract_certs(collateral.qe_identity_issuer_chain.as_bytes())?;
738 let [qe_id_leaf, qe_id_chain @ ..] = &qe_id_certs[..] else {
739 bail!("Certificate chain is too short for QE Identity");
740 };
741 let qe_id_leaf_cert = webpki::EndEntityCert::try_from(qe_id_leaf)
742 .context("Failed to parse QE Identity leaf certificate")?;
743 verify_certificate_chain(&qe_id_leaf_cert, qe_id_chain, now, crls, trust_anchor)?;
744
745 let qe_id_asn1_signature = (backend.encode_ecdsa)(&collateral.qe_identity_signature)?;
747 if qe_id_leaf_cert
748 .verify_signature(
749 backend.sig_algo,
750 collateral.qe_identity.as_bytes(),
751 &qe_id_asn1_signature,
752 )
753 .is_err()
754 {
755 bail!("Signature is invalid for qe_identity in quote_collateral");
756 }
757
758 Ok(qe_identity)
759}
760
761fn verify_pck_cert_chain(
770 collateral: &QuoteCollateralV3,
771 certification_data: &crate::quote::CertificationData,
772 now: UnixTime,
773 crls: &[webpki::CertRevocationList<'_>],
774 trust_anchor: rustls_pki_types::TrustAnchor,
775 backend: &CryptoBackend,
776) -> Result<PckCertChainResult> {
777 let certification_certs = if let Some(pem_chain) = &collateral.pck_certificate_chain {
779 extract_certs(pem_chain.as_bytes())
780 .context("Failed to extract PCK certificates from collateral")?
781 } else {
782 if certification_data.cert_type != PCK_CERT_CHAIN {
783 bail!("Unsupported DCAP PCK cert format: {}. Use get_collateral() to fetch PCK certificate.", certification_data.cert_type);
784 }
785 extract_certs(&certification_data.body.data)
786 .context("Failed to extract PCK certificates from quote")?
787 };
788
789 let [pck_leaf, pck_chain @ ..] = &certification_certs[..] else {
790 bail!("Certificate chain is too short in quote");
791 };
792
793 let pck_leaf_cert =
795 webpki::EndEntityCert::try_from(pck_leaf).context("Failed to parse PCK certificate")?;
796 verify_certificate_chain(&pck_leaf_cert, pck_chain, now, crls, trust_anchor)?;
797
798 let pck_ext = (backend.parse_pck_extension)(pck_leaf)?;
800
801 let pce_id = pck_ext.pce_id.clone();
803
804 let platform_instance_id = pck_ext.platform_instance_id.as_ref().and_then(|v| {
806 let arr: [u8; 16] = v.as_slice().try_into().ok()?;
807 Some(arr)
808 });
809
810 Ok(PckCertChainResult {
811 pck_cert_chain_der: certification_certs
812 .iter()
813 .map(|cert| cert.as_ref().to_vec())
814 .collect(),
815 pck_leaf_der: pck_leaf.as_ref().to_vec(),
816 ppid: pck_ext.ppid,
817 cpu_svn: pck_ext.cpu_svn,
818 pce_svn: pck_ext.pce_svn,
819 fmspc: pck_ext.fmspc,
820 pce_id,
821 sgx_type: pck_ext.sgx_type as u8,
822 platform_instance_id,
823 dynamic_platform: pck_ext.dynamic_platform.into(),
824 cached_keys: pck_ext.cached_keys.into(),
825 smt_enabled: pck_ext.smt_enabled.into(),
826 })
827}
828
829#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
831#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
832#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
833struct PckCertChainResult {
834 #[serde(with = "crate::utils::serde_vec_bytes")]
835 pck_cert_chain_der: Vec<Vec<u8>>,
836 #[serde(with = "serde_bytes")]
837 pck_leaf_der: Vec<u8>,
838 #[serde(with = "serde_bytes")]
839 ppid: Vec<u8>,
840 #[serde(with = "serde_bytes")]
841 cpu_svn: [u8; 16],
842 pce_svn: u16,
843 #[serde(with = "serde_bytes")]
844 fmspc: [u8; 6],
845 #[serde(with = "serde_bytes")]
846 pce_id: Vec<u8>,
847 sgx_type: u8,
848 platform_instance_id: Option<[u8; 16]>,
849 dynamic_platform: PckCertFlag,
850 cached_keys: PckCertFlag,
851 smt_enabled: PckCertFlag,
852}
853
854fn verify_qe_report_signature(
860 pck_leaf: &CertificateDer,
861 auth_data: &crate::quote::AuthDataV3,
862 backend: &CryptoBackend,
863) -> Result<EnclaveReport> {
864 let pck_leaf_cert =
865 webpki::EndEntityCert::try_from(pck_leaf).context("Failed to parse PCK certificate")?;
866
867 let qe_report_signature = (backend.encode_ecdsa)(&auth_data.qe_report_signature)?;
869 if pck_leaf_cert
870 .verify_signature(backend.sig_algo, &auth_data.qe_report, &qe_report_signature)
871 .is_err()
872 {
873 bail!("Signature is invalid for qe_report in quote");
874 }
875
876 let mut qe_report_slice = auth_data.qe_report.as_slice();
878 let qe_report =
879 EnclaveReport::decode(&mut qe_report_slice).context("Failed to decode QE report")?;
880
881 Ok(qe_report)
882}
883
884fn verify_qe_report_data(
890 qe_report: &EnclaveReport,
891 auth_data: &crate::quote::AuthDataV3,
892 backend: &CryptoBackend,
893) -> Result<()> {
894 use crate::constants::{ATTESTATION_KEY_LEN, AUTHENTICATION_DATA_LEN};
895
896 ensure!(
897 auth_data.qe_auth_data.data.len() == AUTHENTICATION_DATA_LEN,
898 "Invalid QE auth data length"
899 );
900 let mut qe_hash_data = [0u8; ATTESTATION_KEY_LEN + AUTHENTICATION_DATA_LEN];
902 qe_hash_data[..ATTESTATION_KEY_LEN].copy_from_slice(&auth_data.ecdsa_attestation_key);
903 qe_hash_data[ATTESTATION_KEY_LEN..].copy_from_slice(&auth_data.qe_auth_data.data);
904 let qe_hash = (backend.sha256)(&qe_hash_data);
905 if qe_hash[..] != qe_report.report_data[..32] {
906 bail!("QE report hash mismatch");
907 }
908 Ok(())
909}
910
911fn verify_isv_report_signature(
923 raw_quote: &[u8],
924 quote: &Quote,
925 auth_data: &crate::quote::AuthDataV3,
926 backend: &CryptoBackend,
927) -> Result<()> {
928 let mut pub_key = [0x04u8; 65];
930 pub_key[1..].copy_from_slice(&auth_data.ecdsa_attestation_key);
931
932 let der_sig = (backend.encode_ecdsa)(&auth_data.ecdsa_signature)?;
934
935 let signed_data = raw_quote
936 .get(..quote.signed_length())
937 .context("Failed to get signed quote scope")?;
938
939 backend
940 .sig_algo
941 .verify_signature(&pub_key, signed_data, &der_sig)
942 .map_err(|_| anyhow::anyhow!("ISV enclave report signature is invalid"))
943}
944
945fn match_platform_tcb(
951 tcb_info: &TcbInfo,
952 quote: &Quote,
953 tee_type: TeeType,
954 cpu_svn: &[u8],
955 pce_svn: u16,
956 fmspc: &[u8],
957) -> Result<TcbLevel> {
958 let tcb_fmspc = hex::decode(&tcb_info.fmspc)
960 .ok()
961 .context("Failed to decode TCB FMSPC")?;
962 if fmspc[..] != tcb_fmspc[..] {
963 bail!("Fmspc mismatch");
964 }
965
966 match tee_type {
968 TeeType::Tdx => {
969 if tcb_info.version < 3 || tcb_info.id != "TDX" {
970 bail!("TDX quote with non-TDX TCB info in the collateral");
971 }
972 }
973 TeeType::Sgx => {
974 if tcb_info.version < 2 || tcb_info.id != "SGX" {
975 bail!("SGX quote with non-SGX TCB info in the collateral");
976 }
977 }
978 }
979
980 for tcb_level in &tcb_info.tcb_levels {
982 if pce_svn < tcb_level.tcb.pce_svn {
983 continue;
984 }
985
986 let sgx_components: Vec<u8> = tcb_level.tcb.sgx_components.iter().map(|c| c.svn).collect();
987 if sgx_components.len() != cpu_svn.len() {
988 bail!(
989 "SGX component count mismatch: expected {}, got {}",
990 cpu_svn.len(),
991 sgx_components.len()
992 );
993 }
994
995 if cpu_svn.iter().zip(&sgx_components).any(|(a, b)| a < b) {
997 continue;
998 }
999
1000 if tee_type.is_tdx() {
1002 let td_report = quote
1003 .report
1004 .as_td10()
1005 .context("Failed to get TD10 report")?;
1006 let tdx_components: Vec<u8> =
1007 tcb_level.tcb.tdx_components.iter().map(|c| c.svn).collect();
1008 if tdx_components.len() != td_report.tee_tcb_svn.len() {
1009 bail!(
1010 "TDX component count mismatch: expected {}, got {}",
1011 td_report.tee_tcb_svn.len(),
1012 tdx_components.len()
1013 );
1014 }
1015 if td_report
1017 .tee_tcb_svn
1018 .iter()
1019 .zip(&tdx_components)
1020 .any(|(a, b)| a < b)
1021 {
1022 continue;
1023 }
1024 }
1025
1026 let mut matched = tcb_level.clone();
1027 if tee_type.is_tdx() {
1028 if let Some(module_status) =
1029 match_tdx_module_identity(tcb_info, quote).context("TDX module identity check")?
1030 {
1031 matched.tcb_status = matched
1032 .tcb_status
1033 .converge_with_component(module_status.status);
1034 for advisory in module_status.advisory_ids {
1035 if !matched.advisory_ids.contains(&advisory) {
1036 matched.advisory_ids.push(advisory);
1037 }
1038 }
1039 }
1040 }
1041 return Ok(matched);
1042 }
1043
1044 bail!("No matching TCB level found");
1045}
1046
1047fn match_tdx_module_identity(
1048 tcb_info: &TcbInfo,
1049 quote: &Quote,
1050) -> Result<Option<TcbStatusWithAdvisory>> {
1051 if tcb_info.id != "TDX" || tcb_info.version < 3 {
1052 return Ok(None);
1053 }
1054
1055 let td_report = quote
1056 .report
1057 .as_td10()
1058 .context("Failed to get TD10 report for TDX module identity")?;
1059
1060 let module_isvsvn = td_report.tee_tcb_svn[0];
1061 let module_version = td_report.tee_tcb_svn[1];
1062
1063 let base_module = match &tcb_info.tdx_module {
1064 Some(m) => m,
1065 None => {
1066 bail!("TDX TCB Info is missing tdxModule field");
1067 }
1068 };
1069
1070 fn decode_hex_array<const N: usize>(hex_str: &str, field: &str) -> Result<[u8; N]> {
1072 let bytes = hex::decode(hex_str)
1073 .map_err(|e| anyhow::anyhow!("Failed to decode {field} as hex: {e}"))?;
1074 ensure!(
1075 bytes.len() == N,
1076 "{field} has invalid length {}, expected {N}",
1077 bytes.len()
1078 );
1079 let mut arr = [0u8; N];
1080 arr.copy_from_slice(&bytes);
1081 Ok(arr)
1082 }
1083
1084 let mut expected_mrsigner =
1086 decode_hex_array::<48>(&base_module.mrsigner, "tdxModule.mrsigner")?;
1087 let mut expected_attributes =
1088 decode_hex_array::<8>(&base_module.attributes, "tdxModule.attributes")?;
1089 let mut attributes_mask =
1090 decode_hex_array::<8>(&base_module.attributes_mask, "tdxModule.attributesMask")?;
1091
1092 let mut identity_tcb_levels: Option<&[TdxModuleTcbLevel]> = None;
1095 if module_version > 0 && !tcb_info.tdx_module_identities.is_empty() {
1096 let wanted_id = format!("TDX_{:02X}", module_version);
1097 let identity = tcb_info
1098 .tdx_module_identities
1099 .iter()
1100 .find(|id| id.id.eq_ignore_ascii_case(&wanted_id))
1101 .with_context(|| {
1102 format!(
1103 "No TDX module identity with id {} found in TCB Info",
1104 wanted_id
1105 )
1106 })?;
1107
1108 expected_mrsigner =
1109 decode_hex_array::<48>(&identity.mrsigner, "tdxModuleIdentity.mrsigner")?;
1110 expected_attributes =
1111 decode_hex_array::<8>(&identity.attributes, "tdxModuleIdentity.attributes")?;
1112 attributes_mask = decode_hex_array::<8>(
1113 &identity.attributes_mask,
1114 "tdxModuleIdentity.attributesMask",
1115 )?;
1116 identity_tcb_levels = Some(&identity.tcb_levels);
1117 }
1118
1119 if td_report.mr_signer_seam != expected_mrsigner {
1121 bail!(
1122 "TDX module MRSIGNER mismatch: expected {}, got {}",
1123 hex::encode_upper(expected_mrsigner),
1124 hex::encode_upper(td_report.mr_signer_seam)
1125 );
1126 }
1127
1128 for (i, ((expected, mask), actual)) in expected_attributes
1131 .iter()
1132 .zip(attributes_mask.iter())
1133 .zip(td_report.seam_attributes.iter())
1134 .enumerate()
1135 {
1136 let expected_masked = expected & mask;
1137 let actual_masked = actual & mask;
1138 if expected_masked != actual_masked {
1139 bail!(
1140 "TDX module SEAMATTRIBUTES mismatch at byte {}: expected {:02X} (masked), got {:02X} (masked)",
1141 i,
1142 expected_masked,
1143 actual_masked
1144 );
1145 }
1146 if actual & !mask != 0 {
1147 bail!(
1148 "TDX module SEAMATTRIBUTES has bits set outside mask at byte {}",
1149 i
1150 );
1151 }
1152 }
1153
1154 if let Some(levels) = identity_tcb_levels {
1156 let mut matched: Option<&TdxModuleTcbLevel> = None;
1157 for level in levels {
1158 if module_isvsvn >= level.tcb.isvsvn {
1159 matched = Some(level);
1160 break;
1161 }
1162 }
1163
1164 let module_level = matched.with_context(|| {
1165 format!(
1166 "TDX module ISVSVN {} is below minimum required from TDX module TCB levels",
1167 module_isvsvn
1168 )
1169 })?;
1170
1171 return Ok(Some(TcbStatusWithAdvisory::new(
1172 module_level.tcb_status,
1173 module_level.advisory_ids.clone(),
1174 )));
1175 }
1176
1177 Ok(None)
1180}
1181
1182#[allow(clippy::too_many_arguments)]
1201fn verify_impl(
1202 raw_quote: &[u8],
1203 collateral: QuoteCollateralV3,
1204 now_secs: u64,
1205 root_ca_der: &[u8],
1206 backend: &CryptoBackend,
1207 allow_service_td: bool,
1208 allow_debug: bool,
1209 #[cfg(feature = "danger-allow-tcb-override")] override_tcb_info: Option<
1210 impl FnOnce(TcbInfo) -> TcbInfo,
1211 >,
1212) -> Result<QuoteVerificationResult> {
1213 let root_ca = CertificateDer::from_slice(root_ca_der);
1215 let trust_anchor =
1216 webpki::anchor_from_trusted_cert(&root_ca).context("Failed to load root ca")?;
1217 let now = UnixTime::since_unix_epoch(Duration::from_secs(now_secs));
1218 let raw_crls = [&collateral.root_ca_crl[..], &collateral.pck_crl];
1219
1220 webpki::check_single_cert_crl(root_ca_der, &raw_crls, now)?;
1222
1223 let crls = parse_crls(&raw_crls)?;
1225
1226 let mut quote_slice = raw_quote;
1228 let quote = Quote::decode(&mut quote_slice).context("Failed to decode quote")?;
1229 if !ALLOWED_QUOTE_VERSIONS.contains("e.header.version) {
1230 bail!("Unsupported DCAP quote version");
1231 }
1232 if quote.header.qe_vendor_id != INTEL_QE_VENDOR_ID {
1233 bail!("Unknown QE vendor ID");
1234 }
1235 let tee_type = TeeType::from_u32(quote.header.tee_type)?;
1236 match tee_type {
1237 TeeType::Sgx => {
1238 if quote.header.version != 3 {
1239 bail!("SGX TEE quote must have version 3");
1240 }
1241 }
1242 TeeType::Tdx => {
1243 if ![4, 5].contains("e.header.version) {
1244 bail!("TDX TEE quote must have version 4 or 5");
1245 }
1246 }
1247 }
1248 if quote.header.attestation_key_type != ATTESTATION_KEY_TYPE_ECDSA256_WITH_P256_CURVE {
1249 bail!("Unsupported DCAP attestation key type");
1250 }
1251 let auth_data = quote.auth_data.clone().into_v3();
1252
1253 let mut tcb_info =
1255 verify_tcb_info_signature(&collateral, now, &crls, trust_anchor.clone(), backend)?;
1256
1257 #[cfg(feature = "danger-allow-tcb-override")]
1258 if let Some(override_tcb_info) = override_tcb_info {
1259 tcb_info = override_tcb_info(tcb_info);
1260 }
1261 tcb_info.canonicalize_tcb_levels();
1262
1263 let qe_identity =
1265 verify_qe_identity_signature(&collateral, now, &crls, trust_anchor.clone(), backend)?;
1266 let (expected_qe_id, allowed_qe_versions): (&str, &[u8]) = match tee_type {
1267 TeeType::Sgx => ("QE", &[2]),
1268 TeeType::Tdx => ("TD_QE", &[2, 3]),
1269 };
1270 if qe_identity.id != expected_qe_id || !allowed_qe_versions.contains(&qe_identity.version) {
1271 bail!(
1272 "Unsupported QE Identity id/version for the quote TEE type: {} version {} (expected {} version {:?})",
1273 qe_identity.id,
1274 qe_identity.version,
1275 expected_qe_id,
1276 allowed_qe_versions
1277 );
1278 }
1279
1280 let pck_result = verify_pck_cert_chain(
1282 &collateral,
1283 &auth_data.certification_data,
1284 now,
1285 &crls,
1286 trust_anchor,
1287 backend,
1288 )?;
1289 let pck_leaf = CertificateDer::from(pck_result.pck_leaf_der.as_slice());
1290
1291 let qe_report = verify_qe_report_signature(&pck_leaf, &auth_data, backend)?;
1293
1294 verify_qe_report_data(&qe_report, &auth_data, backend)?;
1296
1297 let qe_tcb_level = verify_qe_identity_policy(&qe_report, &qe_identity)?;
1299
1300 verify_isv_report_signature(raw_quote, "e, &auth_data, backend)?;
1302
1303 let platform_tcb_level = match_platform_tcb(
1305 &tcb_info,
1306 "e,
1307 tee_type,
1308 &pck_result.cpu_svn,
1309 pck_result.pce_svn,
1310 &pck_result.fmspc,
1311 )?;
1312
1313 let platform_status = TcbStatusWithAdvisory::new(
1315 platform_tcb_level.tcb_status,
1316 platform_tcb_level.advisory_ids.clone(),
1317 );
1318 let qe_status =
1319 TcbStatusWithAdvisory::new(qe_tcb_level.tcb_status, qe_tcb_level.advisory_ids.clone());
1320 let final_status = platform_status.merge(&qe_status);
1321
1322 if final_status.status == TcbStatus::Revoked {
1325 bail!("TCB status is invalid: Revoked");
1326 }
1327
1328 #[cfg(feature = "default-x509")]
1329 let root_key_id = {
1330 let root_cert: x509_cert::Certificate =
1331 der::Decode::from_der(root_ca_der).context("Failed to parse root CA certificate")?;
1332 let raw_key = root_cert
1333 .tbs_certificate()
1334 .subject_public_key_info()
1335 .subject_public_key
1336 .raw_bytes();
1337 (backend.sha384)(raw_key)
1338 };
1339 #[cfg(not(feature = "default-x509"))]
1340 let root_key_id = [0u8; 48];
1341
1342 validate_attrs("e.report, allow_service_td, allow_debug)?;
1344
1345 Ok(QuoteVerificationResult {
1346 header: quote.header,
1347 report: quote.report,
1348 collateral,
1349 pck_cert_chain_der: pck_result.pck_cert_chain_der.clone(),
1350 tee_type: quote.header.tee_type,
1351 tcb_status: final_status.status,
1352 advisory_ids: final_status.advisory_ids,
1353 platform_tcb_level,
1354 qe_tcb_level,
1355 pck_ext: pck_result,
1356 qe_report,
1357 tcb_eval_data_number: tcb_info
1358 .tcb_evaluation_data_number
1359 .min(qe_identity.tcb_evaluation_data_number),
1360 qe_tcb_eval_data_number: qe_identity.tcb_evaluation_data_number,
1361 root_key_id,
1362 })
1363}
1364
1365#[cfg(feature = "default-x509")]
1367struct CollateralDates {
1368 earliest_issue: u64,
1369 latest_issue: u64,
1370 earliest_expiration: u64,
1371 qe_iden_earliest_issue: u64,
1373 qe_iden_latest_issue: u64,
1374 qe_iden_earliest_expiration: u64,
1375}
1376
1377#[cfg(feature = "default-x509")]
1390fn compute_collateral_time_window(
1391 collateral: &QuoteCollateralV3,
1392 pck_cert_chain: &[CertificateDer<'_>],
1393 tcb_info: &TcbInfo,
1394 qe_identity: &QeIdentity,
1395) -> Result<CollateralDates> {
1396 fn parse_crl_dates(crl_der: &[u8]) -> Result<(u64, Option<u64>)> {
1397 use der::Decode as _;
1398 let crl: x509_cert::crl::CertificateList<x509_cert::certificate::Rfc5280> =
1399 x509_cert::crl::CertificateList::from_der(crl_der)
1400 .context("Failed to parse CRL for time window")?;
1401 let this_update = crl.tbs_cert_list.this_update.to_unix_duration().as_secs();
1402 let next_update = crl
1403 .tbs_cert_list
1404 .next_update
1405 .map(|t| t.to_unix_duration().as_secs());
1406 Ok((this_update, next_update))
1407 }
1408
1409 fn fold_cert_chain_dates(
1411 pem_chain: &[u8],
1412 earliest_issue: &mut u64,
1413 latest_issue: &mut u64,
1414 earliest_expiration: &mut u64,
1415 ) -> Result<()> {
1416 let certs = extract_certs(pem_chain)?;
1417 fold_der_cert_dates(&certs, earliest_issue, latest_issue, earliest_expiration)
1418 }
1419
1420 fn fold_der_cert_dates(
1421 certs: &[CertificateDer<'_>],
1422 earliest_issue: &mut u64,
1423 latest_issue: &mut u64,
1424 earliest_expiration: &mut u64,
1425 ) -> Result<()> {
1426 use der::Decode as _;
1427 for cert_der in certs {
1428 let cert = x509_cert::Certificate::from_der(cert_der)
1429 .context("Failed to parse certificate for time window")?;
1430 let not_before = cert
1431 .tbs_certificate()
1432 .validity()
1433 .not_before
1434 .to_unix_duration()
1435 .as_secs();
1436 let not_after = cert
1437 .tbs_certificate()
1438 .validity()
1439 .not_after
1440 .to_unix_duration()
1441 .as_secs();
1442 *earliest_issue = (*earliest_issue).min(not_before);
1443 *latest_issue = (*latest_issue).max(not_before);
1444 *earliest_expiration = (*earliest_expiration).min(not_after);
1445 }
1446 Ok(())
1447 }
1448
1449 let tcb_issue = parse_rfc3339_unix_secs(&tcb_info.issue_date).context("TCBInfo issueDate")?;
1451 let tcb_next = parse_rfc3339_unix_secs(&tcb_info.next_update).context("TCBInfo nextUpdate")?;
1452
1453 let qe_issue =
1455 parse_rfc3339_unix_secs(&qe_identity.issue_date).context("QEIdentity issueDate")?;
1456 let qe_next =
1457 parse_rfc3339_unix_secs(&qe_identity.next_update).context("QEIdentity nextUpdate")?;
1458
1459 let mut earliest_issue = tcb_issue.min(qe_issue);
1460 let mut latest_issue = tcb_issue.max(qe_issue);
1461 let mut earliest_expiration = tcb_next.min(qe_next);
1462
1463 for crl_der in [&collateral.root_ca_crl[..], &collateral.pck_crl[..]] {
1465 let (this_update, next_update) = parse_crl_dates(crl_der)?;
1466 earliest_issue = earliest_issue.min(this_update);
1467 latest_issue = latest_issue.max(this_update);
1468 if let Some(next) = next_update {
1469 earliest_expiration = earliest_expiration.min(next);
1470 }
1471 }
1472
1473 fold_cert_chain_dates(
1476 collateral.pck_crl_issuer_chain.as_bytes(),
1477 &mut earliest_issue,
1478 &mut latest_issue,
1479 &mut earliest_expiration,
1480 )?;
1481 fold_der_cert_dates(
1483 pck_cert_chain,
1484 &mut earliest_issue,
1485 &mut latest_issue,
1486 &mut earliest_expiration,
1487 )?;
1488 fold_cert_chain_dates(
1490 collateral.tcb_info_issuer_chain.as_bytes(),
1491 &mut earliest_issue,
1492 &mut latest_issue,
1493 &mut earliest_expiration,
1494 )?;
1495 let mut qe_chain_earliest_issue = u64::MAX;
1497 let mut qe_chain_latest_issue = 0u64;
1498 let mut qe_chain_earliest_expiration = u64::MAX;
1499 fold_cert_chain_dates(
1500 collateral.qe_identity_issuer_chain.as_bytes(),
1501 &mut qe_chain_earliest_issue,
1502 &mut qe_chain_latest_issue,
1503 &mut qe_chain_earliest_expiration,
1504 )?;
1505 earliest_issue = earliest_issue.min(qe_chain_earliest_issue);
1507 latest_issue = latest_issue.max(qe_chain_latest_issue);
1508 earliest_expiration = earliest_expiration.min(qe_chain_earliest_expiration);
1509
1510 let qe_iden_earliest_issue = qe_chain_earliest_issue.min(qe_issue);
1512 let qe_iden_latest_issue = qe_chain_latest_issue.max(qe_issue);
1513 let qe_iden_earliest_expiration = qe_chain_earliest_expiration.min(qe_next);
1514
1515 Ok(CollateralDates {
1516 earliest_issue,
1517 latest_issue,
1518 earliest_expiration,
1519 qe_iden_earliest_issue,
1520 qe_iden_latest_issue,
1521 qe_iden_earliest_expiration,
1522 })
1523}
1524
1525fn validate_sgx_attrs(report: &EnclaveReport, allow_debug: bool) -> Result<()> {
1526 let is_debug = report.attributes[0] & 0x02 != 0;
1527 if is_debug && !allow_debug {
1528 bail!("Debug mode is enabled");
1529 }
1530 Ok(())
1531}
1532
1533fn validate_attrs(report: &Report, allow_service_td: bool, allow_debug: bool) -> Result<()> {
1534 fn validate_td10(report: &TDReport10, allow_debug: bool) -> Result<()> {
1535 let td_attrs =
1536 TDAttributes::parse(report.td_attributes).context("Failed to parse TD attributes")?;
1537 if td_attrs.tud & !0x01 != 0 {
1538 bail!("Reserved bits in TD attributes are set");
1539 }
1540 if td_attrs.tud & 0x01 != 0 && !allow_debug {
1541 bail!("Debug mode is enabled");
1542 }
1543 if td_attrs.sec.reserved_lower != 0
1544 || td_attrs.sec.reserved_bit29
1545 || td_attrs.other.reserved != 0
1546 {
1547 bail!("Reserved bits in TD attributes are set");
1548 }
1549 if !td_attrs.sec.sept_ve_disable {
1550 bail!("SEPT_VE_DISABLE is not enabled");
1551 }
1552 Ok(())
1553 }
1554 fn validate_td15(report: &TDReport15, allow_service_td: bool, allow_debug: bool) -> Result<()> {
1555 if !allow_service_td && report.mr_service_td != [0u8; 48] {
1556 bail!("Invalid MR service TD");
1557 }
1558 validate_td10(&report.base, allow_debug)
1559 }
1560 match &report {
1561 Report::TD15(report) => validate_td15(report, allow_service_td, allow_debug),
1562 Report::TD10(report) => validate_td10(report, allow_debug),
1563 Report::SgxEnclave(report) => validate_sgx_attrs(report, allow_debug),
1564 }
1565}
1566
1567#[cfg(all(feature = "ring", feature = "default-x509"))]
1571pub mod ring {
1572 use super::*;
1573
1574 fn ring_sha256(data: &[u8]) -> [u8; 32] {
1575 let digest = ::ring::digest::digest(&::ring::digest::SHA256, data);
1576 let mut out = [0u8; 32];
1577 out.copy_from_slice(digest.as_ref());
1578 out
1579 }
1580
1581 fn ring_sha384(data: &[u8]) -> [u8; 48] {
1582 let digest = ::ring::digest::digest(&::ring::digest::SHA384, data);
1583 let mut out = [0u8; 48];
1584 out.copy_from_slice(digest.as_ref());
1585 out
1586 }
1587
1588 pub fn backend() -> CryptoBackend {
1590 CryptoBackend {
1591 sig_algo: webpki::ring::ECDSA_P256_SHA256,
1592 sha256: ring_sha256,
1593 sha384: ring_sha384,
1594 encode_ecdsa: encode_as_der_with::<crate::configs::RingConfig>,
1595 parse_pck_extension: crate::intel::parse_pck_extension_with::<crate::configs::RingConfig>,
1596 }
1597 }
1598
1599 pub fn verify(
1600 raw_quote: &[u8],
1601 collateral: &QuoteCollateralV3,
1602 now_secs: u64,
1603 ) -> Result<VerifiedReport> {
1604 QuoteVerifier::new_prod()
1605 .with_config::<crate::configs::RingConfig>()
1606 .verify(raw_quote, collateral, now_secs)
1607 }
1608
1609 #[cfg(feature = "danger-allow-tcb-override")]
1610 pub fn dangerous_verify_with_tcb_override(
1611 raw_quote: &[u8],
1612 collateral: &QuoteCollateralV3,
1613 now_secs: u64,
1614 override_tcb_info: impl FnOnce(TcbInfo) -> TcbInfo,
1615 ) -> Result<VerifiedReport> {
1616 QuoteVerifier::new_prod()
1617 .with_config::<crate::configs::RingConfig>()
1618 .dangerous_verify_with_tcb_override(raw_quote, collateral, now_secs, override_tcb_info)
1619 }
1620}
1621
1622#[cfg(all(feature = "rustcrypto", feature = "default-x509"))]
1626pub mod rustcrypto {
1627 use super::*;
1628
1629 fn rustcrypto_sha256(data: &[u8]) -> [u8; 32] {
1630 use sha2::Digest;
1631 sha2::Sha256::digest(data).into()
1632 }
1633
1634 fn rustcrypto_sha384(data: &[u8]) -> [u8; 48] {
1635 use sha2::Digest;
1636 sha2::Sha384::digest(data).into()
1637 }
1638
1639 pub fn backend() -> CryptoBackend {
1641 CryptoBackend {
1642 sig_algo: webpki::rustcrypto::ECDSA_P256_SHA256,
1643 sha256: rustcrypto_sha256,
1644 sha384: rustcrypto_sha384,
1645 encode_ecdsa: encode_as_der_with::<crate::configs::RustCryptoConfig>,
1646 parse_pck_extension: crate::intel::parse_pck_extension_with::<
1647 crate::configs::RustCryptoConfig,
1648 >,
1649 }
1650 }
1651
1652 pub fn verify(
1653 raw_quote: &[u8],
1654 collateral: &QuoteCollateralV3,
1655 now_secs: u64,
1656 ) -> Result<VerifiedReport> {
1657 QuoteVerifier::new_prod()
1658 .with_config::<crate::configs::RustCryptoConfig>()
1659 .verify(raw_quote, collateral, now_secs)
1660 }
1661
1662 #[cfg(feature = "danger-allow-tcb-override")]
1663 pub fn dangerous_verify_with_tcb_override(
1664 raw_quote: &[u8],
1665 collateral: &QuoteCollateralV3,
1666 now_secs: u64,
1667 override_tcb_info: impl FnOnce(TcbInfo) -> TcbInfo,
1668 ) -> Result<VerifiedReport> {
1669 QuoteVerifier::new_prod()
1670 .with_config::<crate::configs::RustCryptoConfig>()
1671 .dangerous_verify_with_tcb_override(raw_quote, collateral, now_secs, override_tcb_info)
1672 }
1673}
1674
1675fn verify_qe_identity_policy(
1690 qe_report: &EnclaveReport,
1691 qe_identity: &QeIdentity,
1692) -> Result<QeTcbLevel> {
1693 if qe_report.mr_signer != qe_identity.mrsigner {
1695 bail!(
1696 "QE MRSIGNER mismatch: expected {}, got {}",
1697 hex::encode_upper(qe_identity.mrsigner),
1698 hex::encode_upper(qe_report.mr_signer)
1699 );
1700 }
1701
1702 validate_sgx_attrs(qe_report, false).context("QE report validation failed")?;
1703
1704 if qe_report.isv_prod_id != qe_identity.isvprodid {
1706 bail!(
1707 "QE ISVPRODID mismatch: expected {}, got {}",
1708 qe_identity.isvprodid,
1709 qe_report.isv_prod_id
1710 );
1711 }
1712
1713 let expected_miscselect_u32 = u32::from_le_bytes(qe_identity.miscselect);
1715 let miscselect_mask_u32 = u32::from_le_bytes(qe_identity.miscselect_mask);
1716 let qe_miscselect_masked = qe_report.misc_select & miscselect_mask_u32;
1717 let expected_miscselect_masked = expected_miscselect_u32 & miscselect_mask_u32;
1718
1719 if qe_miscselect_masked != expected_miscselect_masked {
1720 bail!(
1721 "QE MISCSELECT mismatch: expected {:08X} (masked), got {:08X} (masked)",
1722 expected_miscselect_masked,
1723 qe_miscselect_masked
1724 );
1725 }
1726
1727 for (i, ((expected, mask), qe_attr)) in qe_identity
1730 .attributes
1731 .iter()
1732 .zip(qe_identity.attributes_mask.iter())
1733 .zip(qe_report.attributes.iter())
1734 .enumerate()
1735 {
1736 let expected_masked = expected & mask;
1737 let qe_masked = qe_attr & mask;
1738 if expected_masked != qe_masked {
1739 bail!(
1740 "QE ATTRIBUTES mismatch at byte {}: expected {:02X} (masked), got {:02X} (masked)",
1741 i,
1742 expected_masked,
1743 qe_masked
1744 );
1745 }
1746 }
1747
1748 match_qe_tcb_level(qe_report.isv_svn, &qe_identity.tcb_levels)
1750}
1751
1752fn match_qe_tcb_level(
1757 isv_svn: u16,
1758 tcb_levels: &[crate::qe_identity::QeTcbLevel],
1759) -> Result<QeTcbLevel> {
1760 for tcb_level in tcb_levels {
1761 if isv_svn >= tcb_level.tcb.isvsvn {
1762 return Ok(tcb_level.clone());
1763 }
1764 }
1765
1766 match tcb_levels.last().map(|l| l.tcb.isvsvn) {
1767 Some(min_required) => {
1768 bail!("QE ISVSVN {isv_svn} is below minimum required {min_required} from QE Identity");
1769 }
1770 None => {
1771 bail!("No TCB levels found in QE Identity");
1772 }
1773 }
1774}
1775
1776#[cfg(test)]
1777#[allow(clippy::unwrap_used)]
1778mod tests {
1779 use super::*;
1780 use crate::tcb_info::TcbStatus::*;
1781 use hex_literal::hex;
1782
1783 fn make_test_qe_report() -> EnclaveReport {
1784 EnclaveReport {
1785 cpu_svn: [0u8; 16],
1786 misc_select: 0x00000000,
1787 reserved1: [0u8; 28],
1788 attributes: [
1789 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1790 0x00, 0x00,
1791 ],
1792 mr_enclave: [0u8; 32],
1793 reserved2: [0u8; 32],
1794 mr_signer: hex::decode(
1795 "8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF",
1796 )
1797 .unwrap()
1798 .try_into()
1799 .unwrap(),
1800 reserved3: [0u8; 96],
1801 isv_prod_id: 1,
1802 isv_svn: 8,
1803 reserved4: [0u8; 60],
1804 report_data: [0u8; 64],
1805 }
1806 }
1807
1808 fn make_test_qe_identity() -> QeIdentity {
1809 use crate::qe_identity::{QeTcb, QeTcbLevel};
1810
1811 QeIdentity {
1812 id: "QE".to_string(),
1813 version: 2,
1814 issue_date: "2025-06-19T10:01:18Z".to_string(),
1815 next_update: "2025-07-19T10:01:18Z".to_string(),
1816 tcb_evaluation_data_number: 17,
1817 miscselect: hex!("00000000"),
1818 miscselect_mask: hex!("FFFFFFFF"),
1819 attributes: hex!("11000000000000000000000000000000"),
1820 attributes_mask: hex!("FBFFFFFFFFFFFFFF0000000000000000"),
1821 mrsigner: hex!("8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF"),
1822 isvprodid: 1,
1823 tcb_levels: vec![
1824 QeTcbLevel {
1825 tcb: QeTcb { isvsvn: 8 },
1826 tcb_date: "2024-03-13T00:00:00Z".to_string(),
1827 tcb_status: UpToDate,
1828 advisory_ids: vec![],
1829 },
1830 QeTcbLevel {
1831 tcb: QeTcb { isvsvn: 6 },
1832 tcb_date: "2021-11-10T00:00:00Z".to_string(),
1833 tcb_status: OutOfDate,
1834 advisory_ids: vec!["INTEL-SA-00615".to_string()],
1835 },
1836 QeTcbLevel {
1837 tcb: QeTcb { isvsvn: 5 },
1838 tcb_date: "2020-11-11T00:00:00Z".to_string(),
1839 tcb_status: OutOfDate,
1840 advisory_ids: vec!["INTEL-SA-00477".to_string(), "INTEL-SA-00615".to_string()],
1841 },
1842 ],
1843 }
1844 }
1845
1846 #[test]
1847 fn test_qe_identity_policy_valid() {
1848 let qe_report = make_test_qe_report();
1849 let qe_identity = make_test_qe_identity();
1850
1851 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1852 assert!(result.is_ok(), "Expected success, got: {:?}", result);
1853 }
1854
1855 #[test]
1856 fn test_qe_identity_policy_mrsigner_mismatch() {
1857 let qe_report = make_test_qe_report();
1858 let mut qe_identity = make_test_qe_identity();
1859 qe_identity.mrsigner =
1861 hex!("0000000000000000000000000000000000000000000000000000000000000000");
1862
1863 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1864 assert!(result.is_err());
1865 let err_msg = result.unwrap_err().to_string();
1866 assert!(
1867 err_msg.contains("MRSIGNER mismatch"),
1868 "Expected MRSIGNER mismatch error, got: {}",
1869 err_msg
1870 );
1871 }
1872
1873 #[test]
1874 fn test_qe_identity_policy_isvprodid_mismatch() {
1875 let qe_report = make_test_qe_report();
1876 let mut qe_identity = make_test_qe_identity();
1877 qe_identity.isvprodid = 999; let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1880 assert!(result.is_err());
1881 let err_msg = result.unwrap_err().to_string();
1882 assert!(
1883 err_msg.contains("ISVPRODID mismatch"),
1884 "Expected ISVPRODID mismatch error, got: {}",
1885 err_msg
1886 );
1887 }
1888
1889 #[test]
1890 fn test_qe_identity_policy_miscselect_mismatch() {
1891 let mut qe_report = make_test_qe_report();
1892 qe_report.misc_select = 0x00000001; let mut qe_identity = make_test_qe_identity();
1894 qe_identity.miscselect = hex!("00000000");
1895 qe_identity.miscselect_mask = hex!("FFFFFFFF"); let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1898 assert!(result.is_err());
1899 let err_msg = result.unwrap_err().to_string();
1900 assert!(
1901 err_msg.contains("MISCSELECT mismatch"),
1902 "Expected MISCSELECT mismatch error, got: {}",
1903 err_msg
1904 );
1905 }
1906
1907 #[test]
1908 fn test_qe_identity_policy_miscselect_masked() {
1909 let mut qe_report = make_test_qe_report();
1910 qe_report.misc_select = 0x000000FF; let mut qe_identity = make_test_qe_identity();
1912 qe_identity.miscselect = hex!("00000000");
1913 qe_identity.miscselect_mask = hex!("00000000"); let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1917 assert!(
1918 result.is_ok(),
1919 "Expected success with zero mask, got: {:?}",
1920 result
1921 );
1922 }
1923
1924 #[test]
1925 fn test_qe_identity_policy_attributes_mismatch() {
1926 let mut qe_report = make_test_qe_report();
1927 qe_report.attributes[0] = 0;
1928 let qe_identity = make_test_qe_identity();
1929
1930 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1931 assert!(result.is_err());
1932 let err_msg = result.unwrap_err().to_string();
1933 assert!(
1934 err_msg.contains("ATTRIBUTES mismatch"),
1935 "Expected ATTRIBUTES mismatch error, got: {}",
1936 err_msg
1937 );
1938 }
1939
1940 #[test]
1941 fn test_qe_identity_policy_attributes_masked() {
1942 let mut qe_report = make_test_qe_report();
1943 qe_report.attributes[8] = 0xFF;
1945 qe_report.attributes[15] = 0xFF;
1946 let qe_identity = make_test_qe_identity();
1947 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1951 assert!(
1952 result.is_ok(),
1953 "Expected success with masked attributes, got: {:?}",
1954 result
1955 );
1956 }
1957
1958 #[test]
1959 fn test_qe_identity_policy_isvsvn_up_to_date() {
1960 let qe_report = make_test_qe_report(); let qe_identity = make_test_qe_identity();
1962
1963 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1964 assert!(result.is_ok());
1965 let tcb_level = result.unwrap();
1966 assert_eq!(tcb_level.tcb_status, UpToDate);
1967 assert!(tcb_level.advisory_ids.is_empty());
1968 }
1969
1970 #[test]
1971 fn test_qe_identity_policy_isvsvn_out_of_date() {
1972 let mut qe_report = make_test_qe_report();
1973 qe_report.isv_svn = 6; let qe_identity = make_test_qe_identity();
1975
1976 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1977 assert!(result.is_ok());
1978 let tcb_level = result.unwrap();
1979 assert_eq!(tcb_level.tcb_status, OutOfDate);
1980 assert_eq!(tcb_level.advisory_ids, vec!["INTEL-SA-00615"]);
1981 }
1982
1983 #[test]
1984 fn test_qe_identity_policy_isvsvn_higher_than_required() {
1985 let mut qe_report = make_test_qe_report();
1986 qe_report.isv_svn = 10; let qe_identity = make_test_qe_identity();
1988
1989 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
1990 assert!(result.is_ok());
1991 let tcb_level = result.unwrap();
1992 assert_eq!(tcb_level.tcb_status, UpToDate); }
1994
1995 #[test]
1996 fn test_qe_identity_policy_isvsvn_too_low() {
1997 let mut qe_report = make_test_qe_report();
1998 qe_report.isv_svn = 4; let qe_identity = make_test_qe_identity();
2000
2001 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
2002 assert!(result.is_err());
2003 let err_msg = result.unwrap_err().to_string();
2004 assert!(
2005 err_msg.contains("ISVSVN") && err_msg.contains("below minimum"),
2006 "Expected ISVSVN below minimum error, got: {}",
2007 err_msg
2008 );
2009 }
2010
2011 #[test]
2012 fn test_qe_identity_policy_isvsvn_between_levels() {
2013 let mut qe_report = make_test_qe_report();
2014 qe_report.isv_svn = 7; let qe_identity = make_test_qe_identity();
2016
2017 let result = verify_qe_identity_policy(&qe_report, &qe_identity);
2018 assert!(result.is_ok());
2019 let tcb_level = result.unwrap();
2020 assert_eq!(tcb_level.tcb_status, OutOfDate);
2022 assert_eq!(tcb_level.advisory_ids, vec!["INTEL-SA-00615"]);
2023 }
2024}