1 // Copyright 2018 Developers of the Rand project.
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8 use crate::Error;
9
10 extern crate std;
11 use std::thread_local;
12
13 use js_sys::{global, Function, Uint8Array};
14 use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
15
16 // Size of our temporary Uint8Array buffer used with WebCrypto methods
17 // Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
18 const WEB_CRYPTO_BUFFER_SIZE: usize = 256;
19
20 enum RngSource {
21 Node(NodeCrypto),
22 Web(WebCrypto, Uint8Array),
23 }
24
25 // JsValues are always per-thread, so we initialize RngSource for each thread.
26 // See: https://github.com/rustwasm/wasm-bindgen/pull/955
27 thread_local!(
28 static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
29 );
30
getrandom_inner(dest: &mut [u8]) -> Result<(), Error>31 pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
32 RNG_SOURCE.with(|result| {
33 let source = result.as_ref().map_err(|&e| e)?;
34
35 match source {
36 RngSource::Node(n) => {
37 if n.random_fill_sync(dest).is_err() {
38 return Err(Error::NODE_RANDOM_FILL_SYNC);
39 }
40 }
41 RngSource::Web(crypto, buf) => {
42 // getRandomValues does not work with all types of WASM memory,
43 // so we initially write to browser memory to avoid exceptions.
44 for chunk in dest.chunks_mut(WEB_CRYPTO_BUFFER_SIZE) {
45 // The chunk can be smaller than buf's length, so we call to
46 // JS to create a smaller view of buf without allocation.
47 let sub_buf = buf.subarray(0, chunk.len() as u32);
48
49 if crypto.get_random_values(&sub_buf).is_err() {
50 return Err(Error::WEB_GET_RANDOM_VALUES);
51 }
52 sub_buf.copy_to(chunk);
53 }
54 }
55 };
56 Ok(())
57 })
58 }
59
getrandom_init() -> Result<RngSource, Error>60 fn getrandom_init() -> Result<RngSource, Error> {
61 let global: Global = global().unchecked_into();
62
63 // Get the Web Crypto interface if we are in a browser, Web Worker, Deno,
64 // or another environment that supports the Web Cryptography API. This
65 // also allows for user-provided polyfills in unsupported environments.
66 let crypto = match global.crypto() {
67 // Standard Web Crypto interface
68 c if c.is_object() => c,
69 // Node.js CommonJS Crypto module
70 _ if is_node(&global) => {
71 // If module.require isn't a valid function, we are in an ES module.
72 match Module::require_fn().and_then(JsCast::dyn_into::<Function>) {
73 Ok(require_fn) => match require_fn.call1(&global, &JsValue::from_str("crypto")) {
74 Ok(n) => return Ok(RngSource::Node(n.unchecked_into())),
75 Err(_) => return Err(Error::NODE_CRYPTO),
76 },
77 Err(_) => return Err(Error::NODE_ES_MODULE),
78 }
79 }
80 // IE 11 Workaround
81 _ => match global.ms_crypto() {
82 c if c.is_object() => c,
83 _ => return Err(Error::WEB_CRYPTO),
84 },
85 };
86
87 let buf = Uint8Array::new_with_length(WEB_CRYPTO_BUFFER_SIZE as u32);
88 Ok(RngSource::Web(crypto, buf))
89 }
90
91 // Taken from https://www.npmjs.com/package/browser-or-node
is_node(global: &Global) -> bool92 fn is_node(global: &Global) -> bool {
93 let process = global.process();
94 if process.is_object() {
95 let versions = process.versions();
96 if versions.is_object() {
97 return versions.node().is_string();
98 }
99 }
100 false
101 }
102
103 #[wasm_bindgen]
104 extern "C" {
105 // Return type of js_sys::global()
106 type Global;
107
108 // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
109 type WebCrypto;
110 // Getters for the WebCrypto API
111 #[wasm_bindgen(method, getter)]
crypto(this: &Global) -> WebCrypto112 fn crypto(this: &Global) -> WebCrypto;
113 #[wasm_bindgen(method, getter, js_name = msCrypto)]
ms_crypto(this: &Global) -> WebCrypto114 fn ms_crypto(this: &Global) -> WebCrypto;
115 // Crypto.getRandomValues()
116 #[wasm_bindgen(method, js_name = getRandomValues, catch)]
get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>117 fn get_random_values(this: &WebCrypto, buf: &Uint8Array) -> Result<(), JsValue>;
118
119 // Node JS crypto module (https://nodejs.org/api/crypto.html)
120 type NodeCrypto;
121 // crypto.randomFillSync()
122 #[wasm_bindgen(method, js_name = randomFillSync, catch)]
random_fill_sync(this: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>123 fn random_fill_sync(this: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>;
124
125 // Ideally, we would just use `fn require(s: &str)` here. However, doing
126 // this causes a Webpack warning. So we instead return the function itself
127 // and manually invoke it using call1. This also lets us to check that the
128 // function actually exists, allowing for better error messages. See:
129 // https://github.com/rust-random/getrandom/issues/224
130 // https://github.com/rust-random/getrandom/issues/256
131 type Module;
132 #[wasm_bindgen(getter, static_method_of = Module, js_class = module, js_name = require, catch)]
require_fn() -> Result<JsValue, JsValue>133 fn require_fn() -> Result<JsValue, JsValue>;
134
135 // Node JS process Object (https://nodejs.org/api/process.html)
136 #[wasm_bindgen(method, getter)]
process(this: &Global) -> Process137 fn process(this: &Global) -> Process;
138 type Process;
139 #[wasm_bindgen(method, getter)]
versions(this: &Process) -> Versions140 fn versions(this: &Process) -> Versions;
141 type Versions;
142 #[wasm_bindgen(method, getter)]
node(this: &Versions) -> JsValue143 fn node(this: &Versions) -> JsValue;
144 }
145