diff --git a/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/mod.rs b/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/mod.rs index d732e62a..40e3454b 100644 --- a/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/mod.rs +++ b/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/mod.rs @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::hash::{DefaultHasher, Hash, Hasher}; use std::io::Read; @@ -534,10 +535,13 @@ pub trait ProvisioningClient { fn qe_identity(&self) -> Result; - /// Retrieve PCK certificates using `pckcerts()` and fallback to `pckcert()` - /// if provisioning client does not support pckcerts. Note that in case of - /// fallback the returned PckCerts will only contain a single certificate - /// associated with the highest TCB level applied to the platform. + /// Retrieve PCK certificates using `pckcerts()` and fallback to the + /// following method if that's not supported: + /// - Call `pckcert()` to find the FMSPC. + /// - Using the FMSPC value, call `tcbinfo()` to get TCB info. + /// - For each TCB level in the result of previous call: + /// - Call `pckcert()` to get the best available PCK cert for that TCB level. + /// Note that PCK certs for some TCB levels may be missing. fn pckcerts_with_fallback(&self, pck_id: &PckID) -> Result { match self.pckcerts(&pck_id.enc_ppid, pck_id.pce_id) { Ok(pck_certs) => return Ok(pck_certs), @@ -545,6 +549,9 @@ pub trait ProvisioningClient { Err(e) => return Err(e), } // fallback: + + // NOTE: at least with PCCS, any call to `pckcert()` will return the + // "best available" PCK cert for the specified TCB level. let pck_cert = self.pckcert( Some(&pck_id.enc_ppid), &pck_id.pce_id, @@ -552,8 +559,27 @@ pub trait ProvisioningClient { pck_id.pce_isvsvn, Some(&pck_id.qe_id), )?; - - pck_cert + let fmspc = pck_cert.sgx_extension()?.fmspc; + let tcb_info = self.tcbinfo(&fmspc)?; + let tcb_data = tcb_info.data()?; + let mut pcks = HashMap::new(); + for (cpu_svn, pce_isvsvn) in tcb_data.iter_tcb_components() { + let p = match self.pckcert( + Some(&pck_id.enc_ppid), + &pck_id.pce_id, + &cpu_svn, + pce_isvsvn, + Some(&pck_id.qe_id), + ) { + Ok(cert) => cert, + Err(Error::PCSError(StatusCode::NotFound, _)) | + Err(Error::PCSError(StatusCode::NonStandard462, _)) => continue, + Err(other) => return Err(other) + }; + pcks.insert(p.platform_tcb()?.cpusvn, p); + } + let pcks: Vec<_> = pcks.into_iter().map(|(_, v)| v).collect(); + pcks .try_into() .map_err(|e| Error::PCSDecodeError(format!("{}", e).into())) } diff --git a/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/pccs.rs b/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/pccs.rs index 8611b73d..be3cc458 100644 --- a/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/pccs.rs +++ b/intel-sgx/dcap-artifact-retrieval/src/provisioning_client/pccs.rs @@ -536,6 +536,42 @@ mod tests { } } + #[test] + #[ignore = "needs a running PCCS service"] // FIXME + pub fn test_pckcerts_with_fallback() { + for api_version in [PcsVersion::V3, PcsVersion::V4] { + let client = make_client(api_version); + + for pckid in PckID::parse_file(&PathBuf::from(PCKID_TEST_FILE).as_path()) + .unwrap() + .iter() + { + let pckcerts = client.pckcerts_with_fallback(&pckid).unwrap(); + println!("Found {} PCK certs.", pckcerts.as_pck_certs().len()); + + let tcb_info = client.tcbinfo(&pckcerts.fmspc().unwrap()).unwrap(); + let tcb_data = tcb_info.data().unwrap(); + + let selected = pckcerts.select_pck( + &tcb_data, + &pckid.cpu_svn, + pckid.pce_isvsvn, + pckid.pce_id, + ).unwrap(); + + let pck = client.pckcert( + Some(&pckid.enc_ppid), + &pckid.pce_id, + &pckid.cpu_svn, + pckid.pce_isvsvn, + Some(&pckid.qe_id), + ).unwrap(); + + assert_eq!(format!("{:?}", selected.sgx_extension().unwrap()), format!("{:?}", pck.sgx_extension().unwrap())); + } + } + } + #[test] #[ignore = "needs a running PCCS service"] // FIXME pub fn tcb_info() { diff --git a/intel-sgx/pcs/src/pckcrt.rs b/intel-sgx/pcs/src/pckcrt.rs index 4e03335a..eef5d7d1 100644 --- a/intel-sgx/pcs/src/pckcrt.rs +++ b/intel-sgx/pcs/src/pckcrt.rs @@ -9,6 +9,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::convert::{TryFrom, TryInto}; use std::marker::PhantomData; +use std::mem; use std::path::PathBuf; use percent_encoding::percent_decode; @@ -144,6 +145,10 @@ impl TcbComponents { pub fn pce_svn(&self) -> u16 { self.0.pcesvn } + + pub fn cpu_svn(&self) -> CpuSvn { + self.0.sgxtcbcomponents.each_ref().map(|c| c.svn) + } } impl PartialOrd for TcbComponents { @@ -743,6 +748,26 @@ where } } +/// NOTE: This conversion is only correct if all PCK certs in the vec have the +/// same CA chain. +impl TryFrom>> for PckCerts +where + V: VerificationType, +{ + type Error = ASN1Error; + + fn try_from(mut pcks: Vec>) -> Result { + let pck_data = pcks.iter().map(|pck| pck.as_pck_cert_body_item()).collect::, ASN1Error>>()?; + // NOTE: assuming that all PCK certs in the vec have the same CA chain, + // so we pick the ca_chain from the first one: + let ca_chain = match pcks.first_mut() { + Some(first) => mem::take(&mut first.ca_chain), + None => return Err(ASN1Error::new(ASN1ErrorKind::Eof)), + }; + Ok(PckCerts { pck_data, ca_chain }) + } +} + #[cfg(test)] mod tests { use dcap_ql::quote::{Qe3CertDataPckCertChain, Quote, Quote3SignatureEcdsaP256}; diff --git a/intel-sgx/pcs/src/tcb_info.rs b/intel-sgx/pcs/src/tcb_info.rs index 4a383613..6af0bece 100644 --- a/intel-sgx/pcs/src/tcb_info.rs +++ b/intel-sgx/pcs/src/tcb_info.rs @@ -19,7 +19,7 @@ use { }; use crate::pckcrt::TcbComponents; -use crate::{io, Error, TcbStatus, Unverified, VerificationType, Verified}; +use crate::{io, CpuSvn, Error, PceIsvsvn, TcbStatus, Unverified, VerificationType, Verified}; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Fmspc([u8; 6]); @@ -221,6 +221,8 @@ impl TcbData { } impl TcbData { + // NOTE: don't make this publicly available. We want to prevent people from + // accessing the TCB levels without checking whether the TcbInfo is valid. pub(crate) fn tcb_levels(&self) -> &Vec { &self.tcb_levels } @@ -233,6 +235,10 @@ impl TcbData { // TCB Type 0 simply copies cpu svn Ok(TcbComponents::from_raw(*raw_cpusvn, pce_svn)) } + + pub fn iter_tcb_components(&self) -> impl Iterator + '_ { + self.tcb_levels.iter().map(|tcb_level| (tcb_level.tcb.cpu_svn(), tcb_level.tcb.pce_svn())) + } } #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]