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)]
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 fn decode(quote_body: &[u8]) -> Result<QuoteBody> {
160 let mut reader = Cursor::new(quote_body);
161
162 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))?; reader.read_exact(&mut quote_body.basename)?;
175
176 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#[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#[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
246pub 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 let timestamp_now = insecure_posix_time();
257
258 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 let avr_body = ParsedAVR::new(avr)?;
270
271 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("e_body) {
302 Ok(quote_body) => quote_body,
303 _ => return Err(AVRError::MalformedQuote.into()),
304 };
305
306 if policy
308 .gid_blacklist
309 .iter()
310 .any(|gid| gid == "e_body.gid)
311 {
312 return Err(AVRError::BlacklistedGID.into());
313 }
314
315 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 avr_body.tcb_evaluation_data_number()? < policy.min_tcb_evaluation_data_number {
332 return Err(AVRError::TCBEvaluationDataNumberInvalid.into());
333 }
334
335 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 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 if cert_ders.len() != 2 {
403 return Err(AVRError::ChainNotTwoCertificates.into());
404 }
405
406 let time = ASN1Time::from_timestamp(unix_time as i64)?;
409
410 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 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 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 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
514pub(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; let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, SIG, SIG_AT);
535 assert!(result.is_ok());
536
537 let result = validate_avr_signature(IAS_CERT_CHAIN, MSG, SIG, 0);
539 assert!(result.is_err());
540
541 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 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 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; 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; 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}