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

Which features work with WASM (WebAssembly)? #918

Closed
rbalean opened this issue Dec 2, 2019 · 35 comments
Closed

Which features work with WASM (WebAssembly)? #918

rbalean opened this issue Dec 2, 2019 · 35 comments
Milestone

Comments

@rbalean
Copy link

rbalean commented Dec 2, 2019

I'm trying to write a WASM module which uses ECDSA in part of its communication protocol with a backend server. I was hoping to use ring for this.

Various functions from ring::signature e.g. verify, result in the line
import * as __wbg_star0 from 'env';
being generated in the .js file generated by wasm-bindgen. This prevents my browser loading the WASM module and it gives the message:
Error resolving module specifier: env

Is ECDSA sign/verify supported for WASM. If so, how do I use it?

@andrewtj
Copy link
Contributor

andrewtj commented Dec 3, 2019

The port's incomplete, and possibly in need of a sponsor. If you search through the issues (including closed) you can get a better idea of where it's at.

(I'm not associated with this project, I'm just replying as the maintainer appears to be otherwise engaged at the minute.)

@briansmith
Copy link
Owner

In which wasm platform are you trying to use ring? Browser? Node? Wasmer? Something else?

@semaj
Copy link

semaj commented Dec 24, 2019

For another datapoint, I would also love to use ring with WebAssembly, and am targeting the browser. In particular, I'm trying to use rustls, which depends on ring.

@callaars
Copy link

@briansmith I tried using it for the browser, but indeed get the same error. It seems the C files aren't implemented yet in wasm?

image

I have too little knowledge about ring to actually say what's going on. Any advice?

@briansmith
Copy link
Owner

I intend to add more functionality to wasm builds soon.

A few months ago, when I started the port, I couldn't get the wasm32 toolchain to handle some of the ECC code. It seems like the wasm32 backend of clang wasn't quite up to snuff, and/or there was a problem with the wasm32 "linker". I wouldn't be surprised if things are better now.

Before I can add more stuff here, I need to merge the latest BoringSSL changes. I'm doing that now.

@callaars
Copy link

I wish I could help with that, but I'm afraid it's outside of my expertise. Anything else I can look into to help?

@briansmith
Copy link
Owner

As I mentioned above, it would be useful to know which ring features are needed in wasm32. I probably won't have them all working all at once, but one at a time, so it would be useful to prioritize the work.

@semaj
Copy link

semaj commented Jan 28, 2020

I've had success compiling the LIMBS subdirectory to wasm using emscripten. Currently the functionality I at least know I require is montgomery multiplication. If I understand correctly, the port involves writing this assembly code in either C or Rust, correct?

@callaars
Copy link

As far as I see it's mainly the LIMBS and GFp for my usecase (using rustls).

@briansmith
Copy link
Owner

I understand that when people talk about "limbs" they mean the underlying bigint math. But, which features (ECDSA signing, ECDSA verification, RSA signing, RSA verification, ECDH, etc.) do you need? Just because something compiles and the tests seem to pass, doesn't mean that it is correct, especially w.r.t. the side-channel mitigations.

It's also useful to know which wasm32 environment people are deploying to. Just browsers? Node? Something else?

@semaj
Copy link

semaj commented Feb 3, 2020

Hey Brian, I'm mainly interested in compiling and running the QUIC protocol in both the browser and node environments. I'm planning to leverage existing QUIC implementations written in Rust. I don't know the full list of cryptographic functions QUIC uses, but if you aren't sure either, I'm happy to compile one. Thanks so much.

@tmikaeld
Copy link

tmikaeld commented Mar 7, 2020

I'm deploying to Cloudflare Workers, which should be the same as browser (Chromium).

I need RSA verification, I've implemented a full FIDO2 authentication into workers, but it's too slow with the Javascript implementation, so was looking into Rust -> WASM for that reason.

Ring seems to be the only mature library for this.

UPDATE: Specifically, ECDSA with SHA-256 (ES256) and RSASSA-PKCS1-v1_5 with SHA-256 (RS256) at minimum.

@D1plo1d
Copy link

D1plo1d commented Apr 10, 2020

I'll add my use case as well then, I'm porting a ECDH Triple Diffie–Hellman key exchange to wasm - targeting the browser. I'm using it (implemented in JS atm) for end-to-end encrypted WebRTC signalling.

@briansmith
Copy link
Owner

