Skip to main content

dcap_qvl/
verify.rs

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
36/// Crypto backend configuration for quote verification.
37///
38/// Holds the signature verification algorithm and SHA-256 implementation
39/// needed by the verification logic. Use [`ring::backend()`] or
40/// [`rustcrypto::backend()`] to obtain a pre-configured instance.
41pub struct CryptoBackend {
42    /// ECDSA P-256 SHA-256 algorithm for certificate and raw signature verification
43    pub sig_algo: &'static dyn rustls_pki_types::SignatureVerificationAlgorithm,
44    /// SHA-256 hash function
45    pub sha256: fn(&[u8]) -> [u8; 32],
46    /// SHA-384 hash function (used for root_key_id computation)
47    pub sha384: fn(&[u8]) -> [u8; 48],
48    /// Raw ECDSA `r || s` to DER encoder.
49    pub encode_ecdsa: fn(&[u8]) -> Result<Vec<u8>>,
50    /// Parse Intel PCK extensions with the configured X.509 backend.
51    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/// Result of cryptographic quote verification, before policy validation.
106///
107/// The enclave report is private — it can only be obtained by passing a [`Policy`]
108/// via [`validate()`](Self::validate).
109///
110/// [`QuoteClaims`] is built lazily via [`claims()`](Self::claims) —
111/// the `verify()` call itself does the minimum work (crypto only).
112#[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    // -- core verification results (always computed) --
122    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    /// Build the full [`QuoteClaims`] from verification intermediates.
137    ///
138    /// Computes the collateral time window from all 8 sources (TCBInfo, QEIdentity,
139    /// 2 CRLs, 4 certificate chains), root_key_id SHA-384, CRL numbers, and tcb_date_tag.
140    #[cfg(feature = "default-x509")]
141    pub fn claims(&self) -> Result<QuoteClaims> {
142        // Parse collateral JSON for time window computation
143        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        // root_key_id: SHA-384 of root CA's raw public key bytes
157        let root_key_id = self.root_key_id;
158
159        // CRL numbers
160        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        // tcb_date_tag
166        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                    // Intel's upstream DCAP Rego policy checks
193                    // `platform_provider_id`, but the upstream QvE producer
194                    // currently leaves it as a TODO when building the platform
195                    // measurement JSON:
196                    // https://github.com/intel/confidential-computing.tee.dcap/blob/main/ae/QvE/qve/qve.cpp
197                    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    /// Convert directly into [`VerifiedReport`] **without applying any policy**.
219    ///
220    /// # Warning
221    /// This skips all policy checks (TCB status, advisory IDs, collateral
222    /// freshness, platform flags). Use only when you handle validation
223    /// externally or intentionally accept any verification result.
224    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
257/// Quote verifier with configurable root certificate and crypto backend.
258///
259/// Provides both the backwards-compatible report API and the detailed claims API.
260pub 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    /// Create a new verifier with a custom root certificate.
270    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    /// Create a new verifier using Intel's production root certificate.
280    pub fn new_prod() -> Self {
281        Self::new(TRUSTED_ROOT_CA_DER.to_vec())
282    }
283}
284
285impl<C: Config> QuoteVerifier<C> {
286    /// Create a verifier for `C` with a custom root certificate.
287    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    /// Select a different compile-time verification backend.
297    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    /// Verify a quote, apply a policy, and return detailed serializable claims.
317    #[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    /// Verify with the one-shot API using this verifier's [`Config`].
353    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    /// Verify a quote with the configured root certificate, passing a TCB info override.
364    ///
365    /// The override function receives `TcbInfo` after signature verification and can
366    /// modify it before TCB level matching. Use with extreme caution.
367    #[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/// Backwards-compatible one-shot verification using [`DefaultConfig`].
428#[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/// Verification policy builder for JS/WASM.
454///
455/// ```js
456/// const policy = new QuotePolicy(now)
457///     .allow_status("OutOfDate")
458///     .platform_grace_period(7n * 86400n)
459///     .allow_smt(true);
460/// ```
461#[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    /// Create a strict policy: only `UpToDate`, no grace period, no advisory blacklist.
487    #[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    /// Create a pass-through policy for downstream appraisal.
495    #[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    /// Allow an additional TCB status (e.g. "OutOfDate", "SWHardeningNeeded").
503    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    /// Reject a specific advisory ID (e.g. "INTEL-SA-00334").
511    pub fn reject_advisory(self, id: &str) -> Self {
512        Self {
513            inner: self.inner.reject_advisory(id),
514        }
515    }
516
517    /// Reject multiple advisory IDs at once.
518    pub fn reject_advisories(self, ids: Vec<String>) -> Self {
519        Self {
520            inner: self.inner.reject_advisories(&ids),
521        }
522    }
523
524    /// Set platform grace period in seconds.
525    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    /// Set QE grace period in seconds.
532    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    /// Set minimum TCB evaluation data number.
539    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    /// Set whether dynamic platforms are allowed.
546    pub fn allow_dynamic_platform(self, allow: bool) -> Self {
547        Self {
548            inner: self.inner.allow_dynamic_platform(allow),
549        }
550    }
551
552    /// Set whether cached keys are allowed.
553    pub fn allow_cached_keys(self, allow: bool) -> Self {
554        Self {
555            inner: self.inner.allow_cached_keys(allow),
556        }
557    }
558
559    /// Set whether SMT (hyperthreading) is allowed.
560    pub fn allow_smt(self, allow: bool) -> Self {
561        Self {
562            inner: self.inner.allow_smt(allow),
563        }
564    }
565
566    /// Set accepted SGX types (e.g. [0, 1, 2]).
567    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/// Quote verifier for JS/WASM.
575///
576/// ```js
577/// const verifier = new QuoteVerifier();          // Intel production root CA
578/// const verifier = new QuoteVerifier(rootCaDer);  // custom root CA
579/// const result = verifier.verify(quote, collateral, now);
580/// ```
581#[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    /// Create a verifier. No argument = Intel production root CA; pass `rootCaDer` for custom.
591    #[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    /// Backwards-compatible one-shot verification returning `VerifiedReport`.
601    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, &quote_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    /// Verify the quote, apply the built-in policy, and return claims.
620    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    /// Fetch collateral from a PCCS server.
640    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
655// =============================================================================
656// Step 1: Verify TCB Info signature (Intel Root -> TCB Signing Cert -> TCB Info JSON)
657// =============================================================================
658
659/// Verify TCB Info collateral: certificate chain, signature, parsing, and expiration check
660fn 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    // Parse TCB Info
668    let tcb_info = serde_json::from_str::<TcbInfo>(&collateral.tcb_info)
669        .context("Failed to decode TcbInfo")?;
670
671    // Check validity window
672    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    // Verify certificate chain
684    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    // Verify signature
693    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
708// =============================================================================
709// Step 2: Verify QE Identity signature (Intel Root -> QE Identity Signing Cert -> QE Identity JSON)
710// =============================================================================
711
712/// Verify QE Identity collateral: certificate chain, signature, parsing, and expiration check
713fn 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    // Parse QE Identity
721    let qe_identity = serde_json::from_str::<QeIdentity>(&collateral.qe_identity)
722        .context("Failed to decode QeIdentity")?;
723
724    // Check validity window
725    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    // Verify certificate chain
737    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    // Verify signature
746    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
761// =============================================================================
762// Step 3: Verify PCK certificate chain (Intel Root -> PCK CA -> PCK Cert)
763// =============================================================================
764
765/// Verify PCK certificate chain and extract platform data
766///
767/// Verifies the PCK certificate chain against the trusted root and CRLs.
768/// Extracts cpu_svn, pce_svn, fmspc, and ppid from the certificate.
769fn 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    // Extract PCK certificate chain - prefer collateral, fall back to quote
778    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    // Verify PCK certificate chain
794    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    // Extract Intel extension data from PCK cert (parsed once)
799    let pck_ext = (backend.parse_pck_extension)(pck_leaf)?;
800
801    // Preserve pce_id as the raw value from the PCK cert SGX extension.
802    let pce_id = pck_ext.pce_id.clone();
803
804    // Convert platform_instance_id to fixed-size array
805    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/// Result from PCK certificate chain verification
830#[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
854// =============================================================================
855// Step 4: Verify QE Report signature (PCK Cert signs QE Report)
856// =============================================================================
857
858/// Verify QE report signature using PCK certificate
859fn 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    // Verify QE report signature (signed by PCK)
868    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    // Decode QE report
877    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
884// =============================================================================
885// Step 5: Verify QE Report content (QE Hash = hash(attestation_key + auth_data))
886// =============================================================================
887
888/// Verify QE report hash matches attestation key and auth data (panic-free)
889fn 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    // Build hash data: attestation_key || qe_auth_data
901    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
911// =============================================================================
912// Step 6: Verify QE Report policy (QE Report fields match QE Identity policy)
913// =============================================================================
914
915// verify_qe_identity_policy is defined below (after verify_impl)
916
917// =============================================================================
918// Step 7: Verify ISV Report signature (Attestation Key signs ISV Report)
919// =============================================================================
920
921/// Verify ISV enclave report signature using attestation key
922fn verify_isv_report_signature(
923    raw_quote: &[u8],
924    quote: &Quote,
925    auth_data: &crate::quote::AuthDataV3,
926    backend: &CryptoBackend,
927) -> Result<()> {
928    // Prepend 0x04 to raw public key for SEC1 uncompressed format
929    let mut pub_key = [0x04u8; 65];
930    pub_key[1..].copy_from_slice(&auth_data.ecdsa_attestation_key);
931
932    // DER-encode the raw r||s signature for SignatureVerificationAlgorithm
933    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
945// =============================================================================
946// Step 8: Match Platform TCB (PCK Cert's CPU_SVN/PCE_SVN/FMSPC vs TCB Info)
947// =============================================================================
948
949/// Match platform TCB level and return the matched TcbLevel
950fn 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    // Verify FMSPC matches
959    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    // Verify TCB Info type matches quote TEE type
967    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    // Find matching TCB level
981    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        // Component-wise comparison: every cpu_svn[i] must be >= sgx_components[i]
996        if cpu_svn.iter().zip(&sgx_components).any(|(a, b)| a < b) {
997            continue;
998        }
999
1000        // For TDX, also check TDX components
1001        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            // Component-wise comparison: every tee_tcb_svn[i] must be >= tdx_components[i]
1016            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    // Helper to decode a hex string into a fixed-size array
1071    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    // Start from the base module values
1085    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    // If a specific module version is indicated and identities are present,
1093    // override expectations from the matching identity entry.
1094    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    // Verify MRSEAM signer (MR_SIGNER_SEAM) matches expected module MRSIGNER.
1120    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    // Verify SEAMATTRIBUTES with mask: masked bits must match, and bits
1129    // outside the mask must be zero (defense in depth).
1130    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 we have module identity TCB levels, derive module status from them.
1155    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    // No identity-specific TCB levels: we've still corroborated module identity,
1178    // but there is no additional status/advisory to merge.
1179    Ok(None)
1180}
1181
1182// =============================================================================
1183// Main verification flow following the trust chain
1184// =============================================================================
1185
1186/// Cryptographic verification of a quote. Returns [`QuoteClaims`] without
1187/// applying any policy — the caller decides acceptance via [`QuoteClaims::validate()`].
1188///
1189/// Trust chain verification order:
1190/// 1. Verify TCB Info signature (Intel Root -> TCB Signing Cert -> TCB Info JSON)
1191/// 2. Verify QE Identity signature (Intel Root -> QE Identity Signing Cert -> QE Identity JSON)
1192/// 3. Verify PCK certificate chain (Intel Root -> PCK CA -> PCK Cert)
1193/// 4. Verify QE Report signature (PCK Cert signs QE Report)
1194/// 5. Verify QE Report content (QE Hash = hash(attestation_key + auth_data))
1195/// 6. Verify QE Report policy (QE Report fields match QE Identity policy)
1196/// 7. Verify ISV Report signature (Attestation Key signs ISV Report)
1197/// 8. Match Platform TCB (PCK Cert's CPU_SVN/PCE_SVN/FMSPC vs TCB Info)
1198/// 9. Match QE TCB (QE Report's ISVSVN vs QE Identity tcb_levels)
1199/// 10. Merge TCB statuses
1200#[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    // Setup trust anchor and time
1214    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    // Check root CA against CRL
1221    webpki::check_single_cert_crl(root_ca_der, &raw_crls, now)?;
1222
1223    // Parse CRLs once for reuse across all certificate chain verifications
1224    let crls = parse_crls(&raw_crls)?;
1225
1226    // Parse quote and validate header
1227    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(&quote.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(&quote.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    // Step 1: Verify TCB Info signature
1254    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    // Step 2: Verify QE Identity signature
1264    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    // Step 3: Verify PCK certificate chain
1281    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    // Step 4: Verify QE Report signature
1292    let qe_report = verify_qe_report_signature(&pck_leaf, &auth_data, backend)?;
1293
1294    // Step 5: Verify QE Report content (hash check)
1295    verify_qe_report_data(&qe_report, &auth_data, backend)?;
1296
1297    // Step 6: Verify QE Report policy (returns matched QeTcbLevel)
1298    let qe_tcb_level = verify_qe_identity_policy(&qe_report, &qe_identity)?;
1299
1300    // Step 7: Verify ISV Report signature
1301    verify_isv_report_signature(raw_quote, &quote, &auth_data, backend)?;
1302
1303    // Step 8: Match Platform TCB (returns matched TcbLevel)
1304    let platform_tcb_level = match_platform_tcb(
1305        &tcb_info,
1306        &quote,
1307        tee_type,
1308        &pck_result.cpu_svn,
1309        pck_result.pce_svn,
1310        &pck_result.fmspc,
1311    )?;
1312
1313    // Step 9 & 10: Merge statuses (take worst)
1314    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    // Revoked means the platform's keys are compromised — reject unconditionally,
1323    // regardless of policy. This is a security invariant, not a policy decision.
1324    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 report attributes (debug mode check, etc.)
1343    validate_attrs(&quote.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/// Collateral time window dates (8 sources + QE Identity subset).
1366#[cfg(feature = "default-x509")]
1367struct CollateralDates {
1368    earliest_issue: u64,
1369    latest_issue: u64,
1370    earliest_expiration: u64,
1371    /// QE Identity-specific dates (sources \[5\] + \[7\] only).
1372    qe_iden_earliest_issue: u64,
1373    qe_iden_latest_issue: u64,
1374    qe_iden_earliest_expiration: u64,
1375}
1376
1377/// Compute the collateral time window: earliest issue, latest issue, earliest expiration.
1378///
1379/// Matches Intel QVL's `qve_get_collateral_dates()` which considers **8 date sources**:
1380///
1381/// 1. Root CA CRL thisUpdate/nextUpdate
1382/// 2. PCK CRL thisUpdate/nextUpdate
1383/// 3. PCK CRL issuer certificate chain notBefore/notAfter
1384/// 4. PCK certificate chain notBefore/notAfter
1385/// 5. TCBInfo issuer certificate chain notBefore/notAfter
1386/// 6. QEIdentity issuer certificate chain notBefore/notAfter
1387/// 7. TCBInfo JSON issueDate/nextUpdate
1388/// 8. QEIdentity JSON issueDate/nextUpdate
1389#[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    /// Extract notBefore/notAfter from a PEM certificate chain and fold into min/max accumulators.
1410    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    // TCBInfo dates (already parsed upstream)
1450    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    // QEIdentity dates (already parsed upstream)
1454    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    // Include CRL dates (sources 1 & 2)
1464    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    // Include certificate chain dates (sources 3-6)
1474    // PCK CRL issuer chain (same PEM as pck_crl_issuer_chain)
1475    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    // PCK certificate chain
1482    fold_der_cert_dates(
1483        pck_cert_chain,
1484        &mut earliest_issue,
1485        &mut latest_issue,
1486        &mut earliest_expiration,
1487    )?;
1488    // TCBInfo issuer chain
1489    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    // QEIdentity issuer chain (source [5]) — also track QE-specific dates
1496    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    // Fold into global window
1506    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    // QE Identity-specific window: min/max of source [5] (issuer chain) + source [7] (JSON)
1511    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/// Ring crypto backend module.
1568///
1569/// Provides a pre-configured [`CryptoBackend`] using ring for ECDSA P-256 and SHA-256.
1570#[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    /// Returns a [`CryptoBackend`] backed by ring.
1589    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/// RustCrypto backend module.
1623///
1624/// Provides a pre-configured [`CryptoBackend`] using RustCrypto (sha2 + p256) for ECDSA P-256 and SHA-256.
1625#[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    /// Returns a [`CryptoBackend`] backed by RustCrypto.
1640    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
1675// =============================================================================
1676// Step 6 & 9: Verify QE Report policy and match QE TCB
1677// =============================================================================
1678
1679/// Verify QE report fields against QE Identity policy constraints.
1680///
1681/// This enforces Intel's QE Identity policy by checking:
1682/// - MRSIGNER matches the expected value from QE Identity
1683/// - ISVPRODID matches the expected value
1684/// - MISCSELECT matches after applying the mask
1685/// - ATTRIBUTES match after applying the mask
1686/// - ISVSVN meets minimum requirement from QE Identity TCB levels (Step 9)
1687///
1688/// Returns the matched QeTcbLevel based on the QE's ISVSVN.
1689fn verify_qe_identity_policy(
1690    qe_report: &EnclaveReport,
1691    qe_identity: &QeIdentity,
1692) -> Result<QeTcbLevel> {
1693    // Verify MRSIGNER
1694    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    // Verify ISVPRODID
1705    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    // Verify MISCSELECT with mask
1714    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    // Verify ATTRIBUTES with mask
1728    // Apply mask and compare byte-by-byte using iterators
1729    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 based on ISVSVN
1749    match_qe_tcb_level(qe_report.isv_svn, &qe_identity.tcb_levels)
1750}
1751
1752/// Match QE ISVSVN against QE Identity TCB levels
1753///
1754/// TCB levels are expected to be sorted from highest to lowest ISVSVN.
1755/// Returns the matched QeTcbLevel.
1756fn 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        // Change expected MRSIGNER to something different
1860        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; // Different product ID
1878
1879        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; // Set a bit
1893        let mut qe_identity = make_test_qe_identity();
1894        qe_identity.miscselect = hex!("00000000");
1895        qe_identity.miscselect_mask = hex!("FFFFFFFF"); // All bits checked
1896
1897        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; // Set some bits
1911        let mut qe_identity = make_test_qe_identity();
1912        qe_identity.miscselect = hex!("00000000");
1913        qe_identity.miscselect_mask = hex!("00000000"); // No bits checked (mask all zeros)
1914
1915        // Should pass because mask is all zeros - no bits are checked
1916        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        // Set bits in the second half (bytes 8-15) which are masked out
1944        qe_report.attributes[8] = 0xFF;
1945        qe_report.attributes[15] = 0xFF;
1946        let qe_identity = make_test_qe_identity();
1947        // Mask is "FBFFFFFFFFFFFFFF0000000000000000" - second half is all zeros
1948
1949        // Should pass because those bytes are masked out
1950        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(); // isv_svn = 8
1961        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; // Lower than 8, matches second TCB level
1974        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; // Higher than highest TCB level (8)
1987        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); // Matches first level (isvsvn >= 8)
1993    }
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; // Lower than all TCB levels (min is 5)
1999        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; // Between level 8 and 6
2015        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        // Should match level with isvsvn=6 (7 >= 6)
2021        assert_eq!(tcb_level.tcb_status, OutOfDate);
2022        assert_eq!(tcb_level.advisory_ids, vec!["INTEL-SA-00615"]);
2023    }
2024}