1 //! Implementation for WASM based on Web and Node.js
2 use crate::Error;
3
4 extern crate std;
5 use std::{mem::MaybeUninit, thread_local};
6
7 use js_sys::{global, Function, Uint8Array};
8 use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
9
10 // Size of our temporary Uint8Array buffer used with WebCrypto methods
11 // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
12 const WEB_CRYPTO_BUFFER_SIZE: usize = 256;
13 // Node.js's crypto.randomFillSync requires the size to be less than 2**31.
14 const NODE_MAX_BUFFER_SIZE: usize = (1 << 31) - 1;
15
16 enum RngSource {
17 Node(NodeCrypto),
18 Web(WebCrypto, Uint8Array),
19 }
20
21 // JsValues are always per-thread, so we initialize RngSource for each thread.
22 // See: https://github.com/rustwasm/wasm-bindgen/pull/955
23 thread_local!(
24 static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
25 );
26
getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>27 pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
28 RNG_SOURCE.with(|result| {
29 let source = result.as_ref().map_err(|&e| e)?;
30
31 match source {
32 RngSource::Node(n) => {
33 for chunk in dest.chunks_mut(NODE_MAX_BUFFER_SIZE) {
34 // SAFETY: chunk is never used directly, the memory is only
35 // modified via the Uint8Array view, which is passed
36 // directly to JavaScript. Also, crypto.randomFillSync does
37 // not resize the buffer. We know the length is less than
38 // u32::MAX because of the chunking above.
39 // Note that this uses the fact that JavaScript doesn't
40 // have a notion of "uninitialized memory", this is purely
41 // a Rust/C/C++ concept.
42 let res = n.random_fill_sync(unsafe {
43 Uint8Array::view_mut_raw(chunk.as_mut_ptr() as *mut u8, chunk.len())
44 });
45 if res.is_err() {
46 return Err(Error::NODE_RANDOM_FILL_SYNC);
47 }
48 }
49 }
50 RngSource::Web(crypto, buf) => {
51 // getRandomValues does not work with all types of WASM memory,
52 // so we initially write to browser memory to avoid exceptions.
53 for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) {
54 // The chunk can be smaller than buf's length, so we call to
55 // JS to create a smaller view of buf without allocation.
56 let sub_buf = buf.subarray(0, chunk.len() as u32);
57
58 if crypto.get_random_values(&sub_buf).is_err() {
59 return Err(Error::WEB_GET_RANDOM_VALUES);
60 }
61
62 // SAFETY: `sub_buf`'s length is the same length as `chunk`
63 unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) };
64 }
65 }
66 };
67 Ok(())
68 })
69 }
70
getrandom_init() -> Result<RngSource, Error>71 fn getrandom_init() -> Result<RngSource, Error> {
72 let global: Global = global().unchecked_into();
73
74 // Get the Web Crypto interface if we are in a browser, Web Worker, Deno,
75 // or another environment that supports the Web Cryptography API. This
76 // also allows for user-provided polyfills in unsupported environments.
77 let crypto = match global.crypto() {
78 // Standard Web Crypto interface
79 c if c.is_object() => c,
80 // Node.js CommonJS Crypto module
81 _ if is_node(&global) => {
82 // If module.require isn't a valid function, we are in an ES module.
83 match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
84 Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
85 Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
86 Err(_) => return Err(Error::NODE_CRYPTO),
87 },
88 Err(_) => return Err(Error::NODE_ES_MODULE),
89 }
90 }
91 // IE 11 Workaround
92 _ => match global.ms_crypto() {
93 c if c.is_object() => c,
94 _ => return Err(Error::WEB_CRYPTO),
95 },
96 };
97
98 let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
99 Ok(RngSource::Web(crypto, buf))
100 }
101
102 // Taken from https://www.npmjs.com/package/browser-or-node
is_node(global: &Global) -> bool103 fn is_node(global: &Global) -> bool {
104 let process = global.process();
105 if process.is_object() {
106 let versions = process.versions();
107 if versions.is_object() {
108 return versions.node().is_string();
109 }
110 }
111 false
112 }
113
114 #[wasm_bindgen]
115 extern "C" {
116 // Return type of js_sys::global()
117 type Global;
118
119 // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
120 type WebCrypto;
121 // Getters for the WebCrypto API
122 #[wasm_bindgen(method, getter)]
crypto(this: &Global) -> WebCrypto123 fn crypto(this: &Global) -> WebCrypto;
124 #[wasm_bindgen(method, getter, js_name = msCrypto)]
ms_crypto(this: &Global) -> WebCrypto125 fn ms_crypto(this: &Global) -> WebCrypto;
126 // Crypto.getRandomValues()
127 #[wasm_bindgen(method, js_name = getRandomValues, catch)]
get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>128 fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;
129
130 // Node JS crypto module (https://nodejs.org/api/crypto.html)
131 type NodeCrypto;
132 // crypto.randomFillSync()
133 #[wasm_bindgen(method, js_name = randomFillSync, catch)]
random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>134 fn random_fill_sync(this: &NodeCrypto, buf: Uint8Array) -> Result<(), JsValue>;
135
136 // Ideally, we would just use `fn require(s: &str)` here. However, doing
137 // this causes a Webpack warning. So we instead return the function itself
138 // and manually invoke it using call1. This also lets us to check that the
139 // function actually exists, allowing for better error messages. See:
140 // https://github.com/rust-random/getrandom/issues/224
141 // https://github.com/rust-random/getrandom/issues/256
142 type Module;
143 #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)]
require_fn() -> Result<JsValue, JsValue>144 fn require_fn() -> Result<JsValue, JsValue>;
145
146 // Node JS process Object (https://nodejs.org/api/process.html)
147 #[wasm_bindgen(method, getter)]
process(this: &Global) -> Process148 fn process(this: &Global) -> Process;
149 type Process;
150 #[wasm_bindgen(method, getter)]
versions(this: &Process) -> Versions151 fn versions(this: &Process) -> Versions;
152 type Versions;
153 #[wasm_bindgen(method, getter)]
node(this: &Versions) -> JsValue154 fn node(this: &Versions) -> JsValue;
155 }
156