feat(dcap): Impl DCAP RA types/handlers (#139)

This commit is contained in:
Shoaib Ahmed 2024-08-02 18:31:01 +02:00 committed by GitHub
parent 2ac5726463
commit eb90043172
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 162 additions and 129 deletions

View file

@ -14,6 +14,7 @@ pub type DefaultAttestor = EpidAttestor;
#[cfg(feature = "mock-sgx")] #[cfg(feature = "mock-sgx")]
pub type DefaultAttestor = MockAttestor; pub type DefaultAttestor = MockAttestor;
/// The trait defines the interface for generating attestations from within an enclave.
pub trait Attestor { pub trait Attestor {
type Error: ToString; type Error: ToString;
@ -22,6 +23,7 @@ pub trait Attestor {
fn mr_enclave(&self) -> Result<MrEnclave, Self::Error>; fn mr_enclave(&self) -> Result<MrEnclave, Self::Error>;
} }
/// An `Attestor` for generating EPID attestations for Gramine based enclaves.
#[derive(Clone, PartialEq, Debug, Default)] #[derive(Clone, PartialEq, Debug, Default)]
pub struct EpidAttestor; pub struct EpidAttestor;
@ -44,6 +46,31 @@ impl Attestor for EpidAttestor {
} }
} }
/// An `Attestor` for generating DCAP attestations for Gramine based enclaves.
#[derive(Clone, PartialEq, Debug, Default)]
pub struct DcapAttestor;
impl Attestor for DcapAttestor {
type Error = IoError;
fn quote(&self, user_data: impl HasUserData) -> Result<Vec<u8>, Self::Error> {
let user_data = user_data.user_data();
let mut user_report_data = File::create("/dev/attestation/user_report_data")?;
user_report_data.write_all(user_data.as_slice())?;
user_report_data.flush()?;
read("/dev/attestation/quote")
}
fn mr_enclave(&self) -> Result<MrEnclave, Self::Error> {
let quote = self.quote(NullUserData)?;
Ok(quote[112..(112 + 32)]
.try_into()
.expect("hardcoded array size"))
}
}
/// A mock `Attestor` that creates a quote consisting of just the user report data. (only meant for
/// testing purposes)
#[derive(Clone, PartialEq, Debug, Default)] #[derive(Clone, PartialEq, Debug, Default)]
pub struct MockAttestor; pub struct MockAttestor;

View file

