1use serde::de::{Error, MapAccess, Visitor};
5use serde::{Deserialize, Deserializer};
6use std::collections::BTreeMap;
7use std::fmt;
8use x509_parser::public_key::PublicKey;
9use x509_parser::time::ASN1Time;
10use x509_parser::x509::SubjectPublicKeyInfo;
11
12use crate::error::{SuiError, SuiErrorKind, SuiResult};
13
14use ciborium::value::{Integer, Value};
15use once_cell::sync::Lazy;
16use p384::ecdsa::signature::Verifier;
17use p384::ecdsa::{Signature, VerifyingKey};
18use x509_parser::{certificate::X509Certificate, prelude::FromDer};
19
20#[cfg(test)]
21#[path = "unit_tests/nitro_attestation_tests.rs"]
22mod nitro_attestation_tests;
23
24const MAX_CERT_CHAIN_LENGTH: usize = 10;
26const MAX_USER_DATA_LENGTH: usize = 512;
28const MAX_PK_LENGTH: usize = 1024;
30const MAX_PCRS_LENGTH: usize = 32;
32const MAX_CERT_LENGTH: usize = 1024;
34
35static ROOT_CERTIFICATE: Lazy<Vec<u8>> = Lazy::new(|| {
37 let pem_bytes = include_bytes!("./nitro_root_certificate.pem");
38 let mut pem_cursor = std::io::Cursor::new(pem_bytes);
39 let cert = rustls_pemfile::certs(&mut pem_cursor)
40 .next()
41 .expect("should have root cert")
42 .expect("root cert should be valid");
43 cert.to_vec()
44});
45
46#[derive(Debug, PartialEq, Eq)]
48pub enum NitroAttestationVerifyError {
49 InvalidCoseSign1(String),
51 InvalidSignature,
53 InvalidPublicKey,
55 SignatureFailedToVerify,
57 InvalidAttestationDoc(String),
59 InvalidUserData,
61 InvalidCertificate(String),
63 InvalidPcrs,
65}
66
67impl fmt::Display for NitroAttestationVerifyError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 NitroAttestationVerifyError::InvalidCoseSign1(msg) => {
71 write!(f, "InvalidCoseSign1: {}", msg)
72 }
73 NitroAttestationVerifyError::InvalidSignature => write!(f, "InvalidSignature"),
74 NitroAttestationVerifyError::InvalidPublicKey => write!(f, "InvalidPublicKey"),
75 NitroAttestationVerifyError::SignatureFailedToVerify => {
76 write!(f, "SignatureFailedToVerify")
77 }
78 NitroAttestationVerifyError::InvalidAttestationDoc(msg) => {
79 write!(f, "InvalidAttestationDoc: {}", msg)
80 }
81 NitroAttestationVerifyError::InvalidCertificate(msg) => {
82 write!(f, "InvalidCertificate: {}", msg)
83 }
84 NitroAttestationVerifyError::InvalidPcrs => write!(f, "InvalidPcrs"),
85 NitroAttestationVerifyError::InvalidUserData => write!(f, "InvalidUserData"),
86 }
87 }
88}
89
90impl From<NitroAttestationVerifyError> for SuiError {
91 fn from(err: NitroAttestationVerifyError) -> Self {
92 SuiErrorKind::NitroAttestationFailedToVerify(err.to_string()).into()
93 }
94}
95
96pub fn parse_nitro_attestation(
98 attestation_bytes: &[u8],
99 is_upgraded_parsing: bool,
100 include_all_nonzero_pcrs: bool,
101) -> SuiResult<(Vec<u8>, Vec<u8>, AttestationDocument)> {
102 let cose_sign1 = CoseSign1::parse_and_validate(attestation_bytes)?;
103 let doc = AttestationDocument::parse_payload(
104 &cose_sign1.payload,
105 is_upgraded_parsing,
106 include_all_nonzero_pcrs,
107 )?;
108 let msg = cose_sign1.to_signed_message()?;
109 let signature = cose_sign1.signature;
110 Ok((signature, msg, doc))
111}
112
113pub fn verify_nitro_attestation(
117 signature: &[u8],
118 signed_message: &[u8],
119 payload: &AttestationDocument,
120 timestamp: u64,
121) -> SuiResult<()> {
122 let signature = Signature::from_slice(signature)
124 .map_err(|_| NitroAttestationVerifyError::InvalidSignature)?;
125 let cert = X509Certificate::from_der(payload.certificate.as_slice())
126 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?;
127 let pk_bytes = SubjectPublicKeyInfo::parsed(cert.1.public_key())
128 .map_err(|err| NitroAttestationVerifyError::InvalidCertificate(err.to_string()))?;
129
130 match pk_bytes {
132 PublicKey::EC(ec) => {
133 let verifying_key = VerifyingKey::from_sec1_bytes(ec.data())
134 .map_err(|_| NitroAttestationVerifyError::InvalidPublicKey)?;
135 verifying_key
136 .verify(signed_message, &signature)
137 .map_err(|_| NitroAttestationVerifyError::SignatureFailedToVerify)?;
138 }
139 _ => {
140 return Err(NitroAttestationVerifyError::InvalidPublicKey.into());
141 }
142 }
143
144 payload.verify_cert(timestamp)?;
145 Ok(())
146}
147
148#[derive(Debug, Clone)]
154pub struct CoseSign1 {
155 protected: Vec<u8>,
157 #[allow(dead_code)]
159 unprotected: HeaderMap,
160 payload: Vec<u8>,
164 signature: Vec<u8>,
166}
167
168#[derive(Clone, Debug, Default)]
170pub struct HeaderMap;
171
172impl<'de> Deserialize<'de> for HeaderMap {
173 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174 where
175 D: Deserializer<'de>,
176 {
177 struct MapVisitor;
178
179 impl<'de> Visitor<'de> for MapVisitor {
180 type Value = HeaderMap;
181
182 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
183 formatter.write_str("a map")
184 }
185
186 fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
187 where
188 A: MapAccess<'de>,
189 {
190 let mut seen_keys = Vec::new();
191
192 while let Some((key, _value)) = access.next_entry::<Value, Value>()? {
194 if seen_keys.contains(&key) {
195 return Err(Error::custom("duplicate key found in CBOR map"));
196 }
197 seen_keys.push(key);
198 }
199 Ok(HeaderMap)
200 }
201 }
202
203 deserializer.deserialize_map(MapVisitor)
204 }
205}
206
207impl<'de> Deserialize<'de> for CoseSign1 {
208 fn deserialize<D>(deserializer: D) -> Result<CoseSign1, D::Error>
209 where
210 D: Deserializer<'de>,
211 {
212 use serde::de::{Error, SeqAccess, Visitor};
213 use std::fmt;
214
215 struct CoseSign1Visitor;
216
217 impl<'de> Visitor<'de> for CoseSign1Visitor {
218 type Value = CoseSign1;
219
220 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 f.write_str("a possibly tagged CoseSign1 structure")
222 }
223
224 fn visit_seq<A>(self, mut seq: A) -> Result<CoseSign1, A::Error>
225 where
226 A: SeqAccess<'de>,
227 {
228 fn extract_bytes(value: Value) -> Option<Vec<u8>> {
229 match value {
230 Value::Bytes(bytes) => Some(bytes),
231 _ => None,
232 }
233 }
234
235 let protected = match seq.next_element::<Value>()? {
237 Some(v) => extract_bytes(v)
238 .ok_or_else(|| A::Error::custom("protected header must be bytes"))?,
239 None => return Err(A::Error::missing_field("protected")),
240 };
241
242 let unprotected = match seq.next_element()? {
243 Some(v) => v,
244 None => return Err(A::Error::missing_field("unprotected")),
245 };
246 let payload = match seq.next_element::<Value>()? {
248 Some(v) => {
249 extract_bytes(v).ok_or_else(|| A::Error::custom("payload must be bytes"))?
250 }
251 None => return Err(A::Error::missing_field("payload")),
252 };
253
254 let signature = match seq.next_element::<Value>()? {
256 Some(v) => extract_bytes(v)
257 .ok_or_else(|| A::Error::custom("signature must be bytes"))?,
258 None => return Err(A::Error::missing_field("signature")),
259 };
260
261 Ok(CoseSign1 {
262 protected,
263 unprotected,
264 payload,
265 signature,
266 })
267 }
268
269 fn visit_newtype_struct<D>(self, deserializer: D) -> Result<CoseSign1, D::Error>
270 where
271 D: Deserializer<'de>,
272 {
273 deserializer.deserialize_seq(CoseSign1Visitor)
275 }
276 }
277
278 deserializer.deserialize_any(CoseSign1Visitor)
279 }
280}
281
282impl CoseSign1 {
283 pub fn parse_and_validate(bytes: &[u8]) -> Result<Self, NitroAttestationVerifyError> {
285 let tagged_value: ciborium::value::Value = ciborium::de::from_reader(bytes)
286 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
287
288 let (tag, value) = match tagged_value {
289 ciborium::value::Value::Tag(tag, box_value) => (Some(tag), *box_value),
290 other => (None, other),
291 };
292
293 match tag {
295 None | Some(18) => (),
296 Some(_) => {
297 return Err(NitroAttestationVerifyError::InvalidCoseSign1(
298 "invalid tag".to_string(),
299 ));
300 }
301 }
302
303 let mut buf = Vec::new();
305
306 ciborium::ser::into_writer(&value, &mut buf)
308 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
309
310 let cosesign1: Self = ciborium::de::from_reader(&buf[..])
312 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
313
314 let _: HeaderMap = ciborium::de::from_reader(cosesign1.protected.as_slice())
316 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
317
318 cosesign1.validate_header()?;
319 Ok(cosesign1)
320 }
321
322 pub fn validate_header(&self) -> Result<(), NitroAttestationVerifyError> {
324 if !(Self::is_valid_protected_header(self.protected.as_slice())
325 && (1..16384).contains(&self.payload.len())
326 && self.signature.len() == 96)
327 {
328 return Err(NitroAttestationVerifyError::InvalidCoseSign1(
329 "invalid cbor header".to_string(),
330 ));
331 }
332 Ok(())
333 }
334
335 fn is_valid_protected_header(bytes: &[u8]) -> bool {
343 let expected_key: Integer = Integer::from(1);
344 let expected_val: Integer = Integer::from(-35);
345 let value: Value = match ciborium::de::from_reader(bytes) {
346 Ok(v) => v,
347 Err(_) => return false,
348 };
349 match value {
350 Value::Map(vec) => match &vec[..] {
351 [(Value::Integer(key), Value::Integer(val))] => {
352 key == &expected_key && val == &expected_val
353 }
354 _ => false,
355 },
356 _ => false,
357 }
358 }
359
360 fn to_signed_message(&self) -> SuiResult<Vec<u8>> {
362 let value = Value::Array(vec![
363 Value::Text("Signature1".to_string()),
364 Value::Bytes(self.protected.as_slice().to_vec()),
365 Value::Bytes(vec![]),
366 Value::Bytes(self.payload.as_slice().to_vec()),
367 ]);
368 let mut bytes = Vec::with_capacity(self.protected.len() + self.payload.len() + 17);
370 ciborium::ser::into_writer(&value, &mut bytes).map_err(|_| {
371 SuiErrorKind::NitroAttestationFailedToVerify("cannot parse message".to_string())
372 })?;
373 Ok(bytes)
374 }
375}
376
377#[allow(dead_code)]
379#[derive(Debug, Clone)]
380pub struct AttestationDocument {
381 pub module_id: String,
382 pub timestamp: u64,
383 pub digest: String,
384 pub pcr_vec: Vec<Vec<u8>>,
385 pub pcr_map: BTreeMap<u8, Vec<u8>>,
386 certificate: Vec<u8>,
387 cabundle: Vec<Vec<u8>>,
388 pub public_key: Option<Vec<u8>>,
389 pub user_data: Option<Vec<u8>>,
390 pub nonce: Option<Vec<u8>>,
391}
392
393impl AttestationDocument {
394 pub fn parse_payload(
397 payload: &[u8],
398 is_upgraded_parsing: bool,
399 include_all_nonzero_pcrs: bool,
400 ) -> Result<AttestationDocument, NitroAttestationVerifyError> {
401 let document_map = Self::to_map(payload, is_upgraded_parsing)?;
402 Self::validate_document_map(&document_map, is_upgraded_parsing, include_all_nonzero_pcrs)
403 }
404
405 fn to_map(
406 payload: &[u8],
407 is_upgraded_parsing: bool,
408 ) -> Result<BTreeMap<String, Value>, NitroAttestationVerifyError> {
409 let document_data: ciborium::value::Value =
410 ciborium::de::from_reader(payload).map_err(|err| {
411 NitroAttestationVerifyError::InvalidAttestationDoc(format!(
412 "cannot parse payload CBOR: {}",
413 err
414 ))
415 })?;
416
417 let document_map: BTreeMap<String, Value> = match document_data {
418 ciborium::value::Value::Map(map) => {
419 let map_size = map.len();
420 let result = map
421 .into_iter()
422 .map(|(k, v)| {
423 let k = k.as_text().ok_or(
424 NitroAttestationVerifyError::InvalidAttestationDoc(format!(
425 "invalid key type: {:?}",
426 k
427 )),
428 )?;
429 Ok((k.to_string(), v))
430 })
431 .collect::<Result<BTreeMap<String, Value>, NitroAttestationVerifyError>>()?;
432
433 if is_upgraded_parsing && result.len() != map_size {
434 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
435 "duplicate keys found in attestation document".to_string(),
436 ));
437 }
438 result
439 }
440 _ => {
441 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(format!(
442 "expected map, got {:?}",
443 document_data
444 )));
445 }
446 };
447 Ok(document_map)
448 }
449
450 fn validate_document_map(
451 document_map: &BTreeMap<String, Value>,
452 is_upgraded_parsing: bool,
453 include_all_nonzero_pcrs: bool,
454 ) -> Result<AttestationDocument, NitroAttestationVerifyError> {
455 let module_id = document_map
456 .get("module_id")
457 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
458 "module id not found".to_string(),
459 ))?
460 .as_text()
461 .filter(|s| !s.is_empty())
462 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
463 "invalid module id".to_string(),
464 ))?
465 .to_string();
466
467 let digest = document_map
468 .get("digest")
469 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
470 "digest not found".to_string(),
471 ))?
472 .as_text()
473 .filter(|s| s == &"SHA384")
474 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
475 "invalid digest".to_string(),
476 ))?
477 .to_string();
478
479 let certificate = document_map
480 .get("certificate")
481 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
482 "certificate not found".to_string(),
483 ))?
484 .as_bytes()
485 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
486 "invalid certificate".to_string(),
487 ))?
488 .to_vec();
489
490 if certificate.is_empty() || certificate.len() > MAX_CERT_LENGTH {
491 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
492 "invalid certificate".to_string(),
493 ));
494 }
495
496 let timestamp = document_map
497 .get("timestamp")
498 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
499 "timestamp not found".to_string(),
500 ))?
501 .as_integer()
502 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
503 "timestamp is not an integer".to_string(),
504 ))
505 .and_then(|integer| {
506 u64::try_from(integer).map_err(|_| {
507 NitroAttestationVerifyError::InvalidAttestationDoc(
508 "timestamp not u64".to_string(),
509 )
510 })
511 })?;
512
513 let public_key = document_map
514 .get("public_key")
515 .and_then(|v| v.as_bytes())
516 .map(|bytes| bytes.to_vec());
517
518 if let Some(data) = &public_key {
519 if is_upgraded_parsing {
520 if data.len() > MAX_PK_LENGTH {
521 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
522 "invalid public key".to_string(),
523 ));
524 }
525 } else if data.is_empty() || data.len() > MAX_PK_LENGTH {
526 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
527 "invalid public key".to_string(),
528 ));
529 }
530 }
531
532 let user_data = document_map
533 .get("user_data")
534 .and_then(|v| v.as_bytes())
535 .map(|bytes| bytes.to_vec());
536
537 if let Some(data) = &user_data
538 && data.len() > MAX_USER_DATA_LENGTH
539 {
540 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
541 "invalid user data".to_string(),
542 ));
543 }
544
545 let nonce = document_map
546 .get("nonce")
547 .and_then(|v| v.as_bytes())
548 .map(|bytes| bytes.to_vec());
549
550 if let Some(data) = &nonce
551 && data.len() > MAX_USER_DATA_LENGTH
552 {
553 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
554 "invalid nonce".to_string(),
555 ));
556 }
557
558 let (pcr_vec, pcr_map) = document_map
559 .get("pcrs")
560 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
561 "pcrs not found".to_string(),
562 ))?
563 .as_map()
564 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
565 "invalid pcrs format".to_string(),
566 ))
567 .and_then(|pairs| {
568 if pairs.len() > MAX_PCRS_LENGTH {
569 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
570 "invalid PCRs length".to_string(),
571 ));
572 }
573 let mut pcr_vec = Vec::with_capacity(pairs.len());
574 let mut pcr_map = BTreeMap::new();
575 for (k, v) in pairs.iter() {
576 let key = k.as_integer().ok_or(
577 NitroAttestationVerifyError::InvalidAttestationDoc(
578 "invalid PCR key format".to_string(),
579 ),
580 )?;
581 let value =
582 v.as_bytes()
583 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
584 "invalid PCR value format".to_string(),
585 ))?;
586
587 if value.len() != 32 && value.len() != 48 && value.len() != 64 {
588 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
589 "invalid PCR value length".to_string(),
590 ));
591 }
592
593 let key_u64 = u64::try_from(key).map_err(|_| {
595 NitroAttestationVerifyError::InvalidAttestationDoc(
596 "invalid PCR index".to_string(),
597 )
598 })?;
599 for i in [0, 1, 2, 3, 4, 8] {
600 if key_u64 == i {
601 pcr_vec.push(value.to_vec());
602 }
603 }
604
605 if is_upgraded_parsing {
607 let key_u8 = u8::try_from(key).map_err(|_| {
609 NitroAttestationVerifyError::InvalidAttestationDoc(
610 "invalid PCR index".to_string(),
611 )
612 })?;
613
614 if pcr_map.contains_key(&key_u8) {
615 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
616 format!("duplicate PCR index {}", key_u8),
617 ));
618 }
619
620 if include_all_nonzero_pcrs {
621 if key_u8 <= 31 && !value.iter().all(|&b| b == 0) {
625 pcr_map.insert(key_u8, value.to_vec());
626 }
627 } else {
628 if matches!(key_u8, 0 | 1 | 2 | 3 | 4 | 8) {
631 pcr_map.insert(key_u8, value.to_vec());
632 }
633 }
634 }
635 }
636 Ok((pcr_vec, pcr_map))
637 })?;
638
639 let cabundle = document_map
640 .get("cabundle")
641 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
642 "cabundle not found".to_string(),
643 ))?
644 .as_array()
645 .map(|arr| {
646 if arr.is_empty() || arr.len() > MAX_CERT_CHAIN_LENGTH {
647 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
648 "invalid ca chain length".to_string(),
649 ));
650 }
651 let mut certs = Vec::with_capacity(arr.len());
652 for cert in arr.iter() {
653 let cert_bytes = cert.as_bytes().ok_or(
654 NitroAttestationVerifyError::InvalidAttestationDoc(
655 "invalid certificate bytes".to_string(),
656 ),
657 )?;
658 if cert_bytes.is_empty() || cert_bytes.len() > MAX_CERT_LENGTH {
659 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
660 "invalid ca length".to_string(),
661 ));
662 }
663 certs.push(cert_bytes.to_vec());
664 }
665 Ok(certs)
666 })
667 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
668 "invalid cabundle".to_string(),
669 ))??;
670
671 let doc = AttestationDocument {
672 module_id,
673 timestamp,
674 digest,
675 pcr_vec,
676 pcr_map,
677 certificate,
678 cabundle,
679 public_key,
680 user_data,
681 nonce,
682 };
683 Ok(doc)
684 }
685
686 fn verify_cert(&self, now: u64) -> Result<(), NitroAttestationVerifyError> {
689 let mut chain = Vec::with_capacity(1 + self.cabundle.len());
691 chain.push(self.certificate.as_slice());
692 chain.extend(self.cabundle.iter().rev().map(|cert| cert.as_slice()));
693 verify_cert_chain(&chain, now)
694 }
695
696 pub fn get_cert_chain_length(&self) -> usize {
698 self.cabundle.len()
699 }
700}
701fn verify_cert_chain(cert_chain: &[&[u8]], now_ms: u64) -> Result<(), NitroAttestationVerifyError> {
703 let root_cert = X509Certificate::from_der(ROOT_CERTIFICATE.as_slice())
704 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?
705 .1;
706
707 let now_secs =
708 ASN1Time::from_timestamp((now_ms as i64).checked_div(1000).ok_or_else(|| {
709 NitroAttestationVerifyError::InvalidAttestationDoc("timestamp overflow".to_string())
710 })?)
711 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?;
712
713 for i in 0..cert_chain.len() {
715 let cert = X509Certificate::from_der(cert_chain[i])
716 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?
717 .1;
718
719 if let Ok(Some(key_usage)) = cert.key_usage() {
721 if i == 0 {
722 if !key_usage.value.digital_signature() {
724 return Err(NitroAttestationVerifyError::InvalidCertificate(
725 "Target certificate missing digitalSignature key usage".to_string(),
726 ));
727 }
728 } else {
729 if !key_usage.value.key_cert_sign() {
731 return Err(NitroAttestationVerifyError::InvalidCertificate(
732 "CA certificate missing keyCertSign key usage".to_string(),
733 ));
734 }
735 }
736 } else {
737 return Err(NitroAttestationVerifyError::InvalidCertificate(
738 "Missing key usage extension".to_string(),
739 ));
740 }
741
742 if i != 0 {
743 if let Ok(Some(bc)) = cert.basic_constraints() {
746 if !bc.critical || !bc.value.ca {
747 return Err(NitroAttestationVerifyError::InvalidCertificate(
748 "CA certificate invalid".to_string(),
749 ));
750 }
751 if let Some(path_len) = bc.value.path_len_constraint {
752 if i - 1 > path_len as usize {
756 return Err(NitroAttestationVerifyError::InvalidCertificate(
757 "Cert chain exceeds pathLenConstraint".to_string(),
758 ));
759 }
760 }
761 } else {
762 return Err(NitroAttestationVerifyError::InvalidCertificate(
763 "missing basic constraint".to_string(),
764 ));
765 }
766 } else if let Ok(Some(bc)) = cert.basic_constraints() {
767 if bc.value.path_len_constraint.is_some() || bc.value.ca {
770 return Err(NitroAttestationVerifyError::InvalidCertificate(
771 "Cert chain exceeds pathLenConstraint".to_string(),
772 ));
773 }
774 }
775
776 if !cert.validity().is_valid_at(now_secs) {
778 return Err(NitroAttestationVerifyError::InvalidCertificate(
779 "Certificate timestamp not valid".to_string(),
780 ));
781 }
782
783 let issuer_cert = if i < cert_chain.len() - 1 {
785 X509Certificate::from_der(cert_chain[i + 1])
786 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?
787 .1
788 } else {
789 root_cert.clone()
790 };
791
792 if cert.issuer() != issuer_cert.subject() {
794 return Err(NitroAttestationVerifyError::InvalidCertificate(
795 "certificate chain issuer mismatch".to_string(),
796 ));
797 }
798
799 cert.verify_signature(Some(issuer_cert.public_key()))
801 .map_err(|_| {
802 NitroAttestationVerifyError::InvalidCertificate(
803 "certificate fails to verify".to_string(),
804 )
805 })?;
806 }
807
808 Ok(())
809}