Skip to content

Commit

Permalink
Verify the quote certificate chain
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-mobilecoin committed Jul 26, 2022
1 parent b90ae39 commit 8541662
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 10 deletions.
5 changes: 4 additions & 1 deletion untrusted/quote_verify/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ sha2 = "0.10.2"
hex = "0.4.3"
displaydoc = "0.2.3"
p256 = { version = "0.11.1", features = [ "ecdsa", "pem" ] }
x509-parser = "0.14.0"
x509-parser = { version = "0.14.0", features = [ "verify" ] }

[dev-dependencies]
base64 = "0.13.0"
16 changes: 16 additions & 0 deletions untrusted/quote_verify/data/DCAPCACert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw
aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG
A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0
aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT
AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7
1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB
uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ
MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50
ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV
Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI
KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg
AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=
-----END CERTIFICATE-----
165 changes: 156 additions & 9 deletions untrusted/quote_verify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ use x509_parser::{
pem::{self, Pem},
};

/// The root signing CA
const ROOT_CERT_PEM: &[u8] = include_bytes!("../data/DCAPCACert.pem");

// The size of a quote header. Table 3 of
// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf
const QUOTE_HEADER_SIZE: usize = 48;
Expand Down Expand Up @@ -85,6 +88,15 @@ const QUOTING_ENCLAVE_AUTHENTICATION_DATA_SIZE_START: usize =
const QUOTING_ENCLAVE_AUTHENTICATION_DATA_START: usize =
QUOTING_ENCLAVE_AUTHENTICATION_DATA_SIZE_START + 2;

// TODO Should be looking up the certification data instead of hardcoding
// offset, To be fixed with #25
// The starting byte of the certification data for the quote.
// *Certification Data* from Table 9 of
// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf
// which comes from the *QE Certification Data* of the Quote Signature Data
// Structure in Table 4.
const QUOTING_ENCLAVE_CERTIFICATION_DATA_START: usize = 0x41C;

/// A quote for DCAP attestation
pub struct Quote {
bytes: Vec<u8>,
Expand All @@ -102,6 +114,32 @@ impl Quote {
}
}

pub fn verify_certificate_chain(&self) -> Result<(), Error> {
let (_, pem) = pem::parse_x509_pem(ROOT_CERT_PEM)?;
let root_cert = pem.parse_x509()?;

let pems =
Pem::iter_from_buffer(&self.bytes[QUOTING_ENCLAVE_CERTIFICATION_DATA_START..])
.collect::<Result<Vec<_>, _>>()?;

// Certs have a lifetime dependent on `pems` so must create them once
// the pems are held in place.
let mut certs = pems.iter().map(|p|{
p.parse_x509()
}).collect::<Result<Vec<_>, _>>()?;

// Certs are in order from leaf -> .. -> root. We need to start
// verifying from the root.
certs.reverse();

let mut key = root_cert.public_key();
for cert in certs.iter() {
cert.verify_signature(Some(&key))?;
key = cert.public_key();
}
Ok(())
}

