Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eax: Allow variable tag length #231

Merged
merged 1 commit into from
Oct 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 65 additions & 10 deletions eax/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Aes256, U8>::new(key);
//!
//! let nonce = GenericArray::from_slice(b"my unique nonces"); // 128-bits; unique per message
//!
//! let mut buffer: Vec<u8, U128> = 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]
Expand All @@ -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
Expand All @@ -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<u8, U16>;
pub type Tag = aead::Tag<U16>;

pub mod online;

Expand All @@ -110,43 +151,54 @@ 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<Cipher>
pub struct Eax<Cipher, M = U16>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
/// Encryption key
key: Key<Cipher>,
_tag_size: PhantomData<M>,
}

impl<Cipher> NewAead for Eax<Cipher>
impl<Cipher, M> NewAead for Eax<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
type KeySize = Cipher::KeySize;

fn new(key: &Key<Cipher>) -> Self {
Self { key: key.clone() }
Self {
key: key.clone(),
_tag_size: Default::default(),
}
}
}

impl<Cipher> AeadInPlace for Eax<Cipher>
impl<Cipher, M> AeadInPlace for Eax<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
type NonceSize = Cipher::BlockSize;
type TagSize = <Cmac<Cipher> as Mac>::OutputSize;
type TagSize = M;
type CiphertextOverhead = U0;

fn encrypt_in_place_detached(
&self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
) -> Result<aead::Tag<M>, Error> {
if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
}
Expand All @@ -171,15 +223,17 @@ 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::<M>::clone_from_slice(&full_tag[..M::to_usize()]);
Ok(tag)
}

fn decrypt_in_place_detached(
&self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag,
tag: &aead::Tag<M>,
) -> Result<(), Error> {
if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
Expand Down Expand Up @@ -213,10 +267,11 @@ where
}
}

impl<Cipher> Eax<Cipher>
impl<Cipher, M> Eax<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
/// CMAC/OMAC1
///
Expand Down
55 changes: 34 additions & 21 deletions eax/src/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@

use crate::*;

use aead::Tag;
use core::marker::PhantomData;

pub use Eax as EaxOnline;
Expand Down Expand Up @@ -141,27 +142,29 @@ impl CipherOp for Decrypt {}
/// [`Eax`]: ../struct.Eax.html
/// [`Decrypt`]: struct.Decrypt.html
/// [`finish`]: #method.finish
pub struct Eax<Cipher, Op>
pub struct Eax<Cipher, Op, M = U16>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
Op: CipherOp,
M: TagSize,
{
imp: EaxImpl<Cipher>,
imp: EaxImpl<Cipher, M>,
/// Denotes whether this stream is used for encryption or decryption.
marker: PhantomData<Op>,
}

impl<Cipher, Op> Eax<Cipher, Op>
impl<Cipher, Op, M> Eax<Cipher, Op, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
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<Cipher>, nonce: &Nonce<Cipher::BlockSize>) -> Self {
let imp = EaxImpl::<Cipher>::with_key_and_nonce(key, nonce);
let imp = EaxImpl::<Cipher, M>::with_key_and_nonce(key, nonce);

Self {
imp,
Expand All @@ -182,15 +185,16 @@ where
///
///[`finish`]: #method.finish
#[inline]
pub fn tag_clone(&self) -> Tag {
pub fn tag_clone(&self) -> Tag<M> {
self.imp.tag_clone()
}
}

impl<Cipher> Eax<Cipher, Encrypt>
impl<Cipher, M> Eax<Cipher, Encrypt, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
/// Applies encryption to the plaintext.
#[inline]
Expand All @@ -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<M> {
self.imp.tag()
}
}

impl<Cipher> Eax<Cipher, Decrypt>
impl<Cipher, M> Eax<Cipher, Decrypt, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
/// Applies decryption to the ciphertext **without** verifying the
/// authenticity of decrypted message.
Expand Down Expand Up @@ -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<M>) -> Result<(), Error> {
self.imp.verify_ct(expected)
}
}
Expand All @@ -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<Cipher>
struct EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
nonce: Nonce<Cipher::BlockSize>,
data: Cmac<Cipher>,
Expand All @@ -268,12 +274,14 @@ where
// HACK: Needed for the test harness due to AEAD trait online/offline interface mismatch
#[cfg(test)]
key: Key<Cipher>,
_tag_size: PhantomData<M>,
}

impl<Cipher> EaxImpl<Cipher>
impl<Cipher, M> EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
/// Creates a stateful EAX instance that is capable of processing both
/// the associated data and the plaintext in an "on-line" fashion.
Expand Down Expand Up @@ -310,6 +318,7 @@ where
ctr: cipher,
#[cfg(test)]
key: key.clone(),
_tag_size: Default::default(),
}
}

Expand All @@ -335,25 +344,27 @@ where

/// Derives the tag from the encrypted/decrypted message so far.
#[inline]
fn tag(self) -> Tag {
fn tag(self) -> Tag<M> {
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::<M>::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<M> {
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::<M>::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<M>) -> Result<(), Error> {
// Check MAC using secure comparison
use subtle::ConstantTimeEq;

Expand All @@ -376,10 +387,11 @@ mod test_impl {
use super::*;
use aead::AeadMutInPlace;

impl<Cipher> NewAead for EaxImpl<Cipher>
impl<Cipher, M> NewAead for EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
type KeySize = Cipher::KeySize;

Expand All @@ -395,21 +407,22 @@ mod test_impl {
}
}

impl<Cipher> AeadMutInPlace for super::EaxImpl<Cipher>
impl<Cipher, M> AeadMutInPlace for super::EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: TagSize,
{
type NonceSize = Cipher::BlockSize;
type TagSize = <Cmac<Cipher> as Mac>::OutputSize;
type TagSize = M;
type CiphertextOverhead = U0;

fn encrypt_in_place_detached(
&mut self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
) -> Result<Tag<M>, Error> {
// HACK: Reinitialize the instance
*self = Self::with_key_and_nonce(&self.key.clone(), nonce);

Expand All @@ -424,7 +437,7 @@ mod test_impl {
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
expected_tag: &Tag,
expected_tag: &Tag<M>,
) -> Result<(), Error> {
// HACK: Reinitialize the instance
*self = Self::with_key_and_nonce(&self.key.clone(), nonce);
Expand Down
18 changes: 18 additions & 0 deletions eax/src/traits.rs
Original file line number Diff line number Diff line change
@@ -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<T> SealedTag for T where
T: super::IsGreaterOrEqual<super::U4> + super::IsLessOrEqual<super::U16>
{
}
}

pub trait TagSize: ArrayLength<u8> + Unsigned + private::SealedTag {}

impl<T> TagSize for T where T: ArrayLength<u8> + IsGreaterOrEqual<U4> + IsLessOrEqual<U16> {}