Merge pull request #284 from rust-random/webcrypto
Rework JS feature detection
This commit is contained in:
+11
-7
@@ -43,16 +43,19 @@ impl Error {
|
||||
pub const FAILED_RDRAND: Error = internal_error(5);
|
||||
/// RDRAND instruction unsupported on this target.
|
||||
pub const NO_RDRAND: Error = internal_error(6);
|
||||
/// The browser does not have support for `self.crypto`.
|
||||
/// The environment does not support the Web Crypto API.
|
||||
pub const WEB_CRYPTO: Error = internal_error(7);
|
||||
/// The browser does not have support for `crypto.getRandomValues`.
|
||||
/// Calling Web Crypto API `crypto.getRandomValues` failed.
|
||||
pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8);
|
||||
/// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized).
|
||||
pub const VXWORKS_RAND_SECURE: Error = internal_error(11);
|
||||
/// NodeJS does not have support for the `crypto` module.
|
||||
/// Node.js does not have the `crypto` CommonJS module.
|
||||
pub const NODE_CRYPTO: Error = internal_error(12);
|
||||
/// NodeJS does not have support for `crypto.randomFillSync`.
|
||||
/// Calling Node.js function `crypto.randomFillSync` failed.
|
||||
pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13);
|
||||
/// Called from an ES module on Node.js. This is unsupported, see:
|
||||
/// <https://docs.rs/getrandom#nodejs-es-module-support>.
|
||||
pub const NODE_ES_MODULE: Error = internal_error(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
|
||||
@@ -166,10 +169,11 @@ fn internal_desc(error: Error) -> Option<&'static str> {
|
||||
Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"),
|
||||
Error::NO_RDRAND => Some("RDRAND: instruction not supported"),
|
||||
Error::WEB_CRYPTO => Some("Web Crypto API is unavailable"),
|
||||
Error::WEB_GET_RANDOM_VALUES => Some("Web API crypto.getRandomValues is unavailable"),
|
||||
Error::WEB_GET_RANDOM_VALUES => Some("Calling Web API crypto.getRandomValues failed"),
|
||||
Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"),
|
||||
Error::NODE_CRYPTO => Some("Node.js crypto module is unavailable"),
|
||||
Error::NODE_RANDOM_FILL_SYNC => Some("Node.js API crypto.randomFillSync is unavailable"),
|
||||
Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
|
||||
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
|
||||
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+17
-2
@@ -31,7 +31,7 @@
|
||||
//! | Emscripten | `*‑emscripten` | `/dev/random` (identical to `/dev/urandom`)
|
||||
//! | WASI | `wasm32‑wasi` | [`random_get`]
|
||||
//! | Web Browser | `wasm32‑*‑unknown` | [`Crypto.getRandomValues`], see [WebAssembly support]
|
||||
//! | Node.js | `wasm32‑*‑unknown` | [`crypto.randomBytes`], see [WebAssembly support]
|
||||
//! | Node.js | `wasm32‑*‑unknown` | [`crypto.randomFillSync`], see [WebAssembly support]
|
||||
//! | SOLID | `*-kmc-solid_*` | `SOLID_RNG_SampleRandomBytes`
|
||||
//! | Nintendo 3DS | `armv6k-nintendo-3ds` | [`getrandom`][1]
|
||||
//!
|
||||
@@ -91,6 +91,18 @@
|
||||
//!
|
||||
//! This feature has no effect on targets other than `wasm32-unknown-unknown`.
|
||||
//!
|
||||
//! #### 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 implementations
|
||||
//!
|
||||
//! The [`register_custom_getrandom!`] macro allows a user to mark their own
|
||||
@@ -154,11 +166,14 @@
|
||||
//! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
|
||||
//! [`SecRandomCopyBytes`]: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc
|
||||
//! [`cprng_draw`]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw
|
||||
//! [`crypto.randomBytes`]: https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback
|
||||
//! [`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/main/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno
|
||||
//! [WebAssembly support]: #webassembly-support
|
||||
//! [`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
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png",
|
||||
|
||||
Reference in New Issue
Block a user