Skip to content

feat(WASM): add nodejs webcrypto provider through feature gate #189

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions mls-rs-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ exclude = ["test_data"]


[features]
node = []
default = ["std", "rfc_compliant", "fast_serialize"]
arbitrary = ["std", "dep:arbitrary"]
fast_serialize = ["mls-rs-codec/preallocate"]
Expand Down
3 changes: 3 additions & 0 deletions mls-rs-core/js/es-time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function date_now() {
return Date.now();
}
4 changes: 4 additions & 0 deletions mls-rs-core/js/node-time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

module.exports.date_now = function() {
return Date.now();
};
13 changes: 8 additions & 5 deletions mls-rs-core/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ impl From<u64> for MlsTime {
}
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(inline_js = r#"
export function date_now() {
return Date.now();
}"#)]
#[cfg_attr(
all(target_arch = "wasm32", not(feature = "node")),
wasm_bindgen(module = "/js/es-time.js")
)]
#[cfg_attr(
all(target_arch = "wasm32", feature = "node"),
wasm_bindgen(module = "/js/node-time.js")
)]
extern "C" {
fn date_now() -> f64;
}
Expand Down
4 changes: 4 additions & 0 deletions mls-rs-crypto-webcrypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ repository = "https://github.com/awslabs/mls-rs"
keywords = ["mls", "mls-rs"]
license = "Apache-2.0 OR MIT"

[features]
node = []

[dependencies]
cfg-if = "1.0.0"
mls-rs-core = { path = "../mls-rs-core", default-features = false, features = ["std"], version = "0.19.0" }
mls-rs-crypto-hpke = { path = "../mls-rs-crypto-hpke", default-features = false, features = ["std"], version = "0.10.0" }
mls-rs-crypto-traits = { path = "../mls-rs-crypto-traits", default-features = false, features = ["std"], version = "0.11.0" }
Expand Down
5 changes: 5 additions & 0 deletions mls-rs-crypto-webcrypto/js/node-crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const crypto = require("node:crypto").webcrypto;

module.exports.node_crypto = function() {
return crypto;
};
23 changes: 19 additions & 4 deletions mls-rs-crypto-webcrypto/src/key_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright by contributors to this project.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use js_sys::{Array, Reflect, Uint8Array};
use js_sys::{Array, Reflect, Uint8Array, Promise};
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::{CryptoKey, CryptoKeyPair, EcKeyImportParams, HmacImportParams, SubtleCrypto};
Expand Down Expand Up @@ -54,6 +54,22 @@ impl KeyType {
}
}

#[cfg(not(feature = "node"))]
async fn import_with_public_info(&self, crypto: &SubtleCrypto, key: &Uint8Array, params: &EcKeyImportParams, key_usages: &Array) -> Result<Promise, JsValue> {
crypto.import_key_with_object(self.format(), &key, &params, true, &key_usages)
}

#[cfg(feature = "node")]
async fn import_with_public_info(&self, crypto: &SubtleCrypto, bytes: &Uint8Array, params: &EcKeyImportParams, key_usages: &Array) -> Result<Promise, JsValue> {
let crypto_key_promise = crypto.import_key_with_object(self.format(), bytes, params, true, key_usages)?;
let crypto_key = JsFuture::from(crypto_key_promise).await?.into();
// Export the key to jwk to force the generation of the public key.
let jwk_promise = crypto.export_key(&"jwk", &crypto_key)?;
let jwk = JsFuture::from(jwk_promise).await?;
// Re-import into the original requested format with the same usages from jwk.
crypto.import_key_with_object("jwk", &jwk.into(), params, true, &key_usages)
}

pub(crate) async fn import(
&self,
crypto: &SubtleCrypto,
Expand Down Expand Up @@ -87,9 +103,8 @@ impl KeyType {
| KeyType::EcdsaPublic(curve)
| KeyType::EcdsaSecret(curve) => {
let mut params = EcKeyImportParams::new(self.algorithm());
params.named_curve(curve);

crypto.import_key_with_object(self.format(), &key, &params, true, &key_usages)?
params.set_named_curve(curve);
self.import_with_public_info(&crypto, &key, &params, &key_usages).await?
}
};

Expand Down
47 changes: 36 additions & 11 deletions mls-rs-crypto-webcrypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod key_type;

use mls_rs_core::{
crypto::{
CipherSuite, CipherSuiteProvider, CryptoProvider, HpkeCiphertext, HpkePublicKey,
self, CipherSuite, CipherSuiteProvider, CryptoProvider, HpkeCiphertext, HpkePublicKey,
HpkeSecretKey, SignaturePublicKey, SignatureSecretKey,
},
error::{AnyError, IntoAnyError},
Expand All @@ -25,6 +25,8 @@ use mls_rs_crypto_hpke::{

use mls_rs_crypto_traits::{AeadType, KdfType, KemId};

use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use web_sys::SubtleCrypto;
use zeroize::Zeroizing;
Expand All @@ -34,6 +36,7 @@ use crate::{
ec::{EcSigner, Ecdh},
hkdf::Hkdf,
};
use web_sys::Crypto;

#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
Expand Down Expand Up @@ -61,12 +64,37 @@ impl From<JsValue> for CryptoError {
}
}

#[inline]
pub(crate) fn get_crypto() -> Result<SubtleCrypto, CryptoError> {
Ok(web_sys::window()
.ok_or(CryptoError::WindowNotFound)?
.crypto()?
.subtle())
cfg_if! {
if #[cfg(feature = "node")] {
#[wasm_bindgen(module = "/js/node-crypto.js")]
extern "C" {
#[wasm_bindgen]
fn node_crypto() -> Crypto;
}

#[inline]
pub(crate) fn crypto() -> Result<Crypto, CryptoError> {
Ok(node_crypto())
}

#[inline]
pub(crate) fn get_crypto() -> Result<SubtleCrypto, CryptoError> {
Ok(node_crypto()
.subtle())
}
} else {
#[inline]
pub(crate) fn crypto() -> Result<Crypto, CryptoError> {
Ok(web_sys::window()
.ok_or(CryptoError::WindowNotFound)?
.crypto()?)
}

#[inline]
pub(crate) fn get_crypto() -> Result<SubtleCrypto, CryptoError> {
crypto().map(|c| c.subtle())
}
}
}

#[derive(Clone, Default, Debug)]
Expand Down Expand Up @@ -271,10 +299,7 @@ impl CipherSuiteProvider for WebCryptoCipherSuite {
}

fn random_bytes(&self, out: &mut [u8]) -> Result<(), Self::Error> {
web_sys::window()
.ok_or(CryptoError::WindowNotFound)?
.crypto()?
.get_random_values_with_u8_array(out)?;
crypto()?.get_random_values_with_u8_array(out)?;

Ok(())
}
Expand Down
Loading