From a4b395971365d60c1ede4cc2d3aace8dbaea56c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Desr=C3=A9?= Date: Tue, 23 Aug 2022 14:58:07 -0700 Subject: [PATCH 1/3] WIP: expose a JS api through WASM. --- ucan-key-support/Cargo.toml | 4 ++ ucan-key-support/demo/.gitignore | 1 + ucan-key-support/demo/build.sh | 8 +++ ucan-key-support/demo/index.html | 63 ++++++++++++++++++++ ucan-key-support/src/lib.rs | 4 ++ ucan-key-support/src/rsa.rs | 6 +- ucan-key-support/src/web_crypto.rs | 92 ++++++++++++++++++++++++++---- 7 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 ucan-key-support/demo/.gitignore create mode 100755 ucan-key-support/demo/build.sh create mode 100644 ucan-key-support/demo/index.html diff --git a/ucan-key-support/Cargo.toml b/ucan-key-support/Cargo.toml index 22062981..1e095214 100644 --- a/ucan-key-support/Cargo.toml +++ b/ucan-key-support/Cargo.toml @@ -16,6 +16,9 @@ license = "Apache-2.0" readme = "README.md" version = "0.4.0-alpha.1" +[lib] +crate-type = ["cdylib", "rlib"] + [features] default = [] web = ["wasm-bindgen", "wasm-bindgen-futures", "js-sys", "web-sys", "ucan/web", "getrandom/js"] @@ -44,6 +47,7 @@ tokio = { version = "^1", features = ["macros", "rt"] } wasm-bindgen = { version = "0.2", optional = true } wasm-bindgen-futures = { version = "0.4", optional = true } js-sys = { version = "0.3", optional = true } +wee_alloc = {version = "0.4"} [target.'cfg(target_arch="wasm32")'.dependencies.web-sys] version = "0.3" diff --git a/ucan-key-support/demo/.gitignore b/ucan-key-support/demo/.gitignore new file mode 100644 index 00000000..93a464bb --- /dev/null +++ b/ucan-key-support/demo/.gitignore @@ -0,0 +1 @@ +static/ \ No newline at end of file diff --git a/ucan-key-support/demo/build.sh b/ucan-key-support/demo/build.sh new file mode 100755 index 00000000..c589ae30 --- /dev/null +++ b/ucan-key-support/demo/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cargo build --release --target="wasm32-unknown-unknown" --features=web + +wasm-bindgen \ + --target web \ + --out-dir static \ + ../../target/wasm32-unknown-unknown/release/ucan_key_support.wasm diff --git a/ucan-key-support/demo/index.html b/ucan-key-support/demo/index.html new file mode 100644 index 00000000..45fc5231 --- /dev/null +++ b/ucan-key-support/demo/index.html @@ -0,0 +1,63 @@ + + + + UCANs on the Web - Powered by rs-ucan + + + + + + +

+  
+
diff --git a/ucan-key-support/src/lib.rs b/ucan-key-support/src/lib.rs
index 78c0e226..333ca98d 100644
--- a/ucan-key-support/src/lib.rs
+++ b/ucan-key-support/src/lib.rs
@@ -4,5 +4,9 @@ extern crate log;
 #[cfg(all(target_arch = "wasm32", feature = "web"))]
 pub mod web_crypto;
 
+#[cfg(all(target_arch = "wasm32", feature = "web"))]
+#[global_allocator]
+static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
+
 pub mod ed25519;
 pub mod rsa;
diff --git a/ucan-key-support/src/rsa.rs b/ucan-key-support/src/rsa.rs
index 39172c3a..9bb2a741 100644
--- a/ucan-key-support/src/rsa.rs
+++ b/ucan-key-support/src/rsa.rs
@@ -1,11 +1,9 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 
-use rsa::{
-    Hash, PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey,
-};
-use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
 use rsa::pkcs1::der::{Document, Encodable};
+use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
+use rsa::{Hash, PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey};
 
 use sha2::{Digest, Sha256};
 use ucan::crypto::KeyMaterial;
diff --git a/ucan-key-support/src/web_crypto.rs b/ucan-key-support/src/web_crypto.rs
index 3b482104..eb265c44 100644
--- a/ucan-key-support/src/web_crypto.rs
+++ b/ucan-key-support/src/web_crypto.rs
@@ -1,13 +1,14 @@
 use crate::rsa::{RsaKeyMaterial, RSA_ALGORITHM};
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
-use js_sys::{Array, ArrayBuffer, Boolean, Object, Reflect, Uint8Array};
-use rsa::RsaPublicKey;
-use rsa::pkcs1::DecodeRsaPublicKey;
+use js_sys::{Array, ArrayBuffer, Boolean, Object, Promise, Reflect, Uint8Array};
 use rsa::pkcs1::der::Encodable;
