diff --git a/Cargo.lock b/Cargo.lock index 7b2076cee6..fa521076ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.4.7" @@ -262,6 +274,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "brotli" version = "3.3.2" @@ -344,6 +372,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" version = "2.33.3" @@ -734,6 +771,8 @@ dependencies = [ name = "deno_crypto" version = "0.34.0" dependencies = [ + "aes", + "block-modes", "deno_core", "deno_web", "lazy_static", @@ -1996,9 +2035,9 @@ checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -3899,9 +3938,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2 1.0.29", "quote 1.0.10", diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 80275b0021..420fa2a7fb 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -513,6 +513,40 @@ unitTest(async function testHkdfDeriveBits() { assertEquals(result.byteLength, 128 / 8); }); +unitTest(async function testAesCbcEncryptDecrypt() { + const key = await crypto.subtle.generateKey( + { name: "AES-CBC", length: 128 }, + true, + ["encrypt", "decrypt"], + ); + + const iv = await crypto.getRandomValues(new Uint8Array(16)); + const encrypted = await crypto.subtle.encrypt( + { + name: "AES-CBC", + iv, + }, + key as CryptoKey, + new Uint8Array([1, 2, 3, 4, 5, 6]), + ); + + assert(encrypted instanceof ArrayBuffer); + assertEquals(encrypted.byteLength, 16); + + const decrypted = await crypto.subtle.decrypt( + { + name: "AES-CBC", + iv, + }, + key as CryptoKey, + encrypted, + ); + + assert(decrypted instanceof ArrayBuffer); + assertEquals(decrypted.byteLength, 6); + assertEquals(new Uint8Array(decrypted), new Uint8Array([1, 2, 3, 4, 5, 6])); +}); + // TODO(@littledivy): Enable WPT when we have importKey support unitTest(async function testECDH() { const namedCurve = "P-256"; diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 4b4770e130..85849153db 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -117,9 +117,11 @@ }, "encrypt": { "RSA-OAEP": "RsaOaepParams", + "AES-CBC": "AesCbcParams", }, "decrypt": { "RSA-OAEP": "RsaOaepParams", + "AES-CBC": "AesCbcParams", }, "wrapKey": { // TODO(@littledivy): Enable this once implemented. @@ -440,6 +442,41 @@ // 6. return cipherText.buffer; } + case "AES-CBC": { + if (ArrayBufferIsView(normalizedAlgorithm.iv)) { + normalizedAlgorithm.iv = new Uint8Array( + normalizedAlgorithm.iv.buffer, + normalizedAlgorithm.iv.byteOffset, + normalizedAlgorithm.iv.byteLength, + ); + } else { + normalizedAlgorithm.iv = new Uint8Array( + normalizedAlgorithm.iv, + ); + } + normalizedAlgorithm.iv = TypedArrayPrototypeSlice( + normalizedAlgorithm.iv, + ); + + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Initialization vector must be 16 bytes", + "OperationError", + ); + } + + // 2. + const cipherText = await core.opAsync("op_crypto_encrypt_key", { + key: keyData, + algorithm: "AES-CBC", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + }, data); + + // 4. + return cipherText.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } @@ -524,6 +561,40 @@ // 6. return plainText.buffer; } + case "AES-CBC": { + if (ArrayBufferIsView(normalizedAlgorithm.iv)) { + normalizedAlgorithm.iv = new Uint8Array( + normalizedAlgorithm.iv.buffer, + normalizedAlgorithm.iv.byteOffset, + normalizedAlgorithm.iv.byteLength, + ); + } else { + normalizedAlgorithm.iv = new Uint8Array( + normalizedAlgorithm.iv, + ); + } + normalizedAlgorithm.iv = TypedArrayPrototypeSlice( + normalizedAlgorithm.iv, + ); + + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Counter must be 16 bytes", + "OperationError", + ); + } + + const plainText = await core.opAsync("op_crypto_decrypt_key", { + key: keyData, + algorithm: "AES-CBC", + iv: normalizedAlgorithm.iv, + length: key[_algorithm].length, + }, data); + + // 6. + return plainText.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index 90bee464c1..c14e4ff225 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -367,6 +367,18 @@ webidl.converters.Pbkdf2Params = webidl .createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params); + const dictAesCbcParams = [ + ...dictAlgorithm, + { + key: "iv", + converter: webidl.converters["BufferSource"], + required: true, + }, + ]; + + webidl.converters.AesCbcParams = webidl + .createDictionaryConverter("AesCbcParams", dictAesCbcParams); + webidl.converters.CryptoKey = webidl.createInterfaceConverter( "CryptoKey", CryptoKey, diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index 060845b709..16839d8d13 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -14,6 +14,8 @@ description = "Web Cryptography API implementation for Deno" path = "lib.rs" [dependencies] +aes = "0.7.5" +block-modes = "0.8.1" deno_core = { version = "0.102.0", path = "../../core" } deno_web = { version = "0.51.0", path = "../web" } lazy_static = "1.4.0" diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index e5592d5bf9..4ed7c51f8b 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -56,6 +56,10 @@ interface JsonWebKey { y?: string; } +interface AesCbcParams extends Algorithm { + iv: BufferSource; +} + interface HmacKeyGenParams extends Algorithm { hash: HashAlgorithmIdentifier; length?: number; @@ -213,12 +217,12 @@ interface SubtleCrypto { data: BufferSource, ): Promise; encrypt( - algorithm: AlgorithmIdentifier | RsaOaepParams, + algorithm: AlgorithmIdentifier | RsaOaepParams | AesCbcParams, key: CryptoKey, data: BufferSource, ): Promise; decrypt( - algorithm: AlgorithmIdentifier | RsaOaepParams, + algorithm: AlgorithmIdentifier | RsaOaepParams | AesCbcParams, key: CryptoKey, data: BufferSource, ): Promise; diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 6376aedbb6..6b67185dd6 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -19,6 +19,7 @@ use std::convert::TryInto; use std::num::NonZeroU32; use std::rc::Rc; +use block_modes::BlockMode; use lazy_static::lazy_static; use num_traits::cast::FromPrimitive; use rand::rngs::OsRng; @@ -892,8 +893,12 @@ pub async fn op_crypto_derive_bits( pub struct EncryptArg { key: KeyData, algorithm: Algorithm, + // RSA-OAEP hash: Option, label: Option, + // AES-CBC + iv: Option, + length: Option, } pub async fn op_crypto_encrypt_key( @@ -945,6 +950,46 @@ pub async fn op_crypto_encrypt_key( .into(), ) } + Algorithm::AesCbc => { + let key = &*args.key.data; + let length = args + .length + .ok_or_else(|| type_error("Missing argument length".to_string()))?; + let iv = args + .iv + .ok_or_else(|| type_error("Missing argument iv".to_string()))?; + + // 2-3. + let ciphertext = match length { + 128 => { + // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 + type Aes128Cbc = + block_modes::Cbc; + + let cipher = Aes128Cbc::new_from_slices(key, &iv)?; + cipher.encrypt_vec(data) + } + 192 => { + // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 + type Aes192Cbc = + block_modes::Cbc; + + let cipher = Aes192Cbc::new_from_slices(key, &iv)?; + cipher.encrypt_vec(data) + } + 256 => { + // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 + type Aes256Cbc = + block_modes::Cbc; + + let cipher = Aes256Cbc::new_from_slices(key, &iv)?; + cipher.encrypt_vec(data) + } + _ => unreachable!(), + }; + + Ok(ciphertext.into()) + } _ => Err(type_error("Unsupported algorithm".to_string())), } } @@ -1451,8 +1496,12 @@ pub async fn op_crypto_import_key( pub struct DecryptArg { key: KeyData, algorithm: Algorithm, + // RSA-OAEP hash: Option, label: Option, + // AES-CBC + iv: Option, + length: Option, } pub async fn op_crypto_decrypt_key( @@ -1503,6 +1552,47 @@ pub async fn op_crypto_decrypt_key( .into(), ) } + Algorithm::AesCbc => { + let key = &*args.key.data; + let length = args + .length + .ok_or_else(|| type_error("Missing argument length".to_string()))?; + let iv = args + .iv + .ok_or_else(|| type_error("Missing argument iv".to_string()))?; + + // 2. + let plaintext = match length { + 128 => { + // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 + type Aes128Cbc = + block_modes::Cbc; + let cipher = Aes128Cbc::new_from_slices(key, &iv)?; + + cipher.decrypt_vec(data)? + } + 192 => { + // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 + type Aes192Cbc = + block_modes::Cbc; + let cipher = Aes192Cbc::new_from_slices(key, &iv)?; + + cipher.decrypt_vec(data)? + } + 256 => { + // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 + type Aes256Cbc = + block_modes::Cbc; + let cipher = Aes256Cbc::new_from_slices(key, &iv)?; + + cipher.decrypt_vec(data)? + } + _ => unreachable!(), + }; + + // 6. + Ok(plaintext.into()) + } _ => Err(type_error("Unsupported algorithm".to_string())), } }