dcap_artifact_retrieval/provisioning_client/
azure.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
8use pcs::{CpuSvn, EncPpid, PceId, PceIsvsvn, PckCert, QeId, Unverified};
9use rustc_serialize::hex::ToHex;
10use serde::Deserialize;
11use std::time::Duration;
12
13use super::common::PckCertsApiNotSupported;
14use super::intel::{INTEL_BASE_URL, TcbEvaluationDataNumbersApi, PckCrlApi, QeIdApi, TcbInfoApi};
15use super::{
16    Client, ClientBuilder, Fetcher, PckCertIn, PckCertService, PcsVersion, ProvisioningServiceApi,
17    StatusCode,
18};
19use crate::Error;
20
21/// A Provisioning Certificate client builder for Azure. It is based on the internal logic of the Azure DCAP
22/// provider. Only the PCK certificates are downloaded from Azure. For others Intel is contacted.
23/// This is required because Azure by default provides an older `tcbinfo` with a `next_update`
24/// field in the past (see PROD-5800).
25/// For info on the Azure DCAP provider: <https://github.com/microsoft/Azure-DCAP-Client>
26pub struct AzureProvisioningClientBuilder {
27    api_version: PcsVersion,
28    client_builder: ClientBuilder,
29}
30
31impl AzureProvisioningClientBuilder {
32    pub fn new(api_version: PcsVersion) -> Self {
33        Self {
34            api_version,
35            client_builder: ClientBuilder::new(),
36        }
37    }
38
39    pub fn set_retry_timeout(mut self, retry_timeout: Duration) -> Self {
40        self.client_builder = self.client_builder.set_retry_timeout(retry_timeout);
41        self
42    }
43
44    pub fn build<F: for<'a> Fetcher<'a>>(self, fetcher: F) -> Client<F> {
45        let pck_certs = PckCertsApiNotSupported;
46        let pck_cert = PckCertApi::new(self.api_version.clone());
47        let pck_crl = PckCrlApi::new(self.api_version.clone());
48        let qeid = QeIdApi::new(self.api_version.clone());
49        let tcbinfo = TcbInfoApi::new(self.api_version.clone());
50        let evaluation_data_numbers = TcbEvaluationDataNumbersApi::new(INTEL_BASE_URL.into());
51        self.client_builder
52            .build(pck_certs, pck_cert, pck_crl, qeid, tcbinfo, evaluation_data_numbers, fetcher)
53    }
54}
55
56pub struct PckCertApi {
57    api_version: PcsVersion,
58}
59
60impl PckCertApi {
61    // Constants from the Azure DCAP client:
62    // (host of primary URL is down)
63    const SECONDARY_CERT_URL: &'static str = "https://global.acccache.azure.net/sgx/certification";
64    const DEFAULT_CLIENT_ID: &'static str = "production_client";
65    const API_VERSION_07_2021: &'static str = "2021-07-22-preview";
66}
67
68impl PckCertApi {
69    pub(crate) fn new(api_version: PcsVersion) -> PckCertApi {
70        PckCertApi { api_version }
71    }
72}
73
74impl<'inp> PckCertService<'inp> for PckCertApi {
75    fn build_input(
76        &'inp self,
77        encrypted_ppid: Option<&'inp EncPpid>,
78        pce_id: &'inp PceId,
79        cpu_svn: &'inp CpuSvn,
80        pce_isvsvn: PceIsvsvn,
81        qe_id: Option<&'inp QeId>,
82    ) -> <Self as ProvisioningServiceApi<'inp>>::Input {
83        PckCertIn {
84            encrypted_ppid,
85            pce_id,
86            cpu_svn,
87            pce_isvsvn,
88            qe_id,
89            api_version: self.api_version,
90            api_key: &None,
91        }
92    }
93}
94
95impl<'inp> ProvisioningServiceApi<'inp> for PckCertApi {
96    type Input = PckCertIn<'inp>;
97    type Output = PckCert<Unverified>;
98
99    fn build_request(&self, input: &Self::Input) -> Result<(String, Vec<(String, String)>), Error> {
100        // Re-implements `build_pck_cert_url` from Azure's DCAP Client
101        // https://github.com/microsoft/Azure-DCAP-Client/blob/master/src/dcap_provider.cpp#L677
102        fn build_pck_cert_url(
103            pce_id: &PceId,
104            cpu_svn: &CpuSvn,
105            pce_isvsvn: PceIsvsvn,
106            qe_id: &QeId,
107            api_version: PcsVersion,
108        ) -> String {
109            // Constants from the Azure DCAP client:
110            // (host of primary URL is down)
111            let base_url = PckCertApi::SECONDARY_CERT_URL;
112            let version = api_version as u8;
113            let qeid = qe_id.to_hex();
114            let cpusvn = cpu_svn.to_hex();
115            let pcesvn = pce_isvsvn.to_le_bytes().to_hex();
116            let pceid = pce_id.to_le_bytes().to_hex();
117            let clientid = PckCertApi::DEFAULT_CLIENT_ID;
118            let api_version = PckCertApi::API_VERSION_07_2021;
119            format!("{base_url}/v{version}/pckcert?qeid={qeid}&cpusvn={cpusvn}&pcesvn={pcesvn}&pceid={pceid}&clientid={clientid}&api-version={api_version}")
120        }
121
122        let qe_id = input.qe_id.ok_or(Error::NoQeID)?;
123        let url = build_pck_cert_url(
124            input.pce_id,
125            input.cpu_svn,
126            input.pce_isvsvn,
127            qe_id,
128            input.api_version,
129        );
130        Ok((url, Vec::new()))
131    }
132
133    fn validate_response(&self, status_code: StatusCode) -> Result<(), Error> {
134        match &status_code {
135            StatusCode::Ok => Ok(()),
136            StatusCode::BadRequest => Err(Error::PCSError(status_code, "Invalid parameter")),
137            StatusCode::Unauthorized => Err(Error::PCSError(
138                status_code,
139                "Failed to authenticate or authorize the request (check your PCS key)",
140            )),
141            StatusCode::NotFound => Err(Error::PCSError(
142                status_code,
143                "Cannot find the requested certificate",
144            )),
145            StatusCode::InternalServerError => Err(Error::PCSError(
146                status_code,
147                "PCS suffered from an internal server error",
148            )),
149            StatusCode::ServiceUnavailable => Err(Error::PCSError(
150                status_code,
151                "PCS is temporarily unavailable",
152            )),
153            _ => Err(Error::PCSError(
154                status_code.clone(),
155                "Unexpected response from PCS 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<PckCert<Unverified>, Error> {
166        #[derive(Deserialize)]
167        struct AzurePckCertResp {
168            #[serde(rename = "pckCert")]
169            pck_cert: String,
170            // Azure funny business: the `sgx-Pck-Certificate-Issuer-Chain` field is percent
171            // encoded. The `pck_cert` field is not.
172            #[serde(rename = "sgx-Pck-Certificate-Issuer-Chain")]
173            cert_chain: String,
174        }
175
176        let AzurePckCertResp {
177            pck_cert,
178            cert_chain,
179        } = serde_json::from_str(&response_body)
180            .map_err(|e| Error::PCSDecodeError(format!("{}", e).into()))?;
181
182        let cert_chain: Vec<String> = percent_encoding::percent_decode_str(&cert_chain)
183            .decode_utf8()
184            .map_err(|e| Error::PCSDecodeError(format!("{}", e).into()))?
185            .split_inclusive("-----END CERTIFICATE-----\n")
186            .map(|c| c.trim().to_string())
187            .collect();
188        Ok(PckCert::new(pck_cert, cert_chain))
189    }
190}
191
192#[cfg(all(test, feature = "reqwest"))]
193mod tests {
194    use std::path::PathBuf;
195    use std::time::Duration;
196
197    use pcs::PckID;
198
199    use crate::provisioning_client::{
200        test_helpers, AzureProvisioningClientBuilder, DcapArtifactIssuer, PcsVersion, ProvisioningClient,
201    };
202    use crate::reqwest_client;
203
204    const PCKID_TEST_FILE: &str = "./tests/data/azure_icelake_pckid.csv";
205
206    const TIME_RETRY_TIMEOUT: Duration = Duration::from_secs(180);
207
208    #[test]
209    pub fn pcks_azure() {
210        for api_version in [PcsVersion::V3, PcsVersion::V4] {
211            let client = AzureProvisioningClientBuilder::new(api_version)
212                .set_retry_timeout(TIME_RETRY_TIMEOUT)
213                .build(reqwest_client());
214            let root_ca = include_bytes!("../../tests/data/root_SGX_CA_der.cert");
215            let root_cas = [&root_ca[..]];
216
217            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
218                .unwrap()
219                .iter()
220            {
221                let pck = client
222                    .pckcert(
223                        None,
224                        &pckid.pce_id,
225                        &pckid.cpu_svn,
226                        pckid.pce_isvsvn,
227                        Some(&pckid.qe_id),
228                    )
229                    .unwrap();
230
231                let pck = pck.verify(&root_cas, None).unwrap();
232                assert_eq!(
233                    test_helpers::get_cert_subject(&pck.ca_chain().last().unwrap()),
234                    "Intel SGX Root CA"
235                );
236                assert_eq!(
237                    test_helpers::get_cert_subject(&pck.pck_pem()),
238                    "Intel SGX PCK Certificate"
239                );
240
241                let fmspc = pck.fmspc().unwrap();
242                assert!(client.tcbinfo(&fmspc, None).is_ok());
243            }
244        }
245    }
246
247    #[test]
248    pub fn pck_crl() {
249        for api_version in [PcsVersion::V3, PcsVersion::V4] {
250            let client = AzureProvisioningClientBuilder::new(api_version)
251                .set_retry_timeout(TIME_RETRY_TIMEOUT)
252                .build(reqwest_client());
253            assert!(client.pckcrl(DcapArtifactIssuer::PCKProcessorCA).is_ok());
254            assert!(client.pckcrl(DcapArtifactIssuer::PCKPlatformCA).is_ok());
255        }
256    }
257
258    #[test]
259    pub fn qe_identity() {
260        for api_version in [PcsVersion::V3, PcsVersion::V4] {
261            let client = AzureProvisioningClientBuilder::new(api_version)
262                .set_retry_timeout(TIME_RETRY_TIMEOUT)
263                .build(reqwest_client());
264            assert!(client.qe_identity(None).is_ok());
265        }
266    }
267
268    #[test]
269    pub fn test_pckcerts_with_fallback() {
270        for api_version in [PcsVersion::V3, PcsVersion::V4] {
271            let client = AzureProvisioningClientBuilder::new(api_version)
272                .set_retry_timeout(TIME_RETRY_TIMEOUT)
273                .build(reqwest_client());
274
275            for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path())
276                .unwrap()
277                .iter()
278            {
279                let pckcerts = client.pckcerts_with_fallback(&pckid).unwrap();
280                println!("Found {} PCK certs.", pckcerts.as_pck_certs().len());
281
282                let tcb_info = client.tcbinfo(&pckcerts.fmspc().unwrap(), None).unwrap();
283                let tcb_data = tcb_info.data().unwrap();
284
285                let selected = pckcerts.select_pck(
286                    &tcb_data,
287                    &pckid.cpu_svn,
288                    pckid.pce_isvsvn,
289                    pckid.pce_id,
290                ).unwrap();
291
292                let pck = client.pckcert(
293                    Some(&pckid.enc_ppid),
294                    &pckid.pce_id,
295                    &pckid.cpu_svn,
296                    pckid.pce_isvsvn,
297                    Some(&pckid.qe_id),
298                ).unwrap();
299
300                assert_eq!(format!("{:?}", selected.sgx_extension().unwrap()),
301                            format!("{:?}", pck.sgx_extension().unwrap()));
302            }
303        }
304    }
305
306    #[test]
307    pub fn tcb_evaluation_data_numbers() {
308        for api_version in [PcsVersion::V3, PcsVersion::V4] {
309            let client = AzureProvisioningClientBuilder::new(api_version)
310                .set_retry_timeout(TIME_RETRY_TIMEOUT)
311                .build(reqwest_client());
312            assert!(client.tcb_evaluation_data_numbers().is_ok());
313        }
314    }
315}