oasis_core_runtime/common/sgx/pcs/
mod.rs1mod certificates;
4mod constants;
5mod policy;
6mod quote;
7mod report;
8mod tcb;
9mod utils;
10
11#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error("unsupported QE vendor")]
15 UnsupportedQEVendor,
16 #[error("unsupported attestation key type")]
17 UnsupportedAttestationKeyType,
18 #[error("unsupported TEE type")]
19 UnsupportedTeeType,
20 #[error("failed to parse quote: {0}")]
21 QuoteParseError(String),
22 #[error("failed to verify quote: {0}")]
23 VerificationFailed(String),
24 #[error("unexpected certificate chain")]
25 UnexpectedCertificateChain,
26 #[error("unexpected certification data")]
27 UnexpectedCertificationData,
28 #[error("malformed certification data")]
29 MalformedCertificationData,
30 #[error("PCK is malformed")]
31 MalformedPCK,
32 #[error("failed to parse TCB bundle: {0}")]
33 TCBParseError(anyhow::Error),
34 #[error("TCB verification failed")]
35 TCBVerificationFailed,
36 #[error("TCB is expired or not yet valid")]
37 TCBExpired,
38 #[error("TCB is out of date")]
39 TCBOutOfDate,
40 #[error("TCB does not match the quote")]
41 TCBMismatch,
42 #[error("TCB evaluation data number is invalid")]
43 TCBEvaluationDataNumberInvalid,
44 #[error("FMSPC is blacklisted")]
45 BlacklistedFMSPC,
46 #[error("QE report is malformed")]
47 MalformedQEReport,
48 #[error("report is malformed")]
49 MalformedReport,
50 #[error("debug enclaves not allowed")]
51 DebugEnclave,
52 #[error("production enclaves not allowed")]
53 ProductionEnclave,
54 #[error("TEE type not allowed by policy")]
55 TeeTypeNotAllowed,
56 #[error("TDX module not allowed by policy")]
57 TdxModuleNotAllowed,
58 #[error("PCS quotes are disabled by policy")]
59 Disabled,
60 #[error(transparent)]
61 Other(#[from] anyhow::Error),
62}
63
64pub use policy::{QuotePolicy, TdxModulePolicy, TdxQuotePolicy};
65pub use quote::{Quote, QuoteBundle};
66pub use report::{td_enclave_identity, TdAttributes, TdReport};
67pub use tcb::TCBBundle;
68
69#[cfg(test)]
70mod tests {
71 use chrono::prelude::*;
72
73 use super::*;
74
75 #[test]
76 fn test_quote_v3_ecdsa_p256_pck_certificatechain() {
77 const RAW_QUOTE: &[u8] =
78 include_bytes!("../../../../testdata/quote_v3_ecdsa_p256_pck_chain.bin");
79 const RAW_TCB_INFO: &[u8] =
80 include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000.json"); const RAW_CERTS: &[u8] =
82 include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); const RAW_QE_IDENTITY: &[u8] = include_bytes!("../../../../testdata/qe_identity_v2.json"); let qb = QuoteBundle {
86 quote: RAW_QUOTE.to_owned(),
87 tcb: TCBBundle {
88 tcb_info: serde_json::from_slice(RAW_TCB_INFO).unwrap(),
89 qe_identity: serde_json::from_slice(RAW_QE_IDENTITY).unwrap(),
90 certificates: RAW_CERTS.to_owned(),
91 },
92 };
93
94 let now = Utc.timestamp_opt(1671497404, 0).unwrap();
95
96 let verified_quote = qb.verify(&QuotePolicy::default(), now).unwrap();
97 assert_eq!(
98 verified_quote.identity.mr_signer,
99 "9affcfae47b848ec2caf1c49b4b283531e1cc425f93582b36806e52a43d78d1a".into()
100 );
101 assert_eq!(
102 verified_quote.identity.mr_enclave,
103 "68823bc62f409ee33a32ea270cfe45d4b19a6fb3c8570d7bc186cbe062398e8f".into()
104 );
105 }
106
107 #[test]
108 fn test_quote_v4_tdx_ecdsa_p256() {
109 const RAW_QUOTE: &[u8] = include_bytes!("../../../../testdata/quote_v4_tdx_ecdsa_p256.bin");
110 const RAW_TCB_INFO: &[u8] =
111 include_bytes!("../../../../testdata/tcb_info_v3_tdx_fmspc_C0806F000000.json"); const RAW_CERTS: &[u8] =
113 include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); const RAW_QE_IDENTITY: &[u8] =
115 include_bytes!("../../../../testdata/qe_identity_v2_tdx2.json"); let qb = QuoteBundle {
118 quote: RAW_QUOTE.to_owned(),
119 tcb: TCBBundle {
120 tcb_info: serde_json::from_slice(RAW_TCB_INFO).unwrap(),
121 qe_identity: serde_json::from_slice(RAW_QE_IDENTITY).unwrap(),
122 certificates: RAW_CERTS.to_owned(),
123 },
124 };
125
126 let now = Utc.timestamp_opt(1725263032, 0).unwrap();
127 let policy = QuotePolicy {
128 tdx: Some(TdxQuotePolicy::default()), ..Default::default()
130 };
131
132 let verified_quote = qb.verify(&policy, now).unwrap();
133 assert_eq!(
134 verified_quote.identity.mr_signer,
135 "0000000000000000000000000000000000000000000000000000000000000000".into()
136 );
137 assert_eq!(
138 verified_quote.identity.mr_enclave,
139 "63d522d975f7de879a8f3368b4f32dd1e8db635f5a24b651ce8ff81705028813".into()
140 );
141
142 let policy = QuotePolicy {
144 tdx: None,
145 ..Default::default()
146 };
147
148 let result = qb.verify(&policy, now);
149 assert!(matches!(result, Err(Error::TeeTypeNotAllowed)));
150
151 let policy = QuotePolicy {
153 tdx: Some(TdxQuotePolicy {
154 allowed_tdx_modules: vec![TdxModulePolicy {
155 mr_seam: None,
156 mr_signer_seam: [1; 48],
157 }],
158 }),
159 ..Default::default()
160 };
161
162 let result = qb.verify(&policy, now);
163 assert!(matches!(result, Err(Error::TdxModuleNotAllowed)));
164 }
165
166 #[test]
167 fn test_quote_v4_tdx_ecdsa_p256_out_of_date() {
168 const RAW_QUOTE: &[u8] =
169 include_bytes!("../../../../testdata/quote_v4_tdx_ecdsa_p256_out_of_date.bin");
170 const RAW_TCB_INFO: &[u8] =
171 include_bytes!("../../../../testdata/tcb_info_v3_tdx_fmspc_50806F000000.json"); const RAW_CERTS: &[u8] =
173 include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); const RAW_QE_IDENTITY: &[u8] =
175 include_bytes!("../../../../testdata/qe_identity_v2_tdx.json"); let qb = QuoteBundle {
178 quote: RAW_QUOTE.to_owned(),
179 tcb: TCBBundle {
180 tcb_info: serde_json::from_slice(RAW_TCB_INFO).unwrap(),
181 qe_identity: serde_json::from_slice(RAW_QE_IDENTITY).unwrap(),
182 certificates: RAW_CERTS.to_owned(),
183 },
184 };
185
186 let now = Utc.timestamp_opt(1687091776, 0).unwrap();
187 let policy = QuotePolicy {
188 tdx: Some(TdxQuotePolicy::default()), ..Default::default()
190 };
191
192 let result = qb.verify(&policy, now);
193
194 assert!(matches!(result, Err(Error::TCBOutOfDate)));
196 }
197
198 #[test]
199 fn test_quote_bundle_decoding() {
200 const RAW_QUOTE_BUNDLE: &[u8] =
202 include_bytes!("../../../../testdata/pcs_quote_bundle.cbor");
203
204 let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap();
205
206 let now = Utc.timestamp_opt(1671497404, 0).unwrap();
207
208 let verified_quote = qb.verify(&QuotePolicy::default(), now).unwrap();
209 assert_eq!(
210 verified_quote.identity.mr_signer,
211 "9affcfae47b848ec2caf1c49b4b283531e1cc425f93582b36806e52a43d78d1a".into()
212 );
213 assert_eq!(
214 verified_quote.identity.mr_enclave,
215 "68823bc62f409ee33a32ea270cfe45d4b19a6fb3c8570d7bc186cbe062398e8f".into()
216 );
217 }
218
219 #[test]
220 fn test_quote_blacklisted_fmscp() {
221 const RAW_QUOTE_BUNDLE: &[u8] =
223 include_bytes!("../../../../testdata/pcs_quote_bundle.cbor");
224
225 let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap();
226
227 let now = Utc.timestamp_opt(1671497404, 0).unwrap();
228 let policy = &QuotePolicy {
229 fmspc_blacklist: vec!["00606A000000".to_string()],
230 ..Default::default()
231 };
232
233 qb.verify(policy, now)
234 .expect_err("quote verification should fail for blacklisted FMSPCs");
235 }
236}