use alloc::string::{String, ToString};
use anyhow::{anyhow, bail, Context, Result};
use der::Decode as DerDecode;
use scale::Decode;
use x509_cert::{
ext::pkix::{
name::{DistributionPointName, GeneralName},
CrlDistributionPoints,
},
Certificate,
};
use crate::quote::Quote;
use crate::verify::VerifiedReport;
use crate::QuoteCollateralV3;
#[cfg(not(feature = "js"))]
use core::time::Duration;
use std::time::SystemTime;
const PCS_URL: &str = "https://api.trustedservices.intel.com";
struct PcsEndpoints {
base_url: String,
tee: &'static str,
fmspc: String,
ca: &'static str,
}
impl PcsEndpoints {
fn new(base_url: &str, for_sgx: bool, fmspc: String, ca: &'static str) -> Self {
let tee = if for_sgx { "sgx" } else { "tdx" };
let base_url = base_url
.trim_end_matches('/')
.trim_end_matches("/sgx/certification/v4")
.trim_end_matches("/tdx/certification/v4")
.to_owned();
Self {
base_url,
tee,
fmspc,
ca,
}
}
fn is_pcs(&self) -> bool {
self.base_url.starts_with(PCS_URL)
}
fn url_pckcrl(&self) -> String {
self.mk_url("sgx", &format!("pckcrl?ca={}&encoding=der", self.ca))
}
fn url_rootcacrl(&self) -> String {
self.mk_url("sgx", "rootcacrl")
}
fn url_tcb(&self) -> String {
self.mk_url(self.tee, &format!("tcb?fmspc={}", self.fmspc))
}
fn url_qe_identity(&self) -> String {
self.mk_url(self.tee, "qe/identity?update=standard")
}
fn mk_url(&self, tee: &str, path: &str) -> String {
format!("{}/{}/certification/v4/{}", self.base_url, tee, path)
}
}
fn get_header(resposne: &reqwest::Response, name: &str) -> Result<String> {
let value = resposne
.headers()
.get(name)
.ok_or_else(|| anyhow!("Missing {name}"))?
.to_str()?;
let value = urlencoding::decode(value)?;
Ok(value.into_owned())
}
fn extract_crl_url(cert_der: &[u8]) -> Result<Option<String>> {
let cert: Certificate = DerDecode::from_der(cert_der).context("Failed to parse certificate")?;
let Some(extensions) = &cert.tbs_certificate.extensions else {
return Ok(None);
};
for ext in extensions.iter() {
if ext.extn_id.to_string() != "2.5.29.31" {
continue;
}
let crl_dist_points: CrlDistributionPoints = DerDecode::from_der(ext.extn_value.as_bytes())
.context("Failed to parse CRL Distribution Points")?;
for dist_point in crl_dist_points.0.iter() {
let Some(dist_point_name) = &dist_point.distribution_point else {
continue;
};
let DistributionPointName::FullName(general_names) = dist_point_name else {
continue;
};
for general_name in general_names.iter() {
let GeneralName::UniformResourceIdentifier(uri) = general_name else {
continue;
};
return Ok(Some(uri.to_string()));
}
}
}
Ok(None)
}
pub async fn get_collateral(pccs_url: &str, mut quote: &[u8]) -> Result<QuoteCollateralV3> {
let quote = Quote::decode(&mut quote)?;
let ca = quote.ca().context("Failed to get CA")?;
let fmspc = hex::encode_upper(quote.fmspc().context("Failed to get FMSPC")?);
get_collateral_for_fmspc(pccs_url, fmspc, ca, quote.header.is_sgx()).await
}
pub async fn get_collateral_for_fmspc(
pccs_url: &str,
fmspc: String,
ca: &'static str,
for_sgx: bool,
) -> Result<QuoteCollateralV3> {
let builder = reqwest::Client::builder();
#[cfg(not(feature = "js"))]
let builder = builder
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(180));
let client = builder.build()?;
let endpoints = PcsEndpoints::new(pccs_url, for_sgx, fmspc, ca);
let pck_crl_issuer_chain;
let pck_crl;
{
let response = client.get(endpoints.url_pckcrl()).send().await?;
pck_crl_issuer_chain = get_header(&response, "SGX-PCK-CRL-Issuer-Chain")?;
pck_crl = response.bytes().await?.to_vec();
};
let tcb_info_issuer_chain;
let raw_tcb_info;
{
let resposne = client.get(endpoints.url_tcb()).send().await?;
tcb_info_issuer_chain = get_header(&resposne, "SGX-TCB-Info-Issuer-Chain")
.or(get_header(&resposne, "TCB-Info-Issuer-Chain"))?;
raw_tcb_info = resposne.text().await?;
};
let qe_identity_issuer_chain;
let raw_qe_identity;
{
let response = client.get(endpoints.url_qe_identity()).send().await?;
qe_identity_issuer_chain = get_header(&response, "SGX-Enclave-Identity-Issuer-Chain")?;
raw_qe_identity = response.text().await?;
};
async fn http_get(client: &reqwest::Client, url: &str) -> Result<Vec<u8>> {
let response = client.get(url).send().await?;
if !response.status().is_success() {
bail!("Failed to fetch {url}: {}", response.status());
}
Ok(response.bytes().await?.to_vec())
}
let mut root_ca_crl = None;
if !endpoints.is_pcs() {
root_ca_crl = http_get(&client, &endpoints.url_rootcacrl()).await.ok();
}
let root_ca_crl = match root_ca_crl {
Some(crl) => crl,
None => {
let certs = crate::utils::extract_certs(qe_identity_issuer_chain.as_bytes())
.context("Failed to extract certificates from PCK CRL issuer chain")?;
if certs.is_empty() {
bail!("No certificates found in PCK CRL issuer chain");
}
let root_cert_der = certs.last().unwrap();
let crl_url = extract_crl_url(root_cert_der)?;
let Some(url) = crl_url else {
bail!("Could not find CRL distribution point in root certificate");
};
http_get(&client, &url).await?
}
};
let tcb_info_json: serde_json::Value =
serde_json::from_str(&raw_tcb_info).context("TCB Info should be valid JSON")?;
let tcb_info = tcb_info_json["tcbInfo"].to_string();
let tcb_info_signature = tcb_info_json
.get("signature")
.context("TCB Info missing 'signature' field")?
.as_str()
.context("TCB Info signature must be a string")?;
let tcb_info_signature = hex::decode(tcb_info_signature)
.ok()
.context("TCB Info signature must be valid hex")?;
let qe_identity_json: serde_json::Value =
serde_json::from_str(&raw_qe_identity).context("QE Identity should be valid JSON")?;
let qe_identity = qe_identity_json
.get("enclaveIdentity")
.context("QE Identity missing 'enclaveIdentity' field")?
.to_string();
let qe_identity_signature = qe_identity_json
.get("signature")
.context("QE Identity missing 'signature' field")?
.as_str()
.context("QE Identity signature must be a string")?;
let qe_identity_signature = hex::decode(qe_identity_signature)
.ok()
.context("QE Identity signature must be valid hex")?;
Ok(QuoteCollateralV3 {
pck_crl_issuer_chain,
root_ca_crl,
pck_crl,
tcb_info_issuer_chain,
tcb_info,
tcb_info_signature,
qe_identity_issuer_chain,
qe_identity,
qe_identity_signature,
})
}
pub async fn get_collateral_from_pcs(quote: &[u8]) -> Result<QuoteCollateralV3> {
get_collateral(PCS_URL, quote).await
}
pub async fn get_collateral_and_verify(
quote: &[u8],
pccs_url: Option<&str>,
) -> Result<VerifiedReport> {
let pccs_url = pccs_url.unwrap_or_default().trim();
let pccs_url = if pccs_url.is_empty() {
PCS_URL
} else {
pccs_url
};
let collateral = get_collateral(pccs_url, quote).await?;
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.context("Failed to get current time")?
.as_secs();
crate::verify::verify(quote, &collateral, now)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::{PLATFORM_ISSUER_ID, PROCESSOR_ISSUER_ID};
#[test]
fn test_pcs_endpoints_new() {
let sgx_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(sgx_endpoints.base_url, "https://pccs.example.com");
assert_eq!(sgx_endpoints.tee, "sgx");
assert_eq!(sgx_endpoints.fmspc, "B0C06F000000");
assert_eq!(sgx_endpoints.ca, PROCESSOR_ISSUER_ID);
let tdx_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
false,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(tdx_endpoints.base_url, "https://pccs.example.com");
assert_eq!(tdx_endpoints.tee, "tdx");
assert_eq!(tdx_endpoints.fmspc, "B0C06F000000");
assert_eq!(tdx_endpoints.ca, PROCESSOR_ISSUER_ID);
let endpoints_with_trailing_slash = PcsEndpoints::new(
"https://pccs.example.com/",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
endpoints_with_trailing_slash.base_url,
"https://pccs.example.com"
);
let endpoints_with_sgx_path = PcsEndpoints::new(
"https://pccs.example.com/sgx/certification/v4",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(endpoints_with_sgx_path.base_url, "https://pccs.example.com");
let endpoints_with_tdx_path = PcsEndpoints::new(
"https://pccs.example.com/tdx/certification/v4",
false,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(endpoints_with_tdx_path.base_url, "https://pccs.example.com");
}
#[test]
fn test_pcs_endpoints_url_pckcrl() {
let processor_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
processor_endpoints.url_pckcrl(),
"https://pccs.example.com/sgx/certification/v4/pckcrl?ca=processor&encoding=der"
);
let platform_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
true,
"B0C06F000000".to_string(),
PLATFORM_ISSUER_ID,
);
assert_eq!(
platform_endpoints.url_pckcrl(),
"https://pccs.example.com/sgx/certification/v4/pckcrl?ca=platform&encoding=der"
);
}
#[test]
fn test_pcs_endpoints_url_rootcacrl() {
let endpoints = PcsEndpoints::new(
"https://pccs.example.com",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
endpoints.url_rootcacrl(),
"https://pccs.example.com/sgx/certification/v4/rootcacrl"
);
}
#[test]
fn test_pcs_endpoints_url_tcb() {
let sgx_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
sgx_endpoints.url_tcb(),
"https://pccs.example.com/sgx/certification/v4/tcb?fmspc=B0C06F000000"
);
let tdx_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
false,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
tdx_endpoints.url_tcb(),
"https://pccs.example.com/tdx/certification/v4/tcb?fmspc=B0C06F000000"
);
}
#[test]
fn test_pcs_endpoints_url_qe_identity() {
let sgx_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
true,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
sgx_endpoints.url_qe_identity(),
"https://pccs.example.com/sgx/certification/v4/qe/identity?update=standard"
);
let tdx_endpoints = PcsEndpoints::new(
"https://pccs.example.com",
false,
"B0C06F000000".to_string(),
PROCESSOR_ISSUER_ID,
);
assert_eq!(
tdx_endpoints.url_qe_identity(),
"https://pccs.example.com/tdx/certification/v4/qe/identity?update=standard"
);
}
#[test]
fn test_intel_pcs_url() {
assert_eq!(PCS_URL, "https://api.trustedservices.intel.com");
let fmspc = "B0C06F000000";
let intel_endpoints =
PcsEndpoints::new(PCS_URL, true, fmspc.to_string(), PROCESSOR_ISSUER_ID);
assert_eq!(
intel_endpoints.url_pckcrl(),
"https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=processor&encoding=der"
);
assert_eq!(
intel_endpoints.url_rootcacrl(),
"https://api.trustedservices.intel.com/sgx/certification/v4/rootcacrl"
);
assert_eq!(
intel_endpoints.url_tcb(),
"https://api.trustedservices.intel.com/sgx/certification/v4/tcb?fmspc=B0C06F000000"
);
assert_eq!(
intel_endpoints.url_qe_identity(),
"https://api.trustedservices.intel.com/sgx/certification/v4/qe/identity?update=standard"
);
}
}