diff --git a/Cargo.lock b/Cargo.lock index 1696e807..2caea904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4353,6 +4353,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "cw-client", + "dcap-qvl", "dirs 5.0.1", "displaydoc", "figment", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 3eb12352..70a11287 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -10,6 +10,7 @@ homepage.workspace = true categories = ["command-line-utilities", "cryptography::cryptocurrencies", "hardware-support", "wasm"] keywords = ["cosmos", "cosmwasm", "cycles", "quartz", "sgx"] readme = "README.md" +default-run = "quartz" description = """ A CLI tool to streamline development and deployment of Quartz applications. Quartz is a flexible framework for privacy-preserving computation via Trusted Execution Environments (TEEs) organized and secured by smart contracts. """ @@ -18,6 +19,10 @@ A CLI tool to streamline development and deployment of Quartz applications. Quar name = "quartz" path = "src/main.rs" +[[bin]] +name = "gen-quote" +path = "src/bin/gen-quote.rs" + [dependencies] async-trait.workspace = true cargo-generate.workspace = true @@ -53,6 +58,7 @@ figment = { version = "0.10.19", features = ["env", "toml"] } clearscreen = "3.0.0" cargo_metadata = "0.18.1" serde_with = "3.10.0" +dcap-qvl = "0.1.0" # cosmos cosmrs.workspace = true diff --git a/crates/cli/src/bin/gen-quote.manifest.template b/crates/cli/src/bin/gen-quote.manifest.template new file mode 100644 index 00000000..4e52bf6c --- /dev/null +++ b/crates/cli/src/bin/gen-quote.manifest.template @@ -0,0 +1,42 @@ +# Manifest file for creating dummy quotes + +libos.entrypoint = "{{ gen_quote_bin_path }}" + +loader.log_level = "{{ log_level }}" +loader.entrypoint = "file:{{ gramine.libos }}" +loader.env.LD_LIBRARY_PATH = "/lib:/usr/local/lib:{{ arch_libdir }}:/usr{{ arch_libdir }}" +loader.env.HOME = "{{ home }}" +loader.env.INSIDE_SGX = "1" +loader.env.TLS = { passthrough = true } +loader.env.RA_TYPE = { passthrough = true } +loader.env.RA_TLS_ALLOW_DEBUG_ENCLAVE_INSECURE = { passthrough = true } +loader.env.RA_TLS_ALLOW_OUTDATED_TCB_INSECURE = { passthrough = true } +loader.env.RA_TLS_MRENCLAVE = { passthrough = true } +loader.env.RA_TLS_MRSIGNER = { passthrough = true } +loader.env.RA_TLS_ISV_SVN = { passthrough = true } +loader.env.RA_TLS_ISV_PROD_ID = { passthrough = true } +loader.env.MYAPP_DATA = { passthrough = true } + +fs.mounts = [ + { uri = "file:{{ gramine.runtimedir() }}", path = "/lib" }, + { uri = "file:{{ arch_libdir }}", path = "{{ arch_libdir }}" }, + { uri = "file:/usr/{{ arch_libdir }}", path = "/usr{{ arch_libdir }}" }, + { uri = "file:{{ gen_quote_bin_path }}", path = "{{ gen_quote_bin_path }}" }, +] + +sgx.enclave_size = "512M" +sgx.max_threads = 2 +sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }} + +sgx.remote_attestation = "{{ ra_type }}" +sgx.ra_client_linkable = {{ 'true' if ra_client_linkable == '1' else 'false' }} + +sgx.trusted_files = [ + "file:{{ gramine.libos }}", + "file:{{ gen_quote_bin_path }}", + "file:{{ gramine.runtimedir() }}/", + "file:{{ arch_libdir }}/", + "file:/usr/{{ arch_libdir }}/", +] + +sys.enable_sigterm_injection = true diff --git a/crates/cli/src/bin/gen-quote.rs b/crates/cli/src/bin/gen-quote.rs new file mode 100644 index 00000000..01ca3556 --- /dev/null +++ b/crates/cli/src/bin/gen-quote.rs @@ -0,0 +1,20 @@ +use std::{ + fs::File, + io::{self, Read, Write}, +}; + +fn main() -> io::Result<()> { + let user_data = [0u8; 64]; + 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()?; + + let mut file = File::open("/dev/attestation/quote")?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + let quote_hex = hex::encode(&buffer); + print!("{}", quote_hex); + + Ok(()) +} diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index fabac748..940010ad 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -79,6 +79,9 @@ pub enum Command { /// Build, deploy, perform handshake, and run quartz app while listening for changes Dev(DevArgs), + + /// Print the FMSPC of the current platform (SGX only) + PrintFmspc, } #[allow(clippy::large_enum_variant)] @@ -296,6 +299,7 @@ impl ToFigment for Command { Command::Dev(args) => Figment::from(Serialized::defaults(args)) .merge(Serialized::defaults(&args.contract_deploy)) .merge(Serialized::defaults(&args.enclave_build)), + Command::PrintFmspc => Figment::default(), } } } diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index be800c96..1f6ced96 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -12,6 +12,7 @@ pub mod enclave_build; pub mod enclave_start; pub mod handshake; pub mod init; +pub mod print_fmspc; #[async_trait] pub trait Handler { @@ -33,6 +34,7 @@ impl Handler for Request { Request::EnclaveBuild(request) => request.handle(config).await, Request::EnclaveStart(request) => request.handle(config).await, Request::Dev(request) => request.handle(config).await, + Request::PrintFmspc(request) => request.handle(config).await, } .map(Into::into) } diff --git a/crates/cli/src/handler/print_fmspc.rs b/crates/cli/src/handler/print_fmspc.rs new file mode 100644 index 00000000..dec34c48 --- /dev/null +++ b/crates/cli/src/handler/print_fmspc.rs @@ -0,0 +1,167 @@ +use std::{env, path::PathBuf, process::Stdio}; + +use async_trait::async_trait; +use color_eyre::{ + eyre::{eyre, Context}, + owo_colors::OwoColorize, + Report, Result, +}; +use dcap_qvl::collateral::get_collateral; +use tempfile::tempdir; +use tokio::{fs::File, io::AsyncWriteExt, process::Command}; +use tracing::{debug, info}; + +use crate::{ + config::Config, + handler::Handler, + request::print_fmspc::PrintFmspcRequest, + response::{print_fmspc::PrintFmspcResponse, Response}, +}; + +const GEN_QUOTE_MANIFEST_TEMPLATE: &str = include_str!("../bin/gen-quote.manifest.template"); +const DEFAULT_PCCS_URL: &str = "https://localhost:8081/sgx/certification/v4/"; + +#[async_trait] +impl Handler for PrintFmspcRequest { + type Response = Response; + + async fn handle + Send>(self, config: C) -> Result { + let config = config.as_ref().clone(); + + if config.mock_sgx { + return Err(eyre!( + "MOCK_SGX is enabled! print-fmpsc is only available if SGX is enabled" + )); + } + + let current_exe_path = + env::current_exe().context("Failed to get current executable path")?; + let exe_path_str = current_exe_path.to_string_lossy(); + + if exe_path_str.contains("target") { + // i.e. this isn't a `cargo install` based installation + + info!("{}", "\nBuilding dummy enclave".blue().bold()); + + let mut cargo = Command::new("cargo"); + let command = cargo.arg("build"); + + if exe_path_str.contains("release") { + // add the release flag to make sure it's built in the right place + command.arg("--release"); + } + + let status = command.status().await?; + + if !status.success() { + return Err(eyre!("Couldn't build enclave. {:?}", status)); + } + } + + debug!("{}", "\nGenerating SGX private key".blue().bold()); + + let _ = Command::new("gramine-sgx-gen-private-key") + .output() + .await + .map_err(|e| eyre!("Failed to execute gramine-sgx-gen-private-key: {}", e))?; + + let host = target_lexicon::HOST; + let arch_libdir = format!( + "/lib/{}-{}-{}", + host.architecture, host.operating_system, host.environment + ); + + let home_dir = dirs::home_dir() + .ok_or_else(|| eyre!("Home directory not set"))? + .display() + .to_string(); + + let gen_quote_bin_path = file_path(current_exe_path.clone(), "gen-quote"); + + let temp_dir = tempdir()?; + let temp_dir_path = temp_dir.path(); + + let gen_quote_manifest_path = temp_dir_path.join("gen-quote.manifest.template"); + let mut gen_quote_manifest_file = File::create(&gen_quote_manifest_path).await?; + gen_quote_manifest_file + .write_all(GEN_QUOTE_MANIFEST_TEMPLATE.as_bytes()) + .await?; + + let status = Command::new("gramine-manifest") + .arg("-Dlog_level=error") + .arg(format!("-Dhome={}", home_dir)) + .arg(format!("-Darch_libdir={}", arch_libdir)) + .arg("-Dra_type=dcap") + .arg("-Dra_client_linkable=1") + .arg(format!( + "-Dgen_quote_bin_path={}", + gen_quote_bin_path.display() + )) + .arg(gen_quote_manifest_path) + .arg("gen-quote.manifest") + .current_dir(temp_dir_path) + .status() + .await + .map_err(|e| eyre!("Failed to execute gramine-manifest: {}", e))?; + + if !status.success() { + return Err(eyre!( + "gramine-manifest command failed with status: {:?}", + status + )); + } + + let status = Command::new("gramine-sgx-sign") + .arg("--manifest") + .arg("gen-quote.manifest") + .arg("--output") + .arg("gen-quote.manifest.sgx") + .current_dir(temp_dir_path) + .status() + .await + .map_err(|e| eyre!("Failed to execute gramine-sgx-sign: {}", e))?; + + if !status.success() { + return Err(eyre!( + "gramine-sgx-sign command failed with status: {:?}", + status + )); + } + + info!("{}", "\nGenerating dummy quote".blue().bold()); + + let child = Command::new("gramine-sgx") + .arg("./gen-quote") + .kill_on_drop(true) + .current_dir(temp_dir_path) + .stdout(Stdio::piped()) // Redirect stdout to a pipe + .stderr(Stdio::piped()) // Redirect stderr to a pipe + .spawn() + .map_err(|e| eyre!("Failed to spawn gramine-sgx child process: {}", e))?; + + let output = child.wait_with_output().await?; + if !output.status.success() { + return Err(eyre!("Couldn't build enclave. {:?}", status)); + } + + let quote = hex::decode(output.stdout)?; + + let collateral = + get_collateral(DEFAULT_PCCS_URL, "e, std::time::Duration::from_secs(10)) + .await + .expect("failed to get collateral"); + let tcb_info: serde_json::Value = serde_json::from_str(&collateral.tcb_info) + .expect("Retrieved Tcbinfo is not valid JSON"); + + Ok(PrintFmspcResponse { + fmspc: tcb_info["fmspc"].to_string(), + } + .into()) + } +} + +fn file_path(mut current_exe_path: PathBuf, file_name: &str) -> PathBuf { + current_exe_path.pop(); + current_exe_path.push(file_name); + current_exe_path +} diff --git a/crates/cli/src/request.rs b/crates/cli/src/request.rs index 17c98209..367698d0 100644 --- a/crates/cli/src/request.rs +++ b/crates/cli/src/request.rs @@ -5,7 +5,7 @@ use crate::{ request::{ contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest, dev::DevRequest, enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest, - handshake::HandshakeRequest, init::InitRequest, + handshake::HandshakeRequest, init::InitRequest, print_fmspc::PrintFmspcRequest, }, }; @@ -17,6 +17,8 @@ pub mod enclave_start; pub mod handshake; pub mod init; +pub mod print_fmspc; + #[derive(Clone, Debug)] pub enum Request { Init(InitRequest), @@ -26,6 +28,7 @@ pub enum Request { EnclaveBuild(EnclaveBuildRequest), EnclaveStart(EnclaveStartRequest), Dev(DevRequest), + PrintFmspc(PrintFmspcRequest), } impl TryFrom for Request { @@ -62,6 +65,7 @@ impl TryFrom for Request { } .into()) } + Command::PrintFmspc => Ok(Request::PrintFmspc(PrintFmspcRequest)), } } } diff --git a/crates/cli/src/request/print_fmspc.rs b/crates/cli/src/request/print_fmspc.rs new file mode 100644 index 00000000..8dbbd4b4 --- /dev/null +++ b/crates/cli/src/request/print_fmspc.rs @@ -0,0 +1,10 @@ +use crate::request::Request; + +#[derive(Clone, Debug)] +pub struct PrintFmspcRequest; + +impl From for Request { + fn from(request: PrintFmspcRequest) -> Self { + Self::PrintFmspc(request) + } +} diff --git a/crates/cli/src/response.rs b/crates/cli/src/response.rs index 2839c172..c5a21cd7 100644 --- a/crates/cli/src/response.rs +++ b/crates/cli/src/response.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::response::{ contract_build::ContractBuildResponse, contract_deploy::ContractDeployResponse, dev::DevResponse, enclave_build::EnclaveBuildResponse, enclave_start::EnclaveStartResponse, - handshake::HandshakeResponse, init::InitResponse, + handshake::HandshakeResponse, init::InitResponse, print_fmspc::PrintFmspcResponse, }; pub mod contract_build; @@ -14,6 +14,8 @@ pub mod enclave_start; pub mod handshake; pub mod init; +pub mod print_fmspc; + #[derive(Clone, Debug, Serialize)] pub enum Response { Init(InitResponse), @@ -23,4 +25,5 @@ pub enum Response { EnclaveBuild(EnclaveBuildResponse), EnclaveStart(EnclaveStartResponse), Dev(DevResponse), + PrintFmspc(PrintFmspcResponse), } diff --git a/crates/cli/src/response/print_fmspc.rs b/crates/cli/src/response/print_fmspc.rs new file mode 100644 index 00000000..112bb418 --- /dev/null +++ b/crates/cli/src/response/print_fmspc.rs @@ -0,0 +1,14 @@ +use serde::Serialize; + +use crate::response::Response; + +#[derive(Clone, Debug, Serialize)] +pub struct PrintFmspcResponse { + pub fmspc: String, +} + +impl From for Response { + fn from(response: PrintFmspcResponse) -> Self { + Self::PrintFmspc(response) + } +}