+use rsa::pkcs1::DecodeRsaPublicKey;
+use rsa::RsaPublicKey;
 use ucan::crypto::KeyMaterial;
-use wasm_bindgen::{JsCast, JsValue};
-use wasm_bindgen_futures::JsFuture;
+use wasm_bindgen::prelude::wasm_bindgen;
+use wasm_bindgen::{JsCast, JsError, JsValue};
+use wasm_bindgen_futures::{future_to_promise, JsFuture};
 use web_sys::{Crypto, CryptoKey, CryptoKeyPair, SubtleCrypto};
 
 pub fn convert_spki_to_rsa_public_key(spki_bytes: &[u8]) -> Result> {
@@ -18,8 +19,30 @@ pub fn convert_spki_to_rsa_public_key(spki_bytes: &[u8]) -> Result> {
 }
 
 #[derive(Debug)]
-pub struct WebCryptoRsaKeyMaterial(pub CryptoKey, pub Option);
+pub struct WasmError(anyhow::Error);
+
+impl From for JsValue {
+    fn from(err: WasmError) -> JsValue {
+        JsError::new(&format!("{:?}", err)).into()
+    }
+}
+
+impl From for WasmError {
+    fn from(err: anyhow::Error) -> Self {
+        Self(err)
+    }
+}
+
+type WasmResult = std::result::Result;
+
+#[derive(Clone)]
+#[wasm_bindgen]
+pub struct WebCryptoRsaKeyMaterial {
+    public_key: CryptoKey,
+    private_key: Option,
+}
 
+#[wasm_bindgen]
 impl WebCryptoRsaKeyMaterial {
     fn get_subtle_crypto() -> Result {
         // NOTE: Accessing either `Window` or `DedicatedWorkerGlobalScope` in
@@ -33,13 +56,14 @@ impl WebCryptoRsaKeyMaterial {
     }
 
     fn private_key(&self) -> Result<&CryptoKey> {
-        match &self.1 {
+        match &self.private_key {
             Some(key) => Ok(key),
             None => Err(anyhow!("No private key configured")),
         }
     }
 
-    pub async fn generate(key_size: Option) -> Result {
+    #[wasm_bindgen]
+    pub async fn generate(key_size: Option) -> WasmResult {
         let subtle_crypto = Self::get_subtle_crypto()?;
         let algorithm = Object::new();
 
@@ -98,7 +122,53 @@ impl WebCryptoRsaKeyMaterial {
                 .map_err(|error| anyhow!("{:?}", error))?,
         );
 
-        Ok(WebCryptoRsaKeyMaterial(public_key, Some(private_key)))
+        Ok(WebCryptoRsaKeyMaterial {
+            public_key,
+            private_key: Some(private_key),
+        })
+    }
+
+    #[wasm_bindgen(js_name = "getDid")]
+    pub fn wasm_get_did(&self) -> WasmResult {
+        let me = self.clone();
+
+        Ok(future_to_promise(async move {
+            let did = me.get_did().await.map_err(|err| WasmError::from(err))?;
+            Ok(JsValue::from_str(&did))
+        }))
+    }
+
+    #[wasm_bindgen(js_name = "sign")]
+    pub fn wasm_sign(&self, payload: &[u8]) -> WasmResult {
+        let me = self.clone();
+        let payload = payload.to_vec();
+
+        Ok(future_to_promise(async move {
+            let res = me
+                .sign(&payload)
+                .await
+                .map_err(|err| WasmError::from(err))?;
+            Ok(JsValue::from(Uint8Array::from(res.as_slice())))
+        }))
+    }
+
+    #[wasm_bindgen(js_name = "verify")]
+    pub fn wasm_verify(&self, payload: &[u8], signature: &[u8]) -> WasmResult {
+        let me = self.clone();
+        let payload = payload.to_vec();
+        let signature = signature.to_vec();
+
+        Ok(future_to_promise(async move {
+            me.verify(&payload, &signature)
+                .await
+                .map_err(|err| WasmError::from(err))?;
+            Ok(JsValue::UNDEFINED)
+        }))
+    }
+
+    #[wasm_bindgen(js_name = "jwtAlgorithm")]
+    pub fn wasm_jwt_algorithm(&self) -> String {
+        self.get_jwt_algorithm_name()
     }
 }
 
@@ -109,7 +179,7 @@ impl KeyMaterial for WebCryptoRsaKeyMaterial {
     }
 
     async fn get_did(&self) -> Result {
-        let public_key = &self.0;
+        let public_key = &self.public_key;
         let subtle_crypto = Self::get_subtle_crypto()?;
 
         let public_key_bytes = Uint8Array::new(
@@ -168,7 +238,7 @@ impl KeyMaterial for WebCryptoRsaKeyMaterial {
     }
 
     async fn verify(&self, payload: &[u8], signature: &[u8]) -> Result<()> {
-        let key = &self.0;
+        let key = &self.public_key;
         let subtle_crypto = Self::get_subtle_crypto()?;
         let algorithm = Object::new();
 

From 674d8d12d4c9dda6856d67fd6dbf349a8e3e124a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabrice=20Desr=C3=A9?= 
Date: Thu, 25 Aug 2022 11:09:06 -0700
Subject: [PATCH 2/3] WIP: UcanBuilder and friends Unfortunately wasm-bindgen
 can't expose structs with lifetimes, so this commit removes the lifetime on
 UcanBuilder. This requires an additional Clone bound added to KeyMaterial,
 breaking non-wasm builds.

---
 ucan-key-support/Cargo.toml        |  36 ++---
 ucan-key-support/demo/index.html   |  35 ++--
 ucan-key-support/src/web_crypto.rs | 246 ++++++++++++++++++++++++++++-
 ucan/src/builder.rs                |  30 ++--
 4 files changed, 303 insertions(+), 44 deletions(-)

diff --git a/ucan-key-support/Cargo.toml b/ucan-key-support/Cargo.toml
index 1e095214..2d558b23 100644
--- a/ucan-key-support/Cargo.toml
+++ b/ucan-key-support/Cargo.toml
@@ -1,19 +1,19 @@
 [package]
-name = "ucan-key-support"
-description = "Ready to use SigningKey implementations for the ucan crate"
-edition = "2021"
-keywords = ["ucan", "authz", "jwt", "pki"]
 categories = [
   "authorization",
   "cryptography",
   "encoding",
-  "web-programming"
+  "web-programming",
 ]
+description = "Ready to use SigningKey implementations for the ucan crate"
 documentation = "https://docs.rs/ucan"
-repository = "https://github.com/cdata/rs-ucan/"
+edition = "2021"
 homepage = "https://github.com/cdata/rs-ucan"
+keywords = ["ucan", "authz", "jwt", "pki"]
 license = "Apache-2.0"
+name = "ucan-key-support"
 readme = "README.md"
+repository = "https://github.com/cdata/rs-ucan/"
 version = "0.4.0-alpha.1"
 
 [lib]
@@ -24,14 +24,14 @@ default = []
 web = ["wasm-bindgen", "wasm-bindgen-futures", "js-sys", "web-sys", "ucan/web", "getrandom/js"]
 
 [dependencies]
-ucan = {path = "../ucan", version = "0.6.0-alpha.1" }
 anyhow = "1.0.52"
 async-trait = "0.1.52"
+bs58 = "0.4"
 ed25519-zebra = "^3"
+log = "0.4"
 rsa = "0.6"
 sha2 = "0.10"
-bs58 = "0.4"
-log = "0.4"
+ucan = {path = "../ucan", version = "0.6.0-alpha.1"}
 
 [build-dependencies]
 npm_rs = "0.2.1"
@@ -39,27 +39,27 @@ npm_rs = "0.2.1"
 [dev-dependencies]
 rand = "0.8"
 # NOTE: This is needed so that rand can be included in WASM builds
-getrandom = { version = "0.2.5", features = ["js"] }
+getrandom = {version = "0.2.5", features = ["js"]}
+tokio = {version = "^1", features = ["macros", "rt"]}
 wasm-bindgen-test = "0.3"
-tokio = { version = "^1", features = ["macros", "rt"] }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
-wasm-bindgen = { version = "0.2", optional = true }
-wasm-bindgen-futures = { version = "0.4", optional = true }
-js-sys = { version = "0.3", optional = true }
+js-sys = {version = "0.3", optional = true}
+wasm-bindgen = {version = "0.2", features = ["serde-serialize"], optional = true}
+wasm-bindgen-futures = {version = "0.4", optional = true}
 wee_alloc = {version = "0.4"}
 
 [target.'cfg(target_arch="wasm32")'.dependencies.web-sys]
-version = "0.3"
-optional = true
 features = [
   'Window',
   'SubtleCrypto',
   'Crypto',
   'CryptoKey',
   'CryptoKeyPair',
-  'DedicatedWorkerGlobalScope'
+  'DedicatedWorkerGlobalScope',
 ]
+optional = true
+version = "0.3"
 
 [target.'cfg(target_arch="wasm32")'.dev-dependencies]
-pollster = "0.2.5"
\ No newline at end of file
+pollster = "0.2.5"
diff --git a/ucan-key-support/demo/index.html b/ucan-key-support/demo/index.html
index 45fc5231..538ec488 100644
--- a/ucan-key-support/demo/index.html
+++ b/ucan-key-support/demo/index.html
@@ -5,7 +5,9 @@