@@ -10,15 +10,16 @@ use crate::Error;
1010extern crate std;
1111use std:: thread_local;
1212
13- use js_sys:: { global, Uint8Array } ;
13+ use js_sys:: { global, Function , Uint8Array } ;
1414use wasm_bindgen:: { prelude:: wasm_bindgen, JsCast , JsValue } ;
1515
16+ // Size of our temporary Uint8Array buffer used with WebCrypto methods
1617// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
17- const BROWSER_CRYPTO_BUFFER_SIZE : usize = 256 ;
18+ const WEB_CRYPTO_BUFFER_SIZE : usize = 256 ;
1819
1920enum RngSource {
2021 Node ( NodeCrypto ) ,
21- Browser ( BrowserCrypto , Uint8Array ) ,
22+ Web ( WebCrypto , Uint8Array ) ,
2223}
2324
2425// 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> {
3738 return Err ( Error :: NODE_RANDOM_FILL_SYNC ) ;
3839 }
3940 }
40- RngSource :: Browser ( crypto, buf) => {
41+ RngSource :: Web ( crypto, buf) => {
4142 // getRandomValues does not work with all types of WASM memory,
4243 // so we initially write to browser memory to avoid exceptions.
43- for chunk in dest. chunks_mut ( BROWSER_CRYPTO_BUFFER_SIZE ) {
44+ for chunk in dest. chunks_mut ( WEB_CRYPTO_BUFFER_SIZE ) {
4445 // The chunk can be smaller than buf's length, so we call to
4546 // JS to create a smaller view of buf without allocation.
4647 let sub_buf = buf. subarray ( 0 , chunk. len ( ) as u32 ) ;
@@ -58,25 +59,33 @@ pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
5859
5960fn getrandom_init ( ) -> Result < RngSource , Error > {
6061 let global: Global = global ( ) . unchecked_into ( ) ;
61- if is_node ( & global) {
62- let crypto = NODE_MODULE
63- . require ( "crypto" )
64- . map_err ( |_| Error :: NODE_CRYPTO ) ?;
65- return Ok ( RngSource :: Node ( crypto) ) ;
66- }
6762
68- // Assume we are in some Web environment (browser or web worker). We get
69- // `self.crypto` (called `msCrypto` on IE), so we can call
70- // `crypto.getRandomValues`. If `crypto` isn't defined, we assume that
71- // we are in an older web browser and the OS RNG isn't available.
72- let crypto = match ( global. crypto ( ) , global. ms_crypto ( ) ) {
73- ( c, _) if c. is_object ( ) => c,
74- ( _, c) if c. is_object ( ) => c,
75- _ => return Err ( Error :: WEB_CRYPTO ) ,
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+ } ,
7685 } ;
7786
78- let buf = Uint8Array :: new_with_length ( BROWSER_CRYPTO_BUFFER_SIZE as u32 ) ;
79- Ok ( RngSource :: Browser ( crypto, buf) )
87+ let buf = Uint8Array :: new_with_length ( WEB_CRYPTO_BUFFER_SIZE as u32 ) ;
88+ Ok ( RngSource :: Web ( crypto, buf) )
8089}
8190
8291// Taken from https://www.npmjs.com/package/browser-or-node
@@ -93,30 +102,36 @@ fn is_node(global: &Global) -> bool {
93102
94103#[ wasm_bindgen]
95104extern "C" {
96- type Global ; // Return type of js_sys::global()
105+ // Return type of js_sys::global()
106+ type Global ;
97107
98- // Web Crypto API (https://www.w3.org/TR/WebCryptoAPI/)
99- # [ wasm_bindgen ( method , getter , js_name = "msCrypto" ) ]
100- fn ms_crypto ( this : & Global ) -> BrowserCrypto ;
108+ // Web Crypto API: Crypto interface (https://www.w3.org/TR/WebCryptoAPI/)
109+ type WebCrypto ;
110+ // Getters for the WebCrypto API
101111 #[ wasm_bindgen( method, getter) ]
102- fn crypto ( this : & Global ) -> BrowserCrypto ;
103- type BrowserCrypto ;
112+ fn crypto ( this : & Global ) -> WebCrypto ;
113+ #[ wasm_bindgen( method, getter, js_name = msCrypto) ]
114+ fn ms_crypto ( this : & Global ) -> WebCrypto ;
115+ // Crypto.getRandomValues()
104116 #[ wasm_bindgen( method, js_name = getRandomValues, catch) ]
105- fn get_random_values ( this : & BrowserCrypto , buf : & Uint8Array ) -> Result < ( ) , JsValue > ;
117+ fn get_random_values ( this : & WebCrypto , buf : & Uint8Array ) -> Result < ( ) , JsValue > ;
106118
107- // We use a "module" object here instead of just annotating require() with
108- // js_name = "module.require", so that Webpack doesn't give a warning. See:
109- // https://github.com/rust-random/getrandom/issues/224
110- type NodeModule ;
111- #[ wasm_bindgen( js_name = module) ]
112- static NODE_MODULE : NodeModule ;
113119 // Node JS crypto module (https://nodejs.org/api/crypto.html)
114- #[ wasm_bindgen( method, catch) ]
115- fn require ( this : & NodeModule , s : & str ) -> Result < NodeCrypto , JsValue > ;
116120 type NodeCrypto ;
121+ // crypto.randomFillSync()
117122 #[ wasm_bindgen( method, js_name = randomFillSync, catch) ]
118123 fn random_fill_sync ( this : & NodeCrypto , buf : & mut [ u8 ] ) -> Result < ( ) , JsValue > ;
119124
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) ]
133+ fn require_fn ( ) -> Result < JsValue , JsValue > ;
134+
120135 // Node JS process Object (https://nodejs.org/api/process.html)
121136 #[ wasm_bindgen( method, getter) ]
122137 fn process ( this : & Global ) -> Process ;
0 commit comments