feat(cli): print-fmspc command (#247)

Co-authored-by: Dave Kaj <davidkajpust@informal.systems>
This commit is contained in:
Shoaib Ahmed 2024-10-10 17:57:50 +04:00 committed by GitHub
parent 864da77405
commit b3fe3bf213
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 275 additions and 2 deletions

1
Cargo.lock generated
View file

@ -4353,6 +4353,7 @@ dependencies = [
"cosmrs", "cosmrs",
"cosmwasm-std", "cosmwasm-std",
"cw-client", "cw-client",
"dcap-qvl",
"dirs 5.0.1", "dirs 5.0.1",
"displaydoc", "displaydoc",
"figment", "figment",

View file

@ -10,6 +10,7 @@ homepage.workspace = true
categories = ["command-line-utilities", "cryptography::cryptocurrencies", "hardware-support", "wasm"] categories = ["command-line-utilities", "cryptography::cryptocurrencies", "hardware-support", "wasm"]
keywords = ["cosmos", "cosmwasm", "cycles", "quartz", "sgx"] keywords = ["cosmos", "cosmwasm", "cycles", "quartz", "sgx"]
readme = "README.md" readme = "README.md"
default-run = "quartz"
description = """ 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. 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" name = "quartz"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
name = "gen-quote"
path = "src/bin/gen-quote.rs"
[dependencies] [dependencies]
async-trait.workspace = true async-trait.workspace = true
cargo-generate.workspace = true cargo-generate.workspace = true
@ -53,6 +58,7 @@ figment = { version = "0.10.19", features = ["env", "toml"] }
clearscreen = "3.0.0" clearscreen = "3.0.0"
cargo_metadata = "0.18.1" cargo_metadata = "0.18.1"
serde_with = "3.10.0" serde_with = "3.10.0"
dcap-qvl = "0.1.0"
# cosmos # cosmos
cosmrs.workspace = true cosmrs.workspace = true

View file

@ -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

View file

@ -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(())
}

View file

@ -79,6 +79,9 @@ pub enum Command {
/// Build, deploy, perform handshake, and run quartz app while listening for changes /// Build, deploy, perform handshake, and run quartz app while listening for changes
Dev(DevArgs), Dev(DevArgs),
/// Print the FMSPC of the current platform (SGX only)
PrintFmspc,
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -296,6 +299,7 @@ impl ToFigment for Command {
Command::Dev(args) => Figment::from(Serialized::defaults(args)) Command::Dev(args) => Figment::from(Serialized::defaults(args))
.merge(Serialized::defaults(&args.contract_deploy)) .merge(Serialized::defaults(&args.contract_deploy))
.merge(Serialized::defaults(&args.enclave_build)), .merge(Serialized::defaults(&args.enclave_build)),
Command::PrintFmspc => Figment::default(),
} }
} }
} }

View file

@ -12,6 +12,7 @@ pub mod enclave_build;
pub mod enclave_start; pub mod enclave_start;
pub mod handshake; pub mod handshake;
pub mod init; pub mod init;
pub mod print_fmspc;
#[async_trait] #[async_trait]
pub trait Handler { pub trait Handler {
@ -33,6 +34,7 @@ impl Handler for Request {
Request::EnclaveBuild(request) => request.handle(config).await, Request::EnclaveBuild(request) => request.handle(config).await,
Request::EnclaveStart(request) => request.handle(config).await, Request::EnclaveStart(request) => request.handle(config).await,
Request::Dev(request) => request.handle(config).await, Request::Dev(request) => request.handle(config).await,
Request::PrintFmspc(request) => request.handle(config).await,
} }
.map(Into::into) .map(Into::into)
} }

View file

@ -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<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
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, &quote, 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
}

View file

@ -5,7 +5,7 @@ use crate::{
request::{ request::{
contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest, contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest,
dev::DevRequest, enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest, 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 handshake;
pub mod init; pub mod init;
pub mod print_fmspc;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Request { pub enum Request {
Init(InitRequest), Init(InitRequest),
@ -26,6 +28,7 @@ pub enum Request {
EnclaveBuild(EnclaveBuildRequest), EnclaveBuild(EnclaveBuildRequest),
EnclaveStart(EnclaveStartRequest), EnclaveStart(EnclaveStartRequest),
Dev(DevRequest), Dev(DevRequest),
PrintFmspc(PrintFmspcRequest),
} }
impl TryFrom<Command> for Request { impl TryFrom<Command> for Request {
@ -62,6 +65,7 @@ impl TryFrom<Command> for Request {
} }
.into()) .into())
} }
Command::PrintFmspc => Ok(Request::PrintFmspc(PrintFmspcRequest)),
} }
} }
} }

View file

@ -0,0 +1,10 @@
use crate::request::Request;
#[derive(Clone, Debug)]
pub struct PrintFmspcRequest;
impl From<PrintFmspcRequest> for Request {
fn from(request: PrintFmspcRequest) -> Self {
Self::PrintFmspc(request)
}
}

View file

@ -3,7 +3,7 @@ use serde::Serialize;
use crate::response::{ use crate::response::{
contract_build::ContractBuildResponse, contract_deploy::ContractDeployResponse, contract_build::ContractBuildResponse, contract_deploy::ContractDeployResponse,
dev::DevResponse, enclave_build::EnclaveBuildResponse, enclave_start::EnclaveStartResponse, dev::DevResponse, enclave_build::EnclaveBuildResponse, enclave_start::EnclaveStartResponse,
handshake::HandshakeResponse, init::InitResponse, handshake::HandshakeResponse, init::InitResponse, print_fmspc::PrintFmspcResponse,
}; };
pub mod contract_build; pub mod contract_build;
@ -14,6 +14,8 @@ pub mod enclave_start;
pub mod handshake; pub mod handshake;
pub mod init; pub mod init;
pub mod print_fmspc;
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub enum Response { pub enum Response {
Init(InitResponse), Init(InitResponse),
@ -23,4 +25,5 @@ pub enum Response {
EnclaveBuild(EnclaveBuildResponse), EnclaveBuild(EnclaveBuildResponse),
EnclaveStart(EnclaveStartResponse), EnclaveStart(EnclaveStartResponse),
Dev(DevResponse), Dev(DevResponse),
PrintFmspc(PrintFmspcResponse),
} }

View file

@ -0,0 +1,14 @@
use serde::Serialize;
use crate::response::Response;
#[derive(Clone, Debug, Serialize)]
pub struct PrintFmspcResponse {
pub fmspc: String,
}
impl From<PrintFmspcResponse> for Response {
fn from(response: PrintFmspcResponse) -> Self {
Self::PrintFmspc(response)
}
}