From ae836367cce196212a9bc12f447327c3465d257c Mon Sep 17 00:00:00 2001 From: Nick Santana Date: Mon, 27 Jun 2022 13:55:11 -0700 Subject: [PATCH 1/2] Verify the quoting enclave report body Adds logic to verify the quoting enclave report body from a quote. --- untrusted/.gitignore | 10 ++ untrusted/Cargo.toml | 5 + untrusted/quote_verify/Cargo.toml | 10 ++ untrusted/quote_verify/src/lib.rs | 167 ++++++++++++++++++ untrusted/quote_verify/tests/data/README.md | 6 + .../quote_verify/tests/data/hw_quote.dat | Bin 0 -> 4603 bytes 6 files changed, 198 insertions(+) create mode 100644 untrusted/.gitignore create mode 100644 untrusted/Cargo.toml create mode 100644 untrusted/quote_verify/Cargo.toml create mode 100644 untrusted/quote_verify/src/lib.rs create mode 100644 untrusted/quote_verify/tests/data/README.md create mode 100644 untrusted/quote_verify/tests/data/hw_quote.dat diff --git a/untrusted/.gitignore b/untrusted/.gitignore new file mode 100644 index 00000000..088ba6ba --- /dev/null +++ b/untrusted/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/untrusted/Cargo.toml b/untrusted/Cargo.toml new file mode 100644 index 00000000..f2ea88c1 --- /dev/null +++ b/untrusted/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +resolver = "2" +members = [ + "quote_verify", +] diff --git a/untrusted/quote_verify/Cargo.toml b/untrusted/quote_verify/Cargo.toml new file mode 100644 index 00000000..3eff2178 --- /dev/null +++ b/untrusted/quote_verify/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "mc-sgx-quote-verify" +version = "0.1.0" +edition = "2021" + +[dependencies] +pem = "1.0.2" +sha2 = "0.10.2" +mbedtls = "0.8.1" +hex = "0.4.3" diff --git a/untrusted/quote_verify/src/lib.rs b/untrusted/quote_verify/src/lib.rs new file mode 100644 index 00000000..24f8daa8 --- /dev/null +++ b/untrusted/quote_verify/src/lib.rs @@ -0,0 +1,167 @@ +// Copyright (c) 2022 The MobileCoin Foundation + +use mbedtls::alloc::Box as MbedtlsBox; +use mbedtls::hash::Type as HashType; +use mbedtls::x509::Certificate; +use pem::PemError; +use sha2::{Digest, Sha256}; + +// THe size of an enclave report (body). Table 5 of +// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf +const ENCLAVE_REPORT_SIZE: usize = 384; + +// Size of one of the components of the ECDSA signature. +// Table 6 of +// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf +const SIGNATURE_COMPONENT_SIZE: usize = 32; + +// The starting byte of the quote report. The *QE Report* member of the Quote +// Signature Data Structure. Table 4 of +// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf +// TODO as more of the quote structure is brought in, derive the start by summation +// of previous type/member sizes. +const QUOTING_ENCLAVE_REPORT_START: usize = 0x234; + +// The starting byte of the signature for the quote. *QE Report Signature* of +// the Quote Signature Data Structure. Table 4 of +// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf +const QUOTING_ENCLAVE_SIGNATURE_START: usize = QUOTING_ENCLAVE_REPORT_START + ENCLAVE_REPORT_SIZE; + +// ASN.1 Tag for an integer +const ASN1_INTEGER: u8 = 2; +// ASN.1 Tag for a sequence +const ASN1_SEQUENCE: u8 = 48; + +/// A quote for DCAP attestation +pub struct Quote { + bytes: Vec, +} + +#[derive(Debug, PartialEq)] +pub enum Error { + /// Unable to load the Certificate with mbedtls + Certificate(mbedtls::Error), + /// Failure to parse the Pem files from the quote data + PemParsing(PemError), + /// Failure to verify a Signature + Signature(mbedtls::Error), +} + +impl From for Error { + fn from(src: PemError) -> Self { + Self::PemParsing(src) + } +} + +impl Quote { + /// Returns a [Quote] created from the provided `bytes`. + /// + /// # Arguments + /// + /// * `bytes` the bytes of the quote as defined in + /// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf + pub fn from_bytes(bytes: &[u8]) -> Self { + Quote { + bytes: bytes.to_vec(), + } + } + + /// Verify the quoting enclave within the report. + pub fn verify_quoting_enclave_report(&self) -> Result<(), Error> { + let signature = self.get_ans1_signature(); + let report = self.get_quoting_enclave_report(); + let hash = Sha256::digest(report); + let mut cert = self.get_pck_certificate()?; + cert.public_key_mut() + .verify(HashType::Sha256, &hash, &signature) + .map_err(Error::Signature) + } + + /// Returns the PCK certificate that was used for signing the quoting + /// enclave report. + /// Note: This certificate is assumed to be valid. + fn get_pck_certificate(&self) -> Result, Error> { + //TODO this should only be looking at the `Certification Data` + // of the `QE Certification Data`, Table 9 of + // https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf + // However the pem crate walks over the other data nicely for initial development + let pem = pem::parse(self.bytes.as_slice())?; + Certificate::from_pem(&pem.contents).map_err(Error::Certificate) + } + + /// Returns the quoting enclave report from the overall quote. + fn get_quoting_enclave_report(&self) -> &[u8] { + &self.bytes + [QUOTING_ENCLAVE_REPORT_START..QUOTING_ENCLAVE_REPORT_START + ENCLAVE_REPORT_SIZE] + } + + /// Returns the ASN.1 version of the signature. + /// mbedtls wants an ASN.1 version of the signature, while the raw quote + /// format has only the r and s values of the ECDSA signature + fn get_ans1_signature(&self) -> Vec { + let mut start = QUOTING_ENCLAVE_SIGNATURE_START; + let r = &self.bytes[start..start + SIGNATURE_COMPONENT_SIZE]; + start += SIGNATURE_COMPONENT_SIZE; + let s = &self.bytes[start..start + SIGNATURE_COMPONENT_SIZE]; + + // The `2`s are the tag byte + length byte + let length = 2 + r.len() + 2 + s.len(); + let mut signature = vec![ASN1_SEQUENCE, length as u8]; + for component in [r, s] { + signature.extend([ASN1_INTEGER, component.len() as u8]); + signature.extend(component); + } + signature + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const HW_QUOTE: &[u8] = include_bytes!("../tests/data/hw_quote.dat"); + #[test] + fn verify_valid_quote_report() { + let quote = Quote::from_bytes(HW_QUOTE); + assert!(quote.verify_quoting_enclave_report().is_ok()); + } + + #[test] + fn invalid_quote_report() { + let mut bad_quote = HW_QUOTE.to_vec(); + bad_quote[QUOTING_ENCLAVE_REPORT_START + 1] = 0; + let quote = Quote::from_bytes(&bad_quote); + assert_eq!( + quote.verify_quoting_enclave_report(), + Err(Error::Signature(mbedtls::Error::EcpVerifyFailed)) + ); + } + + #[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. + let quote = Quote::from_bytes(&HW_QUOTE[..0x41c]); + assert_eq!( + quote.verify_quoting_enclave_report(), + Err(Error::PemParsing(PemError::MalformedFraming)) + ); + } + + #[test] + fn failure_to_load_certificate() { + let mut bad_cert = HW_QUOTE.to_vec(); + // TODO Once more of the quote parsing logic comes in remove hard coded + // value of 0x440, based on current quote data file. + bad_cert[0x440] = 0; + let quote = Quote::from_bytes(&bad_cert); + + // Since the pem is utilized for parsing the certificates out of the + // quote data, it fails before `Certificate::from_pem()` gets a chance + // to fail + assert!(matches!( + quote.verify_quoting_enclave_report(), + Err(Error::PemParsing(_)) + )); + } +} diff --git a/untrusted/quote_verify/tests/data/README.md b/untrusted/quote_verify/tests/data/README.md new file mode 100644 index 00000000..fbc4a825 --- /dev/null +++ b/untrusted/quote_verify/tests/data/README.md @@ -0,0 +1,6 @@ +Data files to help with testing quote verifications + +* hw_quote.dat: + A valid DCAP quote from an SGX platform. This quote was made using the QuoteGenerationSample from + the [SGXDataCenterAttestationPrimitives](https://github.com/intel/SGXDataCenterAttestationPrimitives) repository. + \ No newline at end of file diff --git a/untrusted/quote_verify/tests/data/hw_quote.dat b/untrusted/quote_verify/tests/data/hw_quote.dat new file mode 100644 index 0000000000000000000000000000000000000000..30c6a286b1d8da5e12a5861153488f92751b9fe1 GIT binary patch literal 4603 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 Date: Wed, 29 Jun 2022 06:57:12 -0700 Subject: [PATCH 2/2] Fix misspelling of ASN.1 in function name Also: - Define the magic value for size of type and length fields of an ASN.1 entry - Grouped common use statements --- untrusted/.gitignore | 2 +- untrusted/quote_verify/src/lib.rs | 49 ++++++++++++++++--------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/untrusted/.gitignore b/untrusted/.gitignore index 088ba6ba..60c31362 100644 --- a/untrusted/.gitignore +++ b/untrusted/.gitignore @@ -7,4 +7,4 @@ Cargo.lock # These are backup files generated by rustfmt -**/*.rs.bk +*.rs.bk diff --git a/untrusted/quote_verify/src/lib.rs b/untrusted/quote_verify/src/lib.rs index 24f8daa8..5ce359e1 100644 --- a/untrusted/quote_verify/src/lib.rs +++ b/untrusted/quote_verify/src/lib.rs @@ -1,8 +1,6 @@ // Copyright (c) 2022 The MobileCoin Foundation -use mbedtls::alloc::Box as MbedtlsBox; -use mbedtls::hash::Type as HashType; -use mbedtls::x509::Certificate; +use mbedtls::{alloc::Box as MbedtlsBox, hash::Type as HashType, x509::Certificate}; use pem::PemError; use sha2::{Digest, Sha256}; @@ -32,27 +30,15 @@ const ASN1_INTEGER: u8 = 2; // ASN.1 Tag for a sequence const ASN1_SEQUENCE: u8 = 48; +// The byte size of the `type` and `length` fields of the ANS.1 +// type-length-value stream +const ASN1_TYPE_LENGTH_SIZE: usize = 2; + /// A quote for DCAP attestation pub struct Quote { bytes: Vec, } -#[derive(Debug, PartialEq)] -pub enum Error { - /// Unable to load the Certificate with mbedtls - Certificate(mbedtls::Error), - /// Failure to parse the Pem files from the quote data - PemParsing(PemError), - /// Failure to verify a Signature - Signature(mbedtls::Error), -} - -impl From for Error { - fn from(src: PemError) -> Self { - Self::PemParsing(src) - } -} - impl Quote { /// Returns a [Quote] created from the provided `bytes`. /// @@ -68,7 +54,7 @@ impl Quote { /// Verify the quoting enclave within the report. pub fn verify_quoting_enclave_report(&self) -> Result<(), Error> { - let signature = self.get_ans1_signature(); + let signature = self.get_asn1_signature(); let report = self.get_quoting_enclave_report(); let hash = Sha256::digest(report); let mut cert = self.get_pck_certificate()?; @@ -98,15 +84,14 @@ impl Quote { /// Returns the ASN.1 version of the signature. /// mbedtls wants an ASN.1 version of the signature, while the raw quote /// format has only the r and s values of the ECDSA signature - fn get_ans1_signature(&self) -> Vec { + fn get_asn1_signature(&self) -> Vec { let mut start = QUOTING_ENCLAVE_SIGNATURE_START; let r = &self.bytes[start..start + SIGNATURE_COMPONENT_SIZE]; start += SIGNATURE_COMPONENT_SIZE; let s = &self.bytes[start..start + SIGNATURE_COMPONENT_SIZE]; - // The `2`s are the tag byte + length byte - let length = 2 + r.len() + 2 + s.len(); - let mut signature = vec![ASN1_SEQUENCE, length as u8]; + let sequence_length = ASN1_TYPE_LENGTH_SIZE + r.len() + ASN1_TYPE_LENGTH_SIZE + s.len(); + let mut signature = vec![ASN1_SEQUENCE, sequence_length as u8]; for component in [r, s] { signature.extend([ASN1_INTEGER, component.len() as u8]); signature.extend(component); @@ -115,6 +100,22 @@ impl Quote { } } +#[derive(Debug, PartialEq)] +pub enum Error { + /// Unable to load the Certificate with mbedtls + Certificate(mbedtls::Error), + /// Failure to parse the Pem files from the quote data + PemParsing(PemError), + /// Failure to verify a Signature + Signature(mbedtls::Error), +} + +impl From for Error { + fn from(src: PemError) -> Self { + Self::PemParsing(src) + } +} + #[cfg(test)] mod tests { use super::*;