ring icon indicating copy to clipboard operation
ring copied to clipboard

Which features work with WASM (WebAssembly)?

Open rbalean opened this issue 5 years ago • 25 comments

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?

rbalean avatar Dec 02 '19 11:12 rbalean

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.)

andrewtj avatar Dec 03 '19 23:12 andrewtj

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

briansmith avatar Dec 23 '19 20:12 briansmith

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.

semaj avatar Dec 24 '19 16:12 semaj

@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?

callaars avatar Jan 28 '20 15:01 callaars

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.

briansmith avatar Jan 28 '20 16:01 briansmith

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?

callaars avatar Jan 28 '20 16:01 callaars

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.

briansmith avatar Jan 28 '20 16:01 briansmith

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?

semaj avatar Jan 28 '20 19:01 semaj

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

callaars avatar Jan 28 '20 19:01 callaars

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 avatar Jan 29 '20 16:01 briansmith

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.

semaj avatar Feb 03 '20 01:02 semaj

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.

tmikaeld avatar Mar 07 '20 07:03 tmikaeld

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.

D1plo1d avatar Apr 10 '20 15:04 D1plo1d

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 avatar Apr 29 '20 21:04 briansmith

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 avatar May 27 '20 18:05 briansmith

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

briansmith avatar May 29 '20 22:05 briansmith

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?

calderonth avatar Jun 08 '20 13:06 calderonth

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.

briansmith avatar Jun 08 '20 17:06 briansmith

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.

calderonth avatar Jun 08 '20 18:06 calderonth

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.

acmiyaguchi avatar Jan 23 '21 01:01 acmiyaguchi

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.

inferiorhumanorgans avatar Jun 27 '21 05:06 inferiorhumanorgans

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.

cryslith avatar Sep 14 '21 22:09 cryslith

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

goodidea-kp avatar Nov 05 '21 20:11 goodidea-kp

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

rhomber avatar Apr 26 '22 08:04 rhomber

@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

DCNick3 avatar Aug 27 '22 21:08 DCNick3

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

cedricschwyter avatar Oct 24 '22 16:10 cedricschwyter

@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: https://github.com/briansmith/ring/issues/1453#issuecomment-1296007898

paulyoung avatar Oct 30 '22 01:10 paulyoung

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

briansmith avatar Nov 09 '22 20:11 briansmith

AFAICT the entire feature set works in WASM now.

briansmith avatar Oct 05 '23 05:10 briansmith

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

jedisct1 avatar Oct 09 '23 19:10 jedisct1