From 8b14d87b9ffac0784d1665911e179c57d9f4b80c Mon Sep 17 00:00:00 2001 From: Flakebi Date: Sat, 17 Oct 2020 19:33:41 +0200 Subject: [PATCH] eax: Allow variable tag length Implemented analogous to ccm by passing a second generic argument to Eax. --- eax/src/lib.rs | 75 ++++++++++++++++++++++++++++++++++++++++------- eax/src/online.rs | 55 +++++++++++++++++++++------------- eax/src/traits.rs | 18 ++++++++++++ 3 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 eax/src/traits.rs diff --git a/eax/src/lib.rs b/eax/src/lib.rs index ae34b7d9..acac0b25 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -67,6 +67,42 @@ //! # } //! ``` //! +//! ## Custom Tag Length +//! +//! The tag for eax is usually 16 bytes long but it can be shortened if needed. +//! The second generic argument of `Eax` can be set to the tag length: +//! +//! ``` +//! # #[cfg(feature = "heapless")] +//! # { +//! use aes::Aes256; +//! use eax::Eax; +//! use eax::aead::{AeadInPlace, NewAead, generic_array::GenericArray}; +//! use eax::aead::heapless::{Vec, consts::U8, consts::U128}; +//! +//! let key = GenericArray::from_slice(b"an example very very secret key."); +//! let cipher = Eax::::new(key); +//! +//! let nonce = GenericArray::from_slice(b"my unique nonces"); // 128-bits; unique per message +//! +//! let mut buffer: Vec = Vec::new(); +//! buffer.extend_from_slice(b"plaintext message"); +//! +//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext +//! let tag = cipher.encrypt_in_place_detached(nonce, b"", &mut buffer).expect("encryption failure!"); +//! +//! // The tag has only 8 bytes, compared to the usual 16 bytes +//! assert_eq!(tag.len(), 8); +//! +//! // `buffer` now contains the message ciphertext +//! assert_ne!(&buffer, b"plaintext message"); +//! +//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext +//! cipher.decrypt_in_place_detached(nonce, b"", &mut buffer, &tag).expect("decryption failure!"); +//! assert_eq!(&buffer, b"plaintext message"); +//! # } +//! ``` +//! //! [1]: https://en.wikipedia.org/wiki/Authenticated_encryption #![no_std] @@ -88,6 +124,11 @@ use cipher::{ stream::{FromBlockCipher, SyncStreamCipher}, }; use cmac::{crypto_mac::NewMac, Cmac, Mac}; +use core::marker::PhantomData; + +mod traits; + +use traits::TagSize; // TODO Max values? /// Maximum length of associated data @@ -100,7 +141,7 @@ pub const P_MAX: u64 = 1 << 36; pub const C_MAX: u64 = (1 << 36) + 16; /// EAX tags -pub type Tag = GenericArray; +pub type Tag = aead::Tag; pub mod online; @@ -110,35 +151,46 @@ pub mod online; /// implementations. /// /// If in doubt, use the built-in [`Aes128Eax`] and [`Aes256Eax`] type aliases. +/// +/// Type parameters: +/// - `Cipher`: block cipher. +/// - `M`: size of MAC tag, valid values: up to `U16`. #[derive(Clone)] -pub struct Eax +pub struct Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { /// Encryption key key: Key, + _tag_size: PhantomData, } -impl NewAead for Eax +impl NewAead for Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { type KeySize = Cipher::KeySize; fn new(key: &Key) -> Self { - Self { key: key.clone() } + Self { + key: key.clone(), + _tag_size: Default::default(), + } } } -impl AeadInPlace for Eax +impl AeadInPlace for Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { type NonceSize = Cipher::BlockSize; - type TagSize = as Mac>::OutputSize; + type TagSize = M; type CiphertextOverhead = U0; fn encrypt_in_place_detached( @@ -146,7 +198,7 @@ where nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - ) -> Result { + ) -> Result, Error> { if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } @@ -171,7 +223,9 @@ where // 5. tag ← n ^ h ^ c // (^ means xor) - Ok(n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b)) + let full_tag = n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b); + let tag = aead::Tag::::clone_from_slice(&full_tag[..M::to_usize()]); + Ok(tag) } fn decrypt_in_place_detached( @@ -179,7 +233,7 @@ where nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - tag: &Tag, + tag: &aead::Tag, ) -> Result<(), Error> { if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); @@ -213,10 +267,11 @@ where } } -impl Eax +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { /// CMAC/OMAC1 /// diff --git a/eax/src/online.rs b/eax/src/online.rs index 2618f935..be4c7c46 100644 --- a/eax/src/online.rs +++ b/eax/src/online.rs @@ -59,6 +59,7 @@ use crate::*; +use aead::Tag; use core::marker::PhantomData; pub use Eax as EaxOnline; @@ -141,27 +142,29 @@ impl CipherOp for Decrypt {} /// [`Eax`]: ../struct.Eax.html /// [`Decrypt`]: struct.Decrypt.html /// [`finish`]: #method.finish -pub struct Eax +pub struct Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, Op: CipherOp, + M: TagSize, { - imp: EaxImpl, + imp: EaxImpl, /// Denotes whether this stream is used for encryption or decryption. marker: PhantomData, } -impl Eax +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, Op: CipherOp, + M: TagSize, { /// Creates a stateful EAX instance that is capable of processing both /// the associated data and the plaintext in an "on-line" fashion. pub fn with_key_and_nonce(key: &Key, nonce: &Nonce) -> Self { - let imp = EaxImpl::::with_key_and_nonce(key, nonce); + let imp = EaxImpl::::with_key_and_nonce(key, nonce); Self { imp, @@ -182,15 +185,16 @@ where /// ///[`finish`]: #method.finish #[inline] - pub fn tag_clone(&self) -> Tag { + pub fn tag_clone(&self) -> Tag { self.imp.tag_clone() } } -impl Eax +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { /// Applies encryption to the plaintext. #[inline] @@ -203,15 +207,16 @@ where /// This *must* be called after the stream encryption is finished. #[must_use = "tag must be saved to later verify decrypted data"] #[inline] - pub fn finish(self) -> Tag { + pub fn finish(self) -> Tag { self.imp.tag() } } -impl Eax +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { /// Applies decryption to the ciphertext **without** verifying the /// authenticity of decrypted message. @@ -246,7 +251,7 @@ where /// /// This *must* be called after the stream decryption is finished. #[must_use = "decrypted data stream must be verified for authenticity"] - pub fn finish(self, expected: &Tag) -> Result<(), Error> { + pub fn finish(self, expected: &Tag) -> Result<(), Error> { self.imp.verify_ct(expected) } } @@ -256,10 +261,11 @@ where /// Main reason behind extracting the logic to a single, separate type is to /// facilitate testing of the internal logic. #[doc(hidden)] -struct EaxImpl +struct EaxImpl where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { nonce: Nonce, data: Cmac, @@ -268,12 +274,14 @@ where // HACK: Needed for the test harness due to AEAD trait online/offline interface mismatch #[cfg(test)] key: Key, + _tag_size: PhantomData, } -impl EaxImpl +impl EaxImpl where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { /// Creates a stateful EAX instance that is capable of processing both /// the associated data and the plaintext in an "on-line" fashion. @@ -310,6 +318,7 @@ where ctr: cipher, #[cfg(test)] key: key.clone(), + _tag_size: Default::default(), } } @@ -335,25 +344,27 @@ where /// Derives the tag from the encrypted/decrypted message so far. #[inline] - fn tag(self) -> Tag { + fn tag(self) -> Tag { let h = self.data.finalize().into_bytes(); let c = self.message.finalize().into_bytes(); - self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b) + let full_tag = self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b); + Tag::::clone_from_slice(&full_tag[..M::to_usize()]) } /// Derives the tag from the encrypted/decrypted message so far. #[inline] - fn tag_clone(&self) -> Tag { + fn tag_clone(&self) -> Tag { let h = self.data.clone().finalize().into_bytes(); let c = self.message.clone().finalize().into_bytes(); - self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b) + let full_tag = self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b); + Tag::::clone_from_slice(&full_tag[..M::to_usize()]) } /// Finishes the decryption stream, verifying whether the associated and /// decrypted data stream has not been tampered with. - fn verify_ct(self, expected: &Tag) -> Result<(), Error> { + fn verify_ct(self, expected: &Tag) -> Result<(), Error> { // Check MAC using secure comparison use subtle::ConstantTimeEq; @@ -376,10 +387,11 @@ mod test_impl { use super::*; use aead::AeadMutInPlace; - impl NewAead for EaxImpl + impl NewAead for EaxImpl where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { type KeySize = Cipher::KeySize; @@ -395,13 +407,14 @@ mod test_impl { } } - impl AeadMutInPlace for super::EaxImpl + impl AeadMutInPlace for super::EaxImpl where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, + M: TagSize, { type NonceSize = Cipher::BlockSize; - type TagSize = as Mac>::OutputSize; + type TagSize = M; type CiphertextOverhead = U0; fn encrypt_in_place_detached( @@ -409,7 +422,7 @@ mod test_impl { nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - ) -> Result { + ) -> Result, Error> { // HACK: Reinitialize the instance *self = Self::with_key_and_nonce(&self.key.clone(), nonce); @@ -424,7 +437,7 @@ mod test_impl { nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], - expected_tag: &Tag, + expected_tag: &Tag, ) -> Result<(), Error> { // HACK: Reinitialize the instance *self = Self::with_key_and_nonce(&self.key.clone(), nonce); diff --git a/eax/src/traits.rs b/eax/src/traits.rs new file mode 100644 index 00000000..5b9fc14a --- /dev/null +++ b/eax/src/traits.rs @@ -0,0 +1,18 @@ +use aead::consts::{U16, U4}; +use aead::generic_array::typenum::type_operators::{IsGreaterOrEqual, IsLessOrEqual}; +use aead::generic_array::typenum::Unsigned; +use aead::generic_array::ArrayLength; + +mod private { + // Sealed traits stop other crates from implementing any traits that use it. + pub trait SealedTag {} + + impl SealedTag for T where + T: super::IsGreaterOrEqual + super::IsLessOrEqual + { + } +} + +pub trait TagSize: ArrayLength + Unsigned + private::SealedTag {} + +impl TagSize for T where T: ArrayLength + IsGreaterOrEqual + IsLessOrEqual {}