-
Notifications
You must be signed in to change notification settings - Fork 229
aead: tweak dev module and add DummyAead tests
#1802
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
d61cdbf
aead: tweak `dev` module
newpavlov 8d795ad
Replace unwrap with expect
newpavlov d471975
fix macro
newpavlov 19c2661
Tweak `run_fail_test`
newpavlov 084352a
Fix macro
newpavlov 70f2969
Add dummy AEAD tests
newpavlov f7900f3
Tweak macro
newpavlov 38fcc6c
Add comment to the dummy test module
newpavlov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<C: AeadInOut>( | ||
| cipher: &C, | ||
| nonce: &Nonce<C>, | ||
| 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<C> = 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<u8> = (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<C: AeadInOut>( | ||
| cipher: &C, | ||
| nonce: &Nonce<C>, | ||
| 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), | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that the failing test ignores plaintext. Maybe it's worth to introduce two separate macros for successful and failing tests? In other words, we would store passing and failing vectors in separate files: aead::new_pass_test!(my_aead_pass, "my_aead_pass", MyAead);
aead::new_fail_test!(my_aead_fail, "my_aead_fail", MyAead); |
||
| _ => 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" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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::<U8>(); | ||
| 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::<U8>(); | ||
| 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 { | ||
| 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<Self>, | ||
| aad: &[u8], | ||
| buffer: InOutBuf<'_, '_, u8>, | ||
| ) -> Result<Tag<Self>> { | ||
| self.0.encrypt_inner(nonce.into(), aad, buffer).map(Array) | ||
| } | ||
|
|
||
| fn decrypt_inout_detached( | ||
| &self, | ||
| nonce: &Nonce<Self>, | ||
| aad: &[u8], | ||
| buffer: InOutBuf<'_, '_, u8>, | ||
| tag: &Tag<Self>, | ||
| ) -> 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 { | ||
| 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<Self>, | ||
| aad: &[u8], | ||
| buffer: InOutBuf<'_, '_, u8>, | ||
| ) -> Result<Tag<Self>> { | ||
| self.0.encrypt_inner(nonce.into(), aad, buffer).map(Array) | ||
| } | ||
|
|
||
| fn decrypt_inout_detached( | ||
| &self, | ||
| nonce: &Nonce<Self>, | ||
| aad: &[u8], | ||
| buffer: InOutBuf<'_, '_, u8>, | ||
| tag: &Tag<Self>, | ||
| ) -> 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); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not every consumer use the
aead::new_test(at least deoxys and ascon-aead does not) this is why theMockBufferwas made available in #1797There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can leave the ascon test as-is for now. We should migrate them to
new_testeither way. The current approach with the huge number of testing functions is slow and unwieldy.