diff --git a/Cargo.lock b/Cargo.lock index 64c51af84418c2..3368e5da298aea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,6 +404,7 @@ dependencies = [ "chrono", "clap", "deno_core", + "deno_crypto", "deno_doc", "deno_fetch", "deno_lint", @@ -483,6 +484,10 @@ version = "0.13.0" dependencies = [ "deno_core", "rand 0.8.3", + "ring", + "serde", + "serde_json", + "tokio", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f05865faff28e9..4811b58e15a6ef 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,6 +28,7 @@ deno_core = { path = "../core", version = "0.79.0" } deno_fetch = { path = "../op_crates/fetch", version = "0.22.0" } deno_web = { path = "../op_crates/web", version = "0.30.0" } deno_websocket = { path = "../op_crates/websocket", version = "0.5.0" } +deno_crypto = { path = "../op_crates/crypto", version = "0.13.0" } regex = "1.4.3" serde = { version = "1.0.123", features = ["derive"] } diff --git a/cli/build.rs b/cli/build.rs index ef6e0350329fb8..cf32d1f0072439 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -59,6 +59,7 @@ fn create_compiler_snapshot( op_crate_libs.insert("deno.web", deno_web::get_declaration()); op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration()); op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration()); + op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration()); // ensure we invalidate the build properly. for (_, path) in op_crate_libs.iter() { @@ -259,6 +260,10 @@ fn main() { "cargo:rustc-env=DENO_WEBSOCKET_LIB_PATH={}", deno_websocket::get_declaration().display() ); + println!( + "cargo:rustc-env=DENO_CRYPTO_LIB_PATH={}", + deno_crypto::get_declaration().display() + ); println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap()); println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap()); diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts index 9cfe45cfa73a99..56985942f6dac6 100644 --- a/cli/dts/lib.deno.shared_globals.d.ts +++ b/cli/dts/lib.deno.shared_globals.d.ts @@ -8,6 +8,7 @@ /// /// /// +/// declare namespace WebAssembly { /** @@ -416,26 +417,6 @@ declare interface Console { declare var console: Console; -declare interface Crypto { - readonly subtle: null; - getRandomValues< - T extends - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | null, - >( - array: T, - ): T; -} - interface MessageEventInit extends EventInit { data?: T; origin?: string; diff --git a/cli/main.rs b/cli/main.rs index 779c45d53d7637..9023689eb8e1a7 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -280,11 +280,12 @@ fn print_cache_info( fn get_types(unstable: bool) -> String { let mut types = format!( - "{}\n{}\n{}\n{}\n{}\n{}", + "{}\n{}\n{}\n{}\n{}\n{}\n{}", crate::tsc::DENO_NS_LIB, crate::tsc::DENO_WEB_LIB, crate::tsc::DENO_FETCH_LIB, crate::tsc::DENO_WEBSOCKET_LIB, + crate::tsc::DENO_CRYPTO_LIB, crate::tsc::SHARED_GLOBALS_LIB, crate::tsc::WINDOW_LIB, ); diff --git a/cli/tsc.rs b/cli/tsc.rs index 5b6d00310a8b79..a8593d9075c449 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -33,6 +33,7 @@ pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH")); pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH")); pub static DENO_WEBSOCKET_LIB: &str = include_str!(env!("DENO_WEBSOCKET_LIB_PATH")); +pub static DENO_CRYPTO_LIB: &str = include_str!(env!("DENO_CRYPTO_LIB_PATH")); pub static SHARED_GLOBALS_LIB: &str = include_str!("dts/lib.deno.shared_globals.d.ts"); pub static WINDOW_LIB: &str = include_str!("dts/lib.deno.window.d.ts"); diff --git a/op_crates/crypto/01_crypto.js b/op_crates/crypto/01_crypto.js index ce13dc74c11e4e..1696a12ccd6287 100644 --- a/op_crates/crypto/01_crypto.js +++ b/op_crates/crypto/01_crypto.js @@ -1,6 +1,8 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. "use strict"; +// Implements https://www.w3.org/TR/WebCryptoAPI + ((window) => { const core = window.Deno.core; @@ -41,11 +43,145 @@ return arrayBufferView; } + // Algorithm normalization, which involves storing the expected data for + // each pair of algorithm and crypto operation, is done on the JS side because + // it is convenient. + // Shall it stay that way, or are we better to move it on the Rust side? + + // We need this method after initialization, anytime we need to normalize + // a provided algorithm. We store it here to prevent prototype pollution. + const toUpperCase = String.prototype.toUpperCase; + + class RegisteredAlgorithmsContainer { + #nameIndex; + #definitions; + + constructor(definitions) { + this.#nameIndex = Object.create(null); + this.#definitions = Object.create(null); + for (const [name, definition] of Object.entries(definitions)) { + this.#nameIndex[name.toUpperCase()] = name; + this.#definitions[name] = definition; + } + } + + /** + * A definition is an object whose keys are the keys that the input must + * have and whose values are validation functions for the associate value. + * The validation function will either return the value if it valid, or + * throw an error that must be forwarded. + */ + getDefinition(name) { + return this.#definitions[name]; + } + + normalizeName(providedName) { + const upperCaseName = toUpperCase.call(providedName); + return this.#nameIndex[upperCaseName]; + } + } + + const supportedAlgorithms = {}; + + function normalizeAlgorithm(algorithm, registeredAlgorithms) { + let alg; + if (typeof algorithm === "string") { + alg = { name: algorithm }; + } else if (typeof algorithm === "object" && algorithm !== null) { + if (typeof algorithm.name !== "string") { + throw new TypeError("Algorithm name is missing or not a string"); + } + alg = { ...algorithm }; + } else { + throw new TypeError("Argument 1 must be an object or a string"); + } + const algorithmName = registeredAlgorithms.normalizeName(alg.name); + if (algorithmName === undefined) { + throw new DOMException( + "Unrecognized algorithm name", + "NotSupportedError", + ); + } + const definition = registeredAlgorithms.getDefinition(algorithmName); + for (const [propertyName, validate] of Object.entries(definition)) { + alg[propertyName] = validate(algorithm[propertyName]); + } + alg.name = algorithmName; + return alg; + } + + const subtle = { + async decrypt(algorithm, key, data) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async deriveBits(algorithm, baseKey, length) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async deriveKey( + algorithm, + baseKey, + derivedKeyType, + extractable, + keyUsages, + ) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async digest(algorithm, data) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async encrypt(algorithm, key, data) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async exportKey(format, key) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async generateKey(algorithm, extractable, keyUsages) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async importKey(format, keyData, algorithm, extractable, keyUsages) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async sign(algorithm, key, data) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async unwrapKey( + format, + wrappedKey, + unwrappingKey, + unwrapAlgorithm, + unwrappedKeyAlgorithm, + extractable, + keyUsages, + ) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async verify(algorithm, key, signature, data) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + async wrapKey(format, key, wrappingKey, wrapAlgorithm) { + await Promise.resolve(); + throw new Error("Not implemented"); + }, + }; + window.crypto = { getRandomValues, + subtle, }; window.__bootstrap = window.__bootstrap || {}; window.__bootstrap.crypto = { getRandomValues, + subtle, }; })(this); diff --git a/op_crates/crypto/Cargo.toml b/op_crates/crypto/Cargo.toml index 641a1cf6615392..f3c99f2cd2f013 100644 --- a/op_crates/crypto/Cargo.toml +++ b/op_crates/crypto/Cargo.toml @@ -16,4 +16,7 @@ path = "lib.rs" [dependencies] deno_core = { version = "0.79.0", path = "../../core" } rand = "0.8.3" - +ring = "0.16.19" +tokio = { version = "1.1.1", features = ["full"] } +serde_json = { version = "1.0.62", features = ["preserve_order"] } +serde = { version = "1.0.123", features = ["derive"] } diff --git a/op_crates/crypto/lib.deno_crypto.d.ts b/op_crates/crypto/lib.deno_crypto.d.ts new file mode 100644 index 00000000000000..72e9f89204fcbb --- /dev/null +++ b/op_crates/crypto/lib.deno_crypto.d.ts @@ -0,0 +1,573 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file camelcase + +/// +/// + +declare interface Crypto { + readonly subtle: SubtleCrypto; + getRandomValues< + T extends + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | null, + >( + array: T, + ): T; +} + +declare interface SubtleCrypto { + decrypt( + algorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesCmacParams + | AesGcmParams + | AesCfbParams, + key: CryptoKey, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; + deriveBits( + algorithm: + | AlgorithmIdentifier + | EcdhKeyDeriveParams + | DhKeyDeriveParams + | ConcatParams + | HkdfParams + | Pbkdf2Params, + baseKey: CryptoKey, + length: number, + ): Promise; + deriveKey( + algorithm: + | AlgorithmIdentifier + | EcdhKeyDeriveParams + | DhKeyDeriveParams + | ConcatParams + | HkdfParams + | Pbkdf2Params, + baseKey: CryptoKey, + derivedKeyType: + | string + | AesDerivedKeyParams + | HmacImportParams + | ConcatParams + | HkdfParams + | Pbkdf2Params, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise; + digest( + algorithm: AlgorithmIdentifier, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; + encrypt( + algorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesCmacParams + | AesGcmParams + | AesCfbParams, + key: CryptoKey, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; + exportKey(format: "jwk", key: CryptoKey): Promise; + exportKey( + format: "raw" | "pkcs8" | "spki", + key: CryptoKey, + ): Promise; + exportKey(format: string, key: CryptoKey): Promise; + generateKey( + algorithm: RsaHashedKeyGenParams | EcKeyGenParams | DhKeyGenParams, + extractable: boolean, + keyUsages: Iterable, + ): Promise; + generateKey( + algorithm: AesKeyGenParams | HmacKeyGenParams | Pbkdf2Params, + extractable: boolean, + keyUsages: Iterable, + ): Promise; + generateKey( + algorithm: AlgorithmIdentifier, + extractable: boolean, + keyUsages: Iterable, + ): Promise; + importKey( + format: "jwk", + keyData: JsonWebKey, + algorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | DhImportKeyParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise; + importKey( + format: "raw" | "pkcs8" | "spki", + keyData: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + algorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | DhImportKeyParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise; + importKey( + format: string, + keyData: + | JsonWebKey + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + algorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | DhImportKeyParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise; + sign( + algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams | AesCmacParams, + key: CryptoKey, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; + unwrapKey( + format: "raw" | "pkcs8" | "spki" | "jwk" | string, + wrappedKey: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + unwrappingKey: CryptoKey, + unwrapAlgorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesCmacParams + | AesGcmParams + | AesCfbParams, + unwrappedKeyAlgorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | EcKeyImportParams + | HmacImportParams + | DhImportKeyParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise; + verify( + algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams | AesCmacParams, + key: CryptoKey, + signature: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer, + ): Promise; + wrapKey( + format: "raw" | "pkcs8" | "spki" | "jwk" | string, + key: CryptoKey, + wrappingKey: CryptoKey, + wrapAlgorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCtrParams + | AesCbcParams + | AesCmacParams + | AesGcmParams + | AesCfbParams, + ): Promise; +} + +type AlgorithmIdentifier = string | Algorithm; + +type HashAlgorithmIdentifier = AlgorithmIdentifier; + +type KeyType = "private" | "public" | "secret"; + +type KeyUsage = + | "decrypt" + | "deriveBits" + | "deriveKey" + | "encrypt" + | "sign" + | "unwrapKey" + | "verify" + | "wrapKey"; + +interface CryptoKey { + readonly algorithm: Algorithm; + readonly extractable: boolean; + readonly type: KeyType; + readonly usages: KeyUsage[]; +} + +interface CryptoKeyPair { + privateKey?: CryptoKey; + publicKey?: CryptoKey; +} + +interface JsonWebKey { + alg?: string; + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + ext?: boolean; + k?: string; + key_ops?: string[]; + kty?: string; + n?: string; + oth?: RsaOtherPrimesInfo[]; + p?: string; + q?: string; + qi?: string; + use?: string; + x?: string; + y?: string; +} + +interface Algorithm { + name: string; +} + +interface RsaHashedImportParams extends Algorithm { + hash: HashAlgorithmIdentifier; +} + +interface RsaHashedKeyGenParams extends RsaKeyGenParams { + hash: HashAlgorithmIdentifier; +} + +interface RsaKeyGenParams extends Algorithm { + modulusLength: number; + publicExponent: Uint8Array; +} + +interface RsaOaepParams extends Algorithm { + label?: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; +} + +interface RsaOtherPrimesInfo { + d?: string; + r?: string; + t?: string; +} + +interface RsaPssParams extends Algorithm { + saltLength: number; +} + +interface AesCbcParams extends Algorithm { + iv: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; +} + +interface AesCtrParams extends Algorithm { + counter: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; + length: number; +} + +interface AesDerivedKeyParams extends Algorithm { + length: number; +} + +interface AesGcmParams extends Algorithm { + additionalData?: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; + iv: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; + tagLength?: number; +} + +interface AesKeyAlgorithm extends Algorithm { + length: number; +} + +interface AesKeyGenParams extends Algorithm { + length: number; +} + +interface AesCfbParams extends Algorithm { + iv: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; +} + +interface AesCmacParams extends Algorithm { + length: number; +} + +interface EcKeyGenParams extends Algorithm { + namedCurve: string; +} + +interface EcKeyImportParams extends Algorithm { + namedCurve: string; +} + +interface EcdhKeyDeriveParams extends Algorithm { + public: CryptoKey; +} + +interface EcdsaParams extends Algorithm { + hash: HashAlgorithmIdentifier; +} + +interface DhImportKeyParams extends Algorithm { + generator: Uint8Array; + prime: Uint8Array; +} + +interface DhKeyAlgorithm extends Algorithm { + generator: Uint8Array; + prime: Uint8Array; +} + +interface DhKeyDeriveParams extends Algorithm { + public: CryptoKey; +} + +interface DhKeyGenParams extends Algorithm { + generator: Uint8Array; + prime: Uint8Array; +} + +interface ConcatParams extends Algorithm { + algorithmId: Uint8Array; + hash?: string | Algorithm; + partyUInfo: Uint8Array; + partyVInfo: Uint8Array; + privateInfo?: Uint8Array; + publicInfo?: Uint8Array; +} + +interface HkdfParams extends Algorithm { + hash: HashAlgorithmIdentifier; + info: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; + salt: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; +} + +interface HmacImportParams extends Algorithm { + hash: HashAlgorithmIdentifier; + length?: number; +} + +interface HmacKeyGenParams extends Algorithm { + hash: HashAlgorithmIdentifier; + length?: number; +} + +interface Pbkdf2Params extends Algorithm { + hash: HashAlgorithmIdentifier; + iterations: number; + salt: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer; +} diff --git a/op_crates/crypto/lib.rs b/op_crates/crypto/lib.rs index 61290080d27a24..b8fe3fefb534b8 100644 --- a/op_crates/crypto/lib.rs +++ b/op_crates/crypto/lib.rs @@ -11,6 +11,7 @@ use deno_core::ZeroCopyBuf; use rand::rngs::StdRng; use rand::thread_rng; use rand::Rng; +use std::path::PathBuf; pub use rand; // Re-export rand @@ -41,3 +42,7 @@ pub fn op_crypto_get_random_values( Ok(json!({})) } + +pub fn get_declaration() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_crypto.d.ts") +} diff --git a/runtime/ops/crypto.rs b/runtime/ops/crypto.rs index 43a9d11267251e..f9833d9ecd35e9 100644 --- a/runtime/ops/crypto.rs +++ b/runtime/ops/crypto.rs @@ -1,5 +1,5 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use deno_crypto::op_crypto_get_random_values; + use deno_crypto::rand::rngs::StdRng; use deno_crypto::rand::SeedableRng; @@ -13,6 +13,6 @@ pub fn init(rt: &mut deno_core::JsRuntime, maybe_seed: Option) { super::reg_json_sync( rt, "op_crypto_get_random_values", - op_crypto_get_random_values, + deno_crypto::op_crypto_get_random_values, ); }