From 1dbda17e674d147af169d09f269a523f1eb03112 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 5 Dec 2024 00:13:53 +0100 Subject: [PATCH 01/12] Cache global `Crypto` object --- Cargo.toml | 4 +-- src/backends/wasm_js.rs | 61 +++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e7f30d4..ca679b70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,8 +64,8 @@ windows-targets = "0.52" # wasm_js [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 = { version = "0.3.73", default-features = false } +wasm-bindgen = { version = "0.2.98", default-features = false } +js-sys = { version = "0.3.75", default-features = false } [target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 7753daf9..3aa60e2a 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -7,52 +7,47 @@ 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, Uint8Array}; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; +use js_sys::Uint8Array; +use wasm_bindgen::{prelude::wasm_bindgen, 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 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(CRYPTO_BUFFER_SIZE.into()); - for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) { - let chunk_len: u32 = chunk - .len() - .try_into() - .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); - - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); + CRYPTO.with(|crypto| { + let crypto = crypto.as_ref().ok_or(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()); + for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) { + let chunk_len: u32 = chunk + .len() + .try_into() + .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); + + 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::()) }; } - - // SAFETY: `sub_buf`'s length is the same length as `chunk` - unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; - } - Ok(()) + Ok(()) + }) } #[wasm_bindgen] extern "C" { - // Return type of js_sys::global() - type Global; // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) type Crypto; - // Getters for the Crypto API - #[wasm_bindgen(method, getter)] - fn crypto(this: &Global) -> Crypto; + // Holds the global `Crypto` object. + #[wasm_bindgen(thread_local_v2, js_name = crypto)] + static CRYPTO: Option; // Crypto.getRandomValues() #[wasm_bindgen(method, js_name = getRandomValues, catch)] fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>; From d80425828358257fd65722847694491703b3ac8f Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 6 Dec 2024 09:30:50 +0100 Subject: [PATCH 02/12] Don't create sub-array unless necessary --- src/backends/wasm_js.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 3aa60e2a..54dca1ef 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -28,7 +28,11 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { .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); + let sub_buf = if chunk_len == u32::from(CRYPTO_BUFFER_SIZE) { + buf.clone() + } else { + buf.subarray(0, chunk_len) + }; if crypto.get_random_values(&sub_buf).is_err() { return Err(Error::WEB_GET_RANDOM_VALUES); From b2ab9a1a20bcf4c880d4355ed700bbafb43966c9 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 5 Dec 2024 01:28:27 +0100 Subject: [PATCH 03/12] Don't use `Uint8Array` when not necessary --- .github/workflows/tests.yml | 43 +++++++++++++++-------- .github/workflows/workspace.yml | 4 +++ src/backends/wasm_js.rs | 62 +++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 045a6bb9..b6e385a8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -229,11 +229,26 @@ jobs: # run: cargo test web: - name: Web + name: Web ${{ matrix.rust.description }} runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + rust: + - { version: stable } + - { + description: with Atomics, + version: nightly, + components: rust-src, + flags: '-Ctarget-feature=+atomics,+bulk-memory', + args: '-Zbuild-std=panic_abort,std', + } steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust.version }} + components: ${{ matrix.rust.components }} - name: Install precompiled wasm-pack shell: bash run: | @@ -244,34 +259,34 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Test (Node) env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: wasm-pack test --node -- --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: wasm-pack test --node -- --features std ${{ matrix.rust.args }} - name: Test (Firefox) env: WASM_BINDGEN_USE_BROWSER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: wasm-pack test --headless --firefox -- --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }} - name: Test (Chrome) env: WASM_BINDGEN_USE_BROWSER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: wasm-pack test --headless --chrome -- --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: wasm-pack test --headless --chrome -- --features std ${{ matrix.rust.args }} - name: Test (dedicated worker) env: WASM_BINDGEN_USE_DEDICATED_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: wasm-pack test --headless --firefox -- --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }} - name: Test (shared worker) env: WASM_BINDGEN_USE_SHARED_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" - run: wasm-pack test --headless --firefox -- --features std + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }} - name: Test (service worker) env: WASM_BINDGEN_USE_SERVICE_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} # Firefox doesn't support module service workers and therefor can't import scripts - run: wasm-pack test --headless --chrome -- --features std + run: wasm-pack test --headless --chrome -- --features std ${{ matrix.rust.args }} wasi: name: WASI diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 2653984d..7f62034a 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -49,6 +49,10 @@ jobs: env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown + - name: Web WASM with atomics (wasm_js.rs) + env: + RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory + run: cargo clippy -Zbuild-std --target wasm32-unknown-unknown - name: Linux (linux_android.rs) env: RUSTFLAGS: -Dwarnings --cfg getrandom_backend="linux_getrandom" diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 54dca1ef..3c1afb07 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -7,39 +7,51 @@ 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!"); +#[cfg(target_feature = "atomics")] use js_sys::Uint8Array; use wasm_bindgen::{prelude::wasm_bindgen, 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 CRYPTO_BUFFER_SIZE: u16 = 256; +// Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. +// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const MAX_BUFFER_SIZE: u16 = 256; pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { CRYPTO.with(|crypto| { let crypto = crypto.as_ref().ok_or(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()); - for chunk in dest.chunks_mut(CRYPTO_BUFFER_SIZE.into()) { - let chunk_len: u32 = chunk - .len() - .try_into() - .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 = if chunk_len == u32::from(CRYPTO_BUFFER_SIZE) { - buf.clone() - } else { - buf.subarray(0, chunk_len) - }; - - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); + #[cfg(not(target_feature = "atomics"))] + { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + if crypto.get_random_values(chunk).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); + } } + } + #[cfg(target_feature = "atomics")] + { + // 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(MAX_BUFFER_SIZE.into()); + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + let chunk_len: u32 = chunk + .len() + .try_into() + .expect("chunk length is bounded by MAX_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 = if chunk_len == u32::from(MAX_BUFFER_SIZE) { + buf.clone() + } else { + 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::()) }; + // SAFETY: `sub_buf`'s length is the same length as `chunk` + unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; + } } Ok(()) }) @@ -53,6 +65,10 @@ extern "C" { #[wasm_bindgen(thread_local_v2, js_name = crypto)] static CRYPTO: Option; // Crypto.getRandomValues() + #[cfg(not(target_feature = "atomics"))] + #[wasm_bindgen(method, js_name = getRandomValues, catch)] + fn get_random_values(this: &Crypto, buf: &mut [MaybeUninit]) -> Result<(), JsValue>; + #[cfg(target_feature = "atomics")] #[wasm_bindgen(method, js_name = getRandomValues, catch)] fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>; } From 02507fe1597297e6770ece171463d083d3714ac3 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 7 Dec 2024 18:45:45 +0100 Subject: [PATCH 04/12] Improve CI test matrix Co-Authored-By: Joe Richey --- .github/workflows/tests.yml | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b6e385a8..ae4910ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -229,19 +229,24 @@ jobs: # run: cargo test web: - name: Web ${{ matrix.rust.description }} + name: ${{ matrix.rust.description }} runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: rust: - - { version: stable } - { - description: with Atomics, + description: Web, + version: stable, + flags: -Dwarnings --cfg getrandom_backend="wasm_js", + args: --features=std, + } + - { + description: Web with Atomics, version: nightly, components: rust-src, - flags: '-Ctarget-feature=+atomics,+bulk-memory', - args: '-Zbuild-std=panic_abort,std', + flags: '-Dwarnings --cfg getrandom_backend="wasm_js" -Ctarget-feature=+atomics,+bulk-memory', + args: '--features=std -Zbuild-std=panic_abort,std', } steps: - uses: actions/checkout@v4 @@ -259,34 +264,34 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Test (Node) env: - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} - run: wasm-pack test --node -- --features std ${{ matrix.rust.args }} + RUSTFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --node -- ${{ matrix.rust.args }} - name: Test (Firefox) env: WASM_BINDGEN_USE_BROWSER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} - run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }} + RUSTFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- ${{ matrix.rust.args }} - name: Test (Chrome) env: WASM_BINDGEN_USE_BROWSER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} - run: wasm-pack test --headless --chrome -- --features std ${{ matrix.rust.args }} + RUSTFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --chrome -- ${{ matrix.rust.args }} - name: Test (dedicated worker) env: WASM_BINDGEN_USE_DEDICATED_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} - run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }} + RUSTFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- ${{ matrix.rust.args }} - name: Test (shared worker) env: WASM_BINDGEN_USE_SHARED_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} - run: wasm-pack test --headless --firefox -- --features std ${{ matrix.rust.args }} + RUSTFLAGS: ${{ matrix.rust.flags }} + run: wasm-pack test --headless --firefox -- ${{ matrix.rust.args }} - name: Test (service worker) env: WASM_BINDGEN_USE_SERVICE_WORKER: 1 - RUSTFLAGS: -Dwarnings --cfg getrandom_backend="wasm_js" ${{ matrix.rust.flags }} + RUSTFLAGS: ${{ matrix.rust.flags }} # Firefox doesn't support module service workers and therefor can't import scripts - run: wasm-pack test --headless --chrome -- --features std ${{ matrix.rust.args }} + run: wasm-pack test --headless --chrome -- ${{ matrix.rust.args }} wasi: name: WASI From dcd8f4ab4445e6bed1d30fc1c8bb7e01e1a72009 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 7 Dec 2024 18:46:19 +0100 Subject: [PATCH 05/12] Remove nesting --- src/backends/wasm_js.rs | 67 +++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 3c1afb07..30113f96 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -18,43 +18,46 @@ const MAX_BUFFER_SIZE: u16 = 256; pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { CRYPTO.with(|crypto| { let crypto = crypto.as_ref().ok_or(Error::WEB_CRYPTO)?; + inner(crypto, dest) + }) +} - #[cfg(not(target_feature = "atomics"))] - { - for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { - if crypto.get_random_values(chunk).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); - } - } +#[cfg(not(target_feature = "atomics"))] +fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + if crypto.get_random_values(chunk).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); } - #[cfg(target_feature = "atomics")] - { - // 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(MAX_BUFFER_SIZE.into()); - for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { - let chunk_len: u32 = chunk - .len() - .try_into() - .expect("chunk length is bounded by MAX_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 = if chunk_len == u32::from(MAX_BUFFER_SIZE) { - buf.clone() - } else { - buf.subarray(0, chunk_len) - }; + } + Ok(()) +} - if crypto.get_random_values(&sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); - } +#[cfg(target_feature = "atomics")] +fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { + // 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(MAX_BUFFER_SIZE.into()); + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + let chunk_len: u32 = chunk + .len() + .try_into() + .expect("chunk length is bounded by MAX_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 = if chunk_len == u32::from(MAX_BUFFER_SIZE) { + buf.clone() + } else { + buf.subarray(0, chunk_len) + }; - // SAFETY: `sub_buf`'s length is the same length as `chunk` - unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; - } + if crypto.get_random_values(&sub_buf).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); } - Ok(()) - }) + + // SAFETY: `sub_buf`'s length is the same length as `chunk` + unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr().cast::()) }; + } + Ok(()) } #[wasm_bindgen] From e024029d61d40ea8ee95e2714e99b5be1068d560 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 7 Dec 2024 23:38:04 +0100 Subject: [PATCH 06/12] Remove unnecessary cloning --- src/backends/wasm_js.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 30113f96..6504c1fd 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -45,12 +45,12 @@ fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { // 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 = if chunk_len == u32::from(MAX_BUFFER_SIZE) { - buf.clone() + &buf } else { - buf.subarray(0, chunk_len) + &buf.subarray(0, chunk_len) }; - if crypto.get_random_values(&sub_buf).is_err() { + if crypto.get_random_values(sub_buf).is_err() { return Err(Error::WEB_GET_RANDOM_VALUES); } From 6feb9b744ec7648cdb84da9712b73891a513543c Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 7 Dec 2024 23:39:19 +0100 Subject: [PATCH 07/12] Guard `js-sys` dependency behind `target_feature = "atomics"` --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ca679b70..05ff4b1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ windows-targets = "0.52" # wasm_js [target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies] wasm-bindgen = { version = "0.2.98", default-features = false } +[target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", any(target_os = "unknown", target_os = "none"), target_feature = "atomics"))'.dependencies] js-sys = { version = "0.3.75", default-features = false } [target.'cfg(all(getrandom_backend = "wasm_js", target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dev-dependencies] wasm-bindgen-test = "0.3" From 65ba72733cfdb41859ea17c24de0be46e93fc870 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 8 Dec 2024 10:36:57 +0100 Subject: [PATCH 08/12] Fix `MAX_BUFFER_SIZE` --- src/backends/wasm_js.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 6504c1fd..4c8156f1 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -13,7 +13,7 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; // Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. // See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues -const MAX_BUFFER_SIZE: u16 = 256; +const MAX_BUFFER_SIZE: u32 = 65536; pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { CRYPTO.with(|crypto| { @@ -24,7 +24,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { #[cfg(not(target_feature = "atomics"))] fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE as usize) { if crypto.get_random_values(chunk).is_err() { return Err(Error::WEB_GET_RANDOM_VALUES); } @@ -36,15 +36,19 @@ fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { // 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(MAX_BUFFER_SIZE.into()); - for chunk in dest.chunks_mut(MAX_BUFFER_SIZE.into()) { + let buf_len = u32::min( + dest.len().try_into().unwrap_or(MAX_BUFFER_SIZE), + MAX_BUFFER_SIZE, + ); + let buf = Uint8Array::new_with_length(buf_len); + for chunk in dest.chunks_mut(buf_len as usize) { let chunk_len: u32 = chunk .len() .try_into() .expect("chunk length is bounded by MAX_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 = if chunk_len == u32::from(MAX_BUFFER_SIZE) { + let sub_buf = if chunk_len == buf_len { &buf } else { &buf.subarray(0, chunk_len) From 055a7c251a0bb9c610f6827f27403a3521203421 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 8 Dec 2024 16:15:50 +0100 Subject: [PATCH 09/12] Remove all `as` casts --- src/backends/wasm_js.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 4c8156f1..b481c66a 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -13,7 +13,7 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; // Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. // See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues -const MAX_BUFFER_SIZE: u32 = 65536; +const MAX_BUFFER_SIZE: usize = 65536; pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { CRYPTO.with(|crypto| { @@ -24,7 +24,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { #[cfg(not(target_feature = "atomics"))] fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(MAX_BUFFER_SIZE as usize) { + for chunk in dest.chunks_mut(MAX_BUFFER_SIZE) { if crypto.get_random_values(chunk).is_err() { return Err(Error::WEB_GET_RANDOM_VALUES); } @@ -36,19 +36,19 @@ fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. - let buf_len = u32::min( - dest.len().try_into().unwrap_or(MAX_BUFFER_SIZE), - MAX_BUFFER_SIZE, - ); - let buf = Uint8Array::new_with_length(buf_len); - for chunk in dest.chunks_mut(buf_len as usize) { - let chunk_len: u32 = chunk + let buf_len = usize::min(dest.len(), MAX_BUFFER_SIZE); + let buf_len_u32 = buf_len + .try_into() + .expect("buffer length is bounded by MAX_BUFFER_SIZE"); + let buf = Uint8Array::new_with_length(buf_len_u32); + for chunk in dest.chunks_mut(buf_len) { + let chunk_len = chunk .len() .try_into() .expect("chunk length is bounded by MAX_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 = if chunk_len == buf_len { + let sub_buf = if chunk_len == buf_len_u32 { &buf } else { &buf.subarray(0, chunk_len) From ec6f28950b94351c3ec5f398ff7dff72971f5e43 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 8 Dec 2024 17:17:18 +0100 Subject: [PATCH 10/12] Use `globalThis` to access `Crypto` --- 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 b481c66a..a0bc7afe 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -69,7 +69,7 @@ extern "C" { // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) type Crypto; // Holds the global `Crypto` object. - #[wasm_bindgen(thread_local_v2, js_name = crypto)] + #[wasm_bindgen(thread_local_v2, js_namespace = globalThis, js_name = crypto)] static CRYPTO: Option; // Crypto.getRandomValues() #[cfg(not(target_feature = "atomics"))] From cf72d6c735fda942a7e70950fd410074f33672a8 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 9 Dec 2024 15:35:06 +0100 Subject: [PATCH 11/12] Remove the `js_sys::Uint8Array` import Co-Authored-By: Artyom Pavlov --- src/backends/wasm_js.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index a0bc7afe..2eb3da4c 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -7,8 +7,6 @@ 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!"); -#[cfg(target_feature = "atomics")] -use js_sys::Uint8Array; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; // Maximum buffer size allowed in `Crypto.getRandomValuesSize` is 65536 bytes. @@ -40,7 +38,7 @@ fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { let buf_len_u32 = buf_len .try_into() .expect("buffer length is bounded by MAX_BUFFER_SIZE"); - let buf = Uint8Array::new_with_length(buf_len_u32); + let buf = js_sys::Uint8Array::new_with_length(buf_len_u32); for chunk in dest.chunks_mut(buf_len) { let chunk_len = chunk .len() @@ -77,5 +75,5 @@ extern "C" { fn get_random_values(this: &Crypto, buf: &mut [MaybeUninit]) -> Result<(), JsValue>; #[cfg(target_feature = "atomics")] #[wasm_bindgen(method, js_name = getRandomValues, catch)] - fn get_random_values(this: &Crypto, buf: &Uint8Array) -> Result<(), JsValue>; + fn get_random_values(this: &Crypto, buf: &js_sys::Uint8Array) -> Result<(), JsValue>; } From e5d68a9fa2f9c37af3bcbecce35eb4120cebfd15 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 9 Dec 2024 16:08:51 +0100 Subject: [PATCH 12/12] Move check for global `Crypto` object to `getRandomValues` --- src/backends/wasm_js.rs | 32 ++++++++++---------------------- src/error.rs | 3 --- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 2eb3da4c..8556f768 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -13,25 +13,18 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; // See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues const MAX_BUFFER_SIZE: usize = 65536; -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - CRYPTO.with(|crypto| { - let crypto = crypto.as_ref().ok_or(Error::WEB_CRYPTO)?; - inner(crypto, dest) - }) -} - #[cfg(not(target_feature = "atomics"))] -fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { for chunk in dest.chunks_mut(MAX_BUFFER_SIZE) { - if crypto.get_random_values(chunk).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); + if get_random_values(chunk).is_err() { + return Err(Error::WEB_CRYPTO); } } Ok(()) } #[cfg(target_feature = "atomics")] -fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. let buf_len = usize::min(dest.len(), MAX_BUFFER_SIZE); @@ -52,8 +45,8 @@ fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { &buf.subarray(0, chunk_len) }; - if crypto.get_random_values(sub_buf).is_err() { - return Err(Error::WEB_GET_RANDOM_VALUES); + if get_random_values(sub_buf).is_err() { + return Err(Error::WEB_CRYPTO); } // SAFETY: `sub_buf`'s length is the same length as `chunk` @@ -64,16 +57,11 @@ fn inner(crypto: &Crypto, dest: &mut [MaybeUninit]) -> Result<(), Error> { #[wasm_bindgen] extern "C" { - // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/) - type Crypto; - // Holds the global `Crypto` object. - #[wasm_bindgen(thread_local_v2, js_namespace = globalThis, js_name = crypto)] - static CRYPTO: Option; // Crypto.getRandomValues() #[cfg(not(target_feature = "atomics"))] - #[wasm_bindgen(method, js_name = getRandomValues, catch)] - fn get_random_values(this: &Crypto, buf: &mut [MaybeUninit]) -> Result<(), JsValue>; + #[wasm_bindgen(js_namespace = ["globalThis", "crypto"], js_name = getRandomValues, catch)] + fn get_random_values(buf: &mut [MaybeUninit]) -> Result<(), JsValue>; #[cfg(target_feature = "atomics")] - #[wasm_bindgen(method, js_name = getRandomValues, catch)] - fn get_random_values(this: &Crypto, buf: &js_sys::Uint8Array) -> Result<(), JsValue>; + #[wasm_bindgen(js_namespace = ["globalThis", "crypto"], js_name = getRandomValues, catch)] + fn get_random_values(buf: &js_sys::Uint8Array) -> Result<(), JsValue>; } diff --git a/src/error.rs b/src/error.rs index 0f486c7b..0c60f929 100644 --- a/src/error.rs +++ b/src/error.rs @@ -39,8 +39,6 @@ impl Error { pub const NO_RDRAND: Error = Self::new_internal(6); /// The environment does not support the Web Crypto API. pub const WEB_CRYPTO: Error = Self::new_internal(7); - /// Calling Web Crypto API `crypto.getRandomValues` failed. - 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); /// Calling Windows ProcessPrng failed. @@ -155,7 +153,6 @@ fn internal_desc(error: Error) -> Option<&'static str> { Error::FAILED_RDRAND => "RDRAND: failed multiple times: CPU issue likely", Error::NO_RDRAND => "RDRAND: instruction not supported", 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::WINDOWS_PROCESS_PRNG => "ProcessPrng: Windows system function failure", Error::RNDR_FAILURE => "RNDR: Could not generate a random number",