use crate::core::{
AuthEncParams, AuthEncProvider, AuthEncSpec, GenericEncryptError, Mac, VerifyMacResult,
};
use cipher::{BlockCipherEncrypt, BlockSizeUser, KeyInit, KeyIvInit, KeySizeUser, StreamCipher};
use core::convert::Infallible;
use core::marker::PhantomData;
use ctr::Ctr32BE;
use ctutils::CtEq;
use ghash::{GHash, universal_hash::UniversalHash};
use hybrid_array::Array;
use hybrid_array::typenum::{U12, U16};
type Block = Array<u8, U16>;
#[derive(Debug, Clone, Copy, Default)]
pub struct AesGcmSpec<Aes>(PhantomData<Aes>);
impl<Aes> AuthEncSpec for AesGcmSpec<Aes>
where Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit
{
type KeySize = Aes::KeySize;
type IvSize = U12;
type MacSize = U16;
}
#[derive(Debug, Clone, Copy)]
pub struct AesGcm<Aes>(PhantomData<Aes>);
impl<Aes> Default for AesGcm<Aes> {
fn default() -> Self { Self(PhantomData) }
}
impl<Aes> AesGcm<Aes>
where Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit
{
fn derive_h(cipher: &Aes) -> Block {
let mut h = Block::default();
cipher.encrypt_block(&mut h);
h
}
fn build_j0(iv: &Array<u8, U12>) -> Block {
let mut j0 = Block::default();
j0[..12].copy_from_slice(iv);
j0[15] = 1;
j0
}
fn feed_slices(ghash: &mut GHash, slices: &[&[u8]]) -> usize {
let mut buf = Block::default();
let mut buf_pos = 0;
let mut total = 0;
for slice in slices {
let mut remaining = *slice;
total += remaining.len();
if buf_pos > 0 {
let fill = (16 - buf_pos).min(remaining.len());
buf[buf_pos..buf_pos + fill].copy_from_slice(&remaining[..fill]);
buf_pos += fill;
remaining = &remaining[fill..];
if buf_pos == 16 {
ghash.update(&[buf]);
buf = Block::default();
buf_pos = 0;
}
}
let full_blocks = remaining.len() / 16;
for block_bytes in remaining[..full_blocks * 16].chunks_exact(16) {
let mut block = Block::default();
block.copy_from_slice(block_bytes);
ghash.update(&[block]);
}
let tail = remaining.len() % 16;
if tail > 0 {
buf[..tail].copy_from_slice(&remaining[full_blocks * 16..]);
buf_pos = tail;
}
}
if buf_pos > 0 {
ghash.update(&[buf]);
}
total
}
fn finalize_tag(mut ghash: GHash, tag_mask: &Block, aad_len: usize, ct_len: usize) -> Block {
let mut len_block = Block::default();
len_block[..8].copy_from_slice(&((aad_len as u64 * 8).to_be_bytes()));
len_block[8..].copy_from_slice(&((ct_len as u64 * 8).to_be_bytes()));
ghash.update(&[len_block]);
let mut tag = ghash.finalize();
for (a, b) in tag.iter_mut().zip(tag_mask.iter()) {
*a ^= *b;
}
tag
}
}
impl<Aes> AuthEncProvider for AesGcm<Aes>
where Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser
{
type Spec = AesGcmSpec<Aes>;
type SealError = GenericEncryptError;
type OpenError = Infallible;
fn seal(
&self,
p: &AuthEncParams<Self::Spec>,
aad: &[&[u8]],
plain: &[u8],
cipher: &mut [u8],
) -> Result<(usize, Mac<<Self::Spec as AuthEncSpec>::MacSize>), Self::SealError> {
if cipher.len() < plain.len() {
return Err(GenericEncryptError::OutputTooSmall);
}
let aes = Aes::new(&p.key);
let h = Self::derive_h(&aes);
let mut ghash = GHash::new(&h);
let j0 = Self::build_j0(&p.iv);
let mut ctr = <Ctr32BE<Aes> as KeyIvInit>::new(&p.key, &j0);
let mut tag_mask = Block::default();
ctr.apply_keystream(&mut tag_mask);
let aad_len = Self::feed_slices(&mut ghash, aad);
cipher[..plain.len()].copy_from_slice(plain);
ctr.apply_keystream(&mut cipher[..plain.len()]);
Self::feed_slices(&mut ghash, &[&cipher[..plain.len()]]);
let tag = Self::finalize_tag(ghash, &tag_mask, aad_len, plain.len());
Ok((plain.len(), tag.into()))
}
fn open(
&self,
p: &AuthEncParams<Self::Spec>,
aad: &[&[u8]],
cipher: &mut [u8],
mac: &[u8],
) -> Result<(usize, VerifyMacResult), Self::OpenError> {
let n = mac.len();
if n == 0 || n > 16 {
return Ok((0, VerifyMacResult::InvalidMac));
}
let aes = Aes::new(&p.key);
let h = Self::derive_h(&aes);
let mut ghash = GHash::new(&h);
let j0 = Self::build_j0(&p.iv);
let mut ctr = <Ctr32BE<Aes> as KeyIvInit>::new(&p.key, &j0);
let mut tag_mask = Block::default();
ctr.apply_keystream(&mut tag_mask);
let aad_len = Self::feed_slices(&mut ghash, aad);
Self::feed_slices(&mut ghash, &[cipher]);
let expected_tag = Self::finalize_tag(ghash, &tag_mask, aad_len, cipher.len());
if !bool::from(mac.ct_eq(&expected_tag[..n])) {
return Ok((0, VerifyMacResult::InvalidMac));
}
ctr.apply_keystream(cipher);
let verify = if n < 16 { VerifyMacResult::OkButTruncated } else { VerifyMacResult::Ok };
Ok((cipher.len(), verify))
}
fn pad_len(_: usize) -> u16 { 0 }
}
#[cfg(test)]
mod tests {
use aes::Aes128;
use std::vec;
use super::AesGcm;
use crate::core::{AuthEncParams, AuthEncProvider, GenericEncryptError, VerifyMacResult};
use assert_matches::assert_matches;
type Provider = AesGcm<Aes128>;
#[test]
fn round_trip() {
let provider = Provider::default();
let params = AuthEncParams { key: [0x77_u8; 16].into(), iv: [0x33_u8; 12].into() };
let aad: &[&[u8]] = &[b"security-header"];
let plain = b"protected-space-pdu";
let mut cipher = vec![0_u8; plain.len()];
let (cipher_len, mac) = provider.seal(¶ms, aad, plain, &mut cipher).unwrap();
assert_eq!(cipher_len, plain.len());
assert_eq!(mac.len(), 16);
let (recovered_len, mac_result) =
provider.open(¶ms, aad, &mut cipher[..cipher_len], mac.as_slice()).unwrap();
assert_matches!(mac_result, VerifyMacResult::Ok);
assert_eq!(&cipher[..recovered_len], plain);
}
#[test]
fn streaming_aad() {
let provider = Provider::default();
let params = AuthEncParams { key: [0x77_u8; 16].into(), iv: [0x33_u8; 12].into() };
let plain = b"protected-space-pdu";
let mut cipher_single = vec![0_u8; plain.len()];
let (_, mac_single) = provider
.seal(¶ms, &[b"header-part-a", b"header-part-b"], plain, &mut cipher_single)
.unwrap();
let mut cipher_concat = vec![0_u8; plain.len()];
let (_, mac_concat) = provider
.seal(¶ms, &[b"header-part-aheader-part-b"], plain, &mut cipher_concat)
.unwrap();
assert_eq!(cipher_single, cipher_concat);
assert_eq!(*mac_single, *mac_concat);
}
#[test]
fn reject_wrong_aad() {
let provider = Provider::default();
let params = AuthEncParams { key: [0x12_u8; 16].into(), iv: [0x34_u8; 12].into() };
let aad: &[&[u8]] = &[b"aad-ok"];
let wrong_aad: &[&[u8]] = &[b"aad-wrong"];
let plain = b"payload";
let mut cipher = vec![0_u8; plain.len()];
let (cipher_len, tag) = provider.seal(¶ms, aad, plain, &mut cipher).unwrap();
let result =
provider.open(¶ms, wrong_aad, &mut cipher[..cipher_len], tag.as_slice()).unwrap();
assert!(matches!(result.1, VerifyMacResult::InvalidMac));
}
#[test]
fn reject_output_too_small() {
let provider = Provider::default();
let params = AuthEncParams { key: [0x44_u8; 16].into(), iv: [0x55_u8; 12].into() };
let aad: &[&[u8]] = &[b"security-header"];
let plain = b"protected-space-pdu";
let mut cipher = vec![0_u8; plain.len() - 1];
let result = provider.seal(¶ms, aad, plain, &mut cipher);
assert_matches!(result, Err(GenericEncryptError::OutputTooSmall));
}
#[test]
fn truncated_mac_accepted() {
let provider = Provider::default();
let params = AuthEncParams { key: [0x44_u8; 16].into(), iv: [0x55_u8; 12].into() };
let aad: &[&[u8]] = &[b"security-header"];
let plain = b"protected-space-pdu";
let mut cipher = vec![0_u8; plain.len()];
let (cipher_len, tag) = provider.seal(¶ms, aad, plain, &mut cipher).unwrap();
let mut decrypt_buf = cipher[..cipher_len].to_vec();
let (recovered, result) =
provider.open(¶ms, aad, &mut decrypt_buf, &tag.as_slice()[..12]).unwrap();
assert_matches!(result, VerifyMacResult::OkButTruncated);
assert_eq!(&decrypt_buf[..recovered], plain);
}
#[test]
fn truncated_mac_rejected_if_tampered() {
let provider = Provider::default();
let params = AuthEncParams { key: [0x44_u8; 16].into(), iv: [0x55_u8; 12].into() };
let aad: &[&[u8]] = &[b"security-header"];
let plain = b"protected-space-pdu";
let mut cipher = vec![0_u8; plain.len()];
let (cipher_len, tag) = provider.seal(¶ms, aad, plain, &mut cipher).unwrap();
let mut bad_tag = tag.as_slice()[..12].to_vec();
bad_tag[0] ^= 0x01;
let mut decrypt_buf = cipher[..cipher_len].to_vec();
let (_, result) = provider.open(¶ms, aad, &mut decrypt_buf, &bad_tag).unwrap();
assert_matches!(result, VerifyMacResult::InvalidMac);
}
}