Skip to main content

oasis_core_runtime/common/sgx/pcs/
mod.rs

1//! Intel Provisioning Certification Services (PCS) quote handling.
2
3mod certificates;
4mod constants;
5mod policy;
6mod quote;
7mod report;
8mod tcb;
9mod utils;
10
11/// Possible errors returned by this module.
12#[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 not whitelisted")]
45    NotWhitelistedFMSPC,
46    #[error("FMSPC is blacklisted")]
47    BlacklistedFMSPC,
48    #[error("QE report is malformed")]
49    MalformedQEReport,
50    #[error("report is malformed")]
51    MalformedReport,
52    #[error("debug enclaves not allowed")]
53    DebugEnclave,
54    #[error("production enclaves not allowed")]
55    ProductionEnclave,
56    #[error("TEE type not allowed by policy")]
57    TeeTypeNotAllowed,
58    #[error("TDX module not allowed by policy")]
59    TdxModuleNotAllowed,
60    #[error("PCS quotes are disabled by policy")]
61    Disabled,
62    #[error(transparent)]
63    Other(#[from] anyhow::Error),
64}
65
66pub use policy::{QuotePolicy, TdxModulePolicy, TdxQuotePolicy};
67pub use quote::{Quote, QuoteBundle};
68pub use report::{td_enclave_identity, TdAttributes, TdReport};
69pub use tcb::TCBBundle;
70
71#[cfg(test)]
72mod tests {
73    use chrono::prelude::*;
74
75    use super::*;
76
77    #[test]
78    fn test_quote_v3_ecdsa_p256_pck_certificatechain() {
79        const RAW_QUOTE: &[u8] =
80            include_bytes!("../../../../testdata/quote_v3_ecdsa_p256_pck_chain.bin");
81        const RAW_TCB_INFO: &[u8] =
82            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000.json"); // From PCS V4 response.
83        const RAW_CERTS: &[u8] =
84            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); // From PCS V4 response (TCB-Info-Issuer-Chain header).
85        const RAW_QE_IDENTITY: &[u8] = include_bytes!("../../../../testdata/qe_identity_v2.json"); // From PCS V4 response.
86
87        let qb = QuoteBundle {
88            quote: RAW_QUOTE.to_owned(),
89            tcb: TCBBundle {
90                tcb_info: serde_json::from_slice(RAW_TCB_INFO).unwrap(),
91                qe_identity: serde_json::from_slice(RAW_QE_IDENTITY).unwrap(),
92                certificates: RAW_CERTS.to_owned(),
93            },
94        };
95
96        let now = Utc.timestamp_opt(1671497404, 0).unwrap();
97
98        let verified_quote = qb.verify(&QuotePolicy::default(), now).unwrap();
99        assert_eq!(
100            verified_quote.identity.mr_signer,
101            "9affcfae47b848ec2caf1c49b4b283531e1cc425f93582b36806e52a43d78d1a".into()
102        );
103        assert_eq!(
104            verified_quote.identity.mr_enclave,
105            "68823bc62f409ee33a32ea270cfe45d4b19a6fb3c8570d7bc186cbe062398e8f".into()
106        );
107    }
108
109    #[test]
110    fn test_quote_v4_tdx_ecdsa_p256() {
111        const RAW_QUOTE: &[u8] = include_bytes!("../../../../testdata/quote_v4_tdx_ecdsa_p256.bin");
112        const RAW_TCB_INFO: &[u8] =
113            include_bytes!("../../../../testdata/tcb_info_v3_tdx_fmspc_C0806F000000.json"); // From PCS V4 response.
114        const RAW_CERTS: &[u8] =
115            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); // From PCS V4 response (TCB-Info-Issuer-Chain header).
116        const RAW_QE_IDENTITY: &[u8] =
117            include_bytes!("../../../../testdata/qe_identity_v2_tdx2.json"); // From PCS V4 response.
118
119        let qb = QuoteBundle {
120            quote: RAW_QUOTE.to_owned(),
121            tcb: TCBBundle {
122                tcb_info: serde_json::from_slice(RAW_TCB_INFO).unwrap(),
123                qe_identity: serde_json::from_slice(RAW_QE_IDENTITY).unwrap(),
124                certificates: RAW_CERTS.to_owned(),
125            },
126        };
127
128        let now = Utc.timestamp_opt(1725263032, 0).unwrap();
129        let policy = QuotePolicy {
130            tdx: Some(TdxQuotePolicy::default()), // Allow TDX.
131            ..Default::default()
132        };
133
134        let verified_quote = qb.verify(&policy, now).unwrap();
135        assert_eq!(
136            verified_quote.identity.mr_signer,
137            "0000000000000000000000000000000000000000000000000000000000000000".into()
138        );
139        assert_eq!(
140            verified_quote.identity.mr_enclave,
141            "63d522d975f7de879a8f3368b4f32dd1e8db635f5a24b651ce8ff81705028813".into()
142        );
143
144        // Ensure TDX quote verification fails in case it is not allowed.
145        let policy = QuotePolicy {
146            tdx: None,
147            ..Default::default()
148        };
149
150        let result = qb.verify(&policy, now);
151        assert!(matches!(result, Err(Error::TeeTypeNotAllowed)));
152
153        // Ensure TDX quote verification fails in case the given TDX Module is not allowed.
154        let policy = QuotePolicy {
155            tdx: Some(TdxQuotePolicy {
156                allowed_tdx_modules: vec![TdxModulePolicy {
157                    mr_seam: None,
158                    mr_signer_seam: [1; 48],
159                }],
160            }),
161            ..Default::default()
162        };
163
164        let result = qb.verify(&policy, now);
165        assert!(matches!(result, Err(Error::TdxModuleNotAllowed)));
166    }
167
168    #[test]
169    fn test_quote_v4_tdx_ecdsa_p256_out_of_date() {
170        const RAW_QUOTE: &[u8] =
171            include_bytes!("../../../../testdata/quote_v4_tdx_ecdsa_p256_out_of_date.bin");
172        const RAW_TCB_INFO: &[u8] =
173            include_bytes!("../../../../testdata/tcb_info_v3_tdx_fmspc_50806F000000.json"); // From PCS V4 response.
174        const RAW_CERTS: &[u8] =
175            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); // From PCS V4 response (TCB-Info-Issuer-Chain header).
176        const RAW_QE_IDENTITY: &[u8] =
177            include_bytes!("../../../../testdata/qe_identity_v2_tdx.json"); // From PCS V4 response.
178
179        let qb = QuoteBundle {
180            quote: RAW_QUOTE.to_owned(),
181            tcb: TCBBundle {
182                tcb_info: serde_json::from_slice(RAW_TCB_INFO).unwrap(),
183                qe_identity: serde_json::from_slice(RAW_QE_IDENTITY).unwrap(),
184                certificates: RAW_CERTS.to_owned(),
185            },
186        };
187
188        let now = Utc.timestamp_opt(1687091776, 0).unwrap();
189        let policy = QuotePolicy {
190            tdx: Some(TdxQuotePolicy::default()), // Allow TDX.
191            ..Default::default()
192        };
193
194        let result = qb.verify(&policy, now);
195
196        // NOTE: The quote is out of date.
197        assert!(matches!(result, Err(Error::TCBOutOfDate)));
198    }
199
200    #[test]
201    fn test_quote_bundle_decoding() {
202        // From Go implementation.
203        const RAW_QUOTE_BUNDLE: &[u8] =
204            include_bytes!("../../../../testdata/pcs_quote_bundle.cbor");
205
206        let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap();
207
208        let now = Utc.timestamp_opt(1671497404, 0).unwrap();
209
210        let verified_quote = qb.verify(&QuotePolicy::default(), now).unwrap();
211        assert_eq!(
212            verified_quote.identity.mr_signer,
213            "9affcfae47b848ec2caf1c49b4b283531e1cc425f93582b36806e52a43d78d1a".into()
214        );
215        assert_eq!(
216            verified_quote.identity.mr_enclave,
217            "68823bc62f409ee33a32ea270cfe45d4b19a6fb3c8570d7bc186cbe062398e8f".into()
218        );
219    }
220
221    #[test]
222    fn test_quote_whitelisted_fmscp() {
223        // From Go implementation.
224        const RAW_QUOTE_BUNDLE: &[u8] =
225            include_bytes!("../../../../testdata/pcs_quote_bundle.cbor");
226
227        let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap();
228        let now = Utc.timestamp_opt(1671497404, 0).unwrap();
229
230        let policy = &QuotePolicy {
231            ..Default::default()
232        };
233        qb.verify(policy, now)
234            .expect("quote verification should succeed for whitelisted FMSPCs");
235
236        let policy = &QuotePolicy {
237            fmspc_whitelist: vec!["00606A000000".to_string()],
238            ..Default::default()
239        };
240        qb.verify(policy, now)
241            .expect("quote verification should succeed for whitelisted FMSPCs");
242
243        let policy: &QuotePolicy = &QuotePolicy {
244            fmspc_whitelist: vec!["00606A000001".to_string()],
245            ..Default::default()
246        };
247        qb.verify(policy, now)
248            .expect_err("quote verification should fail for non-whitelisted FMSPCs");
249    }
250
251    #[test]
252    fn test_quote_blacklisted_fmscp() {
253        // From Go implementation.
254        const RAW_QUOTE_BUNDLE: &[u8] =
255            include_bytes!("../../../../testdata/pcs_quote_bundle.cbor");
256
257        let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap();
258        let now = Utc.timestamp_opt(1671497404, 0).unwrap();
259
260        let policy = &QuotePolicy {
261            fmspc_blacklist: vec!["00606A000000".to_string()],
262            ..Default::default()
263        };
264        qb.verify(policy, now)
265            .expect_err("quote verification should fail for blacklisted FMSPCs");
266    }
267}