aboutsummaryrefslogtreecommitdiff
path: root/src/js.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/js.rs')
-rw-r--r--src/js.rs87
1 files changed, 51 insertions, 36 deletions
diff --git a/src/js.rs b/src/js.rs
index e910f2b..574c4dc 100644
--- a/src/js.rs
+++ b/src/js.rs
@@ -10,15 +10,16 @@ use crate::Error;
extern crate std;
use std::thread_local;
-use js_sys::{global, Uint8Array};
+use js_sys::{global, Function, Uint8Array};
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 BROWSER_CRYPTO_BUFFER_SIZE: usize = 256;
+const WEB_CRYPTO_BUFFER_SIZE: usize = 256;
enum RngSource {
Node(NodeCrypto),
- Browser(BrowserCrypto, Uint8Array),
+ Web(WebCrypto, Uint8Array),
}
// JsValues are always per-thread, so we initialize RngSource for each thread.
@@ -37,10 +38,10 @@ pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
return Err(Error::NODE_RANDOM_FILL_SYNC);
}
}
- RngSource::Browser(crypto, buf) => {
+ 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(BROWSER_CRYPTO_BUFFER_SIZE) {
+ for chunk in dest.chunks_mut(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() as u32);
@@ -58,25 +59,33 @@ pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
fn getrandom_init() -> Result<RngSource, Error> {
let global: Global = global().unchecked_into();
- if is_node(&global) {
- let crypto = NODE_MODULE
- .require("crypto")
- .map_err(|_| Error::NODE_CRYPTO)?;
- return Ok(RngSource::Node(crypto));
- }
- // Assume we are in some Web environment (browser or web worker). We get
- // `self.crypto` (called `msCrypto` on IE), so we can call
- // `crypto.getRandomValues`. If `crypto` isn't defined, we assume that
- // we are in an older web browser and the OS RNG isn't available.
- let crypto = match (global.crypto(), global.ms_crypto()) {
- (c, _) if c.is_object() => c,
- (_, c) if c.is_object() => c,
- _ => return Err(Error::WEB_CRYPTO),
+ // 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 = match global.crypto() {
+ // Standard Web Crypto interface
+ c if c.is_object() => c,
+ // Node.js CommonJS Crypto module
+ _ if is_node(&global) => {
+ // If module.require isn't a valid function, we are in an ES module.
+ match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
+ Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
+ Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
+ Err(_) => return Err(Error::NODE_CRYPTO),
+ },
+ Err(_) => return Err(Error::NODE_ES_MODULE),
+ }
+ }
+ // IE 11 Workaround
+ _ => match global.ms_crypto() {
+ c if c.is_object() => c,
+ _ => return Err(Error::WEB_CRYPTO),
+ },
};
- let buf = Uint8Array::new_with_length(BROWSER_CRYPTO_BUFFER_SIZE as u32);
- Ok(RngSource::Browser(crypto, buf))
+ let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
+ Ok(RngSource::Web(crypto, buf))
}
// Taken from https://www.npmjs.com/package/browser-or-node
@@ -93,30 +102,36 @@ fn is_node(global: &Global) -> bool {
#[wasm_bindgen]
extern "C" {
- type Global; // Return type of js_sys::global()
+ // Return type of js_sys::global()
+ type Global;
- // Web Crypto API (https://www.w3.org/TR/WebCryptoAPI/)
- #[wasm_bindgen(method, getter, js_name = "msCrypto")]
- fn ms_crypto(this: &Global) -> BrowserCrypto;
+ // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
+ type WebCrypto;
+ // Getters for the WebCrypto API
#[wasm_bindgen(method, getter)]
- fn crypto(this: &Global) -> BrowserCrypto;
- type BrowserCrypto;
+ 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: &BrowserCrypto, buf: &Uint8Array) -> Result<(), JsValue>;
+ fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;
- // We use a "module" object here instead of just annotating require() with
- // js_name = "module.require", so that Webpack doesn't give a warning. See:
- // https://github.com/rust-random/getrandom/issues/224
- type NodeModule;
- #[wasm_bindgen(js_name = module)]
- static NODE_MODULE: NodeModule;
// Node JS crypto module (https://nodejs.org/api/crypto.html)
- #[wasm_bindgen(method, catch)]
- fn require(this: &NodeModule, s: &str) -> Result<NodeCrypto, JsValue>;
type NodeCrypto;
+ // crypto.randomFillSync()
#[wasm_bindgen(method, js_name = randomFillSync, catch)]
fn random_fill_sync(this: &NodeCrypto, buf: &mut [u8]) -> 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<JsValue, JsValue>;
+
// Node JS process Object (https://nodejs.org/api/process.html)
#[wasm_bindgen(method, getter)]
fn process(this: &Global) -> Process;