diff --git a/eax/src/lib.rs b/eax/src/lib.rs index c58794f7..2d9e8aad 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -125,6 +125,29 @@ //! assert_eq!(buffer.as_ref(), b"plaintext message"); //! # } //! ``` +//! +//! ## Custom Nonce Length +//! +//! EAX accepts nonces of any length. The nonce is 16 bytes long by default; +//! the third generic argument of `Eax` sets another length: +//! +//! ``` +//! # #[cfg(feature = "alloc")] +//! # { +//! use aes::Aes128; +//! use eax::Eax; +//! use eax::aead::{Aead, KeyInit, array::Array}; +//! use eax::aead::consts::{U12, U16}; +//! +//! let key = Array::from_slice(b"very secret key."); +//! let cipher = Eax::::new(key); +//! +//! let nonce = Array::from_slice(b"unique nonce"); // 96-bits; unique per message +//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref()).unwrap(); +//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap(); +//! assert_eq!(&plaintext, b"plaintext message"); +//! # } +//! ``` mod traits; @@ -133,7 +156,9 @@ pub use cipher; use aead::{TagPosition, inout::InOutBuf}; use cipher::{ - BlockCipherEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore, array::Array, consts::U16, + BlockCipherEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore, + array::{Array, ArraySize}, + consts::U16, }; use cmac::{Cmac, Mac, digest::Output}; use core::{fmt, marker::PhantomData}; @@ -167,52 +192,60 @@ type Ctr128BE = ctr::CtrCore; /// ## Type parameters /// - `Cipher`: block cipher. /// - `M`: size of MAC tag, valid values: up to `U16`. +/// - `NonceSize`: size of nonce, any length (default `U16`). #[derive(Clone)] -pub struct Eax +pub struct Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { /// Encryption key key: Key, _tag_size: PhantomData, + _nonce_size: PhantomData, } -impl KeySizeUser for Eax +impl KeySizeUser for Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { type KeySize = Cipher::KeySize; } -impl KeyInit for Eax +impl KeyInit for Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { fn new(key: &Key) -> Self { Self { key: key.clone(), _tag_size: PhantomData, + _nonce_size: PhantomData, } } } -impl AeadCore for Eax +impl AeadCore for Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { - type NonceSize = Cipher::BlockSize; + type NonceSize = NonceSize; type TagSize = M; const TAG_POSITION: TagPosition = TagPosition::Postfix; } -impl AeadInOut for Eax +impl AeadInOut for Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { fn encrypt_inout_detached( &self, @@ -285,20 +318,22 @@ where } } -impl fmt::Debug for Eax +impl fmt::Debug for Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_struct("Eax").finish_non_exhaustive() } } -impl Eax +impl Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { /// CMAC/OMAC1 /// diff --git a/eax/src/online.rs b/eax/src/online.rs index b139aa08..b78213e3 100644 --- a/eax/src/online.rs +++ b/eax/src/online.rs @@ -60,7 +60,8 @@ use crate::{Cmac, Error, Nonce, Tag, TagSize}; use aead::consts::U16; use cipher::{ - BlockCipherEncrypt, BlockSizeUser, Key, KeyInit, KeyIvInit, StreamCipher, array::Array, + BlockCipherEncrypt, BlockSizeUser, Key, KeyInit, KeyIvInit, StreamCipher, + array::{Array, ArraySize}, typenum::Unsigned, }; use cmac::Mac; @@ -150,31 +151,35 @@ impl CipherOp for Decrypt {} /// [`Eax`]: ../struct.Eax.html /// [`Decrypt`]: struct.Decrypt.html /// [`finish`]: #method.finish -pub struct Eax +pub struct Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, Op: CipherOp, M: TagSize, + NonceSize: ArraySize, { imp: EaxImpl, /// Denotes whether this stream is used for encryption or decryption. marker: PhantomData, + _nonce_size: PhantomData, } -impl Eax +impl Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, Op: CipherOp, M: TagSize, + NonceSize: ArraySize, { /// 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 { + pub fn with_key_and_nonce(key: &Key, nonce: &Nonce) -> Self { let imp = EaxImpl::::with_key_and_nonce(key, nonce); Self { imp, marker: PhantomData, + _nonce_size: PhantomData, } } @@ -196,10 +201,11 @@ where } } -impl Eax +impl Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { /// Applies encryption to the plaintext. #[inline] @@ -217,10 +223,11 @@ where } } -impl Eax +impl Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, M: TagSize, + NonceSize: ArraySize, { /// Applies decryption to the ciphertext **without** verifying the /// authenticity of decrypted message. @@ -267,11 +274,12 @@ where } } -impl fmt::Debug for Eax +impl fmt::Debug for Eax where Cipher: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, Op: CipherOp, M: TagSize, + NonceSize: ArraySize, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_struct("Eax").finish_non_exhaustive() @@ -302,7 +310,7 @@ where { /// 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, nonce: &Nonce) -> Self { + fn with_key_and_nonce(key: &Key, nonce: &[u8]) -> Self { let prepend_cmac = |key, init_val, data| { let mut cmac = as KeyInit>::new(key); cmac.update(&[0; 15]); diff --git a/eax/tests/data/wycheproof-128_nonce-len.blb b/eax/tests/data/wycheproof-128_nonce-len.blb new file mode 100644 index 00000000..0e86d0d8 Binary files /dev/null and b/eax/tests/data/wycheproof-128_nonce-len.blb differ diff --git a/eax/tests/data/wycheproof-192_nonce-len.blb b/eax/tests/data/wycheproof-192_nonce-len.blb new file mode 100644 index 00000000..4cf6d00f Binary files /dev/null and b/eax/tests/data/wycheproof-192_nonce-len.blb differ diff --git a/eax/tests/data/wycheproof-256_nonce-len.blb b/eax/tests/data/wycheproof-256_nonce-len.blb new file mode 100644 index 00000000..583b5979 Binary files /dev/null and b/eax/tests/data/wycheproof-256_nonce-len.blb differ diff --git a/eax/tests/wycheproof.rs b/eax/tests/wycheproof.rs index 31ea4356..29a6681d 100644 --- a/eax/tests/wycheproof.rs +++ b/eax/tests/wycheproof.rs @@ -1,13 +1,86 @@ -//! Test vectors from Wycheproof (`aes_eax_test.json`): the groups with a -//! 128-bit nonce and a 128-bit tag, for AES-128, AES-192 and AES-256. +//! Test vectors from Wycheproof (`aes_eax_test.json`) with a 128-bit tag, for +//! AES-128, AES-192 and AES-256. #![cfg(feature = "alloc")] +use aead::{ + array::ArraySize, + consts::{U0, U4, U8, U12, U16, U20, U32, U64, U128, U257}, + dev::{TestVector, blobby, pass_test}, +}; use aes::{Aes128, Aes192, Aes256}; -use eax::Eax; +use eax::{ + Eax, KeyInit, Tag, + cipher::{BlockCipherEncrypt, BlockSizeUser}, + online::{Decrypt, Eax as EaxOnline, Encrypt}, +}; +// The groups with a 128-bit nonce. aead::new_pass_test!(aes128_pass, "wycheproof-128_pass", Eax); aead::new_fail_test!(aes128_fail, "wycheproof-128_fail", Eax); aead::new_pass_test!(aes192_pass, "wycheproof-192_pass", Eax); aead::new_fail_test!(aes192_fail, "wycheproof-192_fail", Eax); aead::new_pass_test!(aes256_pass, "wycheproof-256_pass", Eax); aead::new_fail_test!(aes256_fail, "wycheproof-256_fail", Eax); + +/// Checks a vector with both `Eax` and `online::Eax` using an `N`-byte nonce. +fn nonce_len_test(i: usize, tv: &TestVector) +where + C: BlockSizeUser + BlockCipherEncrypt + Clone + KeyInit, + N: ArraySize, +{ + if let Err(reason) = pass_test::>(tv) { + panic!("\nFailed test #{i}\nreason:\t{reason:?}\ntest vector:\t{tv:?}\n"); + } + + let key = tv.key.try_into().expect("wrong key size"); + let nonce = tv.nonce.try_into().expect("wrong nonce size"); + let (ct, tag) = tv.ciphertext.split_at(tv.plaintext.len()); + let tag: &Tag = tag.try_into().expect("wrong tag size"); + + let mut buf = tv.plaintext.to_vec(); + let mut cipher = EaxOnline::::with_key_and_nonce(key, nonce); + cipher.update_assoc(tv.aad); + cipher.encrypt(&mut buf); + assert_eq!(buf, ct, "online encryption, test #{i}"); + assert_eq!(&cipher.finish(), tag, "online tag, test #{i}"); + + let mut cipher = EaxOnline::::with_key_and_nonce(key, nonce); + cipher.update_assoc(tv.aad); + cipher.decrypt_unauthenticated_hazmat(&mut buf); + assert_eq!(buf, tv.plaintext, "online decryption, test #{i}"); + assert!(cipher.finish(tag).is_ok(), "online tag check, test #{i}"); +} + +/// The groups with a nonce that is not 128 bits long (0 to 257 bytes). +macro_rules! new_nonce_len_test { + ($name:ident, $test_name:expr, $cipher:ty) => { + #[test] + fn $name() { + blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, nonce, aad, plaintext, ciphertext } + ]; + ); + + for (i, tv) in TEST_VECTORS.iter().enumerate() { + match tv.nonce.len() { + 0 => nonce_len_test::<$cipher, U0>(i, tv), + 4 => nonce_len_test::<$cipher, U4>(i, tv), + 8 => nonce_len_test::<$cipher, U8>(i, tv), + 12 => nonce_len_test::<$cipher, U12>(i, tv), + 20 => nonce_len_test::<$cipher, U20>(i, tv), + 32 => nonce_len_test::<$cipher, U32>(i, tv), + 64 => nonce_len_test::<$cipher, U64>(i, tv), + 128 => nonce_len_test::<$cipher, U128>(i, tv), + 257 => nonce_len_test::<$cipher, U257>(i, tv), + n => panic!("no test for a {n}-byte nonce"), + } + } + } + }; +} + +new_nonce_len_test!(aes128_nonce_len, "wycheproof-128_nonce-len", Aes128); +new_nonce_len_test!(aes192_nonce_len, "wycheproof-192_nonce-len", Aes192); +new_nonce_len_test!(aes256_nonce_len, "wycheproof-256_nonce-len", Aes256);