@ -1,12 +1,15 @@
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; use quartz_tee_ra::{
intel_sgx::dcap::TrustedMrEnclaveIdentity, verify_dcap_attestation, verify_epid_attestation,
Error as RaVerificationError,
};
use crate::{ use crate::{
error::Error, error::Error,
handler::Handler, handler::Handler,
msg::execute::attested::{ msg::execute::attested::{
Attestation, Attested, AttestedMsgSansHandler, EpidAttestation, HasUserData, Attestation, Attested, AttestedMsgSansHandler, DcapAttestation, EpidAttestation,
MockAttestation, HasUserData, MockAttestation,
}, },
state::CONFIG, state::CONFIG,
}; };
@ -29,6 +32,28 @@ impl Handler for EpidAttestation {
} }
} }
impl Handler for DcapAttestation {
fn handle(
self,
_deps: DepsMut<'_>,
_env: &Env,
_info: &MessageInfo,
) -> Result<Response, Error> {
let (quote, collateral) = self.clone().into_tuple();
let mr_enclave = TrustedMrEnclaveIdentity::new(self.mr_enclave().into(), [""; 0], [""; 0]);
// attestation handler MUST verify that the user_data and mr_enclave match the config/msg
let verification_output = verify_dcap_attestation(quote, collateral, &[mr_enclave.into()]);
if verification_output.is_success().into() {
Ok(Response::default())
} else {
Err(Error::RaVerification(RaVerificationError::Dcap(
verification_output,
)))
}
}
}
impl Handler for MockAttestation { impl Handler for MockAttestation {
fn handle( fn handle(
self, self,

View file

@ -5,13 +5,16 @@ use crate::{
error::Error, error::Error,
handler::Handler, handler::Handler,
msg::{ msg::{
execute::attested::{Attestation, EpidAttestation, MockAttestation}, execute::attested::{Attestation, HasUserData},
instantiate::{CoreInstantiate, Instantiate}, instantiate::{CoreInstantiate, Instantiate},
}, },
state::{RawConfig, CONFIG, EPOCH_COUNTER}, state::{RawConfig, CONFIG, EPOCH_COUNTER},
}; };
impl Handler for Instantiate<EpidAttestation> { impl<A> Handler for Instantiate<A>
where
A: Attestation + Handler + HasUserData,
{
fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> {
if self.0.msg().config().mr_enclave() != self.0.attestation().mr_enclave() { if self.0.msg().config().mr_enclave() != self.0.attestation().mr_enclave() {
return Err(RaVerificationError::MrEnclaveMismatch.into()); return Err(RaVerificationError::MrEnclaveMismatch.into());
@ -20,12 +23,6 @@ impl Handler for Instantiate<EpidAttestation> {
} }
} }
impl Handler for Instantiate<MockAttestation> {
fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> {
self.0.handle(deps, env, info)
}
}
impl Handler for CoreInstantiate { impl Handler for CoreInstantiate {
fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> { fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> {
CONFIG CONFIG

View file

@ -2,9 +2,16 @@ use std::{convert::Into, default::Default};
use cosmwasm_schema::cw_serde; use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, StdError}; use cosmwasm_std::{HexBinary, StdError};
use quartz_tee_ra::IASReport; use quartz_tee_ra::{
intel_sgx::dcap::{Collateral, Quote3, Quote3Error},
IASReport,
};
use serde::Serialize; use serde::Serialize;
/// Alias for an owned DCAP quote. This is the main part of a DCAP attestation generated by an
/// enclave that we want to verify on-chain.
pub type Quote = Quote3<Vec<u8>>;
#[cfg(not(feature = "mock-sgx"))] #[cfg(not(feature = "mock-sgx"))]
pub type DefaultAttestation = EpidAttestation; pub type DefaultAttestation = EpidAttestation;
#[cfg(not(feature = "mock-sgx"))] #[cfg(not(feature = "mock-sgx"))]
@ -20,6 +27,7 @@ use crate::{
state::{MrEnclave, UserData}, state::{MrEnclave, UserData},
}; };
/// A wrapper struct for holding a message and it's attestation.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Attested<M, A> { pub struct Attested<M, A> {
pub msg: M, pub msg: M,
@ -87,10 +95,12 @@ where
type DomainType = Attested<RM::DomainType, RA::DomainType>; type DomainType = Attested<RM::DomainType, RA::DomainType>;
} }
/// A trait that defines how to extract user data from a given type.
pub trait HasUserData { pub trait HasUserData {
fn user_data(&self) -> UserData; fn user_data(&self) -> UserData;
} }
/// A verifiable EPID attestation report generated by an enclave.
#[derive(Clone, Debug, PartialEq, Serialize)] #[derive(Clone, Debug, PartialEq, Serialize)]
pub struct EpidAttestation { pub struct EpidAttestation {
report: IASReport, report: IASReport,
@ -149,6 +159,81 @@ impl Attestation for EpidAttestation {
} }
} }
/// A verifiable DCAP attestation generated by an enclave.
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct DcapAttestation {
quote: Quote,
collateral: Collateral,
}
impl DcapAttestation {
pub fn new(quote: Quote, collateral: Collateral) -> Self {
Self { quote, collateral }
}
pub fn into_tuple(self) -> (Quote, Collateral) {
(self.quote, self.collateral)
}
}
#[cw_serde]
pub struct RawDcapAttestation {
quote: HexBinary,
collateral: serde_json::Value,
}
impl TryFrom<RawDcapAttestation> for DcapAttestation {
type Error = StdError;
fn try_from(value: RawDcapAttestation) -> Result<Self, Self::Error> {
let quote_bytes: Vec<u8> = value.quote.into();
let quote = quote_bytes
.try_into()
.map_err(|e: Quote3Error| StdError::parse_err("Quote", e.to_string()))?;
let collateral = serde_json::from_value(value.collateral)
.map_err(|e| StdError::parse_err("Collateral", e.to_string()))?;
Ok(Self { quote, collateral })
}
}
impl From<DcapAttestation> for RawDcapAttestation {
fn from(value: DcapAttestation) -> Self {
Self {
quote: value.quote.as_ref().to_vec().into(),
collateral: serde_json::to_vec(&value.collateral)
.expect("infallible serializer")
.into(),
}
}
}
impl HasDomainType for RawDcapAttestation {
type DomainType = DcapAttestation;
}
impl HasUserData for DcapAttestation {
fn user_data(&self) -> UserData {
let report_data = self.quote.app_report_body().report_data();
let report_data_slice: &[u8] = report_data.as_ref();
report_data_slice
.to_owned()
.try_into()
.expect("fixed size array")
}
}
impl Attestation for DcapAttestation {
fn mr_enclave(&self) -> MrEnclave {
let mr_enclave = self.quote.app_report_body().mr_enclave();
let mr_enclave_slice: &[u8] = mr_enclave.as_ref();
mr_enclave_slice
.to_owned()
.try_into()
.expect("fixed size array")
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct MockAttestation(pub UserData); pub struct MockAttestation(pub UserData);

View file

@ -11,4 +11,6 @@ pub enum Error {
MrEnclaveMismatch, MrEnclaveMismatch,
#[error("EPID specific error: {0}")] #[error("EPID specific error: {0}")]
Epid(#[from] epid::Error), Epid(#[from] epid::Error),
#[error("DCAP specific error: {0:?}")]
Dcap(dcap::VerificationOutput<dcap::DcapVerifierOutput>),
} }

View file

@ -1,26 +1,25 @@
pub mod certificate_chain; pub mod certificate_chain;
pub mod mc_attest_verifier; pub mod mc_attest_verifier;
pub mod mc_attest_verifier_types;
/// Root anchor PEM file for use with DCAP /// Root anchor PEM file for use with DCAP
pub const DCAP_ROOT_ANCHOR: &str = include_str!("../../data/DcapRootCACert.pem"); pub const DCAP_ROOT_ANCHOR: &str = include_str!("../../data/DcapRootCACert.pem");
use mc_attestation_verifier::*; use mc_attestation_verifier::Evidence;
use mc_sgx_dcap_types::{Collateral, Quote3}; pub use mc_attestation_verifier::{
TrustedIdentity, TrustedMrEnclaveIdentity, TrustedMrSignerIdentity, VerificationOutput,
use self::{
mc_attest_verifier::dcap::{DcapVerifier, DcapVerifierOutput},
mc_attest_verifier_types::verification::EnclaveReportDataContents,
}; };
pub use mc_sgx_dcap_types::{Collateral, Quote3, Quote3Error};
use self::mc_attest_verifier::dcap::DcapVerifier;
pub use self::mc_attest_verifier::dcap::DcapVerifierOutput;
pub fn verify( pub fn verify(
quote: Quote3<Vec<u8>>, quote: Quote3<Vec<u8>>,
collateral: Collateral, collateral: Collateral,
identities: &[TrustedIdentity], identities: &[TrustedIdentity],
) -> VerificationOutput<DcapVerifierOutput> { ) -> VerificationOutput<DcapVerifierOutput> {
let report_data_contents = EnclaveReportDataContents::new([0x42u8; 16].into(), [0xAAu8; 32]);
let evidence = Evidence::new(quote, collateral).expect("Failed to get evidence"); let evidence = Evidence::new(quote, collateral).expect("Failed to get evidence");
let verifier = DcapVerifier::new(identities, None, report_data_contents); let verifier = DcapVerifier::new(identities, None);
verifier.verify(&evidence) verifier.verify(&evidence)
} }

View file

@ -4,21 +4,17 @@
use der::DateTime; use der::DateTime;
use mc_attestation_verifier::{ use mc_attestation_verifier::{
Accessor, And, AndOutput, Evidence, EvidenceValue, EvidenceVerifier, ReportDataVerifier, Evidence, EvidenceValue, EvidenceVerifier, TrustedIdentity, VerificationOutput, Verifier,
TrustedIdentity, VerificationOutput, Verifier,
}; };
use mc_sgx_core_types::ReportData;
// use super::DCAP_ROOT_ANCHOR;
use super::super::certificate_chain::TlsCertificateChainVerifier; use super::super::certificate_chain::TlsCertificateChainVerifier;
use super::super::mc_attest_verifier_types::verification::EnclaveReportDataContents;
#[derive(Debug)] #[derive(Debug)]
pub struct DcapVerifier { pub struct DcapVerifier {
verifier: And<EvidenceVerifier<TlsCertificateChainVerifier>, ReportDataHashVerifier>, verifier: EvidenceVerifier<TlsCertificateChainVerifier>,
} }
pub type DcapVerifierOutput = AndOutput<EvidenceValue, ReportData>; pub type DcapVerifierOutput = EvidenceValue;
impl DcapVerifier { impl DcapVerifier {
/// Create a new instance of the DcapVerifier. /// Create a new instance of the DcapVerifier.
@ -30,20 +26,13 @@ impl DcapVerifier {
/// and collateral. If time is provided, verification will fail if this /// and collateral. If time is provided, verification will fail if this
/// time is before or after any of the validity periods. Otherwise, time /// time is before or after any of the validity periods. Otherwise, time
/// validation of certificates will be skipped. /// validation of certificates will be skipped.
pub fn new<I, ID>( pub fn new<I, ID>(trusted_identities: I, time: impl Into<Option<DateTime>>) -> Self
trusted_identities: I,
time: impl Into<Option<DateTime>>,
report_data: EnclaveReportDataContents,
) -> Self
where where
I: IntoIterator<Item = ID>, I: IntoIterator<Item = ID>,
ID: Into<TrustedIdentity>, ID: Into<TrustedIdentity>,
{ {
let certificate_verifier = TlsCertificateChainVerifier; let certificate_verifier = TlsCertificateChainVerifier;
let verifier = And::new( let verifier = EvidenceVerifier::new(certificate_verifier, trusted_identities, time);
EvidenceVerifier::new(certificate_verifier, trusted_identities, time),
ReportDataHashVerifier::new(report_data),
);
Self { verifier } Self { verifier }
} }
@ -52,31 +41,3 @@ impl DcapVerifier {
self.verifier.verify(evidence) self.verifier.verify(evidence)
} }
} }
#[derive(Debug, Clone)]
pub struct ReportDataHashVerifier {
report_data_verifier: ReportDataVerifier,
}
impl ReportDataHashVerifier {
pub fn new(report_data: EnclaveReportDataContents) -> Self {
let mut expected_report_data_bytes = [0u8; 64];
expected_report_data_bytes[..32].copy_from_slice(report_data.sha256().as_ref());
let mut mask = [0u8; 64];
mask[..32].copy_from_slice([0xffu8; 32].as_ref());
let report_data_verifier =
ReportDataVerifier::new(expected_report_data_bytes.into(), mask.into());
Self {
report_data_verifier,
}
}
}
impl<E: Accessor<ReportData>> Verifier<E> for ReportDataHashVerifier {
type Value = ReportData;
fn verify(&self, evidence: &E) -> VerificationOutput<Self::Value> {
self.report_data_verifier.verify(evidence)
}
}

View file

@ -1,4 +0,0 @@
// Heavily trimmed down version of the `mc-attest-verifier-types` crate
// https://github.com/hu55a1n1/mobilecoin/blob/89d90c427bf9cf637a124c0afad266d52b573dc8/attest/verifier/types/src/verification.rs
pub mod verification;

View file

@ -1,60 +0,0 @@
// Copyright (c) 2018-2022 The MobileCoin Foundation
//! Attestation Verification Report type.
use core::fmt::Debug;
use mc_sgx_core_types::QuoteNonce;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
/// Structure for holding the contents of the Enclave's Report Data.
/// The Enclave Quote's ReportData member contains a SHA256 hash of this
/// structure's contents.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct EnclaveReportDataContents {
nonce: QuoteNonce,
custom_identity: Option<[u8; 32]>,
}
impl EnclaveReportDataContents {
/// Create a new EnclaveReportDataContents.
///
/// # Arguments
/// * `nonce` - The nonce provided from the enclave when generating the
/// Report.
/// * `key` - The public key of the enclave. Previously this was bytes 0..32
/// of the enclave's [`ReportData`](mc-sgx-core-types::ReportData).
/// * `custom_identity` - The custom identity of the enclave. Previously
/// this was bytes 32..64 of the enclave's
/// [`ReportData`](mc-sgx-core-types::ReportData).
pub fn new(nonce: QuoteNonce, custom_identity: impl Into<Option<[u8; 32]>>) -> Self {
Self {
nonce,
custom_identity: custom_identity.into(),
}
}
/// Get the nonce
pub fn nonce(&self) -> &QuoteNonce {
&self.nonce
}
/// Get the custom identity
pub fn custom_identity(&self) -> Option<&[u8; 32]> {
self.custom_identity.as_ref()
}
/// Returns a SHA256 hash of the contents of this structure.
///
/// This is the value that is stored in bytes 0..32 of the enclave's
/// [`ReportData`](mc-sgx-core-types::ReportData).
pub fn sha256(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(&self.nonce);
if let Some(custom_identity) = &self.custom_identity {
hasher.update(custom_identity);
}
hasher.finalize().into()
}
}

View file

@ -19,6 +19,7 @@
pub mod intel_sgx; pub mod intel_sgx;
pub use intel_sgx::{ pub use intel_sgx::{
dcap::verify as verify_dcap_attestation,
epid::{types::IASReport, verifier::verify as verify_epid_attestation}, epid::{types::IASReport, verifier::verify as verify_epid_attestation},
Error, Error,
}; };