From fad53d3345fc5117a6f83f8f59f82e9245759979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 4 Dec 2024 21:00:20 +0300 Subject: [PATCH 01/17] wasm_js: remove TLS --- src/backends/wasm_js.rs | 134 ++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 15a4e66bb..b66ce0380 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -4,6 +4,8 @@ use crate::Error; extern crate std; use std::{mem::MaybeUninit, thread_local}; +use core::sync::atomic::{AtomicU8, Ordering}; + pub use crate::util::{inner_u32, inner_u64}; #[cfg(not(all(target_arch = "wasm32", target_os = "unknown",)))] @@ -18,63 +20,89 @@ const WEB_CRYPTO_BUFFER_SIZE: u16 = 256; // Node.js's crypto.randomFillSync requires the size to be less than 2**31. const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; -enum RngSource { - Node(NodeCrypto), - Web(WebCrypto, Uint8Array), -} +const KIND_UNINIT: u8 = 0; +const KIND_WEB: u8 = 1; +const KIND_NODE: u8 = 2; +const KIND_NA: u8 = 255; -// JsValues are always per-thread, so we initialize RngSource for each thread. -// See: https://github.com/rustwasm/wasm-bindgen/pull/955 -thread_local!( - static RNG_SOURCE: Result = getrandom_init(); -); +static KIND: AtomicU8 = AtomicU8::new(KIND_UNINIT); pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - RNG_SOURCE.with(|result| { - let source = result.as_ref().map_err(|&e| e)?; - - match source { - RngSource::Node(n) => { - for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { - // SAFETY: chunk is never used directly, the memory is only - // modified via the Uint8Array view, which is passed - // directly to JavaScript. Also, crypto.randomFillSync does - // not resize the buffer. We know the length is less than - // u32::MAX because of the chunking above. - // Note that this uses the fact that JavaScript doesn't - // have a notion of "uninitialized memory", this is purely - // a Rust/C/C++ concept. - let res = n.random_fill_sync(unsafe { - Uint8Array::view_mut_raw(chunk.as_mut_ptr().cast::(), chunk.len()) - }); - if res.is_err() { - return Err(Error::NODE_RANDOM_FILL_SYNC); - } - } - } - RngSource::Web(crypto, buf) => { - // getRandomValues does not work with all types of WASM memory, - // so we initially write to browser memory to avoid exceptions. - for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE.into()) { - let chunk_len: u32 = chunk - .len() - .try_into() - .expect("chunk length is bounded by WEB_CRYPTO_BUFFER_SIZE"); - // The chunk can be smaller than buf's length, so we call to - // JS to create a smaller view of buf without allocation. - let sub_buf = buf.subarray(0, chunk_len); - - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); - } - - // SAFETY: `sub_buf`'s length is the same length as `chunk` - unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; - } + loop { + break match KIND.load(Ordering::Relaxed) { + KIND_UNINIT => { + let global: Global = global().unchecked_into(); + let crypto = global.crypto(); + let val = if crypot.is_object() { + KIND_WEB + } else if is_node(&global) { + KIND_NODE + } else { + KIND_NA + }; + KIND.store(val, Ordering::Relaxed); + continue; } + KIND_WEB => web_fill(dest), + KIND_NODE => node_fill(dest), + _ => Err(Error::WEB_CRYPTO), }; - Ok(()) - }) + } +} + +pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let global: Global = global().unchecked_into(); + let crypto = global.crypto(); + + // getRandomValues does not work with all types of WASM memory, + // so we initially write to browser memory to avoid exceptions. + let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE.into()); + for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE.into()) { + let chunk_len: u32 = chunk + .len() + .try_into() + .expect("chunk length is bounded by WEB_CRYPTO_BUFFER_SIZE"); + // The chunk can be smaller than buf's length, so we call to + // JS to create a smaller view of buf without allocation. + let sub_buf = buf.subarray(0, chunk_len); + + if crypto.get_random_values(&sub_buf).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); + } + + // SAFETY: `sub_buf`'s length is the same length as `chunk` + unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; + } + Ok(()) +} + +pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // If module.require isn't a valid function, we are in an ES module. + let require_fn = Module::require_fn() + .and_then(JsCast::dyn_into::) + .map_err(|_| Error::NODE_ES_MODULE)?; + let n = require_fn + .call1(&global, &JsValue::from_str("crypto")) + .map_err(|_| Error::NODE_CRYPTO)? + .unchecked_into(); + + for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { + // SAFETY: chunk is never used directly, the memory is only + // modified via the Uint8Array view, which is passed + // directly to JavaScript. Also, crypto.randomFillSync does + // not resize the buffer. We know the length is less than + // u32::MAX because of the chunking above. + // Note that this uses the fact that JavaScript doesn't + // have a notion of "uninitialized memory", this is purely + // a Rust/C/C++ concept. + let res = n.random_fill_sync(unsafe { + Uint8Array::view_mut_raw(chunk.as_mut_ptr().cast::(), chunk.len()) + }); + if res.is_err() { + return Err(Error::NODE_RANDOM_FILL_SYNC); + } + } + Ok(()) } fn getrandom_init() -> Result { @@ -124,8 +152,6 @@ extern "C" { // Getters for the WebCrypto API #[wasm_bindgen(method, getter)] fn crypto(this: &Global) -> WebCrypto; - #[wasm_bindgen(method, getter, js_name = msCrypto)] - fn ms_crypto(this: &Global) -> WebCrypto; // Crypto.getRandomValues() #[wasm_bindgen(method, js_name = getRandomValues, catch)] fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>; From d661ff0d3e9230050f02ad8cff665b9f850bd719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 4 Dec 2024 21:07:17 +0300 Subject: [PATCH 02/17] fix --- src/backends/wasm_js.rs | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index b66ce0380..1dd8e70f0 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -2,7 +2,7 @@ use crate::Error; extern crate std; -use std::{mem::MaybeUninit, thread_local}; +use std::mem::MaybeUninit; use core::sync::atomic::{AtomicU8, Ordering}; @@ -33,7 +33,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { KIND_UNINIT => { let global: Global = global().unchecked_into(); let crypto = global.crypto(); - let val = if crypot.is_object() { + let val = if crypto.is_object() { KIND_WEB } else if is_node(&global) { KIND_NODE @@ -76,12 +76,14 @@ pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { Ok(()) } -pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { +pub fn node_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let global: Global = global().unchecked_into(); + // If module.require isn't a valid function, we are in an ES module. let require_fn = Module::require_fn() .and_then(JsCast::dyn_into::) .map_err(|_| Error::NODE_ES_MODULE)?; - let n = require_fn + let n: NodeCrypto = require_fn .call1(&global, &JsValue::from_str("crypto")) .map_err(|_| Error::NODE_CRYPTO)? .unchecked_into(); @@ -105,31 +107,6 @@ pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { Ok(()) } -fn getrandom_init() -> Result { - let global: Global = global().unchecked_into(); - - // Get the Web Crypto interface if we are in a browser, Web Worker, Deno, - // or another environment that supports the Web Cryptography API. This - // also allows for user-provided polyfills in unsupported environments. - let crypto = global.crypto(); - if crypto.is_object() { - let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE.into()); - Ok(RngSource::Web(crypto, buf)) - } else if is_node(&global) { - // If module.require isn't a valid function, we are in an ES module. - let require_fn = Module::require_fn() - .and_then(JsCast::dyn_into::) - .map_err(|_| Error::NODE_ES_MODULE)?; - let n = require_fn - .call1(&global, &JsValue::from_str("crypto")) - .map_err(|_| Error::NODE_CRYPTO)? - .unchecked_into(); - Ok(RngSource::Node(n)) - } else { - Err(Error::WEB_CRYPTO) - } -} - // Taken from https://www.npmjs.com/package/browser-or-node fn is_node(global: &Global) -> bool { let process = global.process(); From 9fe766ca718cfb28e091997a347959ac01d07018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 4 Dec 2024 21:15:06 +0300 Subject: [PATCH 03/17] add wasm32v1-none support --- Cargo.toml | 10 +++++----- README.md | 2 +- src/backends.rs | 2 +- src/backends/wasm_js.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28b48a64c..78c8ddd24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,11 +63,11 @@ wasi = { version = "0.13", default-features = false } windows-targets = "0.52" # wasm_js -[target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", target_os = "unknown"))'.dependencies] -wasm-bindgen = { version = "0.2.89", default-features = false } -js-sys = "0.3" -[target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] -wasm-bindgen-test = "0.3.39" +[target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies] +wasm-bindgen = { version = "0.2.96", default-features = false } +js-sys = "0.3.73" +[target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dev-dependencies] +wasm-bindgen-test = "0.3" [features] # Implement std::error::Error for getrandom::Error and diff --git a/README.md b/README.md index d145c2fe1..417ab92e4 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ of randomness based on their specific needs: | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction | `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register | `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low-quality entropy without proper hardware configuration! -| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js (see [WebAssembly support]) +| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js (see [WebAssembly support]) | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend]) Opt-in backends can be enabled using the `getrandom_backend` configuration flag. diff --git a/src/backends.rs b/src/backends.rs index a744a2295..f7b720f58 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -150,7 +150,7 @@ cfg_if! { pub use rdrand::*; } else if #[cfg(all( target_arch = "wasm32", - target_os = "unknown", + any(target_os = "unknown", target_os = "none") ))] { compile_error!("the wasm32-unknown-unknown targets are not supported \ by default, you may need to enable the \"wasm_js\" \ diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 1dd8e70f0..f9cd98d18 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -8,7 +8,7 @@ use core::sync::atomic::{AtomicU8, Ordering}; pub use crate::util::{inner_u32, inner_u64}; -#[cfg(not(all(target_arch = "wasm32", target_os = "unknown",)))] +#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); use js_sys::{global, Function, Uint8Array}; From 084c339371b0b5a335196633f6b66bfd04e88e21 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 00:56:17 +0300 Subject: [PATCH 04/17] Remove separate codepath for Node.js --- src/backends/wasm_js.rs | 102 ---------------------------------------- 1 file changed, 102 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index f9cd98d18..2246b8b9a 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -17,40 +17,8 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; // Size of our temporary Uint8Array buffer used with WebCrypto methods // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues const WEB_CRYPTO_BUFFER_SIZE: u16 = 256; -// Node.js's crypto.randomFillSync requires the size to be less than 2**31. -const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1; - -const KIND_UNINIT: u8 = 0; -const KIND_WEB: u8 = 1; -const KIND_NODE: u8 = 2; -const KIND_NA: u8 = 255; - -static KIND: AtomicU8 = AtomicU8::new(KIND_UNINIT); pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - loop { - break match KIND.load(Ordering::Relaxed) { - KIND_UNINIT => { - let global: Global = global().unchecked_into(); - let crypto = global.crypto(); - let val = if crypto.is_object() { - KIND_WEB - } else if is_node(&global) { - KIND_NODE - } else { - KIND_NA - }; - KIND.store(val, Ordering::Relaxed); - continue; - } - KIND_WEB => web_fill(dest), - KIND_NODE => node_fill(dest), - _ => Err(Error::WEB_CRYPTO), - }; - } -} - -pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { let global: Global = global().unchecked_into(); let crypto = global.crypto(); @@ -76,54 +44,10 @@ pub fn web_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { Ok(()) } -pub fn node_fill(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let global: Global = global().unchecked_into(); - - // If module.require isn't a valid function, we are in an ES module. - let require_fn = Module::require_fn() - .and_then(JsCast::dyn_into::) - .map_err(|_| Error::NODE_ES_MODULE)?; - let n: NodeCrypto = require_fn - .call1(&global, &JsValue::from_str("crypto")) - .map_err(|_| Error::NODE_CRYPTO)? - .unchecked_into(); - - for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) { - // SAFETY: chunk is never used directly, the memory is only - // modified via the Uint8Array view, which is passed - // directly to JavaScript. Also, crypto.randomFillSync does - // not resize the buffer. We know the length is less than - // u32::MAX because of the chunking above. - // Note that this uses the fact that JavaScript doesn't - // have a notion of "uninitialized memory", this is purely - // a Rust/C/C++ concept. - let res = n.random_fill_sync(unsafe { - Uint8Array::view_mut_raw(chunk.as_mut_ptr().cast::(), chunk.len()) - }); - if res.is_err() { - return Err(Error::NODE_RANDOM_FILL_SYNC); - } - } - Ok(()) -} - -// Taken from https://www.npmjs.com/package/browser-or-node -fn is_node(global: &Global) -> bool { - let process = global.process(); - if process.is_object() { - let versions = process.versions(); - if versions.is_object() { - return versions.node().is_string(); - } - } - false -} - #[wasm_bindgen] extern "C" { // Return type of js_sys::global() type Global; - // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) type WebCrypto; // Getters for the WebCrypto API @@ -132,30 +56,4 @@ extern "C" { // Crypto.getRandomValues() #[wasm_bindgen(method, js_name = getRandomValues, catch)] fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>; - - // Node JS crypto module (https://nodejs.org/api/crypto.html) - type NodeCrypto; - // crypto.randomFillSync() - #[wasm_bindgen(method, js_name = randomFillSync, catch)] - fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>; - - // Ideally, we would just use `fn require(s: &str)` here. However, doing - // this causes a Webpack warning. So we instead return the function itself - // and manually invoke it using call1. This also lets us to check that the - // function actually exists, allowing for better error messages. See: - // https://github.com/rust-random/getrandom/issues/224 - // https://github.com/rust-random/getrandom/issues/256 - type Module; - #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)] - fn require_fn() -> Result; - - // Node JS process Object (https://nodejs.org/api/process.html) - #[wasm_bindgen(method, getter)] - fn process(this: &Global) -> Process; - type Process; - #[wasm_bindgen(method, getter)] - fn versions(this: &Process) -> Versions; - type Versions; - #[wasm_bindgen(method, getter)] - fn node(this: &Versions) -> JsValue; } From 6abdd3317f18a056ce310ae949d1526020613e95 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:01:54 +0300 Subject: [PATCH 05/17] fix imports --- src/backends/wasm_js.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 2246b8b9a..634943495 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -1,17 +1,13 @@ //! Implementation for WASM based on Web and Node.js use crate::Error; - -extern crate std; -use std::mem::MaybeUninit; - -use core::sync::atomic::{AtomicU8, Ordering}; +use core::mem::MaybeUninit; pub use crate::util::{inner_u32, inner_u64}; #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); -use js_sys::{global, Function, Uint8Array}; +use js_sys::{global, Uint8Array}; use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; // Size of our temporary Uint8Array buffer used with WebCrypto methods From 20ac4c77c4454eb739d0710b746e7183aa826e0f Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:06:38 +0300 Subject: [PATCH 06/17] tweak readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 417ab92e4..2d8ec13e7 100644 --- a/README.md +++ b/README.md @@ -115,9 +115,9 @@ which JavaScript interface should be used (or if JavaScript is available at all) Instead, *if the `wasm_js` backend is enabled*, this crate will assume that you are building for an environment containing JavaScript, and will -call the appropriate methods. Both web browser (main window and Web Workers) -and Node.js environments are supported, invoking the methods -[described above](#opt-in-backends) using the [`wasm-bindgen`] toolchain. +call the appropriate Web Crypto methods [described above](#opt-in-backends) +the [`wasm-bindgen`] toolchain. Both web browser (main window and Web Workers) +and Node.js (v19 or later) environments are supported. To enable the `wasm_js` backend, you can add the following lines to your project's `.cargo/config.toml` file: From e24e348937c713018dfc28a41e974cc21fa22f3f Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:08:47 +0300 Subject: [PATCH 07/17] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4cebbc9..36cabb352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Switch from `libpthread`'s mutex to `futex` on Linux and to `nanosleep`-based wait loop on other targets in the `use_file` backend [#490] - Do not retry on `EAGAIN` while polling `/dev/random` on Linux [#522] - +- Remove separate codepath for Node.js in the `wasm_js` backend (bumps minimum supported Node.js + version to v19) [#557] + ### Added - `wasm32-wasip1` and `wasm32-wasip2` support [#499] - `getrandom_backend` configuration flag for selection of opt-in backends [#504] @@ -58,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#544]: https://github.com/rust-random/getrandom/pull/544 [#554]: https://github.com/rust-random/getrandom/pull/554 [#555]: https://github.com/rust-random/getrandom/pull/555 +[#557]: https://github.com/rust-random/getrandom/pull/557 ## [0.2.15] - 2024-05-06 ### Added From 5f8f3558df4480e25690fb86cd71ae70e8645d8c Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:16:48 +0300 Subject: [PATCH 08/17] Remove outdated node.js section --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index 2d8ec13e7..e06e4eb19 100644 --- a/README.md +++ b/README.md @@ -126,18 +126,6 @@ project's `.cargo/config.toml` file: rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] ``` -#### Node.js ES module support - -Node.js supports both [CommonJS modules] and [ES modules]. Due to -limitations in wasm-bindgen's [`module`] support, we cannot directly -support ES Modules running on Node.js. However, on Node v15 and later, the -module author can add a simple shim to support the Web Cryptography API: -```js -import { webcrypto } from 'node:crypto' -globalThis.crypto = webcrypto -``` -This crate will then use the provided `webcrypto` implementation. - ### Custom backend If this crate does not support your target out of the box or you have to use From d0c19e8a8b2ebee14b4381901c0df8513d2d4ace Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:18:47 +0300 Subject: [PATCH 09/17] Remove `getrandom_browser_test` from Cargo.toml --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 78c8ddd24..875a7fd07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,6 @@ level = "warn" check-cfg = [ 'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "linux_rustix", "wasm_js", "esp_idf"))', 'cfg(getrandom_sanitize)', - 'cfg(getrandom_browser_test)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_netbsd_fallback)', ] From dfa56a814cf0260fb98c72c25b15737267504b4f Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:23:52 +0300 Subject: [PATCH 10/17] Tweak readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e06e4eb19..92acb33d1 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ which JavaScript interface should be used (or if JavaScript is available at all) Instead, *if the `wasm_js` backend is enabled*, this crate will assume that you are building for an environment containing JavaScript, and will -call the appropriate Web Crypto methods [described above](#opt-in-backends) +call the appropriate Web Crypto methods [described above](#opt-in-backends) using the [`wasm-bindgen`] toolchain. Both web browser (main window and Web Workers) and Node.js (v19 or later) environments are supported. From 085dd265fd3a746a2f6d654bba5053fc93fceb84 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:37:13 +0300 Subject: [PATCH 11/17] Update README.md Co-authored-by: daxpedda --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92acb33d1..f7ce5d531 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ of randomness based on their specific needs: | `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction | `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register | `esp_idf` | ESP-IDF | `*‑espidf` | [`esp_fill_random`]. WARNING: can return low-quality entropy without proper hardware configuration! -| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`] if available, then [`crypto.randomFillSync`] if on Node.js (see [WebAssembly support]) +| `wasm_js` | Web Browser, Node.js | `wasm32‑unknown‑unknown`, `wasm32v1-none` | [`Crypto.getRandomValues`] | `custom` | All targets | `*` | User-provided custom implementation (see [custom backend]) Opt-in backends can be enabled using the `getrandom_backend` configuration flag. From 5fba3fd6492ddc3e717cfb3d5c4460bebdd52ce5 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:41:46 +0300 Subject: [PATCH 12/17] Apply review suggestions --- src/backends/wasm_js.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 634943495..3e66e95af 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -12,16 +12,20 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; // Size of our temporary Uint8Array buffer used with WebCrypto methods // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues -const WEB_CRYPTO_BUFFER_SIZE: u16 = 256; +const CRYPTO_BUFFER_SIZE: u16 = 256; pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let global: Global = global().unchecked_into(); let crypto = global.crypto(); + if !crypto.is_object() { + return Err(Error::WEB_CRYPTO); + } + // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. - let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE.into()); - for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE.into()) { + let buf = Uint8Array::new_with_length(CRYPTO_BUFFER_SIZE.into()); + for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) { let chunk_len: u32 = chunk .len() .try_into() @@ -45,11 +49,11 @@ extern "C" { // Return type of js_sys::global() type Global; // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) - type WebCrypto; - // Getters for the WebCrypto API + type Crypto; + // Getters for the Crypto API #[wasm_bindgen(method, getter)] - fn crypto(this: &Global) -> WebCrypto; + fn crypto(this: &Global) -> Crypto; // Crypto.getRandomValues() #[wasm_bindgen(method, js_name = getRandomValues, catch)] - fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>; + fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>; } From abd06d58cee162230568265152a42aef6a380502 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:42:49 +0300 Subject: [PATCH 13/17] Remove outdated errors --- src/error.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index b27ef26b4..64cfb4076 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,19 +43,12 @@ impl Error { pub const WEB_GET_RANDOM_VALUES: Error = Self::new_internal(8); /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). pub const VXWORKS_RAND_SECURE: Error = Self::new_internal(11); - /// Node.js does not have the `crypto` CommonJS module. - pub const NODE_CRYPTO: Error = Self::new_internal(12); - /// Calling Node.js function `crypto.randomFillSync` failed. - pub const NODE_RANDOM_FILL_SYNC: Error = Self::new_internal(13); - /// Called from an ES module on Node.js. This is unsupported, see: - /// . - pub const NODE_ES_MODULE: Error = Self::new_internal(14); /// Calling Windows ProcessPrng failed. - pub const WINDOWS_PROCESS_PRNG: Error = Self::new_internal(15); + pub const WINDOWS_PROCESS_PRNG: Error = Self::new_internal(12); /// RNDR register read failed due to a hardware issue. - pub const RNDR_FAILURE: Error = Self::new_internal(16); + pub const RNDR_FAILURE: Error = Self::new_internal(13); /// RNDR register is not supported on this target. - pub const RNDR_NOT_AVAILABLE: Error = Self::new_internal(17); + pub const RNDR_NOT_AVAILABLE: Error = Self::new_internal(14); /// Codes below this point represent OS Errors (i.e. positive i32 values). /// Codes at or above this point, but below [`Error::CUSTOM_START`] are From 8fa4829d6da987ee7610587634da9172db02baf5 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:43:30 +0300 Subject: [PATCH 14/17] fmt --- src/backends/wasm_js.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 3e66e95af..d882f2461 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -21,7 +21,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { if !crypto.is_object() { return Err(Error::WEB_CRYPTO); } - + // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. let buf = Uint8Array::new_with_length(CRYPTO_BUFFER_SIZE.into()); From f72daf8bcc9c0a22cc495a134723c94b5854abc4 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 01:51:44 +0300 Subject: [PATCH 15/17] Fix `Error::internal_desc` --- src/error.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 64cfb4076..0f486c7b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -157,9 +157,6 @@ fn internal_desc(error: Error) -> Option<&'static str> { Error::WEB_CRYPTO => "Web Crypto API is unavailable", Error::WEB_GET_RANDOM_VALUES => "Calling Web API crypto.getRandomValues failed", Error::VXWORKS_RAND_SECURE => "randSecure: VxWorks RNG module is not initialized", - Error::NODE_CRYPTO => "Node.js crypto CommonJS module is unavailable", - Error::NODE_RANDOM_FILL_SYNC => "Calling Node.js API crypto.randomFillSync failed", - Error::NODE_ES_MODULE => "Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support", Error::WINDOWS_PROCESS_PRNG => "ProcessPrng: Windows system function failure", Error::RNDR_FAILURE => "RNDR: Could not generate a random number", Error::RNDR_NOT_AVAILABLE => "RNDR: Register not supported", From 27d9dc040049637b1575585a3a56340b78619ccc Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 02:27:57 +0300 Subject: [PATCH 16/17] Update src/backends/wasm_js.rs Co-authored-by: daxpedda --- src/backends/wasm_js.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index d882f2461..7753daf98 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -29,7 +29,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let chunk_len: u32 = chunk .len() .try_into() - .expect("chunk length is bounded by WEB_CRYPTO_BUFFER_SIZE"); + .expect("chunk length is bounded by CRYPTO_BUFFER_SIZE"); // The chunk can be smaller than buf's length, so we call to // JS to create a smaller view of buf without allocation. let sub_buf = buf.subarray(0, chunk_len); From 20827a750be98457af6ffdd09d525fa285abf672 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 5 Dec 2024 02:29:42 +0300 Subject: [PATCH 17/17] Remove outdated links --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f7ce5d531..ebd1b76aa 100644 --- a/README.md +++ b/README.md @@ -336,17 +336,13 @@ dual licensed as above, without any additional terms or conditions. [`RNDR`]: https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/RNDR--Random-Number [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw -[`crypto.randomFillSync`]: https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size [`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t [`random_get`]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno [`get-random-u64`]: https://github.com/WebAssembly/WASI/blob/v0.2.1/wasip2/random/random.wit#L23-L28 -[WebAssembly support]: #webassembly-support [configuration flags]: #configuration-flags [custom backend]: #custom-backend [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen [`module`]: https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-js-imports/module.html -[CommonJS modules]: https://nodejs.org/api/modules.html -[ES modules]: https://nodejs.org/api/esm.html [`sys_read_entropy`]: https://github.com/hermit-os/kernel/blob/315f58ff5efc81d9bf0618af85a59963ff55f8b1/src/syscalls/entropy.rs#L47-L55 [platform-support]: https://doc.rust-lang.org/stable/rustc/platform-support.html [WASI]: https://github.com/CraneStation/wasi