[wasm][crypto] RandomNumberGenerator mapped to Web Crypto getRandomValues (#42728)
authorKenneth Pouncey <kjpou@pt.lu>
Thu, 1 Oct 2020 21:44:56 +0000 (23:44 +0200)
committerGitHub <noreply@github.com>
Thu, 1 Oct 2020 21:44:56 +0000 (23:44 +0200)
* [wasm][crypto] RandomNumberGenerator mapped to Web Crypto getRandomValues

- Uses Web Crypto API [`getRandomValues`](https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues) if available.
- Falls back to `/dev/urandom` as default if the crypto library is missing.

* Remove the emscripten interface code from the driver.c.

* remove extraneous code comment

* Move emscripten definition around.

* Address review comment

* Add javascript bridge implementation library to Native source tree.

- Javascript checks for crypto interface and uses `crypto.getRandomValues`
- Add api bridge call when building for emscripten browser.
- separate out into browser subdirectory
- If we couldn't find a proper implementation, as Math.random() is not suitable we will abort.

```

ABORT: no cryptographic support found getRandomValues. Consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };

```

* Change tests to set random values of the buffer instead of return a single value.

* Remove old test code

* Remove testing code

* Incorporate the PAL bridge layer into the `--js-library` build process

* Address review comments about directory structure and naming

* Update src/mono/wasm/runtime-test.js

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>
* Add note about insecure code for testing purposes

* Formatting

* Return -1 if crypto does not exist instead of aborting from js.  This allows the managed code exception flow to continue as normal.

Co-authored-by: Ryan Lucia <ryan@luciaonline.net>
src/libraries/Native/Unix/System.Native/pal_random.c
src/libraries/Native/Unix/System.Native/pal_random.js [new file with mode: 0644]
src/mono/wasm/Makefile
src/mono/wasm/runtime-test.js
src/mono/wasm/runtime/library_mono.js

index 8f5e892..b824b29 100644 (file)
@@ -63,11 +63,24 @@ Return 0 on success, -1 on failure.
 */
 int32_t SystemNative_GetCryptographicallySecureRandomBytes(uint8_t* buffer, int32_t bufferLength)
 {
+    assert(buffer != NULL);
+
+#ifdef __EMSCRIPTEN__
+    extern int32_t dotnet_browser_entropy(uint8_t* buffer, int32_t bufferLength);
+    static bool sMissingBrowserCrypto;
+    if (!sMissingBrowserCrypto)
+    {
+        int32_t bff = dotnet_browser_entropy(buffer, bufferLength);
+        if (bff == -1)
+            sMissingBrowserCrypto = true;
+        else
+            return 0;
+    }
+#else
+
     static volatile int rand_des = -1;
     static bool sMissingDevURandom;
 
-    assert(buffer != NULL);
-
     if (!sMissingDevURandom)
     {
         if (rand_des == -1)
@@ -121,6 +134,6 @@ int32_t SystemNative_GetCryptographicallySecureRandomBytes(uint8_t* buffer, int3
             return 0;
         }
     }
-
+#endif
     return -1;
 }
diff --git a/src/libraries/Native/Unix/System.Native/pal_random.js b/src/libraries/Native/Unix/System.Native/pal_random.js
new file mode 100644 (file)
index 0000000..231eda0
--- /dev/null
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+var DotNetEntropyLib = {
+    $DOTNETENTROPY: {
+    },
+    dotnet_browser_entropy : function (buffer, bufferLength) {
+        // check that we have crypto available
+        if (typeof crypto === 'object' && typeof crypto['getRandomValues'] === 'function') {
+            // for modern web browsers
+            // map the work array to the memory buffer passed with the length
+            var wrkArray = new Uint8Array(Module.HEAPU8.buffer, buffer, bufferLength);
+            crypto.getRandomValues(wrkArray);
+            return 0;
+        } else {
+            // we couldn't find a proper implementation, as Math.random() is not suitable
+            // instead of aborting here we will return and let managed code handle the message
+            return -1;
+        }
+    },
+};
+
+autoAddDeps(DotNetEntropyLib, '$DOTNETENTROPY')
+mergeInto(LibraryManager.library, DotNetEntropyLib)
index 4fc42d4..12f1962 100644 (file)
@@ -15,6 +15,7 @@ PINVOKE_TABLE?=$(TOP)/artifacts/obj/wasm/pinvoke-table.h
 MONO_BIN_DIR?=$(BINDIR)/mono/Browser.wasm.$(CONFIG)
 NATIVE_BIN_DIR?=$(BINDIR)/native/net5.0-Browser-$(CONFIG)-wasm
 ICU_LIBDIR?=
+SYSTEM_NATIVE_LIBDIR?=$(TOP)/src/libraries/Native/Unix/System.Native
 ENABLE_ES6?=false
 
 all: build-native icu-data
@@ -81,8 +82,8 @@ $(NATIVE_BIN_DIR):
 $(BUILDS_OBJ_DIR):
        mkdir -p $$@
 
-$(NATIVE_BIN_DIR)/dotnet.js: $(BUILDS_OBJ_DIR)/driver.o $(BUILDS_OBJ_DIR)/pinvoke.o $(BUILDS_OBJ_DIR)/corebindings.o runtime/library_mono.js runtime/binding_support.js runtime/dotnet_support.js $(2) | $(NATIVE_BIN_DIR)
-       $(EMCC) $(EMCC_FLAGS) $(1) --js-library runtime/library_mono.js --js-library runtime/binding_support.js --js-library runtime/dotnet_support.js $(BUILDS_OBJ_DIR)/driver.o $(BUILDS_OBJ_DIR)/pinvoke.o $(BUILDS_OBJ_DIR)/corebindings.o $(2) -o $(NATIVE_BIN_DIR)/dotnet.js
+$(NATIVE_BIN_DIR)/dotnet.js: $(BUILDS_OBJ_DIR)/driver.o $(BUILDS_OBJ_DIR)/pinvoke.o $(BUILDS_OBJ_DIR)/corebindings.o runtime/library_mono.js runtime/binding_support.js runtime/dotnet_support.js $(SYSTEM_NATIVE_LIBDIR)/pal_random.js $(2) | $(NATIVE_BIN_DIR)
+       $(EMCC) $(EMCC_FLAGS) $(1) --js-library runtime/library_mono.js --js-library runtime/binding_support.js --js-library runtime/dotnet_support.js --js-library $(SYSTEM_NATIVE_LIBDIR)/pal_random.js $(BUILDS_OBJ_DIR)/driver.o $(BUILDS_OBJ_DIR)/pinvoke.o $(BUILDS_OBJ_DIR)/corebindings.o $(2) -o $(NATIVE_BIN_DIR)/dotnet.js
 
 $(BUILDS_OBJ_DIR)/pinvoke-table.h: $(PINVOKE_TABLE) | $(BUILDS_OBJ_DIR)
        if cmp -s $(PINVOKE_TABLE) $$@ ; then : ; else cp $(PINVOKE_TABLE) $$@ ; fi
index 7cf27af..1d217aa 100644 (file)
@@ -62,12 +62,14 @@ if (typeof console !== "undefined") {
                console.error = console.log;
 }
 
-if (typeof crypto == 'undefined') {
+if (typeof crypto === 'undefined') {
+       // **NOTE** this is a simple insecure polyfill for testing purposes only
        // /dev/random doesn't work on js shells, so define our own
        // See library_fs.js:createDefaultDevices ()
        var crypto = {
                getRandomValues: function (buffer) {
-                       buffer[0] = (Math.random()*256)|0;
+                       for (var i = 0; i < buffer.length; i++)
+                               buffer [i] = (Math.random () * 256) | 0;
                }
        }
 }
index 4ae04e1..dafc3e2 100644 (file)
@@ -211,7 +211,7 @@ var MonoSupportLib = {
                        },
                        /** @returns {ManagedPointer} */
                        get: function (index) {
-                               this._check_in_range (index);                           
+                               this._check_in_range (index);
                                return Module.HEAP32[this.get_address_32 (index)];
                        },
                        set: function (index, value) {
@@ -317,7 +317,7 @@ var MonoSupportLib = {
                                throw new Error ("capacity >= 1");
 
                        capacity = capacity | 0;
-                               
+
                        var capacityBytes = capacity * 4;
                        var offset = Module._malloc (capacityBytes);
                        if ((offset % 4) !== 0)
@@ -328,7 +328,7 @@ var MonoSupportLib = {
                        var result = Object.create (this._mono_wasm_root_buffer_prototype);
                        result.__offset = offset;
                        result.__offset32 = (offset / 4) | 0;
-                       result.__count = capacity;      
+                       result.__count = capacity;
                        result.length = capacity;
                        result.__handle = this.mono_wasm_register_root (offset, capacityBytes, msg || 0);
 
@@ -347,7 +347,7 @@ var MonoSupportLib = {
                mono_wasm_new_root: function (value) {
                        var index = this._mono_wasm_claim_scratch_index ();
                        var buffer = this._scratch_root_buffer;
-                               
+
                        var result = Object.create (this._mono_wasm_root_prototype);
                        result.__buffer = buffer;
                        result.__index = index;
@@ -395,7 +395,7 @@ var MonoSupportLib = {
                 * Multiple objects may be passed on the argument list.
                 * 'undefined' may be passed as an argument so it is safe to call this method from finally blocks
                 *  even if you are not sure all of your roots have been created yet.
-                * @param {... WasmRoot} roots 
+                * @param {... WasmRoot} roots
                 */
                mono_wasm_release_roots: function () {
                        for (var i = 0; i < arguments.length; i++) {