My plan is to do AES-GCM first, then RSA, and then make a plan for the rest. Issue #104 is tracking the AES-GCM effort.

@briansmith
Copy link
Owner

PR #992 is the next step in expanding the wasm32 functionality. It it the first step to allowing ring to use C code in WebAssembly. Going forward I intend to make it unnecessary to have a C compiler for targeting wasm32; this is a stepping stone to that.

I expect that somebody could now enable Ed25519 and X25519 support on top of PR #992, guarded by the wasm32_c feature and tested in the same way as demonstrated for HMAC and PBKDF2 in PR #992.

@briansmith
Copy link
Owner

RSA signatures (signing and verifying) is in PR #996.

@briansmith briansmith changed the title Which features work with WASM? Which features work with WASM (WebAssembly)? May 30, 2020
@calderonth
Copy link

Since #992 and #996 have been done it's closer to potentially work in WASM, however it's quite hard to test the current progress for Webassembly.
For example, I'm trying to use Keats/jsonwebtoken that depends on Ring and even with the latest changes, I have the following symbols that are problematic:

  (import "env" "GFp_nistz256_point_mul_base" (func $env.GFp_nistz256_point_mul_base (type $t1)))
  (import "env" "GFp_p256_scalar_mul_mont" (func $env.GFp_p256_scalar_mul_mont (type $t0)))
  (import "env" "GFp_p256_scalar_sqr_mont" (func $env.GFp_p256_scalar_sqr_mont (type $t1)))
  (import "env" "GFp_p256_scalar_sqr_rep_mont" (func $env.GFp_p256_scalar_sqr_rep_mont (type $t0)))
  (import "env" "GFp_p384_elem_mul_mont" (func $env.GFp_p384_elem_mul_mont (type $t0)))
  (import "env" "GFp_nistz384_point_mul" (func $env.GFp_nistz384_point_mul (type $t4)))
  (import "env" "GFp_p384_scalar_mul_mont" (func $env.GFp_p384_scalar_mul_mont (type $t0)))
  (import "env" "GFp_nistz256_add" (func $env.GFp_nistz256_add (type $t0)))
  (import "env" "GFp_nistz256_mul_mont" (func $env.GFp_nistz256_mul_mont (type $t0)))
  (import "env" "GFp_nistz256_point_add" (func $env.GFp_nistz256_point_add (type $t0)))
  (import "env" "GFp_nistz256_point_mul" (func $env.GFp_nistz256_point_mul (type $t4)))
  (import "env" "GFp_p384_elem_add" (func $env.GFp_p384_elem_add (type $t0)))
  (import "env" "GFp_nistz384_point_add" (func $env.GFp_nistz384_point_add (type $t0)))

Is there a way to disable EC at compile-time from Ring so that I can test the JWT RSA code path within WebAssembly?

@briansmith
Copy link
Owner

My plan is to implement those functions in a way that works on all targets, including WebAssembly. I don't intend to spend effort to work around the lack of the functions in the interim because I don't have time to do one and then the other.

 (import "env" "GFp_nistz256_point_mul_base" (func $env.GFp_nistz256_point_mul_base (type $t1)))
 (import "env" "GFp_p256_scalar_mul_mont" (func $env.GFp_p256_scalar_mul_mont (type $t0)))
 (import "env" "GFp_p256_scalar_sqr_mont" (func $env.GFp_p256_scalar_sqr_mont (type $t1)))

What tools were you using to get this output? I usually target browsers for webassembly and I don't get nearly as useful error messages; I just get some vague error about "env" that doesn't list the missing symbols.

@calderonth
Copy link

calderonth commented Jun 8, 2020

Thanks that makes sense.

To get that output I used wasm2wat which can also be used online here https://webassembly.github.io/wabt/demo/wasm2wat/.

The trace was obtained by running wasm2wat on the result of wasm-pack build on my code.

@acmiyaguchi
Copy link

For reference, here's how to go about testing whether a particular module works in wasm via wasm-bindgen-test. I have particular interest in the key agreement module, so here's a patch that enables a single, failing browser test.

From 8cddf22bc0b2d9227dc65e8075118534071a81ec Mon Sep 17 00:00:00 2001
From: Anthony Miyaguchi <[email protected]>
Date: Fri, 22 Jan 2021 16:20:30 -0800
Subject: [PATCH] Enable failing agreement tests (dependent on ecdsa)

