marlin/
attestation.rs

1use std::collections::BTreeMap;
2
3use aws_lc_rs::signature::{ECDSA_P384_SHA384_FIXED, UnparsedPublicKey};
4use aws_nitro_enclaves_cose::{
5    CoseSign1,
6    crypto::{Hash, MessageDigest, SignatureAlgorithm, SigningPublicKey},
7    error::CoseError,
8};
9use serde_cbor::{self, value::Value};
10use sha2::{Digest, Sha256};
11use thiserror::Error;
12use x509_parser::{
13    oid_registry::{OID_KEY_TYPE_EC_PUBLIC_KEY, OID_SIG_ECDSA_WITH_SHA384},
14    prelude::{FromDer, TbsCertificate, X509Certificate},
15    time::ASN1Time,
16};
17
18pub const AWS_ROOT_KEY: [u8; 96] = hex_literal::hex!(
19    "fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4"
20);
21pub const MOCK_ROOT_KEY: [u8; 96] = hex_literal::hex!(
22    "6c79411ebaae7489a4e8355545c0346784b31df5d08cb1f7c0097836a82f67240f2a7201862880a1d09a0bb326637188fbbafab47a10abe3630fcf8c18d35d96532184985e582c0dce3dace8441f37b9cc9211dff935baae69e4872cc3494410"
23);
24
25#[derive(Debug)]
26pub struct AttestationDecoded {
27    pub root_public_key: Box<[u8]>,
28    pub image_id: [u8; 32],
29    pub pcrs: [[u8; 48]; 12],
30    pub timestamp_ms: u64,
31    pub public_key: Box<[u8]>,
32    pub user_data: Box<[u8]>,
33}
34
35#[derive(Error, Debug)]
36pub enum AttestationError {
37    // parse errors
38    #[error("failed to parse cose")]
39    InvalidCose(#[source] CoseError),
40    #[error("failed to parse cbor")]
41    InvalidCbor(#[source] serde_cbor::Error),
42    #[error("x509 error in {context}: {error}")]
43    X509 {
44        context: String,
45        #[source]
46        error: x509_parser::nom::Err<x509_parser::error::X509Error>,
47    },
48    #[error("missing field: {0}")]
49    MissingField(String),
50    #[error("field {0} has invalid type")]
51    InvalidType(String),
52    #[error("field {0} has invalid length: {1}")]
53    InvalidLength(String, String),
54    #[error("timestamp conversion error: {0}")]
55    TimestampConversion(#[from] std::num::TryFromIntError),
56    // verification errors
57    #[error("cose signature verification failed")]
58    CoseSignatureVerifyFailed(#[source] CoseError),
59    #[error("leaf signature verification failed")]
60    LeafSignatureVerifyFailed,
61    #[error("certificate chain signature verification failed at index {index}")]
62    CertChainSignatureFailed { index: usize },
63    #[error("certificate chain issuer or subject mismatch at index {index}")]
64    CertChainIssuerOrSubjectMismatch { index: usize },
65    #[error("certificate chain expired at index {index}")]
66    CertChainExpired { index: usize },
67    // expectation mismatch errors
68    #[error("timestamp mismatch: expected {expected}, got {got}")]
69    TimestampMismatch { expected: u64, got: u64 },
70    #[error("too old: expected age {age}, got {got}, now {now}")]
71    TooOld { age: u64, got: u64, now: u64 },
72    #[error("pcr{idx} mismatch: expected {expected:?}, got {got:?}")]
73    PcrsMismatch {
74        idx: usize,
75        expected: [u8; 48],
76        got: [u8; 48],
77    },
78    #[error("image id mismatch: expected {expected}, got {got}")]
79    ImageIdMismatch { expected: String, got: String },
80    #[error("root public key mismatch: expected {expected}, got {got}")]
81    RootPublicKeyMismatch { expected: String, got: String },
82    #[error("public key mismatch: expected {expected}, got {got}")]
83    PublicKeyMismatch { expected: String, got: String },
84    #[error("user data mismatch: expected {expected}, got {got}")]
85    UserDataMismatch { expected: String, got: String },
86}
87
88#[derive(Debug, Default, Clone)]
89pub struct AttestationExpectations<'a> {
90    pub root_public_key: Option<&'a [u8]>,
91    pub pcrs: [Option<[u8; 48]>; 12],
92    pub image_id: Option<&'a [u8; 32]>,
93    pub timestamp_ms: Option<u64>,
94    // (max age, current timestamp), in ms
95    pub age_ms: Option<(u64, u64)>,
96    pub public_key: Option<&'a [u8]>,
97    pub user_data: Option<&'a [u8]>,
98}
99
100pub fn verify(
101    attestation_doc: &[u8],
102    expectations: AttestationExpectations,
103) -> Result<AttestationDecoded, AttestationError> {
104    let mut result = AttestationDecoded {
105        root_public_key: Default::default(),
106        image_id: Default::default(),
107        pcrs: [[0; 48]; 12],
108        timestamp_ms: 0,
109        public_key: Default::default(),
110        user_data: Default::default(),
111    };
112
113    // parse attestation doc
114    let (cosesign1, mut attestation_doc) = parse_attestation_doc(attestation_doc)?;
115
116    // parse timestamp
117    result.timestamp_ms = parse_timestamp(&mut attestation_doc)?;
118
119    // check expected timestamp if exists
120    if let Some(expected_ts) = expectations.timestamp_ms
121        && result.timestamp_ms != expected_ts
122    {
123        return Err(AttestationError::TimestampMismatch {
124            expected: expected_ts,
125            got: result.timestamp_ms,
126        });
127    }
128
129    // check age if exists
130    if let Some((max_age, current_ts)) = expectations.age_ms
131        && result.timestamp_ms <= current_ts
132        && current_ts - result.timestamp_ms > max_age
133    {
134        return Err(AttestationError::TooOld {
135            age: max_age,
136            got: result.timestamp_ms,
137            now: current_ts,
138        });
139    }
140
141    // parse pcrs
142    result.pcrs = parse_pcrs(&mut attestation_doc)?;
143
144    // check pcrs if exists
145    if let Some((idx, _)) = expectations
146        .pcrs
147        .iter()
148        // add index
149        .enumerate()
150        // return None if expectation is None so it gets filtered
151        // return result of comparison if expectation exists
152        .filter_map(|(idx, &expected_pcr)| Some((idx, expected_pcr? == result.pcrs[idx])))
153        // short circuit on first comparison failure
154        .find(|&(_, res)| !res)
155    {
156        return Err(AttestationError::PcrsMismatch {
157            idx,
158            expected: expectations.pcrs[idx].unwrap_or([0; 48]),
159            got: result.pcrs[idx],
160        });
161    };
162
163    // compute image id
164    let mut hasher = Sha256::new();
165    // bitflags denoting what pcrs are part of the computation
166    // this one has 4-15
167    hasher.update((4..=15).fold(0u32, |acc, x| acc | (1 << x)).to_be_bytes());
168    hasher.update(result.pcrs.as_flattened());
169    result.image_id = hasher.finalize().into();
170
171    // check image id if exists
172    if let Some(image_id) = expectations.image_id
173        && &result.image_id != image_id
174    {
175        return Err(AttestationError::ImageIdMismatch {
176            expected: hex::encode(image_id),
177            got: hex::encode(result.image_id),
178        });
179    }
180
181    // verify signature and cert chain
182    result.root_public_key =
183        verify_root_of_trust(&mut attestation_doc, &cosesign1, result.timestamp_ms)?;
184
185    // check root public key if exists
186    if let Some(root_public_key) = expectations.root_public_key
187        && result.root_public_key.as_ref() != root_public_key
188    {
189        return Err(AttestationError::RootPublicKeyMismatch {
190            expected: hex::encode(root_public_key),
191            got: hex::encode(&result.root_public_key),
192        });
193    }
194
195    // return the enclave key
196    result.public_key = parse_enclave_key(&mut attestation_doc)?;
197
198    // check enclave public key if exists
199    if let Some(public_key) = expectations.public_key
200        && result.public_key.as_ref() != public_key
201    {
202        return Err(AttestationError::PublicKeyMismatch {
203            expected: hex::encode(public_key),
204            got: hex::encode(&result.public_key),
205        });
206    }
207
208    // return the user data
209    result.user_data = parse_user_data(&mut attestation_doc)?;
210
211    // check user data if exists
212    if let Some(user_data) = expectations.user_data
213        && result.user_data.as_ref() != user_data
214    {
215        return Err(AttestationError::UserDataMismatch {
216            expected: hex::encode(user_data),
217            got: hex::encode(&result.user_data),
218        });
219    }
220
221    Ok(result)
222}
223
224fn parse_attestation_doc(
225    attestation_doc: &[u8],
226) -> Result<(CoseSign1, BTreeMap<Value, Value>), AttestationError> {
227    let cosesign1 =
228        CoseSign1::from_bytes(attestation_doc).map_err(AttestationError::InvalidCose)?;
229    // SAFETY: method cannot fail if no key is proided
230    let payload = cosesign1
231        .get_payload::<CertHasher>(None)
232        .expect("cannot fail");
233    let cbor = serde_cbor::from_slice::<BTreeMap<Value, Value>>(&payload)
234        .map_err(AttestationError::InvalidCbor)?;
235
236    Ok((cosesign1, cbor))
237}
238
239fn parse_timestamp(attestation_doc: &mut BTreeMap<Value, Value>) -> Result<u64, AttestationError> {
240    let timestamp = attestation_doc
241        .remove(&"timestamp".to_owned().into())
242        .ok_or(AttestationError::MissingField("timestamp".into()))?;
243    let timestamp = (match timestamp {
244        Value::Integer(b) => Ok(b),
245        _ => Err(AttestationError::InvalidType("timestamp".into())),
246    })?;
247    let timestamp = timestamp.try_into()?;
248
249    Ok(timestamp)
250}
251
252fn parse_pcrs(
253    attestation_doc: &mut BTreeMap<Value, Value>,
254) -> Result<[[u8; 48]; 12], AttestationError> {
255    let pcrs_arr = attestation_doc
256        .remove(&"nitrotpm_pcrs".to_owned().into())
257        .ok_or(AttestationError::MissingField("nitrotpm_pcrs".into()))?;
258    let mut pcrs_arr = (match pcrs_arr {
259        Value::Map(b) => Ok(b),
260        _ => Err(AttestationError::InvalidType("nitrotpm_pcrs".to_string())),
261    })?;
262
263    let mut result = [[0; 48]; 12];
264    for (i, result_pcr) in result.iter_mut().take(12).enumerate() {
265        let i = i + 4;
266        let pcr = pcrs_arr
267            .remove(&(i as u32).into())
268            .ok_or(AttestationError::MissingField(format!("pcr{i}")))?;
269        let pcr = (match pcr {
270            Value::Bytes(b) => Ok(b),
271            _ => Err(AttestationError::InvalidType(format!("pcr{i}"))),
272        })?;
273        *result_pcr = pcr
274            .as_slice()
275            .try_into()
276            .map_err(|e| AttestationError::InvalidLength(format!("pcr{i}"), format!("{e}")))?;
277    }
278
279    Ok(result)
280}
281
282fn verify_root_of_trust(
283    attestation_doc: &mut BTreeMap<Value, Value>,
284    cosesign1: &CoseSign1,
285    timestamp: u64,
286) -> Result<Box<[u8]>, AttestationError> {
287    // verify attestation doc signature
288    let enclave_certificate_bytes = attestation_doc
289        .remove(&"certificate".to_owned().into())
290        .ok_or(AttestationError::MissingField("certificate".into()))?;
291    let enclave_certificate_bytes = (match enclave_certificate_bytes {
292        Value::Bytes(b) => Ok(b),
293        _ => Err(AttestationError::InvalidType("enclave certificate".into())),
294    })?;
295    let (_, cert) = X509Certificate::from_der(&enclave_certificate_bytes).map_err(|e| {
296        AttestationError::X509 {
297            context: "leaf".into(),
298            error: e,
299        }
300    })?;
301
302    // Extract public key for COSE verification
303    let verifier_cert = CertWrapper(&cert.tbs_certificate);
304
305    let verify_result = cosesign1
306        .verify_signature::<CertHasher>(&verifier_cert)
307        .map_err(AttestationError::CoseSignatureVerifyFailed)?;
308
309    if !verify_result {
310        return Err(AttestationError::LeafSignatureVerifyFailed);
311    }
312
313    // verify certificate chain
314    let cabundle = attestation_doc
315        .remove(&"cabundle".to_owned().into())
316        .ok_or(AttestationError::MissingField("cabundle".into()))?;
317    let mut cabundle = (match cabundle {
318        Value::Array(b) => Ok(b),
319        _ => Err(AttestationError::InvalidType("cabundle".into())),
320    })?;
321    cabundle.reverse();
322
323    let root_public_key = verify_cert_chain(cert, &cabundle, timestamp)?;
324
325    Ok(root_public_key)
326}
327
328fn verify_cert_chain(
329    cert: X509Certificate,
330    cabundle: &[Value],
331    timestamp: u64,
332) -> Result<Box<[u8]>, AttestationError> {
333    let mut certs = Vec::with_capacity(cabundle.len() + 1);
334    certs.push(cert);
335
336    for (i, cert_val) in cabundle.iter().enumerate() {
337        let cert_der = (match cert_val {
338            Value::Bytes(b) => Ok(b),
339            _ => Err(AttestationError::InvalidType("cert decode".into())),
340        })?;
341        let (_, cert) =
342            X509Certificate::from_der(cert_der).map_err(|e| AttestationError::X509 {
343                context: format!("bundle {}", i),
344                error: e,
345            })?;
346        certs.push(cert);
347    }
348
349    for i in 0..(certs.len() - 1) {
350        let issuer_spki = &certs[i + 1].tbs_certificate.subject_pki;
351
352        // Use Some(issuer_spki) as expected by x509-parser
353        certs[i]
354            .verify_signature(Some(issuer_spki))
355            .map_err(|_| AttestationError::CertChainSignatureFailed { index: i })?;
356
357        if certs[i + 1].tbs_certificate.subject != certs[i].tbs_certificate.issuer {
358            return Err(AttestationError::CertChainIssuerOrSubjectMismatch { index: i });
359        }
360
361        let current_time = ASN1Time::from_timestamp((timestamp / 1000) as i64).map_err(|e| {
362            AttestationError::X509 {
363                context: format!("timestamp {}", i),
364                error: e.into(),
365            }
366        })?;
367
368        if certs[i].tbs_certificate.validity.not_after < current_time
369            || certs[i].tbs_certificate.validity.not_before > current_time
370        {
371            return Err(AttestationError::CertChainExpired { index: i });
372        }
373    }
374
375    let root_public_key = certs
376        .last()
377        .ok_or(AttestationError::MissingField("root".into()))?
378        .tbs_certificate
379        .subject_pki
380        .subject_public_key
381        .data[1..]
382        .to_vec()
383        .into_boxed_slice();
384
385    Ok(root_public_key)
386}
387
388fn parse_enclave_key(
389    attestation_doc: &mut BTreeMap<Value, Value>,
390) -> Result<Box<[u8]>, AttestationError> {
391    let public_key = attestation_doc
392        .remove(&"public_key".to_owned().into())
393        .ok_or(AttestationError::MissingField("public_key".into()))?;
394    let public_key = (match public_key {
395        Value::Bytes(b) => Ok(b),
396        _ => Err(AttestationError::InvalidType("public_key".into())),
397    })?;
398
399    Ok(public_key.into_boxed_slice())
400}
401
402fn parse_user_data(
403    attestation_doc: &mut BTreeMap<Value, Value>,
404) -> Result<Box<[u8]>, AttestationError> {
405    let user_data = attestation_doc
406        .remove(&"user_data".to_owned().into())
407        .ok_or(AttestationError::MissingField("user_data".into()))?;
408    let user_data = (match user_data {
409        Value::Bytes(b) => Ok(b),
410        Value::Null => Ok(vec![]),
411        _ => Err(AttestationError::InvalidType("user_data".into())),
412    })?;
413
414    Ok(user_data.into_boxed_slice())
415}
416
417pub struct CertHasher;
418
419impl Hash for CertHasher {
420    fn hash(_algorithm: MessageDigest, data: &[u8]) -> Result<Vec<u8>, CoseError> {
421        // NOTE: the verifier function internally hashes the message, return it as is
422
423        // match algorithm {
424        //     MessageDigest::Sha256 => Ok(Sha256::digest(data).to_vec()),
425        //     MessageDigest::Sha384 => Ok(Sha384::digest(data).to_vec()),
426        //     MessageDigest::Sha512 => Ok(Sha512::digest(data).to_vec()),
427        // }
428        Ok(data.into())
429    }
430}
431
432struct CertWrapper<'a>(&'a TbsCertificate<'a>);
433
434impl<'a> SigningPublicKey for CertWrapper<'a> {
435    fn get_parameters(&self) -> Result<(SignatureAlgorithm, MessageDigest), CoseError> {
436        if self.0.subject_pki.algorithm.algorithm != OID_KEY_TYPE_EC_PUBLIC_KEY {
437            return Err(CoseError::UnsupportedError("Unsupported key type".into()));
438        }
439        match self.0.subject_pki.subject_public_key.data.len() {
440            65 => Ok((SignatureAlgorithm::ES256, MessageDigest::Sha256)),
441            97 => Ok((SignatureAlgorithm::ES384, MessageDigest::Sha384)),
442            129 => Ok((SignatureAlgorithm::ES512, MessageDigest::Sha512)),
443            _ => Err(CoseError::UnsupportedError("Unsupported key type".into())),
444        }
445    }
446
447    fn verify(&self, digest: &[u8], signature: &[u8]) -> Result<bool, CoseError> {
448        if self.0.signature.algorithm != OID_SIG_ECDSA_WITH_SHA384 {
449            return Err(CoseError::UnsupportedError(
450                "Unsupported signature type".into(),
451            ));
452        }
453        let pubkey = UnparsedPublicKey::new(
454            &ECDSA_P384_SHA384_FIXED,
455            &self.0.subject_pki.subject_public_key.data,
456        );
457        pubkey
458            .verify(digest, signature)
459            .map_err(|_| CoseError::UnverifiedSignature)
460            .map(|_| true)
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use hex_literal::hex;
467
468    use crate::attestation::{AWS_ROOT_KEY, AttestationExpectations, MOCK_ROOT_KEY};
469
470    use super::verify;
471
472    // generated using `curl <ip>:<port>/attestation/raw`
473    // on the attestation server of a real instance
474    #[test]
475    fn test_aws_none_specified() {
476        let attestation =
477            std::fs::read(file!().rsplit_once('/').unwrap().0.to_owned() + "/testcases/aws.bin")
478                .unwrap();
479
480        let decoded = verify(&attestation, Default::default()).unwrap();
481
482        assert_eq!(decoded.timestamp_ms, 0x0000019ba6c8dab7);
483        assert_eq!(
484            decoded.pcrs[0],
485            hex!(
486                "6e7fd58cca2dcd0b6ca3186c260e47f33d33e4c63b9819609d5b75efb55453529fed4098a7383bfca18d3ca869ff202f"
487            )
488        );
489        assert_eq!(
490            decoded.pcrs[1],
491            hex!(
492                "c10ef05cbff856c3b0b83793118e887985b0ab263162db25badf0affcb01746494a984db2ba517608c2eade447d2dbe9"
493            )
494        );
495        assert_eq!(
496            decoded.pcrs[2],
497            hex!(
498                "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4"
499            )
500        );
501        assert_eq!(
502            decoded.pcrs[3],
503            hex!(
504                "98441c7f7625d10058c47683aec486ce311c633235eb555593a7ee791121e3578ae72d04ecef661f272d59058b77af35"
505            )
506        );
507        assert_eq!(
508            decoded.pcrs[4],
509            hex!(
510                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
511            )
512        );
513        assert_eq!(
514            decoded.pcrs[5],
515            hex!(
516                "39c33c06e6a163f8f04f23b43d1b342677abfcd3bebbd3777bf0429fadb6a7ef5e2e533c0bf7d6dc9aec44370b29d5f4"
517            )
518        );
519        assert_eq!(
520            decoded.pcrs[6],
521            hex!(
522                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
523            )
524        );
525        assert_eq!(
526            decoded.pcrs[7],
527            hex!(
528                "6dac65857819c581c671ab7edafac3cdf85700c880392d98127af1c94e305f96baa73564f721bf0cee3b190d90c2750f"
529            )
530        );
531        assert_eq!(
532            decoded.pcrs[8],
533            hex!(
534                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
535            )
536        );
537        assert_eq!(
538            decoded.pcrs[9],
539            hex!(
540                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
541            )
542        );
543        assert_eq!(
544            decoded.pcrs[10],
545            hex!(
546                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
547            )
548        );
549        assert_eq!(
550            decoded.pcrs[11],
551            hex!(
552                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
553            )
554        );
555        assert_eq!(decoded.user_data.as_ref(), hex!("1234"));
556        assert_eq!(decoded.public_key.as_ref(), hex!("abcd"));
557        assert_eq!(decoded.root_public_key.as_ref(), AWS_ROOT_KEY);
558        assert_eq!(
559            decoded.image_id,
560            hex!("b3dc7d48651dec3f088737ce37e0806a28e516e0caa01d54cf9438176a1c4d00")
561        );
562    }
563
564    // generated using `curl <ip>:<port>/attestation/raw`
565    // on the attestation server of a real Nitro enclave
566    #[test]
567    fn test_aws_all_specified() {
568        let attestation =
569            std::fs::read(file!().rsplit_once('/').unwrap().0.to_owned() + "/testcases/aws.bin")
570                .unwrap();
571
572        let decoded = verify(
573            &attestation,
574            AttestationExpectations {
575                timestamp_ms: Some(0x0000019ba6c8dab7),
576                age_ms: Some((
577                    300000,
578                    0x0000019ba6c8dab7 + 300000,
579                )),
580                pcrs: [
581                    Some(hex!( "6e7fd58cca2dcd0b6ca3186c260e47f33d33e4c63b9819609d5b75efb55453529fed4098a7383bfca18d3ca869ff202f")),
582                    Some(hex!( "c10ef05cbff856c3b0b83793118e887985b0ab263162db25badf0affcb01746494a984db2ba517608c2eade447d2dbe9")),
583                    Some(hex!("518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4")),
584                    Some(hex!("98441c7f7625d10058c47683aec486ce311c633235eb555593a7ee791121e3578ae72d04ecef661f272d59058b77af35")),
585                    Some(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
586                    Some(hex!("39c33c06e6a163f8f04f23b43d1b342677abfcd3bebbd3777bf0429fadb6a7ef5e2e533c0bf7d6dc9aec44370b29d5f4")),
587                    Some(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
588                    Some(hex!("6dac65857819c581c671ab7edafac3cdf85700c880392d98127af1c94e305f96baa73564f721bf0cee3b190d90c2750f")),
589                    Some(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
590                    Some(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
591                    Some(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
592                    Some(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"))
593                ],
594                public_key: Some(&hex!("abcd")),
595                user_data: Some(&hex!("1234")),
596                root_public_key: Some(&AWS_ROOT_KEY),
597                image_id: Some(&hex!("b3dc7d48651dec3f088737ce37e0806a28e516e0caa01d54cf9438176a1c4d00")),
598            },
599        )
600        .unwrap();
601
602        assert_eq!(decoded.timestamp_ms, 0x0000019ba6c8dab7);
603        assert_eq!(
604            decoded.pcrs[0],
605            hex!(
606                "6e7fd58cca2dcd0b6ca3186c260e47f33d33e4c63b9819609d5b75efb55453529fed4098a7383bfca18d3ca869ff202f"
607            )
608        );
609        assert_eq!(
610            decoded.pcrs[1],
611            hex!(
612                "c10ef05cbff856c3b0b83793118e887985b0ab263162db25badf0affcb01746494a984db2ba517608c2eade447d2dbe9"
613            )
614        );
615        assert_eq!(
616            decoded.pcrs[2],
617            hex!(
618                "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4"
619            )
620        );
621        assert_eq!(
622            decoded.pcrs[3],
623            hex!(
624                "98441c7f7625d10058c47683aec486ce311c633235eb555593a7ee791121e3578ae72d04ecef661f272d59058b77af35"
625            )
626        );
627        assert_eq!(
628            decoded.pcrs[4],
629            hex!(
630                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
631            )
632        );
633        assert_eq!(
634            decoded.pcrs[5],
635            hex!(
636                "39c33c06e6a163f8f04f23b43d1b342677abfcd3bebbd3777bf0429fadb6a7ef5e2e533c0bf7d6dc9aec44370b29d5f4"
637            )
638        );
639        assert_eq!(
640            decoded.pcrs[6],
641            hex!(
642                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
643            )
644        );
645        assert_eq!(
646            decoded.pcrs[7],
647            hex!(
648                "6dac65857819c581c671ab7edafac3cdf85700c880392d98127af1c94e305f96baa73564f721bf0cee3b190d90c2750f"
649            )
650        );
651        assert_eq!(
652            decoded.pcrs[8],
653            hex!(
654                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
655            )
656        );
657        assert_eq!(
658            decoded.pcrs[9],
659            hex!(
660                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
661            )
662        );
663        assert_eq!(
664            decoded.pcrs[10],
665            hex!(
666                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
667            )
668        );
669        assert_eq!(
670            decoded.pcrs[11],
671            hex!(
672                "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
673            )
674        );
675        assert_eq!(decoded.user_data.as_ref(), hex!("1234"));
676        assert_eq!(decoded.public_key.as_ref(), hex!("abcd"));
677        assert_eq!(decoded.root_public_key.as_ref(), AWS_ROOT_KEY);
678        assert_eq!(
679            decoded.image_id,
680            hex!("b3dc7d48651dec3f088737ce37e0806a28e516e0caa01d54cf9438176a1c4d00")
681        );
682    }
683
684    // generated using `curl <ip>:<port>/attestation/raw?public_key=abcd&user_data=1234`
685    // on a custom mock attestation server running locally
686    #[test]
687    fn test_mock_none_specified() {
688        let attestation =
689            std::fs::read(file!().rsplit_once('/').unwrap().0.to_owned() + "/testcases/mock.bin")
690                .unwrap();
691
692        let decoded = verify(&attestation, Default::default()).unwrap();
693
694        assert_eq!(decoded.timestamp_ms, 0x0000019ba7060dce);
695        assert_eq!(decoded.pcrs[0], [4; 48]);
696        assert_eq!(decoded.pcrs[1], [5; 48]);
697        assert_eq!(decoded.pcrs[2], [6; 48]);
698        assert_eq!(decoded.pcrs[3], [7; 48]);
699        assert_eq!(decoded.pcrs[4], [8; 48]);
700        assert_eq!(decoded.pcrs[5], [9; 48]);
701        assert_eq!(decoded.pcrs[6], [10; 48]);
702        assert_eq!(decoded.pcrs[7], [11; 48]);
703        assert_eq!(decoded.pcrs[8], [12; 48]);
704        assert_eq!(decoded.pcrs[9], [13; 48]);
705        assert_eq!(decoded.pcrs[10], [14; 48]);
706        assert_eq!(decoded.pcrs[11], [15; 48]);
707        assert_eq!(decoded.user_data.as_ref(), hex!("1234"));
708        assert_eq!(decoded.public_key.as_ref(), hex!("abcd"));
709        assert_eq!(decoded.root_public_key.as_ref(), MOCK_ROOT_KEY);
710        assert_eq!(
711            decoded.image_id,
712            hex!("e3caee4ad768d705b977a3687c74b25ff89e4dbd71091e16e04b3f9f867c926b")
713        );
714    }
715
716    // generated using `curl <ip>:<port>/attestation/raw?public_key=abcd&user_data=1234`
717    // on a custom mock attestation server running locally
718    #[test]
719    fn test_mock_all_specified() {
720        let attestation =
721            std::fs::read(file!().rsplit_once('/').unwrap().0.to_owned() + "/testcases/mock.bin")
722                .unwrap();
723
724        let decoded = verify(
725            &attestation,
726            AttestationExpectations {
727                timestamp_ms: Some(0x0000019ba7060dce),
728                age_ms: Some((300000, 0x0000019ba7060dce + 300000)),
729                pcrs: [
730                    Some([4; 48]),
731                    Some([5; 48]),
732                    Some([6; 48]),
733                    Some([7; 48]),
734                    Some([8; 48]),
735                    Some([9; 48]),
736                    Some([10; 48]),
737                    Some([11; 48]),
738                    Some([12; 48]),
739                    Some([13; 48]),
740                    Some([14; 48]),
741                    Some([15; 48]),
742                ],
743                public_key: Some(&hex!("abcd")),
744                user_data: Some(&hex!("1234")),
745                root_public_key: Some(&MOCK_ROOT_KEY),
746                image_id: Some(&hex!(
747                    "e3caee4ad768d705b977a3687c74b25ff89e4dbd71091e16e04b3f9f867c926b"
748                )),
749            },
750        )
751        .unwrap();
752
753        assert_eq!(decoded.timestamp_ms, 0x0000019ba7060dce);
754        assert_eq!(decoded.pcrs[0], [4; 48]);
755        assert_eq!(decoded.pcrs[1], [5; 48]);
756        assert_eq!(decoded.pcrs[2], [6; 48]);
757        assert_eq!(decoded.pcrs[3], [7; 48]);
758        assert_eq!(decoded.pcrs[4], [8; 48]);
759        assert_eq!(decoded.pcrs[5], [9; 48]);
760        assert_eq!(decoded.pcrs[6], [10; 48]);
761        assert_eq!(decoded.pcrs[7], [11; 48]);
762        assert_eq!(decoded.pcrs[8], [12; 48]);
763        assert_eq!(decoded.pcrs[9], [13; 48]);
764        assert_eq!(decoded.pcrs[10], [14; 48]);
765        assert_eq!(decoded.pcrs[11], [15; 48]);
766        assert_eq!(decoded.user_data.as_ref(), hex!("1234"));
767        assert_eq!(decoded.public_key.as_ref(), hex!("abcd"));
768        assert_eq!(decoded.root_public_key.as_ref(), MOCK_ROOT_KEY);
769        assert_eq!(
770            decoded.image_id,
771            hex!("e3caee4ad768d705b977a3687c74b25ff89e4dbd71091e16e04b3f9f867c926b")
772        );
773    }
774}