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