---
 build.rs                 | 9 +++++----
 tests/agreement_tests.rs | 8 ++++++++
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/build.rs b/build.rs
index f2db9a841..5517dcded 100644
--- a/build.rs
+++ b/build.rs
@@ -45,10 +45,11 @@ const RING_SRCS: &[(&[&str], &str)] = &[
 
     (&[AARCH64, ARM, X86_64, X86], "crypto/crypto.c"),
     (&[AARCH64, ARM, X86_64, X86], "crypto/curve25519/curve25519.c"),
-    (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/ecp_nistz.c"),
-    (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/ecp_nistz256.c"),
-    (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/gfp_p256.c"),
-    (&[AARCH64, ARM, X86_64, X86], "crypto/fipsmodule/ec/gfp_p384.c"),
+    (&[], "crypto/curve25519/curve25519.c"),
+    (&[], "crypto/fipsmodule/ec/ecp_nistz.c"),
+    (&[], "crypto/fipsmodule/ec/ecp_nistz256.c"),
+    (&[], "crypto/fipsmodule/ec/gfp_p256.c"),
+    (&[], "crypto/fipsmodule/ec/gfp_p384.c"),
 
     (&[X86_64, X86], "crypto/cpu-intel.c"),
 
diff --git a/tests/agreement_tests.rs b/tests/agreement_tests.rs
index 416201537..ae7891802 100644
--- a/tests/agreement_tests.rs
+++ b/tests/agreement_tests.rs
@@ -16,7 +16,15 @@ extern crate alloc;
 
 use ring::{agreement, error, rand, test, test_file};
 
+#[cfg(target_arch = "wasm32")]
+use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
+
+#[cfg(target_arch = "wasm32")]
+wasm_bindgen_test_configure!(run_in_browser);
+
 #[test]
+#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+#[cfg(any(not(target_arch = "wasm32"), feature = "wasm32_c"))]
 fn agreement_traits() {
     use alloc::vec::Vec;
 
-- 
2.30.0

Run the tests using wasm-pack with the feature gate enabled for the C modules.

% wasm-pack test --firefox --headless --  --features wasm32_c --test agreement_tests   

This fails with the following error:

JavaScript error: http://127.0.0.1:50603/wasm-bindgen-test, line 1: TypeError: Error resolving module specifier “env”. Relative module specifiers must start with “./”, “../” or “/”.

Find the path to the built wasm module from the test output and inspect the imports, which match with most of the other comments here:

% wasm2wat target/wasm32-unknown-unknown/debug/deps/agreement_tests-0e45a4224d6f1446.wasm | grep 'import "env"'

  (import "env" "GFp_nistz256_add" (func $GFp_nistz256_add (type 6)))
  (import "env" "GFp_nistz256_mul_mont" (func $GFp_nistz256_mul_mont (type 6)))
  (import "env" "GFp_nistz256_sqr_mont" (func $GFp_nistz256_sqr_mont (type 5)))
  (import "env" "GFp_nistz256_point_double" (func $GFp_nistz256_point_double (type 5)))
  (import "env" "GFp_nistz256_neg" (func $GFp_nistz256_neg (type 5)))
  (import "env" "GFp_bn_mul_mont" (func $GFp_bn_mul_mont (type 19)))

These files only exist in generated assembly, so a cross-architecture implementation of these functions would solve most of the issues here.

@inferiorhumanorgans
Copy link

inferiorhumanorgans commented Jun 27, 2021

I understand that when people talk about "limbs" they mean the underlying bigint math. But, which features (ECDSA signing, ECDSA verification, RSA signing, RSA verification, ECDH, etc.) do you need? Just because something compiles and the tests seem to pass, doesn't mean that it is correct, especially w.r.t. the side-channel mitigations.

It's also useful to know which wasm32 environment people are deploying to. Just browsers? Node? Something else?

@briansmith My use case is verifying the ECDSA signature on a JWT with jsonwebtoken in a browser. The tokens come from an issuer that mandates use of ECDSA.

@cryslith
Copy link

cryslith commented Sep 14, 2021

For another use case, I'm trying to receive an AEAD-sealed message on a browser. (For technical reasons I can't use the built-in Crypto.subtle web API for this, so I'm using ring.) I'm currently using AES-256-GCM since my understanding from #104 was that a pure Rust implementation is available which would work in WASM.

Building for the web target and inspecting the imports using wasm2wat as suggested above, I find the following problematic imports:

  (import "env" "GFp_memcmp" (func $env.GFp_memcmp (type $t1)))
  (import "env" "GFp_aes_nohw_encrypt" (func $env.GFp_aes_nohw_encrypt (type $t4)))
  (import "env" "GFp_aes_nohw_set_encrypt_key" (func $env.GFp_aes_nohw_set_encrypt_key (type $t1)))
  (import "env" "GFp_aes_nohw_ctr32_encrypt_blocks" (func $env.GFp_aes_nohw_ctr32_encrypt_blocks (type $t9)))

Edit: I misunderstood the status of the implementation; AES support still requires the wasm_c feature.

@goodidea-kp
Copy link

goodidea-kp commented Nov 5, 2021

Adding this line:let client_config=rustls::client::ClientConfig::builder() .with_safe_defaults() .with_root_certificates(rcs).with_no_client_auth();
gives me an error on page rendering: Uncaught TypeError: Error resolving module specifier “env”. Relative module specifiers must start with “./”, “../” or “/”. However compilation is success:Nov 05 15:23:28.779 INFO fetching cargo artifacts Nov 05 15:23:28.884 INFO processing WASM Nov 05 15:23:28.896 INFO using system installed binary app="wasm-bindgen" version="0.2.78" Nov 05 15:23:28.896 INFO calling wasm-bindgen Nov 05 15:23:29.127 INFO copying generated wasm-bindgen artifacts Nov 05 15:23:29.130 INFO applying new distribution Nov 05 15:23:29.131 INFO ✅ success

@rhomber
Copy link

rhomber commented Apr 26, 2022

I am testing "0.17.0-alpha.11" which appears to have far less exports but still produces some. My wasm environment requires no exports other than the custom defined ones (none useful to ring). It would be really nice to have a feature flag to disable all wasm exports.

The changes I needed to make to get it working:

  1. Remove this from Cargo.toml

`[target.'cfg(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown", target_env = ""))'.dependencies]
web-sys = { version = "0.3.37", default-features = false, features = ["Crypto", "Window"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = { version = "0.3.18", default-features = false }
`
2. Modify src/rand.rs and add "unimplemented!()" to "sysrand_chunk" vs. trying to use window.crypto

@DCNick3
Copy link

DCNick3 commented Aug 27, 2022

@briansmith My use case is verifying the ECDSA signature on a JWT with jsonwebtoken in a browser. The tokens come from an issuer that mandates use of ECDSA.

It seems that for now you can use jwt-compact crate for using JWT in WASM. It is explicitly tested for WASM compatiblity

@cedricschwyter
Copy link

What about aead functionality, how is web assembly support looking there?

@paulyoung
Copy link

@rhomber is there anything you can share about how you got things to work?

I'm running into errors like Module imports function 'LIMBS_are_zero' from 'env' that is not exported by the runtime for extern "C" functions.

More info here: #1453 (comment)

@briansmith
Copy link
Owner

This mostly depends on resolving #1455 and then expanding the test coverage.

@briansmith
Copy link
Owner

AFAICT the entire feature set works in WASM now.

@briansmith briansmith added this to the 0.17.0 milestone Oct 6, 2023
@jedisct1
Copy link

jedisct1 commented Oct 9, 2023

Yep, compiles fine with cargo zigbuild --target=wasm32-wasi 🎉

@briansmith briansmith modified the milestones: 0.17.0, 0.17.4 Oct 14, 2023
@briansmith
Copy link
Owner

#1746 enabled the ring::agreement for wasm32-* targets. #1745 enabled the tests for wasm32-wasi.

@jyrihogman
Copy link

jyrihogman commented Mar 5, 2024

Any news on this? Trying to use ring on CF Workers fails with the build. Trying to build the repo with cargo install -q worker-build && worker-build --release fails due to:

Error: crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml file:

[lib]
crate-type = ["cdylib", "rlib"]
Caused by: crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml

Adding the crate-type and you'd get an error from getrandom:

error: the wasm*-unknown-unknown targets are not supported by default, you may need to enable the "js" feature. For more information see: https://docs.rs/getrandom/#webassembly-support
   --> /Users/jyri.hogman/.cargo/registry/src/index.crates.io-6f17d22bba15001f/getrandom-0.2.12/src/lib.rs:291:9
    |
291 | /         compile_error!("the wasm*-unknown-unknown targets are not supported by \
292 | |                         default, you may need to enable the \"js\" feature. \
293 | |                         For more information see: \
294 | |                         https://docs.rs/getrandom/#webassembly-support");
    | |________________________________________________________________________^

error[E0433]: failed to resolve: use of undeclared crate or module `imp`
   --> /Users/jyri.hogman/.cargo/registry/src/index.crates.io-6f17d22bba15001f/getrandom-0.2.12/src/lib.rs:347:9
    |
347 |         imp::getrandom_inner(dest)?;
    |         ^^^ use of undeclared crate or module `imp`

Finally explicitly enabling that feature when declaring the dependency, the error:

warning: [email protected]: error: unable to create target: 'No available targets are compatible with triple "wasm32-unknown-unknown"'
warning: [email protected]: 1 error generated.

error: failed to run custom build command for `ring v0.17.8 (/Users/jyri.hogman/personal/ring)`

Caused by:
  process didn't exit successfully: `/Users/jyri.hogman/personal/ring/target/release/build/ring-ebf672175ab95db3/build-script-build` (exit status: 1)
  --- stdout
  cargo:rerun-if-env-changed=CARGO_MANIFEST_DIR
  cargo:rerun-if-env-changed=CARGO_PKG_NAME
  cargo:rerun-if-env-changed=CARGO_PKG_VERSION_MAJOR
  cargo:rerun-if-env-changed=CARGO_PKG_VERSION_MINOR
  cargo:rerun-if-env-changed=CARGO_PKG_VERSION_PATCH
  cargo:rerun-if-env-changed=CARGO_PKG_VERSION_PRE
  cargo:rerun-if-env-changed=CARGO_MANIFEST_LINKS
  cargo:rerun-if-env-changed=RING_PREGENERATE_ASM
  cargo:rerun-if-env-changed=OUT_DIR
  cargo:rerun-if-env-changed=CARGO_CFG_TARGET_ARCH
  cargo:rerun-if-env-changed=CARGO_CFG_TARGET_OS
  cargo:rerun-if-env-changed=CARGO_CFG_TARGET_ENV
  cargo:rerun-if-env-changed=DEBUG
  cargo:rerun-if-env-changed=PERL_EXECUTABLE
  OPT_LEVEL = Some("3")
  TARGET = Some("wasm32-unknown-unknown")
  HOST = Some("x86_64-apple-darwin")
  cargo:rerun-if-env-changed=CC_wasm32-unknown-unknown
  CC_wasm32-unknown-unknown = None
  cargo:rerun-if-env-changed=CC_wasm32_unknown_unknown
  CC_wasm32_unknown_unknown = None
  cargo:rerun-if-env-changed=TARGET_CC
  TARGET_CC = None
  cargo:rerun-if-env-changed=CC
  CC = None
  cargo:rerun-if-env-changed=CRATE_CC_NO_DEFAULTS
  CRATE_CC_NO_DEFAULTS = None
  DEBUG = Some("false")
  cargo:rerun-if-env-changed=CFLAGS_wasm32-unknown-unknown
  CFLAGS_wasm32-unknown-unknown = None
  cargo:rerun-if-env-changed=CFLAGS_wasm32_unknown_unknown
  CFLAGS_wasm32_unknown_unknown = None
  cargo:rerun-if-env-changed=TARGET_CFLAGS
  TARGET_CFLAGS = None
  cargo:rerun-if-env-changed=CFLAGS
  CFLAGS = None
  cargo:warning=error: unable to create target: 'No available targets are compatible with triple "wasm32-unknown-unknown"'
  cargo:warning=1 error generated.

@briansmith
Copy link
Owner

@jyrihogman I think you need to set TARGET_CC or equivalent to point to a clang that supports wasm32. On macOS, you can't use Apple Clang; you must use LLVM clang.

@Mubelotix
Copy link

If you are encountering failed ring builds and getrandom asking you to enable the js feature, you juste have to enable wasm32_unknown_unknown_js on ring itself

@briansmith
Copy link
Owner

In your own project's Cargo.toml, you can add this if you are targeting web environments (web pages, browser extensions, etc.):

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
ring = { version = "...", features = ["wasm32_unknown_unknown_js"]

or for non-web situations, you can use the "custom" feature:

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
ring = { version = "...", features = ["less-safe-getrandom-custom-or-rdrand"] }

Putting this in your project's top-level Cargo.toml will work even for your dependencies, so you don't need to fork Rustls or other ring-based projects to enable the features. Cargo decides which features to enable at the time it builds ring by taking the union of all activated features in all relevant Cargo.toml files in the build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests