Skip to main content

dcap_qvl/
quote.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3
4use anyhow::{bail, Context, Result};
5use scale::{Decode, Encode, Input, Output};
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "borsh_schema")]
9use borsh::BorshSchema;
10#[cfg(feature = "borsh")]
11use borsh::{BorshDeserialize, BorshSerialize};
12
13use crate::constants::*;
14
15#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
16#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
17#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
18pub struct Data<T> {
19    pub data: Vec<u8>,
20    _marker: core::marker::PhantomData<T>,
21}
22
23impl<T> Data<T> {
24    pub fn new(data: Vec<u8>) -> Self {
25        Self {
26            data,
27            _marker: core::marker::PhantomData,
28        }
29    }
30}
31
32impl<T> Serialize for Data<T> {
33    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
34        serde_bytes::serialize(&self.data, serializer)
35    }
36}
37
38impl<'de, T> Deserialize<'de> for Data<T> {
39    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
40        let data = serde_bytes::deserialize(deserializer)?;
41        Ok(Data {
42            data,
43            _marker: core::marker::PhantomData,
44        })
45    }
46}
47
48impl<T: Decode + Into<u64>> Decode for Data<T> {
49    fn decode<I: Input>(input: &mut I) -> Result<Self, scale::Error> {
50        const MAX_DATA_LEN: u64 = 1_048_576; // 1 MiB upper bound for variable-length fields
51
52        let len = T::decode(input)?;
53        let len_u64 = len.into();
54        if len_u64 > MAX_DATA_LEN {
55            return Err(scale::Error::from("Data length exceeds maximum"));
56        }
57
58        let mut data = vec![0u8; len_u64 as usize];
59        input.read(&mut data)?;
60        Ok(Data {
61            data,
62            _marker: core::marker::PhantomData,
63        })
64    }
65}
66
67impl Encode for Data<u16> {
68    fn encode_to<O: Output + ?Sized>(&self, output: &mut O) {
69        let len = self.data.len() as u16;
70        len.encode_to(output);
71        output.write(&self.data);
72    }
73}
74
75impl Encode for Data<u32> {
76    fn encode_to<O: Output + ?Sized>(&self, output: &mut O) {
77        let len = self.data.len() as u32;
78        len.encode_to(output);
79        output.write(&self.data);
80    }
81}
82
83#[derive(
84    Decode, Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize,
85)]
86#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
87#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
88pub struct Header {
89    pub version: u16,
90    pub attestation_key_type: u16,
91    pub tee_type: u32,
92    pub qe_svn: u16,
93    pub pce_svn: u16,
94    #[serde(with = "serde_bytes")]
95    pub qe_vendor_id: [u8; 16],
96    #[serde(with = "serde_bytes")]
97    pub user_data: [u8; 20],
98}
99
100impl Header {
101    pub fn is_sgx(&self) -> bool {
102        self.tee_type == TEE_TYPE_SGX
103    }
104}
105
106#[derive(Decode, Encode, Debug)]
107pub struct Body {
108    pub body_type: u16,
109    pub size: u32,
110}
111
112#[derive(
113    Decode, Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize,
114)]
115#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
116#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
117pub struct EnclaveReport {
118    #[serde(with = "serde_bytes")]
119    pub cpu_svn: [u8; 16],
120    pub misc_select: u32,
121    #[serde(with = "serde_bytes")]
122    pub reserved1: [u8; 28],
123    #[serde(with = "serde_bytes")]
124    pub attributes: [u8; 16],
125    #[serde(with = "serde_bytes")]
126    pub mr_enclave: [u8; 32],
127    #[serde(with = "serde_bytes")]
128    pub reserved2: [u8; 32],
129    #[serde(with = "serde_bytes")]
130    pub mr_signer: [u8; 32],
131    #[serde(with = "serde_bytes")]
132    pub reserved3: [u8; 96],
133    pub isv_prod_id: u16,
134    pub isv_svn: u16,
135    #[serde(with = "serde_bytes")]
136    pub reserved4: [u8; 60],
137    #[serde(with = "serde_bytes")]
138    pub report_data: [u8; 64],
139}
140
141/// TD Attributes as defined in Intel TDX Module specification A.3.4
142#[derive(Debug, Clone)]
143pub struct TDAttributes {
144    /// TUD (TD Under Debug) flags (bits 7:0)
145    /// If any of the bits in this group are set to 1, the TD is untrusted.
146    pub tud: u8,
147
148    /// SEC attributes that may impact the security of the TD (bits 31:8)
149    pub sec: SECFlags,
150
151    /// OTHER attributes that do not impact the security of the TD (bits 63:32)
152    pub other: OTHERFlags,
153}
154
155/// TUD (TD Under Debug) flags (bits 7:0)
156#[derive(Debug, Clone)]
157pub struct TUDFlags {
158    /// DEBUG: Defines whether the TD runs in TD debug mode (set to 1) or not (set to 0).
159    /// In TD debug mode, the CPU state and private memory are accessible by the host VMM.
160    pub debug: bool,
161
162    /// Reserved for future TUD flags - must be 0 (bits 7:1)
163    pub reserved: u8,
164}
165
166/// SEC attributes that may impact the security of the TD (bits 31:8)
167#[derive(Debug, Clone)]
168pub struct SECFlags {
169    /// Reserved for future SEC flags - must be 0 (bits 27:8)
170    pub reserved_lower: u32,
171
172    /// SEPT_VE_DISABLE: Disable EPT violation conversion to #VE on TD access of PENDING pages
173    pub sept_ve_disable: bool,
174
175    /// Reserved for future SEC flags - must be 0 (bit 29)
176    pub reserved_bit29: bool,
177
178    /// PKS: TD is allowed to use Supervisor Protection Keys
179    pub pks: bool,
180
181    /// KL: TD is allowed to use Key Locker
182    pub kl: bool,
183}
184
185/// OTHER attributes that do not impact the security of the TD (bits 63:32)
186#[derive(Debug, Clone)]
187pub struct OTHERFlags {
188    /// Reserved for future OTHER flags - must be 0 (bits 62:32)
189    pub reserved: u32,
190
191    /// PERFMON: TD is allowed to use Perfmon and PERF_METRICS capabilities
192    pub perfmon: bool,
193}
194
195impl TDAttributes {
196    pub fn parse(input: [u8; 8]) -> Result<Self, scale::Error> {
197        let tud = input[0];
198        // Extract SEC flags (27:8 bits, bytes 1-3 and part of byte 4)
199        let reserved_lower =
200            (((input[3] & 0x0f) as u32) << 16) | ((input[2] as u32) << 8) | (input[1] as u32);
201        let sept_ve_disable = (input[3] & 0x10) != 0; // Bit 28
202        let reserved_bit29 = (input[3] & 0x20) != 0; // Bit 29
203        let pks = (input[3] & 0x40) != 0; // Bit 30
204        let kl = (input[3] & 0x80) != 0; // Bit 31
205
206        // Extract OTHER flags (bytes 4-7)
207        // Mask bit 7 of input[7] (= PERFMON, bit 63) out of reserved_other.
208        let reserved_other = (((input[7] as u32) & 0x7F) << 24)
209            | ((input[6] as u32) << 16)
210            | ((input[5] as u32) << 8)
211            | (input[4] as u32);
212        let perfmon = (input[7] & 0x80) != 0; // Bit 63
213
214        Ok(TDAttributes {
215            tud,
216            sec: SECFlags {
217                reserved_lower,
218                sept_ve_disable,
219                reserved_bit29,
220                pks,
221                kl,
222            },
223            other: OTHERFlags {
224                reserved: reserved_other,
225                perfmon,
226            },
227        })
228    }
229}
230
231#[derive(
232    Decode, Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize,
233)]
234#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
235#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
236pub struct TDReport10 {
237    #[serde(with = "serde_bytes")]
238    pub tee_tcb_svn: [u8; 16],
239    #[serde(with = "serde_bytes")]
240    pub mr_seam: [u8; 48],
241    #[serde(with = "serde_bytes")]
242    pub mr_signer_seam: [u8; 48],
243    #[serde(with = "serde_bytes")]
244    pub seam_attributes: [u8; 8],
245    #[serde(with = "serde_bytes")]
246    pub td_attributes: [u8; 8],
247    #[serde(with = "serde_bytes")]
248    pub xfam: [u8; 8],
249    #[serde(with = "serde_bytes")]
250    pub mr_td: [u8; 48],
251    #[serde(with = "serde_bytes")]
252    pub mr_config_id: [u8; 48],
253    #[serde(with = "serde_bytes")]
254    pub mr_owner: [u8; 48],
255    #[serde(with = "serde_bytes")]
256    pub mr_owner_config: [u8; 48],
257    #[serde(with = "serde_bytes")]
258    pub rt_mr0: [u8; 48],
259    #[serde(with = "serde_bytes")]
260    pub rt_mr1: [u8; 48],
261    #[serde(with = "serde_bytes")]
262    pub rt_mr2: [u8; 48],
263    #[serde(with = "serde_bytes")]
264    pub rt_mr3: [u8; 48],
265    #[serde(with = "serde_bytes")]
266    pub report_data: [u8; 64],
267}
268
269#[derive(
270    Decode, Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize,
271)]
272#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
273#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
274pub struct TDReport15 {
275    pub base: TDReport10,
276    #[serde(with = "serde_bytes")]
277    pub tee_tcb_svn2: [u8; 16],
278    #[serde(with = "serde_bytes")]
279    pub mr_service_td: [u8; 48],
280}
281
282#[derive(Decode, Encode, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
283#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
284#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
285pub struct CertificationData {
286    pub cert_type: u16,
287    pub body: Data<u32>,
288}
289
290impl core::fmt::Debug for CertificationData {
291    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
292        let body_str = String::from_utf8_lossy(&self.body.data);
293        f.debug_struct("CertificationData")
294            .field("cert_type", &self.cert_type)
295            .field("body", &body_str)
296            .finish()
297    }
298}
299
300#[derive(
301    Decode, Encode, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize,
302)]
303#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
304#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
305pub struct QEReportCertificationData {
306    #[serde(with = "serde_bytes")]
307    pub qe_report: [u8; ENCLAVE_REPORT_BYTE_LEN],
308    #[serde(with = "serde_bytes")]
309    pub qe_report_signature: [u8; QE_REPORT_SIG_BYTE_LEN],
310    pub qe_auth_data: Data<u16>,
311    pub certification_data: CertificationData,
312}
313
314#[derive(
315    Decode, Encode, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize,
316)]
317#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
318#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
319pub struct AuthDataV3 {
320    #[serde(with = "serde_bytes")]
321    pub ecdsa_signature: [u8; ECDSA_SIGNATURE_BYTE_LEN],
322    #[serde(with = "serde_bytes")]
323    pub ecdsa_attestation_key: [u8; ECDSA_PUBKEY_BYTE_LEN],
324    #[serde(with = "serde_bytes")]
325    pub qe_report: [u8; ENCLAVE_REPORT_BYTE_LEN],
326    #[serde(with = "serde_bytes")]
327    pub qe_report_signature: [u8; QE_REPORT_SIG_BYTE_LEN],
328    pub qe_auth_data: Data<u16>,
329    pub certification_data: CertificationData,
330}
331
332#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
333#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
334#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
335pub struct AuthDataV4 {
336    #[serde(with = "serde_bytes")]
337    pub ecdsa_signature: [u8; ECDSA_SIGNATURE_BYTE_LEN],
338    #[serde(with = "serde_bytes")]
339    pub ecdsa_attestation_key: [u8; ECDSA_PUBKEY_BYTE_LEN],
340    pub certification_data: CertificationData,
341    pub qe_report_data: QEReportCertificationData,
342}
343
344impl AuthDataV4 {
345    pub fn into_v3(self) -> AuthDataV3 {
346        AuthDataV3 {
347            ecdsa_signature: self.ecdsa_signature,
348            ecdsa_attestation_key: self.ecdsa_attestation_key,
349            qe_report: self.qe_report_data.qe_report,
350            qe_report_signature: self.qe_report_data.qe_report_signature,
351            qe_auth_data: self.qe_report_data.qe_auth_data,
352            certification_data: self.qe_report_data.certification_data,
353        }
354    }
355}
356
357impl Decode for AuthDataV4 {
358    fn decode<I: Input>(input: &mut I) -> Result<Self, scale::Error> {
359        let ecdsa_signature = Decode::decode(input)?;
360        let ecdsa_attestation_key = Decode::decode(input)?;
361        let certification_data: CertificationData = Decode::decode(input)?;
362        let qe_report_data =
363            QEReportCertificationData::decode(&mut &certification_data.body.data[..])?;
364        Ok(AuthDataV4 {
365            ecdsa_signature,
366            ecdsa_attestation_key,
367            certification_data,
368            qe_report_data,
369        })
370    }
371}
372
373impl Encode for AuthDataV4 {
374    fn encode_to<O: Output + ?Sized>(&self, output: &mut O) {
375        self.ecdsa_signature.encode_to(output);
376        self.ecdsa_attestation_key.encode_to(output);
377
378        // Encode qe_report_data into certification_data body
379        let mut qe_data_bytes = Vec::new();
380        self.qe_report_data.encode_to(&mut qe_data_bytes);
381
382        let cert_data = CertificationData {
383            cert_type: self.certification_data.cert_type,
384            body: Data {
385                data: qe_data_bytes,
386                _marker: core::marker::PhantomData,
387            },
388        };
389        cert_data.encode_to(output);
390    }
391}
392
393#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
394#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
395pub enum AuthData {
396    V3(AuthDataV3),
397    V4(AuthDataV4),
398}
399
400// Manual implementation of BorshSchema for AuthData to work around
401// the derive bug described in https://github.com/near/borsh-rs/issues/355
402#[cfg(feature = "borsh_schema")]
403impl borsh::BorshSchema for AuthData {
404    fn declaration() -> borsh::schema::Declaration {
405        "AuthData".to_string()
406    }
407
408    fn add_definitions_recursively(
409        definitions: &mut borsh::__private::maybestd::collections::BTreeMap<
410            borsh::schema::Declaration,
411            borsh::schema::Definition,
412        >,
413    ) {
414        let definition = borsh::schema::Definition::Enum {
415            tag_width: 1,
416            variants: vec![
417                (0, "V3".to_string(), AuthDataV3::declaration()),
418                (1, "V4".to_string(), AuthDataV4::declaration()),
419            ],
420        };
421
422        borsh::schema::add_definition(Self::declaration(), definition, definitions);
423
424        AuthDataV3::add_definitions_recursively(definitions);
425        AuthDataV4::add_definitions_recursively(definitions);
426    }
427}
428
429impl AuthData {
430    pub fn into_v3(self) -> AuthDataV3 {
431        match self {
432            AuthData::V3(data) => data,
433            AuthData::V4(data) => data.into_v3(),
434        }
435    }
436}
437
438impl Encode for AuthData {
439    fn encode_to<O: Output + ?Sized>(&self, output: &mut O) {
440        match self {
441            AuthData::V3(data) => data.encode_to(output),
442            AuthData::V4(data) => data.encode_to(output),
443        }
444    }
445}
446
447fn decode_auth_data(ver: u16, input: &mut &[u8]) -> Result<AuthData, scale::Error> {
448    match ver {
449        3 => {
450            let auth_data = AuthDataV3::decode(input)?;
451            Ok(AuthData::V3(auth_data))
452        }
453        4 => {
454            let auth_data = AuthDataV4::decode(input)?;
455            Ok(AuthData::V4(auth_data))
456        }
457        _ => Err(scale::Error::from("Unsupported auth data version")),
458    }
459}
460
461#[derive(Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
462#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
463#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
464pub enum Report {
465    SgxEnclave(EnclaveReport),
466    TD10(TDReport10),
467    TD15(TDReport15),
468}
469
470impl Report {
471    pub fn is_sgx(&self) -> bool {
472        matches!(self, Report::SgxEnclave(_))
473    }
474
475    pub fn as_td10(&self) -> Option<&TDReport10> {
476        match self {
477            Report::TD10(report) => Some(report),
478            Report::TD15(report) => Some(&report.base),
479            _ => None,
480        }
481    }
482
483    pub fn as_td15(&self) -> Option<&TDReport15> {
484        match self {
485            Report::TD15(report) => Some(report),
486            _ => None,
487        }
488    }
489
490    pub fn as_sgx(&self) -> Option<&EnclaveReport> {
491        match self {
492            Report::SgxEnclave(report) => Some(report),
493            _ => None,
494        }
495    }
496}
497
498#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
499#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
500#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
501pub struct Quote {
502    pub header: Header,
503    pub report: Report,
504    pub auth_data: AuthData,
505}
506
507impl Decode for Quote {
508    fn decode<I: Input>(input: &mut I) -> Result<Self, scale::Error> {
509        let header = Header::decode(input)?;
510        let report;
511        match header.version {
512            3 => {
513                if header.tee_type != TEE_TYPE_SGX {
514                    return Err(scale::Error::from("Invalid TEE type"));
515                }
516                report = Report::SgxEnclave(EnclaveReport::decode(input)?);
517            }
518            4 => match header.tee_type {
519                TEE_TYPE_SGX => {
520                    report = Report::SgxEnclave(EnclaveReport::decode(input)?);
521                }
522                TEE_TYPE_TDX => {
523                    report = Report::TD10(TDReport10::decode(input)?);
524                }
525                _ => return Err(scale::Error::from("Invalid TEE type")),
526            },
527            5 => {
528                let body = Body::decode(input)?;
529                match body.body_type {
530                    BODY_SGX_ENCLAVE_REPORT_TYPE => {
531                        report = Report::SgxEnclave(EnclaveReport::decode(input)?);
532                    }
533                    BODY_TD_REPORT10_TYPE => {
534                        report = Report::TD10(TDReport10::decode(input)?);
535                    }
536                    BODY_TD_REPORT15_TYPE => {
537                        report = Report::TD15(TDReport15::decode(input)?);
538                    }
539                    _ => return Err(scale::Error::from("Unsupported body type")),
540                }
541            }
542            _ => return Err(scale::Error::from("Unsupported quote version")),
543        }
544        let data = Data::<u32>::decode(input)?;
545        // Quote v5 uses v4 auth data format
546        let auth_version = if header.version == 5 {
547            4
548        } else {
549            header.version
550        };
551        let auth_data = decode_auth_data(auth_version, &mut &data.data[..])?;
552        Ok(Quote {
553            header,
554            report,
555            auth_data,
556        })
557    }
558}
559
560impl Encode for Quote {
561    fn encode_to<O: Output + ?Sized>(&self, output: &mut O) {
562        // Encode header
563        self.header.encode_to(output);
564
565        // Encode body for version 5
566        if self.header.version == 5 {
567            let body = match &self.report {
568                Report::SgxEnclave(_) => Body {
569                    body_type: BODY_SGX_ENCLAVE_REPORT_TYPE,
570                    size: ENCLAVE_REPORT_BYTE_LEN as u32,
571                },
572                Report::TD10(_) => Body {
573                    body_type: BODY_TD_REPORT10_TYPE,
574                    size: TD_REPORT10_BYTE_LEN as u32,
575                },
576                Report::TD15(_) => Body {
577                    body_type: BODY_TD_REPORT15_TYPE,
578                    size: TD_REPORT15_BYTE_LEN as u32,
579                },
580            };
581            body.encode_to(output);
582        }
583
584        // Encode report
585        match &self.report {
586            Report::SgxEnclave(report) => report.encode_to(output),
587            Report::TD10(report) => report.encode_to(output),
588            Report::TD15(report) => report.encode_to(output),
589        }
590
591        // Encode auth data with length prefix
592        let mut auth_data_bytes = Vec::new();
593        self.auth_data.encode_to(&mut auth_data_bytes);
594        let auth_data_len = auth_data_bytes.len() as u32;
595        auth_data_len.encode_to(output);
596        output.write(&auth_data_bytes);
597    }
598}
599
600impl Quote {
601    /// Parse a TEE quote from a byte slice.
602    pub fn parse(quote: &[u8]) -> Result<Self> {
603        let mut input = quote;
604        let quote = Quote::decode(&mut input)?;
605        Ok(quote)
606    }
607
608    /// Get the raw certificate chain from the quote.
609    pub fn raw_cert_chain(&self) -> Result<&[u8]> {
610        let cert_data = match &self.auth_data {
611            AuthData::V3(data) => &data.certification_data,
612            AuthData::V4(data) => &data.qe_report_data.certification_data,
613        };
614        if cert_data.cert_type != 5 {
615            bail!("Unsupported cert type: {}", cert_data.cert_type);
616        }
617        Ok(&cert_data.body.data)
618    }
619
620    /// Get the length of signed data in the quote.
621    pub fn signed_length(&self) -> usize {
622        let mut len = match self.report {
623            Report::SgxEnclave(_) => HEADER_BYTE_LEN + ENCLAVE_REPORT_BYTE_LEN,
624            Report::TD10(_) => HEADER_BYTE_LEN + TD_REPORT10_BYTE_LEN,
625            Report::TD15(_) => HEADER_BYTE_LEN + TD_REPORT15_BYTE_LEN,
626        };
627        #[allow(clippy::arithmetic_side_effects)]
628        if self.header.version == 5 {
629            len += BODY_BYTE_SIZE;
630        }
631        len
632    }
633
634    /// Get the inner certification data type.
635    /// For V3 quotes: returns the cert_type directly.
636    /// For V4 quotes with cert_type 6: returns the inner cert_type from qe_report_data.
637    pub fn inner_cert_type(&self) -> u16 {
638        match &self.auth_data {
639            AuthData::V3(data) => data.certification_data.cert_type,
640            AuthData::V4(data) => data.qe_report_data.certification_data.cert_type,
641        }
642    }
643
644    /// Get the inner certification data body.
645    pub fn inner_cert_data(&self) -> &[u8] {
646        match &self.auth_data {
647            AuthData::V3(data) => &data.certification_data.body.data,
648            AuthData::V4(data) => &data.qe_report_data.certification_data.body.data,
649        }
650    }
651
652    /// Get the QE report bytes.
653    pub fn qe_report(&self) -> &[u8; ENCLAVE_REPORT_BYTE_LEN] {
654        match &self.auth_data {
655            AuthData::V3(data) => &data.qe_report,
656            AuthData::V4(data) => &data.qe_report_data.qe_report,
657        }
658    }
659
660    /// Get the QE ID from the quote header.
661    pub fn qeid(&self) -> &[u8] {
662        &self.header.user_data[..16]
663    }
664
665    /// For cert_type 3 (encrypted PPID), extract the parameters needed to fetch PCK certificate.
666    /// Returns (encrypted_ppid, cpusvn, pcesvn, pceid).
667    pub fn encrypted_ppid_params(&self) -> Result<EncryptedPpidParams> {
668        // The cert body for encrypted PPID contains:
669        // - encrypted_ppid (variable length: 256 bytes for RSA-2048, 384 bytes for RSA-3072)
670        // - cpusvn (16 bytes)
671        // - pcesvn (2 bytes, little endian)
672        // - pceid (2 bytes, little endian)
673        // Total trailer: 20 bytes
674        #[derive(Decode)]
675        struct EncPpidDecoder<const N: usize> {
676            encrypted_ppid: [u8; N],
677            cpusvn: CpuSvn,
678            pcesvn: Svn,
679            pceid: [u8; 2],
680        }
681        impl<const N: usize> EncPpidDecoder<N> {
682            fn into_params(self) -> EncryptedPpidParams {
683                EncryptedPpidParams {
684                    encrypted_ppid: self.encrypted_ppid.to_vec(),
685                    cpusvn: self.cpusvn,
686                    pcesvn: self.pcesvn,
687                    pceid: self.pceid,
688                }
689            }
690        }
691
692        let mut cert_body = self.inner_cert_data();
693        let params = match self.inner_cert_type() {
694            PCK_ID_ENCRYPTED_PPID_2048 => EncPpidDecoder::<256>::decode(&mut cert_body)
695                .context("Failed to decode ENCRYPTED_PPID_2048")?
696                .into_params(),
697            PCK_ID_ENCRYPTED_PPID_3072 => EncPpidDecoder::<384>::decode(&mut cert_body)
698                .context("Failed to decode ENCRYPTED_PPID_3072")?
699                .into_params(),
700            other => bail!("encrypted_ppid_params() requires cert_type 2 or 3, got {other}"),
701        };
702        Ok(params)
703    }
704}
705
706/// Parameters extracted from a quote with cert_type 2/3 (encrypted PPID).
707/// Used to fetch PCK certificate from PCCS.
708#[derive(Debug, Clone)]
709pub struct EncryptedPpidParams {
710    /// The encrypted PPID (256 bytes for RSA-2048, 384 bytes for RSA-3072).
711    pub encrypted_ppid: Vec<u8>,
712    /// CPU SVN from certification data trailer (16 bytes).
713    pub cpusvn: [u8; 16],
714    /// PCE SVN from certification data trailer.
715    pub pcesvn: u16,
716    /// PCE ID from certification data trailer (2 bytes).
717    pub pceid: [u8; 2],
718}