use core::{
marker::PhantomData,
num::NonZeroUsize,
ops::{Shl, Shr},
};
use aes::{
cipher::{
block_padding::{Padding, Pkcs7},
BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockSizeUser, InnerIvInit, KeyInit, KeySizeUser,
},
Aes128, Aes192, Aes256,
};
use cbc::{Decryptor as CbcDecryptor, Encryptor as CbcEncryptor};
use generic_array::{
typenum::{Unsigned, B1},
ArrayLength,
};
use hmac::{
digest::{Digest, FixedOutput, OutputSizeUser, Update},
SimpleHmac,
};
use sha2::{Sha256, Sha384, Sha512};
use subtle::ConstantTimeEq;
use zeroize::Zeroize;
use crate::ciphers::traits::{Aead, Key, Nonce, Tag};
pub type Aes128CbcHmac256 = CbcHmac<Aes128, Pkcs7, Sha256>;
pub type Aes192CbcHmac384 = CbcHmac<Aes192, Pkcs7, Sha384>;
pub type Aes256CbcHmac512 = CbcHmac<Aes256, Pkcs7, Sha512>;
#[derive(Clone, Copy, Debug)]
pub struct CbcHmac<Cipher, Pad, Hash> {
cipher: PhantomData<Cipher>,
padding: PhantomData<Pad>,
digest: PhantomData<Hash>,
}
impl<Cipher, Pad, Hash> CbcHmac<Cipher, Pad, Hash>
where
Cipher: BlockCipher,
{
const BLOCK_SIZE: usize = <<Cipher as BlockSizeUser>::BlockSize as Unsigned>::USIZE;
}
impl<Cipher, Pad, Hash> Aead for CbcHmac<Cipher, Pad, Hash>
where
Cipher: BlockCipher + BlockDecryptMut + BlockEncryptMut + KeySizeUser + KeyInit,
<Cipher as KeySizeUser>::KeySize: ArrayLength<u8> + Shl<B1>,
<<Cipher as KeySizeUser>::KeySize as Shl<B1>>::Output: ArrayLength<u8>,
Pad: Padding<<Cipher as BlockSizeUser>::BlockSize>,
Hash: BlockSizeUser + Digest + FixedOutput,
<Hash as OutputSizeUser>::OutputSize: ArrayLength<u8> + Shr<B1>,
<<Hash as OutputSizeUser>::OutputSize as Shr<B1>>::Output: ArrayLength<u8>,
{
type KeyLength = <<Cipher as KeySizeUser>::KeySize as Shl<B1>>::Output;
type NonceLength = <Cipher as BlockSizeUser>::BlockSize;
type TagLength = <<Hash as OutputSizeUser>::OutputSize as Shr<B1>>::Output;
const NAME: &'static str = "AES-CBC";
fn encrypt(
key: &Key<Self>,
nonce: &Nonce<Self>,
associated_data: &[u8],
plaintext: &[u8],
ciphertext: &mut [u8],
tag: &mut Tag<Self>,
) -> crate::Result<usize> {
let length: usize = plaintext.len();
let padding: usize = Self::padsize(plaintext).map(NonZeroUsize::get).unwrap_or_default();
let expected: usize = length + padding;
if expected > ciphertext.len() {
return Err(crate::Error::BufferSize {
name: "ciphertext",
needs: expected,
has: ciphertext.len(),
});
}
let cipher = CbcEncryptor::<Cipher>::inner_iv_init(
Cipher::new_from_slice(&key.as_slice()[Self::KEY_LENGTH / 2..]).unwrap(),
nonce,
);
let cipher_slice = cipher
.encrypt_padded_b2b_mut::<Pad>(plaintext, ciphertext)
.map_err(|_| crate::Error::CipherError { alg: Self::NAME })?;
debug_assert_eq!(expected, cipher_slice.len());
let mut hmac: SimpleHmac<Hash> =
<SimpleHmac<Hash> as KeyInit>::new_from_slice(&key.as_slice()[..Self::KEY_LENGTH / 2]).unwrap();
hmac.update(associated_data);
hmac.update(nonce);
hmac.update(ciphertext);
hmac.update(&((associated_data.len() as u64) * 8).to_be_bytes());
let hash = hmac.finalize_fixed();
tag.copy_from_slice(&hash.as_slice()[..Self::TAG_LENGTH]);
Ok(expected)
}
fn decrypt(
key: &Key<Self>,
nonce: &Nonce<Self>,
associated_data: &[u8],
plaintext: &mut [u8],
ciphertext: &[u8],
tag: &Tag<Self>,
) -> crate::Result<usize> {
let length = ciphertext.len();
if length > plaintext.len() {
return Err(crate::Error::BufferSize {
name: "plaintext",
needs: length,
has: plaintext.len(),
});
}
let mut hmac: SimpleHmac<Hash> =
<SimpleHmac<Hash> as KeyInit>::new_from_slice(&key.as_slice()[..Self::KEY_LENGTH / 2]).unwrap();
hmac.update(associated_data);
hmac.update(nonce);
hmac.update(ciphertext);
hmac.update(&((associated_data.len() as u64) * 8).to_be_bytes());
let hash = hmac.finalize_fixed();
let mut computed = Tag::<Self>::default();
computed.copy_from_slice(&hash.as_slice()[..Self::TAG_LENGTH]);
if !bool::from(computed.ct_eq(tag)) {
return Err(crate::Error::CipherError { alg: Self::NAME });
}
let cipher = CbcDecryptor::<Cipher>::inner_iv_init(
Cipher::new_from_slice(&key.as_slice()[Self::KEY_LENGTH / 2..]).unwrap(),
nonce,
);
if let Ok(plaintext_slice) = cipher.decrypt_padded_b2b_mut::<Pad>(ciphertext, plaintext) {
Ok(plaintext_slice.len())
} else {
plaintext.zeroize();
Err(crate::Error::CipherError { alg: Self::NAME })
}
}
fn padsize(plaintext: &[u8]) -> Option<NonZeroUsize> {
NonZeroUsize::new(Self::BLOCK_SIZE - (plaintext.len() % Self::BLOCK_SIZE))
}
}