From 0d1beed2e3633d71d5e288e0382b85be361ec13d Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 8 Aug 2024 06:04:10 -0700 Subject: [PATCH] fix(ext/node): add `CipherIv.setAutoPadding()` (#24940) Co-Authored-By: Luca Casonato Fixes https://github.com/denoland/deno/issues/21804 Ref https://github.com/denoland/deno/issues/20924 --------- Signed-off-by: Divy Srivastava Co-authored-by: Luca Casonato --- ext/node/lib.rs | 1 + ext/node/ops/crypto/cipher.rs | 123 ++++++++++++++++--- ext/node/ops/crypto/mod.rs | 21 +++- ext/node/polyfills/internal/crypto/cipher.ts | 32 +++-- 4 files changed, 146 insertions(+), 31 deletions(-) diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 51b22cefb2..2005e4c315 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -232,6 +232,7 @@ deno_core::extension!(deno_node, ops::crypto::op_node_decipheriv_decrypt, ops::crypto::op_node_decipheriv_final, ops::crypto::op_node_decipheriv_set_aad, + ops::crypto::op_node_decipheriv_take, ops::crypto::op_node_dh_compute_secret, ops::crypto::op_node_diffie_hellman, ops::crypto::op_node_ecdh_compute_public_key, diff --git a/ext/node/ops/crypto/cipher.rs b/ext/node/ops/crypto/cipher.rs index 1072cc8c0f..0c1218d31a 100644 --- a/ext/node/ops/crypto/cipher.rs +++ b/ext/node/ops/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::generic_array::GenericArray; use digest::KeyInit; use std::borrow::Cow; @@ -65,13 +66,14 @@ impl CipherContext { pub fn r#final( self, + auto_pad: bool, input: &[u8], output: &mut [u8], ) -> Result { Rc::try_unwrap(self.cipher) .map_err(|_| type_error("Cipher context is already in use"))? .into_inner() - .r#final(input, output) + .r#final(auto_pad, input, output) } } @@ -92,6 +94,7 @@ impl DecipherContext { pub fn r#final( self, + auto_pad: bool, input: &[u8], output: &mut [u8], auth_tag: &[u8], @@ -99,7 +102,7 @@ impl DecipherContext { Rc::try_unwrap(self.decipher) .map_err(|_| type_error("Decipher context is already in use"))? .into_inner() - .r#final(input, output, auth_tag) + .r#final(auto_pad, input, output, auth_tag) } } @@ -209,42 +212,82 @@ impl Cipher { } /// r#final encrypts the last block of the input data. - fn r#final(self, input: &[u8], output: &mut [u8]) -> Result { + fn r#final( + self, + auto_pad: bool, + input: &[u8], + output: &mut [u8], + ) -> Result { assert!(input.len() < 16); use Cipher::*; - match self { - Aes128Cbc(encryptor) => { + match (self, auto_pad) { + (Aes128Cbc(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot pad the input data"))?; Ok(None) } - Aes128Ecb(encryptor) => { + (Aes128Cbc(mut encryptor), false) => { + encryptor.encrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(None) + } + (Aes128Ecb(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot pad the input data"))?; Ok(None) } - Aes192Ecb(encryptor) => { + (Aes128Ecb(mut encryptor), false) => { + encryptor.encrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(None) + } + (Aes192Ecb(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot pad the input data"))?; Ok(None) } - Aes256Ecb(encryptor) => { + (Aes192Ecb(mut encryptor), false) => { + encryptor.encrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(None) + } + (Aes256Ecb(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot pad the input data"))?; Ok(None) } - Aes128Gcm(cipher) => Ok(Some(cipher.finish().to_vec())), - Aes256Gcm(cipher) => Ok(Some(cipher.finish().to_vec())), - Aes256Cbc(encryptor) => { + (Aes256Ecb(mut encryptor), false) => { + encryptor.encrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(None) + } + (Aes128Gcm(cipher), _) => Ok(Some(cipher.finish().to_vec())), + (Aes256Gcm(cipher), _) => Ok(Some(cipher.finish().to_vec())), + (Aes256Cbc(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot pad the input data"))?; Ok(None) } + (Aes256Cbc(mut encryptor), false) => { + encryptor.encrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(None) + } } } } @@ -345,41 +388,70 @@ impl Decipher { /// r#final decrypts the last block of the input data. fn r#final( self, + auto_pad: bool, input: &[u8], output: &mut [u8], auth_tag: &[u8], ) -> Result<(), AnyError> { use Decipher::*; - match self { - Aes128Cbc(decryptor) => { + match (self, auto_pad) { + (Aes128Cbc(decryptor), true) => { assert!(input.len() == 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot unpad the input data"))?; Ok(()) } - Aes128Ecb(decryptor) => { + (Aes128Cbc(mut decryptor), false) => { + decryptor.decrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(()) + } + (Aes128Ecb(decryptor), true) => { assert!(input.len() == 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot unpad the input data"))?; Ok(()) } - Aes192Ecb(decryptor) => { + (Aes128Ecb(mut decryptor), false) => { + decryptor.decrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(()) + } + (Aes192Ecb(decryptor), true) => { assert!(input.len() == 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot unpad the input data"))?; Ok(()) } - Aes256Ecb(decryptor) => { + (Aes192Ecb(mut decryptor), false) => { + decryptor.decrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(()) + } + (Aes256Ecb(decryptor), true) => { assert!(input.len() == 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot unpad the input data"))?; Ok(()) } - Aes128Gcm(decipher) => { + (Aes256Ecb(mut decryptor), false) => { + decryptor.decrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(()) + } + (Aes128Gcm(decipher), true) => { let tag = decipher.finish(); if tag.as_slice() == auth_tag { Ok(()) @@ -387,7 +459,10 @@ impl Decipher { Err(type_error("Failed to authenticate data")) } } - Aes256Gcm(decipher) => { + (Aes128Gcm(_), false) => Err(type_error( + "setAutoPadding(false) not supported for Aes256Gcm yet", + )), + (Aes256Gcm(decipher), true) => { let tag = decipher.finish(); if tag.as_slice() == auth_tag { Ok(()) @@ -395,13 +470,23 @@ impl Decipher { Err(type_error("Failed to authenticate data")) } } - Aes256Cbc(decryptor) => { + (Aes256Gcm(_), false) => Err(type_error( + "setAutoPadding(false) not supported for Aes256Gcm yet", + )), + (Aes256Cbc(decryptor), true) => { assert!(input.len() == 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::(input, output) .map_err(|_| type_error("Cannot unpad the input data"))?; Ok(()) } + (Aes256Cbc(mut decryptor), false) => { + decryptor.decrypt_block_b2b_mut( + GenericArray::from_slice(input), + GenericArray::from_mut_slice(output), + ); + Ok(()) + } } } } diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index 567affd528..8780495a48 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -262,13 +262,14 @@ pub fn op_node_cipheriv_encrypt( pub fn op_node_cipheriv_final( state: &mut OpState, #[smi] rid: u32, + auto_pad: bool, #[buffer] input: &[u8], - #[buffer] output: &mut [u8], + #[anybuffer] output: &mut [u8], ) -> Result>, AnyError> { let context = state.resource_table.take::(rid)?; let context = Rc::try_unwrap(context) .map_err(|_| type_error("Cipher context is already in use"))?; - context.r#final(input, output) + context.r#final(auto_pad, input, output) } #[op2(fast)] @@ -317,17 +318,29 @@ pub fn op_node_decipheriv_decrypt( } #[op2(fast)] +pub fn op_node_decipheriv_take( + state: &mut OpState, + #[smi] rid: u32, +) -> Result<(), AnyError> { + let context = state.resource_table.take::(rid)?; + Rc::try_unwrap(context) + .map_err(|_| type_error("Cipher context is already in use"))?; + Ok(()) +} + +#[op2] pub fn op_node_decipheriv_final( state: &mut OpState, #[smi] rid: u32, + auto_pad: bool, #[buffer] input: &[u8], - #[buffer] output: &mut [u8], + #[anybuffer] output: &mut [u8], #[buffer] auth_tag: &[u8], ) -> Result<(), AnyError> { let context = state.resource_table.take::(rid)?; let context = Rc::try_unwrap(context) .map_err(|_| type_error("Cipher context is already in use"))?; - context.r#final(input, output, auth_tag) + context.r#final(auto_pad, input, output, auth_tag) } #[op2] diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts index d83d4fa8fd..0a0a1ca064 100644 --- a/ext/node/polyfills/internal/crypto/cipher.ts +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -17,6 +17,7 @@ import { op_node_decipheriv_decrypt, op_node_decipheriv_final, op_node_decipheriv_set_aad, + op_node_decipheriv_take, op_node_private_decrypt, op_node_private_encrypt, op_node_public_encrypt, @@ -163,6 +164,8 @@ export class Cipheriv extends Transform implements Cipher { #authTag?: Buffer; + #autoPadding = true; + constructor( cipher: string, key: CipherKey, @@ -191,8 +194,13 @@ export class Cipheriv extends Transform implements Cipher { final(encoding: string = getDefaultEncoding()): Buffer | string { const buf = new Buffer(16); + + if (!this.#autoPadding && this.#cache.cache.byteLength != 16) { + throw new Error("Invalid final block size"); + } const maybeTag = op_node_cipheriv_final( this.#context, + this.#autoPadding, this.#cache.cache, buf, ); @@ -217,8 +225,8 @@ export class Cipheriv extends Transform implements Cipher { return this; } - setAutoPadding(_autoPadding?: boolean): this { - notImplemented("crypto.Cipheriv.prototype.setAutoPadding"); + setAutoPadding(autoPadding?: boolean): this { + this.#autoPadding = !!autoPadding; return this; } @@ -299,6 +307,8 @@ export class Decipheriv extends Transform implements Cipher { /** DecipherContext resource id */ #context: number; + #autoPadding = true; + /** ciphertext data cache */ #cache: BlockModeCache; @@ -333,18 +343,23 @@ export class Decipheriv extends Transform implements Cipher { } final(encoding: string = getDefaultEncoding()): Buffer | string { + if (!this.#needsBlockCache || this.#cache.cache.byteLength === 0) { + op_node_decipheriv_take(this.#context); + return encoding === "buffer" ? Buffer.from([]) : ""; + } + if (this.#cache.cache.byteLength != 16) { + throw new Error("Invalid final block size"); + } + let buf = new Buffer(16); op_node_decipheriv_final( this.#context, + this.#autoPadding, this.#cache.cache, buf, this.#authTag || NO_TAG, ); - if (!this.#needsBlockCache) { - return encoding === "buffer" ? Buffer.from([]) : ""; - } - buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode return encoding === "buffer" ? buf : buf.toString(encoding); } @@ -364,8 +379,9 @@ export class Decipheriv extends Transform implements Cipher { return this; } - setAutoPadding(_autoPadding?: boolean): this { - notImplemented("crypto.Decipheriv.prototype.setAutoPadding"); + setAutoPadding(autoPadding?: boolean): this { + this.#autoPadding = Boolean(autoPadding); + return this; } update(