#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![cfg_attr(feature = "getrandom", doc = "```")]
#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
#![cfg_attr(
not(all(feature = "getrandom", feature = "arrayvec")),
doc = "```ignore"
)]
pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser};
#[cfg(feature = "aes")]
pub use aes;
use aead::{TagPosition, inout::InOutBuf};
use cipher::{
BlockCipherEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore,
array::{Array, ArraySize},
consts::U16,
};
use core::{fmt, marker::PhantomData};
use ghash::{GHash, universal_hash::UniversalHash};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[cfg(feature = "aes")]
use aes::{Aes128, Aes256, cipher::consts::U12};
pub const A_MAX: u64 = (1 << 61) - 1;
pub const P_MAX: u64 = (1 << 36) - 32;
pub type Nonce<NonceSize> = Array<u8, NonceSize>;
pub type Tag<TagSize = U16> = Array<u8, TagSize>;
pub trait TagSize: private::SealedTagSize {}
impl<T: private::SealedTagSize> TagSize for T {}
mod private {
use cipher::{array::ArraySize, consts, typenum::Unsigned};
pub trait SealedTagSize: ArraySize + Unsigned {}
#[cfg(feature = "hazmat")]
impl SealedTagSize for consts::U4 {}
#[cfg(feature = "hazmat")]
impl SealedTagSize for consts::U8 {}
impl SealedTagSize for consts::U12 {}
impl SealedTagSize for consts::U13 {}
impl SealedTagSize for consts::U14 {}
impl SealedTagSize for consts::U15 {}
impl SealedTagSize for consts::U16 {}
}
#[cfg(feature = "aes")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
pub type Aes128Gcm = AesGcm<Aes128, U12>;
#[cfg(feature = "aes")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
pub type Aes256Gcm = AesGcm<Aes256, U12>;
type Block = Array<u8, U16>;
type Ctr32BE<Aes> = ctr::CtrCore<Aes, ctr::flavors::Ctr32BE>;
#[derive(Clone)]
pub struct AesGcm<Aes, NonceSize, TagSize = U16>
where
TagSize: crate::TagSize,
{
cipher: Aes,
ghash: GHash,
nonce_size: PhantomData<NonceSize>,
tag_size: PhantomData<TagSize>,
}
impl<Aes, NonceSize, TagSize> KeySizeUser for AesGcm<Aes, NonceSize, TagSize>
where
Aes: KeySizeUser,
TagSize: crate::TagSize,
{
type KeySize = Aes::KeySize;
}
impl<Aes, NonceSize, TagSize> KeyInit for AesGcm<Aes, NonceSize, TagSize>
where
Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
TagSize: crate::TagSize,
{
fn new(key: &Key<Self>) -> Self {
Aes::new(key).into()
}
}
impl<Aes, NonceSize, TagSize> From<Aes> for AesGcm<Aes, NonceSize, TagSize>
where
Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
TagSize: crate::TagSize,
{
fn from(cipher: Aes) -> Self {
let mut ghash_key = ghash::Key::default();
cipher.encrypt_block(&mut ghash_key);
let ghash = GHash::new(&ghash_key);
#[cfg(feature = "zeroize")]
ghash_key.zeroize();
Self {
cipher,
ghash,
nonce_size: PhantomData,
tag_size: PhantomData,
}
}
}
impl<Aes, NonceSize, TagSize> AeadCore for AesGcm<Aes, NonceSize, TagSize>
where
NonceSize: ArraySize,
TagSize: crate::TagSize,
{
type NonceSize = NonceSize;
type TagSize = TagSize;
const TAG_POSITION: TagPosition = TagPosition::Postfix;
}
impl<Aes, NonceSize, TagSize> AeadInOut for AesGcm<Aes, NonceSize, TagSize>
where
Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
NonceSize: ArraySize,
TagSize: crate::TagSize,
{
fn encrypt_inout_detached(
&self,
nonce: &Nonce<NonceSize>,
associated_data: &[u8],
mut buffer: InOutBuf<'_, '_, u8>,
) -> Result<Tag<TagSize>, Error> {
if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
}
let (ctr, mask) = self.init_ctr(nonce);
ctr.apply_keystream_partial(buffer.reborrow());
let full_tag = self.compute_tag(mask, associated_data, buffer.get_out());
full_tag[..TagSize::to_usize()]
.try_into()
.map_err(|_| Error)
}
fn decrypt_inout_detached(
&self,
nonce: &Nonce<NonceSize>,
associated_data: &[u8],
buffer: InOutBuf<'_, '_, u8>,
tag: &Tag<TagSize>,
) -> Result<(), Error> {
if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
}
let (ctr, mask) = self.init_ctr(nonce);
let expected_tag = self.compute_tag(mask, associated_data, buffer.get_in());
use subtle::ConstantTimeEq;
if expected_tag[..TagSize::to_usize()].ct_eq(tag).into() {
ctr.apply_keystream_partial(buffer);
Ok(())
} else {
Err(Error)
}
}
}
impl<Aes, NonceSize, TagSize> AesGcm<Aes, NonceSize, TagSize>
where
Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
NonceSize: ArraySize,
TagSize: crate::TagSize,
{
fn init_ctr(&self, nonce: &Nonce<NonceSize>) -> (Ctr32BE<&Aes>, Block) {
let j0 = if NonceSize::to_usize() == 12 {
let mut block = ghash::Block::default();
block[..12].copy_from_slice(nonce);
block[15] = 1;
block
} else {
let mut ghash = self.ghash.clone();
ghash.update_padded(nonce);
let mut block = ghash::Block::default();
let nonce_bits = (NonceSize::to_usize() as u64) * 8;
block[8..].copy_from_slice(&nonce_bits.to_be_bytes());
ghash.update(&[block]);
ghash.finalize()
};
let mut ctr = Ctr32BE::inner_iv_init(&self.cipher, &j0);
let mut tag_mask = Block::default();
ctr.write_keystream_block(&mut tag_mask);
(ctr, tag_mask)
}
fn compute_tag(&self, mask: Block, associated_data: &[u8], buffer: &[u8]) -> Tag {
let mut ghash = self.ghash.clone();
ghash.update_padded(associated_data);
ghash.update_padded(buffer);
let associated_data_bits = (associated_data.len() as u64) * 8;
let buffer_bits = (buffer.len() as u64) * 8;
let mut block = ghash::Block::default();
block[..8].copy_from_slice(&associated_data_bits.to_be_bytes());
block[8..].copy_from_slice(&buffer_bits.to_be_bytes());
ghash.update(&[block]);
let mut tag = ghash.finalize();
for (a, b) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) {
*a ^= *b;
}
tag
}
}
impl<Aes, NonceSize, TagSize> fmt::Debug for AesGcm<Aes, NonceSize, TagSize>
where
TagSize: crate::TagSize,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("AesGcm").finish_non_exhaustive()
}
}