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) -> SuiResult<(Vec<u8>, Vec<u8>, AttestationDocument)> {
101 let cose_sign1 = CoseSign1::parse_and_validate(attestation_bytes)?;
102 let doc = AttestationDocument::parse_payload(&cose_sign1.payload, is_upgraded_parsing)?;
103 let msg = cose_sign1.to_signed_message()?;
104 let signature = cose_sign1.signature;
105 Ok((signature, msg, doc))
106}
107
108pub fn verify_nitro_attestation(
112 signature: &[u8],
113 signed_message: &[u8],
114 payload: &AttestationDocument,
115 timestamp: u64,
116) -> SuiResult<()> {
117 let signature = Signature::from_slice(signature)
119 .map_err(|_| NitroAttestationVerifyError::InvalidSignature)?;
120 let cert = X509Certificate::from_der(payload.certificate.as_slice())
121 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?;
122 let pk_bytes = SubjectPublicKeyInfo::parsed(cert.1.public_key())
123 .map_err(|err| NitroAttestationVerifyError::InvalidCertificate(err.to_string()))?;
124
125 match pk_bytes {
127 PublicKey::EC(ec) => {
128 let verifying_key = VerifyingKey::from_sec1_bytes(ec.data())
129 .map_err(|_| NitroAttestationVerifyError::InvalidPublicKey)?;
130 verifying_key
131 .verify(signed_message, &signature)
132 .map_err(|_| NitroAttestationVerifyError::SignatureFailedToVerify)?;
133 }
134 _ => {
135 return Err(NitroAttestationVerifyError::InvalidPublicKey.into());
136 }
137 }
138
139 payload.verify_cert(timestamp)?;
140 Ok(())
141}
142
143#[derive(Debug, Clone)]
149pub struct CoseSign1 {
150 protected: Vec<u8>,
152 #[allow(dead_code)]
154 unprotected: HeaderMap,
155 payload: Vec<u8>,
159 signature: Vec<u8>,
161}
162
163#[derive(Clone, Debug, Default)]
165pub struct HeaderMap;
166
167impl<'de> Deserialize<'de> for HeaderMap {
168 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169 where
170 D: Deserializer<'de>,
171 {
172 struct MapVisitor;
173
174 impl<'de> Visitor<'de> for MapVisitor {
175 type Value = HeaderMap;
176
177 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 formatter.write_str("a map")
179 }
180
181 fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
182 where
183 A: MapAccess<'de>,
184 {
185 let mut seen_keys = Vec::new();
186
187 while let Some((key, _value)) = access.next_entry::<Value, Value>()? {
189 if seen_keys.contains(&key) {
190 return Err(Error::custom("duplicate key found in CBOR map"));
191 }
192 seen_keys.push(key);
193 }
194 Ok(HeaderMap)
195 }
196 }
197
198 deserializer.deserialize_map(MapVisitor)
199 }
200}
201
202impl<'de> Deserialize<'de> for CoseSign1 {
203 fn deserialize<D>(deserializer: D) -> Result<CoseSign1, D::Error>
204 where
205 D: Deserializer<'de>,
206 {
207 use serde::de::{Error, SeqAccess, Visitor};
208 use std::fmt;
209
210 struct CoseSign1Visitor;
211
212 impl<'de> Visitor<'de> for CoseSign1Visitor {
213 type Value = CoseSign1;
214
215 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 f.write_str("a possibly tagged CoseSign1 structure")
217 }
218
219 fn visit_seq<A>(self, mut seq: A) -> Result<CoseSign1, A::Error>
220 where
221 A: SeqAccess<'de>,
222 {
223 fn extract_bytes(value: Value) -> Option<Vec<u8>> {
224 match value {
225 Value::Bytes(bytes) => Some(bytes),
226 _ => None,
227 }
228 }
229
230 let protected = match seq.next_element::<Value>()? {
232 Some(v) => extract_bytes(v)
233 .ok_or_else(|| A::Error::custom("protected header must be bytes"))?,
234 None => return Err(A::Error::missing_field("protected")),
235 };
236
237 let unprotected = match seq.next_element()? {
238 Some(v) => v,
239 None => return Err(A::Error::missing_field("unprotected")),
240 };
241 let payload = match seq.next_element::<Value>()? {
243 Some(v) => {
244 extract_bytes(v).ok_or_else(|| A::Error::custom("payload must be bytes"))?
245 }
246 None => return Err(A::Error::missing_field("payload")),
247 };
248
249 let signature = match seq.next_element::<Value>()? {
251 Some(v) => extract_bytes(v)
252 .ok_or_else(|| A::Error::custom("signature must be bytes"))?,
253 None => return Err(A::Error::missing_field("signature")),
254 };
255
256 Ok(CoseSign1 {
257 protected,
258 unprotected,
259 payload,
260 signature,
261 })
262 }
263
264 fn visit_newtype_struct<D>(self, deserializer: D) -> Result<CoseSign1, D::Error>
265 where
266 D: Deserializer<'de>,
267 {
268 deserializer.deserialize_seq(CoseSign1Visitor)
270 }
271 }
272
273 deserializer.deserialize_any(CoseSign1Visitor)
274 }
275}
276
277impl CoseSign1 {
278 pub fn parse_and_validate(bytes: &[u8]) -> Result<Self, NitroAttestationVerifyError> {
280 let tagged_value: ciborium::value::Value = ciborium::de::from_reader(bytes)
281 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
282
283 let (tag, value) = match tagged_value {
284 ciborium::value::Value::Tag(tag, box_value) => (Some(tag), *box_value),
285 other => (None, other),
286 };
287
288 match tag {
290 None | Some(18) => (),
291 Some(_) => {
292 return Err(NitroAttestationVerifyError::InvalidCoseSign1(
293 "invalid tag".to_string(),
294 ));
295 }
296 }
297
298 let mut buf = Vec::new();
300
301 ciborium::ser::into_writer(&value, &mut buf)
303 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
304
305 let cosesign1: Self = ciborium::de::from_reader(&buf[..])
307 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
308
309 let _: HeaderMap = ciborium::de::from_reader(cosesign1.protected.as_slice())
311 .map_err(|e| NitroAttestationVerifyError::InvalidCoseSign1(e.to_string()))?;
312
313 cosesign1.validate_header()?;
314 Ok(cosesign1)
315 }
316
317 pub fn validate_header(&self) -> Result<(), NitroAttestationVerifyError> {
319 if !(Self::is_valid_protected_header(self.protected.as_slice())
320 && (1..16384).contains(&self.payload.len())
321 && self.signature.len() == 96)
322 {
323 return Err(NitroAttestationVerifyError::InvalidCoseSign1(
324 "invalid cbor header".to_string(),
325 ));
326 }
327 Ok(())
328 }
329
330 fn is_valid_protected_header(bytes: &[u8]) -> bool {
338 let expected_key: Integer = Integer::from(1);
339 let expected_val: Integer = Integer::from(-35);
340 let value: Value = match ciborium::de::from_reader(bytes) {
341 Ok(v) => v,
342 Err(_) => return false,
343 };
344 match value {
345 Value::Map(vec) => match &vec[..] {
346 [(Value::Integer(key), Value::Integer(val))] => {
347 key == &expected_key && val == &expected_val
348 }
349 _ => false,
350 },
351 _ => false,
352 }
353 }
354
355 fn to_signed_message(&self) -> SuiResult<Vec<u8>> {
357 let value = Value::Array(vec![
358 Value::Text("Signature1".to_string()),
359 Value::Bytes(self.protected.as_slice().to_vec()),
360 Value::Bytes(vec![]),
361 Value::Bytes(self.payload.as_slice().to_vec()),
362 ]);
363 let mut bytes = Vec::with_capacity(self.protected.len() + self.payload.len() + 17);
365 ciborium::ser::into_writer(&value, &mut bytes).map_err(|_| {
366 SuiErrorKind::NitroAttestationFailedToVerify("cannot parse message".to_string())
367 })?;
368 Ok(bytes)
369 }
370}
371
372#[allow(dead_code)]
374#[derive(Debug, Clone)]
375pub struct AttestationDocument {
376 pub module_id: String,
377 pub timestamp: u64,
378 pub digest: String,
379 pub pcr_vec: Vec<Vec<u8>>,
380 pub pcr_map: BTreeMap<u8, Vec<u8>>,
381 certificate: Vec<u8>,
382 cabundle: Vec<Vec<u8>>,
383 pub public_key: Option<Vec<u8>>,
384 pub user_data: Option<Vec<u8>>,
385 pub nonce: Option<Vec<u8>>,
386}
387
388impl AttestationDocument {
389 pub fn parse_payload(
392 payload: &[u8],
393 is_upgraded_parsing: bool,
394 ) -> Result<AttestationDocument, NitroAttestationVerifyError> {
395 let document_map = Self::to_map(payload, is_upgraded_parsing)?;
396 Self::validate_document_map(&document_map, is_upgraded_parsing)
397 }
398
399 fn to_map(
400 payload: &[u8],
401 is_upgraded_parsing: bool,
402 ) -> Result<BTreeMap<String, Value>, NitroAttestationVerifyError> {
403 let document_data: ciborium::value::Value =
404 ciborium::de::from_reader(payload).map_err(|err| {
405 NitroAttestationVerifyError::InvalidAttestationDoc(format!(
406 "cannot parse payload CBOR: {}",
407 err
408 ))
409 })?;
410
411 let document_map: BTreeMap<String, Value> = match document_data {
412 ciborium::value::Value::Map(map) => {
413 let map_size = map.len();
414 let result = map
415 .into_iter()
416 .map(|(k, v)| {
417 let k = k.as_text().ok_or(
418 NitroAttestationVerifyError::InvalidAttestationDoc(format!(
419 "invalid key type: {:?}",
420 k
421 )),
422 )?;
423 Ok((k.to_string(), v))
424 })
425 .collect::<Result<BTreeMap<String, Value>, NitroAttestationVerifyError>>()?;
426
427 if is_upgraded_parsing && result.len() != map_size {
428 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
429 "duplicate keys found in attestation document".to_string(),
430 ));
431 }
432 result
433 }
434 _ => {
435 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(format!(
436 "expected map, got {:?}",
437 document_data
438 )));
439 }
440 };
441 Ok(document_map)
442 }
443
444 fn validate_document_map(
445 document_map: &BTreeMap<String, Value>,
446 is_upgraded_parsing: bool,
447 ) -> Result<AttestationDocument, NitroAttestationVerifyError> {
448 let module_id = document_map
449 .get("module_id")
450 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
451 "module id not found".to_string(),
452 ))?
453 .as_text()
454 .filter(|s| !s.is_empty())
455 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
456 "invalid module id".to_string(),
457 ))?
458 .to_string();
459
460 let digest = document_map
461 .get("digest")
462 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
463 "digest not found".to_string(),
464 ))?
465 .as_text()
466 .filter(|s| s == &"SHA384")
467 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
468 "invalid digest".to_string(),
469 ))?
470 .to_string();
471
472 let certificate = document_map
473 .get("certificate")
474 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
475 "certificate not found".to_string(),
476 ))?
477 .as_bytes()
478 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
479 "invalid certificate".to_string(),
480 ))?
481 .to_vec();
482
483 if certificate.is_empty() || certificate.len() > MAX_CERT_LENGTH {
484 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
485 "invalid certificate".to_string(),
486 ));
487 }
488
489 let timestamp = document_map
490 .get("timestamp")
491 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
492 "timestamp not found".to_string(),
493 ))?
494 .as_integer()
495 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
496 "timestamp is not an integer".to_string(),
497 ))
498 .and_then(|integer| {
499 u64::try_from(integer).map_err(|_| {
500 NitroAttestationVerifyError::InvalidAttestationDoc(
501 "timestamp not u64".to_string(),
502 )
503 })
504 })?;
505
506 let public_key = document_map
507 .get("public_key")
508 .and_then(|v| v.as_bytes())
509 .map(|bytes| bytes.to_vec());
510
511 if let Some(data) = &public_key {
512 if is_upgraded_parsing {
513 if data.len() > MAX_PK_LENGTH {
514 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
515 "invalid public key".to_string(),
516 ));
517 }
518 } else if data.is_empty() || data.len() > MAX_PK_LENGTH {
519 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
520 "invalid public key".to_string(),
521 ));
522 }
523 }
524
525 let user_data = document_map
526 .get("user_data")
527 .and_then(|v| v.as_bytes())
528 .map(|bytes| bytes.to_vec());
529
530 if let Some(data) = &user_data
531 && data.len() > MAX_USER_DATA_LENGTH
532 {
533 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
534 "invalid user data".to_string(),
535 ));
536 }
537
538 let nonce = document_map
539 .get("nonce")
540 .and_then(|v| v.as_bytes())
541 .map(|bytes| bytes.to_vec());
542
543 if let Some(data) = &nonce
544 && data.len() > MAX_USER_DATA_LENGTH
545 {
546 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
547 "invalid nonce".to_string(),
548 ));
549 }
550
551 let (pcr_vec, pcr_map) = document_map
552 .get("pcrs")
553 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
554 "pcrs not found".to_string(),
555 ))?
556 .as_map()
557 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
558 "invalid pcrs format".to_string(),
559 ))
560 .and_then(|pairs| {
561 if pairs.len() > MAX_PCRS_LENGTH {
562 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
563 "invalid PCRs length".to_string(),
564 ));
565 }
566 let mut pcr_vec = Vec::with_capacity(pairs.len());
567 let mut pcr_map = BTreeMap::new();
568 for (k, v) in pairs.iter() {
569 let key = k.as_integer().ok_or(
570 NitroAttestationVerifyError::InvalidAttestationDoc(
571 "invalid PCR key format".to_string(),
572 ),
573 )?;
574 let value =
575 v.as_bytes()
576 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
577 "invalid PCR value format".to_string(),
578 ))?;
579
580 if value.len() != 32 && value.len() != 48 && value.len() != 64 {
581 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
582 "invalid PCR value length".to_string(),
583 ));
584 }
585
586 let key_u64 = u64::try_from(key).map_err(|_| {
588 NitroAttestationVerifyError::InvalidAttestationDoc(
589 "invalid PCR index".to_string(),
590 )
591 })?;
592 for i in [0, 1, 2, 3, 4, 8] {
593 if key_u64 == i {
594 pcr_vec.push(value.to_vec());
595 }
596 }
597
598 if is_upgraded_parsing {
600 let key_u8 = u8::try_from(key).map_err(|_| {
602 NitroAttestationVerifyError::InvalidAttestationDoc(
603 "invalid PCR index".to_string(),
604 )
605 })?;
606
607 if !matches!(key_u8, 0 | 1 | 2 | 3 | 4 | 8) {
610 continue;
611 }
612
613 if pcr_map.contains_key(&key_u8) {
614 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
615 format!("duplicate PCR index {}", key_u8),
616 ));
617 }
618
619 pcr_map.insert(key_u8, value.to_vec());
620 }
621 }
622 Ok((pcr_vec, pcr_map))
623 })?;
624
625 let cabundle = document_map
626 .get("cabundle")
627 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
628 "cabundle not found".to_string(),
629 ))?
630 .as_array()
631 .map(|arr| {
632 if arr.is_empty() || arr.len() > MAX_CERT_CHAIN_LENGTH {
633 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
634 "invalid ca chain length".to_string(),
635 ));
636 }
637 let mut certs = Vec::with_capacity(arr.len());
638 for cert in arr.iter() {
639 let cert_bytes = cert.as_bytes().ok_or(
640 NitroAttestationVerifyError::InvalidAttestationDoc(
641 "invalid certificate bytes".to_string(),
642 ),
643 )?;
644 if cert_bytes.is_empty() || cert_bytes.len() > MAX_CERT_LENGTH {
645 return Err(NitroAttestationVerifyError::InvalidAttestationDoc(
646 "invalid ca length".to_string(),
647 ));
648 }
649 certs.push(cert_bytes.to_vec());
650 }
651 Ok(certs)
652 })
653 .ok_or(NitroAttestationVerifyError::InvalidAttestationDoc(
654 "invalid cabundle".to_string(),
655 ))??;
656
657 let doc = AttestationDocument {
658 module_id,
659 timestamp,
660 digest,
661 pcr_vec,
662 pcr_map,
663 certificate,
664 cabundle,
665 public_key,
666 user_data,
667 nonce,
668 };
669 Ok(doc)
670 }
671
672 fn verify_cert(&self, now: u64) -> Result<(), NitroAttestationVerifyError> {
675 let mut chain = Vec::with_capacity(1 + self.cabundle.len());
677 chain.push(self.certificate.as_slice());
678 chain.extend(self.cabundle.iter().rev().map(|cert| cert.as_slice()));
679 verify_cert_chain(&chain, now)
680 }
681
682 pub fn get_cert_chain_length(&self) -> usize {
684 self.cabundle.len()
685 }
686}
687fn verify_cert_chain(cert_chain: &[&[u8]], now_ms: u64) -> Result<(), NitroAttestationVerifyError> {
689 let root_cert = X509Certificate::from_der(ROOT_CERTIFICATE.as_slice())
690 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?
691 .1;
692
693 let now_secs =
694 ASN1Time::from_timestamp((now_ms as i64).checked_div(1000).ok_or_else(|| {
695 NitroAttestationVerifyError::InvalidAttestationDoc("timestamp overflow".to_string())
696 })?)
697 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?;
698
699 for i in 0..cert_chain.len() {
701 let cert = X509Certificate::from_der(cert_chain[i])
702 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?
703 .1;
704
705 if let Ok(Some(key_usage)) = cert.key_usage() {
707 if i == 0 {
708 if !key_usage.value.digital_signature() {
710 return Err(NitroAttestationVerifyError::InvalidCertificate(
711 "Target certificate missing digitalSignature key usage".to_string(),
712 ));
713 }
714 } else {
715 if !key_usage.value.key_cert_sign() {
717 return Err(NitroAttestationVerifyError::InvalidCertificate(
718 "CA certificate missing keyCertSign key usage".to_string(),
719 ));
720 }
721 }
722 } else {
723 return Err(NitroAttestationVerifyError::InvalidCertificate(
724 "Missing key usage extension".to_string(),
725 ));
726 }
727
728 if i != 0 {
729 if let Ok(Some(bc)) = cert.basic_constraints() {
732 if !bc.critical || !bc.value.ca {
733 return Err(NitroAttestationVerifyError::InvalidCertificate(
734 "CA certificate invalid".to_string(),
735 ));
736 }
737 if let Some(path_len) = bc.value.path_len_constraint {
738 if i - 1 > path_len as usize {
742 return Err(NitroAttestationVerifyError::InvalidCertificate(
743 "Cert chain exceeds pathLenConstraint".to_string(),
744 ));
745 }
746 }
747 } else {
748 return Err(NitroAttestationVerifyError::InvalidCertificate(
749 "missing basic constraint".to_string(),
750 ));
751 }
752 } else if let Ok(Some(bc)) = cert.basic_constraints() {
753 if bc.value.path_len_constraint.is_some() || bc.value.ca {
756 return Err(NitroAttestationVerifyError::InvalidCertificate(
757 "Cert chain exceeds pathLenConstraint".to_string(),
758 ));
759 }
760 }
761
762 if !cert.validity().is_valid_at(now_secs) {
764 return Err(NitroAttestationVerifyError::InvalidCertificate(
765 "Certificate timestamp not valid".to_string(),
766 ));
767 }
768
769 let issuer_cert = if i < cert_chain.len() - 1 {
771 X509Certificate::from_der(cert_chain[i + 1])
772 .map_err(|e| NitroAttestationVerifyError::InvalidCertificate(e.to_string()))?
773 .1
774 } else {
775 root_cert.clone()
776 };
777
778 if cert.issuer() != issuer_cert.subject() {
780 return Err(NitroAttestationVerifyError::InvalidCertificate(
781 "certificate chain issuer mismatch".to_string(),
782 ));
783 }
784
785 cert.verify_signature(Some(issuer_cert.public_key()))
787 .map_err(|_| {
788 NitroAttestationVerifyError::InvalidCertificate(
789 "certificate fails to verify".to_string(),
790 )
791 })?;
792 }
793
794 Ok(())
795}