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