/// Verify the enclave report body within the quote.
pub fn verify_enclave_report_body(&self) -> Result<(), Error> {
let bytes = self.get_header_and_enclave_report_body();
Expand Down Expand Up @@ -175,9 +213,8 @@ impl Quote {
/// the quoting enclave report.
/// Note: The certificate is assumed to be valid.
fn get_pck_pem(&self) -> Result<Pem, Error> {
//TODO Should be looking up the certification data instead of hardcoding
// offset, To be fixed with #25
let (_, pem) = pem::parse_x509_pem(&self.bytes[0x41C..])?;
let (_, pem) =
pem::parse_x509_pem(&self.bytes[QUOTING_ENCLAVE_CERTIFICATION_DATA_START..])?;
Ok(pem)
}

Expand Down Expand Up @@ -210,8 +247,8 @@ impl Quote {
#[derive(Display, Debug, PartialEq, Eq)]
/// Error from verifying a Quote
pub enum Error {
/// Unable to load the signing Certificate
Certificate,
/// Unable to validate certificate used in signing
Certificate(String),

/// Failure to parse the Pem files from the quote data
PemParsing(String),
Expand All @@ -238,6 +275,12 @@ impl From<spkiError> for Error {
}
}

impl From<X509Error> for Error {
fn from(src: X509Error) -> Self {
Self::Certificate(src.to_string())
}
}

impl From<x509_parser::nom::Err<X509Error>> for Error {
fn from(src: x509_parser::nom::Err<X509Error>) -> Self {
Self::PemParsing(src.to_string())
Expand All @@ -250,11 +293,45 @@ impl From<x509_parser::nom::Err<PEMError>> for Error {
}
}

impl From<PEMError> for Error {
fn from(src: PEMError) -> Self {
Self::PemParsing(src.to_string())
}
}

#[cfg(test)]
mod tests {
use super::*;

const HW_QUOTE: &[u8] = include_bytes!("../tests/data/hw_quote.dat");

/// Returns the PEM Certificate of the `der` contents.
///
/// #Arguments:
/// * `der` - The der version of a certificate to convert to PEM format
fn pem_certificate(der: &[u8]) -> String {
let encoded = base64::encode(der);
let pem_certificate = format!(
"-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----\n",
encoded
);
pem_certificate
}

/// Returns the PEMs in `chain` as individual Strings per pem.
/// # Arguments:
/// * `chain` - Contiguous bytes representing a cert chain as PEMs.
fn extract_pems(chain: &[u8]) -> Vec<String> {
let pems = Pem::iter_from_buffer(chain)
.map(|p| {
let pem = p.unwrap();
pem_certificate(&pem.contents)
})
.collect::<Vec<_>>();

pems
}

#[test]
fn verify_valid_quote_report() {
let quote = Quote::from_bytes(HW_QUOTE);
Expand All @@ -274,10 +351,7 @@ mod tests {

#[test]
fn failure_to_parse_pem_certificates() {
// TODO Once more of the quote parsing logic comes in remove hard coded
// value of 0x41c, based on current quote data file. To be fixed with
// #25
let quote = Quote::from_bytes(&HW_QUOTE[..0x41c]);
let quote = Quote::from_bytes(&HW_QUOTE[..QUOTING_ENCLAVE_CERTIFICATION_DATA_START]);
assert!(matches!(
quote.verify_quoting_enclave_report(),
Err(Error::PemParsing(_))
Expand Down Expand Up @@ -356,4 +430,77 @@ mod tests {
quote.bytes[QUOTING_ENCLAVE_REPORT_DATA_START + (ENCLAVE_REPORT_DATA_SIZE - 1)] = 1;
assert_eq!(quote.verify_attestation_key(), Err(Error::AttestationKey));
}

#[test]
fn verify_valid_certificate_chain() {
let quote = Quote::from_bytes(HW_QUOTE);
assert!(quote.verify_certificate_chain().is_ok());
}

#[test]
fn invalid_certificate_chain_fails() {
let mut hw_quote = HW_QUOTE.to_vec();
let pem_contents = hw_quote
.drain(QUOTING_ENCLAVE_CERTIFICATION_DATA_START..)
.collect::<Vec<_>>();
let pems = extract_pems(pem_contents.as_slice());

hw_quote.extend(pems[0].as_bytes());
// Skipping the intermediate cert to force a signing chain error
hw_quote.extend(pems[2].as_bytes());

let quote = Quote::from_bytes(hw_quote.as_slice());
assert!(matches!(
quote.verify_certificate_chain(),
Err(Error::Certificate(_))
));
}

#[test]
fn bad_pem_file_in_cert_chain() {
let mut hw_quote = HW_QUOTE.to_vec();
let pem_contents = hw_quote
.drain(QUOTING_ENCLAVE_CERTIFICATION_DATA_START..)
.collect::<Vec<_>>();
let mut pems = extract_pems(pem_contents.as_slice());

// 'A' isn't significant, just a value inserted in the middle that
// should force an error state
let middle = pems[1].len() / 2;
pems[1].insert(middle, 'A');

hw_quote.extend(pems.join("").as_bytes());

let quote = Quote::from_bytes(hw_quote.as_slice());
assert!(matches!(
quote.verify_certificate_chain(),
Err(Error::PemParsing(_))
));
}

#[test]
fn bad_der_representation_in_cert_chain() {
let mut hw_quote = HW_QUOTE.to_vec();

let pem_contents = hw_quote
.drain(QUOTING_ENCLAVE_CERTIFICATION_DATA_START..)
.collect::<Vec<_>>();
let mut pems = extract_pems(pem_contents.as_slice());

let (_, pem) = pem::parse_x509_pem(pems[2].as_bytes()).unwrap();
let mut der_contents = pem.contents.clone();

// '3' isn't significant, just a value inserted in the middle that
// should force an error state
der_contents.insert(der_contents.len() / 2, b'3');
pems[2] = pem_certificate(&der_contents);

hw_quote.extend(pems.join("").as_bytes());

let quote = Quote::from_bytes(hw_quote.as_slice());
assert!(matches!(
quote.verify_certificate_chain(),
Err(Error::PemParsing(_))
));
}
}

0 comments on commit 8541662

Please sign in to comment.