diff --git a/Cargo.lock b/Cargo.lock index 9f287ecfc774fa..613108f3ac1f21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2047,6 +2047,7 @@ dependencies = [ "bytes", "cbc", "const-oid", + "ctr", "data-encoding", "deno_core", "deno_error", diff --git a/Cargo.toml b/Cargo.toml index 9014177efa0a7f..e68db18432cb84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,6 +123,7 @@ cbc = { version = "=0.1.2", features = ["alloc"] } chrono = { version = "0.4", default-features = false, features = ["std", "serde"] } color-print = "0.3.5" console_static_text = "=0.8.1" +ctr = { version = "0.9.2", features = ["alloc"] } dashmap = "5.5.3" data-encoding = "2.3.3" data-url = "=0.3.1" diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index c008788f3ab7a5..3d57249e4c53d9 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -20,7 +20,7 @@ aes-kw = { version = "0.2.1", features = ["alloc"] } base64.workspace = true cbc.workspace = true const-oid = "0.9.0" -ctr = "0.9.1" +ctr.workspace = true curve25519-dalek = "4.1.3" deno_core.workspace = true deno_error.workspace = true diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index 05d9520ac3f1fd..9ff96e8179acdf 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -27,6 +27,7 @@ brotli.workspace = true bytes.workspace = true cbc.workspace = true const-oid = "0.9.5" +ctr.workspace = true data-encoding.workspace = true deno_core.workspace = true deno_error.workspace = true diff --git a/ext/node/ops/crypto/cipher.rs b/ext/node/ops/crypto/cipher.rs index 444c01b90026ab..3d116c68e69449 100644 --- a/ext/node/ops/crypto/cipher.rs +++ b/ext/node/ops/crypto/cipher.rs @@ -9,6 +9,7 @@ use aes::cipher::BlockDecryptMut; use aes::cipher::BlockEncryptMut; use aes::cipher::KeyIvInit; use aes::cipher::KeySizeUser; +use aes::cipher::StreamCipher; use deno_core::Resource; use digest::generic_array::GenericArray; use digest::KeyInit; @@ -26,6 +27,9 @@ enum Cipher { Aes128Gcm(Box), Aes256Gcm(Box), Aes256Cbc(Box>), + Aes128Ctr(Box>), + Aes192Ctr(Box>), + Aes256Ctr(Box>), // TODO(kt3k): add more algorithms Aes192Cbc, etc. } @@ -37,6 +41,9 @@ enum Decipher { Aes128Gcm(Box), Aes256Gcm(Box), Aes256Cbc(Box>), + Aes128Ctr(Box>), + Aes192Ctr(Box>), + Aes256Ctr(Box>), // TODO(kt3k): add more algorithms Aes192Cbc, Aes128GCM, etc. } @@ -220,6 +227,33 @@ impl Cipher { Aes256Cbc(Box::new(cbc::Encryptor::new(key.into(), iv.into()))) } + "aes-256-ctr" => { + if key.len() != 32 { + return Err(CipherError::InvalidKeyLength); + } + if iv.len() != 16 { + return Err(CipherError::InvalidInitializationVector); + } + Aes256Ctr(Box::new(ctr::Ctr128BE::new(key.into(), iv.into()))) + } + "aes-192-ctr" => { + if key.len() != 24 { + return Err(CipherError::InvalidKeyLength); + } + if iv.len() != 16 { + return Err(CipherError::InvalidInitializationVector); + } + Aes192Ctr(Box::new(ctr::Ctr128BE::new(key.into(), iv.into()))) + } + "aes-128-ctr" => { + if key.len() != 16 { + return Err(CipherError::InvalidKeyLength); + } + if iv.len() != 16 { + return Err(CipherError::InvalidInitializationVector); + } + Aes128Ctr(Box::new(ctr::Ctr128BE::new(key.into(), iv.into()))) + } _ => return Err(CipherError::UnknownCipher(algorithm_name.to_string())), }) } @@ -279,6 +313,15 @@ impl Cipher { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } + Aes256Ctr(encryptor) => { + encryptor.apply_keystream_b2b(input, output).unwrap(); + } + Aes192Ctr(encryptor) => { + encryptor.apply_keystream_b2b(input, output).unwrap(); + } + Aes128Ctr(encryptor) => { + encryptor.apply_keystream_b2b(input, output).unwrap(); + } } } @@ -359,6 +402,7 @@ impl Cipher { ); Ok(None) } + (Aes256Ctr(_) | Aes128Ctr(_) | Aes192Ctr(_), _) => Ok(None), } } @@ -444,6 +488,33 @@ impl Decipher { Aes256Cbc(Box::new(cbc::Decryptor::new(key.into(), iv.into()))) } + "aes-256-ctr" => { + if key.len() != 32 { + return Err(DecipherError::InvalidKeyLength); + } + if iv.len() != 16 { + return Err(DecipherError::InvalidInitializationVector); + } + Aes256Ctr(Box::new(ctr::Ctr128BE::new(key.into(), iv.into()))) + } + "aes-192-ctr" => { + if key.len() != 24 { + return Err(DecipherError::InvalidKeyLength); + } + if iv.len() != 16 { + return Err(DecipherError::InvalidInitializationVector); + } + Aes192Ctr(Box::new(ctr::Ctr128BE::new(key.into(), iv.into()))) + } + "aes-128-ctr" => { + if key.len() != 16 { + return Err(DecipherError::InvalidKeyLength); + } + if iv.len() != 16 { + return Err(DecipherError::InvalidInitializationVector); + } + Aes128Ctr(Box::new(ctr::Ctr128BE::new(key.into(), iv.into()))) + } _ => { return Err(DecipherError::UnknownCipher(algorithm_name.to_string())) } @@ -505,6 +576,15 @@ impl Decipher { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } + Aes256Ctr(decryptor) => { + decryptor.apply_keystream_b2b(input, output).unwrap(); + } + Aes192Ctr(decryptor) => { + decryptor.apply_keystream_b2b(input, output).unwrap(); + } + Aes128Ctr(decryptor) => { + decryptor.apply_keystream_b2b(input, output).unwrap(); + } } } @@ -615,6 +695,18 @@ impl Decipher { ); Ok(()) } + (Aes256Ctr(mut decryptor), _) => { + decryptor.apply_keystream_b2b(input, output).unwrap(); + Ok(()) + } + (Aes192Ctr(mut decryptor), _) => { + decryptor.apply_keystream_b2b(input, output).unwrap(); + Ok(()) + } + (Aes128Ctr(mut decryptor), _) => { + decryptor.apply_keystream_b2b(input, output).unwrap(); + Ok(()) + } } } } diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts index b0593b7c516d6d..93d61d70aa06a8 100644 --- a/ext/node/polyfills/internal/crypto/cipher.ts +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -186,7 +186,9 @@ export class Cipheriv extends Transform implements Cipher { this.#cache = new BlockModeCache(false); this.#context = op_node_create_cipheriv(cipher, toU8(key), toU8(iv)); this.#needsBlockCache = - !(cipher == "aes-128-gcm" || cipher == "aes-256-gcm"); + !(cipher == "aes-128-gcm" || cipher == "aes-256-gcm" || + cipher == "aes-128-ctr" || cipher == "aes-192-ctr" || + cipher == "aes-256-ctr"); if (this.#context == 0) { throw new TypeError("Unknown cipher"); } @@ -344,7 +346,9 @@ export class Decipheriv extends Transform implements Cipher { this.#cache = new BlockModeCache(this.#autoPadding); this.#context = op_node_create_decipheriv(cipher, toU8(key), toU8(iv)); this.#needsBlockCache = - !(cipher == "aes-128-gcm" || cipher == "aes-256-gcm"); + !(cipher == "aes-128-gcm" || cipher == "aes-256-gcm" || + cipher == "aes-128-ctr" || cipher == "aes-192-ctr" || + cipher == "aes-256-ctr"); if (this.#context == 0) { throw new TypeError("Unknown cipher"); } diff --git a/tests/unit_node/crypto/crypto_cipher_test.ts b/tests/unit_node/crypto/crypto_cipher_test.ts index bc001c8d0f187d..64ff65078ebd4c 100644 --- a/tests/unit_node/crypto/crypto_cipher_test.ts +++ b/tests/unit_node/crypto/crypto_cipher_test.ts @@ -125,6 +125,21 @@ Deno.test({ "dc95c078a2408989ad48a2149284208708c374848c228233c2b34f332bd2e9d38b70c515a6663d38cdb8e6532b266491", "2e62607a5e8b715e4cb229a12169f2b2", ], + [ + ["aes-128-ctr", 16, 16], + "66e94bd4ef8a2c3b884cfa59ca342b2e58e2fccefa7e3061367f1d57a4e7455a0388dace60b6a392f328c2b971b2fe78f795", + "", + ], + [ + ["aes-192-ctr", 24, 16], + "aae06992acbf52a3e8f4a96ec9300bd7cd33b28ac773f74ba00ed1f31257243598e7247c07f0fe411c267e4384b0f6002a34", + "", + ], + [ + ["aes-256-ctr", 32, 16], + "dc95c078a2408989ad48a21492842087530f8afbc74536b9a963b4f1c4cb738bcea7403d4d606b6e074ec5d3baf39d187260", + "", + ], ] as const; for ( const [[alg, keyLen, ivLen], expectedUpdate, expectedFinal] of table @@ -217,13 +232,28 @@ Deno.test({ ["aes-256-cbc", 32, 16], "dc95c078a2408989ad48a2149284208708c374848c228233c2b34f332bd2e9d38b70c515a6663d38cdb8e6532b2664915d0dcc192580aee9ef8a8568193f1b44bfca557c6bab7dc79da07ffd42191b2659e6bee99cb2a6a7299f0e9a21686fc7", ], + [ + ["aes-128-ctr", 16, 16], + "66e94bd4ef8a2c3b884cfa59ca342b2e58e2fccefa7e3061367f1d57a4e7455a0388dace60b6a392f328c2b971b2fe78f795aaab494b5923f7fd89ff948bc1e0200211214e7394da2089b6acd093abe0", + Buffer.alloc(0), + ], + [ + ["aes-192-ctr", 24, 16], + "aae06992acbf52a3e8f4a96ec9300bd7cd33b28ac773f74ba00ed1f31257243598e7247c07f0fe411c267e4384b0f6002a3493e66235ee67deeccd2f3b393bd8fdaa17c2cde20268fe36e164ea532151", + Buffer.alloc(0), + ], + [ + ["aes-256-ctr", 32, 16], + "dc95c078a2408989ad48a21492842087530f8afbc74536b9a963b4f1c4cb738bcea7403d4d606b6e074ec5d3baf39d18726003ca37a62a74d1a2f58e7506358edd4ab1284d4ae17b41e85924470c36f7", + Buffer.alloc(0), + ], ] as const; for ( - const [[alg, keyLen, ivLen], input] of table + const [[alg, keyLen, ivLen], input, final] 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)); + assertEquals(cipher.final(), final ?? Buffer.alloc(10)); } }, }); @@ -264,38 +294,36 @@ Deno.test({ Deno.test({ name: "createCipheriv - invalid inputs", fn() { - assertThrows( - () => - crypto.createCipheriv("aes256", new Uint8Array(31), new Uint8Array(16)), - RangeError, - "Invalid key length", - ); - assertThrows( - () => - crypto.createCipheriv( - "aes-256-cbc", - new Uint8Array(31), - new Uint8Array(16), - ), - RangeError, - "Invalid key length", - ); - assertThrows( - () => - crypto.createCipheriv("aes256", new Uint8Array(32), new Uint8Array(15)), - TypeError, - "Invalid initialization vector", - ); - assertThrows( - () => - crypto.createCipheriv( - "aes-256-cbc", - new Uint8Array(32), - new Uint8Array(15), - ), - TypeError, - "Invalid initialization vector", - ); + const enum Invalid { + Key, + Iv, + } + const table = [ + ["aes256", 31, 16, Invalid.Key], + ["aes-256-cbc", 31, 16, Invalid.Key], + ["aes256", 32, 15, Invalid.Iv], + ["aes-256-cbc", 32, 15, Invalid.Iv], + ["aes-128-ctr", 32, 16, Invalid.Key], + ["aes-128-ctr", 16, 32, Invalid.Iv], + ["aes-192-ctr", 16, 16, Invalid.Key], + ["aes-192-ctr", 24, 32, Invalid.Iv], + ["aes-256-ctr", 16, 16, Invalid.Key], + ["aes-256-ctr", 32, 32, Invalid.Iv], + ] as const; + for (const [algorithm, keyLen, ivLen, invalid] of table) { + assertThrows( + () => + crypto.createCipheriv( + algorithm, + new Uint8Array(keyLen), + new Uint8Array(ivLen), + ), + invalid === Invalid.Key ? RangeError : TypeError, + invalid === Invalid.Key + ? "Invalid key length" + : "Invalid initialization vector", + ); + } }, }); @@ -314,46 +342,36 @@ Deno.test({ Deno.test({ name: "createDecipheriv - invalid inputs", fn() { - assertThrows( - () => - crypto.createDecipheriv( - "aes256", - new Uint8Array(31), - new Uint8Array(16), - ), - RangeError, - "Invalid key length", - ); - assertThrows( - () => - crypto.createDecipheriv( - "aes-256-cbc", - new Uint8Array(31), - new Uint8Array(16), - ), - RangeError, - "Invalid key length", - ); - assertThrows( - () => - crypto.createDecipheriv( - "aes256", - new Uint8Array(32), - new Uint8Array(15), - ), - TypeError, - "Invalid initialization vector", - ); - assertThrows( - () => - crypto.createDecipheriv( - "aes-256-cbc", - new Uint8Array(32), - new Uint8Array(15), - ), - TypeError, - "Invalid initialization vector", - ); + const enum Invalid { + Key, + Iv, + } + const table = [ + ["aes256", 31, 16, Invalid.Key], + ["aes-256-cbc", 31, 16, Invalid.Key], + ["aes256", 32, 15, Invalid.Iv], + ["aes-256-cbc", 32, 15, Invalid.Iv], + ["aes-128-ctr", 32, 16, Invalid.Key], + ["aes-128-ctr", 16, 32, Invalid.Iv], + ["aes-192-ctr", 16, 16, Invalid.Key], + ["aes-192-ctr", 24, 32, Invalid.Iv], + ["aes-256-ctr", 16, 16, Invalid.Key], + ["aes-256-ctr", 32, 32, Invalid.Iv], + ] as const; + for (const [algorithm, keyLen, ivLen, invalid] of table) { + assertThrows( + () => + crypto.createDecipheriv( + algorithm, + new Uint8Array(keyLen), + new Uint8Array(ivLen), + ), + invalid === Invalid.Key ? RangeError : TypeError, + invalid === Invalid.Key + ? "Invalid key length" + : "Invalid initialization vector", + ); + } }, });