#![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, consts};
use aead::{
TagPosition,
array::{Array, ArraySize, typenum::Unsigned},
consts::U16,
inout::InOutBuf,
};
use cipher::{
Block, BlockCipherEncrypt, BlockSizeUser, InnerIvInit, StreamCipher, StreamCipherSeek,
};
use core::{fmt, marker::PhantomData};
use ctr::{Ctr32BE, Ctr64BE, CtrCore};
use subtle::ConstantTimeEq;
mod private;
pub type Nonce<NonceSize> = Array<u8, NonceSize>;
pub type Tag<TagSize> = Array<u8, TagSize>;
pub trait TagSize: private::SealedTag {}
impl<T: private::SealedTag> TagSize for T {}
pub trait NonceSize: private::SealedNonce {}
impl<T: private::SealedNonce> NonceSize for T {}
#[derive(Clone)]
pub struct Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
cipher: C,
_pd: PhantomData<(M, N)>,
}
impl<C, M, N> Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
fn extend_nonce(nonce: &Nonce<N>) -> Block<C> {
let mut ext_nonce = Block::<C>::default();
ext_nonce[0] = N::get_l() - 1;
ext_nonce[1..][..nonce.len()].copy_from_slice(nonce);
ext_nonce
}
fn calc_mac(
&self,
nonce: &Nonce<N>,
adata: &[u8],
buffer: &[u8],
) -> Result<Tag<C::BlockSize>, Error> {
let is_ad = !adata.is_empty();
let l = N::get_l();
let flags = 64 * u8::from(is_ad) + 8 * M::get_m_tick() + (l - 1);
if buffer.len() > N::get_max_len() {
return Err(Error);
}
let mut b0 = Block::<C>::default();
b0[0] = flags;
let n = 1 + N::to_usize();
b0[1..n].copy_from_slice(nonce);
let cb = b0.len() - n;
#[allow(clippy::cast_possible_truncation, reason = "TODO")]
if cb > 4 {
let b = (buffer.len() as u64).to_be_bytes();
b0[n..].copy_from_slice(&b[b.len() - cb..]);
} else {
let b = (buffer.len() as u32).to_be_bytes();
b0[n..].copy_from_slice(&b[b.len() - cb..]);
}
let mut mac = CbcMac::from_cipher(&self.cipher);
mac.block_update(&b0);
if !adata.is_empty() {
let alen = adata.len();
let (n, mut b) = fill_aad_header(alen);
if b.len() - n >= alen {
b[n..][..alen].copy_from_slice(adata);
mac.block_update(&b);
} else {
let (l, r) = adata.split_at(b.len() - n);
b[n..].copy_from_slice(l);
mac.block_update(&b);
mac.update(r);
}
}
mac.update(buffer);
Ok(mac.finalize())
}
}
impl<C, M, N> From<C> for Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
fn from(cipher: C) -> Self {
Self {
cipher,
_pd: PhantomData,
}
}
}
impl<C, M, N> KeySizeUser for Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
type KeySize = C::KeySize;
}
impl<C, M, N> KeyInit for Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
fn new(key: &Key<Self>) -> Self {
Self::from(C::new(key))
}
}
impl<C, M, N> AeadCore for Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
type NonceSize = N;
type TagSize = M;
const TAG_POSITION: TagPosition = TagPosition::Postfix;
}
impl<C, M, N> AeadInOut for Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
fn encrypt_inout_detached(
&self,
nonce: &Nonce<N>,
adata: &[u8],
buffer: InOutBuf<'_, '_, u8>,
) -> Result<Tag<Self::TagSize>, Error> {
let mut full_tag = self.calc_mac(nonce, adata, buffer.get_in())?;
let ext_nonce = Self::extend_nonce(nonce);
let cb = C::BlockSize::USIZE - N::USIZE - 1;
if cb > 4 {
let mut ctr = Ctr64BE::from_core(CtrCore::inner_iv_init(&self.cipher, &ext_nonce));
ctr.apply_keystream(&mut full_tag);
ctr.apply_keystream_inout(buffer);
} else {
let mut ctr = Ctr32BE::from_core(CtrCore::inner_iv_init(&self.cipher, &ext_nonce));
ctr.apply_keystream(&mut full_tag);
ctr.apply_keystream_inout(buffer);
}
full_tag[..M::to_usize()].try_into().map_err(|_| Error)
}
fn decrypt_inout_detached(
&self,
nonce: &Nonce<N>,
adata: &[u8],
mut buffer: InOutBuf<'_, '_, u8>,
tag: &Tag<Self::TagSize>,
) -> Result<(), Error> {
let ext_nonce = Self::extend_nonce(nonce);
let cb = C::BlockSize::USIZE - N::USIZE - 1;
if cb > 4 {
let mut ctr = Ctr64BE::from_core(CtrCore::inner_iv_init(&self.cipher, &ext_nonce));
ctr.seek(C::BlockSize::USIZE);
ctr.apply_keystream_inout(buffer.reborrow());
} else {
let mut ctr = Ctr32BE::from_core(CtrCore::inner_iv_init(&self.cipher, &ext_nonce));
ctr.seek(C::BlockSize::USIZE);
ctr.apply_keystream_inout(buffer.reborrow());
}
let mut full_tag = self.calc_mac(nonce, adata, buffer.get_out())?;
if cb > 4 {
let mut ctr = Ctr64BE::from_core(CtrCore::inner_iv_init(&self.cipher, &ext_nonce));
ctr.apply_keystream(&mut full_tag);
} else {
let mut ctr = Ctr32BE::from_core(CtrCore::inner_iv_init(&self.cipher, &ext_nonce));
ctr.apply_keystream(&mut full_tag);
}
if full_tag[..tag.len()].ct_eq(tag).into() {
Ok(())
} else {
buffer.get_out().fill(0);
Err(Error)
}
}
}
impl<C, M, N> fmt::Debug for Ccm<C, M, N>
where
C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
M: ArraySize + TagSize,
N: ArraySize + NonceSize,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ccm").finish_non_exhaustive()
}
}
struct CbcMac<'a, C: BlockCipherEncrypt> {
cipher: &'a C,
state: Block<C>,
}
impl<'a, C> CbcMac<'a, C>
where
C: BlockCipherEncrypt,
{
fn from_cipher(cipher: &'a C) -> Self {
Self {
cipher,
state: Default::default(),
}
}
fn update(&mut self, data: &[u8]) {
let (blocks, rem) = Block::<C>::slice_as_chunks(data);
for block in blocks {
self.block_update(block);
}
if !rem.is_empty() {
let mut bn = Block::<C>::default();
bn[..rem.len()].copy_from_slice(rem);
self.block_update(&bn);
}
}
fn block_update(&mut self, block: &Block<C>) {
self.state
.iter_mut()
.zip(block.iter())
.for_each(|(a, b)| *a ^= b);
self.cipher.encrypt_block(&mut self.state);
}
fn finalize(self) -> Block<C> {
self.state
}
}
#[allow(clippy::cast_possible_truncation, reason = "TODO")]
fn fill_aad_header(adata_len: usize) -> (usize, Array<u8, U16>) {
debug_assert_ne!(adata_len, 0);
let mut b = Array::<u8, U16>::default();
let n = if adata_len < 0xFF00 {
b[..2].copy_from_slice(&(adata_len as u16).to_be_bytes());
2
} else if u32::try_from(adata_len).is_ok() {
b[0] = 0xFF;
b[1] = 0xFE;
b[2..6].copy_from_slice(&(adata_len as u32).to_be_bytes());
6
} else {
b[0] = 0xFF;
b[1] = 0xFF;
b[2..10].copy_from_slice(&(adata_len as u64).to_be_bytes());
10
};
(n, b)
}
#[cfg(test)]
mod tests {
#[test]
fn fill_aad_header_test() {
use super::fill_aad_header;
use hex_literal::hex;
let (n, b) = fill_aad_header(0x0123);
assert_eq!(n, 2);
assert_eq!(b[..], hex!("01230000000000000000000000000000")[..]);
let (n, b) = fill_aad_header(0xFF00);
assert_eq!(n, 6);
assert_eq!(b[..], hex!("FFFE0000FF0000000000000000000000")[..]);
let (n, b) = fill_aad_header(0x01234567);
assert_eq!(n, 6);
assert_eq!(b[..], hex!("FFFE0123456700000000000000000000")[..]);
#[cfg(target_pointer_width = "64")]
{
let (n, b) = fill_aad_header(0x0123456789ABCDEF);
assert_eq!(n, 10);
assert_eq!(b[..], hex!("FFFF0123456789ABCDEF000000000000")[..]);
}
}
}