Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Erhardt <eric.erhardt@microsoft.com>2022-07-06 03:46:09 +0300
committerGitHub <noreply@github.com>2022-07-06 03:46:09 +0300
commit4222e699371ed72ac1fe702e5cfb44a01f3847d8 (patch)
treef3a14cf07b502644b8559606a246f2b8018d2fb1 /src/mono/wasm/runtime
parent0e5fcea02506b4f4526aa5e3aaec13d81a181b2d (diff)
Use crypto.subtle for AES on Browser WASM (#71501)
* Use crypto.subtle for AES on Browser WASM Implement the browser "native" portion for AES on Browser WASM. There are two issues to solve .NET's Aes API on crypto.subtle: 1. The .NET API supports streaming while crypto.subtle only supports "one shot" APIs. 2. The .NET API supports multiple padding modes while crypto.subtle only supports PKCS7. To solve these issues, we use the following approach: 1. We only invoke crypto.subtle with complete AES "blocks" of data. This allows us to make assumptions about the padding behavior. 2. To implement streaming, remember the last block of the previous cipher text to use as the IV for the next stream of data. 3. When encrypting, since we have a complete block of data and crypto.subtle uses PKCS7 padding, strip off the last block of cipher text which will always be a full block of padding. 4. When decrypting do the inverse of encrypting - append an encrypted block of padding to the cipher text so crypto.subtle will return the full message as plain text. Other changes: - Make a few refactoring / simplifications where necessary. - SubtleCrypto doesn't support 192 bit AES keys, so no longer support AES-192 on Browser. Contributes to #40074 * Use an empty array to create encrypted padding block.
Diffstat (limited to 'src/mono/wasm/runtime')
-rw-r--r--src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js1
-rw-r--r--src/mono/wasm/runtime/crypto-worker.ts29
-rw-r--r--src/mono/wasm/runtime/es6/dotnet.es6.lib.js1
-rw-r--r--src/mono/wasm/runtime/exports.ts4
-rw-r--r--src/mono/wasm/runtime/workers/dotnet-crypto-worker.js78
5 files changed, 110 insertions, 3 deletions
diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
index 35da8f4d649..cd78c6a2240 100644
--- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
+++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
@@ -90,6 +90,7 @@ const linked_functions = [
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_sign",
+ "dotnet_browser_encrypt_decrypt",
/// mono-threads-wasm.c
#if USE_PTHREADS
diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts
index 92171e76562..674f1700d22 100644
--- a/src/mono/wasm/runtime/crypto-worker.ts
+++ b/src/mono/wasm/runtime/crypto-worker.ts
@@ -54,6 +54,35 @@ export function dotnet_browser_sign(hashAlgorithm: number, key_buffer: number, k
return 1;
}
+const AesBlockSizeBytes = 16; // 128 bits
+
+export function dotnet_browser_encrypt_decrypt(isEncrypting: boolean, key_buffer: number, key_len: number, iv_buffer: number, iv_len: number, input_buffer: number, input_len: number, output_buffer: number, output_len: number): number {
+ mono_assert(!!mono_wasm_crypto, "subtle crypto not initialized");
+
+ if (input_len <= 0 || input_len % AesBlockSizeBytes !== 0) {
+ throw "ENCRYPT DECRYPT: data was not a full block: " + input_len;
+ }
+
+ const msg = {
+ func: "encrypt_decrypt",
+ isEncrypting: isEncrypting,
+ key: Array.from(Module.HEAPU8.subarray(key_buffer, key_buffer + key_len)),
+ iv: Array.from(Module.HEAPU8.subarray(iv_buffer, iv_buffer + iv_len)),
+ data: Array.from(Module.HEAPU8.subarray(input_buffer, input_buffer + input_len))
+ };
+
+ const response = mono_wasm_crypto.channel.send_msg(JSON.stringify(msg));
+ const result = JSON.parse(response);
+
+ if (result.length > output_len) {
+ console.info("dotnet_browser_encrypt_decrypt: about to throw!");
+ throw "ENCRYPT DECRYPT: Encrypt/Decrypt length exceeds output length: " + result.length + " > " + output_len;
+ }
+
+ Module.HEAPU8.set(result, output_buffer);
+ return result.length;
+}
+
export function init_crypto(): void {
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined"
&& typeof SharedArrayBuffer !== "undefined"
diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
index c5851007de1..a559c8f42de 100644
--- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
+++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
@@ -127,6 +127,7 @@ const linked_functions = [
"dotnet_browser_can_use_subtle_crypto_impl",
"dotnet_browser_simple_digest_hash",
"dotnet_browser_sign",
+ "dotnet_browser_encrypt_decrypt",
/// mono-threads-wasm.c
#if USE_PTHREADS
diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts
index d91a67adcfe..436e0ba347a 100644
--- a/src/mono/wasm/runtime/exports.ts
+++ b/src/mono/wasm/runtime/exports.ts
@@ -71,7 +71,8 @@ import { diagnostics } from "./diagnostics";
import {
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
- dotnet_browser_sign
+ dotnet_browser_sign,
+ dotnet_browser_encrypt_decrypt
} from "./crypto-worker";
import { mono_wasm_cancel_promise_ref } from "./cancelable-promise";
import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket";
@@ -406,6 +407,7 @@ export const __linker_exports: any = {
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
dotnet_browser_sign,
+ dotnet_browser_encrypt_decrypt,
// threading exports, if threading is enabled
...mono_wasm_threads_exports,
diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
index 5e27dd59b5f..f6d66c82da6 100644
--- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
+++ b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.js
@@ -187,7 +187,7 @@ async function sign(type, key, data) {
}
function get_hash_name(type) {
- switch(type) {
+ switch (type) {
case 0: return "SHA-1";
case 1: return "SHA-256";
case 2: return "SHA-384";
@@ -197,6 +197,75 @@ function get_hash_name(type) {
}
}
+const AesBlockSizeBytes = 16; // 128 bits
+
+async function encrypt_decrypt(isEncrypting, key, iv, data) {
+ const algorithmName = "AES-CBC";
+ const keyUsage = isEncrypting ? ["encrypt"] : ["encrypt", "decrypt"];
+ const cryptoKey = await importKey(key, algorithmName, keyUsage);
+ const algorithm = {
+ name: algorithmName,
+ iv: new Uint8Array(iv)
+ };
+
+ const result = await (isEncrypting ?
+ crypto.subtle.encrypt(
+ algorithm,
+ cryptoKey,
+ new Uint8Array(data)) :
+ decrypt(
+ algorithm,
+ cryptoKey,
+ data));
+
+ let resultByteArray = new Uint8Array(result);
+ if (isEncrypting) {
+ // trim off the last block, which is always a padding block.
+ resultByteArray = resultByteArray.slice(0, resultByteArray.length - AesBlockSizeBytes);
+ }
+ return Array.from(resultByteArray);
+}
+
+async function decrypt(algorithm, cryptoKey, data) {
+ // crypto.subtle AES-CBC will only allow a PaddingMode of PKCS7, but we need to use
+ // PaddingMode None. To simulate this, we only decrypt full blocks of data, with an extra full
+ // padding block of 0x10 (16) bytes appended to data. crypto.subtle will see that padding block and return
+ // the fully decrypted message. To create the encrypted padding block, we encrypt an empty array using the
+ // last block of the cipher text as the IV. This will create a full block of padding bytes.
+
+ const paddingBlockIV = new Uint8Array(data).slice(data.length - AesBlockSizeBytes);
+ const empty = new Uint8Array();
+ const encryptedPaddingBlockResult = await crypto.subtle.encrypt(
+ {
+ name: algorithm.name,
+ iv: paddingBlockIV
+ },
+ cryptoKey,
+ empty
+ );
+
+ const encryptedPaddingBlock = new Uint8Array(encryptedPaddingBlockResult);
+ for (var i = 0; i < encryptedPaddingBlock.length; i++) {
+ data.push(encryptedPaddingBlock[i]);
+ }
+
+ return await crypto.subtle.decrypt(
+ algorithm,
+ cryptoKey,
+ new Uint8Array(data));
+}
+
+function importKey(key, algorithmName, keyUsage) {
+ return crypto.subtle.importKey(
+ "raw",
+ new Uint8Array(key),
+ {
+ name: algorithmName
+ },
+ false /* extractable */,
+ keyUsage);
+}
+
// Operation to perform.
async function async_call(msg) {
const req = JSON.parse(msg);
@@ -208,7 +277,12 @@ async function async_call(msg) {
else if (req.func === "sign") {
const signResult = await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data));
return JSON.stringify(signResult);
- } else {
+ }
+ else if (req.func === "encrypt_decrypt") {
+ const signResult = await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data);
+ return JSON.stringify(signResult);
+ }
+ else {
throw "CRYPTO: Unknown request: " + req.func;
}
}