feat(dcap): Impl DCAP RA types/handlers (#139)
This commit is contained in:
parent
2ac5726463
commit
eb90043172
10 changed files with 162 additions and 129 deletions
|
@ -14,6 +14,7 @@ pub type DefaultAttestor = EpidAttestor;
|
|||
#[cfg(feature = "mock-sgx")]
|
||||
pub type DefaultAttestor = MockAttestor;
|
||||
|
||||
/// The trait defines the interface for generating attestations from within an enclave.
|
||||
pub trait Attestor {
|
||||
type Error: ToString;
|
||||
|
||||
|
@ -22,6 +23,7 @@ pub trait Attestor {
|
|||
fn mr_enclave(&self) -> Result<MrEnclave, Self::Error>;
|
||||
}
|
||||
|
||||
/// An `Attestor` for generating EPID attestations for Gramine based enclaves.
|
||||
#[derive(Clone, PartialEq, Debug, Default)]
|
||||
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)]
|
||||
pub struct MockAttestor;
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
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::{
|
||||
error::Error,
|
||||
handler::Handler,
|
||||
msg::execute::attested::{
|
||||
Attestation, Attested, AttestedMsgSansHandler, EpidAttestation, HasUserData,
|
||||
MockAttestation,
|
||||
Attestation, Attested, AttestedMsgSansHandler, DcapAttestation, EpidAttestation,
|
||||
HasUserData, MockAttestation,
|
||||
},
|
||||
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 {
|
||||
fn handle(
|
||||
self,
|
||||
|
|
|
@ -5,13 +5,16 @@ use crate::{
|
|||
error::Error,
|
||||
handler::Handler,
|
||||
msg::{
|
||||
execute::attested::{Attestation, EpidAttestation, MockAttestation},
|
||||
execute::attested::{Attestation, HasUserData},
|
||||
instantiate::{CoreInstantiate, Instantiate},
|
||||
},
|
||||
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> {
|
||||
if self.0.msg().config().mr_enclave() != self.0.attestation().mr_enclave() {
|
||||
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 {
|
||||
fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> {
|
||||
CONFIG
|
||||
|
|
|
@ -2,9 +2,16 @@ use std::{convert::Into, default::Default};
|
|||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{HexBinary, StdError};
|
||||
use quartz_tee_ra::IASReport;
|
||||
use quartz_tee_ra::{
|
||||
intel_sgx::dcap::{Collateral, Quote3, Quote3Error},
|
||||
IASReport,
|
||||
};
|
||||
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"))]
|
||||
pub type DefaultAttestation = EpidAttestation;
|
||||
#[cfg(not(feature = "mock-sgx"))]
|
||||
|
@ -20,6 +27,7 @@ use crate::{
|
|||
state::{MrEnclave, UserData},
|
||||
};
|
||||
|
||||
/// A wrapper struct for holding a message and it's attestation.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Attested<M, A> {
|
||||
pub msg: M,
|
||||
|
@ -87,10 +95,12 @@ where
|
|||
type DomainType = Attested<RM::DomainType, RA::DomainType>;
|
||||
}
|
||||
|
||||
/// A trait that defines how to extract user data from a given type.
|
||||
pub trait HasUserData {
|
||||
fn user_data(&self) -> UserData;
|
||||
}
|
||||
|
||||
/// A verifiable EPID attestation report generated by an enclave.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct EpidAttestation {
|
||||
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)]
|
||||
pub struct MockAttestation(pub UserData);
|
||||
|
||||
|
|
|
@ -11,4 +11,6 @@ pub enum Error {
|
|||
MrEnclaveMismatch,
|
||||
#[error("EPID specific error: {0}")]
|
||||
Epid(#[from] epid::Error),
|
||||
#[error("DCAP specific error: {0:?}")]
|
||||
Dcap(dcap::VerificationOutput<dcap::DcapVerifierOutput>),
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
pub mod certificate_chain;
|
||||
pub mod mc_attest_verifier;
|
||||
pub mod mc_attest_verifier_types;
|
||||
|
||||
/// Root anchor PEM file for use with DCAP
|
||||
pub const DCAP_ROOT_ANCHOR: &str = include_str!("../../data/DcapRootCACert.pem");
|
||||
|
||||
use mc_attestation_verifier::*;
|
||||
use mc_sgx_dcap_types::{Collateral, Quote3};
|
||||
|
||||
use self::{
|
||||
mc_attest_verifier::dcap::{DcapVerifier, DcapVerifierOutput},
|
||||
mc_attest_verifier_types::verification::EnclaveReportDataContents,
|
||||
use mc_attestation_verifier::Evidence;
|
||||
pub use mc_attestation_verifier::{
|
||||
TrustedIdentity, TrustedMrEnclaveIdentity, TrustedMrSignerIdentity, VerificationOutput,
|
||||
};
|
||||
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(
|
||||
quote: Quote3<Vec<u8>>,
|
||||
collateral: Collateral,
|
||||
identities: &[TrustedIdentity],
|
||||
) -> 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 verifier = DcapVerifier::new(identities, None, report_data_contents);
|
||||
let verifier = DcapVerifier::new(identities, None);
|
||||
verifier.verify(&evidence)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,21 +4,17 @@
|
|||
|
||||
use der::DateTime;
|
||||
use mc_attestation_verifier::{
|
||||
Accessor, And, AndOutput, Evidence, EvidenceValue, EvidenceVerifier, ReportDataVerifier,
|
||||
TrustedIdentity, VerificationOutput, Verifier,
|
||||
Evidence, EvidenceValue, EvidenceVerifier, TrustedIdentity, VerificationOutput, Verifier,
|
||||
};
|
||||
use mc_sgx_core_types::ReportData;
|
||||
|
||||
// use super::DCAP_ROOT_ANCHOR;
|
||||
use super::super::certificate_chain::TlsCertificateChainVerifier;
|
||||
use super::super::mc_attest_verifier_types::verification::EnclaveReportDataContents;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DcapVerifier {
|
||||
verifier: And<EvidenceVerifier<TlsCertificateChainVerifier>, ReportDataHashVerifier>,
|
||||
verifier: EvidenceVerifier<TlsCertificateChainVerifier>,
|
||||
}
|
||||
|
||||
pub type DcapVerifierOutput = AndOutput<EvidenceValue, ReportData>;
|
||||
pub type DcapVerifierOutput = EvidenceValue;
|
||||
|
||||
impl 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
|
||||
/// time is before or after any of the validity periods. Otherwise, time
|
||||
/// validation of certificates will be skipped.
|
||||
pub fn new<I, ID>(
|
||||
trusted_identities: I,
|
||||
time: impl Into<Option<DateTime>>,
|
||||
report_data: EnclaveReportDataContents,
|
||||
) -> Self
|
||||
pub fn new<I, ID>(trusted_identities: I, time: impl Into<Option<DateTime>>) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = ID>,
|
||||
ID: Into<TrustedIdentity>,
|
||||
{
|
||||
let certificate_verifier = TlsCertificateChainVerifier;
|
||||
let verifier = And::new(
|
||||
EvidenceVerifier::new(certificate_verifier, trusted_identities, time),
|
||||
ReportDataHashVerifier::new(report_data),
|
||||
);
|
||||
let verifier = EvidenceVerifier::new(certificate_verifier, trusted_identities, time);
|
||||
Self { verifier }
|
||||
}
|
||||
|
||||
|
@ -52,31 +41,3 @@ impl DcapVerifier {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
pub mod intel_sgx;
|
||||
|
||||
pub use intel_sgx::{
|
||||
dcap::verify as verify_dcap_attestation,
|
||||
epid::{types::IASReport, verifier::verify as verify_epid_attestation},
|
||||
Error,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue