feat(dcap): use tcbinfo from contract for RA verification (#179)

Co-authored-by: hu55a1n1 <sufialhussaini@gmail.com>
This commit is contained in:
dusterbloom 2024-09-05 12:07:35 +02:00 committed by GitHub
parent e7cd6b1151
commit 1a2ab7d008
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 123 additions and 15 deletions

2
Cargo.lock generated
View file

@ -4145,12 +4145,14 @@ dependencies = [
"cosmwasm-schema", "cosmwasm-schema",
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
"hex",
"k256", "k256",
"quartz-tee-ra", "quartz-tee-ra",
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"sha2 0.10.8", "sha2 0.10.8",
"tcbinfo",
"thiserror", "thiserror",
] ]

View file

@ -771,6 +771,7 @@ dependencies = [
"ff", "ff",
"generic-array", "generic-array",
"group", "group",
"pem-rfc7468",
"pkcs8", "pkcs8",
"rand_core", "rand_core",
"sec1", "sec1",
@ -858,6 +859,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [ dependencies = [
"ahash", "ahash",
"allocator-api2", "allocator-api2",
"serde",
] ]
[[package]] [[package]]
@ -1208,12 +1210,14 @@ dependencies = [
"cosmwasm-schema", "cosmwasm-schema",
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
"hex",
"k256", "k256",
"quartz-tee-ra", "quartz-tee-ra",
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"sha2", "sha2",
"tcbinfo",
"thiserror", "thiserror",
] ]
@ -1565,6 +1569,29 @@ dependencies = [
"syn 2.0.76", "syn 2.0.76",
] ]
[[package]]
name = "tcbinfo"
version = "0.1.0"
dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-storage-plus",
"cw2",
"der",
"getrandom",
"hashbrown 0.14.5",
"hex",
"mc-attestation-verifier",
"p256",
"quartz-tee-ra",
"schemars",
"serde",
"serde_json",
"thiserror",
"x509-cert",
"x509-parser",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.63" version = "1.0.63"

View file

