Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wasm support for both web and node environment #1

Merged
merged 14 commits into from
Oct 17, 2024
Merged
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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", features = ["js"] }
serde-wasm-bindgen = "0.4"
wasm-bindgen = "0.2.95"
serde_bytes = "0.11"
Copy link
Collaborator

@kvinwang kvinwang Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make them optional and enable them by the feature js.


[dependencies.webpki]
version = "0.102.8"
Expand All @@ -56,6 +60,9 @@ features = ["alloc", "ring"]
insta = "1"
tokio = { version = "1", features = ["full"] }

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["std", "report"]
std = [
Expand All @@ -74,3 +81,4 @@ std = [
"urlencoding",
]
report = ["std", "tracing", "futures"]
js = ["ring/wasm32_unknown_unknown_js"]
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/collateral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fn get_header(resposne: &reqwest::Response, name: &str) -> Result<String> {
///
/// * `Ok(QuoteCollateralV3)` - The quote collateral
/// * `Err(Error)` - The error
#[cfg(not(feature = "js"))]
pub async fn get_collateral(
pccs_url: &str,
mut quote: &[u8],
Expand Down
1 change: 0 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
35 changes: 31 additions & 4 deletions src/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use alloc::vec::Vec;

use anyhow::Result;
use scale::{Decode, Input};
use serde::{Deserialize, Serialize};

use crate::{constants::*, utils, Error};

Expand Down Expand Up @@ -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],
}

Expand Down Expand Up @@ -183,7 +210,7 @@ fn decode_auth_data(ver: u16, input: &mut &[u8]) -> Result<AuthData, scale::Erro
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Report {
SgxEnclave(EnclaveReport),
TD10(TDReport10),
Expand Down
26 changes: 25 additions & 1 deletion src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,38 @@ use crate::{
utils::{self, encode_as_der, extract_certs, verify_certificate_chain},
};
use crate::{Error, QuoteCollateralV3};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VerifiedReport {
pub status: String,
pub advisory_ids: Vec<String>,
pub report: Report,
}

#[wasm_bindgen]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[wasm_bindgen]
#[cfg(feature = "js")]
#[wasm_bindgen]

pub fn js_verify(
raw_quote: JsValue,
quote_collateral: JsValue,
now: u64,
) -> Result<JsValue, JsValue> {
let raw_quote: Vec<u8> = serde_wasm_bindgen::from_value(raw_quote)
.map_err(|_| JsValue::from_str("Failed to decode raw_quote"))?;
let quote_collateral_bytes: Vec<u8> = 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, &quote_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
Expand Down
2 changes: 2 additions & 0 deletions tests/js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pkg
sample
19 changes: 19 additions & 0 deletions tests/js/README.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions tests/js/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verify Quote</title>
</head>
<body>
<h1>Verify Quote</h1>
<script type="module" src="./verify_quote_web.js"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions tests/js/verify_quote_node.js
Original file line number Diff line number Diff line change
@@ -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);
}
36 changes: 36 additions & 0 deletions tests/js/verify_quote_web.js
Original file line number Diff line number Diff line change
@@ -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();