1use super::{
15 chain_algorithms, field, le_u64, verify_ecdsa, AnyKeyUsage, TeeKind, TrustStore,
16 VerifiedEvidence,
17};
18use rustls_pki_types::pem::PemObject;
19use rustls_pki_types::{CertificateDer, UnixTime};
20use sha2::{Digest, Sha256};
21use std::collections::BTreeMap;
22use x509_parser::prelude::*;
23
24const HEADER_LEN: usize = 48;
25const SGX_REPORT_BODY_LEN: usize = 384;
26const TD10_REPORT_BODY_LEN: usize = 584;
27const TD15_REPORT_BODY_LEN: usize = 648;
28
29const ATT_KEY_TYPE_ECDSA_P256: u16 = 2;
31const TEE_TYPE_SGX: u32 = 0x0000_0000;
33const TEE_TYPE_TDX: u32 = 0x0000_0081;
34const BODY_TYPE_SGX: u16 = 1;
36const BODY_TYPE_TD10: u16 = 2;
37const BODY_TYPE_TD15: u16 = 3;
38const CERT_TYPE_PCK_CHAIN: u16 = 5;
40const CERT_TYPE_QE_REPORT: u16 = 6;
41
42const SGX_ATTRIBUTES: usize = 48;
44const SGX_MRENCLAVE: usize = 64;
45const SGX_MRSIGNER: usize = 128;
46const SGX_REPORT_DATA: usize = 320;
47const SGX_FLAG_DEBUG: u64 = 1 << 1;
49
50const TD_MRSEAM: usize = 16;
52const TD_ATTRIBUTES: usize = 120;
53const TD_MRTD: usize = 136;
54const TD_MRCONFIGID: usize = 184;
55const TD_MROWNER: usize = 232;
56const TD_MROWNERCONFIG: usize = 280;
57const TD_RTMR0: usize = 328;
58const TD_REPORT_DATA: usize = 520;
59const TD_ATTRIBUTE_DEBUG: u64 = 1;
61
62pub fn verify(
64 quote: &[u8],
65 tee: TeeKind,
66 now: UnixTime,
67 trust: &TrustStore,
68) -> Result<VerifiedEvidence, String> {
69 let parts = QuoteParts::parse(quote, tee)?;
70
71 let pck_leaf = verify_pck_chain(parts.pck_chain, now, trust)?;
72 let (_, pck) = X509Certificate::from_der(&pck_leaf)
73 .map_err(|e| format!("malformed PCK certificate: {e}"))?;
74 verify_ecdsa(
75 &ring::signature::ECDSA_P256_SHA256_FIXED,
76 &pck.public_key().subject_public_key.data,
77 parts.qe_report,
78 parts.qe_report_signature,
79 )
80 .map_err(|_| "QE report signature is invalid".to_string())?;
81 if le_u64(parts.qe_report, SGX_ATTRIBUTES) & SGX_FLAG_DEBUG != 0 {
82 return Err("quote was produced by a debug-mode Quoting Enclave".into());
83 }
84
85 let mut hasher = Sha256::new();
86 hasher.update(parts.attestation_key);
87 hasher.update(parts.qe_auth_data);
88 let expected = hasher.finalize();
89 let qe_report_data = &parts.qe_report[SGX_REPORT_DATA..SGX_REPORT_DATA + 64];
90 if !super::is_bound_to(qe_report_data, &expected) {
91 return Err("QE report does not bind the quote's attestation key".into());
92 }
93
94 let mut attestation_key = Vec::with_capacity(65);
95 attestation_key.push(0x04);
96 attestation_key.extend_from_slice(parts.attestation_key);
97 verify_ecdsa(
98 &ring::signature::ECDSA_P256_SHA256_FIXED,
99 &attestation_key,
100 parts.signed,
101 parts.quote_signature,
102 )
103 .map_err(|_| "quote signature is invalid".to_string())?;
104
105 Ok(match tee {
106 TeeKind::Tdx => td_evidence(parts.body),
107 _ => sgx_evidence(parts.body),
108 })
109}
110
111fn td_evidence(body: &[u8]) -> VerifiedEvidence {
113 let mut measurements = BTreeMap::from([
114 ("mrseam".to_string(), field(body, TD_MRSEAM, 48)),
115 ("mrtd".to_string(), field(body, TD_MRTD, 48)),
116 ("mrconfigid".to_string(), field(body, TD_MRCONFIGID, 48)),
117 ("mrowner".to_string(), field(body, TD_MROWNER, 48)),
118 (
119 "mrownerconfig".to_string(),
120 field(body, TD_MROWNERCONFIG, 48),
121 ),
122 ]);
123 for i in 0..4 {
124 measurements.insert(format!("rtmr{i}"), field(body, TD_RTMR0 + 48 * i, 48));
125 }
126 VerifiedEvidence {
127 tee: TeeKind::Tdx,
128 report_data: field(body, TD_REPORT_DATA, 64),
129 measurements,
130 debug: le_u64(body, TD_ATTRIBUTES) & TD_ATTRIBUTE_DEBUG != 0,
131 nitro: None,
132 }
133}
134
135fn sgx_evidence(body: &[u8]) -> VerifiedEvidence {
137 VerifiedEvidence {
138 tee: TeeKind::Sgx,
139 report_data: field(body, SGX_REPORT_DATA, 64),
140 measurements: BTreeMap::from([
141 ("mrenclave".to_string(), field(body, SGX_MRENCLAVE, 32)),
142 ("mrsigner".to_string(), field(body, SGX_MRSIGNER, 32)),
143 ]),
144 debug: le_u64(body, SGX_ATTRIBUTES) & SGX_FLAG_DEBUG != 0,
145 nitro: None,
146 }
147}
148
149fn verify_pck_chain(data: &[u8], now: UnixTime, trust: &TrustStore) -> Result<Vec<u8>, String> {
155 let chain = parse_cert_chain(data)?;
156 let leaf_index = chain
157 .iter()
158 .position(|der| {
159 X509Certificate::from_der(der)
160 .map(|(_, cert)| !cert.is_ca())
161 .unwrap_or(false)
162 })
163 .ok_or("quote PCK certificate chain has no leaf certificate")?;
164 let leaf_der = &chain[leaf_index];
165 let intermediates: Vec<CertificateDer<'_>> = chain
166 .iter()
167 .enumerate()
168 .filter(|(i, _)| *i != leaf_index)
169 .map(|(_, der)| der.clone())
170 .collect();
171
172 let root_der = CertificateDer::from(trust.intel_sgx_root.as_slice());
173 let anchor = webpki::anchor_from_trusted_cert(&root_der)
174 .map_err(|e| format!("invalid Intel SGX root certificate: {e:?}"))?;
175 let leaf = webpki::EndEntityCert::try_from(leaf_der)
176 .map_err(|e| format!("invalid PCK certificate: {e:?}"))?;
177 leaf.verify_for_usage(
178 chain_algorithms(),
179 &[anchor],
180 &intermediates,
181 now,
182 AnyKeyUsage,
183 None,
184 None,
185 )
186 .map_err(|e| format!("PCK certificate chain is invalid: {e:?}"))?;
187 Ok(leaf_der.to_vec())
188}
189
190fn parse_cert_chain(data: &[u8]) -> Result<Vec<CertificateDer<'static>>, String> {
193 let data = match data.iter().rposition(|b| *b != 0) {
194 Some(end) => &data[..=end],
195 None => return Err("quote PCK certificate chain is empty".into()),
196 };
197 if data.starts_with(b"-----BEGIN") {
198 return CertificateDer::pem_slice_iter(data)
199 .collect::<Result<Vec<_>, _>>()
200 .map_err(|e| format!("malformed PCK certificate chain: {e:?}"));
201 }
202 let mut chain = Vec::new();
203 let mut rest = data;
204 while !rest.is_empty() {
205 let (remaining, _) = X509Certificate::from_der(rest)
206 .map_err(|e| format!("malformed PCK certificate chain: {e}"))?;
207 let len = rest.len() - remaining.len();
208 chain.push(CertificateDer::from(rest[..len].to_vec()));
209 rest = remaining;
210 }
211 Ok(chain)
212}
213
214struct QuoteParts<'a> {
216 signed: &'a [u8],
218 body: &'a [u8],
220 quote_signature: &'a [u8],
221 attestation_key: &'a [u8],
223 qe_report: &'a [u8],
224 qe_report_signature: &'a [u8],
225 qe_auth_data: &'a [u8],
226 pck_chain: &'a [u8],
227}
228
229impl<'a> QuoteParts<'a> {
231 fn parse(quote: &'a [u8], tee: TeeKind) -> Result<Self, String> {
233 let mut r = Reader::new(quote);
234 let header = r.take(HEADER_LEN)?;
235 let version = u16::from_le_bytes([header[0], header[1]]);
236 let att_key_type = u16::from_le_bytes([header[2], header[3]]);
237 let tee_type = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
238
239 if att_key_type != ATT_KEY_TYPE_ECDSA_P256 {
240 return Err(format!(
241 "unsupported quote attestation key type {att_key_type}"
242 ));
243 }
244 let expected_tee_type = if tee == TeeKind::Tdx {
245 TEE_TYPE_TDX
246 } else {
247 TEE_TYPE_SGX
248 };
249 if tee_type != expected_tee_type {
250 return Err(format!(
251 "quote TEE type {tee_type:#x} does not match {tee} evidence"
252 ));
253 }
254
255 let body = match (version, tee) {
256 (3, TeeKind::Sgx) | (4, TeeKind::Sgx) => r.take(SGX_REPORT_BODY_LEN)?,
257 (4, TeeKind::Tdx) => r.take(TD10_REPORT_BODY_LEN)?,
258 (5, _) => {
259 let body_type = r.u16()?;
260 let body_len = r.u32()? as usize;
261 let expected_len = match (body_type, tee) {
262 (BODY_TYPE_SGX, TeeKind::Sgx) => SGX_REPORT_BODY_LEN,
263 (BODY_TYPE_TD10, TeeKind::Tdx) => TD10_REPORT_BODY_LEN,
264 (BODY_TYPE_TD15, TeeKind::Tdx) => TD15_REPORT_BODY_LEN,
265 _ => {
266 return Err(format!(
267 "unsupported quote v5 body type {body_type} for {tee}"
268 ))
269 }
270 };
271 if body_len != expected_len {
272 return Err(format!(
273 "quote v5 body is {body_len} bytes, expected {expected_len}"
274 ));
275 }
276 r.take(body_len)?
277 }
278 _ => return Err(format!("unsupported {tee} quote version {version}")),
279 };
280 let signed = "e[..r.pos];
281
282 let signature_data_len = r.u32()? as usize;
283 let mut s = Reader::new(r.take(signature_data_len)?);
284 let quote_signature = s.take(64)?;
285 let attestation_key = s.take(64)?;
286
287 let mut qe = if version == 3 {
289 s
290 } else {
291 let cert_type = s.u16()?;
292 if cert_type != CERT_TYPE_QE_REPORT {
293 return Err(format!(
294 "unsupported quote certification data type {cert_type}"
295 ));
296 }
297 let len = s.u32()? as usize;
298 Reader::new(s.take(len)?)
299 };
300 let qe_report = qe.take(SGX_REPORT_BODY_LEN)?;
301 let qe_report_signature = qe.take(64)?;
302 let auth_len = qe.u16()? as usize;
303 let qe_auth_data = qe.take(auth_len)?;
304 let cert_type = qe.u16()?;
305 if cert_type != CERT_TYPE_PCK_CHAIN {
306 return Err(format!(
307 "quote does not embed a PCK certificate chain (certification data type {cert_type})"
308 ));
309 }
310 let pck_len = qe.u32()? as usize;
311 let pck_chain = qe.take(pck_len)?;
312
313 Ok(Self {
314 signed,
315 body,
316 quote_signature,
317 attestation_key,
318 qe_report,
319 qe_report_signature,
320 qe_auth_data,
321 pck_chain,
322 })
323 }
324}
325
326struct Reader<'a> {
328 buf: &'a [u8],
329 pos: usize,
330}
331
332impl<'a> Reader<'a> {
334 fn new(buf: &'a [u8]) -> Self {
336 Self { buf, pos: 0 }
337 }
338
339 fn take(&mut self, len: usize) -> Result<&'a [u8], String> {
341 let end = self
342 .pos
343 .checked_add(len)
344 .filter(|end| *end <= self.buf.len())
345 .ok_or("quote is truncated")?;
346 let bytes = &self.buf[self.pos..end];
347 self.pos = end;
348 Ok(bytes)
349 }
350
351 fn u16(&mut self) -> Result<u16, String> {
353 Ok(u16::from_le_bytes(
354 self.take(2)?.try_into().expect("2-byte slice"),
355 ))
356 }
357
358 fn u32(&mut self) -> Result<u32, String> {
360 Ok(u32::from_le_bytes(
361 self.take(4)?.try_into().expect("4-byte slice"),
362 ))
363 }
364}