diff --git a/Cargo.lock b/Cargo.lock index 90ab50377c..31874a748a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1150,6 +1150,7 @@ dependencies = [ "cbc", "deno_core", "digest 0.10.6", + "ecb", "hex", "idna 0.3.0", "indexmap", @@ -1526,6 +1527,15 @@ dependencies = [ "memmap2", ] +[[package]] +name = "ecb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17fd84ba81a904351ee27bbccb4aa2461e1cca04176a63ab4f8ca087757681a2" +dependencies = [ + "cipher", +] + [[package]] name = "ecdsa" version = "0.14.8" diff --git a/Cargo.toml b/Cargo.toml index a277bec7c9..b364a9cf44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ console_static_text = "=0.7.1" data-url = "=0.2.0" dlopen = "0.1.8" encoding_rs = "=0.8.31" +ecb = "=0.1.1" flate2 = "=1.0.24" fs3 = "0.5.0" futures = "0.3.21" diff --git a/cli/tests/unit_node/crypto_cipher_test.ts b/cli/tests/unit_node/crypto_cipher_test.ts index 3f740f40c5..2c8cca2567 100644 --- a/cli/tests/unit_node/crypto_cipher_test.ts +++ b/cli/tests/unit_node/crypto_cipher_test.ts @@ -17,6 +17,10 @@ const rsaPublicKey = Deno.readTextFileSync( const input = new TextEncoder().encode("hello world"); +function zeros(length: number): Uint8Array { + return new Uint8Array(length); +} + Deno.test({ name: "rsa public encrypt and private decrypt", fn() { @@ -52,7 +56,7 @@ Deno.test({ }); Deno.test({ - name: "createCipheriv - basic", + name: "createCipheriv - multiple chunk inputs", fn() { const cipher = crypto.createCipheriv( "aes-128-cbc", @@ -75,6 +79,31 @@ Deno.test({ }, }); +Deno.test({ + name: "createCipheriv - algorithms", + fn() { + const table = [ + [ + ["aes-128-cbc", 16, 16], + "66e94bd4ef8a2c3b884cfa59ca342b2ef795bd4a52e29ed713d313fa20e98dbca10cf66d0fddf3405370b4bf8df5bfb3", + "d5f65ecda64511e9d3d12206411ffd72", + ], + [ + ["aes-128-ecb", 16, 0], + "66e94bd4ef8a2c3b884cfa59ca342b2e66e94bd4ef8a2c3b884cfa59ca342b2e66e94bd4ef8a2c3b884cfa59ca342b2e", + "baf823258ca2e6994f638daa3515e986", + ], + ] as const; + for ( + const [[alg, keyLen, ivLen], expectedUpdate, expectedFinal] of table + ) { + const cipher = crypto.createCipheriv(alg, zeros(keyLen), zeros(ivLen)); + assertEquals(cipher.update(zeros(50), undefined, "hex"), expectedUpdate); + assertEquals(cipher.final("hex"), expectedFinal); + } + }, +}); + Deno.test({ name: "createCipheriv - input encoding", fn() { @@ -113,24 +142,25 @@ Deno.test({ }); Deno.test({ - name: "createDecipheriv - basic", + name: "createDecipheriv - algorithms", fn() { - const decipher = crypto.createDecipheriv( - "aes-128-cbc", - new Uint8Array(16), - new Uint8Array(16), - ); - assertEquals( - decipher.update( + const table = [ + [ + ["aes-128-cbc", 16, 16], "66e94bd4ef8a2c3b884cfa59ca342b2ef795bd4a52e29ed713d313fa20e98dbca10cf66d0fddf3405370b4bf8df5bfb347c78395e0d8ae2194da0a90abc9888a94ee48f6c78fcd518a941c3896102cb1e11901dde4a2f99fe4efc707e48c6aed", - "hex", - ), - Buffer.alloc(80), - ); - assertEquals( - decipher.final(), - Buffer.alloc(10), // Checks the padding - ); + ], + [ + ["aes-128-ecb", 16, 0], + "66e94bd4ef8a2c3b884cfa59ca342b2e66e94bd4ef8a2c3b884cfa59ca342b2e66e94bd4ef8a2c3b884cfa59ca342b2e66e94bd4ef8a2c3b884cfa59ca342b2e66e94bd4ef8a2c3b884cfa59ca342b2ec29a917cbaf72fa9bc32129bb0d17663", + ], + ] as const; + for ( + const [[alg, keyLen, ivLen], input] of table + ) { + const cipher = crypto.createDecipheriv(alg, zeros(keyLen), zeros(ivLen)); + assertEquals(cipher.update(input, "hex"), Buffer.alloc(80)); + assertEquals(cipher.final(), Buffer.alloc(10)); + } }, }); diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index b555111cd5..1cd742defb 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -18,6 +18,7 @@ aes.workspace = true cbc.workspace = true deno_core.workspace = true digest = { version = "0.10.5", features = ["core-api", "std"] } +ecb.workspace = true hex.workspace = true idna = "0.3.0" indexmap.workspace = true diff --git a/ext/node/crypto/cipher.rs b/ext/node/crypto/cipher.rs index 54cd611329..4f3f7f20dc 100644 --- a/ext/node/crypto/cipher.rs +++ b/ext/node/crypto/cipher.rs @@ -7,6 +7,7 @@ use aes::cipher::KeyIvInit; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::Resource; +use digest::KeyInit; use std::borrow::Cow; use std::cell::RefCell; @@ -14,12 +15,14 @@ use std::rc::Rc; enum Cipher { Aes128Cbc(Box>), - // TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128ECB, Aes128GCM, etc. + Aes128Ecb(Box>), + // TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128GCM, etc. } enum Decipher { Aes128Cbc(Box>), - // TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128ECB, Aes128GCM, etc. + Aes128Ecb(Box>), + // TODO(kt3k): add more algorithms Aes192Cbc, Aes256Cbc, Aes128GCM, etc. } pub struct CipherContext { @@ -99,6 +102,7 @@ impl Cipher { "aes-128-cbc" => { Aes128Cbc(Box::new(cbc::Encryptor::new(key.into(), iv.into()))) } + "aes-128-ecb" => Aes128Ecb(Box::new(ecb::Encryptor::new(key.into()))), _ => return Err(type_error(format!("Unknown cipher {algorithm_name}"))), }) } @@ -113,6 +117,12 @@ impl Cipher { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } + Aes128Ecb(encryptor) => { + assert!(input.len() % 16 == 0); + for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { + encryptor.encrypt_block_b2b_mut(input.into(), output.into()); + } + } } } @@ -127,6 +137,12 @@ impl Cipher { .map_err(|_| type_error("Cannot pad the input data"))?; Ok(()) } + Aes128Ecb(encryptor) => { + let _ = (*encryptor) + .encrypt_padded_b2b_mut::(input, output) + .map_err(|_| type_error("Cannot pad the input data"))?; + Ok(()) + } } } } @@ -142,6 +158,7 @@ impl Decipher { "aes-128-cbc" => { Aes128Cbc(Box::new(cbc::Decryptor::new(key.into(), iv.into()))) } + "aes-128-ecb" => Aes128Ecb(Box::new(ecb::Decryptor::new(key.into()))), _ => return Err(type_error(format!("Unknown cipher {algorithm_name}"))), }) } @@ -156,6 +173,12 @@ impl Decipher { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } + Aes128Ecb(decryptor) => { + assert!(input.len() % 16 == 0); + for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { + decryptor.decrypt_block_b2b_mut(input.into(), output.into()); + } + } } } @@ -170,6 +193,12 @@ impl Decipher { .map_err(|_| type_error("Cannot unpad the input data"))?; Ok(()) } + Aes128Ecb(decryptor) => { + let _ = (*decryptor) + .decrypt_padded_b2b_mut::(input, output) + .map_err(|_| type_error("Cannot unpad the input data"))?; + Ok(()) + } } } } diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts index 670c1bcce1..050cf59048 100644 --- a/ext/node/polyfills/internal/crypto/cipher.ts +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -18,7 +18,7 @@ import type { } from "ext:deno_node/internal/crypto/types.ts"; import { getDefaultEncoding } from "ext:deno_node/internal/crypto/util.ts"; -const { ops } = globalThis.__bootstrap.core; +const { ops, encode } = globalThis.__bootstrap.core; export type CipherCCMTypes = | "aes-128-ccm" @@ -116,6 +116,10 @@ export interface DecipherOCB extends Decipher { ): this; } +function toU8(input: string | Uint8Array): Uint8Array { + return typeof input === "string" ? encode(input) : input; +} + export class Cipheriv extends Transform implements Cipher { /** CipherContext resource id */ #context: number; @@ -141,7 +145,7 @@ export class Cipheriv extends Transform implements Cipher { ...options, }); this.#cache = new BlockModeCache(false); - this.#context = ops.op_node_create_cipheriv(cipher, key, iv); + this.#context = ops.op_node_create_cipheriv(cipher, toU8(key), toU8(iv)); } final(encoding: string = getDefaultEncoding()): Buffer | string { @@ -257,7 +261,7 @@ export class Decipheriv extends Transform implements Cipher { ...options, }); this.#cache = new BlockModeCache(true); - this.#context = ops.op_node_create_decipheriv(cipher, key, iv); + this.#context = ops.op_node_create_decipheriv(cipher, toU8(key), toU8(iv)); } final(encoding: string = getDefaultEncoding()): Buffer | string {