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: Introduce a streaming API #214

Merged
merged 7 commits into from
Sep 30, 2020
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
WIP: eax: Separate internal online impl for testing
  • Loading branch information
Xanewok committed Sep 17, 2020
commit 63e0b6c12c351a3ddc016ae0a0ea6c10120fb0a8
282 changes: 211 additions & 71 deletions eax/src/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
//! // Now decrypt it, using the same key and nonce
//! let mut cipher = Eax::<Aes256, Decrypt>::with_key_and_nonce(key, nonce);
//! cipher.update_assoc(&assoc[..]);
//! cipher.decrypt(&mut buffer[..5]);
//! cipher.decrypt(&mut buffer[5..10]);
//! cipher.decrypt(&mut buffer[10..]);
//! cipher.decrypt_unauthenticated(&mut buffer[..5]);
//! cipher.decrypt_unauthenticated(&mut buffer[5..10]);
//! cipher.decrypt_unauthenticated(&mut buffer[10..]);
//! let res = cipher.finish(&tag);
//!
//! assert_eq!(res, Ok(()));
Expand All @@ -48,7 +48,7 @@
//! // Decrypting the ciphertext with tampered associated data should fail
//! let mut cipher = Eax::<Aes256, Decrypt>::with_key_and_nonce(key, nonce);
//! cipher.update_assoc(b"tampered");
//! cipher.decrypt(&mut cloned);
//! cipher.decrypt_unauthenticated(&mut cloned);
//! let res = cipher.finish(&tag);
//!
//! assert_eq!(res, Err(Error));
Expand Down Expand Up @@ -121,9 +121,9 @@ impl CipherOp for Decrypt {}
/// // Now decrypt it, using the same key and nonce
/// let mut cipher = Eax::<Aes256, Decrypt>::with_key_and_nonce(key, nonce);
/// cipher.update_assoc(&assoc[..]);
/// cipher.decrypt(&mut buffer[..5]);
/// cipher.decrypt(&mut buffer[5..10]);
/// cipher.decrypt(&mut buffer[10..]);
/// cipher.decrypt_unauthenticated(&mut buffer[..5]);
/// cipher.decrypt_unauthenticated(&mut buffer[5..10]);
/// cipher.decrypt_unauthenticated(&mut buffer[10..]);
/// let res = cipher.finish(&tag);
///
/// assert_eq!(res, Ok(()));
Expand All @@ -133,7 +133,7 @@ impl CipherOp for Decrypt {}
/// let mut cipher = Eax::<Aes256, Decrypt>::with_key_and_nonce(key, nonce);
///
/// cipher.update_assoc(b"tampered");
/// cipher.decrypt(&mut cloned);
/// cipher.decrypt_unauthenticated(&mut cloned);
/// let res = cipher.finish(&tag);
///
/// assert_eq!(res, Err(Error));
Expand All @@ -149,10 +149,7 @@ where
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
Op: CipherOp,
{
nonce: Nonce<Cipher::BlockSize>,
data: Cmac<Cipher>,
message: Cmac<Cipher>,
ctr: ctr::Ctr128<Cipher>,
imp: EaxImpl<Cipher>,
/// Denotes whether this stream is used for encryption or decryption.
marker: PhantomData<Op>,
}
Expand All @@ -166,6 +163,107 @@ where
/// 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);

Self {
imp,
marker: PhantomData,
}
}

/// Process the associated data (AD).
#[inline]
pub fn update_assoc(&mut self, aad: &[u8]) {
self.imp.update_assoc(aad);
}

/// Derives the tag from the encrypted/decrypted message so far.
///
/// If the encryption/decryption operation is finished, [`finish`] method
/// *must* be called instead.
///
///[`finish`]: #method.finish
#[inline]
pub fn tag_clone(&self) -> Tag {
self.imp.tag_clone()
}
}

impl<Cipher> Eax<Cipher, Encrypt>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
{
/// Applies encryption to the plaintext.
#[inline]
pub fn encrypt(&mut self, msg: &mut [u8]) {
self.imp.encrypt(msg)
}

/// Finishes the encryption stream, returning the derived tag.
///
/// 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 {
self.imp.tag()
}
}

impl<Cipher> Eax<Cipher, Decrypt>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
{
/// Applies decryption to the ciphertext **without** verifying the
/// authenticity of decrypted message.
///
/// To correctly verify the authenticity, use the [`finish`] associated
/// function.
///
/// [`finish`]: #method.finish
#[inline]
pub fn decrypt_unauthenticated(&mut self, msg: &mut [u8]) {
self.imp.decrypt(msg)
}

/// Finishes the decryption stream, verifying whether the associated and
/// decrypted data stream has not been tampered with.
///
/// 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> {
self.imp.verify_ct(expected)
}
}

