tdx_quote/
lib.rs

1//! Parses and verifies Intel TDX quotes (v4 and v5)
2//!
3//! This crate is `no_std`.
4//!
5//! This is inspired by [tdx-quote-parser](https://github.com/MoeMahhouk/tdx-quote-parser) for the types
6//! and [sgx-quote](https://docs.rs/sgx-quote) for the no-std parsing using [nom](https://docs.rs/nom).
7//!
8//! This is based on the specification described in the [Intel TDX DCAP Quoting Library API](https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf),
9//! appendix 3.
10//!
11//! The `mock` feature flag allows generating mock quotes, which this library can parse and verify. This
12//! is used for testing attestation features on without needing TDX hardware.
13//!
14//! The `pck` feature flag (enabled by default) allows parsing and verifying PCK certificate chains.
15//!
16//! Warning: This is in early stages of development and has not been audited.
17//!
18//! For quote generation, see [`configfs-tsm`](https://crates.io/crates/configfs-tsm).
19#![no_std]
20mod error;
21#[cfg(feature = "mock")]
22mod mock;
23mod take_n;
24
25#[cfg(feature = "pck")]
26pub mod pck;
27
28pub use error::{QuoteParseError, QuoteVerificationError, VerifyingKeyError};
29use p256::EncodedPoint;
30use take_n::{take16, take2, take20, take384, take48, take64, take8};
31
32extern crate alloc;
33use alloc::{boxed::Box, vec::Vec};
34
35use nom::{
36    bytes::complete::take,
37    combinator::{map, map_res},
38    number::complete::{le_i16, le_i32, le_u16, le_u32},
39    sequence::tuple,
40    IResult,
41};
42#[cfg(feature = "mock")]
43pub use p256::ecdsa::SigningKey;
44pub use p256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
45use sha2::{Digest, Sha256};
46
47const QUOTE_HEADER_LENGTH: usize = 48;
48const V4_QUOTE_BODY_LENGTH: usize = 584;
49const V5_QUOTE_BODY_LENGTH: usize = V4_QUOTE_BODY_LENGTH + 64;
50
51/// A TDX Quote
52#[derive(Debug, Eq, PartialEq)]
53pub struct Quote {
54    pub header: QuoteHeader,
55    pub body: QuoteBody,
56    pub signature: Signature,
57    pub attestation_key: VerifyingKey,
58    pub certification_data: CertificationData,
59}
60
61impl Quote {
62    /// Parse and validate a TDX quote
63    pub fn from_bytes(original_input: &[u8]) -> Result<Self, QuoteParseError> {
64        // Parse header
65        let (input, header) = quote_header_parser(original_input)?;
66        if header.attestation_key_type != AttestionKeyType::ECDSA256WithP256 {
67            return Err(QuoteParseError::UnsupportedAttestationKeyType);
68        };
69        let body_length = match header.version {
70            4 => V4_QUOTE_BODY_LENGTH,
71            5 => V5_QUOTE_BODY_LENGTH,
72            _ => return Err(QuoteParseError::UnknownQuoteVersion),
73        };
74
75        // Get signed data
76        let signed_data = &original_input[..QUOTE_HEADER_LENGTH + body_length];
77
78        // Parse body
79        let (input, body) = body_parser(input, header.version)?;
80
81        // Signature
82        let (input, _signature_section_length) = le_i32(input)?;
83        let (input, signature) = take(64u8)(input)?;
84        let signature = Signature::from_bytes(signature.into())?;
85
86        // Attestation key
87        let (input, attestation_key) = take(64u8)(input)?;
88        let attestation_key_bytes = attestation_key;
89        let attestation_key = [&[4], attestation_key].concat(); // 0x04 means uncompressed
90        let attestation_key = VerifyingKey::from_sec1_bytes(&attestation_key)?;
91
92        // Verify signature
93        attestation_key.verify(signed_data, &signature)?;
94
95        // Certification data
96        let (input, certification_data_type) = le_i16(input)?;
97        let (input, certification_dat_len) = le_i32(input)?;
98        let certification_dat_len: usize = certification_dat_len.try_into()?;
99        let (_input, certification_data) = take(certification_dat_len)(input)?;
100        let certification_data = CertificationData::new(
101            certification_data_type,
102            certification_data.to_vec(),
103            attestation_key_bytes.to_vec(),
104        )?;
105
106        Ok(Quote {
107            header,
108            body,
109            signature,
110            attestation_key,
111            certification_data,
112        })
113    }
114
115    /// Returns the report data
116    pub fn report_input_data(&self) -> [u8; 64] {
117        self.body.reportdata
118    }
119
120    /// Returns the build-time measurement register
121    pub fn mrtd(&self) -> [u8; 48] {
122        self.body.mrtd
123    }
124
125    /// Returns run-time measurement register 0
126    pub fn rtmr0(&self) -> [u8; 48] {
127        self.body.rtmr0
128    }
129
130    /// Returns run-time measurement register 1
131    pub fn rtmr1(&self) -> [u8; 48] {
132        self.body.rtmr1
133    }
134
135    /// Returns run-time measurement register 2
136    pub fn rtmr2(&self) -> [u8; 48] {
137        self.body.rtmr2
138    }
139
140    /// Returns run-time measurement register 3
141    pub fn rtmr3(&self) -> [u8; 48] {
142        self.body.rtmr3
143    }
144
145    /// Returns the QeReportCertificationData if present
146    pub fn qe_report_certification_data(&self) -> Option<QeReportCertificationData> {
147        if let CertificationData::QeReportCertificationData(qe_report_certification_data) =
148            &self.certification_data
149        {
150            Some(*qe_report_certification_data.clone())
151        } else {
152            None
153        }
154    }
155
156    /// Attempt to verify the report with a given provisioning certification key (PCK)
157    pub fn verify_with_pck(&self, pck: &VerifyingKey) -> Result<(), QuoteVerificationError> {
158        let qe_report_certification_data = self
159            .qe_report_certification_data()
160            .ok_or(QuoteVerificationError::NoQeReportCertificationData)?;
161        pck.verify(
162            &qe_report_certification_data.qe_report[..],
163            &qe_report_certification_data.signature,
164        )?;
165        Ok(())
166    }
167
168    /// Return the pem-encoded PCK cert chain if present
169    /// Parsing the certificates themselves is outside of the scope of this crate
170    pub fn pck_cert_chain(&self) -> Result<Vec<u8>, QuoteVerificationError> {
171        match &self.certification_data {
172            CertificationData::PckCertChain(cert_chain) => Ok(cert_chain.clone()),
173            CertificationData::QeReportCertificationData(qe_report_certification_data) => {
174                if let CertificationDataInner::PckCertChain(cert_chain) =
175                    &qe_report_certification_data.certification_data
176                {
177                    Ok(cert_chain.clone())
178                } else {
179                    Err(QuoteVerificationError::NoPckCertChain)
180                }
181            }
182            _ => Err(QuoteVerificationError::NoPckCertChain),
183        }
184    }
185
186    /// Verify the quote using the embedded PCK certificate chain, and if successful return the PCK
187    #[cfg(feature = "pck")]
188    pub fn verify(&self) -> Result<VerifyingKey, QuoteVerificationError> {
189        let cert_chain = self.pck_cert_chain()?;
190        let pck = pck::verify_pck_certificate_chain_pem(cert_chain)?;
191
192        self.verify_with_pck(&pck)?;
193        Ok(pck)
194    }
195}
196
197/// Type of TEE used
198#[derive(Debug, Eq, PartialEq, Clone)]
199pub enum TEEType {
200    SGX = 0x00000000,
201    TDX = 0x00000081,
202}
203
204impl TryFrom<u32> for TEEType {
205    type Error = nom::Err<u32>;
206
207    fn try_from(value: u32) -> Result<Self, Self::Error> {
208        match value {
209            0x00000000 => Ok(TEEType::SGX),
210            0x00000081 => Ok(TEEType::TDX),
211            _ => Err(nom::Err::Failure(value)),
212        }
213    }
214}
215
216/// Type of the Attestation Key used by the Quoting Enclave
217#[non_exhaustive]
218#[derive(Debug, Eq, PartialEq, Clone)]
219pub enum AttestionKeyType {
220    ECDSA256WithP256 = 2,
221    /// Not yet supported by TDX
222    ECDSA384WithP384 = 3,
223}
224
225impl TryFrom<u16> for AttestionKeyType {
226    type Error = nom::Err<u32>;
227
228    fn try_from(value: u16) -> Result<Self, Self::Error> {
229        match value {
230            2 => Ok(Self::ECDSA256WithP256),
231            3 => Ok(Self::ECDSA384WithP384),
232            _ => Err(nom::Err::Failure(value as u32)),
233        }
234    }
235}
236
237/// A TDX quote header
238#[derive(Debug, Eq, PartialEq)]
239pub struct QuoteHeader {
240    /// Quote version (4 or 5)
241    pub version: u16,
242    /// Type of the Attestation Key used by the Quoting Enclave
243    pub attestation_key_type: AttestionKeyType,
244    /// Type of TEE used
245    pub tee_type: TEEType,
246    /// Currently unused
247    pub reserved1: [u8; 2],
248    /// Currently unused
249    pub reserved2: [u8; 2],
250    /// UUID for the quoting enclave vendor
251    pub qe_vendor_id: [u8; 16], // Could use Uuid crate
252    pub user_data: [u8; 20],
253}
254
255/// Version of TDX used to create the quote
256#[derive(Debug, Eq, PartialEq, Clone)]
257pub enum TDXVersion {
258    /// TDX v1
259    One,
260    /// TDX v1.5
261    OnePointFive,
262}
263
264impl TryFrom<u16> for TDXVersion {
265    type Error = QuoteParseError;
266
267    fn try_from(value: u16) -> Result<Self, Self::Error> {
268        match value {
269            2 => Ok(TDXVersion::One),
270            3 => Ok(TDXVersion::OnePointFive),
271            _ => Err(QuoteParseError::Parse),
272        }
273    }
274}
275
276/// A TDX quote body
277#[derive(Debug, Clone, Eq, PartialEq)]
278pub struct QuoteBody {
279    pub tdx_version: TDXVersion,
280    pub tee_tcb_svn: [u8; 16],
281    pub mrseam: [u8; 48],
282    pub mrsignerseam: [u8; 48],
283    pub seamattributes: [u8; 8],
284    pub tdattributes: [u8; 8],
285    pub xfam: [u8; 8],
286    /// Build-time measurement
287    pub mrtd: [u8; 48],
288    pub mrconfigid: [u8; 48],
289    pub mrowner: [u8; 48],
290    pub mrownerconfig: [u8; 48],
291    /// Runtime extendable measurement register
292    pub rtmr0: [u8; 48],
293    pub rtmr1: [u8; 48],
294    pub rtmr2: [u8; 48],
295    pub rtmr3: [u8; 48],
296    /// User defined input data
297    pub reportdata: [u8; 64],
298    /// Optional as only for TDX 1.5
299    pub tee_tcb_svn_2: Option<[u8; 16]>,
300    /// Optional as only for TDX 1.5
301    pub mrservicetd: Option<[u8; 48]>,
302}
303
304/// Data related to certifying the QE Report
305#[non_exhaustive]
306#[derive(Debug, PartialEq, Eq)]
307#[repr(i16)]
308pub enum CertificationData {
309    PckIdPpidPlainCpusvnPcesvn(Vec<u8>) = 1,
310    PckIdPpidRSA2048CpusvnPcesvn(Vec<u8>) = 2,
311    PckIdPpidRSA3072CpusvnPcesvn(Vec<u8>) = 3,
312    PckLeafCert(Vec<u8>) = 4,
313    PckCertChain(Vec<u8>) = 5,
314    QeReportCertificationData(Box<QeReportCertificationData>) = 6,
315    PlatformManifest(Vec<u8>) = 7,
316}
317
318impl CertificationData {
319    pub fn new(
320        certification_data_type: i16,
321        data: Vec<u8>,
322        attestation_key: Vec<u8>,
323    ) -> Result<Self, QuoteParseError> {
324        match certification_data_type {
325            1 => Ok(Self::PckIdPpidPlainCpusvnPcesvn(data)),
326            2 => Ok(Self::PckIdPpidRSA2048CpusvnPcesvn(data)),
327            3 => Ok(Self::PckIdPpidRSA3072CpusvnPcesvn(data)),
328            4 => Ok(Self::PckLeafCert(data)),
329            5 => Ok(Self::PckCertChain(data)),
330            6 => Ok(Self::QeReportCertificationData(Box::new(
331                QeReportCertificationData::new(data, attestation_key)?,
332            ))),
333            7 => Ok(Self::PlatformManifest(data)),
334            _ => Err(QuoteParseError::UnknownCertificationDataType),
335        }
336    }
337}
338
339/// Inner Data related to certifying the QE report
340/// This is needed to avoid a recursive type
341#[non_exhaustive]
342#[derive(Debug, PartialEq, Eq, Clone)]
343#[repr(i16)]
344pub enum CertificationDataInner {
345    PckIdPpidPlainCpusvnPcesvn(Vec<u8>) = 1,
346    PckIdPpidRSA2048CpusvnPcesvn(Vec<u8>) = 2,
347    PckIdPpidRSA3072CpusvnPcesvn(Vec<u8>) = 3,
348    PckLeafCert(Vec<u8>) = 4,
349    PckCertChain(Vec<u8>) = 5,
350    PlatformManifest(Vec<u8>) = 7,
351}
352
353impl CertificationDataInner {
354    pub fn new(certification_data_type: i16, data: Vec<u8>) -> Result<Self, QuoteParseError> {
355        match certification_data_type {
356            1 => Ok(Self::PckIdPpidPlainCpusvnPcesvn(data)),
357            2 => Ok(Self::PckIdPpidRSA2048CpusvnPcesvn(data)),
358            3 => Ok(Self::PckIdPpidRSA3072CpusvnPcesvn(data)),
359            4 => Ok(Self::PckLeafCert(data)),
360            5 => Ok(Self::PckCertChain(data)),
361            // No 6 because that would be a recursive type
362            7 => Ok(Self::PlatformManifest(data)),
363            _ => Err(QuoteParseError::UnknownCertificationDataType),
364        }
365    }
366}
367
368/// Certification data which contains a signature from the PCK
369#[derive(Debug, PartialEq, Eq, Clone)]
370pub struct QeReportCertificationData {
371    /// This should contain SHA256(attestation_public_key || QE authentication data) || 32 null from_bytes
372    pub qe_report: [u8; 384],
373    /// Signature of the qe_report field made using the PCK key
374    pub signature: Signature,
375    /// Authentication data used by the quoting enclave to provide additional context
376    pub qe_authentication_data: Vec<u8>,
377    /// Data required to verify the QE report signature
378    pub certification_data: CertificationDataInner,
379}
380
381impl QeReportCertificationData {
382    /// Parse QeReportCertificationData from given input, checking the hash contains the given
383    /// attestation key
384    fn new(input: Vec<u8>, attestation_key: Vec<u8>) -> Result<Self, QuoteParseError> {
385        let (input, qe_report) = take384(&input)?;
386        // The last part of the qe_report is the hash of the attestation key and authentication
387        // data, followed by 32 null bytes (which we ignore)
388        let expected_hash = &qe_report[384 - 64..384 - 32];
389
390        let (input, signature) = take64(input)?;
391        let signature = Signature::from_bytes((&signature).into())?;
392        let (input, qe_authentication_data_size) = le_i16(input)?;
393        let qe_authentication_data_size: usize = qe_authentication_data_size.try_into()?;
394        let (input, qe_authentication_data) = take(qe_authentication_data_size)(input)?;
395
396        // Certification Data
397        let (input, certification_data_type) = le_i16(input)?;
398        let (input, certification_dat_len) = le_i32(input)?;
399        let certification_dat_len: usize = certification_dat_len.try_into()?;
400        let (_input, certification_data) = take(certification_dat_len)(input)?;
401        let certification_data =
402            CertificationDataInner::new(certification_data_type, certification_data.to_vec())?;
403
404        // Check the hash in the qe_report
405        let hash = {
406            let mut hasher = Sha256::new();
407            hasher.update(&attestation_key);
408            hasher.update(qe_authentication_data);
409            hasher.finalize()
410        };
411        if hash[..] != *expected_hash {
412            return Err(QuoteParseError::AttestationKeyDoesNotMatch);
413        }
414
415        Ok(Self {
416            qe_report,
417            signature,
418            qe_authentication_data: qe_authentication_data.to_vec(),
419            certification_data,
420        })
421    }
422}
423
424/// Helper function to encode a public key as bytes
425pub fn encode_verifying_key(input: &VerifyingKey) -> Result<[u8; 33], VerifyingKeyError> {
426    input
427        .to_encoded_point(true)
428        .as_bytes()
429        .try_into()
430        .map_err(|_| VerifyingKeyError::BadSize)
431}
432
433/// Helper function to decode bytes to a public key
434pub fn decode_verifying_key(
435    verifying_key_encoded: &[u8; 33],
436) -> Result<VerifyingKey, VerifyingKeyError> {
437    let point = EncodedPoint::from_bytes(verifying_key_encoded)
438        .map_err(|_| VerifyingKeyError::DecodeEncodedPoint)?;
439    VerifyingKey::from_encoded_point(&point)
440        .map_err(|_| VerifyingKeyError::EncodedPointToVerifyingKey)
441}
442
443/// Parser for a quote header
444fn quote_header_parser(input: &[u8]) -> IResult<&[u8], QuoteHeader> {
445    map_res(
446        tuple((le_u16, le_u16, le_u32, take2, take2, take16, take20)),
447        |(
448            version,
449            attestation_key_type,
450            tee_type,
451            reserved1,
452            reserved2,
453            qe_vendor_id,
454            user_data,
455        )| {
456            Ok::<QuoteHeader, nom::Err<u32>>(QuoteHeader {
457                version,
458                attestation_key_type: attestation_key_type.try_into()?,
459                tee_type: tee_type.try_into()?,
460                reserved1,
461                reserved2,
462                qe_vendor_id,
463                user_data,
464            })
465        },
466    )(input)
467}
468
469/// Parser for a quote body
470fn body_parser(input: &[u8], version: u16) -> IResult<&[u8], QuoteBody> {
471    let (input, tdx_version) = match version {
472        // For a version 4 quote format, we know its TDX v1
473        4 => (input, TDXVersion::One),
474        // For version 5 quote format, read the TDX version from the quote
475        5 => {
476            let (input, body_type) = le_u16(input)?;
477            let (input, _body_size) = le_u32(input)?;
478            (
479                input,
480                body_type.try_into().map_err(|_| {
481                    nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Fail))
482                })?,
483            )
484        }
485        _ => {
486            return Err(nom::Err::Failure(nom::error::Error::new(
487                input,
488                nom::error::ErrorKind::Fail,
489            )))
490        }
491    };
492    // Process the body assuming TDX v1, then add the extra bits for v1.5 if needed
493    let (input, mut body) = basic_body_parser(input)?;
494    let input = if tdx_version == TDXVersion::OnePointFive {
495        body.tdx_version = TDXVersion::OnePointFive;
496        let (input, tee_tcb_svn_2) = take16(input)?;
497        body.tee_tcb_svn_2 = Some(tee_tcb_svn_2);
498        let (input, mrservicetd) = take48(input)?;
499        body.mrservicetd = Some(mrservicetd);
500        input
501    } else {
502        input
503    };
504    Ok((input, body))
505}
506
507/// Parser for a quote body - omitting optional extra fields for TDX 1.5
508fn basic_body_parser(input: &[u8]) -> IResult<&[u8], QuoteBody> {
509    map(
510        tuple((
511            take16, take48, take48, take8, take8, take8, take48, take48, take48, take48, take48,
512            take48, take48, take48, take64,
513        )),
514        |(
515            tee_tcb_svn,
516            mrseam,
517            mrsignerseam,
518            seamattributes,
519            tdattributes,
520            xfam,
521            mrtd,
522            mrconfigid,
523            mrowner,
524            mrownerconfig,
525            rtmr0,
526            rtmr1,
527            rtmr2,
528            rtmr3,
529            reportdata,
530        )| QuoteBody {
531            tdx_version: TDXVersion::One,
532            tee_tcb_svn,
533            mrseam,
534            mrsignerseam,
535            seamattributes,
536            tdattributes,
537            xfam,
538            mrtd,
539            mrconfigid,
540            mrowner,
541            mrownerconfig,
542            rtmr0,
543            rtmr1,
544            rtmr2,
545            rtmr3,
546            reportdata,
547            tee_tcb_svn_2: None,
548            mrservicetd: None,
549        },
550    )(input)
551}