From 3f9187c366be362a219274ded5be9e679b96af98 Mon Sep 17 00:00:00 2001 From: Casper Beyer Date: Sun, 6 Jun 2021 18:57:10 +0800 Subject: [PATCH] feat(extensions/crypto): implement subtle.digest (#10796) Co-authored-by: Yacine Hmito yacinehmito@users.noreply.github.com --- Cargo.lock | 2 + extensions/crypto/00_webidl.js | 26 ++++++ extensions/crypto/01_crypto.js | 107 +++++++++++++++++++++++++ extensions/crypto/Cargo.toml | 2 + extensions/crypto/lib.deno_crypto.d.ts | 32 +++++++- extensions/crypto/lib.rs | 29 +++++++ runtime/js/99_main.js | 1 + tools/wpt/expectation.json | 20 ++--- 8 files changed, 204 insertions(+), 15 deletions(-) create mode 100644 extensions/crypto/00_webidl.js diff --git a/Cargo.lock b/Cargo.lock index d313c3f1d4..211ce5469a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,6 +646,8 @@ dependencies = [ "deno_core", "deno_web", "rand 0.8.3", + "ring", + "tokio", "uuid", ] diff --git a/extensions/crypto/00_webidl.js b/extensions/crypto/00_webidl.js new file mode 100644 index 0000000000..4545526bf9 --- /dev/null +++ b/extensions/crypto/00_webidl.js @@ -0,0 +1,26 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +((window) => { + const webidl = window.__bootstrap.webidl; + webidl.converters["AlgorithmIdentifier"] = (V, opts) => { + // Union for (object or DOMString) + if (typeof V == "object") { + return webidl.converters["object"](V, opts); + } + + return webidl.converters["DOMString"](V, opts); + }; + + const algorithmDictionary = [ + { + key: "name", + converter: webidl.converters["DOMString"], + }, + ]; + + webidl.converters["Algorithm"] = webidl.createDictionaryConverter( + "Algorithm", + algorithmDictionary, + ); +})(this); diff --git a/extensions/crypto/01_crypto.js b/extensions/crypto/01_crypto.js index 2b3982c32c..0f8f72937e 100644 --- a/extensions/crypto/01_crypto.js +++ b/extensions/crypto/01_crypto.js @@ -5,6 +5,102 @@ const core = window.Deno.core; const webidl = window.__bootstrap.webidl; + const supportedAlgorithms = { + "digest": { + "SHA-1": {}, + "SHA-256": {}, + "SHA-384": {}, + "SHA-512": {}, + }, + }; + + function normalizeAlgorithm(algorithm, op) { + if (typeof algorithm == "string") { + return normalizeAlgorithm({ name: algorithm }, op); + } + + const initialAlgorithm = webidl.converters["Algorithm"](algorithm, { + context: "Argument 1", + }); + + const registeredAlgorithms = supportedAlgorithms[op]; + const algorithmName = Object.keys(registeredAlgorithms) + .find((key) => key.toLowerCase() == initialAlgorithm.name.toLowerCase()); + + if (algorithmName === undefined) { + throw new DOMException( + "Unrecognized algorithm name", + "NotSupportedError", + ); + } + + // TODO(caspervonb) Step 6 (create from webidl definition), when the need arises. + // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm + const normalizedAlgorithm = {}; + normalizedAlgorithm.name = algorithmName; + + // TODO(caspervonb) Step 9 and 10, when the need arises. + // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm + return normalizedAlgorithm; + } + + // Should match op_crypto_subtle_digest() in extensions/crypto/lib.rs + function digestToId(name) { + switch (name) { + case "SHA-1": + return 0; + case "SHA-256": + return 1; + case "SHA-384": + return 2; + case "SHA-512": + return 3; + } + } + + class SubtleCrypto { + constructor() { + webidl.illegalConstructor(); + } + + async digest(algorithm, data) { + const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; + + webidl.assertBranded(this, SubtleCrypto); + webidl.requiredArguments(arguments.length, 2); + + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", + }); + + if (ArrayBuffer.isView(data)) { + data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } else { + data = new Uint8Array(data); + } + + data = data.slice(); + + algorithm = normalizeAlgorithm(algorithm, "digest"); + + const result = await core.opAsync( + "op_crypto_subtle_digest", + digestToId(algorithm.name), + data, + ); + + return result.buffer; + } + } + + const subtle = webidl.createBranded(SubtleCrypto); + class Crypto { constructor() { webidl.illegalConstructor(); @@ -48,6 +144,11 @@ return core.opSync("op_crypto_random_uuid"); } + get subtle() { + webidl.assertBranded(this, Crypto); + return subtle; + } + get [Symbol.toStringTag]() { return "Crypto"; } @@ -57,7 +158,13 @@ } } + Object.defineProperty(Crypto.prototype, "subtle", { + configurable: true, + enumerable: true, + }); + window.__bootstrap.crypto = { + SubtleCrypto, crypto: webidl.createBranded(Crypto), Crypto, }; diff --git a/extensions/crypto/Cargo.toml b/extensions/crypto/Cargo.toml index b263b60145..3f4ea1e1a9 100644 --- a/extensions/crypto/Cargo.toml +++ b/extensions/crypto/Cargo.toml @@ -16,5 +16,7 @@ path = "lib.rs" [dependencies] deno_core = { version = "0.88.1", path = "../../core" } deno_web = { version = "0.38.1", path = "../web" } +tokio = { version = "1.6.1", features = ["full"] } rand = "0.8.3" +ring = "0.16.20" uuid = { version = "0.8.2", features = ["v4"] } diff --git a/extensions/crypto/lib.deno_crypto.d.ts b/extensions/crypto/lib.deno_crypto.d.ts index 854696676f..c787f5e3ce 100644 --- a/extensions/crypto/lib.deno_crypto.d.ts +++ b/extensions/crypto/lib.deno_crypto.d.ts @@ -6,7 +6,7 @@ declare var crypto: Crypto; declare interface Crypto { - readonly subtle: null; + readonly subtle: SubtleCrypto; getRandomValues< T extends | Int8Array @@ -25,3 +25,33 @@ declare interface Crypto { ): T; randomUUID(): string; } + +interface Algorithm { + name: string; +} + +/** This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto). */ +interface SubtleCrypto { + digest( + algorithm: AlgorithmIdentifier, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; +} + +declare var SubtleCrypto: { + prototype: SubtleCrypto; + new (): SubtleCrypto; +}; + +type AlgorithmIdentifier = string | Algorithm; diff --git a/extensions/crypto/lib.rs b/extensions/crypto/lib.rs index 0ec7f47179..9d71cc761f 100644 --- a/extensions/crypto/lib.rs +++ b/extensions/crypto/lib.rs @@ -1,7 +1,9 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use deno_core::error::null_opbuf; use deno_core::error::AnyError; use deno_core::include_js_files; +use deno_core::op_async; use deno_core::op_sync; use deno_core::Extension; use deno_core::OpState; @@ -10,7 +12,10 @@ use rand::rngs::StdRng; use rand::thread_rng; use rand::Rng; use rand::SeedableRng; +use ring::digest; +use std::cell::RefCell; use std::path::PathBuf; +use std::rc::Rc; pub use rand; // Re-export rand @@ -18,6 +23,7 @@ pub fn init(maybe_seed: Option) -> Extension { Extension::builder() .js(include_js_files!( prefix "deno:extensions/crypto", + "00_webidl.js", "01_crypto.js", )) .ops(vec![ @@ -25,6 +31,7 @@ pub fn init(maybe_seed: Option) -> Extension { "op_crypto_get_random_values", op_sync(op_crypto_get_random_values), ), + ("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)), ("op_crypto_random_uuid", op_sync(op_crypto_random_uuid)), ]) .state(move |state| { @@ -78,6 +85,28 @@ pub fn op_crypto_random_uuid( Ok(uuid.to_string()) } +pub async fn op_crypto_subtle_digest( + _state: Rc>, + algorithm_id: i8, + data: Option, +) -> Result { + let algorithm = match algorithm_id { + 0 => &digest::SHA1_FOR_LEGACY_USE_ONLY, + 1 => &digest::SHA256, + 2 => &digest::SHA384, + 3 => &digest::SHA512, + _ => panic!("Invalid algorithm id"), + }; + + let input = data.ok_or_else(null_opbuf)?; + let output = tokio::task::spawn_blocking(move || { + digest::digest(algorithm, &input).as_ref().to_vec().into() + }) + .await?; + + Ok(output) +} + pub fn get_declaration() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_crypto.d.ts") } diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 6c79468d1e..f196f3008e 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -314,6 +314,7 @@ delete Object.prototype.__proto__; ), crypto: util.readOnly(crypto.crypto), Crypto: util.nonEnumerable(crypto.Crypto), + SubtleCrypto: util.nonEnumerable(crypto.SubtleCrypto), fetch: util.writable(fetch.fetch), performance: util.writable(performance.performance), setInterval: util.writable(timers.setInterval), diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index d87017c9eb..d724b6b5c9 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -19,7 +19,7 @@ "pbkdf2.https.any.html?8001-last": false }, "digest": { - "digest.https.any.html": false + "digest.https.any.html": true }, "encrypt_decrypt": { "aes_cbc.https.any.html": false, @@ -70,11 +70,12 @@ "successes_RSASSA-PKCS1-v1_5.https.any.html?21-30": false, "successes_RSASSA-PKCS1-v1_5.https.any.html?31-last": false }, - "historical.any.html": true, + "historical.any.html": [ + "Non-secure context window does not have access to crypto.subtle", + "Non-secure context window does not have access to SubtleCrypto" + ], "idlharness.https.any.html": [ - "Crypto interface: attribute subtle", "Crypto interface: operation getRandomValues(ArrayBufferView)", - "Crypto interface: crypto must inherit property \"subtle\" with the proper type", "CryptoKey interface: existence and properties of interface object", "CryptoKey interface object length", "CryptoKey interface object name", @@ -85,12 +86,6 @@ "CryptoKey interface: attribute extractable", "CryptoKey interface: attribute algorithm", "CryptoKey interface: attribute usages", - "SubtleCrypto interface: existence and properties of interface object", - "SubtleCrypto interface object length", - "SubtleCrypto interface object name", - "SubtleCrypto interface: existence and properties of interface prototype object", - "SubtleCrypto interface: existence and properties of interface prototype object's \"constructor\" property", - "SubtleCrypto interface: existence and properties of interface prototype object's @@unscopables property", "SubtleCrypto interface: operation encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)", "SubtleCrypto interface: operation decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)", "SubtleCrypto interface: operation sign(AlgorithmIdentifier, CryptoKey, BufferSource)", @@ -103,7 +98,6 @@ "SubtleCrypto interface: operation exportKey(KeyFormat, CryptoKey)", "SubtleCrypto interface: operation wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)", "SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)", - "SubtleCrypto must be primary interface of crypto.subtle", "Stringification of crypto.subtle", "SubtleCrypto interface: crypto.subtle must inherit property \"encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)\" with the proper type", "SubtleCrypto interface: calling encrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError", @@ -113,8 +107,6 @@ "SubtleCrypto interface: calling sign(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)\" with the proper type", "SubtleCrypto interface: calling verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource) on crypto.subtle with too few arguments must throw TypeError", - "SubtleCrypto interface: crypto.subtle must inherit property \"digest(AlgorithmIdentifier, BufferSource)\" with the proper type", - "SubtleCrypto interface: calling digest(AlgorithmIdentifier, BufferSource) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"generateKey(AlgorithmIdentifier, boolean, sequence)\" with the proper type", "SubtleCrypto interface: calling generateKey(AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)\" with the proper type", @@ -1218,4 +1210,4 @@ "set.any.html": true } } -} \ No newline at end of file +}