• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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