/// Implementation of the raw EAX operations.
///
/// Main reason behind extracting the logic to a single, separate type is to
/// facilitate testing of the internal logic.
#[doc(hidden)]
struct EaxImpl<Cipher>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
{
nonce: Nonce<Cipher::BlockSize>,
data: Cmac<Cipher>,
message: Cmac<Cipher>,
ctr: ctr::Ctr128<Cipher>,
// HACK: Needed for the test harness due to AEAD trait online/offline interface mismatch
#[cfg(test)]
key: Key<Cipher>,
Comment on lines +270 to +272
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it'd really be good if we could figure out some other solution to this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is my one lingering concern but I don't consider it a showstopper

}

impl<Cipher> EaxImpl<Cipher>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
{
/// Creates a stateful EAX instance that is capable of processing both
/// the associated data and the plaintext in an "on-line" fashion.
fn with_key_and_nonce(key: &Key<Cipher>, nonce: &Nonce<Cipher::BlockSize>) -> Self {
let prepend_cmac = |key, init_val, data| {
let mut cmac = Cmac::<Cipher>::new(key);
cmac.update(&[0; 15]);
Expand All @@ -191,102 +289,144 @@ where

let cipher = ctr::Ctr128::<Cipher>::from_block_cipher(Cipher::new(&key), &n);

Eax {
Self {
nonce: n,
data: h,
message: c,
ctr: cipher,
marker: PhantomData,
#[cfg(test)]
key: key.clone(),
}
}
}

impl<Cipher, Op> Eax<Cipher, Op>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
Op: CipherOp,
{
/// Process the associated data (AD).
#[inline]
pub fn update_assoc(&mut self, aad: &[u8]) {
self.data.update(aad);
}

/// Applies encryption to the plaintext.
#[inline]
fn encrypt(&mut self, msg: &mut [u8]) {
self.ctr.apply_keystream(msg);
self.message.update(msg);
}

/// Applies decryption to the ciphertext.
#[inline]
fn decrypt(&mut self, msg: &mut [u8]) {
self.message.update(msg);
self.ctr.apply_keystream(msg);
}

/// Derives the tag from the encrypted/decrypted message so far.
///
/// NOTE: This has to be called when the value is consumed.
#[inline]
fn finish_inner(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)
}

/// Derives the tag from the encrypted/decrypted message so far.
///
/// If the encryption/decryption operation is finished, [`finish`] method
/// *must* be called instead.
///
///[`finish`]: #method.finish
#[inline]
pub 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)
}
}

impl<Cipher> Eax<Cipher, Encrypt>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
{
/// Applies encryption to the plaintext.
#[inline]
pub fn encrypt(&mut self, msg: &mut [u8]) {
self.ctr.apply_keystream(msg);
self.message.update(msg);
}

/// Finishes the encryption stream, returning the derived tag.
///
/// 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 {
self.finish_inner()
}
}

impl<Cipher> Eax<Cipher, Decrypt>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
{
/// Applies decryption to the ciphertext.
#[inline]
pub fn decrypt(&mut self, msg: &mut [u8]) {
self.message.update(msg);
self.ctr.apply_keystream(msg);
}

/// Finishes the decryption stream, verifying whether the associated and
/// decrypted data stream has not been tampered with.
///
/// 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> {
// Check mac using secure comparison
fn verify_ct(self, expected: &Tag) -> Result<(), Error> {
// Check MAC using secure comparison
use subtle::ConstantTimeEq;

let resulting_tag = &self.finish_inner()[..expected.len()];
let resulting_tag = &self.tag()[..expected.len()];
if resulting_tag.ct_eq(expected).unwrap_u8() == 1 {
Ok(())
} else {
Err(Error)
}
}
}

// Because the current AEAD test harness expects the types to implement both
// `NewAead` and `AeadMutInPlace` traits, do so here so that we can test the
// internal logic used by the public interface for the online EAX variant.
// These are not publicly implemented in general, because the traits are
// designed for offline usage and are somewhat wasteful when used in online mode.
#[cfg(test)]
mod test_impl {
use super::*;
use aead::AeadMutInPlace;

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

fn new(key: &Key<Cipher>) -> Self {
// HACK: The nonce will be initialized by the appropriate
// decrypt/encrypt functions from `AeadMutInPlace` implementation.
// This is currently done so because that trait only implements
// offline operations and thus need to re-initialize the `EaxImpl`
// instance.
let nonce = GenericArray::default();

Self::with_key_and_nonce(key, &nonce)
}
}

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

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

self.update_assoc(associated_data);
self.encrypt(buffer);

Ok(self.tag_clone())
}

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

self.update_assoc(associated_data);
self.decrypt(buffer);

let tag = self.tag_clone();

// Check mac using secure comparison
use subtle::ConstantTimeEq;
if expected_tag.ct_eq(&tag).unwrap_u8() == 1 {
Ok(())
} else {
Err(Error)
}
}
}
}