1use core::time::Duration;
2
3use anyhow::{bail, Result};
4use serde::{Deserialize, Serialize};
5
6use {
7 super::{PckCertFlag, Policy, QuoteClaims},
8 crate::tcb_info::TcbStatus,
9 crate::utils::parse_rfc3339_unix_secs,
10 alloc::string::String,
11 alloc::vec::Vec,
12};
13
14#[derive(Clone, Debug)]
38pub struct QuotePolicy {
39 claims_only: bool,
40 acceptable_statuses: u8,
41
42 now: u64,
44 platform_grace_period: u64,
45 qe_grace_period: u64,
46
47 min_tcb_eval_data_number: Option<u32>,
49
50 rejected_advisory_ids: Vec<String>,
52
53 allow_dynamic_platform: bool,
55 allow_cached_keys: bool,
56 allow_smt: bool,
57
58 accepted_sgx_types: Option<Vec<u8>>,
60}
61
62impl QuotePolicy {
63 const UP_TO_DATE: u8 = 1 << 0;
64 const SW_HARDENING_NEEDED: u8 = 1 << 1;
65 const CONFIGURATION_NEEDED: u8 = 1 << 2;
66 const CONFIGURATION_AND_SW_HARDENING_NEEDED: u8 = 1 << 3;
67 const OUT_OF_DATE: u8 = 1 << 4;
68 const OUT_OF_DATE_CONFIGURATION_NEEDED: u8 = 1 << 5;
69
70 fn status_to_flag(status: TcbStatus) -> u8 {
71 match status {
72 TcbStatus::UpToDate => Self::UP_TO_DATE,
73 TcbStatus::SWHardeningNeeded => Self::SW_HARDENING_NEEDED,
74 TcbStatus::ConfigurationNeeded => Self::CONFIGURATION_NEEDED,
75 TcbStatus::ConfigurationAndSWHardeningNeeded => {
76 Self::CONFIGURATION_AND_SW_HARDENING_NEEDED
77 }
78 TcbStatus::OutOfDate => Self::OUT_OF_DATE,
79 TcbStatus::OutOfDateConfigurationNeeded => Self::OUT_OF_DATE_CONFIGURATION_NEEDED,
80 TcbStatus::Revoked => 0,
81 }
82 }
83
84 fn new_with_statuses(now: u64, acceptable_statuses: u8) -> Self {
85 Self {
86 claims_only: false,
87 acceptable_statuses,
88 now,
89 platform_grace_period: 0,
90 qe_grace_period: 0,
91 min_tcb_eval_data_number: None,
92 rejected_advisory_ids: Vec::new(),
93 allow_dynamic_platform: false,
94 allow_cached_keys: false,
95 allow_smt: false,
96 accepted_sgx_types: None,
97 }
98 }
99
100 pub fn strict(now_secs: u64) -> Self {
103 Self::new_with_statuses(now_secs, Self::UP_TO_DATE)
104 }
105
106 pub fn claims_only(now_secs: u64) -> Self {
110 let mut policy = Self::new_with_statuses(now_secs, 0);
111 policy.claims_only = true;
112 policy
113 }
114
115 pub fn allow_status(mut self, status: TcbStatus) -> Self {
117 self.acceptable_statuses |= Self::status_to_flag(status);
118 self
119 }
120
121 pub fn platform_grace_period(mut self, duration: Duration) -> Self {
125 self.platform_grace_period = duration.as_secs();
126 self
127 }
128
129 pub fn qe_grace_period(mut self, duration: Duration) -> Self {
132 self.qe_grace_period = duration.as_secs();
133 self
134 }
135
136 pub fn min_tcb_eval_data_number(mut self, min: u32) -> Self {
139 self.min_tcb_eval_data_number = Some(min);
140 self
141 }
142
143 pub fn reject_advisory(mut self, id: impl Into<String>) -> Self {
147 self.rejected_advisory_ids.push(id.into());
148 self
149 }
150
151 pub fn reject_advisories(mut self, ids: &[impl AsRef<str>]) -> Self {
153 self.rejected_advisory_ids
154 .extend(ids.iter().map(|id| id.as_ref().to_string()));
155 self
156 }
157
158 pub fn allow_dynamic_platform(mut self, allow: bool) -> Self {
161 self.allow_dynamic_platform = allow;
162 self
163 }
164
165 pub fn allow_cached_keys(mut self, allow: bool) -> Self {
168 self.allow_cached_keys = allow;
169 self
170 }
171
172 pub fn allow_smt(mut self, allow: bool) -> Self {
175 self.allow_smt = allow;
176 self
177 }
178
179 pub fn accepted_sgx_types(mut self, types: &[u8]) -> Self {
182 self.accepted_sgx_types = Some(types.to_vec());
183 self
184 }
185
186 pub fn is_status_acceptable(&self, status: TcbStatus) -> bool {
188 let flag = Self::status_to_flag(status);
189 (self.acceptable_statuses & flag) != 0
190 }
191}
192
193impl Policy for QuotePolicy {
194 fn validate(&self, data: &QuoteClaims) -> Result<()> {
195 if self.claims_only {
196 return Ok(());
197 }
198 fn within_grace(date_tag: u64, grace_period: u64, now: u64) -> bool {
199 date_tag.saturating_add(grace_period) >= now
200 }
201
202 fn advisory_rejected(rejected_advisory_ids: &[String], id: &str) -> bool {
203 rejected_advisory_ids
204 .iter()
205 .any(|a| a.eq_ignore_ascii_case(id))
206 }
207
208 if !self.is_status_acceptable(data.tcb.status) {
210 bail!(
211 "TCB status {:?} is not acceptable by policy",
212 data.tcb.status
213 );
214 }
215
216 if data.earliest_expiration_date < self.now {
219 bail!(
220 "Collateral expired: earliest_expiration {} < now {}",
221 data.earliest_expiration_date,
222 self.now
223 );
224 }
225
226 let platform_is_out_of_date = matches!(
228 data.platform.tcb_level.tcb_status,
229 TcbStatus::OutOfDate | TcbStatus::OutOfDateConfigurationNeeded
230 );
231 let platform_in_grace = platform_is_out_of_date
232 && within_grace(
233 data.platform.tcb_date_tag,
234 self.platform_grace_period,
235 self.now,
236 );
237 if platform_is_out_of_date && !platform_in_grace {
238 bail!(
239 "Platform TCB too old: tcb_date_tag {} + grace {} < now {}",
240 data.platform.tcb_date_tag,
241 self.platform_grace_period,
242 self.now
243 );
244 }
245
246 let qe_tcb_date_tag = parse_rfc3339_unix_secs(&data.qe.tcb_level.tcb_date)
248 .map_err(|e| anyhow::anyhow!("Failed to parse QE TCB date: {e}"))?;
249 let qe_is_out_of_date = data.qe.tcb_level.tcb_status == TcbStatus::OutOfDate;
250 let qe_in_grace =
251 qe_is_out_of_date && within_grace(qe_tcb_date_tag, self.qe_grace_period, self.now);
252 if qe_is_out_of_date && !qe_in_grace {
253 bail!(
254 "QE TCB too old: tcb_date_tag {} + grace {} < now {}",
255 qe_tcb_date_tag,
256 self.qe_grace_period,
257 self.now
258 );
259 }
260
261 for id in &data.platform.tcb_level.advisory_ids {
263 if advisory_rejected(&self.rejected_advisory_ids, id) {
264 bail!("Advisory ID {id} is rejected by policy");
265 }
266 }
267 for id in &data.qe.tcb_level.advisory_ids {
268 if advisory_rejected(&self.rejected_advisory_ids, id) {
269 bail!("Advisory ID {id} is rejected by policy");
270 }
271 }
272
273 if let Some(min) = self.min_tcb_eval_data_number {
275 if data.tcb.eval_data_number < min {
276 bail!(
277 "TCB eval data number {} is below minimum {}",
278 data.tcb.eval_data_number,
279 min
280 );
281 }
282 }
283
284 if !self.allow_dynamic_platform && data.platform.pck.dynamic_platform == PckCertFlag::True {
286 bail!("Dynamic platform is not allowed by policy");
287 }
288
289 if !self.allow_cached_keys && data.platform.pck.cached_keys == PckCertFlag::True {
291 bail!("Cached keys are not allowed by policy");
292 }
293
294 if !self.allow_smt && data.platform.pck.smt_enabled == PckCertFlag::True {
296 bail!("SMT (hyperthreading) is not allowed by policy");
297 }
298
299 if let Some(ref types) = self.accepted_sgx_types {
301 if !types.contains(&data.platform.pck.sgx_type) {
302 bail!(
303 "SGX type {} is not in accepted types {:?}",
304 data.platform.pck.sgx_type,
305 types
306 );
307 }
308 }
309
310 Ok(())
311 }
312}
313
314#[derive(Clone, Debug, Default, Serialize, Deserialize)]
331#[serde(deny_unknown_fields)]
332pub struct QuotePolicyConfig {
333 #[serde(default)]
334 pub allowed_statuses: Vec<TcbStatus>,
335 #[serde(default)]
336 pub rejected_advisory_ids: Vec<String>,
337 #[serde(default)]
338 pub platform_grace_period_secs: u64,
339 #[serde(default)]
340 pub qe_grace_period_secs: u64,
341 #[serde(default)]
342 pub min_tcb_eval_data_number: u32,
343 #[serde(default)]
344 pub allow_dynamic_platform: bool,
345 #[serde(default)]
346 pub allow_cached_keys: bool,
347 #[serde(default)]
348 pub allow_smt: bool,
349 #[serde(default)]
350 pub accepted_sgx_types: Option<Vec<u8>>,
351}
352
353impl QuotePolicyConfig {
354 pub fn into_policy(self, now_secs: u64) -> QuotePolicy {
358 let mut policy = if self.allowed_statuses.is_empty() {
359 QuotePolicy::strict(now_secs)
360 } else {
361 let mut p = QuotePolicy::new_with_statuses(now_secs, 0);
362 for status in self.allowed_statuses {
363 p = p.allow_status(status);
364 }
365 p
366 };
367 for id in self.rejected_advisory_ids {
368 policy = policy.reject_advisory(id);
369 }
370 if self.platform_grace_period_secs > 0 {
371 policy =
372 policy.platform_grace_period(Duration::from_secs(self.platform_grace_period_secs));
373 }
374 if self.qe_grace_period_secs > 0 {
375 policy = policy.qe_grace_period(Duration::from_secs(self.qe_grace_period_secs));
376 }
377 if self.min_tcb_eval_data_number > 0 {
378 policy = policy.min_tcb_eval_data_number(self.min_tcb_eval_data_number);
379 }
380 policy = policy.allow_dynamic_platform(self.allow_dynamic_platform);
381 policy = policy.allow_cached_keys(self.allow_cached_keys);
382 policy = policy.allow_smt(self.allow_smt);
383 if let Some(types) = self.accepted_sgx_types {
384 policy = policy.accepted_sgx_types(&types);
385 }
386 policy
387 }
388}
389
390#[cfg(test)]
391#[allow(clippy::unwrap_used)]
392mod tests {
393 use super::*;
394 use crate::policy::{PckIdentity, PlatformInfo, QeInfo, TcbVerdict};
395 use crate::tcb_info::TcbStatus::*;
396
397 fn make_test_claims(tcb_status: TcbStatus) -> QuoteClaims {
398 use crate::qe_identity::{QeTcb, QeTcbLevel};
399 use crate::tcb_info::{Tcb, TcbComponents, TcbLevel};
400
401 QuoteClaims {
402 claims_version: 1,
403 header: crate::quote::Header {
404 version: 3,
405 attestation_key_type: 2,
406 tee_type: 0,
407 qe_svn: 0,
408 pce_svn: 0,
409 qe_vendor_id: [0; 16],
410 user_data: [0; 20],
411 },
412 tee_type: 0,
413 tcb: TcbVerdict {
414 status: tcb_status,
415 advisory_ids: vec![],
416 eval_data_number: 17,
417 },
418 platform: PlatformInfo {
419 tcb_level: TcbLevel {
420 tcb: Tcb {
421 sgx_components: vec![TcbComponents { svn: 0 }; 16],
422 tdx_components: vec![],
423 pce_svn: 13,
424 },
425 tcb_date: "2023-07-22T00:00:00Z".to_string(),
426 tcb_status,
427 advisory_ids: vec![],
428 },
429 tcb_date_tag: 1_690_000_000, pck: PckIdentity {
431 ppid: vec![0u8; 16],
432 cpu_svn: [0u8; 16],
433 pce_svn: 13,
434 pce_id: vec![0u8; 2],
435 fmspc: [0u8; 6],
436 sgx_type: 0,
437 platform_instance_id: None,
438 dynamic_platform: PckCertFlag::Undefined,
439 cached_keys: PckCertFlag::Undefined,
440 smt_enabled: PckCertFlag::Undefined,
441 platform_provider_id: None,
442 },
443 root_key_id: vec![0u8; 48],
444 pck_crl_num: 1,
445 root_ca_crl_num: 1,
446 },
447 qe: QeInfo {
448 tcb_level: QeTcbLevel {
449 tcb: QeTcb { isvsvn: 8 },
450 tcb_date: "2024-03-13T00:00:00Z".to_string(),
451 tcb_status: UpToDate,
452 advisory_ids: vec![],
453 },
454 report: crate::quote::EnclaveReport {
455 cpu_svn: [0u8; 16],
456 misc_select: 0,
457 reserved1: [0u8; 28],
458 attributes: [0u8; 16],
459 mr_enclave: [0u8; 32],
460 reserved2: [0u8; 32],
461 mr_signer: [0u8; 32],
462 reserved3: [0u8; 96],
463 isv_prod_id: 1,
464 isv_svn: 8,
465 reserved4: [0u8; 60],
466 report_data: [0u8; 64],
467 },
468 tcb_eval_data_number: 17,
469 },
470 report: crate::quote::Report::SgxEnclave(crate::quote::EnclaveReport {
471 cpu_svn: [0u8; 16],
472 misc_select: 0,
473 reserved1: [0u8; 28],
474 attributes: [0u8; 16],
475 mr_enclave: [0u8; 32],
476 reserved2: [0u8; 32],
477 mr_signer: [0u8; 32],
478 reserved3: [0u8; 96],
479 isv_prod_id: 0,
480 isv_svn: 0,
481 reserved4: [0u8; 60],
482 report_data: [0u8; 64],
483 }),
484 earliest_issue_date: 1_690_000_000,
485 latest_issue_date: 1_690_100_000,
486 earliest_expiration_date: 1_703_000_000, qe_iden_earliest_issue_date: 1_690_000_000,
488 qe_iden_latest_issue_date: 1_690_100_000,
489 qe_iden_earliest_expiration_date: 1_703_000_000,
490 }
491 }
492
493 #[test]
496 fn policy_strict_accepts_up_to_date() {
497 let data = make_test_claims(UpToDate);
498 let policy = QuotePolicy::strict(1_702_000_000); assert!(policy.validate(&data).is_ok());
500 }
501
502 #[test]
503 fn policy_strict_rejects_sw_hardening() {
504 let data = make_test_claims(SWHardeningNeeded);
505 let policy = QuotePolicy::strict(1_702_000_000);
506 let err = policy.validate(&data).unwrap_err().to_string();
507 assert!(err.contains("TCB status"), "{err}");
508 }
509
510 #[test]
511 fn policy_out_of_date_with_fresh_tcb_date_accepts() {
512 let mut data = make_test_claims(OutOfDate);
513 data.platform.tcb_date_tag = 1_702_000_000;
514 let policy = QuotePolicy::strict(1_702_000_000).allow_status(OutOfDate);
515 assert!(policy.validate(&data).is_ok());
516 }
517
518 #[test]
519 fn policy_allow_status_builder() {
520 let data = make_test_claims(SWHardeningNeeded);
521 let policy = QuotePolicy::strict(1_702_000_000).allow_status(SWHardeningNeeded);
522 assert!(policy.validate(&data).is_ok());
523 }
524
525 #[test]
528 fn policy_allows_advisory_when_not_blacklisted() {
529 let mut data = make_test_claims(UpToDate);
530 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
531 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
532 let policy = QuotePolicy::strict(1_702_000_000);
533 assert!(policy.validate(&data).is_ok());
534 }
535
536 #[test]
537 fn policy_rejects_blacklisted_advisory() {
538 let mut data = make_test_claims(UpToDate);
539 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
540 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
541 let policy = QuotePolicy::strict(1_702_000_000).reject_advisory("INTEL-SA-00615");
542 let err = policy.validate(&data).unwrap_err().to_string();
543 assert!(err.contains("INTEL-SA-00615"), "{err}");
544 }
545
546 #[test]
547 fn policy_advisory_blacklist_case_insensitive() {
548 let mut data = make_test_claims(UpToDate);
549 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
550 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
551 let policy = QuotePolicy::strict(1_702_000_000).reject_advisory("intel-sa-00615");
552 let err = policy.validate(&data).unwrap_err().to_string();
553 assert!(err.contains("INTEL-SA-00615"), "{err}");
554 }
555
556 #[test]
557 fn policy_reject_advisories_batch() {
558 let mut data = make_test_claims(UpToDate);
559 data.tcb.advisory_ids = vec!["INTEL-SA-00820".to_string()];
560 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00820".to_string()];
561 let policy = QuotePolicy::strict(1_702_000_000)
562 .reject_advisories(&["INTEL-SA-00615", "INTEL-SA-00820"]);
563 let err = policy.validate(&data).unwrap_err().to_string();
564 assert!(err.contains("INTEL-SA-00820"), "{err}");
565 }
566
567 #[test]
568 fn policy_empty_advisories_passes() {
569 let data = make_test_claims(UpToDate);
570 assert!(data.tcb.advisory_ids.is_empty());
571 let policy = QuotePolicy::strict(1_702_000_000);
572 assert!(policy.validate(&data).is_ok());
573 }
574
575 #[test]
578 fn policy_collateral_expired_rejects() {
579 let data = make_test_claims(UpToDate);
580 let policy = QuotePolicy::strict(1_704_000_000);
581 let err = policy.validate(&data).unwrap_err().to_string();
582 assert!(err.contains("Collateral expired"), "{err}");
583 }
584
585 #[test]
586 fn policy_collateral_not_expired_passes() {
587 let data = make_test_claims(UpToDate);
588 let policy = QuotePolicy::strict(1_702_000_000);
589 assert!(policy.validate(&data).is_ok());
590 }
591
592 #[test]
595 fn policy_platform_grace_skipped_for_up_to_date() {
596 let data = make_test_claims(UpToDate);
597 let policy = QuotePolicy::strict(1_702_000_000);
598 assert!(policy.validate(&data).is_ok());
599 }
600
601 #[test]
602 fn policy_platform_grace_skipped_for_sw_hardening() {
603 let data = make_test_claims(SWHardeningNeeded);
604 let policy = QuotePolicy::strict(1_702_000_000).allow_status(SWHardeningNeeded);
605 assert!(policy.validate(&data).is_ok());
606 }
607
608 #[test]
609 fn policy_platform_grace_skipped_for_config_needed() {
610 let data = make_test_claims(ConfigurationNeeded);
611 let policy = QuotePolicy::strict(1_702_000_000).allow_status(ConfigurationNeeded);
612 assert!(policy.validate(&data).is_ok());
613 }
614
615 #[test]
616 fn policy_platform_grace_checked_for_out_of_date_rejects() {
617 let data = make_test_claims(OutOfDate);
618 let policy = QuotePolicy::strict(1_702_000_000).allow_status(OutOfDate);
619 let err = policy.validate(&data).unwrap_err().to_string();
620 assert!(err.contains("Platform TCB too old"), "{err}");
621 }
622
623 #[test]
624 fn policy_platform_grace_checked_for_out_of_date_accepts_with_grace() {
625 let data = make_test_claims(OutOfDate);
626 let policy = QuotePolicy::strict(1_702_000_000)
627 .allow_status(OutOfDate)
628 .platform_grace_period(Duration::from_secs(13_000_000));
629 assert!(policy.validate(&data).is_ok());
630 }
631
632 #[test]
633 fn policy_platform_grace_too_short_rejects() {
634 let data = make_test_claims(OutOfDate);
635 let policy = QuotePolicy::strict(1_702_000_000)
636 .allow_status(OutOfDate)
637 .platform_grace_period(Duration::from_secs(11_000_000));
638 let err = policy.validate(&data).unwrap_err().to_string();
639 assert!(err.contains("Platform TCB too old"), "{err}");
640 }
641
642 #[test]
643 fn policy_platform_grace_checked_for_out_of_date_config_needed() {
644 let data = make_test_claims(OutOfDateConfigurationNeeded);
645 let policy = QuotePolicy::strict(1_702_000_000).allow_status(OutOfDateConfigurationNeeded);
646 let err = policy.validate(&data).unwrap_err().to_string();
647 assert!(err.contains("Platform TCB too old"), "{err}");
648 }
649
650 #[test]
653 fn policy_min_eval_num_rejects_below() {
654 let data = make_test_claims(UpToDate);
655 assert_eq!(data.tcb.eval_data_number, 17);
656 let policy = QuotePolicy::strict(1_702_000_000).min_tcb_eval_data_number(20);
657 let err = policy.validate(&data).unwrap_err().to_string();
658 assert!(err.contains("below minimum"), "{err}");
659 }
660
661 #[test]
662 fn policy_min_eval_num_accepts_equal() {
663 let data = make_test_claims(UpToDate);
664 let policy = QuotePolicy::strict(1_702_000_000).min_tcb_eval_data_number(17);
665 assert!(policy.validate(&data).is_ok());
666 }
667
668 #[test]
671 fn policy_rejects_dynamic_platform_true() {
672 let mut data = make_test_claims(UpToDate);
673 data.platform.pck.dynamic_platform = PckCertFlag::True;
674 let policy = QuotePolicy::strict(1_702_000_000);
675 let err = policy.validate(&data).unwrap_err().to_string();
676 assert!(err.contains("Dynamic platform"), "{err}");
677 }
678
679 #[test]
680 fn policy_allows_dynamic_platform_when_configured() {
681 let mut data = make_test_claims(UpToDate);
682 data.platform.pck.dynamic_platform = PckCertFlag::True;
683 let policy = QuotePolicy::strict(1_702_000_000).allow_dynamic_platform(true);
684 assert!(policy.validate(&data).is_ok());
685 }
686
687 #[test]
688 fn policy_undefined_platform_flags_pass() {
689 let data = make_test_claims(UpToDate);
690 assert_eq!(data.platform.pck.dynamic_platform, PckCertFlag::Undefined);
691 assert_eq!(data.platform.pck.cached_keys, PckCertFlag::Undefined);
692 assert_eq!(data.platform.pck.smt_enabled, PckCertFlag::Undefined);
693 let policy = QuotePolicy::strict(1_702_000_000);
694 assert!(policy.validate(&data).is_ok());
695 }
696
697 #[test]
698 fn policy_rejects_smt_true() {
699 let mut data = make_test_claims(UpToDate);
700 data.platform.pck.smt_enabled = PckCertFlag::True;
701 let policy = QuotePolicy::strict(1_702_000_000);
702 let err = policy.validate(&data).unwrap_err().to_string();
703 assert!(err.contains("SMT"), "{err}");
704 }
705
706 #[test]
707 fn policy_rejects_cached_keys_true() {
708 let mut data = make_test_claims(UpToDate);
709 data.platform.pck.cached_keys = PckCertFlag::True;
710 let policy = QuotePolicy::strict(1_702_000_000);
711 let err = policy.validate(&data).unwrap_err().to_string();
712 assert!(err.contains("Cached keys"), "{err}");
713 }
714
715 #[test]
718 fn policy_sgx_type_not_configured_passes() {
719 let data = make_test_claims(UpToDate);
720 let policy = QuotePolicy::strict(1_702_000_000);
721 assert!(policy.validate(&data).is_ok());
722 }
723
724 #[test]
725 fn policy_sgx_type_whitelist_rejects() {
726 let mut data = make_test_claims(UpToDate);
727 data.platform.pck.sgx_type = 1;
728 let policy = QuotePolicy::strict(1_702_000_000).accepted_sgx_types(&[0]);
729 let err = policy.validate(&data).unwrap_err().to_string();
730 assert!(err.contains("SGX type"), "{err}");
731 }
732
733 #[test]
734 fn policy_sgx_type_whitelist_accepts() {
735 let mut data = make_test_claims(UpToDate);
736 data.platform.pck.sgx_type = 1;
737 let policy = QuotePolicy::strict(1_702_000_000).accepted_sgx_types(&[0, 1, 2]);
738 assert!(policy.validate(&data).is_ok());
739 }
740
741 #[test]
744 fn policy_blacklist_checked_during_platform_grace() {
745 let mut data = make_test_claims(OutOfDate);
746 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
747 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
748 let policy = QuotePolicy::strict(1_702_000_000)
749 .allow_status(OutOfDate)
750 .platform_grace_period(Duration::from_secs(13_000_000))
751 .reject_advisory("INTEL-SA-00615");
752 let err = policy.validate(&data).unwrap_err().to_string();
753 assert!(err.contains("INTEL-SA-00615"), "{err}");
754 }
755
756 #[test]
757 fn policy_blacklist_checked_for_out_of_date_config_needed() {
758 let mut data = make_test_claims(OutOfDateConfigurationNeeded);
759 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
760 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
761 let policy = QuotePolicy::strict(1_702_000_000)
762 .allow_status(OutOfDateConfigurationNeeded)
763 .platform_grace_period(Duration::from_secs(13_000_000))
764 .reject_advisory("INTEL-SA-00615");
765 let err = policy.validate(&data).unwrap_err().to_string();
766 assert!(err.contains("INTEL-SA-00615"), "{err}");
767 }
768
769 #[test]
770 fn policy_blacklist_checked_without_grace() {
771 let mut data = make_test_claims(UpToDate);
772 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
773 data.platform.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
774 let policy = QuotePolicy::strict(1_702_000_000).reject_advisory("INTEL-SA-00615");
775 let err = policy.validate(&data).unwrap_err().to_string();
776 assert!(err.contains("INTEL-SA-00615"), "{err}");
777 }
778
779 #[test]
780 fn policy_platform_grace_does_not_cover_qe_out_of_date() {
781 let mut data = make_test_claims(OutOfDate);
782 data.platform.tcb_level.tcb_status = UpToDate;
783 data.platform.tcb_level.advisory_ids = vec![];
784 data.platform.tcb_date_tag = 1_702_000_000;
785 data.qe.tcb_level.tcb_status = OutOfDate;
786 data.qe.tcb_level.tcb_date = "2023-07-22T00:00:00Z".to_string();
787 data.qe.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
788 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
789
790 let policy = QuotePolicy::strict(1_702_000_000)
791 .allow_status(OutOfDate)
792 .platform_grace_period(Duration::from_secs(13_000_000));
793 let err = policy.validate(&data).unwrap_err().to_string();
794 assert!(err.contains("QE TCB too old"), "{err}");
795 }
796
797 #[test]
798 fn policy_qe_grace_accepts_qe_out_of_date() {
799 let mut data = make_test_claims(OutOfDate);
800 data.platform.tcb_level.tcb_status = UpToDate;
801 data.platform.tcb_level.advisory_ids = vec![];
802 data.qe.tcb_level.tcb_status = OutOfDate;
803 data.qe.tcb_level.tcb_date = "2023-07-22T00:00:00Z".to_string();
804 data.qe.tcb_level.advisory_ids = vec!["INTEL-SA-00615".to_string()];
805 data.tcb.advisory_ids = vec!["INTEL-SA-00615".to_string()];
806
807 let policy = QuotePolicy::strict(1_702_000_000)
808 .allow_status(OutOfDate)
809 .qe_grace_period(Duration::from_secs(13_000_000));
810 assert!(policy.validate(&data).is_ok());
811 }
812
813 #[test]
816 fn policy_config_default_is_strict() {
817 let config: QuotePolicyConfig = serde_json::from_str("{}").unwrap();
818 let policy = config.into_policy(1_702_000_000);
819 assert!(policy.validate(&make_test_claims(UpToDate)).is_ok());
820 let err = policy
821 .validate(&make_test_claims(SWHardeningNeeded))
822 .unwrap_err()
823 .to_string();
824 assert!(err.contains("not acceptable"), "{err}");
825 }
826
827 #[test]
828 fn policy_config_into_policy_applies_fields() {
829 let config: QuotePolicyConfig = serde_json::from_str(
830 r#"{
831 "allowed_statuses": ["UpToDate", "SWHardeningNeeded"],
832 "rejected_advisory_ids": ["INTEL-SA-00334"],
833 "allow_smt": true
834 }"#,
835 )
836 .unwrap();
837 let policy = config.into_policy(1_702_000_000);
838 assert!(policy
839 .validate(&make_test_claims(SWHardeningNeeded))
840 .is_ok());
841
842 let mut data = make_test_claims(UpToDate);
843 data.platform.tcb_level.advisory_ids = vec!["intel-sa-00334".to_string()];
844 let err = policy.validate(&data).unwrap_err().to_string();
845 assert!(err.contains("rejected by policy"), "{err}");
846 }
847
848 #[test]
849 fn policy_config_rejects_unknown_fields() {
850 let result: Result<QuotePolicyConfig, _> =
852 serde_json::from_str(r#"{"collateral_grace_period_secs": 2592000}"#);
853 assert!(result.is_err());
854 }
855}