diff --git a/aead/Cargo.toml b/aead/Cargo.toml index 2665704b4..f004bc699 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -28,7 +28,7 @@ heapless = { version = "0.8", optional = true, default-features = false } [features] default = ["rand_core"] alloc = [] -dev = ["blobby"] +dev = ["blobby", "alloc"] os_rng = ["crypto-common/os_rng", "rand_core"] rand_core = ["crypto-common/rand_core"] diff --git a/aead/src/dev.rs b/aead/src/dev.rs index b128014bd..f216b72cc 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,74 +1,116 @@ //! Development-related functionality +use crate::{ + Aead, AeadInOut, Nonce, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, +}; pub use blobby; +/// Run AEAD test for the provided passing test vector +pub fn run_pass_test( + cipher: &C, + nonce: &Nonce, + aad: &[u8], + pt: &[u8], + ct: &[u8], +) -> Result<(), &'static str> { + let res = cipher + .encrypt(nonce, Payload { aad, msg: pt }) + .map_err(|_| "encryption failure")?; + if res != ct { + return Err("encrypted data is different from target ciphertext"); + } + + let res = cipher + .decrypt(nonce, Payload { aad, msg: ct }) + .map_err(|_| "decryption failure")?; + if res != pt { + return Err("decrypted data is different from target plaintext"); + } + + let (ct, tag) = match C::TAG_POSITION { + TagPosition::Prefix => { + let (tag, ct) = ct.split_at(C::TagSize::USIZE); + (ct, tag) + } + TagPosition::Postfix => ct.split_at(pt.len()), + }; + let tag: &Tag = tag.try_into().expect("tag has correct length"); + + // Fill output buffer with "garbage" to test that its data does not get read during encryption + let mut buf: alloc::vec::Vec = (0..pt.len()).map(|i| i as u8).collect(); + let inout_buf = InOutBuf::new(pt, &mut buf).expect("pt and buf have the same length"); + + let calc_tag = cipher + .encrypt_inout_detached(nonce, aad, inout_buf) + .map_err(|_| "encrypt_inout_detached: encryption failure")?; + if tag != &calc_tag { + return Err("encrypt_inout_detached: tag mismatch"); + } + if ct != buf { + return Err("encrypt_inout_detached: ciphertext mismatch"); + } + + // Fill output buffer with "garbage" + buf.iter_mut().enumerate().for_each(|(i, v)| *v = i as u8); + + let inout_buf = InOutBuf::new(ct, &mut buf).expect("ct and buf have the same length"); + cipher + .decrypt_inout_detached(nonce, aad, inout_buf, tag) + .map_err(|_| "decrypt_inout_detached: decryption failure")?; + if pt != buf { + return Err("decrypt_inout_detached: plaintext mismatch"); + } + + Ok(()) +} + +/// Run AEAD test for the provided failing test vector +pub fn run_fail_test( + cipher: &C, + nonce: &Nonce, + aad: &[u8], + ct: &[u8], +) -> Result<(), &'static str> { + let res = cipher.decrypt(nonce, Payload { aad, msg: ct }); + if res.is_ok() { + Err("decryption must return error") + } else { + Ok(()) + } +} + /// Define AEAD test #[macro_export] macro_rules! new_test { ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { - use aead::{ - Aead, KeyInit, Payload, - array::{Array, typenum::Unsigned}, - dev::blobby::Blob6Iterator, - }; - - fn run_test( - key: &[u8], - nonce: &[u8], - aad: &[u8], - pt: &[u8], - ct: &[u8], - pass: bool, - ) -> Result<(), &'static str> { - let key = key.try_into().map_err(|_| "wrong key size")?; - let cipher = <$cipher>::new(key); - let nonce = nonce.try_into().map_err(|_| "wrong nonce size")?; - - if !pass { - let res = cipher.decrypt(nonce, Payload { aad: aad, msg: ct }); - if res.is_ok() { - return Err("decryption must return error"); - } - return Ok(()); - } - - let res = cipher - .encrypt(nonce, Payload { aad: aad, msg: pt }) - .map_err(|_| "encryption failure")?; - if res != ct { - return Err("encrypted data is different from target ciphertext"); - } - let res = cipher - .decrypt(nonce, Payload { aad: aad, msg: ct }) - .map_err(|_| "decryption failure")?; - if res != pt { - return Err("decrypted data is different from target plaintext"); - } - Ok(()) - } + use $crate::KeyInit; + use $crate::dev::blobby::Blob6Iterator; let data = include_bytes!(concat!("data/", $test_name, ".blb")); for (i, row) in Blob6Iterator::new(data).unwrap().enumerate() { let [key, nonce, aad, pt, ct, status] = row.unwrap(); - let pass = match status[0] { - 0 => false, - 1 => true, + let key = key.try_into().expect("wrong key size"); + let nonce = nonce.try_into().expect("wrong nonce size"); + let cipher = <$cipher as KeyInit>::new(key); + + let res = match status { + [0] => $crate::dev::run_fail_test(&cipher, nonce, aad, ct), + [1] => $crate::dev::run_pass_test(&cipher, nonce, aad, pt, ct), _ => panic!("invalid value for pass flag"), }; - if let Err(reason) = run_test(key, nonce, aad, pt, ct, pass) { + let mut pass = status[0] == 1; + if let Err(reason) = res { panic!( "\n\ - Failed test №{}\n\ - reason: \t{:?}\n\ - key:\t{:?}\n\ - nonce:\t{:?}\n\ - aad:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n\ - pass:\t{}\n\ - ", - i, reason, key, nonce, aad, pt, ct, pass, + Failed test #{i}\n\ + reason:\t{reason:?}\n\ + key:\t{key:?}\n\ + nonce:\t{nonce:?}\n\ + aad:\t{aad:?}\n\ + plaintext:\t{pt:?}\n\ + ciphertext:\t{ct:?}\n\ + pass:\t{pass}\n" ); } } diff --git a/aead/tests/data/postfix.blb b/aead/tests/data/postfix.blb new file mode 100644 index 000000000..97d850e1c Binary files /dev/null and b/aead/tests/data/postfix.blb differ diff --git a/aead/tests/data/prefix.blb b/aead/tests/data/prefix.blb new file mode 100644 index 000000000..8a5929ee6 Binary files /dev/null and b/aead/tests/data/prefix.blb differ diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs new file mode 100644 index 000000000..817ab945c --- /dev/null +++ b/aead/tests/dummy.rs @@ -0,0 +1,175 @@ +//! This module defines dummy (horribly insecure!) AEAD implementations +//! to test implementation of the AEAD traits and helper macros in the `dev` module. +use aead::{ + AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, + array::Array, consts::U8, +}; +use inout::InOutBuf; + +struct DummyAead { + key: [u8; 8], +} + +impl DummyAead { + fn process_aad(&self, nonce: &[u8; 8], aad: &[u8]) -> u64 { + let mut tag = u64::from_le_bytes(*nonce); + let key = u64::from_le_bytes(self.key); + + let mut aad_iter = aad.chunks_exact(8); + for chunk in &mut aad_iter { + tag ^= u64::from_le_bytes(chunk.try_into().unwrap()); + tag = tag.wrapping_add(key); + } + let aad_rem = aad_iter.remainder(); + if !aad_rem.is_empty() { + let mut chunk = [0u8; 8]; + chunk[..aad_rem.len()].copy_from_slice(aad_rem); + tag ^= u64::from_le_bytes(chunk); + tag = tag.wrapping_add(key); + } + + tag + } + + fn encrypt_inner( + &self, + nonce: &[u8; 8], + aad: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result<[u8; 8]> { + let mut tag = self.process_aad(nonce, aad); + + let (blocks, mut rem) = buffer.into_chunks::(); + for mut block in blocks { + block.xor_in2out(&self.key.into()); + tag ^= u64::from_be_bytes(block.get_out().0); + } + + if !rem.is_empty() { + rem.xor_in2out(&self.key[..rem.len()]); + + let out_rem = rem.get_out(); + let mut block = [0u8; 8]; + block[..out_rem.len()].copy_from_slice(out_rem); + tag ^= u64::from_le_bytes(block); + } + + Ok(tag.to_le_bytes()) + } + + fn decrypt_inner( + &self, + nonce: &[u8; 8], + aad: &[u8], + mut buffer: InOutBuf<'_, '_, u8>, + tag: &[u8; 8], + ) -> Result<()> { + let exp_tag = u64::from_le_bytes(*tag); + let mut tag = self.process_aad(nonce, aad); + + let (blocks, mut rem) = buffer.reborrow().into_chunks::(); + for mut block in blocks { + tag ^= u64::from_be_bytes(block.get_in().0); + block.xor_in2out(&self.key.into()); + } + + if !rem.is_empty() { + let in_rem = rem.get_in(); + let mut block = [0u8; 8]; + block[..in_rem.len()].copy_from_slice(in_rem); + tag ^= u64::from_le_bytes(block); + + rem.xor_in2out(&self.key[..rem.len()]); + } + + if tag == exp_tag { + Ok(()) + } else { + buffer.get_out().fill(0); + Err(Error) + } + } +} + +struct PrefixDummyAead(DummyAead); + +impl KeySizeUser for PrefixDummyAead { + type KeySize = U8; +} + +impl KeyInit for PrefixDummyAead { + fn new(key: &Key) -> Self { + Self(DummyAead { key: key.0 }) + } +} + +impl AeadCore for PrefixDummyAead { + type NonceSize = U8; + type TagSize = U8; + const TAG_POSITION: TagPosition = TagPosition::Prefix; +} + +impl AeadInOut for PrefixDummyAead { + fn encrypt_inout_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result> { + self.0.encrypt_inner(nonce.into(), aad, buffer).map(Array) + } + + fn decrypt_inout_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<()> { + self.0.decrypt_inner(nonce.into(), aad, buffer, tag.into()) + } +} + +struct PostfixDummyAead(DummyAead); + +impl KeySizeUser for PostfixDummyAead { + type KeySize = U8; +} + +impl KeyInit for PostfixDummyAead { + fn new(key: &Key) -> Self { + Self(DummyAead { key: key.0 }) + } +} + +impl AeadCore for PostfixDummyAead { + type NonceSize = U8; + type TagSize = U8; + const TAG_POSITION: TagPosition = TagPosition::Postfix; +} + +impl AeadInOut for PostfixDummyAead { + fn encrypt_inout_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result> { + self.0.encrypt_inner(nonce.into(), aad, buffer).map(Array) + } + + fn decrypt_inout_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &Tag, + ) -> Result<()> { + self.0.decrypt_inner(nonce.into(), aad, buffer, tag.into()) + } +} + +#[cfg(feature = "dev")] +aead::new_test!(dummy_prefix, "prefix", PrefixDummyAead); +#[cfg(feature = "dev")] +aead::new_test!(dummy_postfix, "postfix", PostfixDummyAead);