Repository: denoland/deno
Pull request: fix(ext/node): add `CipherIv.setAutoPadding()`

Co-Authored-By: Luca Casonato <hello@lcas.dev>

Fixes https://github.com/denoland/deno/issues/21804
Ref https://github.com/denoland/deno/issues/20924

Changed files:
filename: ext/node/lib.rs
patch: @@ -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,

filename: ext/node/ops/crypto/cipher.rs
patch: @@ -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<Tag, AnyError> {
     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,14 +94,15 @@ impl DecipherContext {
 
   pub fn r#final(
     self,
+    auto_pad: bool,
     input: &[u8],
     output: &mut [u8],
     auth_tag: &[u8],
   ) -> Result<(), AnyError> {
     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<Tag, AnyError> {
+  fn r#final(
+    self,
+    auto_pad: bool,
+    input: &[u8],
+    output: &mut [u8],
+  ) -> Result<Tag, AnyError> {
     assert!(input.len() < 16);
     use Cipher::*;
-    match self {
-      Aes128Cbc(encryptor) => {
+    match (self, auto_pad) {
+      (Aes128Cbc(encryptor), true) => {
         let _ = (*encryptor)
           .encrypt_padded_b2b_mut::<Pkcs7>(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::<Pkcs7>(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::<Pkcs7>(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::<Pkcs7>(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::<Pkcs7>(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,63 +388,105 @@ 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::<Pkcs7>(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::<Pkcs7>(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::<Pkcs7>(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::<Pkcs7>(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(())
         } else {
           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(())
         } else {
           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::<Pkcs7>(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(())
+      }
     }
   }
 }

filename: ext/node/ops/crypto/mod.rs
patch: @@ -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<Option<Vec<u8>>, AnyError> {
   let context = state.resource_table.take::<cipher::CipherContext>(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::<cipher::DecipherContext>(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::<cipher::DecipherContext>(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]

filename: ext/node/polyfills/internal/crypto/cipher.ts
patch: @@ -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(