@ -183,7 +183,8 @@ mod tests {
"trusting_period": 1209600, "trusting_period": 1209600,
"max_clock_drift": 5, "max_clock_drift": 5,
"max_block_lag": 5 "max_block_lag": 5
} },
"tcbinfo_contract": "wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d"
} }
}, },
"attestation": { "attestation": {

View file

@ -2,7 +2,7 @@ use std::net::SocketAddr;
use clap::Parser; use clap::Parser;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use cosmrs::tendermint::Hash; use cosmrs::{tendermint::Hash, AccountId};
use tendermint_light_client::types::{Height, TrustThreshold}; use tendermint_light_client::types::{Height, TrustThreshold};
fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> { fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> {
@ -26,6 +26,10 @@ pub struct Cli {
#[clap(long)] #[clap(long)]
pub chain_id: String, pub chain_id: String,
/// TcbInfo contract address
#[clap(long)]
pub tcbinfo_contract: AccountId,
/// Height of the trusted header (AKA root-of-trust) /// Height of the trusted header (AKA root-of-trust)
#[clap(long)] #[clap(long)]
pub trusted_height: Height, pub trusted_height: Height,

View file

@ -61,6 +61,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
attestor.mr_enclave()?, attestor.mr_enclave()?,
Duration::from_secs(30 * 24 * 60), Duration::from_secs(30 * 24 * 60),
light_client_opts, light_client_opts,
args.tcbinfo_contract.to_string(),
); );
let sk = Arc::new(Mutex::new(None)); let sk = Arc::new(Mutex::new(None));

View file

@ -1189,6 +1189,7 @@ dependencies = [
"cosmwasm-schema", "cosmwasm-schema",
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
"hex",
"k256", "k256",
"quartz-tee-ra", "quartz-tee-ra",
"serde", "serde",

View file

@ -2,7 +2,7 @@ use std::{env, net::SocketAddr};
use clap::Parser; use clap::Parser;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use cosmrs::tendermint::Hash; use cosmrs::{tendermint::Hash, AccountId};
use tendermint_light_client::types::{Height, TrustThreshold}; use tendermint_light_client::types::{Height, TrustThreshold};
fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> { fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> {
@ -26,6 +26,10 @@ pub struct Cli {
#[clap(long)] #[clap(long)]
pub chain_id: String, pub chain_id: String,
/// TcbInfo contract address
#[clap(long)]
pub tcbinfo_contract: AccountId,
/// Height of the trusted header (AKA root-of-trust) /// Height of the trusted header (AKA root-of-trust)
#[clap(long)] #[clap(long)]
pub trusted_height: Height, pub trusted_height: Height,

View file

@ -61,6 +61,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
attestor.mr_enclave()?, attestor.mr_enclave()?,
Duration::from_secs(30 * 24 * 60), Duration::from_secs(30 * 24 * 60),
light_client_opts, light_client_opts,
args.tcbinfo_contract.to_string(),
); );
let sk = Arc::new(Mutex::new(None)); let sk = Arc::new(Mutex::new(None));

View file

@ -22,7 +22,7 @@ serde_json.workspace = true
serde_with.workspace = true serde_with.workspace = true
sha2.workspace = true sha2.workspace = true
thiserror.workspace = true thiserror.workspace = true
hex.workspace = true
# cosmos # cosmos
cw-storage-plus.workspace = true cw-storage-plus.workspace = true
cosmwasm-schema.workspace = true cosmwasm-schema.workspace = true
@ -31,5 +31,8 @@ cosmwasm-std.workspace = true
# quartz # quartz
quartz-tee-ra.workspace = true quartz-tee-ra.workspace = true
# tcbinfo
tcbinfo = { path = "../tcbinfo", features = ["library"] }
[dev-dependencies] [dev-dependencies]
serde_json.workspace = true serde_json.workspace = true

View file

@ -13,6 +13,10 @@ pub enum Error {
K256(K256Error), K256(K256Error),
#[error("invalid session nonce or attempt to reset pub_key")] #[error("invalid session nonce or attempt to reset pub_key")]
BadSessionTransition, BadSessionTransition,
#[error("Invalid FMSPC: {0}")]
InvalidFmspc(String),
#[error("TCB Info query error: {0}")]
TcbInfoQueryError(String),
} }
impl From<K256Error> for Error { impl From<K256Error> for Error {

View file

@ -1,8 +1,12 @@
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use cosmwasm_std::{
use quartz_tee_ra::{ from_json, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, QueryRequest, Response,
intel_sgx::dcap::TrustedMrEnclaveIdentity, verify_dcap_attestation, verify_epid_attestation, WasmQuery,
Error as RaVerificationError,
}; };
use quartz_tee_ra::{
intel_sgx::dcap::{Collateral, TrustedMrEnclaveIdentity},
verify_dcap_attestation, verify_epid_attestation, Error as RaVerificationError,
};
use tcbinfo::msg::{GetTcbInfoResponse, QueryMsg as TcbInfoQueryMsg};
use crate::{ use crate::{
error::Error, error::Error,
@ -14,6 +18,28 @@ use crate::{
state::CONFIG, state::CONFIG,
}; };
pub fn query_tcbinfo(deps: Deps<'_>, fmspc: String) -> Result<Binary, Error> {
let config = CONFIG.load(deps.storage).map_err(Error::Std)?;
let tcbinfo_addr = config.tcb_info();
let fmspc_bytes =
hex::decode(&fmspc).map_err(|_| Error::InvalidFmspc("Invalid FMSPC format".to_string()))?;
if fmspc_bytes.len() != 6 {
return Err(Error::InvalidFmspc("FMSPC must be 6 bytes".to_string()));
}
let query_msg = TcbInfoQueryMsg::GetTcbInfo { fmspc };
let request = QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: tcbinfo_addr,
msg: to_json_binary(&query_msg).map_err(Error::Std)?,
});
deps.querier
.query(&request)
.map_err(|err| Error::TcbInfoQueryError(err.to_string()))
}
impl Handler for EpidAttestation { impl Handler for EpidAttestation {
fn handle( fn handle(
self, self,
@ -33,17 +59,37 @@ impl Handler for EpidAttestation {
} }
impl Handler for DcapAttestation { impl Handler for DcapAttestation {
fn handle( fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> {
self,
_deps: DepsMut<'_>,
_env: &Env,
_info: &MessageInfo,
) -> Result<Response, Error> {
let (quote, collateral) = self.clone().into_tuple(); let (quote, collateral) = self.clone().into_tuple();
let mr_enclave = TrustedMrEnclaveIdentity::new(self.mr_enclave().into(), [""; 0], [""; 0]); let mr_enclave = TrustedMrEnclaveIdentity::new(self.mr_enclave().into(), [""; 0], [""; 0]);
// Retrieve the FMSPC from the collateral
let fmspc_hex = collateral.tcb_info().to_string();
// Query the tcbinfo contract with the FMSPC retrieved and validated
let tcb_info_query = query_tcbinfo(deps.as_ref(), fmspc_hex)?;
let tcb_info_response: GetTcbInfoResponse = from_json(tcb_info_query)?;
// Serialize the existing collateral
let mut collateral_json: serde_json::Value =
serde_json::to_value(&collateral).map_err(|e| {
Error::TcbInfoQueryError(format!("Failed to serialize collateral: {}", e))
})?;
// Update the tcb_info in the serialized data
collateral_json["tcb_info"] = tcb_info_response.tcb_info;
// Deserialize back into a Collateral
let updated_collateral: Collateral =
serde_json::from_value(collateral_json).map_err(|e| {
Error::TcbInfoQueryError(format!("Failed to deserialize updated collateral: {}", e))
})?;
// attestation handler MUST verify that the user_data and mr_enclave match the config/msg
let verification_output =
verify_dcap_attestation(quote, updated_collateral, &[mr_enclave.into()]);
// attestation handler MUST verify that the user_data and mr_enclave match the config/msg // 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() { if verification_output.is_success().into() {
Ok(Response::default()) Ok(Response::default())
} else { } else {

View file

@ -17,6 +17,7 @@ pub struct Config {
mr_enclave: MrEnclave, mr_enclave: MrEnclave,
epoch_duration: Duration, epoch_duration: Duration,
light_client_opts: LightClientOpts, light_client_opts: LightClientOpts,
tcbinfo_contract: String,
} }
impl Config { impl Config {
@ -24,11 +25,13 @@ impl Config {
mr_enclave: MrEnclave, mr_enclave: MrEnclave,
epoch_duration: Duration, epoch_duration: Duration,
light_client_opts: LightClientOpts, light_client_opts: LightClientOpts,
tcbinfo_contract: String,
) -> Self { ) -> Self {
Self { Self {
mr_enclave, mr_enclave,
epoch_duration, epoch_duration,
light_client_opts, light_client_opts,
tcbinfo_contract,
} }
} }
@ -39,6 +42,10 @@ impl Config {
pub fn mr_enclave(&self) -> MrEnclave { pub fn mr_enclave(&self) -> MrEnclave {
self.mr_enclave self.mr_enclave
} }
pub fn tcbinfo_contract(&self) -> &str {
&self.tcbinfo_contract
}
} }
#[cw_serde] #[cw_serde]
@ -46,12 +53,17 @@ pub struct RawConfig {
mr_enclave: HexBinary, mr_enclave: HexBinary,
epoch_duration: Duration, epoch_duration: Duration,
light_client_opts: RawLightClientOpts, light_client_opts: RawLightClientOpts,
tcbinfo_contract: String,
} }
impl RawConfig { impl RawConfig {
pub fn mr_enclave(&self) -> &[u8] { pub fn mr_enclave(&self) -> &[u8] {
self.mr_enclave.as_slice() self.mr_enclave.as_slice()
} }
pub fn tcb_info(&self) -> String {
self.tcbinfo_contract.to_string()
}
} }
impl TryFrom<RawConfig> for Config { impl TryFrom<RawConfig> for Config {
@ -65,6 +77,7 @@ impl TryFrom<RawConfig> for Config {
.light_client_opts .light_client_opts
.try_into() .try_into()
.map_err(|e| StdError::parse_err("light_client_opts", e))?, .map_err(|e| StdError::parse_err("light_client_opts", e))?,
tcbinfo_contract: value.tcbinfo_contract,
}) })
} }
} }
@ -75,6 +88,7 @@ impl From<Config> for RawConfig {
mr_enclave: value.mr_enclave.into(), mr_enclave: value.mr_enclave.into(),
epoch_duration: value.epoch_duration, epoch_duration: value.epoch_duration,
light_client_opts: value.light_client_opts.into(), light_client_opts: value.light_client_opts.into(),
tcbinfo_contract: value.tcbinfo_contract,
} }
} }
} }