VYPR
High severityNVD Advisory· Published Jun 3, 2025· Updated Jun 4, 2025

Deno's AES GCM authentication tags are not verified

CVE-2025-24015

Description

Deno is a JavaScript, TypeScript, and WebAssembly runtime. Versions 1.46.0 through 2.1.6 have an issue that affects AES-256-GCM and AES-128-GCM in Deno in which the authentication tag is not being validated. This means tampered ciphertexts or incorrect keys might not be detected, which breaks the guarantees expected from AES-GCM. Older versions of Deno correctly threw errors in such cases, as does Node.js. Without authentication tag verification, AES-GCM degrades to essentially CTR mode, removing integrity protection. Authenticated data set with set_aad is also affected, as it is incorporated into the GCM hash (ghash) but this too is not validated, rendering AAD checks ineffective. Version 2.1.7 includes a patch that addresses this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
denocrates.io
>= 1.46.0, < 2.1.72.1.7
deno_nodecrates.io
>= 0.102.0, < 0.125.00.125.0

Affected products

1

Patches

4
a4003a5292bd

fix(ext/node): GCM auth tag check on DechiperIv#final (#27733)

https://github.com/denoland/denoDivy SrivastavaJan 20, 2025via ghsa
5 files changed · +19 24
  • ext/node/lib.rs+0 1 modified
    @@ -226,7 +226,6 @@ 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,
    
  • ext/node/ops/crypto/cipher.rs+5 0 modified
    @@ -500,6 +500,11 @@ impl Decipher {
         auth_tag: &[u8],
       ) -> Result<(), DecipherError> {
         use Decipher::*;
    +
    +    if input.is_empty() && !matches!(self, Aes128Gcm(_) | Aes256Gcm(_)) {
    +      return Ok(());
    +    }
    +
         match (self, auto_pad) {
           (Aes128Cbc(decryptor), true) => {
             assert!(input.len() == 16);
    
  • ext/node/ops/crypto/mod.rs+0 11 modified
    @@ -332,17 +332,6 @@ pub fn op_node_decipheriv_decrypt(
       true
     }
     
    -#[op2(fast)]
    -pub fn op_node_decipheriv_take(
    -  state: &mut OpState,
    -  #[smi] rid: u32,
    -) -> Result<(), cipher::DecipherContextError> {
    -  let context = state.resource_table.take::<cipher::DecipherContext>(rid)?;
    -  Rc::try_unwrap(context)
    -    .map_err(|_| cipher::DecipherContextError::ContextInUse)?;
    -  Ok(())
    -}
    -
     #[op2]
     pub fn op_node_decipheriv_final(
       state: &mut OpState,
    
  • ext/node/polyfills/internal/crypto/cipher.ts+7 9 modified
    @@ -18,7 +18,6 @@ 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,
    @@ -352,14 +351,6 @@ 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,
    @@ -369,6 +360,13 @@ export class Decipheriv extends Transform implements Cipher {
           this.#authTag || NO_TAG,
         );
     
    +    if (!this.#needsBlockCache || this.#cache.cache.byteLength === 0) {
    +      return encoding === "buffer" ? Buffer.from([]) : "";
    +    }
    +    if (this.#cache.cache.byteLength != 16) {
    +      throw new Error("Invalid final block size");
    +    }
    +
         buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode
         return encoding === "buffer" ? buf : buf.toString(encoding);
       }
    
  • tests/unit_node/crypto/crypto_cipher_gcm_test.ts+7 3 modified
    @@ -4,7 +4,7 @@ import crypto from "node:crypto";
     import { Buffer } from "node:buffer";
     import testVectors128 from "./gcmEncryptExtIV128.json" with { type: "json" };
     import testVectors256 from "./gcmEncryptExtIV256.json" with { type: "json" };
    -import { assertEquals } from "@std/assert";
    +import { assertEquals, assertThrows } from "@std/assert";
     
     const aesGcm = (bits: string, key: Uint8Array) => {
       const ALGO = bits == "128" ? `aes-128-gcm` : `aes-256-gcm`;
    @@ -123,7 +123,7 @@ Deno.test({
     // Issue #27441
     // https://github.com/denoland/deno/issues/27441
     Deno.test({
    -  name: "aes-256-gcm supports IV of non standard length",
    +  name: "aes-256-gcm supports IV of non standard length and auth tag check",
       fn() {
         const decipher = crypto.createDecipheriv(
           "aes-256-gcm",
    @@ -136,6 +136,10 @@ Deno.test({
           "utf-8",
         );
         assertEquals(decrypted, "this is a secret");
    -    decipher.final();
    +    assertThrows(
    +      () => decipher.final(),
    +      TypeError,
    +      "Failed to authenticate data",
    +    );
       },
     });
    
4f27d7cdc02e

fix(ext/node): GCM auth tag check on DechiperIv#final (#27733)

https://github.com/denoland/denoDivy SrivastavaJan 20, 2025via ghsa
5 files changed · +19 24
  • ext/node/lib.rs+0 1 modified
    @@ -226,7 +226,6 @@ 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,
    
  • ext/node/ops/crypto/cipher.rs+5 0 modified
    @@ -500,6 +500,11 @@ impl Decipher {
         auth_tag: &[u8],
       ) -> Result<(), DecipherError> {
         use Decipher::*;
    +
    +    if input.is_empty() && !matches!(self, Aes128Gcm(_) | Aes256Gcm(_)) {
    +      return Ok(());
    +    }
    +
         match (self, auto_pad) {
           (Aes128Cbc(decryptor), true) => {
             assert!(input.len() == 16);
    
  • ext/node/ops/crypto/mod.rs+0 11 modified
    @@ -332,17 +332,6 @@ pub fn op_node_decipheriv_decrypt(
       true
     }
     
    -#[op2(fast)]
    -pub fn op_node_decipheriv_take(
    -  state: &mut OpState,
    -  #[smi] rid: u32,
    -) -> Result<(), cipher::DecipherContextError> {
    -  let context = state.resource_table.take::<cipher::DecipherContext>(rid)?;
    -  Rc::try_unwrap(context)
    -    .map_err(|_| cipher::DecipherContextError::ContextInUse)?;
    -  Ok(())
    -}
    -
     #[op2]
     pub fn op_node_decipheriv_final(
       state: &mut OpState,
    
  • ext/node/polyfills/internal/crypto/cipher.ts+7 9 modified
    @@ -18,7 +18,6 @@ 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,
    @@ -352,14 +351,6 @@ 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,
    @@ -369,6 +360,13 @@ export class Decipheriv extends Transform implements Cipher {
           this.#authTag || NO_TAG,
         );
     
    +    if (!this.#needsBlockCache || this.#cache.cache.byteLength === 0) {
    +      return encoding === "buffer" ? Buffer.from([]) : "";
    +    }
    +    if (this.#cache.cache.byteLength != 16) {
    +      throw new Error("Invalid final block size");
    +    }
    +
         buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode
         return encoding === "buffer" ? buf : buf.toString(encoding);
       }
    
  • tests/unit_node/crypto/crypto_cipher_gcm_test.ts+7 3 modified
    @@ -4,7 +4,7 @@ import crypto from "node:crypto";
     import { Buffer } from "node:buffer";
     import testVectors128 from "./gcmEncryptExtIV128.json" with { type: "json" };
     import testVectors256 from "./gcmEncryptExtIV256.json" with { type: "json" };
    -import { assertEquals } from "@std/assert";
    +import { assertEquals, assertThrows } from "@std/assert";
     
     const aesGcm = (bits: string, key: Uint8Array) => {
       const ALGO = bits == "128" ? `aes-128-gcm` : `aes-256-gcm`;
    @@ -123,7 +123,7 @@ Deno.test({
     // Issue #27441
     // https://github.com/denoland/deno/issues/27441
     Deno.test({
    -  name: "aes-256-gcm supports IV of non standard length",
    +  name: "aes-256-gcm supports IV of non standard length and auth tag check",
       fn() {
         const decipher = crypto.createDecipheriv(
           "aes-256-gcm",
    @@ -136,6 +136,10 @@ Deno.test({
           "utf-8",
         );
         assertEquals(decrypted, "this is a secret");
    -    decipher.final();
    +    assertThrows(
    +      () => decipher.final(),
    +      TypeError,
    +      "Failed to authenticate data",
    +    );
       },
     });
    
0d1beed

fix(ext/node): add `CipherIv.setAutoPadding()` (#24940)

https://github.com/denoland/denoDivy SrivastavaAug 8, 2024via ghsa
4 files changed · +146 31
  • ext/node/lib.rs+1 0 modified
    @@ -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,
    
  • ext/node/ops/crypto/cipher.rs+104 19 modified
    @@ -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(())
    +      }
         }
       }
     }
    
  • ext/node/ops/crypto/mod.rs+17 4 modified
    @@ -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]
    
  • ext/node/polyfills/internal/crypto/cipher.ts+24 8 modified
    @@ -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(
    
0d1beed2e363

fix(ext/node): add `CipherIv.setAutoPadding()` (#24940)

https://github.com/denoland/denoDivy SrivastavaAug 8, 2024via ghsa
4 files changed · +146 31
  • ext/node/lib.rs+1 0 modified
    @@ -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,
    
  • ext/node/ops/crypto/cipher.rs+104 19 modified
    @@ -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(())
    +      }
         }
       }
     }
    
  • ext/node/ops/crypto/mod.rs+17 4 modified
    @@ -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]
    
  • ext/node/polyfills/internal/crypto/cipher.ts+24 8 modified
    @@ -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(
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.