feat(cli): print-fmspc command (#247)
Co-authored-by: Dave Kaj <davidkajpust@informal.systems>
This commit is contained in:
parent
864da77405
commit
b3fe3bf213
11 changed files with 275 additions and 2 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
42
crates/cli/src/bin/gen-quote.manifest.template
Normal file
42
crates/cli/src/bin/gen-quote.manifest.template
Normal 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
|
20
crates/cli/src/bin/gen-quote.rs
Normal file
20
crates/cli/src/bin/gen-quote.rs
Normal 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(())
|
||||||
|
}
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
167
crates/cli/src/handler/print_fmspc.rs
Normal file
167
crates/cli/src/handler/print_fmspc.rs
Normal 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, "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
|
||||||
|
}
|
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
crates/cli/src/request/print_fmspc.rs
Normal file
10
crates/cli/src/request/print_fmspc.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
14
crates/cli/src/response/print_fmspc.rs
Normal file
14
crates/cli/src/response/print_fmspc.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue