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,
}
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),
};
let mut aes_key = MaybeUninit::<aws_lc_sys::AES_KEY>::uninit();
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);
}
let aes_key = unsafe { aes_key.assume_init() };
let is_pad = algorithm.ends_with("-pad");
if is_pad {
if iv.len() != 4 {
return Err(AesWrapError::InvalidIv);
}
if data.is_empty() {
return Err(AesWrapError::InvalidInputLength);
}
let mli = data.len() as u32;
let mut aiv = [0u8; 8];
aiv[..4].copy_from_slice(iv);
aiv[4..].copy_from_slice(&mli.to_be_bytes());
if data.len() <= 8 {
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];
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];
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 {
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];
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)
}
}
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]);
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)
}
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),
};
let mut aes_key = MaybeUninit::<aws_lc_sys::AES_KEY>::uninit();
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);
}
let aes_key = unsafe { aes_key.assume_init() };
let is_pad = algorithm.ends_with("-pad");
if is_pad {
if iv.len() != 4 {
return Err(AesWrapError::InvalidIv);
}
if data.len() < 16 || !data.len().is_multiple_of(8) {
return Err(AesWrapError::InvalidInputLength);
}
let (mut out, recovered_aiv) = if data.len() == 16 {
let mut block = [0u8; 16];
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)
};
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;
let n_blocks = out.len() / 8;
let mli_blocks = mli.div_ceil(8);
let len_ok = (mli >= 1 && mli_blocks == n_blocks) as u8;
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 {
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,
}
struct ChaCha20Poly1305Cipher {
ctx: *mut aws_lc_sys::EVP_CIPHER_CTX,
aad_buf: Vec<u8>,
aad_flushed: bool,
auth_tag_length: usize,
}
unsafe impl Send for ChaCha20Poly1305Cipher {}
impl ChaCha20Poly1305Cipher {
fn new(
key: &[u8],
iv: &[u8],
auth_tag_length: usize,
encrypting: bool,
) -> Result<Self, CipherInitError> {
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);
}
fn flush_aad(&mut self) {
if !self.aad_flushed {
self.aad_flushed = true;
if !self.aad_buf.is_empty() {
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();
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();
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();
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();
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) {
unsafe {
aws_lc_sys::EVP_CIPHER_CTX_free(self.ctx);
}
}
}
struct ChaCha20Cipher {
key: [u8; 32],
nonce: [u8; 12],
counter: u32,
pos: u64,
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],
}
}
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];
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,
);
}
}
fn apply_keystream(&mut self, input: &[u8], output: &mut [u8]) {
assert!(output.len() >= input.len());
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..];
let whole = input.len() - input.len() % 64;
if whole != 0 {
let counter = self.counter.wrapping_add((self.pos / 64) as u32);
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;
}
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>),
}
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>),
}
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);
}
_ => {}
}
}
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);
}
}
}
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 => {
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);
}
_ => {}
}
}
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);
}
}
}
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(())
}
}
}
}