1use 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#[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;
63pub type QuoteContext = [u8; QUOTE_CONTEXT_LEN];
66
67const 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#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
122pub struct QuotePolicy {
123 #[cbor(optional)]
125 pub disabled: bool,
126
127 #[cbor(optional)]
132 pub allowed_quote_statuses: Vec<i64>, #[cbor(optional)]
136 pub gid_blacklist: Vec<u32>,
137
138 #[cbor(optional)]
140 pub min_tcb_evaluation_data_number: u32,
141}
142
143#[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 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 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))?; reader.read_exact(&mut quote_body.basename)?;
172
173 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#[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#[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
243pub 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 let timestamp_now = insecure_posix_time();
254
255 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 let avr_body = ParsedAVR::new(avr)?;
267
268 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("e_body) {
299 Ok(quote_body) => quote_body,
300 _ => return Err(AVRError::MalformedQuote.into()),
301 };
302
303 if policy
305 .gid_blacklist
306 .iter()
307 .any(|gid| gid == "e_body.gid)
308 {
309 return Err(AVRError::BlacklistedGID.into());
310 }
311
312 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 avr_body.tcb_evaluation_data_number()? < policy.min_tcb_evaluation_data_number {
329 return Err(AVRError::TCBEvaluationDataNumberInvalid.into());
330 }
331
332 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 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 if cert_ders.len() != 2 {
400 return Err(AVRError::ChainNotTwoCertificates.into());
401 }
402
403 let time = ASN1Time::from_timestamp(unix_time as i64)?;
406
407 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 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 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 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
511pub(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; let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, SIG, SIG_AT);
532 assert!(result.is_ok());
533
534 let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, SIG, 0);
536 assert!(result.is_err());
537
538 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 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 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; 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; 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}