use super::IndCpaCipher;
use tink_core::{utils::wrap_err, TinkError};
pub struct EncryptThenAuthenticate {
ind_cpa_cipher: Box<dyn IndCpaCipher>,
mac: Box<dyn tink_core::Mac>,
tag_size: usize,
}
impl Clone for EncryptThenAuthenticate {
fn clone(&self) -> Self {
Self {
ind_cpa_cipher: self.ind_cpa_cipher.box_clone(),
mac: self.mac.box_clone(),
tag_size: self.tag_size,
}
}
}
const MIN_TAG_SIZE_IN_BYTES: usize = 10;
impl EncryptThenAuthenticate {
pub fn new(
ind_cpa_cipher: Box<dyn IndCpaCipher>,
mac: Box<dyn tink_core::Mac>,
tag_size: usize,
) -> Result<EncryptThenAuthenticate, TinkError> {
if tag_size < MIN_TAG_SIZE_IN_BYTES {
return Err("EncryptThenAuthenticate: tag size too small".into());
}
Ok(EncryptThenAuthenticate {
ind_cpa_cipher,
mac,
tag_size,
})
}
}
impl tink_core::Aead for EncryptThenAuthenticate {
fn encrypt(&self, plaintext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, TinkError> {
let mut ciphertext = self
.ind_cpa_cipher
.encrypt(plaintext)
.map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
let mut to_auth_data = Vec::with_capacity(additional_data.len() + ciphertext.len() + 8);
to_auth_data.extend_from_slice(additional_data);
to_auth_data.extend_from_slice(&ciphertext);
let aad_size_in_bits: u64 = (additional_data.len() as u64)
.checked_mul(8)
.ok_or_else(|| TinkError::new("EncryptThenAuthenticate: additional data too long"))?;
to_auth_data.extend_from_slice(&aad_size_in_bits.to_be_bytes());
let tag = self
.mac
.compute_mac(&to_auth_data)
.map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
if tag.len() != self.tag_size {
return Err("EncryptThenAuthenticate: invalid tag size".into());
}
ciphertext.extend_from_slice(&tag);
Ok(ciphertext)
}
fn decrypt(&self, ciphertext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, TinkError> {
if ciphertext.len() < self.tag_size {
return Err("EncryptThenAuthenticate: ciphertext too short".into());
}
let payload = &ciphertext[..(ciphertext.len() - self.tag_size)];
let mut to_auth_data = Vec::with_capacity(additional_data.len() + payload.len() + 8);
to_auth_data.extend_from_slice(additional_data);
to_auth_data.extend_from_slice(payload);
let aad_size_in_bits: u64 = (additional_data.len() as u64)
.checked_mul(8)
.ok_or_else(|| TinkError::new("EncryptThenAuthenticate: additional data too long"))?;
to_auth_data.extend_from_slice(&aad_size_in_bits.to_be_bytes());
self.mac
.verify_mac(
&ciphertext[(ciphertext.len() - self.tag_size)..],
&to_auth_data,
)
.map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
let plaintext = self
.ind_cpa_cipher
.decrypt(payload)
.map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
Ok(plaintext)
}
}