diff --git a/Cargo.toml b/Cargo.toml index 683c785..afdd551 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ log = { version = "0.4.20", default-features = false } anyhow = { version = "1", optional = true } -ring = { version = "0.17.8", default-features = false, features = [ +ring = { version = "0.17", default-features = false, features = [ "alloc", ] } reqwest = { version = "0.11.27", optional = true, default-features = false, features = [ @@ -45,6 +45,10 @@ serde_json = { version = "1.0.108", optional = true, features = [ ] } tracing = { version = "0.1", optional = true } futures = { version = "0.3", optional = true } +getrandom = { version = "0.2", optional = true, features = ["js"] } +serde-wasm-bindgen = { version = "0.4", optional = true} +wasm-bindgen = { version = "0.2.95", optional = true } +serde_bytes = { version = "0.11" } [dependencies.webpki] version = "0.102.8" @@ -56,6 +60,9 @@ features = ["alloc", "ring"] insta = "1" tokio = { version = "1", features = ["full"] } +[lib] +crate-type = ["cdylib", "rlib"] + [features] default = ["std", "report"] std = [ @@ -74,3 +81,4 @@ std = [ "urlencoding", ] report = ["std", "tracing", "futures"] +js = ["ring/wasm32_unknown_unknown_js", "getrandom", "serde-wasm-bindgen", "wasm-bindgen"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1132ec6 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +WASM_PACK = wasm-pack +INSTALL_TOOL = cargo install wasm-pack +BUILD_WEB = $(WASM_PACK) build --release --target web --out-dir pkg/web --out-name dcap-qvl-web -- --features=js +BUILD_NODE = $(WASM_PACK) build --release --target nodejs --out-dir pkg/node --out-name dcap-qvl-node -- --features=js + +all: install_wasm_tool build_web_pkg build_node_pkg + +install_wasm_tool: + @echo "Installing wasm-pack if not already installed..." + @if ! command -v $(WASM_PACK) &> /dev/null; then \ + echo "wasm-pack not found, installing..."; \ + $(INSTALL_TOOL); \ + else \ + echo "wasm-pack is already installed."; \ + fi + +build_web_pkg: install_wasm_tool + @echo "Building for web browsers..." + $(BUILD_WEB) + +build_node_pkg: install_wasm_tool + @echo "Building for Node.js..." + $(BUILD_NODE) + +clean: + @echo "Cleaning up..." + rm -rf pkg + +.PHONY: all install_wasm_tool build_web_pkg build_node_pkg clean diff --git a/src/collateral.rs b/src/collateral.rs index db3b615..5e78d99 100644 --- a/src/collateral.rs +++ b/src/collateral.rs @@ -29,6 +29,7 @@ fn get_header(resposne: &reqwest::Response, name: &str) -> Result { /// /// * `Ok(QuoteCollateralV3)` - The quote collateral /// * `Err(Error)` - The error +#[cfg(not(feature = "js"))] pub async fn get_collateral( pccs_url: &str, mut quote: &[u8], diff --git a/src/constants.rs b/src/constants.rs index 909e2da..c0afcb0 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -73,7 +73,6 @@ pub const ATTESTATION_KEY_LEN: usize = 64; pub const AUTHENTICATION_DATA_LEN: usize = 32; pub const QE_HASH_DATA_BYTE_LEN: usize = ATTESTATION_KEY_LEN + AUTHENTICATION_DATA_LEN; - pub const PCK_ID_PLAIN: u16 = 1; pub const PCK_ID_RSA_2048_OAEP: u16 = 2; pub const PCK_ID_RSA_3072_OAEP: u16 = 3; diff --git a/src/lib.rs b/src/lib.rs index cabe483..5f35c53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,8 +40,9 @@ extern crate alloc; use scale::{Decode, Encode}; use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; -#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)] +#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Error { InvalidCertificate, InvalidSignature, @@ -75,7 +76,7 @@ pub enum Error { OidIsMissing, } -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct QuoteCollateralV3 { pub pck_crl_issuer_chain: String, pub root_ca_crl: String, diff --git a/src/quote.rs b/src/quote.rs index 38f6f14..02076f5 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use anyhow::Result; use scale::{Decode, Input}; +use serde::{Deserialize, Serialize}; use crate::{constants::*, utils, Error}; @@ -41,45 +42,71 @@ pub struct Body { pub size: u32, } -#[derive(Decode, Debug, Clone)] +#[derive(Serialize, Deserialize, Decode, Debug, Clone)] pub struct EnclaveReport { + #[serde(with = "serde_bytes")] pub cpu_svn: [u8; 16], pub misc_select: u32, + #[serde(with = "serde_bytes")] pub reserved1: [u8; 28], + #[serde(with = "serde_bytes")] pub attributes: [u8; 16], + #[serde(with = "serde_bytes")] pub mr_enclave: [u8; 32], + #[serde(with = "serde_bytes")] pub reserved2: [u8; 32], + #[serde(with = "serde_bytes")] pub mr_signer: [u8; 32], + #[serde(with = "serde_bytes")] pub reserved3: [u8; 96], pub isv_prod_id: u16, pub isv_svn: u16, + #[serde(with = "serde_bytes")] pub reserved4: [u8; 60], + #[serde(with = "serde_bytes")] pub report_data: [u8; 64], } -#[derive(Decode, Debug, Clone)] +#[derive(Decode, Debug, Clone, Serialize, Deserialize)] pub struct TDReport10 { + #[serde(with = "serde_bytes")] pub tee_tcb_svn: [u8; 16], + #[serde(with = "serde_bytes")] pub mr_seam: [u8; 48], + #[serde(with = "serde_bytes")] pub mr_signer_seam: [u8; 48], + #[serde(with = "serde_bytes")] pub seam_attributes: [u8; 8], + #[serde(with = "serde_bytes")] pub td_attributes: [u8; 8], + #[serde(with = "serde_bytes")] pub xfam: [u8; 8], + #[serde(with = "serde_bytes")] pub mr_td: [u8; 48], + #[serde(with = "serde_bytes")] pub mr_config_id: [u8; 48], + #[serde(with = "serde_bytes")] pub mr_owner: [u8; 48], + #[serde(with = "serde_bytes")] pub mr_owner_config: [u8; 48], + #[serde(with = "serde_bytes")] pub rt_mr0: [u8; 48], + #[serde(with = "serde_bytes")] pub rt_mr1: [u8; 48], + #[serde(with = "serde_bytes")] pub rt_mr2: [u8; 48], + #[serde(with = "serde_bytes")] pub rt_mr3: [u8; 48], + #[serde(with = "serde_bytes")] pub report_data: [u8; 64], } -#[derive(Decode, Debug, Clone)] +#[derive(Decode, Debug, Clone, Serialize, Deserialize)] pub struct TDReport15 { pub base: TDReport10, + #[serde(with = "serde_bytes")] pub tee_tcb_svn2: [u8; 16], + #[serde(with = "serde_bytes")] pub mr_service_td: [u8; 48], } @@ -183,7 +210,7 @@ fn decode_auth_data(ver: u16, input: &mut &[u8]) -> Result, pub report: Report, } +#[cfg(feature = "js")] +#[wasm_bindgen] +pub fn js_verify( + raw_quote: JsValue, + quote_collateral: JsValue, + now: u64, +) -> Result { + let raw_quote: Vec = serde_wasm_bindgen::from_value(raw_quote) + .map_err(|_| JsValue::from_str("Failed to decode raw_quote"))?; + let quote_collateral_bytes: Vec = serde_wasm_bindgen::from_value(quote_collateral) + .map_err(|_| JsValue::from_str("Failed to decode quote_collateral"))?; + let quote_collateral = QuoteCollateralV3::decode(&mut quote_collateral_bytes.as_slice()) + .map_err(|_| JsValue::from_str("Failed to decode quote_collateral_bytes"))?; + + let verified_report = verify(&raw_quote, "e_collateral, now).map_err(|e| { + serde_wasm_bindgen::to_value(&e) + .unwrap_or_else(|_| JsValue::from_str("Failed to encode Error")) + })?; + + serde_wasm_bindgen::to_value(&verified_report) + .map_err(|_| JsValue::from_str("Failed to encode verified_report")) +} + /// Verify a quote /// /// # Arguments diff --git a/tests/js/.gitignore b/tests/js/.gitignore new file mode 100644 index 0000000..242f99d --- /dev/null +++ b/tests/js/.gitignore @@ -0,0 +1,2 @@ +pkg +sample \ No newline at end of file diff --git a/tests/js/README.md b/tests/js/README.md new file mode 100644 index 0000000..a0ab1b0 --- /dev/null +++ b/tests/js/README.md @@ -0,0 +1,19 @@ +# Test the JS bindings + +## Verify Quote with Node + +``` +cd tests/js +node verify_quote_node.js +``` + +## Verify Quote with Web + +``` +cd tests/js +ln -sf ../../pkg pkg +ln -sf ../../sample sample +python3 -m http.server 8000 +``` + +Open http://localhost:8000/index.html in browser, and check the console for the result. diff --git a/tests/js/index.html b/tests/js/index.html new file mode 100644 index 0000000..624422b --- /dev/null +++ b/tests/js/index.html @@ -0,0 +1,12 @@ + + + + + + Verify Quote + + +

Verify Quote

+ + + \ No newline at end of file diff --git a/tests/js/verify_quote_node.js b/tests/js/verify_quote_node.js new file mode 100644 index 0000000..150ed9c --- /dev/null +++ b/tests/js/verify_quote_node.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const path = require('path'); +const { js_verify } = require('../../pkg/node/dcap-qvl-node'); + +// Function to read a file as a Uint8Array +function readFileAsUint8Array(filePath) { + const data = fs.readFileSync(filePath); + return new Uint8Array(data); +} + +// Paths to your sample files +const rawQuotePath = path.join(__dirname, '../../sample', 'tdx_quote'); +const quoteCollateralPath = path.join(__dirname, '../../sample', 'tdx_quote_collateral'); + +// Read the files +const rawQuote = readFileAsUint8Array(rawQuotePath); +const quoteCollateral = readFileAsUint8Array(quoteCollateralPath); + +// Current timestamp +// TCBInfoExpired when using current timestamp, pick the time from verify_quote.rs +// const now = BigInt(Math.floor(Date.now() / 1000)); +const now = BigInt(1725258675); + +try { + // Call the js_verify function + const result = js_verify(rawQuote, quoteCollateral, now); + console.log('Verification Result:', result); +} catch (error) { + console.error('Verification failed:', error); +} \ No newline at end of file diff --git a/tests/js/verify_quote_web.js b/tests/js/verify_quote_web.js new file mode 100644 index 0000000..3145428 --- /dev/null +++ b/tests/js/verify_quote_web.js @@ -0,0 +1,36 @@ +import init, { js_verify } from '/pkg/web/dcap-qvl-web.js'; + +// Function to fetch a file as a Uint8Array +async function fetchFileAsUint8Array(url) { + const response = await fetch(url); + const data = await response.arrayBuffer(); + return new Uint8Array(data); +} + +// URLs to your sample files +const rawQuoteUrl = '/sample/tdx_quote'; +const quoteCollateralUrl = '/sample/tdx_quote_collateral'; + +// Load the files +async function loadFilesAndVerify() { + try { + // Initialize the WASM module + await init('/pkg/web/dcap-qvl-web_bg.wasm'); + + const rawQuote = await fetchFileAsUint8Array(rawQuoteUrl); + const quoteCollateral = await fetchFileAsUint8Array(quoteCollateralUrl); + + // Current timestamp + const now = BigInt(1725258675); + + // Call the js_verify function + const result = js_verify(rawQuote, quoteCollateral, now); + console.log('Verification Result:', result); + } catch (error) { + console.error('Verification failed:', error); + } +} + +// Execute the verification +loadFilesAndVerify(); +