dcap_artifact_retrieval/provisioning_client/
pccs.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 */
7
8//! Interface to the Intel SGX Provisioning Certificate Caching Service (PCCS).
9//!
10//! Reference:
11//! <https://download.01.org/intel-sgx/sgx-dcap/1.22/linux/docs/SGX_DCAP_Caching_Service_Design_Guide.pdf>
12
13use std::borrow::Cow;
14use std::time::Duration;
15
16use pcs::{
17    CpuSvn, DcapArtifactIssuer, EncPpid, Fmspc, PceId, PceIsvsvn, PckCert, PckCrl, QeId, QeIdentitySigned, TcbInfo,
18    Unverified,
19};
20use rustc_serialize::hex::{FromHex, ToHex};
21
22use super::common::*;
23use super::{
24    Client, ClientBuilder, Fetcher, PckCertIn, PckCertService, PckCrlIn, PckCrlService, PcsVersion,
25    ProvisioningServiceApi, QeIdIn, QeIdService, StatusCode, TcbInfoIn, TcbInfoService,
26};
27use super::intel::TcbEvaluationDataNumbersApi;
28use crate::Error;
29
30pub struct PccsProvisioningClientBuilder {
31    base_url: Cow<'static, str>,
32    api_version: PcsVersion,
33    client_builder: ClientBuilder,
34}
35
36impl PccsProvisioningClientBuilder {
37    pub fn new<T: Into<Cow<'static, str>>>(api_version: PcsVersion, base_url: T) -> Self {
38        Self {
39            base_url: base_url.into(),
40            api_version,
41            client_builder: ClientBuilder::new(),
42        }
43    }
44
45    pub fn set_retry_timeout(mut self, retry_timeout: Duration) -> Self {
46        self.client_builder = self.client_builder.set_retry_timeout(retry_timeout);
47        self
48    }
49
50    pub fn build<F: for<'a> Fetcher<'a>>(self, fetcher: F) -> Client<F> {
51        let pck_certs = PckCertsApiNotSupported;
52        let pck_cert = PckCertApi::new(self.base_url.clone(), self.api_version);
53        let pck_crl = PckCrlApi::new(self.base_url.clone(), self.api_version);
54        let qeid = QeIdApi::new(self.base_url.clone(), self.api_version);
55        let tcbinfo = TcbInfoApi::new(self.base_url.clone(), self.api_version);
56        let evaluation_data_numbers = TcbEvaluationDataNumbersApi::new(self.base_url.clone());
57        self.client_builder
58            .build(pck_certs, pck_cert, pck_crl, qeid, tcbinfo, evaluation_data_numbers, fetcher)
59    }
60}
61
62pub struct PckCertApi {
63    base_url: Cow<'static, str>,
64    api_version: PcsVersion,
65}
66
67impl PckCertApi {
68    pub(crate) fn new(base_url: Cow<'static, str>, api_version: PcsVersion) -> PckCertApi {
69        PckCertApi {
70            base_url,
71            api_version,
72        }
73    }
74}
75
76impl<'inp> PckCertService<'inp> for PckCertApi {
77    fn build_input(
78        &'inp self,
79        encrypted_ppid: Option<&'inp EncPpid>,
80        pce_id: &'inp PceId,
81        cpu_svn: &'inp CpuSvn,
82        pce_isvsvn: PceIsvsvn,
83        qe_id: Option<&'inp QeId>,
84    ) -> <Self as ProvisioningServiceApi<'inp>>::Input {
85        PckCertIn {
86            encrypted_ppid,
87            pce_id,
88            cpu_svn,
89            pce_isvsvn,
90            qe_id,
91            api_key: &None,
92            api_version: self.api_version,
93        }
94    }
95}
96
97/// Implementation of Get PCK Certificate API (section 3.1 in the [reference]).
98///
99/// [reference]: <https://download.01.org/intel-sgx/sgx-dcap/1.22/linux/docs/SGX_DCAP_Caching_Service_Design_Guide.pdf>
100impl<'inp> ProvisioningServiceApi<'inp> for PckCertApi {
101    type Input = PckCertIn<'inp>;
102    type Output = PckCert<Unverified>;
103
104    fn build_request(&self, input: &Self::Input) -> Result<(String, Vec<(String, String)>), Error> {
105        let api_version = input.api_version as u8;
106        let encrypted_ppid = input
107            .encrypted_ppid
108            .ok_or(Error::NoEncPPID)
109            .map(|e_ppid| e_ppid.to_hex())?;
110
111        let cpu_svn = input.cpu_svn.to_hex();
112        let pce_isvsvn = input.pce_isvsvn.to_le_bytes().to_hex();
113        let pce_id = input.pce_id.to_le_bytes().to_hex();
114        let qe_id = input
115            .qe_id
116            .ok_or(Error::NoQeID)
117            .map(|qe_id| qe_id.to_hex())?;
118
119        let url = format!(
120            "{}/sgx/certification/v{}/pckcert?encrypted_ppid={}&cpusvn={}&pcesvn={}&pceid={}&qeid={}",
121            self.base_url, api_version, encrypted_ppid, cpu_svn, pce_isvsvn, pce_id, qe_id,
122        );
123        let headers = Vec::new();
124        Ok((url, headers))
125    }
126
127    fn validate_response(&self, status_code: StatusCode) -> Result<(), Error> {
128        match status_code {
129            StatusCode::Ok => Ok(()),
130            StatusCode::BadRequest => {
131                Err(Error::PCSError(status_code, "Invalid request parameters"))
132            }
133            StatusCode::NotFound => Err(Error::PCSError(
134                status_code,
135                "No cache data for this platform",
136            )),
137            StatusCode::NonStandard461 => Err(Error::PCSError(
138                status_code,
139                "The platform was not found in the cache",
140            )),
141            StatusCode::NonStandard462 => Err(Error::PCSError(
142                status_code,
143                "Certificates are not available for certain TCBs",
144            )),
145            StatusCode::InternalServerError => Err(Error::PCSError(
146                status_code,
147                "PCCS suffered from an internal server error",
148            )),
149            StatusCode::BadGateway => Err(Error::PCSError(
150                status_code,
151                "Unable to retrieve the collateral from the Intel SGX PCS",
152            )),
153            _ => Err(Error::PCSError(
154                status_code,
155                "Unexpected response from PCCS server",
156            )),
157        }
158    }
159
160    fn parse_response(
161        &self,
162        response_body: String,
163        response_headers: Vec<(String, String)>,
164        _api_version: PcsVersion,
165    ) -> Result<Self::Output, Error> {
166        let ca_chain = parse_issuer_header(&response_headers, PCK_CERTIFICATE_ISSUER_CHAIN_HEADER)?;
167        Ok(PckCert::new(response_body, ca_chain))
168    }
169}
170
171pub struct PckCrlApi {
172    base_url: Cow<'static, str>,
173    api_version: PcsVersion,
174}
175
176impl PckCrlApi {
177    pub fn new(base_url: Cow<'static, str>, api_version: PcsVersion) -> Self {
178        PckCrlApi {
179            base_url,
180            api_version,
181        }
182    }
183}
184
185impl<'inp> PckCrlService<'inp> for PckCrlApi {
186    fn build_input(&'inp self, ca: DcapArtifactIssuer) -> <Self as ProvisioningServiceApi<'inp>>::Input {
187        PckCrlIn {
188            api_version: self.api_version,
189            ca,
190        }
191    }
192}
193
194/// Implementation of Get PCK Cert CRL API (section 3.2 of [reference]).
195///
196/// [reference]: <https://download.01.org/intel-sgx/sgx-dcap/1.22/linux/docs/SGX_DCAP_Caching_Service_Design_Guide.pdf>
197impl<'inp> ProvisioningServiceApi<'inp> for PckCrlApi {
198    type Input = PckCrlIn;
199    type Output = PckCrl<Unverified>;
200
201    fn build_request(&self, input: &Self::Input) -> Result<(String, Vec<(String, String)>), Error> {
202        let ca = match input.ca {
203            DcapArtifactIssuer::PCKProcessorCA => "processor",
204            DcapArtifactIssuer::PCKPlatformCA => "platform",
205            DcapArtifactIssuer::SGXRootCA => {
206                return Err(Error::PCSError(StatusCode::BadRequest, "Invalid ca parameter"));
207            },
208        };
209        let url = format!(
210            "{}/sgx/certification/v{}/pckcrl?ca={}",
211            self.base_url, input.api_version as u8, ca
212        );
213        Ok((url, Vec::new()))
214    }
215
216    fn validate_response(&self, status_code: StatusCode) -> Result<(), Error> {
217        match &status_code {
218            StatusCode::Ok => Ok(()),
219            StatusCode::BadRequest => {
220                Err(Error::PCSError(status_code, "Invalid request parameters"))
221            }
222            StatusCode::NotFound => Err(Error::PCSError(status_code, "PCK CRL cannot be found")),
223            StatusCode::InternalServerError => Err(Error::PCSError(
224                status_code,
225                "PCCS suffered from an internal server error",
226            )),
227            StatusCode::BadGateway => Err(Error::PCSError(
228                status_code,
229                "Unable to retrieve the collateral from the Intel SGX PCS",
230            )),
231            _ => Err(Error::PCSError(
232                status_code,
233                "Unexpected response from PCCS server",
234            )),
235        }
236    }
237
238    fn parse_response(
239        &self,
240        response_body: String,
241        response_headers: Vec<(String, String)>,
242        _api_version: PcsVersion,
243    ) -> Result<Self::Output, Error> {
244        let ca_chain = parse_issuer_header(&response_headers, PCK_CRL_ISSUER_CHAIN_HEADER)?;
245        let pem_crl = pkix::pem::der_to_pem(
246            &response_body.from_hex().map_err(|e| {
247                Error::ReadResponseError(
248                    format!("failed to parse response body as hex-encoded DER: {}", e).into(),
249                )
250            })?,
251            pkix::pem::PEM_CRL,
252        );
253        Ok(PckCrl::new(pem_crl, ca_chain)?)
254    }
255}
256
257pub struct TcbInfoApi {
258    base_url: Cow<'static, str>,
259    api_version: PcsVersion,
260}
261
262impl TcbInfoApi {
263    pub fn new(base_url: Cow<'static, str>, api_version: PcsVersion) -> Self {
264        TcbInfoApi {
265            base_url,
266            api_version,
267        }
268    }
269}
270
271impl<'inp> TcbInfoService<'inp> for TcbInfoApi {
272    fn build_input(
273        &'inp self,
274        fmspc: &'inp Fmspc,
275        tcb_evaluation_data_number: Option<u16>,
276    ) -> <Self as ProvisioningServiceApi<'inp>>::Input {
277        TcbInfoIn {
278            api_version: self.api_version,
279            fmspc,
280            tcb_evaluation_data_number,
281        }
282    }
283}
284
285/// Implementation of Get TCB Info API (section 3.3 of [reference]).
286///
287/// [reference]: <https://download.01.org/intel-sgx/sgx-dcap/1.22/linux/docs/SGX_DCAP_Caching_Service_Design_Guide.pdf>
288impl<'inp> ProvisioningServiceApi<'inp> for TcbInfoApi {
289    type Input = TcbInfoIn<'inp>;
290    type Output = TcbInfo;
291
292    fn build_request(&self, input: &Self::Input) -> Result<(String, Vec<(String, String)>), Error> {
293        let api_version = input.api_version as u8;
294        let fmspc = input.fmspc.as_bytes().to_hex();
295        let url = if let Some(evaluation_data_number) = input.tcb_evaluation_data_number {
296            format!(
297                "{}/sgx/certification/v{}/tcb?fmspc={}&tcbEvaluationDataNumber={}",
298                self.base_url, api_version, fmspc, evaluation_data_number)
299        } else {
300            format!(
301                "{}/sgx/certification/v{}/tcb?fmspc={}&update=early",
302                self.base_url, api_version, fmspc,
303            )
304        };
305        Ok((url, Vec::new()))
306    }
307
308    fn validate_response(&self, status_code: StatusCode) -> Result<(), Error> {
309        match &status_code {
310            StatusCode::Ok => Ok(()),
311            StatusCode::BadRequest => {
312                Err(Error::PCSError(status_code, "Invalid request parameters"))
313            }
314            StatusCode::NotFound => Err(Error::PCSError(
315                status_code,
316                "TCB information for provided FMSPC cannot be found",
317            )),
318            StatusCode::InternalServerError => Err(Error::PCSError(
319                status_code,
320                "PCCS suffered from an internal server error",
321            )),
322            StatusCode::BadGateway => Err(Error::PCSError(
323                status_code,
324                "Unable to retrieve the collateral from the Intel SGX PCS",
325            )),
326            _ => Err(Error::PCSError(
327                status_code,
328                "Unexpected response from PCCS server",
329            )),
330        }
331    }
332
333    fn parse_response(
334        &self,
335        response_body: String,
336        response_headers: Vec<(String, String)>,
337        api_version: PcsVersion,
338    ) -> Result<Self::Output, Error> {
339        let key = match api_version {
340            PcsVersion::V3 => TCB_INFO_ISSUER_CHAIN_HEADER_V3,
341            PcsVersion::V4 => TCB_INFO_ISSUER_CHAIN_HEADER_V4,
342        };
343        let ca_chain = parse_issuer_header(&response_headers, key)?;
344        Ok(TcbInfo::parse(&response_body, ca_chain)?)
345    }
346}
347
348pub struct QeIdApi {
349    base_url: Cow<'static, str>,
350    api_version: PcsVersion,
351}
352
353impl QeIdApi {
354    pub fn new(base_url: Cow<'static, str>, api_version: PcsVersion) -> Self {
355        QeIdApi {
356            base_url,
357            api_version,
358        }
359    }
360}
361
362impl<'inp> QeIdService<'inp> for QeIdApi {
363    fn build_input(&'inp self, tcb_evaluation_data_number: Option<u16>) -> <Self as ProvisioningServiceApi<'inp>>::Input {
364        QeIdIn {
365            api_version: self.api_version,
366            tcb_evaluation_data_number,
367        }
368    }
369}
370
371/// Implementation of Get Intel's QE Identity API (section 3.4 of [reference]).
372///
373/// [reference]: <https://download.01.org/intel-sgx/sgx-dcap/1.22/linux/docs/SGX_DCAP_Caching_Service_Design_Guide.pdf>
374impl<'inp> ProvisioningServiceApi<'inp> for QeIdApi {
375    type Input = QeIdIn;
376    type Output = QeIdentitySigned;
377
378    fn build_request(&self, input: &Self::Input) -> Result<(String, Vec<(String, String)>), Error> {
379        let api_version = input.api_version as u8;
380        let url = if let Some(tcb_evaluation_data_number) = input.tcb_evaluation_data_number {
381            format!(
382                "{}/sgx/certification/v{}/qe/identity?tcbEvaluationDataNumber={}",
383                self.base_url, api_version, tcb_evaluation_data_number
384            )
385        } else {
386            format!(
387                "{}/sgx/certification/v{}/qe/identity?update=early",
388                self.base_url, api_version,
389            )
390        };
391        Ok((url, Vec::new()))
392    }
393
394    fn validate_response(&self, status_code: StatusCode) -> Result<(), Error> {
395        match &status_code {
396            StatusCode::Ok => Ok(()),
397            StatusCode::NotFound => Err(Error::PCSError(
398                status_code,
399                "QE identity information cannot be found",
400            )),
401            StatusCode::InternalServerError => Err(Error::PCSError(
402                status_code,
403                "PCCS suffered from an internal server error",
404            )),
405            StatusCode::BadGateway => Err(Error::PCSError(
406                status_code,
407                "Unable to retrieve the collateral from the Intel SGX PCS",
408            )),
409            _ => Err(Error::PCSError(
410                status_code,
411                "Unexpected response from PCCS server",
412            )),
413        }
414    }
415
416    fn parse_response(
417        &self,
418        response_body: String,
419        response_headers: Vec<(String, String)>,
420        _api_version: PcsVersion,
421    ) -> Result<Self::Output, Error> {
422        let ca_chain = parse_issuer_header(&response_headers, ENCLAVE_ID_ISSUER_CHAIN_HEADER)?;
423        let id = QeIdentitySigned::parse(&response_body, ca_chain)?;
424        Ok(id)
425    }
426}
427
428#[cfg(all(test, feature = "reqwest"))]
429mod tests {
430    use std::convert::TryFrom;
431    use std::hash::{DefaultHasher, Hash, Hasher};
432    use std::path::PathBuf;
433    use std::sync::OnceLock;
434    use std::time::Duration;
435
436    use pcs::{
437        EnclaveIdentity, Fmspc, PckID, Platform, RawTcbEvaluationDataNumbers,
438        TcbEvaluationDataNumbers,
439    };
440
441    use super::Client;
442    use crate::provisioning_client::{
443        test_helpers, DcapArtifactIssuer, PccsProvisioningClientBuilder, PcsVersion,
444        ProvisioningClient,
445    };
446    use crate::{reqwest_client_insecure_tls, ReqwestClient};
447
448    const PCKID_TEST_FILE: &str = "./tests/data/pckid_retrieval.csv";
449    const OUTPUT_TEST_DIR: &str = "./tests/data/";
450    const TIME_RETRY_TIMEOUT: Duration = Duration::from_secs(180);
451
452    static PCCS_URL: OnceLock<String> = OnceLock::new();
453
454    fn pccs_url_from_env() -> String {
455        let url = std::env::var("PCCS_URL").unwrap_or(String::from("https://pccs.fortanix.com"));
456        assert!(
457            !url.is_empty(),
458            "Empty string in environment variable: PCCS_URL"
459        );
460        url
461    }
462
463    fn make_client(api_version: PcsVersion) -> Client<ReqwestClient> {
464        let url = &*PCCS_URL.get_or_init(pccs_url_from_env);
465        PccsProvisioningClientBuilder::new(api_version, url)
466            .set_retry_timeout(TIME_RETRY_TIMEOUT)
467            .build(reqwest_client_insecure_tls())
468    }
469
470    #[test]
471    pub fn pck() {
472        for api_version in [PcsVersion::V3, PcsVersion::V4] {
473            let client = make_client(api_version);
474
475            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
476                .unwrap()
477                .iter()
478            {
479                let pck = client
480                    .pckcert(
481                        Some(&pckid.enc_ppid),
482                        &pckid.pce_id,
483                        &pckid.cpu_svn,
484                        pckid.pce_isvsvn,
485                        Some(&pckid.qe_id),
486                    )
487                    .unwrap();
488
489                assert_eq!(
490                    test_helpers::get_cert_subject(pck.ca_chain().last().unwrap()),
491                    "Intel SGX Root CA"
492                );
493            }
494        }
495    }
496
497    #[test]
498    pub fn pck_cached() {
499        let root_ca = include_bytes!("../../tests/data/root_SGX_CA_der.cert");
500        let root_cas = [&root_ca[..]];
501        for api_version in [PcsVersion::V3, PcsVersion::V4] {
502            let client = make_client(api_version);
503
504            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
505                .unwrap()
506                .iter()
507            {
508                let pck = client
509                    .pckcert(
510                        Some(&pckid.enc_ppid),
511                        &pckid.pce_id,
512                        &pckid.cpu_svn,
513                        pckid.pce_isvsvn,
514                        Some(&pckid.qe_id),
515                    )
516                    .unwrap();
517
518                let pck = pck.verify(&root_cas, None).unwrap();
519
520                // The cache should be populated after initial service call
521                {
522                    let mut cache = client.pckcert_service.cache.lock().unwrap();
523
524                    assert!(cache.len() > 0);
525
526                    let (cached_pck, _) = {
527                        let mut hasher = DefaultHasher::new();
528                        let input = client.pckcert_service.pcs_service().build_input(
529                            Some(&pckid.enc_ppid),
530                            &pckid.pce_id,
531                            &pckid.cpu_svn,
532                            pckid.pce_isvsvn,
533                            Some(&pckid.qe_id),
534                        );
535                        input.hash(&mut hasher);
536
537                        cache
538                            .get_mut(&hasher.finish())
539                            .expect("Can't find key in cache")
540                            .to_owned()
541                    };
542
543                    assert_eq!(
544                        pck.fmspc().unwrap(),
545                        cached_pck
546                            .clone()
547                            .verify(&root_cas, None)
548                            .unwrap()
549                            .fmspc()
550                            .unwrap()
551                    );
552                    assert_eq!(pck.ca_chain(), cached_pck.ca_chain());
553                }
554
555                // Second service call should return value from cache
556                let pck_from_service = client
557                    .pckcert(
558                        Some(&pckid.enc_ppid),
559                        &pckid.pce_id,
560                        &pckid.cpu_svn,
561                        pckid.pce_isvsvn,
562                        Some(&pckid.qe_id),
563                    )
564                    .unwrap();
565
566                assert_eq!(
567                    pck.fmspc().unwrap(),
568                    pck_from_service
569                        .clone()
570                        .verify(&root_cas, None)
571                        .unwrap()
572                        .fmspc()
573                        .unwrap()
574                );
575                assert_eq!(pck.ca_chain(), pck_from_service.ca_chain());
576            }
577        }
578    }
579
580    #[test]
581    pub fn test_pckcerts_with_fallback() {
582        for api_version in [PcsVersion::V3, PcsVersion::V4] {
583            let client = make_client(api_version);
584
585            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
586                .unwrap()
587                .iter()
588            {
589                let pckcerts = client.pckcerts_with_fallback(&pckid).unwrap();
590                println!("Found {} PCK certs.", pckcerts.as_pck_certs().len());
591
592                let tcb_info = client.tcbinfo(&pckcerts.fmspc().unwrap(), None).unwrap();
593                let tcb_data = tcb_info.data().unwrap();
594
595                let selected = pckcerts
596                    .select_pck(&tcb_data, &pckid.cpu_svn, pckid.pce_isvsvn, pckid.pce_id)
597                    .unwrap();
598
599                let pck = client
600                    .pckcert(
601                    Some(&pckid.enc_ppid),
602                    &pckid.pce_id,
603                    &pckid.cpu_svn,
604                    pckid.pce_isvsvn,
605                    Some(&pckid.qe_id),
606                    )
607                    .unwrap();
608
609                assert_eq!(
610                    format!("{:?}", selected.sgx_extension().unwrap()),
611                    format!("{:?}", pck.sgx_extension().unwrap())
612                );
613            }
614        }
615    }
616
617    #[test]
618    pub fn tcb_info() {
619        for api_version in [PcsVersion::V3, PcsVersion::V4] {
620            let client = make_client(api_version);
621
622            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
623                .unwrap()
624                .iter()
625            {
626                let pckcerts = client.pckcerts_with_fallback(&pckid).unwrap();
627
628                assert!(client
629                    .tcbinfo(&pckcerts.fmspc().unwrap(), None)
630                    .and_then(|tcb| { Ok(tcb.store(OUTPUT_TEST_DIR).unwrap()) })
631                    .is_ok());
632            }
633        }
634    }
635
636    #[test]
637    pub fn tcb_info_with_evaluation_data_number() {
638        let client = make_client(PcsVersion::V4);
639        for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
640            .unwrap()
641            .iter()
642        {
643            let pckcerts = client
644                .pckcerts_with_fallback(&pckid)
645                .unwrap();
646
647            let fmspc = pckcerts.fmspc().unwrap();
648
649            let evaluation_data_numbers = client
650                .tcb_evaluation_data_numbers()
651                .unwrap()
652                .evaluation_data_numbers()
653                .unwrap();
654
655            for number in evaluation_data_numbers.numbers() {
656                // TODO(#811): Since PCCS is cache service and not able to cache the
657                // `Gone` response mentioned below from Intel PCS, We need to change
658                // the test behavior to call TCB INFO API with update=standard to get the
659                // smallest TcbEvaluationDataNumber that's still available.
660                //
661                // Here, we temporarily fix this be hardcoding.
662                if number.number() < 17 {
663                    continue;
664                }
665                let tcb = match client.tcbinfo(&fmspc, Some(number.number())) {
666                    Ok(tcb) => tcb,
667                    // API query with update="standard" will return QE Identity with TCB Evaluation Data Number M.
668                    // A 410 Gone response is returned when the inputted TCB Evaluation Data Number is < M,
669                    // so we ignore these TCB Evaluation Data Numbers.
670                    Err(super::Error::PCSError(status_code, _)) if status_code == super::StatusCode::Gone => continue,
671                    res @Err(_) => res.unwrap(),
672                };
673                tcb.store(OUTPUT_TEST_DIR).unwrap();
674            }
675        }
676    }
677
678    #[test]
679    pub fn tcb_info_cached() {
680        for api_version in [PcsVersion::V3, PcsVersion::V4] {
681            let client = make_client(api_version);
682
683            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
684                .unwrap()
685                .iter()
686            {
687                let pckcerts = client.pckcerts_with_fallback(&pckid).unwrap();
688                let fmspc = pckcerts.fmspc().unwrap();
689                let tcb_info = client.tcbinfo(&fmspc, None).unwrap();
690
691                // The cache should be populated after initial service call
692                {
693                    let mut cache = client.tcbinfo_service.cache.lock().unwrap();
694
695                    assert!(cache.len() > 0);
696
697                    let (cached_tcb_info, _) = {
698                        let mut hasher = DefaultHasher::new();
699                        let input = client
700                            .tcbinfo_service
701                            .pcs_service()
702                            .build_input(&fmspc, None);
703                        input.hash(&mut hasher);
704
705                        cache
706                            .get_mut(&hasher.finish())
707                            .expect("Can't find key in cache")
708                            .to_owned()
709                    };
710
711                    assert_eq!(tcb_info, cached_tcb_info);
712                }
713
714                // Second service call should return value from cache
715                let tcb_info_from_service = client.tcbinfo(&fmspc, None).unwrap();
716
717                assert_eq!(tcb_info, tcb_info_from_service);
718            }
719        }
720    }
721
722    #[test]
723    pub fn pckcrl() {
724        for api_version in [PcsVersion::V3, PcsVersion::V4] {
725            let client = make_client(api_version);
726            assert!(client
727                .pckcrl(DcapArtifactIssuer::PCKProcessorCA)
728                .and_then(|crl| Ok(crl.write_to_file(OUTPUT_TEST_DIR).unwrap()))
729                .is_ok());
730            assert!(client
731                .pckcrl(DcapArtifactIssuer::PCKPlatformCA)
732                .and_then(|crl| Ok(crl.write_to_file(OUTPUT_TEST_DIR).unwrap()))
733                .is_ok());
734        }
735    }
736
737    #[test]
738    pub fn pckcrl_cached() {
739        for ca in [
740            DcapArtifactIssuer::PCKProcessorCA,
741            DcapArtifactIssuer::PCKPlatformCA,
742        ] {
743            for api_version in [PcsVersion::V3, PcsVersion::V4] {
744                let client = make_client(api_version);
745                let pckcrl = client.pckcrl(ca).unwrap();
746
747                // The cache should be populated after initial service call
748                {
749                    let mut cache = client.pckcrl_service.cache.lock().unwrap();
750
751                    assert!(cache.len() > 0);
752
753                    let (cached_pckcrl, _) = {
754                        let mut hasher = DefaultHasher::new();
755                        let input = client.pckcrl_service.pcs_service().build_input(ca);
756                        input.hash(&mut hasher);
757
758                        cache
759                            .get_mut(&hasher.finish())
760                            .expect("Can't find key in cache")
761                            .to_owned()
762                    };
763
764                    assert_eq!(pckcrl, cached_pckcrl);
765                }
766
767                // Second service call should return value from cache
768                let pckcrl_from_service = client.pckcrl(ca).unwrap();
769
770                assert_eq!(pckcrl, pckcrl_from_service);
771            }
772        }
773    }
774
775    #[test]
776    pub fn qe_identity() {
777        for api_version in [PcsVersion::V3, PcsVersion::V4] {
778            let client = make_client(api_version);
779            let qe_id = client.qe_identity(None);
780            assert!(qe_id.is_ok());
781            assert!(qe_id.unwrap().write_to_file(OUTPUT_TEST_DIR).is_ok());
782        }
783    }
784
785    #[test]
786    pub fn qe_identity_cached() {
787        for api_version in [PcsVersion::V3, PcsVersion::V4] {
788            let client = make_client(api_version);
789            let qe_id = client.qe_identity(None).unwrap();
790
791            // The cache should be populated after initial service call
792            {
793                let mut cache = client.qeid_service.cache.lock().unwrap();
794
795                assert!(cache.len() > 0);
796
797                let (cached_qeid, _) = {
798                    let mut hasher = DefaultHasher::new();
799                    let input = client.qeid_service.pcs_service().build_input(None);
800                    input.hash(&mut hasher);
801
802                    cache
803                        .get_mut(&hasher.finish())
804                        .expect("Can't find key in cache")
805                        .to_owned()
806                };
807
808                assert_eq!(qe_id, cached_qeid);
809            }
810
811            // Second service call should return value from cache
812            let qeid_from_service = client.qe_identity(None).unwrap();
813
814            assert_eq!(qe_id, qeid_from_service);
815        }
816    }
817
818    #[test]
819    pub fn tcb_evaluation_data_numbers() {
820        let root_ca = include_bytes!("../../tests/data/root_SGX_CA_der.cert");
821        let root_cas = [&root_ca[..]];
822        let client = make_client(PcsVersion::V4);
823        let eval_numbers = client.tcb_evaluation_data_numbers().unwrap();
824
825        let eval_numbers2 = serde_json::ser::to_vec(&eval_numbers)
826            .and_then(|v| serde_json::from_slice::<RawTcbEvaluationDataNumbers>(&v))
827            .unwrap();
828        assert_eq!(eval_numbers, eval_numbers2);
829
830        let fmspc = Fmspc::try_from("90806f000000").unwrap();
831        let eval_numbers: TcbEvaluationDataNumbers =
832            eval_numbers.verify(&root_cas, Platform::SGX).unwrap();
833        for number in eval_numbers.numbers().map(|n| n.number()) {
834            // TODO(#811): Since PCCS is cache service and not able to cache the
835            // `Gone` response mentioned below from Intel PCS, We need to change
836            // the test behavior to call QE ID API with update=standard to get the
837            // smallest TcbEvaluationDataNumber that's still available.
838            //
839            // Here, we temporarily fix this be hardcoding.
840            if number < 17 {
841                continue;
842            }
843            let qe_identity = match client.qe_identity(Some(number)) {
844                Ok(id) => id,
845                // API query with update="standard" will return QE Identity with TCB Evaluation Data Number M.
846                // A 410 Gone response is returned when the inputted TCB Evaluation Data Number is < M,
847                // so we ignore these TCB Evaluation Data Numbers.
848                Err(super::Error::PCSError(status_code, _)) if status_code == super::StatusCode::Gone => continue,
849                res @Err(_) => res.unwrap(),
850            };
851            let verified_qe_id = qe_identity
852                .verify(&root_cas, EnclaveIdentity::QE)
853                .unwrap();
854            assert_eq!(verified_qe_id.tcb_evaluation_data_number(), u64::from(number));
855
856            let tcb_info = client
857                    .tcbinfo(&fmspc, Some(number))
858                    .unwrap()
859                    .verify(&root_cas, Platform::SGX, 2)
860                    .unwrap();
861            assert_eq!(tcb_info.tcb_evaluation_data_number(), u64::from(number));
862        }
863    }
864}