oasis_core_runtime/common/sgx/
ias.rs

1//! Intel Attestation Service (IAS) attestation verification report handling.
2use std::io::{Cursor, Read, Seek, SeekFrom};
3
4use anyhow::{anyhow, Result};
5use base64::prelude::*;
6use byteorder::{LittleEndian, ReadBytesExt};
7use chrono::prelude::*;
8use lazy_static::lazy_static;
9use oid_registry::{OID_PKCS1_RSAENCRYPTION, OID_PKCS1_SHA256WITHRSA};
10use rsa::{pkcs1::DecodeRsaPublicKey, pkcs1v15::Pkcs1v15Sign, RsaPublicKey};
11use sgx_isa::{AttributesFlags, Report};
12use sha2::{digest::Update as _, Digest, Sha256};
13use thiserror::Error;
14use x509_parser::prelude::*;
15
16use crate::common::{
17    sgx::{EnclaveIdentity, MrEnclave, MrSigner, VerifiedQuote},
18    time::{insecure_posix_time, update_insecure_posix_time},
19};
20
21/// AVR verification error.
22#[derive(Error, Debug)]
23enum AVRError {
24    #[error("failed to parse report body")]
25    MalformedReportBody,
26    #[error("report body did not contain timestamp")]
27    MissingTimestamp,
28    #[error("failed to parse timestamp")]
29    MalformedTimestamp,
30    #[error("timestamp differs by more than 1 day")]
31    TimestampOutOfRange,
32    #[error("rejecting quote status ({status:?})")]
33    QuoteStatusInvalid { status: String },
34    #[error("debug enclaves not allowed")]
35    DebugEnclave,
36    #[error("production enclaves not allowed")]
37    ProductionEnclave,
38    #[error("AVR did not contain quote status")]
39    MissingQuoteStatus,
40    #[error("AVR did not contain quote body")]
41    MissingQuoteBody,
42    #[error("failed to parse quote")]
43    MalformedQuote,
44    #[error("unable to find exactly 2 certificates")]
45    ChainNotTwoCertificates,
46    #[error("malformed certificate PEM")]
47    MalformedCertificatePEM,
48    #[error("malformed certificate DER")]
49    MalformedCertificateDER,
50    #[error("expired certificate")]
51    ExpiredCertificate,
52    #[error("invalid signature")]
53    InvalidSignature,
54    #[error("IAS quotes are disabled by policy")]
55    Disabled,
56    #[error("blacklisted IAS quote GID")]
57    BlacklistedGID,
58    #[error("TCB evaluation data number is invalid")]
59    TCBEvaluationDataNumberInvalid,
60}
61
62pub const QUOTE_CONTEXT_LEN: usize = 8;
63/// The purpose of `QuoteContext` is to prevent quotes from being used in
64/// different contexts. The value is included as a prefix in report data.
65pub type QuoteContext = [u8; QUOTE_CONTEXT_LEN];
66
67// AVR signature validation constants.
68const IAS_TRUST_ANCHOR_PEM: &str = r#"-----BEGIN CERTIFICATE-----
69MIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
70BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV
71BAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0
72YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy
73MzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL
74U2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD
75DCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G
76CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR+tXc8u1EtJzLA10Feu1Wg+p7e
77LmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh
78rgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT
79L/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe
80NpEJUmg4ktal4qgIAxk+QHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ
81byinkNndn+Bgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H
82afuVeLHcDsRp6hol4P+ZFIhu8mmbI1u0hH3W/0C2BuYXB5PC+5izFFh/nP0lc2Lf
836rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM
84RoOaX4AS+909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX
85MFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50
86L0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW
87BBR4Q3t2pn680K9+QjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9+Qjfr
88NXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq
89hkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir
90IEqucRiJSSx+HjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi+ripMtPZ
91sFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi
92zLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra
93Ud4APK0wZTGtfPXU7w+IBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA
94152Sq049ESDz+1rRGc2NVEqh1KaGXmtXvqxXcTB+Ljy5Bw2ke0v8iGngFBPqCTVB
953op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5+xmBc388v9Dm21HGfcC8O
96DD+gT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R+mJTLwPXVMrv
97DaVzWh5aiEx+idkSGMnX
98-----END CERTIFICATE-----"#;
99const PEM_CERTIFICATE_LABEL: &str = "CERTIFICATE";
100const IAS_TS_FMT: &str = "%FT%T%.6f";
101lazy_static! {
102    static ref IAS_TRUST_ANCHOR: Vec<u8> = {
103        let pem = match parse_x509_pem(IAS_TRUST_ANCHOR_PEM.as_bytes()) {
104            Ok((rem, pem)) => {
105                assert!(rem.is_empty(), "anchor PEM has trailing garbage");
106                assert!(
107                    pem.label == PEM_CERTIFICATE_LABEL,
108                    "PEM does not contain a certificate: '{:?}'",
109                    pem.label
110                );
111                pem
112            }
113            err => panic!("failed to decode anchor PEM: {:?}", err),
114        };
115
116        pem.contents.to_vec()
117    };
118}
119
120/// Quote validity policy.
121#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
122pub struct QuotePolicy {
123    /// Whether IAS quotes are disabled and will always be rejected.
124    #[cbor(optional)]
125    pub disabled: bool,
126
127    /// Allowed quote statuses.
128    ///
129    /// Note: QuoteOK and QuoteSwHardeningNeeded are ALWAYS allowed, and do not need to be
130    /// specified.
131    #[cbor(optional)]
132    pub allowed_quote_statuses: Vec<i64>, // TODO: Define ISVEnclaveQuoteStatus type.
133
134    /// List of blocked platform EPID group IDs.
135    #[cbor(optional)]
136    pub gid_blacklist: Vec<u32>,
137
138    /// The minimum TCB evaluation data number that should be accepted.
139    #[cbor(optional)]
140    pub min_tcb_evaluation_data_number: u32,
141}
142
143/// Decoded quote body.
144#[derive(Default, Debug)]
145struct QuoteBody {
146    version: u16,
147    signature_type: u16,
148    gid: u32,
149    isv_svn_qe: u16,
150    isv_svn_pce: u16,
151    basename: [u8; 32],
152    report_body: Report,
153}
154
155#[allow(clippy::unused_io_amount)]
156impl QuoteBody {
157    /// Decode quote body.
158    fn decode(quote_body: &[u8]) -> Result<QuoteBody> {
159        let mut reader = Cursor::new(quote_body);
160        let mut quote_body: QuoteBody = QuoteBody::default();
161
162        // TODO: Should we ensure that reserved bytes are all zero?
163
164        // Quote body.
165        quote_body.version = reader.read_u16::<LittleEndian>()?;
166        quote_body.signature_type = reader.read_u16::<LittleEndian>()?;
167        quote_body.gid = reader.read_u32::<LittleEndian>()?;
168        quote_body.isv_svn_qe = reader.read_u16::<LittleEndian>()?;
169        quote_body.isv_svn_pce = reader.read_u16::<LittleEndian>()?;
170        reader.seek(SeekFrom::Current(4))?; // 4 reserved bytes.
171        reader.read_exact(&mut quote_body.basename)?;
172
173        // Report body.
174        let mut report_buf = vec![0; Report::UNPADDED_SIZE];
175        reader.read(&mut report_buf)?;
176        quote_body.report_body = match Report::try_copy_from(&report_buf) {
177            Some(r) => r,
178            None => return Err(AVRError::MalformedReportBody.into()),
179        };
180
181        Ok(quote_body)
182    }
183}
184
185/// Attestation verification report.
186#[derive(Debug, Default, Clone, PartialEq, Eq, cbor::Encode, cbor::Decode)]
187pub struct AVR {
188    pub body: Vec<u8>,
189    pub signature: Vec<u8>,
190    pub certificate_chain: Vec<u8>,
191}
192
193/// Parsed AVR body.
194#[derive(Debug, Clone)]
195pub(crate) struct ParsedAVR {
196    body: serde_json::Value,
197}
198
199impl ParsedAVR {
200    pub(crate) fn new(avr: &AVR) -> Result<Self> {
201        let body = match serde_json::from_slice(&avr.body) {
202            Ok(avr_body) => avr_body,
203            _ => return Err(AVRError::MalformedReportBody.into()),
204        };
205        Ok(Self { body })
206    }
207
208    fn isv_enclave_quote_status(&self) -> Result<String> {
209        match self.body["isvEnclaveQuoteStatus"].as_str() {
210            Some(status) => Ok(status.to_string()),
211            None => Err(AVRError::MissingQuoteStatus.into()),
212        }
213    }
214
215    fn isv_enclave_quote_body(&self) -> Result<String> {
216        match self.body["isvEnclaveQuoteBody"].as_str() {
217            Some(quote_body) => Ok(quote_body.to_string()),
218            None => Err(AVRError::MissingQuoteBody.into()),
219        }
220    }
221
222    fn tcb_evaluation_data_number(&self) -> Result<u32> {
223        match self.body["tcbEvaluationDataNumber"].as_u64() {
224            None => Ok(0),
225            Some(eval_num) if eval_num > Into::<u64>::into(u32::MAX) => {
226                Err(AVRError::TCBEvaluationDataNumberInvalid.into())
227            }
228            Some(eval_num) => Ok(eval_num as u32),
229        }
230    }
231
232    fn timestamp(&self) -> Result<i64> {
233        let timestamp = match self.body["timestamp"].as_str() {
234            Some(timestamp) => timestamp,
235            None => {
236                return Err(AVRError::MissingTimestamp.into());
237            }
238        };
239        parse_avr_timestamp(timestamp)
240    }
241}
242
243/// Verify attestation report.
244pub fn verify(avr: &AVR, policy: &QuotePolicy) -> Result<VerifiedQuote> {
245    if policy.disabled {
246        return Err(AVRError::Disabled.into());
247    }
248
249    let unsafe_skip_avr_verification = option_env!("OASIS_UNSAFE_SKIP_AVR_VERIFY").is_some();
250    let unsafe_lax_avr_verification = option_env!("OASIS_UNSAFE_LAX_AVR_VERIFY").is_some();
251
252    // Get the time.
253    let timestamp_now = insecure_posix_time();
254
255    // Verify IAS signature.
256    if !unsafe_skip_avr_verification {
257        validate_avr_signature(
258            &avr.certificate_chain,
259            &avr.body,
260            &avr.signature,
261            timestamp_now as u64,
262        )?;
263    }
264
265    // Parse AV report body.
266    let avr_body = ParsedAVR::new(avr)?;
267
268    // Check timestamp, reject if report is too old.
269    let timestamp = avr_body.timestamp()?;
270    if !timestamp_is_fresh(timestamp_now, timestamp) {
271        return Err(AVRError::TimestampOutOfRange.into());
272    }
273
274    let quote_status = avr_body.isv_enclave_quote_status()?;
275    match quote_status.as_str() {
276        "OK" | "SW_HARDENING_NEEDED" => {}
277        "GROUP_OUT_OF_DATE" | "CONFIGURATION_NEEDED" | "CONFIGURATION_AND_SW_HARDENING_NEEDED" => {
278            if !unsafe_lax_avr_verification {
279                return Err(AVRError::QuoteStatusInvalid {
280                    status: quote_status.to_owned(),
281                }
282                .into());
283            }
284        }
285        _ => {
286            return Err(AVRError::QuoteStatusInvalid {
287                status: quote_status.to_owned(),
288            }
289            .into());
290        }
291    };
292
293    let quote_body = avr_body.isv_enclave_quote_body()?;
294    let quote_body = match BASE64_STANDARD.decode(quote_body) {
295        Ok(quote_body) => quote_body,
296        _ => return Err(AVRError::MalformedQuote.into()),
297    };
298    let quote_body = match QuoteBody::decode(&quote_body) {
299        Ok(quote_body) => quote_body,
300        _ => return Err(AVRError::MalformedQuote.into()),
301    };
302
303    // Verify EPID GID not blacklisted.
304    if policy
305        .gid_blacklist
306        .iter()
307        .any(|gid| gid == &quote_body.gid)
308    {
309        return Err(AVRError::BlacklistedGID.into());
310    }
311
312    // Disallow debug enclaves, if we are in production environment and disallow production enclaves,
313    // if we are in debug environment.
314    let is_debug = quote_body
315        .report_body
316        .attributes
317        .flags
318        .contains(AttributesFlags::DEBUG);
319    let allow_debug = option_env!("OASIS_UNSAFE_ALLOW_DEBUG_ENCLAVES").is_some();
320    if is_debug && !allow_debug {
321        return Err(AVRError::DebugEnclave.into());
322    } else if !is_debug && allow_debug {
323        return Err(AVRError::ProductionEnclave.into());
324    }
325
326    // If the minimum TCB evaluation data number is present in the policy,
327    // then the report's shouldn't be too low.
328    if avr_body.tcb_evaluation_data_number()? < policy.min_tcb_evaluation_data_number {
329        return Err(AVRError::TCBEvaluationDataNumberInvalid.into());
330    }
331
332    // Force-ratchet the clock forward, to at least the time in the AVR.
333    update_insecure_posix_time(timestamp);
334
335    Ok(VerifiedQuote {
336        report_data: quote_body.report_body.reportdata.to_vec(),
337        identity: EnclaveIdentity {
338            mr_enclave: MrEnclave::from(quote_body.report_body.mrenclave.to_vec()),
339            mr_signer: MrSigner::from(quote_body.report_body.mrsigner.to_vec()),
340        },
341        timestamp,
342    })
343}
344
345fn parse_avr_timestamp(timestamp: &str) -> Result<i64> {
346    let timestamp_unix = match NaiveDateTime::parse_from_str(timestamp, IAS_TS_FMT) {
347        Ok(timestamp) => timestamp.and_utc().timestamp(),
348        _ => return Err(AVRError::MalformedTimestamp.into()),
349    };
350    Ok(timestamp_unix)
351}
352
353fn validate_avr_signature(
354    cert_chain: &[u8],
355    message: &[u8],
356    signature: &[u8],
357    unix_time: u64,
358) -> Result<()> {
359    // WARNING: This is the entirely wrong way to validate a certificate
360    // chain as it does not come close to implementing anything resembling
361    // what is specified in RFC 5280 6.1.  There probably should be a CRL
362    // check here as well, now that I think about it.
363    //
364    // The main assumptions made about how exactly the signing key is
365    // certified/distributed, and the AVR is signed are based on the
366    // following documentation:
367    //
368    //  * 4.2.2 Report Signature:
369    //    * "The Attestation Verification Report is cryptographically
370    //       signed by Report Signing Key (owned by the Attestation
371    //       Service) using the RSA-SHA256 algorithm."
372    //  * 4.2.3 Report Signing Certificate Chain:
373    //    * "The public part of Report Key is distributed in the form
374    //       of an x.509 digital certificate called Attestation Report
375    //       Signing Certificate. It is a leaf certificate issued by
376    //       the Attestation Report Signing CA Certificate"
377    //    * "A PEM-encoded certificate chain consisting of Attestation
378    //       Report Signing Certificate and Attestation Report Signing
379    //       CA Certificate is returned..."
380    //
381    // See: "Attestation Service for Intel(R) Software Guard Extensions
382    // (Intel(R) SGX): API Documentation" (Revision: 6.0)
383
384    // Decode the certificate chain from percent encoded PEM to DER.
385    let raw_pem = percent_encoding::percent_decode(cert_chain).decode_utf8()?;
386    let mut cert_ders = Vec::new();
387    for pem in pem::Pem::iter_from_buffer(raw_pem.as_bytes()) {
388        let pem = match pem {
389            Ok(p) => p,
390            Err(_) => return Err(AVRError::MalformedCertificatePEM.into()),
391        };
392        if pem.label != PEM_CERTIFICATE_LABEL {
393            return Err(AVRError::MalformedCertificatePEM.into());
394        }
395        cert_ders.push(pem.contents);
396    }
397
398    // IAS per the API will only ever send two certificates.
399    if cert_ders.len() != 2 {
400        return Err(AVRError::ChainNotTwoCertificates.into());
401    }
402
403    // Convert our timestamp to something that can be used to check
404    // certificate expiration.
405    let time = ASN1Time::from_timestamp(unix_time as i64)?;
406
407    // Attestation Report Signing CA Certificate:
408    //
409    // Ensure that it matches the hard-coded copy, and decode it, so
410    // that the expiration can be validated and the public key can
411    // be used to verify the leaf certificate's signature.
412    //
413    // This could be more paranoid and check that the cert doesn't
414    // have trailing garbage, the usage is correct, etc, but we can
415    // take it as a matter of faith that it is well-formed since
416    // it is the same as the hard-coded one.
417    //
418    // TODO/perf: In theory this can be done once and only once, but
419    // the borrow checker thwarted my attempts to initialize a tuple
420    // containing a X509Certificate and Pem via lazy_static.
421    if cert_ders[1] != *IAS_TRUST_ANCHOR {
422        return Err(anyhow!("AVR certificate chain trust anchor mismatch"));
423    }
424    let anchor = match parse_x509_certificate(&cert_ders[1]) {
425        Ok((_, cert)) => cert,
426        Err(_) => return Err(AVRError::MalformedCertificateDER.into()),
427    };
428    if !anchor.validity().is_valid_at(time) {
429        return Err(AVRError::ExpiredCertificate.into());
430    }
431    let anchor_pk = extract_certificate_rsa_public_key(&anchor)?;
432    if !check_certificate_rsa_signature(&anchor, &anchor_pk) {
433        // The hard-coded cert is self-signed.  This will need to be
434        // changed if it ever isn't.
435        return Err(anyhow!(
436            "AVR certificate chain trust anchor has invalid signature"
437        ));
438    }
439    if !anchor.tbs_certificate.is_ca() {
440        return Err(anyhow!("AVR certificate trust anchor is not a CA"));
441    }
442
443    // Attestation Report Signing Certificate (leaf):
444    //
445    // Decode the certificate, ensure that it appears to be sensible,
446    // and then pull out the public key that presumably signs the AVR.
447    let leaf = match parse_x509_certificate(&cert_ders[0]) {
448        Ok((rem, cert)) => {
449            if !rem.is_empty() {
450                return Err(AVRError::MalformedCertificateDER.into());
451            }
452            cert
453        }
454        Err(_) => return Err(AVRError::MalformedCertificateDER.into()),
455    };
456    if !check_certificate_rsa_signature(&leaf, &anchor_pk) {
457        return Err(anyhow!("invalid leaf certificate signature"));
458    }
459
460    if !leaf.validity().is_valid_at(time) {
461        return Err(AVRError::ExpiredCertificate.into());
462    }
463    match leaf.tbs_certificate.key_usage()? {
464        Some(ku) => {
465            if !ku.value.digital_signature() {
466                return Err(anyhow!("leaf certificate can't sign"));
467            }
468        }
469        None => {
470            return Err(anyhow!("leaf cert missing key usage"));
471        }
472    }
473
474    // Validate the actual signature.
475    let leaf_pk = extract_certificate_rsa_public_key(&leaf)?;
476    let scheme = Pkcs1v15Sign::new::<sha2::Sha256>();
477    let digest = Sha256::new().chain(message).finalize();
478    let signature = BASE64_STANDARD.decode(signature)?;
479    leaf_pk
480        .verify(scheme, &digest, &signature)
481        .map_err(|_| AVRError::InvalidSignature)?;
482    Ok(())
483}
484
485fn extract_certificate_rsa_public_key(cert: &X509Certificate) -> Result<RsaPublicKey> {
486    let cert_spki = &cert.tbs_certificate.subject_pki;
487    if cert_spki.algorithm.algorithm != OID_PKCS1_RSAENCRYPTION {
488        return Err(anyhow!("invalid certificate public key algorithm"));
489    }
490
491    match RsaPublicKey::from_pkcs1_der(&cert_spki.subject_public_key.data) {
492        Ok(pk) => Ok(pk),
493        Err(err) => Err(anyhow!("invalid certificate public key: {:?}", err)),
494    }
495}
496
497fn check_certificate_rsa_signature(cert: &X509Certificate, public_key: &RsaPublicKey) -> bool {
498    if cert.signature_algorithm.algorithm != OID_PKCS1_SHA256WITHRSA {
499        return false;
500    }
501    let scheme = Pkcs1v15Sign::new::<sha2::Sha256>();
502    let digest = Sha256::new()
503        .chain(cert.tbs_certificate.as_ref())
504        .finalize();
505
506    public_key
507        .verify(scheme, &digest, &cert.signature_value.data)
508        .is_ok()
509}
510
511/// Return true iff the (POXIX) timestamp is considered "fresh" for the purposes
512/// of a cached AVR, given the current time.
513pub(crate) fn timestamp_is_fresh(now: i64, timestamp: i64) -> bool {
514    (now - timestamp).abs() < 60 * 60 * 24
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520
521    const IAS_CERT_CHAIN: &[u8] =
522        include_bytes!("../../../testdata/avr_certificates_urlencoded.pem");
523
524    #[test]
525    fn test_validate_avr_signature() {
526        const MSG: &[u8] = include_bytes!("../../../testdata/avr_body_group_out_of_date.json");
527        const SIG: &[u8] = include_bytes!("../../../testdata/avr_signature_group_out_of_date.sig");
528        const SIG_AT: u64 = 1522447346; // 2018-03-30T22:02:26
529
530        // Positive test.
531        let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, SIG, SIG_AT);
532        assert!(result.is_ok());
533
534        // Invalid timestamp.
535        let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, SIG, 0);
536        assert!(result.is_err());
537
538        // Bad message.
539        let bad_msg: &mut [u8] = &mut MSG.to_owned();
540        bad_msg[0] ^= 0x23;
541        let result = validate_avr_signature(IAS_CERT_CHAIN, bad_msg, SIG, SIG_AT);
542        assert!(result.is_err());
543
544        // Bad signature.
545        let bad_sig = BASE64_STANDARD.decode(SIG).unwrap();
546        let bad_sig = &mut bad_sig.to_owned();
547        bad_sig[0] ^= 0x42;
548        let bad_sig = BASE64_STANDARD.encode(bad_sig);
549        let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, bad_sig.as_bytes(), SIG_AT);
550        assert!(result.is_err());
551
552        // Test timestamp validation while we're at it.
553        let timestamp = parse_avr_timestamp("2018-03-30T22:02:26.123456").unwrap();
554        assert_eq!(timestamp, SIG_AT as i64);
555    }
556
557    #[test]
558    fn test_decode_avr_v4() {
559        const AVR: &[u8] = include_bytes!(
560            "../../../../go/common/sgx/ias/testdata/avr_v4_body_sw_hardening_needed.json"
561        );
562        const SIG: &[u8] = include_bytes!(
563            "../../../../go/common/sgx/ias/testdata/avr_v4_body_sw_hardening_needed.sig"
564        );
565        const SIG_AT: u64 = 1589188875; // 2020-05-11T09:21:15
566        const ID: &str = "323119119247496566074708526703373820736";
567        const NONCE: &str = "biNMqBAuTPF2hp/0fXa4P3splRkLHJf0";
568        const EPID_PSEUDONYM: &str = "uAFRLXADu90LsPq9Btgx8MWUPOzmDHE51pwLlUlU3hzFUk2EmvWpF6fZsyokOVkQUJ0UwZk0nCF8XPaCcSmLwqXAzLa+n/K7TdwlxKofEyTgG8da8mmrShNoFw3BSD74wSA4aAc753IfrbnnmuYk00lkmSUOTzqsqHlAORcweqg=";
569
570        let result = validate_avr_signature(IAS_CERT_CHAIN, AVR, SIG, SIG_AT);
571        assert!(result.is_ok());
572
573        let raw_avr = AVR {
574            body: AVR.to_vec(),
575            signature: SIG.to_vec(),
576            certificate_chain: IAS_CERT_CHAIN.to_vec(),
577        };
578        let avr = ParsedAVR::new(&raw_avr).expect("parsing raw AVR should succeed");
579        assert_eq!(avr.body["id"].as_str().expect("id should be present"), ID);
580        assert_eq!(
581            avr.timestamp().expect("timestamp should exist"),
582            SIG_AT as i64
583        );
584        assert_eq!(
585            avr.body["version"].as_u64().expect("version should exist"),
586            4
587        );
588        assert_eq!(
589            avr.isv_enclave_quote_status()
590                .expect("isv enclave quote status should exist"),
591            "SW_HARDENING_NEEDED"
592        );
593
594        let isv_enclave_quote_body = BASE64_STANDARD
595            .decode(
596                avr.isv_enclave_quote_body()
597                    .expect("isv enclave quote body should exist"),
598            )
599            .expect("decoding isv enclave quote body should succeed");
600        assert_eq!(isv_enclave_quote_body.len(), 432);
601        assert!(avr.body.get("revocationReason").is_none());
602        assert!(avr.body.get("pseManifestStatus").is_none());
603        assert!(avr.body.get("pseManifestHash").is_none());
604        assert!(avr.body.get("platformInfoBlob").is_none());
605        assert_eq!(
606            avr.body["nonce"].as_str().expect("nonce should exist"),
607            NONCE
608        );
609        assert_eq!(
610            avr.body["epidPseudonym"]
611                .as_str()
612                .expect("epid pseudonym should exist"),
613            EPID_PSEUDONYM
614        );
615        assert_eq!(
616            avr.body["advisoryURL"]
617                .as_str()
618                .expect("advisory URL should exist"),
619            "https://security-center.intel.com"
620        );
621        for (avr_id, known_id) in avr.body["advisoryIDs"]
622            .as_array()
623            .expect("advisory ID array should exist")
624            .iter()
625            .zip(["INTEL-SA-00334"])
626        {
627            assert_eq!(
628                avr_id.as_str().expect("advisory ID should be a string"),
629                known_id
630            );
631        }
632    }
633
634    #[test]
635    fn test_decode_avr_v5() {
636        const AVR: &[u8] = include_bytes!(
637            "../../../../go/common/sgx/ias/testdata/avr_v5_body_sw_hardening_needed.json"
638        );
639        const SIG: &[u8] = include_bytes!(
640            "../../../../go/common/sgx/ias/testdata/avr_v5_body_sw_hardening_needed.sig"
641        );
642        const SIG_AT: u64 = 1695829918; // 2023-09-27T15:51:58
643        const ID: &str = "325753020347524304899139732345489823748";
644        const EPID_PSEUDONYM: &str = "twLvZuBD1sOHsNPsHGbZOVlGh9rXw9XzVQTVKUuvsqypw0iWcFKwR7aNoHmDSoeFc/+pH6LLCI2bQBKx/ygwXphePD4GTTRwBi9EIBFRlURTk4p4NosbA7xcCG4hRuCDaEKPtAX6XHjNKEvWA+4f1aAfD7jwOtGAzHeaqBldaD8=";
645
646        let result = validate_avr_signature(IAS_CERT_CHAIN, AVR, SIG, SIG_AT);
647        assert!(result.is_ok());
648
649        let raw_avr = AVR {
650            body: AVR.to_vec(),
651            signature: SIG.to_vec(),
652            certificate_chain: IAS_CERT_CHAIN.to_vec(),
653        };
654        let avr = ParsedAVR::new(&raw_avr).expect("parsing raw AVR should succeed");
655        assert_eq!(avr.body["id"].as_str().expect("id should be present"), ID);
656        assert_eq!(
657            avr.timestamp().expect("timestamp should exist"),
658            SIG_AT as i64
659        );
660        assert_eq!(
661            avr.body["version"].as_u64().expect("version should exist"),
662            5
663        );
664        assert_eq!(
665            avr.isv_enclave_quote_status()
666                .expect("isv enclave quote status should exist"),
667            "SW_HARDENING_NEEDED"
668        );
669
670        let isv_enclave_quote_body = BASE64_STANDARD
671            .decode(
672                avr.isv_enclave_quote_body()
673                    .expect("isv enclave quote body should exist"),
674            )
675            .expect("decoding isv enclave quote body should succeed");
676        assert_eq!(isv_enclave_quote_body.len(), 432);
677        assert!(avr.body.get("revocationReason").is_none());
678        assert!(avr.body.get("pseManifestStatus").is_none());
679        assert!(avr.body.get("pseManifestHash").is_none());
680        assert!(avr.body.get("platformInfoBlob").is_none());
681        assert!(avr.body.get("nonce").is_none());
682        assert_eq!(
683            avr.body["epidPseudonym"]
684                .as_str()
685                .expect("epid pseudonym should exist"),
686            EPID_PSEUDONYM
687        );
688        assert_eq!(
689            avr.body["advisoryURL"]
690                .as_str()
691                .expect("advisory URL should exist"),
692            "https://security-center.intel.com"
693        );
694        for (avr_id, known_id) in avr.body["advisoryIDs"]
695            .as_array()
696            .expect("advisory ID array should exist")
697            .iter()
698            .zip(["INTEL-SA-00334", "INTEL-SA-00615"])
699        {
700            assert_eq!(
701                avr_id.as_str().expect("advisory ID should be a string"),
702                known_id
703            );
704        }
705    }
706}