From b4e5f01cd158896520f9c5dcca8ebd574f5f2ced Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 19 Feb 2024 21:25:46 +0530 Subject: [PATCH] Quartz base impl (#10) --- .../packages/quartz-cw/.cargo/config | 5 + .../packages/quartz-cw/Cargo.toml | 19 ++ bisenzone-cw-mvp/packages/quartz-cw/README.md | 1 + .../packages/quartz-cw/src/error.rs | 22 ++ .../packages/quartz-cw/src/handler.rs | 208 ++++++++++++++++++ .../packages/quartz-cw/src/handler/execute.rs | 18 ++ .../quartz-cw/src/handler/execute/attested.rs | 49 +++++ .../src/handler/execute/session_create.rs | 17 ++ .../handler/execute/session_set_pub_key.rs | 25 +++ .../quartz-cw/src/handler/instantiate.rs | 27 +++ .../packages/quartz-cw/src/lib.rs | 22 ++ .../packages/quartz-cw/src/msg.rs | 12 + .../packages/quartz-cw/src/msg/execute.rs | 63 ++++++ .../quartz-cw/src/msg/execute/attested.rs | 146 ++++++++++++ .../src/msg/execute/session_create.rs | 51 +++++ .../src/msg/execute/session_set_pub_key.rs | 65 ++++++ .../packages/quartz-cw/src/msg/instantiate.rs | 89 ++++++++ .../packages/quartz-cw/src/msg/query.rs | 1 + .../packages/quartz-cw/src/prelude.rs | 3 + .../packages/quartz-cw/src/state.rs | 52 +++++ .../packages/quartz-tee-ra/Cargo.toml | 15 ++ .../packages/quartz-tee-ra/src/intel_sgx.rs | 13 ++ .../quartz-tee-ra/src/intel_sgx/epid.rs | 17 ++ .../quartz-tee-ra/src/intel_sgx/epid/types.rs | 49 +++++ .../src/intel_sgx/epid/verifier.rs | 172 +++++++++++++++ .../packages/quartz-tee-ra/src/lib.rs | 22 ++ 26 files changed, 1183 insertions(+) create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/.cargo/config create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/README.md create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/error.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/state.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs diff --git a/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config b/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config new file mode 100644 index 0000000..76fb435 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" + diff --git a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml new file mode 100644 index 0000000..f09e59a --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "quartz-cw" +version = "0.1.0" +authors = ["hu55a1n1 "] +edition = "2021" + +[dependencies] +cw-storage-plus = "1.1.0" +cosmwasm-schema = "1.4.0" +cosmwasm-std = "1.4.0" +k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } +serde = { version = "1.0.188", default-features = false, features = ["derive"] } +sha2 = "0.10.8" +thiserror = "1.0.57" + +quartz-tee-ra = { path = "../../packages/quartz-tee-ra" } + +[dev-dependencies] +serde_json = "1.0.113" diff --git a/bisenzone-cw-mvp/packages/quartz-cw/README.md b/bisenzone-cw-mvp/packages/quartz-cw/README.md new file mode 100644 index 0000000..f4a6bbc --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/README.md @@ -0,0 +1 @@ +# Quartz CosmWasm spec diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs new file mode 100644 index 0000000..228d20d --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::StdError; +use k256::ecdsa::Error as K256Error; +use quartz_tee_ra::Error as RaVerificationError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Std(#[from] StdError), + #[error("{0}")] + RaVerification(#[from] RaVerificationError), + #[error("Not Secp256K1")] + K256(K256Error), + #[error("invalid session nonce or attempt to reset pub_key")] + BadSessionTransition, +} + +impl From for Error { + fn from(e: K256Error) -> Self { + Self::K256(e) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs new file mode 100644 index 0000000..c7acbf8 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs @@ -0,0 +1,208 @@ +pub mod execute; +pub mod instantiate; + +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::error::Error; +use crate::msg::HasDomainType; + +pub trait Handler { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result; +} + +pub trait RawHandler: HasDomainType { + fn handle_raw( + self, + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result; +} + +impl RawHandler for RM +where + RM: HasDomainType, + RM::DomainType: Handler, +{ + fn handle_raw( + self, + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result { + let execute: RM::DomainType = self.try_into()?; + execute.handle(deps, env, info) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::DepsMut; + use serde::Deserialize; + + use crate::handler::Handler; + use crate::msg::{HasDomainType, RawExecuteMsg, RawInstantiateMsg}; + use crate::state::SESSION; + + fn parse_msg<'a, R>(msg_str: &'a str) -> R::DomainType + where + R: HasDomainType + Deserialize<'a>, + { + let raw_msg: R = + serde_json::from_str(msg_str).expect("deserialization failure for hard-coded RawMsg"); + raw_msg.try_into().expect("invalid hard-coded RawMsg") + } + + fn handle_msg<'a, R>(mut deps: DepsMut<'_>, msg_str: &'a str) + where + R: HasDomainType + Deserialize<'a>, + R::DomainType: Handler, + { + let msg = parse_msg::(msg_str); + let info = mock_info("creator", &[]); + let env = mock_env(); + let res = msg + .handle(deps.branch(), &env, &info) + .expect("msg handler failure"); + + assert_eq!(0, res.messages.len()); + } + + fn instantiate(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "msg": { + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ); + } + + fn session_create(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "session_create": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c27770000000000000000000000000000000000000000000000000000000000000000" + } + } + }"#, + ); + } + + fn session_set_pub_key(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "session_set_pub_key": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + "pub_key": "03E67EF09213633074FB4FBF338643F4F0C574ED60EF11D03422EEB06FA38C8F3F" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c27770000000000000000000000000000000000000000000000000000000000000000" + } + } + }"#, + ); + } + + #[test] + #[ignore] + fn test_instantiate_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + } + + #[test] + #[ignore] + fn test_session_create_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + session_create(deps.as_mut()); + SESSION.load(&deps.storage).expect("Session not created"); + } + + #[test] + #[ignore] + fn test_session_set_pub_key_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + session_create(deps.as_mut()); + session_set_pub_key(deps.as_mut()); + SESSION.load(&deps.storage).expect("Session not created"); + // TODO(hu55a1n1): check that nonce & pub_key match, etc. + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs new file mode 100644 index 0000000..7d6ad47 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs @@ -0,0 +1,18 @@ +pub mod attested; +pub mod session_create; +pub mod session_set_pub_key; + +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::Execute; + +impl Handler for Execute { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + match self { + Execute::SessionCreate(msg) => msg.handle(deps, env, info), + Execute::SessionSetPubKey(msg) => msg.handle(deps, env, info), + } + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs new file mode 100644 index 0000000..f2dc5e0 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs @@ -0,0 +1,49 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::attested::{Attestation, Attested, EpidAttestation, HasUserData}; +use crate::state::CONFIG; + +impl Handler for EpidAttestation { + fn handle( + self, + _deps: DepsMut<'_>, + _env: &Env, + _info: &MessageInfo, + ) -> Result { + let (report, mr_enclave, user_data) = self.into_tuple(); + verify_epid_attestation(report, mr_enclave, user_data) + .map(|_| Response::default()) + .map_err(Error::RaVerification) + } +} + +impl Handler for Attested +where + M: Handler + HasUserData, + A: Handler + HasUserData + Attestation, +{ + fn handle( + self, + mut deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result { + let (msg, attestation) = self.into_tuple(); + if msg.user_data() != attestation.user_data() { + return Err(RaVerificationError::UserDataMismatch.into()); + } + + if let Some(config) = CONFIG.may_load(deps.storage)? { + // if we weren't able to load then the context was from InstantiateMsg so we don't fail + if *config.mr_enclave() != attestation.mr_enclave() { + return Err(RaVerificationError::MrEnclaveMismatch.into()); + } + } + + Handler::handle(attestation, deps.branch(), env, info)?; + Handler::handle(msg, deps, env, info) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs new file mode 100644 index 0000000..5144734 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::session_create::SessionCreate; +use crate::state::{Session, SESSION}; + +impl Handler for SessionCreate { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + // TODO(hu55a1n1): overwrite previous session? + SESSION + .save(deps.storage, &Session::create(self.into_nonce())) + .map_err(Error::Std)?; + + Ok(Response::new().add_attribute("action", "session_create")) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs new file mode 100644 index 0000000..865dff0 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::session_set_pub_key::SessionSetPubKey; +use crate::state::SESSION; + +impl Handler for SessionSetPubKey { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + let session = SESSION.load(deps.storage).map_err(Error::Std)?; + let (nonce, pub_key) = self.into_tuple(); + let session = session + .with_pub_key(nonce, pub_key) + .ok_or(Error::BadSessionTransition)?; + + SESSION.save(deps.storage, &session).map_err(Error::Std)?; + + Ok(Response::new() + .add_attribute("action", "session_set_pub_key") + .add_attribute( + "pub_key", + HexBinary::from(pub_key.to_sec1_bytes().into_vec()).to_hex(), + )) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs new file mode 100644 index 0000000..ab25d04 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use quartz_tee_ra::Error as RaVerificationError; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::attested::{Attestation, EpidAttestation}; +use crate::msg::instantiate::{CoreInstantiate, Instantiate}; +use crate::state::Config; +use crate::state::CONFIG; + +impl Handler for Instantiate { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + if self.0.msg().mr_enclave() != self.0.attestation().mr_enclave() { + return Err(RaVerificationError::MrEnclaveMismatch.into()); + } + self.0.handle(deps, env, info) + } +} + +impl Handler for CoreInstantiate { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + CONFIG + .save(deps.storage, &Config::new(self.mr_enclave())) + .map_err(Error::Std)?; + Ok(Response::new().add_attribute("action", "instantiate")) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs new file mode 100644 index 0000000..4438a11 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs @@ -0,0 +1,22 @@ +#![warn( + clippy::checked_conversions, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + rust_2018_idioms, + unused_lifetimes +)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + warnings +)] +#![forbid(unsafe_code)] + +pub mod error; +pub mod handler; +pub mod msg; +pub mod prelude; +pub mod state; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs new file mode 100644 index 0000000..8b8864b --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs @@ -0,0 +1,12 @@ +pub mod execute; +pub mod instantiate; +pub mod query; + +pub use execute::{Execute as ExecuteMsg, RawExecute as RawExecuteMsg}; +pub use instantiate::{Instantiate as InstantiateMsg, RawInstantiate as RawInstantiateMsg}; + +use cosmwasm_std::StdError; + +pub trait HasDomainType: From { + type DomainType: TryFrom; +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs new file mode 100644 index 0000000..f21dafc --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs @@ -0,0 +1,63 @@ +pub mod attested; +pub mod session_create; +pub mod session_set_pub_key; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; + +use crate::msg::execute::attested::{Attested, EpidAttestation}; +use crate::msg::execute::attested::{RawAttested, RawEpidAttestation}; +use crate::msg::execute::session_create::{RawSessionCreate, SessionCreate}; +use crate::msg::execute::session_set_pub_key::{RawSessionSetPubKey, SessionSetPubKey}; +use crate::msg::HasDomainType; + +#[derive(Clone, Debug, PartialEq)] +pub enum Execute { + SessionCreate(Attested), + SessionSetPubKey(Attested), +} + +#[cw_serde] +pub enum RawExecute { + #[serde(rename = "session_create")] + RawSessionCreate(RawAttested), + #[serde(rename = "session_set_pub_key")] + RawSessionSetPubKey(RawAttested), +} + +impl TryFrom> for Execute +where + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawExecute) -> Result { + match value { + RawExecute::RawSessionCreate(msg) => { + Ok(Execute::SessionCreate(TryFrom::try_from(msg)?)) + } + RawExecute::RawSessionSetPubKey(msg) => { + Ok(Execute::SessionSetPubKey(TryFrom::try_from(msg)?)) + } + } + } +} + +impl From> for RawExecute +where + RA: HasDomainType, +{ + fn from(value: Execute) -> Self { + match value { + Execute::SessionCreate(msg) => RawExecute::RawSessionCreate(From::from(msg)), + Execute::SessionSetPubKey(msg) => RawExecute::RawSessionSetPubKey(From::from(msg)), + } + } +} + +impl HasDomainType for RawExecute +where + A: HasDomainType, +{ + type DomainType = Execute; +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs new file mode 100644 index 0000000..1e252fe --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs @@ -0,0 +1,146 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use quartz_tee_ra::IASReport; + +use crate::msg::HasDomainType; +use crate::state::{MrEnclave, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Attested { + msg: M, + attestation: A, +} + +impl Attested { + pub fn into_tuple(self) -> (M, A) { + let Attested { msg, attestation } = self; + (msg, attestation) + } + + pub fn msg(&self) -> &M { + &self.msg + } + + pub fn attestation(&self) -> &A { + &self.attestation + } +} + +#[cw_serde] +pub struct RawAttested { + pub msg: RM, + pub attestation: RA, +} + +impl TryFrom> for Attested +where + RM: HasDomainType, + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawAttested) -> Result { + Ok(Self { + msg: value.msg.try_into()?, + attestation: value.attestation.try_into()?, + }) + } +} + +impl From> for RawAttested +where + RM: HasDomainType, + RA: HasDomainType, +{ + fn from(value: Attested) -> Self { + Self { + msg: value.msg.into(), + attestation: value.attestation.into(), + } + } +} + +impl HasDomainType for RawAttested +where + RM: HasDomainType, + RA: HasDomainType, +{ + type DomainType = Attested; +} + +pub trait HasUserData { + fn user_data(&self) -> UserData; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EpidAttestation { + report: IASReport, + mr_enclave: MrEnclave, + user_data: UserData, +} + +impl EpidAttestation { + pub fn into_tuple(self) -> (IASReport, MrEnclave, UserData) { + let EpidAttestation { + report, + mr_enclave, + user_data, + } = self; + (report, mr_enclave, user_data) + } + + pub fn report(&self) -> &IASReport { + &self.report + } +} + +#[cw_serde] +pub struct RawEpidAttestation { + report: IASReport, + mr_enclave: HexBinary, + user_data: HexBinary, +} + +impl TryFrom for EpidAttestation { + type Error = StdError; + + fn try_from(value: RawEpidAttestation) -> Result { + let mr_enclave = value.mr_enclave.to_array()?; + let user_data = value.user_data.to_array()?; + Ok(Self { + report: value.report, + mr_enclave, + user_data, + }) + } +} + +impl From for RawEpidAttestation { + fn from(value: EpidAttestation) -> Self { + Self { + report: value.report, + mr_enclave: value.mr_enclave.into(), + user_data: value.user_data.into(), + } + } +} + +impl HasDomainType for RawEpidAttestation { + type DomainType = EpidAttestation; +} + +impl HasUserData for EpidAttestation { + fn user_data(&self) -> UserData { + self.user_data + } +} + +pub trait Attestation { + fn mr_enclave(&self) -> MrEnclave; +} + +impl Attestation for EpidAttestation { + fn mr_enclave(&self) -> MrEnclave { + self.report().report.isv_enclave_quote_body.mrenclave() + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs new file mode 100644 index 0000000..d515421 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs @@ -0,0 +1,51 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; + +use crate::msg::execute::attested::HasUserData; +use crate::msg::HasDomainType; +use crate::state::{Nonce, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SessionCreate { + nonce: Nonce, +} + +impl SessionCreate { + pub fn into_nonce(self) -> Nonce { + self.nonce + } +} + +#[cw_serde] +pub struct RawSessionCreate { + nonce: HexBinary, +} + +impl TryFrom for SessionCreate { + type Error = StdError; + + fn try_from(value: RawSessionCreate) -> Result { + let nonce = value.nonce.to_array()?; + Ok(Self { nonce }) + } +} + +impl From for RawSessionCreate { + fn from(value: SessionCreate) -> Self { + Self { + nonce: value.nonce.into(), + } + } +} + +impl HasDomainType for RawSessionCreate { + type DomainType = SessionCreate; +} + +impl HasUserData for SessionCreate { + fn user_data(&self) -> UserData { + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&self.nonce); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs new file mode 100644 index 0000000..9a0db70 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs @@ -0,0 +1,65 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use k256::ecdsa::VerifyingKey; +use sha2::{Digest, Sha256}; + +use crate::error::Error; +use crate::msg::execute::attested::HasUserData; +use crate::msg::HasDomainType; +use crate::state::{Nonce, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SessionSetPubKey { + nonce: Nonce, + pub_key: VerifyingKey, +} + +impl SessionSetPubKey { + pub fn into_tuple(self) -> (Nonce, VerifyingKey) { + (self.nonce, self.pub_key) + } +} + +#[cw_serde] +pub struct RawSessionSetPubKey { + nonce: HexBinary, + pub_key: HexBinary, +} + +impl TryFrom for SessionSetPubKey { + type Error = StdError; + + fn try_from(value: RawSessionSetPubKey) -> Result { + let nonce = value.nonce.to_array()?; + let pub_key = VerifyingKey::from_sec1_bytes(&value.pub_key) + .map_err(Error::from) + .map_err(|e| StdError::generic_err(e.to_string()))?; + Ok(Self { nonce, pub_key }) + } +} + +impl From for RawSessionSetPubKey { + fn from(value: SessionSetPubKey) -> Self { + Self { + nonce: value.nonce.into(), + pub_key: value.pub_key.to_sec1_bytes().into_vec().into(), + } + } +} + +impl HasDomainType for RawSessionSetPubKey { + type DomainType = SessionSetPubKey; +} + +impl HasUserData for SessionSetPubKey { + fn user_data(&self) -> UserData { + let mut hasher = Sha256::new(); + hasher.update(self.nonce); + hasher.update(self.pub_key.to_sec1_bytes()); + let digest: [u8; 32] = hasher.finalize().into(); + + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&digest); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs new file mode 100644 index 0000000..03659c7 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs @@ -0,0 +1,89 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use sha2::{Digest, Sha256}; + +use crate::msg::execute::attested::{ + Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, +}; +use crate::msg::HasDomainType; +use crate::state::{MrEnclave, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Instantiate(pub(crate) Attested); + +#[cw_serde] +pub struct RawInstantiate(RawAttested); + +impl TryFrom> for Instantiate +where + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawInstantiate) -> Result { + Ok(Self(TryFrom::try_from(value.0)?)) + } +} + +impl From> for RawInstantiate +where + RA: HasDomainType, +{ + fn from(value: Instantiate) -> Self { + Self(From::from(value.0)) + } +} + +impl HasDomainType for RawInstantiate { + type DomainType = Instantiate; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CoreInstantiate { + mr_enclave: MrEnclave, + // TODO(hu55a1n1): config - e.g. Epoch duration, light client opts +} + +impl CoreInstantiate { + pub fn mr_enclave(&self) -> MrEnclave { + self.mr_enclave + } +} + +#[cw_serde] +pub struct RawCoreInstantiate { + mr_enclave: HexBinary, +} + +impl TryFrom for CoreInstantiate { + type Error = StdError; + + fn try_from(value: RawCoreInstantiate) -> Result { + let mr_enclave = value.mr_enclave.to_array()?; + Ok(Self { mr_enclave }) + } +} + +impl From for RawCoreInstantiate { + fn from(value: CoreInstantiate) -> Self { + Self { + mr_enclave: value.mr_enclave.into(), + } + } +} + +impl HasDomainType for RawCoreInstantiate { + type DomainType = CoreInstantiate; +} + +impl HasUserData for CoreInstantiate { + fn user_data(&self) -> UserData { + let mut hasher = Sha256::new(); + hasher.update(self.mr_enclave); + let digest: [u8; 32] = hasher.finalize().into(); + + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&digest); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs @@ -0,0 +1 @@ + diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs new file mode 100644 index 0000000..b998726 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs @@ -0,0 +1,3 @@ +pub use crate::handler::RawHandler; +pub use crate::msg::execute::RawExecute as QuartzExecuteMsg; +pub use crate::msg::instantiate::RawInstantiate as QuartzInstantiateMsg; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs new file mode 100644 index 0000000..5e3cb06 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs @@ -0,0 +1,52 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::HexBinary; +use cw_storage_plus::Item; +use k256::ecdsa::VerifyingKey; + +pub type MrEnclave = [u8; 32]; +pub type Nonce = [u8; 32]; +pub type UserData = [u8; 64]; + +#[cw_serde] +pub struct Config { + mr_enclave: HexBinary, +} + +impl Config { + pub fn new(mr_enclave: MrEnclave) -> Self { + Self { + mr_enclave: mr_enclave.into(), + } + } + + pub fn mr_enclave(&self) -> &HexBinary { + &self.mr_enclave + } +} + +#[cw_serde] +pub struct Session { + nonce: HexBinary, + pub_key: Option, +} + +impl Session { + pub fn create(nonce: Nonce) -> Self { + Self { + nonce: nonce.into(), + pub_key: None, + } + } + + pub fn with_pub_key(mut self, nonce: Nonce, pub_key: VerifyingKey) -> Option { + if self.nonce == nonce && self.pub_key.is_none() { + self.pub_key = Some(pub_key.to_sec1_bytes().into_vec().into()); + Some(self) + } else { + None + } + } +} + +pub const CONFIG: Item<'_, Config> = Item::new("quartz_config"); +pub const SESSION: Item<'_, Session> = Item::new("quartz_session"); diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml new file mode 100644 index 0000000..e0160db --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "quartz-tee-ra" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-schema = "1.4.0" +cosmwasm-std = "1.4.0" +hex-literal = "0.4.1" +num-bigint = "0.4.4" +serde_json = "1.0.108" +sha2 = "0.10.8" +thiserror = { version = "1.0.49" } diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs new file mode 100644 index 0000000..e5cefa4 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs @@ -0,0 +1,13 @@ +use thiserror::Error; + +pub mod epid; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Specified user data does not match the report")] + UserDataMismatch, + #[error("Specified MRENCLAVE does not match the report")] + MrEnclaveMismatch, + #[error("EPID specific error: {0}")] + Epid(#[from] epid::Error), +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs new file mode 100644 index 0000000..f1d11f4 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs @@ -0,0 +1,17 @@ +use hex_literal::hex; +use thiserror::Error; + +pub const INTEL_ROOT_MODULUS: &[u8] = &hex!("a97a2de0e66ea6147c9ee745ac0162686c7192099afc4b3f040fad6de093511d74e802f510d716038157dcaf84f4104bd3fed7e6b8f99c8817fd1ff5b9b864296c3d81fa8f1b729e02d21d72ffee4ced725efe74bea68fbc4d4244286fcdd4bf64406a439a15bcb4cf67754489c423972b4a80df5c2e7c5bc2dbaf2d42bb7b244f7c95bf92c75d3b33fc5410678a89589d1083da3acc459f2704cd99598c275e7c1878e00757e5bdb4e840226c11c0a17ff79c80b15c1ddb5af21cc2417061fbd2a2da819ed3b72b7efaa3bfebe2805c9b8ac19aa346512d484cfc81941e15f55881cc127e8f7aa12300cd5afb5742fa1d20cb467a5beb1c666cf76a368978b5"); + +pub const INTEL_ROOT_EXPONENT: &[u8] = + &hex!("0000000000000000000000000000000000000000000000000000000000010001"); + +pub mod types; + +pub mod verifier; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Recovered digest from signature does not match the specified report")] + RecoveredDigestMismatch, +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs new file mode 100644 index 0000000..b80a100 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs @@ -0,0 +1,49 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Binary; + +#[cw_serde] +pub struct IASReport { + pub report: ReportBody, + #[serde(rename = "reportsig")] + pub report_sig: Binary, +} + +#[cw_serde] +pub struct ReportBody { + pub id: String, + pub timestamp: String, + pub version: u64, + #[serde(rename = "epidPseudonym")] + pub epid_pseudonym: Binary, + #[serde(rename = "advisoryURL")] + pub advisory_url: String, + #[serde(rename = "advisoryIDs")] + pub advisory_ids: Vec, + #[serde(rename = "isvEnclaveQuoteStatus")] + pub isv_enclave_quote_status: String, + #[serde(rename = "platformInfoBlob")] + pub platform_info_blob: String, + #[serde(rename = "isvEnclaveQuoteBody")] + pub isv_enclave_quote_body: IsvEnclaveQuoteBody, +} + +#[cw_serde] +#[serde(transparent)] +pub struct IsvEnclaveQuoteBody(Binary); + +impl IsvEnclaveQuoteBody { + pub fn mrenclave(&self) -> [u8; 32] { + Self::array_chunk(self.0.as_slice(), 112) + } + + pub fn user_data(&self) -> [u8; 64] { + Self::array_chunk(self.0.as_slice(), 368) + } + + fn array_chunk(quote_body: &[u8], offset: usize) -> [u8; N] { + assert!(offset + N <= quote_body.len()); + quote_body[offset..offset + N] + .try_into() + .expect("array length mismatch") + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs new file mode 100644 index 0000000..2d09248 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs @@ -0,0 +1,172 @@ +use cosmwasm_std::ensure_eq; +use num_bigint::BigUint; +use sha2::{Digest, Sha256}; + +use crate::intel_sgx::epid::types::IASReport; +use crate::intel_sgx::epid::Error as EpidError; +use crate::intel_sgx::epid::{INTEL_ROOT_EXPONENT, INTEL_ROOT_MODULUS}; +use crate::intel_sgx::Error; + +/// Given an RSA signature and the signer's exponent + modulus we recover the digest that was signed by the signature. +pub fn recover_signature_digest(signature: &[u8], exponent: &[u8], modulus: &[u8]) -> Vec { + let sig_as_bignum_be = BigUint::from_bytes_be(signature); + let intel_modulus_be = BigUint::from_bytes_be(modulus); + let intel_exponent_be = BigUint::from_bytes_be(exponent); + + let digest_be = sig_as_bignum_be.modpow(&intel_exponent_be, &intel_modulus_be); + + // last 32 bytes contain the digest + let digest_bytes = digest_be.to_bytes_be(); + let n = digest_bytes.len(); + digest_bytes[n - 32..n].to_vec() +} + +pub fn verify( + ias_report: IASReport, + mrenclave: impl AsRef<[u8]>, + user_data: impl AsRef<[u8]>, +) -> Result<(), Error> { + // Extract the payload from the quote body + let user_data_in_quote = ias_report.report.isv_enclave_quote_body.user_data(); + + // check user_report_data + ensure_eq!( + user_data_in_quote, + user_data.as_ref(), + Error::UserDataMismatch + ); + + // Extract the mrenclave from the quote body + let mrenclave_in_quote = ias_report.report.isv_enclave_quote_body.mrenclave(); + + // check mrenclave + ensure_eq!( + mrenclave_in_quote, + mrenclave.as_ref(), + Error::MrEnclaveMismatch + ); + + // Recover the RSA signature's digest + let recovered_digest = recover_signature_digest( + ias_report.report_sig.as_slice(), + INTEL_ROOT_EXPONENT, + INTEL_ROOT_MODULUS, + ); + // Convert the recovered digest into a byte slice + let recovered_digest = recovered_digest.as_slice(); + + // Convert the ias report as a json string, removing all the backslashes to escape stuff + let ias_report_asjson = serde_json::to_string(&ias_report.report) + .expect("infallible serializer for IASReport") + .replace('\\', ""); + // Convert the ias report without the back slashes to bytes + let ias_report_asbytes = ias_report_asjson.as_bytes(); + // We are going to calculate our own digest of the ias report + let mut hasher = Sha256::default(); + // Update the hasher contents with that of the ias report json + hasher.update(ias_report_asbytes); + // finalize the sha256 hasher so that it produces a byte slice of the digest, should be 32 bytes + let ias_report_digest = &hasher.finalize()[..]; + + // ensure that the recovered digest from the signature matches the digest of the report + ensure_eq!( + ias_report_digest, + recovered_digest, + EpidError::RecoveredDigestMismatch + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::HexBinary; + + use super::*; + + #[cw_serde] + pub enum QueryMsg { + /// Verify an attestation + VerifyEpidAttestation { + // The report that is generated by an enclave + report: IASReport, + // The MRENCLAVE of this enclave (as hex string) + mrenclave: HexBinary, + // User data - whose commitment is in the `user_report_data` (as hex string) + user_data: HexBinary, + }, + } + + fn parse_attestation(query_verifier: &str) -> (IASReport, HexBinary, HexBinary) { + let query_verifier: QueryMsg = + serde_json::from_str(query_verifier).expect("deserialize query"); + match query_verifier { + QueryMsg::VerifyEpidAttestation { + report, + mrenclave, + user_data, + } => (report, mrenclave, user_data), + } + } + + fn verify_attestation(query_verifier: &str) { + let (report, mrenclave, user_data) = parse_attestation(query_verifier); + verify(report, mrenclave, user_data).expect("RA verification failure"); + } + + #[test] + fn test_verifier_ok() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_mrenclave() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "f3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_user_data() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_platform_info_blob() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YaY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs new file mode 100644 index 0000000..908a4cd --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs @@ -0,0 +1,22 @@ +#![warn( + clippy::checked_conversions, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + rust_2018_idioms, + unused_lifetimes +)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + warnings +)] +#![forbid(unsafe_code)] + +pub mod intel_sgx; + +pub use intel_sgx::epid::types::IASReport; +pub use intel_sgx::epid::verifier::verify as verify_epid_attestation; +pub use intel_sgx::Error;