/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node_crypto/cipher.rs
1 740 строк
52 KB
Mattias Runge-Broberg
feat(ext/node): support raw chacha20 cipher in crypto.createCipheriv (#36016)
16 июл 2026, 12:36
Не верифицирован
16 июл 2026, 12:36
803a3c9
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use std::mem::MaybeUninit; use std::rc::Rc; use aes::cipher::BlockDecryptMut; use aes::cipher::BlockEncryptMut; use aes::cipher::KeyIvInit; use aes::cipher::KeySizeUser; use aes::cipher::StreamCipher; use aes::cipher::block_padding::Pkcs7; use deno_core::Resource; use deno_error::JsErrorClass; use digest::KeyInit; use digest::generic_array::GenericArray; use subtle::ConstantTimeEq; type Tag = Option<Vec<u8>>; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum AesWrapError { #[class(range)] #[error("Invalid key length")] InvalidKeyLength, #[class(type)] #[error("Invalid initialization vector")] InvalidIv, #[class(range)] #[error("Invalid input length")] InvalidInputLength, #[class(type)] #[error("AES wrap failed")] WrapFailed, #[class(type)] #[error("AES unwrap failed")] UnwrapFailed, } /// AES Key Wrap (RFC 3394) with optional custom IV. /// /// For standard wrap (`aes128-wrap`, `aes192-wrap`, `aes256-wrap`): /// - iv: 8-byte IV (NULL → default 0xA6A6A6A6A6A6A6A6) /// - input must be a multiple of 8 and at least 16 bytes /// - output = input_len + 8 /// /// For padded wrap (`id-aes128-wrap-pad`, `id-aes192-wrap-pad`, `id-aes256-wrap-pad`): /// - iv: 4-byte constant (prepended; padded AIV = iv || MLI) /// - input can be any length >= 1 /// - output = ceil(input_len / 8) * 8 + 8 pub fn aes_wrap_key( algorithm: &str, key: &[u8], iv: &[u8], data: &[u8], ) -> Result<Vec<u8>, AesWrapError> { let bits = match key.len() { 16 => 128, 24 => 192, 32 => 256, _ => return Err(AesWrapError::InvalidKeyLength), }; // SAFETY: AES_KEY is an opaque type; MaybeUninit lets us avoid zeroing. let mut aes_key = MaybeUninit::<aws_lc_sys::AES_KEY>::uninit(); // SAFETY: key slice is valid and bits matches key.len(). let ret = unsafe { aws_lc_sys::AES_set_encrypt_key(key.as_ptr(), bits, aes_key.as_mut_ptr()) }; if ret != 0 { return Err(AesWrapError::InvalidKeyLength); } // SAFETY: AES_set_encrypt_key succeeded so aes_key is fully initialised. let aes_key = unsafe { aes_key.assume_init() }; let is_pad = algorithm.ends_with("-pad"); if is_pad { // Padded wrap (RFC 5649). iv must be 4 bytes (the constant part of AIV). if iv.len() != 4 { return Err(AesWrapError::InvalidIv); } if data.is_empty() { return Err(AesWrapError::InvalidInputLength); } let mli = data.len() as u32; // AIV = iv_constant || MLI (big-endian) let mut aiv = [0u8; 8]; aiv[..4].copy_from_slice(iv); aiv[4..].copy_from_slice(&mli.to_be_bytes()); if data.len() <= 8 { // Single-block case: wrap = AES_encrypt(AIV || data_padded). // AES_wrap_key requires padded_len >= 16, so handle this explicitly. let mut block = [0u8; 16]; block[..8].copy_from_slice(&aiv); block[8..8 + data.len()].copy_from_slice(data); let mut out = vec![0u8; 16]; // SAFETY: aes_key is initialised; block and out are 16 bytes. unsafe { aws_lc_sys::AES_encrypt(block.as_ptr(), out.as_mut_ptr(), &aes_key); } return Ok(out); } let padded_len = data.len().next_multiple_of(8); let mut padded = vec![0u8; padded_len]; padded[..data.len()].copy_from_slice(data); let out_len = padded_len + 8; let mut out = vec![0u8; out_len]; // SAFETY: aes_key is initialised; aiv, out, padded are valid slices. let ret = unsafe { aws_lc_sys::AES_wrap_key( &aes_key, aiv.as_ptr(), out.as_mut_ptr(), padded.as_ptr(), padded_len, ) }; if ret < 0 { return Err(AesWrapError::WrapFailed); } Ok(out) } else { // Standard wrap (RFC 3394). iv must be 8 bytes or empty (→ default IV). if !iv.is_empty() && iv.len() != 8 { return Err(AesWrapError::InvalidIv); } if data.len() < 16 || !data.len().is_multiple_of(8) { return Err(AesWrapError::InvalidInputLength); } let iv_ptr = if iv.is_empty() { std::ptr::null() } else { iv.as_ptr() }; let out_len = data.len() + 8; let mut out = vec![0u8; out_len]; // SAFETY: aes_key is initialised; pointers and lengths are valid. let ret = unsafe { aws_lc_sys::AES_wrap_key( &aes_key, iv_ptr, out.as_mut_ptr(), data.as_ptr(), data.len(), ) }; if ret < 0 { return Err(AesWrapError::WrapFailed); } Ok(out) } } // RFC 3394 / 5649 inverse step: unwrap n+1 blocks → n plaintext blocks // and the recovered AIV. Mirrors aws-lc's static `aes_unwrap_key_inner` // (crypto/fipsmodule/aes/key_wrap.c) so we can validate the AIV ourselves // in constant time — needed because `AES_unwrap_key` does the IV check // internally and rejects any non-default AIV. fn aes_unwrap_inner( key: &aws_lc_sys::AES_KEY, data: &[u8], ) -> (Vec<u8>, [u8; 8]) { debug_assert!(data.len().is_multiple_of(8) && data.len() >= 24); let n = data.len() / 8 - 1; let mut out = vec![0u8; data.len() - 8]; let mut a = [0u8; 8]; a.copy_from_slice(&data[..8]); out.copy_from_slice(&data[8..]); let mut block = [0u8; 16]; for j in (0..6u32).rev() { for i in (1..=n).rev() { let t = (n as u32) * j + i as u32; a[7] ^= (t & 0xff) as u8; a[6] ^= ((t >> 8) & 0xff) as u8; a[5] ^= ((t >> 16) & 0xff) as u8; a[4] ^= ((t >> 24) & 0xff) as u8; block[..8].copy_from_slice(&a); block[8..].copy_from_slice(&out[(i - 1) * 8..i * 8]); // SAFETY: key is initialised; block is a valid 16-byte buffer. unsafe { aws_lc_sys::AES_decrypt(block.as_ptr(), block.as_mut_ptr(), key); } a.copy_from_slice(&block[..8]); out[(i - 1) * 8..i * 8].copy_from_slice(&block[8..]); } } (out, a) } /// AES Key Unwrap (RFC 3394 / RFC 5649) with optional custom IV. pub fn aes_unwrap_key( algorithm: &str, key: &[u8], iv: &[u8], data: &[u8], ) -> Result<Vec<u8>, AesWrapError> { let bits = match key.len() { 16 => 128, 24 => 192, 32 => 256, _ => return Err(AesWrapError::InvalidKeyLength), }; // SAFETY: AES_KEY is an opaque type; MaybeUninit avoids zeroing. let mut aes_key = MaybeUninit::<aws_lc_sys::AES_KEY>::uninit(); // SAFETY: key slice and bits are valid. let ret = unsafe { aws_lc_sys::AES_set_decrypt_key(key.as_ptr(), bits, aes_key.as_mut_ptr()) }; if ret != 0 { return Err(AesWrapError::InvalidKeyLength); } // SAFETY: AES_set_decrypt_key succeeded so aes_key is initialised. let aes_key = unsafe { aes_key.assume_init() }; let is_pad = algorithm.ends_with("-pad"); if is_pad { // Padded unwrap (RFC 5649). iv is the 4-byte AIV constant. if iv.len() != 4 { return Err(AesWrapError::InvalidIv); } if data.len() < 16 || !data.len().is_multiple_of(8) { return Err(AesWrapError::InvalidInputLength); } // Decrypt once, recover AIV. Two cases per RFC 5649: // - len == 16: single-block (MLI ≤ 8); decrypt one block, AIV is high // half, plaintext (zero-padded to 8 bytes) is low half. // - len > 16: standard unwrap inverse, AIV is the recovered A. let (mut out, recovered_aiv) = if data.len() == 16 { let mut block = [0u8; 16]; // SAFETY: aes_key is initialised; data and block are 16 bytes. unsafe { aws_lc_sys::AES_decrypt(data.as_ptr(), block.as_mut_ptr(), &aes_key); } let mut aiv = [0u8; 8]; aiv.copy_from_slice(&block[..8]); let mut pt = vec![0u8; 8]; pt.copy_from_slice(&block[8..]); (pt, aiv) } else { aes_unwrap_inner(&aes_key, data) }; // Validate AIV constant in constant time. let constant_ok = recovered_aiv[..4].ct_eq(iv).unwrap_u8(); let mli = u32::from_be_bytes([ recovered_aiv[4], recovered_aiv[5], recovered_aiv[6], recovered_aiv[7], ]) as usize; // MLI must satisfy: (n-1)*8 < MLI <= n*8, where n = ceil(MLI/8) blocks. // i.e. ceil(MLI/8) == out.len()/8, and MLI >= 1. let n_blocks = out.len() / 8; let mli_blocks = mli.div_ceil(8); let len_ok = (mli >= 1 && mli_blocks == n_blocks) as u8; // Padding bytes (out[mli..]) must all be zero. let pad_ok = if mli <= out.len() { let mut acc = 0u8; for &b in &out[mli.min(out.len())..] { acc |= b; } (acc == 0) as u8 } else { 0 }; if (constant_ok & len_ok & pad_ok) != 1 { return Err(AesWrapError::UnwrapFailed); } out.truncate(mli); Ok(out) } else { // Standard unwrap (RFC 3394). iv must be 8 bytes or empty (→ default IV). if !iv.is_empty() && iv.len() != 8 { return Err(AesWrapError::InvalidIv); } if data.len() < 24 || !data.len().is_multiple_of(8) { return Err(AesWrapError::InvalidInputLength); } let (out, recovered_aiv) = aes_unwrap_inner(&aes_key, data); let expected: &[u8] = if iv.is_empty() { &[0xa6; 8] } else { iv }; if recovered_aiv.ct_eq(expected).unwrap_u8() != 1 { return Err(AesWrapError::UnwrapFailed); } Ok(out) } } type Aes128Gcm = aead_gcm_stream::AesGcm<aes::Aes128>; type Aes256Gcm = aead_gcm_stream::AesGcm<aes::Aes256>; enum CipherInitError { ContextAllocation, InitFailed, } /// ChaCha20-Poly1305 cipher backed by aws-lc-sys (BoringSSL). /// /// Uses the streaming EVP_CIPHER API for hardware-accelerated performance /// on all platforms (NEON on aarch64, AVX2/SSE on x86_64). struct ChaCha20Poly1305Cipher { ctx: *mut aws_lc_sys::EVP_CIPHER_CTX, aad_buf: Vec<u8>, aad_flushed: bool, auth_tag_length: usize, } // SAFETY: ChaCha20Poly1305Cipher is only accessed from a single thread // (via RefCell in CipherContext/DecipherContext). The EVP_CIPHER_CTX // pointer is exclusively owned by this struct. unsafe impl Send for ChaCha20Poly1305Cipher {} impl ChaCha20Poly1305Cipher { fn new( key: &[u8], iv: &[u8], auth_tag_length: usize, encrypting: bool, ) -> Result<Self, CipherInitError> { // SAFETY: We allocate a new EVP_CIPHER_CTX and initialize it with // validated key/iv. The ctx is exclusively owned by this struct and // freed in Drop. unsafe { let ctx = aws_lc_sys::EVP_CIPHER_CTX_new(); if ctx.is_null() { return Err(CipherInitError::ContextAllocation); } let cipher = aws_lc_sys::EVP_chacha20_poly1305(); let enc = if encrypting { 1 } else { 0 }; let ret = aws_lc_sys::EVP_CipherInit_ex( ctx, cipher, std::ptr::null_mut(), key.as_ptr(), iv.as_ptr(), enc, ); if ret != 1 { aws_lc_sys::EVP_CIPHER_CTX_free(ctx); return Err(CipherInitError::InitFailed); } Ok(ChaCha20Poly1305Cipher { ctx, aad_buf: Vec::new(), aad_flushed: false, auth_tag_length, }) } } fn set_aad(&mut self, aad: &[u8]) { self.aad_buf.extend_from_slice(aad); } /// Flush buffered AAD to EVP context. Called lazily before the first /// encrypt/decrypt so that multiple setAAD() calls are concatenated. fn flush_aad(&mut self) { if !self.aad_flushed { self.aad_flushed = true; if !self.aad_buf.is_empty() { // SAFETY: ctx is valid, aad_buf is a valid slice. Passing NULL // output tells EVP this is AAD, not plaintext/ciphertext. // Length is validated to fit in i32 before casting. unsafe { let aad_len: i32 = self .aad_buf .len() .try_into() .expect("AAD length exceeds i32::MAX"); let mut outl: i32 = 0; let ret = aws_lc_sys::EVP_CipherUpdate( self.ctx, std::ptr::null_mut(), &mut outl, self.aad_buf.as_ptr(), aad_len, ); assert_eq!(ret, 1, "EVP_CipherUpdate for AAD failed"); } } } } fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { assert!(output.len() >= input.len()); self.flush_aad(); // SAFETY: ctx is valid and initialized for encryption. output is // caller-provided with at least input.len() bytes. EVP_CipherUpdate // writes at most input.len() bytes for a stream cipher. // Length is validated to fit in i32 before casting. unsafe { let input_len: i32 = input .len() .try_into() .expect("input length exceeds i32::MAX"); let mut outl: i32 = 0; let ret = aws_lc_sys::EVP_CipherUpdate( self.ctx, output.as_mut_ptr(), &mut outl, input.as_ptr(), input_len, ); assert_eq!(ret, 1, "EVP_CipherUpdate for encryption failed"); } } fn decrypt(&mut self, input: &[u8], output: &mut [u8]) { assert!(output.len() >= input.len()); self.flush_aad(); // SAFETY: ctx is valid and initialized for decryption. output is // caller-provided with at least input.len() bytes. // Length is validated to fit in i32 before casting. unsafe { let input_len: i32 = input .len() .try_into() .expect("input length exceeds i32::MAX"); let mut outl: i32 = 0; let ret = aws_lc_sys::EVP_CipherUpdate( self.ctx, output.as_mut_ptr(), &mut outl, input.as_ptr(), input_len, ); assert_eq!(ret, 1, "EVP_CipherUpdate for decryption failed"); } } fn compute_tag(mut self) -> Vec<u8> { self.flush_aad(); // SAFETY: ctx is valid. CipherFinal_ex finalizes the AEAD operation, // then CTRL_AEAD_GET_TAG retrieves the computed authentication tag. // The tag buffer is freshly allocated with the correct length // (validated to 1..=16 at construction). unsafe { let mut outl: i32 = 0; let ret = aws_lc_sys::EVP_CipherFinal_ex( self.ctx, std::ptr::null_mut(), &mut outl, ); assert_eq!(ret, 1, "EVP_CipherFinal_ex failed"); let mut tag = vec![0u8; self.auth_tag_length]; let ret = aws_lc_sys::EVP_CIPHER_CTX_ctrl( self.ctx, aws_lc_sys::EVP_CTRL_AEAD_GET_TAG, self.auth_tag_length as i32, tag.as_mut_ptr() as *mut std::ffi::c_void, ); assert_eq!(ret, 1, "EVP_CTRL_AEAD_GET_TAG failed"); tag } } fn verify_tag(mut self, auth_tag: &[u8]) -> bool { self.flush_aad(); // SAFETY: ctx is valid and initialized for decryption. We set the // expected tag via CTRL_AEAD_SET_TAG, then CipherFinal_ex performs // constant-time tag comparison internally, returning 0 on mismatch. unsafe { let ret = aws_lc_sys::EVP_CIPHER_CTX_ctrl( self.ctx, aws_lc_sys::EVP_CTRL_AEAD_SET_TAG, auth_tag.len() as i32, auth_tag.as_ptr() as *mut std::ffi::c_void, ); if ret != 1 { return false; } let mut outl: i32 = 0; let ret = aws_lc_sys::EVP_CipherFinal_ex( self.ctx, std::ptr::null_mut(), &mut outl, ); ret == 1 } } } impl Drop for ChaCha20Poly1305Cipher { fn drop(&mut self) { // SAFETY: ctx was allocated by EVP_CIPHER_CTX_new and is exclusively // owned by this struct. This is the only place it is freed. unsafe { aws_lc_sys::EVP_CIPHER_CTX_free(self.ctx); } } } /// Raw ChaCha20 stream cipher backed by aws-lc-sys (BoringSSL). /// /// Matches OpenSSL's `EVP_chacha20` semantics used by Node.js: the 16-byte /// IV consists of a 32-bit little-endian block counter followed by a /// 96-bit nonce. struct ChaCha20Cipher { key: [u8; 32], nonce: [u8; 12], counter: u32, /// Number of keystream bytes consumed so far. pos: u64, /// Keystream of the 64-byte block containing `pos`. Only meaningful /// while `pos` is mid-block (`pos % 64 != 0`); the bytes from /// `pos % 64` on are not yet consumed. keystream_block: [u8; 64], } impl ChaCha20Cipher { fn new(key: &[u8], iv: &[u8]) -> Self { debug_assert_eq!(key.len(), 32); debug_assert_eq!(iv.len(), 16); let mut key_arr = [0u8; 32]; key_arr.copy_from_slice(key); let mut nonce = [0u8; 12]; nonce.copy_from_slice(&iv[4..16]); let counter = u32::from_le_bytes([iv[0], iv[1], iv[2], iv[3]]); Self { key: key_arr, nonce, counter, pos: 0, keystream_block: [0u8; 64], } } /// Fills `keystream_block` with the keystream of the block containing /// `pos` by encrypting 64 zero bytes. fn refill_keystream_block(&mut self) { debug_assert_eq!(self.pos % 64, 0); let counter = self.counter.wrapping_add((self.pos / 64) as u32); self.keystream_block = [0u8; 64]; // SAFETY: keystream_block is a valid 64-byte buffer for in-place // encryption (in == out is allowed); key and nonce have the exact // sizes CRYPTO_chacha_20 requires (32 and 12 bytes). unsafe { aws_lc_sys::CRYPTO_chacha_20( self.keystream_block.as_mut_ptr(), self.keystream_block.as_ptr(), self.keystream_block.len(), self.key.as_ptr(), self.nonce.as_ptr(), counter, ); } } /// XORs the keystream into `input`, writing to `output`. First consumes /// any keystream left over in `keystream_block` from a previous /// mid-block call, then processes the remaining whole blocks in one /// pass, and finally caches the keystream of a trailing partial block /// for the next call — no per-call allocation. fn apply_keystream(&mut self, input: &[u8], output: &mut [u8]) { assert!(output.len() >= input.len()); // Use up the cached keystream of the current partial block. let offset = (self.pos % 64) as usize; let mut consumed = 0; if offset != 0 { consumed = input.len().min(64 - offset); for ((out, inp), key) in output .iter_mut() .zip(input) .zip(&self.keystream_block[offset..]) { *out = inp ^ key; } self.pos += consumed as u64; } let input = &input[consumed..]; let output = &mut output[consumed..]; // Process all remaining whole blocks directly input -> output. let whole = input.len() - input.len() % 64; if whole != 0 { let counter = self.counter.wrapping_add((self.pos / 64) as u32); // SAFETY: input and output are valid for `whole` bytes (output // length asserted above); key and nonce have the exact sizes // CRYPTO_chacha_20 requires (32 and 12 bytes). unsafe { aws_lc_sys::CRYPTO_chacha_20( output.as_mut_ptr(), input.as_ptr(), whole, self.key.as_ptr(), self.nonce.as_ptr(), counter, ); } self.pos += whole as u64; } // A trailing partial block: cache its keystream and XOR from it. let tail = &input[whole..]; if !tail.is_empty() { self.refill_keystream_block(); for ((out, inp), key) in output[whole..] .iter_mut() .zip(tail) .zip(&self.keystream_block) { *out = inp ^ key; } self.pos += tail.len() as u64; } } } enum Cipher { Aes128Cbc(Box<cbc::Encryptor<aes::Aes128>>), Aes128Ecb(Box<ecb::Encryptor<aes::Aes128>>), Aes192Ecb(Box<ecb::Encryptor<aes::Aes192>>), Aes256Ecb(Box<ecb::Encryptor<aes::Aes256>>), Aes128Gcm(Box<Aes128Gcm>, Option<usize>), Aes256Gcm(Box<Aes256Gcm>, Option<usize>), Aes256Cbc(Box<cbc::Encryptor<aes::Aes256>>), Aes128Ctr(Box<ctr::Ctr128BE<aes::Aes128>>), Aes192Ctr(Box<ctr::Ctr128BE<aes::Aes192>>), Aes256Ctr(Box<ctr::Ctr128BE<aes::Aes256>>), DesEde3Cbc(Box<cbc::Encryptor<des::TdesEde3>>), ChaCha20(Box<ChaCha20Cipher>), ChaCha20Poly1305(Box<ChaCha20Poly1305Cipher>), // TODO(kt3k): add more algorithms Aes192Cbc, etc. } enum Decipher { Aes128Cbc(Box<cbc::Decryptor<aes::Aes128>>), Aes128Ecb(Box<ecb::Decryptor<aes::Aes128>>), Aes192Ecb(Box<ecb::Decryptor<aes::Aes192>>), Aes256Ecb(Box<ecb::Decryptor<aes::Aes256>>), Aes128Gcm(Box<Aes128Gcm>, Option<usize>), Aes256Gcm(Box<Aes256Gcm>, Option<usize>), Aes256Cbc(Box<cbc::Decryptor<aes::Aes256>>), Aes128Ctr(Box<ctr::Ctr128BE<aes::Aes128>>), Aes192Ctr(Box<ctr::Ctr128BE<aes::Aes192>>), Aes256Ctr(Box<ctr::Ctr128BE<aes::Aes256>>), DesEde3Cbc(Box<cbc::Decryptor<des::TdesEde3>>), ChaCha20(Box<ChaCha20Cipher>), ChaCha20Poly1305(Box<ChaCha20Poly1305Cipher>, Option<usize>), // TODO(kt3k): add more algorithms Aes192Cbc, Aes128GCM, etc. } pub struct CipherContext { cipher: Rc<RefCell<Cipher>>, } pub struct DecipherContext { decipher: Rc<RefCell<Decipher>>, } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum CipherContextError { #[class(type)] #[error("Cipher context is already in use")] ContextInUse, #[class(inherit)] #[error("{0}")] Resource(#[from] deno_core::error::ResourceError), #[class(inherit)] #[error(transparent)] Cipher(#[from] CipherError), } impl CipherContext { pub fn new( algorithm: &str, key: &[u8], iv: &[u8], auth_tag_length: Option<usize>, ) -> Result<Self, CipherContextError> { Ok(Self { cipher: Rc::new(RefCell::new(Cipher::new( algorithm, key, iv, auth_tag_length, )?)), }) } pub fn set_aad(&self, aad: &[u8]) { self.cipher.borrow_mut().set_aad(aad); } pub fn encrypt(&self, input: &[u8], output: &mut [u8]) { self.cipher.borrow_mut().encrypt(input, output); } pub fn take_tag(self) -> Tag { Rc::try_unwrap(self.cipher).ok()?.into_inner().take_tag() } pub fn r#final( self, auto_pad: bool, input: &[u8], output: &mut [u8], ) -> Result<Tag, CipherContextError> { Rc::try_unwrap(self.cipher) .map_err(|_| CipherContextError::ContextInUse)? .into_inner() .r#final(auto_pad, input, output) .map_err(Into::into) } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum DecipherContextError { #[class(type)] #[error("Decipher context is already in use")] ContextInUse, #[class(inherit)] #[error("{0}")] Resource(#[from] deno_core::error::ResourceError), #[class(inherit)] #[error(transparent)] Decipher(#[from] DecipherError), } impl DecipherContext { pub fn new( algorithm: &str, key: &[u8], iv: &[u8], auth_tag_length: Option<usize>, ) -> Result<Self, DecipherContextError> { Ok(Self { decipher: Rc::new(RefCell::new(Decipher::new( algorithm, key, iv, auth_tag_length, )?)), }) } pub fn validate_auth_tag( &self, length: usize, ) -> Result<(), DecipherContextError> { self.decipher.borrow().validate_auth_tag(length)?; Ok(()) } pub fn set_aad(&self, aad: &[u8]) { self.decipher.borrow_mut().set_aad(aad); } pub fn decrypt(&self, input: &[u8], output: &mut [u8]) { self.decipher.borrow_mut().decrypt(input, output); } pub fn r#final( self, auto_pad: bool, input: &[u8], output: &mut [u8], auth_tag: &[u8], ) -> Result<(), DecipherContextError> { Rc::try_unwrap(self.decipher) .map_err(|_| DecipherContextError::ContextInUse)? .into_inner() .r#final(auto_pad, input, output, auth_tag) .map_err(Into::into) } } impl Resource for CipherContext { fn name(&self) -> Cow<'_, str> { "cryptoCipher".into() } } impl Resource for DecipherContext { fn name(&self) -> Cow<'_, str> { "cryptoDecipher".into() } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum CipherError { #[class(type)] #[error("IV length must be 12 bytes")] InvalidIvLength, #[class(range)] #[error("Invalid key length")] InvalidKeyLength, #[class(type)] #[error("Invalid initialization vector")] InvalidInitializationVector, #[class(type)] #[error("bad decrypt")] CannotPadInputData, #[class(type)] #[error("Unknown cipher {0}")] UnknownCipher(String), #[class(type)] #[error("Invalid authentication tag length: {0}")] InvalidAuthTag(usize), } fn is_valid_chacha20_poly1305_tag_length(tag_len: usize) -> bool { (1..=16).contains(&tag_len) } impl Cipher { fn new( algorithm_name: &str, key: &[u8], iv: &[u8], auth_tag_length: Option<usize>, ) -> Result<Self, CipherError> { use Cipher::*; Ok(match algorithm_name { "aes128" | "aes-128-cbc" => { if key.len() != 16 { return Err(CipherError::InvalidKeyLength); } if iv.len() != 16 { return Err(CipherError::InvalidInitializationVector); } Aes128Cbc(Box::new(cbc::Encryptor::new(key.into(), iv.into()))) } "aes-128-ecb" => { if key.len() != 16 { return Err(CipherError::InvalidKeyLength); } if !iv.is_empty() { return Err(CipherError::InvalidInitializationVector); } Aes128Ecb(Box::new(ecb::Encryptor::new(key.into()))) } "aes-192-ecb" => { if key.len() != 24 { return Err(CipherError::InvalidKeyLength); } if !iv.is_empty() { return Err(CipherError::InvalidInitializationVector); } Aes192Ecb(Box::new(ecb::Encryptor::new(key.into()))) } "aes-256-ecb" => { if key.len() != 32 { return Err(CipherError::InvalidKeyLength); } if !iv.is_empty() { return Err(CipherError::InvalidInitializationVector); } Aes256Ecb(Box::new(ecb::Encryptor::new(key.into()))) } "aes-128-gcm" => { if key.len() != aes::Aes128::key_size() { return Err(CipherError::InvalidKeyLength); } if iv.is_empty() { return Err(CipherError::InvalidInitializationVector); } if let Some(tag_len) = auth_tag_length && !is_valid_gcm_tag_length(tag_len) { return Err(CipherError::InvalidAuthTag(tag_len)); } let cipher = aead_gcm_stream::AesGcm::<aes::Aes128>::new(key.into(), iv); Aes128Gcm(Box::new(cipher), auth_tag_length) } "aes-256-gcm" => { if key.len() != aes::Aes256::key_size() { return Err(CipherError::InvalidKeyLength); } if iv.is_empty() { return Err(CipherError::InvalidInitializationVector); } if let Some(tag_len) = auth_tag_length && !is_valid_gcm_tag_length(tag_len) { return Err(CipherError::InvalidAuthTag(tag_len)); } let cipher = aead_gcm_stream::AesGcm::<aes::Aes256>::new(key.into(), iv); Aes256Gcm(Box::new(cipher), auth_tag_length) } "aes256" | "aes-256-cbc" => { if key.len() != 32 { return Err(CipherError::InvalidKeyLength); } if iv.len() != 16 { return Err(CipherError::InvalidInitializationVector); } 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()))) } "des-ede3-cbc" => { if key.len() != 24 { return Err(CipherError::InvalidKeyLength); } if iv.len() != 8 { return Err(CipherError::InvalidInitializationVector); } DesEde3Cbc(Box::new(cbc::Encryptor::new(key.into(), iv.into()))) } "chacha20" => { if key.len() != 32 { return Err(CipherError::InvalidKeyLength); } if iv.len() != 16 { return Err(CipherError::InvalidInitializationVector); } ChaCha20(Box::new(ChaCha20Cipher::new(key, iv))) } "chacha20-poly1305" => { if key.len() != 32 { return Err(CipherError::InvalidKeyLength); } if iv.len() != 12 { return Err(CipherError::InvalidInitializationVector); } let tag_len = auth_tag_length.unwrap_or(16); if !is_valid_chacha20_poly1305_tag_length(tag_len) { return Err(CipherError::InvalidAuthTag(tag_len)); } ChaCha20Poly1305(Box::new( ChaCha20Poly1305Cipher::new(key, iv, tag_len, true).map_err(|e| { match e { CipherInitError::ContextAllocation => { panic!("Failed to allocate EVP_CIPHER_CTX") } CipherInitError::InitFailed => CipherError::InvalidKeyLength, } })?, )) } _ => return Err(CipherError::UnknownCipher(algorithm_name.to_string())), }) } fn set_aad(&mut self, aad: &[u8]) { use Cipher::*; match self { Aes128Gcm(cipher, _) => { cipher.set_aad(aad); } Aes256Gcm(cipher, _) => { cipher.set_aad(aad); } ChaCha20Poly1305(cipher) => { cipher.set_aad(aad); } _ => {} } } /// encrypt encrypts the data in the middle of the input. fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { use Cipher::*; match self { Aes128Cbc(encryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } Aes128Ecb(encryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } Aes192Ecb(encryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } Aes256Ecb(encryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } Aes128Gcm(cipher, _) => { output[..input.len()].copy_from_slice(input); cipher.encrypt(output); } Aes256Gcm(cipher, _) => { output[..input.len()].copy_from_slice(input); cipher.encrypt(output); } Aes256Cbc(encryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { 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(); } DesEde3Cbc(encryptor) => { assert!(input.len().is_multiple_of(8)); for (input, output) in input.chunks(8).zip(output.chunks_mut(8)) { encryptor.encrypt_block_b2b_mut(input.into(), output.into()); } } ChaCha20(cipher) => { cipher.apply_keystream(input, output); } ChaCha20Poly1305(cipher) => { cipher.encrypt(input, output); } } } /// r#final encrypts the last block of the input data. fn r#final( self, auto_pad: bool, input: &[u8], output: &mut [u8], ) -> Result<Tag, CipherError> { use Cipher::*; match (self, auto_pad) { (Aes128Cbc(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| CipherError::CannotPadInputData)?; Ok(None) } (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(|_| CipherError::CannotPadInputData)?; Ok(None) } (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(|_| CipherError::CannotPadInputData)?; Ok(None) } (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(|_| CipherError::CannotPadInputData)?; Ok(None) } (Aes256Ecb(mut encryptor), false) => { encryptor.encrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); Ok(None) } (Aes128Gcm(cipher, auth_tag_length), _) => { let mut tag = cipher.finish().to_vec(); if let Some(tag_len) = auth_tag_length { tag.truncate(tag_len); } Ok(Some(tag)) } (Aes256Gcm(cipher, auth_tag_length), _) => { let mut tag = cipher.finish().to_vec(); if let Some(tag_len) = auth_tag_length { tag.truncate(tag_len); } Ok(Some(tag)) } (Aes256Cbc(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| CipherError::CannotPadInputData)?; Ok(None) } (Aes256Cbc(mut encryptor), false) => { encryptor.encrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); Ok(None) } (Aes256Ctr(_) | Aes128Ctr(_) | Aes192Ctr(_) | ChaCha20(_), _) => Ok(None), (ChaCha20Poly1305(cipher), _) => { let tag = cipher.compute_tag(); Ok(Some(tag)) } (DesEde3Cbc(encryptor), true) => { let _ = (*encryptor) .encrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| CipherError::CannotPadInputData)?; Ok(None) } (DesEde3Cbc(mut encryptor), false) => { encryptor.encrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); Ok(None) } } } fn take_tag(self) -> Tag { use Cipher::*; match self { Aes128Gcm(cipher, auth_tag_length) => { let mut tag = cipher.finish().to_vec(); if let Some(tag_len) = auth_tag_length { tag.truncate(tag_len); } Some(tag) } Aes256Gcm(cipher, auth_tag_length) => { let mut tag = cipher.finish().to_vec(); if let Some(tag_len) = auth_tag_length { tag.truncate(tag_len); } Some(tag) } ChaCha20Poly1305(cipher) => { let tag = cipher.compute_tag(); Some(tag) } _ => None, } } } #[derive(Debug, thiserror::Error, deno_error::JsError)] #[property("library" = "Provider routines")] #[property("reason" = self.reason())] #[property("code" = self.code())] pub enum DecipherError { #[class(type)] #[error("IV length must be 12 bytes")] InvalidIvLength, #[class(range)] #[error("Invalid key length")] InvalidKeyLength, #[class(type)] #[error("Invalid authentication tag length: {0}")] InvalidAuthTag(usize), #[class(range)] #[error("error:1C80006B:Provider routines::wrong final block length")] InvalidFinalBlockLength, #[class(type)] #[error("Invalid initialization vector")] InvalidInitializationVector, #[class(type)] #[error("bad decrypt")] CannotUnpadInputData, #[class(type)] #[error("Unsupported state or unable to authenticate data")] DataAuthenticationFailed, #[class(type)] #[error("Unknown cipher {0}")] UnknownCipher(String), } impl DecipherError { fn code(&self) -> deno_error::PropertyValue { match self { Self::InvalidIvLength => { deno_error::PropertyValue::String("ERR_CRYPTO_INVALID_IV_LENGTH".into()) } Self::InvalidKeyLength => deno_error::PropertyValue::String( "ERR_CRYPTO_INVALID_KEY_LENGTH".into(), ), Self::InvalidAuthTag(_) => { deno_error::PropertyValue::String("ERR_CRYPTO_INVALID_AUTH_TAG".into()) } Self::InvalidFinalBlockLength => deno_error::PropertyValue::String( "ERR_OSSL_WRONG_FINAL_BLOCK_LENGTH".into(), ), Self::CannotUnpadInputData => { deno_error::PropertyValue::String("ERR_OSSL_EVP_BAD_DECRYPT".into()) } _ => deno_error::PropertyValue::String("ERR_CRYPTO_DECIPHER".into()), } } fn reason(&self) -> deno_error::PropertyValue { match self { Self::InvalidFinalBlockLength => { deno_error::PropertyValue::String("wrong final block length".into()) } _ => deno_error::PropertyValue::String(self.get_message()), } } } macro_rules! assert_block_len { ($input:expr, $len:expr) => { if $input != $len { return Err(DecipherError::InvalidFinalBlockLength); } }; } fn is_valid_gcm_tag_length(tag_len: usize) -> bool { tag_len == 4 || tag_len == 8 || (12..=16).contains(&tag_len) } impl Decipher { fn new( algorithm_name: &str, key: &[u8], iv: &[u8], auth_tag_length: Option<usize>, ) -> Result<Self, DecipherError> { use Decipher::*; Ok(match algorithm_name { "aes-128-cbc" => { if key.len() != 16 { return Err(DecipherError::InvalidKeyLength); } if iv.len() != 16 { return Err(DecipherError::InvalidInitializationVector); } Aes128Cbc(Box::new(cbc::Decryptor::new(key.into(), iv.into()))) } "aes-128-ecb" => { if key.len() != 16 { return Err(DecipherError::InvalidKeyLength); } if !iv.is_empty() { return Err(DecipherError::InvalidInitializationVector); } Aes128Ecb(Box::new(ecb::Decryptor::new(key.into()))) } "aes-192-ecb" => { if key.len() != 24 { return Err(DecipherError::InvalidKeyLength); } if !iv.is_empty() { return Err(DecipherError::InvalidInitializationVector); } Aes192Ecb(Box::new(ecb::Decryptor::new(key.into()))) } "aes-256-ecb" => { if key.len() != 32 { return Err(DecipherError::InvalidKeyLength); } if !iv.is_empty() { return Err(DecipherError::InvalidInitializationVector); } Aes256Ecb(Box::new(ecb::Decryptor::new(key.into()))) } "aes-128-gcm" => { if key.len() != aes::Aes128::key_size() { return Err(DecipherError::InvalidKeyLength); } if iv.is_empty() { return Err(DecipherError::InvalidInitializationVector); } if let Some(tag_len) = auth_tag_length && !is_valid_gcm_tag_length(tag_len) { return Err(DecipherError::InvalidAuthTag(tag_len)); } let decipher = aead_gcm_stream::AesGcm::<aes::Aes128>::new(key.into(), iv); Aes128Gcm(Box::new(decipher), auth_tag_length) } "aes-256-gcm" => { if key.len() != aes::Aes256::key_size() { return Err(DecipherError::InvalidKeyLength); } if iv.is_empty() { return Err(DecipherError::InvalidInitializationVector); } if let Some(tag_len) = auth_tag_length && !is_valid_gcm_tag_length(tag_len) { return Err(DecipherError::InvalidAuthTag(tag_len)); } let decipher = aead_gcm_stream::AesGcm::<aes::Aes256>::new(key.into(), iv); Aes256Gcm(Box::new(decipher), auth_tag_length) } "aes256" | "aes-256-cbc" => { if key.len() != 32 { return Err(DecipherError::InvalidKeyLength); } if iv.len() != 16 { return Err(DecipherError::InvalidInitializationVector); } 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()))) } "des-ede3-cbc" => { if key.len() != 24 { return Err(DecipherError::InvalidKeyLength); } if iv.len() != 8 { return Err(DecipherError::InvalidInitializationVector); } DesEde3Cbc(Box::new(cbc::Decryptor::new(key.into(), iv.into()))) } "chacha20" => { if key.len() != 32 { return Err(DecipherError::InvalidKeyLength); } if iv.len() != 16 { return Err(DecipherError::InvalidInitializationVector); } ChaCha20(Box::new(ChaCha20Cipher::new(key, iv))) } "chacha20-poly1305" => { if key.len() != 32 { return Err(DecipherError::InvalidKeyLength); } if iv.len() != 12 { return Err(DecipherError::InvalidInitializationVector); } let tag_len = auth_tag_length.unwrap_or(16); if !is_valid_chacha20_poly1305_tag_length(tag_len) { return Err(DecipherError::InvalidAuthTag(tag_len)); } ChaCha20Poly1305( Box::new( ChaCha20Poly1305Cipher::new(key, iv, tag_len, false).map_err( |e| match e { CipherInitError::ContextAllocation => { panic!("Failed to allocate EVP_CIPHER_CTX") } CipherInitError::InitFailed => DecipherError::InvalidKeyLength, }, )?, ), auth_tag_length, ) } _ => { return Err(DecipherError::UnknownCipher(algorithm_name.to_string())); } }) } fn validate_auth_tag(&self, length: usize) -> Result<(), DecipherError> { match self { Decipher::Aes128Gcm(_, Some(tag_len)) | Decipher::Aes256Gcm(_, Some(tag_len)) if *tag_len != length => { return Err(DecipherError::InvalidAuthTag(length)); } Decipher::Aes128Gcm(_, None) | Decipher::Aes256Gcm(_, None) if !is_valid_gcm_tag_length(length) => { return Err(DecipherError::InvalidAuthTag(length)); } Decipher::ChaCha20Poly1305(_, Some(tag_len)) if *tag_len != length => { return Err(DecipherError::InvalidAuthTag(length)); } Decipher::ChaCha20Poly1305(_, None) if length != 16 => { // Default tag length is 16; reject anything else return Err(DecipherError::InvalidAuthTag(length)); } _ => {} } Ok(()) } fn set_aad(&mut self, aad: &[u8]) { use Decipher::*; match self { Aes128Gcm(decipher, _) => { decipher.set_aad(aad); } Aes256Gcm(decipher, _) => { decipher.set_aad(aad); } ChaCha20Poly1305(decipher, _) => { decipher.set_aad(aad); } _ => {} } } /// decrypt decrypts the data in the middle of the input. fn decrypt(&mut self, input: &[u8], output: &mut [u8]) { use Decipher::*; match self { Aes128Cbc(decryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } Aes128Ecb(decryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } Aes192Ecb(decryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } Aes256Ecb(decryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } Aes128Gcm(decipher, _) => { output[..input.len()].copy_from_slice(input); decipher.decrypt(output); } Aes256Gcm(decipher, _) => { output[..input.len()].copy_from_slice(input); decipher.decrypt(output); } Aes256Cbc(decryptor) => { assert!(input.len().is_multiple_of(16)); for (input, output) in input.chunks(16).zip(output.chunks_mut(16)) { 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(); } DesEde3Cbc(decryptor) => { assert!(input.len().is_multiple_of(8)); for (input, output) in input.chunks(8).zip(output.chunks_mut(8)) { decryptor.decrypt_block_b2b_mut(input.into(), output.into()); } } ChaCha20(decipher) => { decipher.apply_keystream(input, output); } ChaCha20Poly1305(decipher, _) => { decipher.decrypt(input, output); } } } /// 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<(), DecipherError> { use Decipher::*; if input.is_empty() && !matches!( self, Aes128Ecb(..) | Aes192Ecb(..) | Aes256Ecb(..) | Aes128Gcm(..) | Aes256Gcm(..) | ChaCha20Poly1305(..) ) { return Ok(()); } match (self, auto_pad) { (Aes128Cbc(decryptor), true) => { assert_block_len!(input.len(), 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| DecipherError::CannotUnpadInputData)?; Ok(()) } (Aes128Cbc(mut decryptor), false) => { if !input.is_empty() { assert_block_len!(input.len(), 16); decryptor.decrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); } Ok(()) } (Aes128Ecb(decryptor), true) => { assert_block_len!(input.len(), 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| DecipherError::CannotUnpadInputData)?; Ok(()) } (Aes128Ecb(mut decryptor), false) => { if !input.is_empty() { assert_block_len!(input.len(), 16); decryptor.decrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); } Ok(()) } (Aes192Ecb(decryptor), true) => { assert_block_len!(input.len(), 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| DecipherError::CannotUnpadInputData)?; Ok(()) } (Aes192Ecb(mut decryptor), false) => { if !input.is_empty() { assert_block_len!(input.len(), 16); decryptor.decrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); } Ok(()) } (Aes256Ecb(decryptor), true) => { assert_block_len!(input.len(), 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| DecipherError::CannotUnpadInputData)?; Ok(()) } (Aes256Ecb(mut decryptor), false) => { if !input.is_empty() { assert_block_len!(input.len(), 16); decryptor.decrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); } Ok(()) } (Aes128Gcm(decipher, auth_tag_length), _) => { let tag = decipher.finish(); let tag_slice = tag.as_slice(); let truncated_tag = if let Some(len) = auth_tag_length { &tag_slice[..len] } else { tag_slice }; if truncated_tag.ct_eq(auth_tag).into() { Ok(()) } else { Err(DecipherError::DataAuthenticationFailed) } } (Aes256Gcm(decipher, auth_tag_length), _) => { let tag = decipher.finish(); let tag_slice = tag.as_slice(); let truncated_tag = if let Some(len) = auth_tag_length { &tag_slice[..len] } else { tag_slice }; if truncated_tag.ct_eq(auth_tag).into() { Ok(()) } else { Err(DecipherError::DataAuthenticationFailed) } } (ChaCha20Poly1305(decipher, _), _) => { if auth_tag.is_empty() { return Err(DecipherError::DataAuthenticationFailed); } if decipher.verify_tag(auth_tag) { Ok(()) } else { Err(DecipherError::DataAuthenticationFailed) } } (Aes256Cbc(decryptor), true) => { assert_block_len!(input.len(), 16); let _ = (*decryptor) .decrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| DecipherError::CannotUnpadInputData)?; Ok(()) } (Aes256Cbc(mut decryptor), false) => { if !input.is_empty() { assert_block_len!(input.len(), 16); decryptor.decrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); } 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(()) } (ChaCha20(mut decipher), _) => { decipher.apply_keystream(input, output); Ok(()) } (DesEde3Cbc(decryptor), true) => { assert_block_len!(input.len(), 8); let _ = (*decryptor) .decrypt_padded_b2b_mut::<Pkcs7>(input, output) .map_err(|_| DecipherError::CannotUnpadInputData)?; Ok(()) } (DesEde3Cbc(mut decryptor), false) => { if !input.is_empty() { assert_block_len!(input.len(), 8); decryptor.decrypt_block_b2b_mut( GenericArray::from_slice(input), GenericArray::from_mut_slice(output), ); } Ok(()) } } } }