1use alloc::string::{String, ToString};
2use anyhow::{anyhow, bail, Context, Result};
3use core::marker::PhantomData;
4use der::Decode as DerDecode;
5use scale::Decode;
6use serde::Deserialize;
7use x509_cert::{
8 ext::pkix::{
9 name::{DistributionPointName, GeneralName},
10 CrlDistributionPoints,
11 },
12 Certificate,
13};
14
15use crate::config::{Config, ParsedCert, X509Codec};
16use crate::configs::DefaultConfig;
17use crate::constants::{
18 PCK_ID_ENCRYPTED_PPID_2048, PCK_ID_ENCRYPTED_PPID_3072, PCK_ID_PCK_CERT_CHAIN,
19};
20use crate::quote::{EncryptedPpidParams, Quote};
21use crate::QuoteCollateralV3;
22
23#[derive(Deserialize)]
24struct TcbInfoResponse {
25 #[serde(rename = "tcbInfo")]
26 tcb_info: serde_json::Value,
27 signature: String,
28}
29
30#[derive(Deserialize)]
31struct QeIdentityResponse {
32 #[serde(rename = "enclaveIdentity")]
33 enclave_identity: serde_json::Value,
34 signature: String,
35}
36
37#[cfg(not(feature = "js"))]
38use core::time::Duration;
39
40pub const PHALA_PCCS_URL: &str = "https://pccs.phala.network";
44
45pub const INTEL_PCS_URL: &str = "https://api.trustedservices.intel.com";
48
49struct PcsEndpoints {
50 base_url: String,
51 tee: &'static str,
52 fmspc: String,
53 ca: String,
54}
55
56impl PcsEndpoints {
57 fn new(base_url: &str, for_sgx: bool, fmspc: String, ca: &str) -> Self {
58 let tee = if for_sgx { "sgx" } else { "tdx" };
59 let base_url = base_url
60 .trim_end_matches('/')
61 .trim_end_matches("/sgx/certification/v4")
62 .trim_end_matches("/tdx/certification/v4")
63 .to_owned();
64 Self {
65 base_url,
66 tee,
67 fmspc,
68 ca: ca.to_owned(),
69 }
70 }
71
72 fn is_pcs(&self) -> bool {
73 self.base_url.starts_with(INTEL_PCS_URL)
74 }
75
76 fn url_pckcrl(&self) -> String {
77 self.mk_url("sgx", &format!("pckcrl?ca={}&encoding=der", self.ca))
78 }
79
80 fn url_rootcacrl(&self) -> String {
81 self.mk_url("sgx", "rootcacrl")
82 }
83
84 fn url_tcb(&self) -> String {
85 self.mk_url(self.tee, &format!("tcb?fmspc={}", self.fmspc))
86 }
87
88 fn url_qe_identity(&self) -> String {
89 self.mk_url(self.tee, "qe/identity?update=standard")
90 }
91
92 fn mk_url(&self, tee: &str, path: &str) -> String {
93 format!("{}/{}/certification/v4/{}", self.base_url, tee, path)
94 }
95}
96
97fn get_header(response: &reqwest::Response, name: &str) -> Result<String> {
98 let value = response
99 .headers()
100 .get(name)
101 .ok_or_else(|| anyhow!("Missing {name}"))?
102 .to_str()?;
103 let value = urlencoding::decode(value)?;
104 Ok(value.into_owned())
105}
106
107fn extract_crl_url(cert_der: &[u8]) -> Result<Option<String>> {
120 let cert: Certificate = DerDecode::from_der(cert_der).context("Failed to parse certificate")?;
121
122 let Some(extensions) = &cert.tbs_certificate.extensions else {
123 return Ok(None);
124 };
125 for ext in extensions.iter() {
126 if ext.extn_id.to_string() != "2.5.29.31" {
127 continue;
128 }
129 let crl_dist_points: CrlDistributionPoints = DerDecode::from_der(ext.extn_value.as_bytes())
130 .context("Failed to parse CRL Distribution Points")?;
131
132 for dist_point in crl_dist_points.0.iter() {
133 let Some(dist_point_name) = &dist_point.distribution_point else {
134 continue;
135 };
136 let DistributionPointName::FullName(general_names) = dist_point_name else {
137 continue;
138 };
139 for general_name in general_names.iter() {
140 let GeneralName::UniformResourceIdentifier(uri) = general_name else {
141 continue;
142 };
143 return Ok(Some(uri.to_string()));
144 }
145 }
146 }
147 Ok(None)
148}
149
150async fn fetch_pck_certificate(
152 client: &reqwest::Client,
153 pccs_url: &str,
154 qeid: &[u8],
155 params: &EncryptedPpidParams,
156) -> Result<String> {
157 let qeid = hex::encode_upper(qeid);
160 let encrypted_ppid = hex::encode_upper(¶ms.encrypted_ppid);
161 let cpusvn = hex::encode_upper(params.cpusvn);
162 let pcesvn = hex::encode_upper(params.pcesvn.to_le_bytes());
163 let pceid = hex::encode_upper(params.pceid);
164
165 let base_url = pccs_url
166 .trim_end_matches('/')
167 .trim_end_matches("/sgx/certification/v4")
168 .trim_end_matches("/tdx/certification/v4");
169 let url = format!(
170 "{base_url}/sgx/certification/v4/pckcert?qeid={qeid}&encrypted_ppid={encrypted_ppid}&cpusvn={cpusvn}&pcesvn={pcesvn}&pceid={pceid}"
171 );
172 let response = client.get(&url).send().await?;
173
174 if !response.status().is_success() {
175 bail!(
176 "Failed to fetch PCK certificate from {}: {}",
177 url,
178 response.status()
179 );
180 }
181
182 if let Some(tcbm) = response.headers().get("SGX-TCBm") {
185 let tcbm_str = tcbm
186 .to_str()
187 .context("SGX-TCBm header contains invalid characters")?;
188 let tcbm_bytes =
189 hex::decode(tcbm_str).map_err(|e| anyhow!("SGX-TCBm header is not valid hex: {e}"))?;
190 let (matched_cpusvn, matched_pcesvn) = <([u8; 16], u16)>::decode(&mut &tcbm_bytes[..])
191 .context("SGX-TCBm header too short: expected 18 bytes")?;
192
193 if matched_cpusvn != params.cpusvn || matched_pcesvn != params.pcesvn {
194 bail!(
195 "TCB level mismatch: Platform's current TCB (cpusvn={}, pcesvn={}) \
196 is not registered with Intel PCS. Intel matched to a lower TCB level \
197 (cpusvn={}, pcesvn={}). This typically means the platform had a \
198 microcode/firmware update but MPA registration was not re-run afterward. \
199 Solution: Run 'mpa_manage -c mpa_registration.conf' on the platform \
200 to register the new TCB level with Intel.",
201 hex::encode(params.cpusvn),
202 params.pcesvn,
203 hex::encode(matched_cpusvn),
204 matched_pcesvn
205 );
206 }
207 }
208
209 let pck_cert_chain = get_header(&response, "SGX-PCK-Certificate-Issuer-Chain")?;
211
212 let pck_cert = response.text().await?;
214
215 Ok(format!("{pck_cert}\n{pck_cert_chain}"))
217}
218
219fn extract_fmspc_and_ca_with<C: Config>(pem_chain: &str) -> Result<(String, &'static str)> {
224 let certs = crate::utils::extract_certs(pem_chain.as_bytes())
225 .context("Failed to extract certificates from PEM chain")?;
226 let cert = certs
227 .first()
228 .ok_or_else(|| anyhow!("Empty certificate chain"))?;
229
230 let parsed = <C as Config>::X509::from_der(cert).context("Failed to decode certificate")?;
232
233 let extension = parsed
235 .extension(crate::oids::SGX_EXTENSION.as_bytes())
236 .context("Failed to get Intel extension from certificate")?
237 .ok_or_else(|| anyhow!("Intel extension not found"))?;
238 let fmspc = crate::utils::get_fmspc(&extension)?;
239 let fmspc_hex = hex::encode_upper(fmspc);
240
241 let issuer = parsed
243 .issuer_dn()
244 .context("Failed to extract certificate issuer")?;
245 let ca = if issuer.contains(crate::constants::PROCESSOR_ISSUER) {
246 crate::constants::PROCESSOR_ISSUER_ID
247 } else if issuer.contains(crate::constants::PLATFORM_ISSUER) {
248 crate::constants::PLATFORM_ISSUER_ID
249 } else {
250 crate::constants::PROCESSOR_ISSUER_ID
251 };
252
253 Ok((fmspc_hex, ca))
254}
255
256pub fn default_http_client() -> Result<reqwest::Client> {
261 let builder = reqwest::Client::builder();
262 #[cfg(not(feature = "js"))]
263 let builder = builder.timeout(Duration::from_secs(180));
264 Ok(builder.build()?)
265}
266
267async fn get_pck_chain(client: &reqwest::Client, pccs_url: &str, quote: &Quote) -> Result<String> {
271 match quote.inner_cert_type() {
272 PCK_ID_PCK_CERT_CHAIN => Ok(String::from_utf8_lossy(quote.inner_cert_data()).to_string()),
273 PCK_ID_ENCRYPTED_PPID_2048 | PCK_ID_ENCRYPTED_PPID_3072 => {
274 let params = quote.encrypted_ppid_params()?;
275 fetch_pck_certificate(client, pccs_url, quote.qeid(), ¶ms).await
276 }
277 other => bail!("Unsupported certification data type: {other}"),
278 }
279}
280
281pub struct CollateralClient<C: Config = DefaultConfig> {
316 http: reqwest::Client,
317 pccs_url: String,
318 _cfg: PhantomData<fn() -> C>,
319}
320
321impl<C: Config> Clone for CollateralClient<C> {
322 fn clone(&self) -> Self {
323 Self {
324 http: self.http.clone(),
325 pccs_url: self.pccs_url.clone(),
326 _cfg: PhantomData,
327 }
328 }
329}
330
331impl<C: Config> CollateralClient<C> {
332 pub fn new(http: reqwest::Client, pccs_url: impl Into<String>) -> Self {
339 Self {
340 http,
341 pccs_url: pccs_url.into(),
342 _cfg: PhantomData,
343 }
344 }
345
346 pub fn with_config<D: Config>(self) -> CollateralClient<D> {
348 CollateralClient {
349 http: self.http,
350 pccs_url: self.pccs_url,
351 _cfg: PhantomData,
352 }
353 }
354
355 pub async fn fetch(&self, quote: &[u8]) -> Result<QuoteCollateralV3> {
364 let mut quote = quote;
365 let parsed = Quote::decode(&mut quote).context("Failed to parse quote")?;
366
367 let pck_chain = get_pck_chain(&self.http, &self.pccs_url, &parsed)
368 .await
369 .context("Failed to get PCK certificate chain")?;
370
371 let (fmspc, ca) = extract_fmspc_and_ca_with::<C>(&pck_chain)?;
372
373 let mut collateral = self
374 .fetch_for_fmspc(&fmspc, ca, parsed.header.is_sgx())
375 .await?;
376
377 collateral.pck_certificate_chain = Some(pck_chain);
378 Ok(collateral)
379 }
380
381 pub(crate) async fn fetch_for_fmspc(
391 &self,
392 fmspc: &str,
393 ca: &str,
394 for_sgx: bool,
395 ) -> Result<QuoteCollateralV3> {
396 let endpoints = PcsEndpoints::new(&self.pccs_url, for_sgx, fmspc.to_owned(), ca);
397 let client = &self.http;
398
399 async fn checked_get(client: &reqwest::Client, url: &str) -> Result<reqwest::Response> {
403 let response = client.get(url).send().await?;
404 if !response.status().is_success() {
405 bail!("Failed to fetch {url}: {}", response.status());
406 }
407 Ok(response)
408 }
409
410 let pck_crl_issuer_chain;
411 let pck_crl;
412 {
413 let response = checked_get(client, &endpoints.url_pckcrl()).await?;
414 pck_crl_issuer_chain = get_header(&response, "SGX-PCK-CRL-Issuer-Chain")?;
415 pck_crl = response.bytes().await?.to_vec();
416 };
417
418 let tcb_info_issuer_chain;
419 let raw_tcb_info;
420 {
421 let response = checked_get(client, &endpoints.url_tcb()).await?;
422 tcb_info_issuer_chain = get_header(&response, "SGX-TCB-Info-Issuer-Chain")
423 .or(get_header(&response, "TCB-Info-Issuer-Chain"))?;
424 raw_tcb_info = response.text().await?;
425 };
426 let qe_identity_issuer_chain;
427 let raw_qe_identity;
428 {
429 let response = checked_get(client, &endpoints.url_qe_identity()).await?;
430 qe_identity_issuer_chain = get_header(&response, "SGX-Enclave-Identity-Issuer-Chain")?;
431 raw_qe_identity = response.text().await?;
432 };
433
434 async fn http_get(client: &reqwest::Client, url: &str) -> Result<Vec<u8>> {
435 Ok(checked_get(client, url).await?.bytes().await?.to_vec())
436 }
437
438 let mut root_ca_crl = None;
440 if !endpoints.is_pcs() {
441 root_ca_crl = http_get(client, &endpoints.url_rootcacrl()).await.ok();
442
443 if let Some(ref crl) = root_ca_crl {
444 let hex_str = core::str::from_utf8(crl)
446 .context("Failed to convert hex-encoded CRL to string")?;
447 let ca_crl = hex::decode(hex_str)
448 .map_err(|_| anyhow!("Failed to decode hex-encoded root CA CRL"))?;
449 root_ca_crl = Some(ca_crl);
450 }
451 }
452 let root_ca_crl = match root_ca_crl {
453 Some(crl) => crl,
454 None => {
455 let certs = crate::utils::extract_certs(qe_identity_issuer_chain.as_bytes())
456 .context("Failed to extract certificates from QE identity issuer chain")?;
457 let root_cert_der = certs
458 .last()
459 .context("No certificate found in QE identity issuer chain")?;
460 let crl_url = extract_crl_url(root_cert_der)?;
461 let Some(url) = crl_url else {
462 bail!("Could not find CRL distribution point in root certificate");
463 };
464 http_get(client, &url).await?
465 }
466 };
467
468 let tcb_info_resp: TcbInfoResponse =
469 serde_json::from_str(&raw_tcb_info).context("TCB Info should be valid JSON")?;
470 let tcb_info = tcb_info_resp.tcb_info.to_string();
471 let tcb_info_signature = hex::decode(&tcb_info_resp.signature)
472 .ok()
473 .context("TCB Info signature must be valid hex")?;
474
475 let qe_identity_resp: QeIdentityResponse =
476 serde_json::from_str(&raw_qe_identity).context("QE Identity should be valid JSON")?;
477 let qe_identity = qe_identity_resp.enclave_identity.to_string();
478 let qe_identity_signature = hex::decode(&qe_identity_resp.signature)
479 .ok()
480 .context("QE Identity signature must be valid hex")?;
481
482 Ok(QuoteCollateralV3 {
483 pck_crl_issuer_chain,
484 root_ca_crl,
485 pck_crl,
486 tcb_info_issuer_chain,
487 tcb_info,
488 tcb_info_signature,
489 qe_identity_issuer_chain,
490 qe_identity,
491 qe_identity_signature,
492 pck_certificate_chain: None,
493 })
494 }
495
496 #[cfg(feature = "_anycrypto")]
499 pub async fn fetch_and_verify(&self, quote: &[u8]) -> Result<crate::verify::VerifiedReport> {
500 use std::time::SystemTime;
501
502 let collateral = self.fetch(quote).await?;
503 let now = SystemTime::now()
504 .duration_since(SystemTime::UNIX_EPOCH)
505 .context("Failed to get current time")?
506 .as_secs();
507 crate::verify::verify_with::<C>(quote, &collateral, now)
508 }
509}
510
511impl CollateralClient<DefaultConfig> {
512 pub fn with_default_http(pccs_url: impl Into<String>) -> Result<Self> {
516 Ok(Self::new(default_http_client()?, pccs_url))
517 }
518
519 pub fn from_env() -> Result<Self> {
523 let pccs_url = std::env::var("PCCS_URL")
524 .ok()
525 .map(|s| s.trim().to_owned())
526 .filter(|s| !s.is_empty())
527 .unwrap_or_else(|| PHALA_PCCS_URL.to_owned());
528 Self::with_default_http(pccs_url)
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 #![allow(clippy::unwrap_used)]
535
536 use super::*;
537 use crate::constants::{PLATFORM_ISSUER_ID, PROCESSOR_ISSUER_ID};
538
539 const TEST_PCK_CHAIN_PROCESSOR: &str = r#"-----BEGIN CERTIFICATE-----
541MIIEjTCCBDSgAwIBAgIVAIG3dzK3YemOubljpKvR5bm/XdjWMAoGCCqGSM49BAMC
542MHExIzAhBgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQK
543DBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNV
544BAgMAkNBMQswCQYDVQQGEwJVUzAeFw0yMzA5MjAyMTUzNDNaFw0zMDA5MjAyMTUz
545NDNaMHAxIjAgBgNVBAMMGUludGVsIFNHWCBQQ0sgQ2VydGlmaWNhdGUxGjAYBgNV
546BAoMEUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkG
547A1UECAwCQ0ExCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
548kgmE7N3D+RspyaCZ2YoDTLDCuh5pnvAu4crPn2uAGujq9tOgwU8/y7jttShCB603
549U6r+h9ayOk2nZ9jewk25lqOCAqgwggKkMB8GA1UdIwQYMBaAFNDoqtp11/kuSReY
550PHsUZdDV8llNMGwGA1UdHwRlMGMwYaBfoF2GW2h0dHBzOi8vYXBpLnRydXN0ZWRz
551ZXJ2aWNlcy5pbnRlbC5jb20vc2d4L2NlcnRpZmljYXRpb24vdjQvcGNrY3JsP2Nh
552PXByb2Nlc3NvciZlbmNvZGluZz1kZXIwHQYDVR0OBBYEFIW4KX263PRxYJah2Cfj
553AlrcvAC9MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMIIB1AYJKoZIhvhN
554AQ0BBIIBxTCCAcEwHgYKKoZIhvhNAQ0BAQQQ0E7AbU5tktyQ0K089e4t3zCCAWQG
555CiqGSIb4TQENAQIwggFUMBAGCyqGSIb4TQENAQIBAgELMBAGCyqGSIb4TQENAQIC
556AgELMBAGCyqGSIb4TQENAQIDAgECMBAGCyqGSIb4TQENAQIEAgECMBEGCyqGSIb4
557TQENAQIFAgIA/zAQBgsqhkiG+E0BDQECBgIBATAQBgsqhkiG+E0BDQECBwIBADAQ
558BgsqhkiG+E0BDQECCAIBADAQBgsqhkiG+E0BDQECCQIBADAQBgsqhkiG+E0BDQEC
559CgIBADAQBgsqhkiG+E0BDQECCwIBADAQBgsqhkiG+E0BDQECDAIBADAQBgsqhkiG
560+E0BDQECDQIBADAQBgsqhkiG+E0BDQECDgIBADAQBgsqhkiG+E0BDQECDwIBADAQ
561BgsqhkiG+E0BDQECEAIBADAQBgsqhkiG+E0BDQECEQIBDTAfBgsqhkiG+E0BDQEC
562EgQQCwsCAv8BAAAAAAAAAAAAADAQBgoqhkiG+E0BDQEDBAIAADAUBgoqhkiG+E0B
563DQEEBAYAoGcRAAAwDwYKKoZIhvhNAQ0BBQoBADAKBggqhkjOPQQDAgNHADBEAiBm
564SMZEtlQEjnZgGa192W3ArnZ3iyY6ckM/sTsXxCRmJgIgLf20tZHNw3a1b31JDSOW
565E6wesxoAmTeqJGRqZl621qI=
566-----END CERTIFICATE-----
567-----BEGIN CERTIFICATE-----
568MIICmDCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC
569MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD
570b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw
571CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHExIzAh
572BgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl
573bCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB
574MQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg
575tdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i
576HBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww
577UgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl
578cnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFNDo
579qtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG
580AQH/AgEAMAoGCCqGSM49BAMCA0gAMEUCIQCJgTbtVqOyZ1m3jqiAXM6QYa6r5sWS
5814y/G7y8uIJGxdwIgRqPvBSKzzQagBLQq5s5A70pdoiaRJ8z/0uDz4NgV91k=
582-----END CERTIFICATE-----
583-----BEGIN CERTIFICATE-----
584MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw
585aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
586cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
587BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG
588A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0
589aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT
590AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7
5911OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB
592uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ
593MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50
594ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV
595Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI
596KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg
597AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=
598-----END CERTIFICATE-----
599"#;
600
601 #[test]
602 fn test_extract_fmspc_and_ca_processor() {
603 let (fmspc, ca) =
604 extract_fmspc_and_ca_with::<DefaultConfig>(TEST_PCK_CHAIN_PROCESSOR).unwrap();
605 assert_eq!(fmspc, "00A067110000");
606 assert_eq!(ca, PROCESSOR_ISSUER_ID);
607 }
608
609 #[test]
610 fn test_pcs_endpoints_new() {
611 let sgx_endpoints = PcsEndpoints::new(
613 "https://pccs.example.com",
614 true,
615 "B0C06F000000".to_string(),
616 PROCESSOR_ISSUER_ID,
617 );
618 assert_eq!(sgx_endpoints.base_url, "https://pccs.example.com");
619 assert_eq!(sgx_endpoints.tee, "sgx");
620 assert_eq!(sgx_endpoints.fmspc, "B0C06F000000");
621 assert_eq!(sgx_endpoints.ca, PROCESSOR_ISSUER_ID);
622
623 let tdx_endpoints = PcsEndpoints::new(
625 "https://pccs.example.com",
626 false,
627 "B0C06F000000".to_string(),
628 PROCESSOR_ISSUER_ID,
629 );
630 assert_eq!(tdx_endpoints.base_url, "https://pccs.example.com");
631 assert_eq!(tdx_endpoints.tee, "tdx");
632 assert_eq!(tdx_endpoints.fmspc, "B0C06F000000");
633 assert_eq!(tdx_endpoints.ca, PROCESSOR_ISSUER_ID);
634
635 let endpoints_with_trailing_slash = PcsEndpoints::new(
637 "https://pccs.example.com/",
638 true,
639 "B0C06F000000".to_string(),
640 PROCESSOR_ISSUER_ID,
641 );
642 assert_eq!(
643 endpoints_with_trailing_slash.base_url,
644 "https://pccs.example.com"
645 );
646
647 let endpoints_with_sgx_path = PcsEndpoints::new(
649 "https://pccs.example.com/sgx/certification/v4",
650 true,
651 "B0C06F000000".to_string(),
652 PROCESSOR_ISSUER_ID,
653 );
654 assert_eq!(endpoints_with_sgx_path.base_url, "https://pccs.example.com");
655
656 let endpoints_with_tdx_path = PcsEndpoints::new(
658 "https://pccs.example.com/tdx/certification/v4",
659 false,
660 "B0C06F000000".to_string(),
661 PROCESSOR_ISSUER_ID,
662 );
663 assert_eq!(endpoints_with_tdx_path.base_url, "https://pccs.example.com");
664 }
665
666 #[test]
667 fn test_pcs_endpoints_url_pckcrl() {
668 let processor_endpoints = PcsEndpoints::new(
670 "https://pccs.example.com",
671 true,
672 "B0C06F000000".to_string(),
673 PROCESSOR_ISSUER_ID,
674 );
675 assert_eq!(
676 processor_endpoints.url_pckcrl(),
677 "https://pccs.example.com/sgx/certification/v4/pckcrl?ca=processor&encoding=der"
678 );
679
680 let platform_endpoints = PcsEndpoints::new(
682 "https://pccs.example.com",
683 true,
684 "B0C06F000000".to_string(),
685 PLATFORM_ISSUER_ID,
686 );
687 assert_eq!(
688 platform_endpoints.url_pckcrl(),
689 "https://pccs.example.com/sgx/certification/v4/pckcrl?ca=platform&encoding=der"
690 );
691 }
692
693 #[test]
694 fn test_pcs_endpoints_url_rootcacrl() {
695 let endpoints = PcsEndpoints::new(
696 "https://pccs.example.com",
697 true,
698 "B0C06F000000".to_string(),
699 PROCESSOR_ISSUER_ID,
700 );
701 assert_eq!(
702 endpoints.url_rootcacrl(),
703 "https://pccs.example.com/sgx/certification/v4/rootcacrl"
704 );
705 }
706
707 #[test]
708 fn test_pcs_endpoints_url_tcb() {
709 let sgx_endpoints = PcsEndpoints::new(
711 "https://pccs.example.com",
712 true,
713 "B0C06F000000".to_string(),
714 PROCESSOR_ISSUER_ID,
715 );
716 assert_eq!(
717 sgx_endpoints.url_tcb(),
718 "https://pccs.example.com/sgx/certification/v4/tcb?fmspc=B0C06F000000"
719 );
720
721 let tdx_endpoints = PcsEndpoints::new(
723 "https://pccs.example.com",
724 false,
725 "B0C06F000000".to_string(),
726 PROCESSOR_ISSUER_ID,
727 );
728 assert_eq!(
729 tdx_endpoints.url_tcb(),
730 "https://pccs.example.com/tdx/certification/v4/tcb?fmspc=B0C06F000000"
731 );
732 }
733
734 #[test]
735 fn test_pcs_endpoints_url_qe_identity() {
736 let sgx_endpoints = PcsEndpoints::new(
738 "https://pccs.example.com",
739 true,
740 "B0C06F000000".to_string(),
741 PROCESSOR_ISSUER_ID,
742 );
743 assert_eq!(
744 sgx_endpoints.url_qe_identity(),
745 "https://pccs.example.com/sgx/certification/v4/qe/identity?update=standard"
746 );
747
748 let tdx_endpoints = PcsEndpoints::new(
750 "https://pccs.example.com",
751 false,
752 "B0C06F000000".to_string(),
753 PROCESSOR_ISSUER_ID,
754 );
755 assert_eq!(
756 tdx_endpoints.url_qe_identity(),
757 "https://pccs.example.com/tdx/certification/v4/qe/identity?update=standard"
758 );
759 }
760
761 #[test]
762 fn test_intel_pcs_url() {
763 assert_eq!(INTEL_PCS_URL, "https://api.trustedservices.intel.com");
765
766 assert_eq!(PHALA_PCCS_URL, "https://pccs.phala.network");
768
769 let fmspc = "B0C06F000000";
771 let intel_endpoints =
772 PcsEndpoints::new(INTEL_PCS_URL, true, fmspc.to_string(), PROCESSOR_ISSUER_ID);
773
774 assert_eq!(
775 intel_endpoints.url_pckcrl(),
776 "https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=processor&encoding=der"
777 );
778
779 assert_eq!(
780 intel_endpoints.url_rootcacrl(),
781 "https://api.trustedservices.intel.com/sgx/certification/v4/rootcacrl"
782 );
783
784 assert_eq!(
785 intel_endpoints.url_tcb(),
786 "https://api.trustedservices.intel.com/sgx/certification/v4/tcb?fmspc=B0C06F000000"
787 );
788
789 assert_eq!(
790 intel_endpoints.url_qe_identity(),
791 "https://api.trustedservices.intel.com/sgx/certification/v4/qe/identity?update=standard"
792 );
793 }
794}