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 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"); // From PCS V4 response.
81        const RAW_CERTS: &[u8] =
82            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); // From PCS V4 response (TCB-Info-Issuer-Chain header).
83        const RAW_QE_IDENTITY: &[u8] = include_bytes!("../../../../testdata/qe_identity_v2.json"); // From PCS V4 response.
84
85        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"); // From PCS V4 response.
112        const RAW_CERTS: &[u8] =
113            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); // From PCS V4 response (TCB-Info-Issuer-Chain header).
114        const RAW_QE_IDENTITY: &[u8] =
115            include_bytes!("../../../../testdata/qe_identity_v2_tdx2.json"); // From PCS V4 response.
116
117        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()), // Allow TDX.
129            ..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        // Ensure TDX quote verification fails in case it is not allowed.
143        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        // Ensure TDX quote verification fails in case the given TDX Module is not allowed.
152        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"); // From PCS V4 response.
172        const RAW_CERTS: &[u8] =
173            include_bytes!("../../../../testdata/tcb_info_v3_fmspc_00606A000000_certs.pem"); // From PCS V4 response (TCB-Info-Issuer-Chain header).
174        const RAW_QE_IDENTITY: &[u8] =
175            include_bytes!("../../../../testdata/qe_identity_v2_tdx.json"); // From PCS V4 response.
176
177        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()), // Allow TDX.
189            ..Default::default()
190        };
191
192        let result = qb.verify(&policy, now);
193
194        // NOTE: The quote is out of date.
195        assert!(matches!(result, Err(Error::TCBOutOfDate)));
196    }
197
198    #[test]
199    fn test_quote_bundle_decoding() {
200        // From Go implementation.
201        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        // From Go implementation.
222        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}