Skip to content

Commit

Permalink
dcap-artifact-retrieval: Implement provisioning client for PCCS
Browse files Browse the repository at this point in the history
  • Loading branch information
mzohreva committed Dec 13, 2024
1 parent 52c797d commit 4034ce6
Show file tree
Hide file tree
Showing 8 changed files with 887 additions and 139 deletions.
64 changes: 41 additions & 23 deletions intel-sgx/dcap-artifact-retrieval/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use std::convert::TryInto;
use std::path::{Path, PathBuf};

use clap::clap_app;
use pcs::PckID;
use reqwest::Url;
use rustc_serialize::hex::ToHex;
use serde::de::{value, IntoDeserializer};
use serde::Deserialize;

use crate::{
AzureProvisioningClientBuilder, Error, IntelProvisioningClientBuilder, PcsVersion,
ProvisioningClient, StatusCode,
AzureProvisioningClientBuilder, Error, IntelProvisioningClientBuilder,
PccsProvisioningClientBuilder, PcsVersion, ProvisioningClient, StatusCode,
};

#[derive(Debug, Deserialize, Copy, Clone, Eq, PartialEq, Hash)]
#[serde(rename_all = "kebab-case")]
enum Origin {
Intel,
Azure,
Pccs,
}

fn str_deserialize(s: &str) -> value::StrDeserializer<value::Error> {
Expand Down Expand Up @@ -63,21 +64,10 @@ fn download_dcap_artifacts(
println!(" Storing artifacts:");
}

// Fetch pckcerts, note that Azure does not support this API, instead we mimic it
let pckcerts = match prov_client.pckcerts(&pckid.enc_ppid, pckid.pce_id) {
Ok(pckcerts) => pckcerts,
Err(Error::RequestNotSupported) => prov_client
.pckcert(
None,
&pckid.pce_id,
&pckid.cpu_svn,
pckid.pce_isvsvn,
Some(&pckid.qe_id),
)?
.try_into()
.map_err(|e| Error::PCSDecodeError(format!("{}", e).into()))?,
Err(e) => return Err(e),
};
// Fetch pckcerts, note that Azure and PCCS do not support this API,
// instead we mimic it using pckcert API.
let pckcerts = prov_client.pckcerts_with_fallback(&pckid)?;

let pckcerts_file = pckcerts.store(output_dir, pckid.qe_id.as_slice())?;

if verbose {
Expand Down Expand Up @@ -136,13 +126,23 @@ pub fn main() {
match value {
"3" => Ok(PcsVersion::V3),
"4" => Ok(PcsVersion::V4),
_ => Err(format!(
"Expected 3 or 4, found `{}`",
value
)),
_ => Err(format!("Expected 3 or 4, found `{}`", value)),
}
}

fn is_url(value: String) -> Result<(), String> {
let url = Url::parse(&value)
.map_err(|e| format!("cannot parse `{}` as a valid URL: {}", value, e))?;

if url.scheme() != "http" && url.scheme() != "https" {
return Err(format!(
"Expected an http or https URL found: `{}`",
url.scheme()
));
}
Ok(())
}

let matches = clap::clap_app!(("DCAP Artifact Retrieval Tool") =>
(author: "Fortanix")
(about: "Fortanix ecdsa artifact retrieval tool for DCAP attestation")
Expand Down Expand Up @@ -171,6 +171,15 @@ pub fn main() {
@arg API_KEY: --("api-key") +takes_value
"API key for authenticating with Intel provisioning service"
)
(
@arg PCCS_URL: --("pccs-url") +takes_value required_if("ORIGIN", "pccs")
validator(is_url)
"PCCS base URL. This is relevant only when using `--origin pccs`."
)
(
@arg INSECURE: -k --insecure
"Do not verify that server's hostname matches their TLS certificate and accept self-signed certificates. This is insecure."
)
(
@arg VERBOSE: -v --verbose
"Print information of which files are fetched"
Expand All @@ -190,7 +199,11 @@ pub fn main() {
let origin =
parse_origin(matches.value_of("ORIGIN").unwrap_or("intel")).expect("validated");

let fetcher = crate::reqwest_client();
let fetcher = match matches.is_present("INSECURE") {
false => crate::reqwest_client(),
true => crate::reqwest_client_insecure_tls(),
};

let client: Box<dyn ProvisioningClient> = match origin {
Origin::Intel => {
let mut client_builder = IntelProvisioningClientBuilder::new(api_version);
Expand All @@ -203,6 +216,11 @@ pub fn main() {
let client_builder = AzureProvisioningClientBuilder::new(api_version);
Box::new(client_builder.build(fetcher))
}
Origin::Pccs => {
let pccs_url = matches.value_of("PCCS_URL").expect("validated").to_owned();
let client_builder = PccsProvisioningClientBuilder::new(api_version, pccs_url);
Box::new(client_builder.build(fetcher))
}
};
download_dcap_artifacts(&*client, pckid_file, output_dir, 0 < verboseness)
}
Expand Down
13 changes: 12 additions & 1 deletion intel-sgx/dcap-artifact-retrieval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! DCAP attestations require access to Intel-signed artifacts. This library provides clients to
//! access these artifacts both from Intel directly, and from Microsoft Azure.
#[cfg(not(target_env = "sgx"))]
#[cfg(all(not(target_env = "sgx"), feature = "reqwest"))]
pub mod cli;
pub mod provisioning_client;

Expand Down Expand Up @@ -120,3 +120,14 @@ pub fn reqwest_client() -> ReqwestClient {
.build()
.expect("Failed to build reqwest client")
}

#[cfg(feature = "reqwest")]
#[doc(hidden)]
pub fn reqwest_client_insecure_tls() -> ReqwestClient {
ReqwestClient::builder()
.use_native_tls()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()
.expect("Failed to build reqwest client")
}
2 changes: 1 addition & 1 deletion intel-sgx/dcap-artifact-retrieval/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#[cfg(not(target_env = "sgx"))]
#[cfg(all(not(target_env = "sgx"), feature = "reqwest"))]
fn main() {
dcap_artifact_retrieval::cli::main()
}
65 changes: 7 additions & 58 deletions intel-sgx/dcap-artifact-retrieval/src/provisioning_client/azure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use pcs::{CpuSvn, EncPpid, PceId, PceIsvsvn, PckCert, PckCerts, QeId, Unverified};
use pcs::{CpuSvn, EncPpid, PceId, PceIsvsvn, PckCert, QeId, Unverified};
use rustc_serialize::hex::ToHex;
use serde::Deserialize;
use std::time::Duration;

use super::common::PckCertsApiNotSupported;
use super::intel::{PckCrlApi, QeIdApi, TcbInfoApi};
use super::{Fetcher, ProvisioningServiceApi, StatusCode};
use crate::provisioning_client::{
Client, ClientBuilder, PckCertIn, PckCertService, PckCertsIn, PckCertsService, PcsVersion,
use super::{
Client, ClientBuilder, Fetcher, PckCertIn, PckCertService, PcsVersion, ProvisioningServiceApi,
StatusCode,
};
use crate::Error;

Expand Down Expand Up @@ -41,7 +42,7 @@ impl AzureProvisioningClientBuilder {
}

pub fn build<F: for<'a> Fetcher<'a>>(self, fetcher: F) -> Client<F> {
let pck_certs = PckCertsApi::new(None);
let pck_certs = PckCertsApiNotSupported;
let pck_cert = PckCertApi::new(self.api_version.clone());
let pck_crl = PckCrlApi::new(self.api_version.clone());
let qeid = QeIdApi::new(self.api_version.clone());
Expand All @@ -51,58 +52,6 @@ impl AzureProvisioningClientBuilder {
}
}

pub struct PckCertsApi {
api_key: Option<String>,
}

impl PckCertsApi {
pub(crate) fn new(api_key: Option<String>) -> PckCertsApi {
PckCertsApi { api_key }
}
}

impl<'inp> PckCertsService<'inp> for PckCertsApi {
fn build_input(
&'inp self,
enc_ppid: &'inp EncPpid,
pce_id: PceId,
) -> <Self as ProvisioningServiceApi<'inp>>::Input {
PckCertsIn {
enc_ppid,
pce_id,
api_key: &self.api_key,
api_version: PcsVersion::V3,
}
}
}

/// Implementation of pckcerts
/// <https://api.portal.trustedservices.intel.com/documentation#pcs-certificates-v4>
impl<'inp> ProvisioningServiceApi<'inp> for PckCertsApi {
type Input = PckCertsIn<'inp>;
type Output = PckCerts;

fn build_request(
&self,
_input: &Self::Input,
) -> Result<(String, Vec<(String, String)>), Error> {
Err(Error::RequestNotSupported)
}

fn validate_response(&self, _status_code: StatusCode) -> Result<(), Error> {
Err(Error::RequestNotSupported)
}

fn parse_response(
&self,
_response_body: String,
_response_headers: Vec<(String, String)>,
_api_version: PcsVersion,
) -> Result<Self::Output, Error> {
Err(Error::RequestNotSupported)
}
}

pub struct PckCertApi {
api_version: PcsVersion,
}
Expand Down Expand Up @@ -200,7 +149,7 @@ impl<'inp> ProvisioningServiceApi<'inp> for PckCertApi {
status_code,
"PCS is temporarily unavailable",
)),
__ => Err(Error::PCSError(
_ => Err(Error::PCSError(
status_code.clone(),
"Unexpected response from PCS server",
)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* Copyright (c) Fortanix, Inc.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use pcs::{EncPpid, PceId, PckCerts};
use percent_encoding::percent_decode;
use pkix::pem::PemBlock;

use super::{PckCertsIn, PckCertsService, PcsVersion, ProvisioningServiceApi, StatusCode};
use crate::Error;

pub const PCK_CERTIFICATE_ISSUER_CHAIN_HEADER: &'static str = "SGX-PCK-Certificate-Issuer-Chain";
pub const PCK_CRL_ISSUER_CHAIN_HEADER: &'static str = "SGX-PCK-CRL-Issuer-Chain";
pub const TCB_INFO_ISSUER_CHAIN_HEADER_V3: &'static str = "SGX-TCB-Info-Issuer-Chain";
pub const TCB_INFO_ISSUER_CHAIN_HEADER_V4: &'static str = "TCB-Info-Issuer-Chain";
pub const ENCLAVE_ID_ISSUER_CHAIN_HEADER: &'static str = "SGX-Enclave-Identity-Issuer-Chain";

pub struct PckCertsApiNotSupported;

impl<'inp> PckCertsService<'inp> for PckCertsApiNotSupported {
fn build_input(
&'inp self,
enc_ppid: &'inp EncPpid,
pce_id: PceId,
) -> <Self as ProvisioningServiceApi<'inp>>::Input {
PckCertsIn {
enc_ppid,
pce_id,
api_key: &None,
api_version: PcsVersion::V3, // does not matter, this API is not supported!
}
}
}

impl<'inp> ProvisioningServiceApi<'inp> for PckCertsApiNotSupported {
type Input = PckCertsIn<'inp>;
type Output = PckCerts;

fn build_request(
&self,
_input: &Self::Input,
) -> Result<(String, Vec<(String, String)>), Error> {
Err(Error::RequestNotSupported)
}

fn validate_response(&self, _status_code: StatusCode) -> Result<(), Error> {
Err(Error::RequestNotSupported)
}

fn parse_response(
&self,
_response_body: String,
_response_headers: Vec<(String, String)>,
_api_version: PcsVersion,
) -> Result<Self::Output, Error> {
Err(Error::RequestNotSupported)
}
}

/// Returns the certificate chain starting from the leaf CA.
pub fn parse_issuer_header(
headers: &Vec<(String, String)>,
header: &'static str,
) -> Result<Vec<String>, Error> {
let cert_chain = headers
.iter()
.find_map(|(key, value)| {
if key.to_lowercase() == header.to_lowercase() {
Some(value)
} else {
None
}
})
.ok_or(Error::HeaderMissing(header))?;

let cert_chain = percent_decode(cert_chain.as_bytes())
.decode_utf8()
.map_err(|e| Error::HeaderDecodeError(e))?;

let mut chain: Vec<String> = vec![];
for cert in PemBlock::new(cert_chain.as_bytes()) {
let cert = String::from_utf8(cert.to_vec())
.map_err(|_| Error::CertificateParseError("Cert could not be decoded into utf8"))?;

chain.push(cert);
}
Ok(chain)
}
Loading

0 comments on commit 4034ce6

Please sign in to comment.