diff --git a/Cargo.lock b/Cargo.lock index 3580b98c7..c0ad36c6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,8 +70,7 @@ dependencies = [ [[package]] name = "blobby" version = "0.4.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a859067dcb257cb2ae028cb821399b55140b76fb8b2a360e052fe109019db43" +source = "git+https://github.com/RustCrypto/utils?branch=blobby%2Fconst_prsing#0f7121f4d195f1c035dea6a2656de7d10029c6e9" [[package]] name = "block-buffer" diff --git a/Cargo.toml b/Cargo.toml index 7d2fdee4d..eb7b93615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ signature = { path = "signature" } der = { git = "https://github.com/RustCrypto/formats.git" } pkcs8 = { git = "https://github.com/RustCrypto/formats.git" } sec1 = { git = "https://github.com/RustCrypto/formats.git" } +blobby = { git = "https://github.com/RustCrypto/utils", branch = "blobby/const_prsing" } diff --git a/aead/src/dev.rs b/aead/src/dev.rs index f216b72cc..2f942d557 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,43 +1,80 @@ //! Development-related functionality use crate::{ - Aead, AeadInOut, Nonce, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, + Aead, AeadInOut, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, }; pub use blobby; +use crypto_common::KeyInit; + +/// AEAD test vector +#[derive(Debug, Clone, Copy)] +pub struct TestVector { + /// Initialization key + pub key: &'static [u8], + /// Nonce + pub nonce: &'static [u8], + /// Additional associated data + pub aad: &'static [u8], + /// Plaintext + pub plaintext: &'static [u8], + /// Ciphertext + pub ciphertext: &'static [u8], + /// Whether the test vector should pass (`[1]`) or fail (`[0]`) + pub pass: &'static [u8], +} /// Run AEAD test for the provided passing test vector -pub fn run_pass_test( - cipher: &C, - nonce: &Nonce, - aad: &[u8], - pt: &[u8], - ct: &[u8], +pub fn pass_test( + &TestVector { + key, + nonce, + aad, + plaintext, + ciphertext, + pass, + }: &TestVector, ) -> Result<(), &'static str> { + assert_eq!(pass, &[1]); + let nonce = nonce.try_into().expect("wrong nonce size"); + let cipher = ::new_from_slice(key).expect("failed to initialize the cipher"); + let res = cipher - .encrypt(nonce, Payload { aad, msg: pt }) + .encrypt( + nonce, + Payload { + aad, + msg: plaintext, + }, + ) .map_err(|_| "encryption failure")?; - if res != ct { + if res != ciphertext { return Err("encrypted data is different from target ciphertext"); } let res = cipher - .decrypt(nonce, Payload { aad, msg: ct }) + .decrypt( + nonce, + Payload { + aad, + msg: ciphertext, + }, + ) .map_err(|_| "decryption failure")?; - if res != pt { + if res != plaintext { 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); + let (tag, ct) = ciphertext.split_at(C::TagSize::USIZE); (ct, tag) } - TagPosition::Postfix => ct.split_at(pt.len()), + TagPosition::Postfix => ciphertext.split_at(plaintext.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 mut buf: alloc::vec::Vec = (0..plaintext.len()).map(|i| i as u8).collect(); + let inout_buf = InOutBuf::new(plaintext, &mut buf).expect("pt and buf have the same length"); let calc_tag = cipher .encrypt_inout_detached(nonce, aad, inout_buf) @@ -50,13 +87,15 @@ pub fn run_pass_test( } // Fill output buffer with "garbage" - buf.iter_mut().enumerate().for_each(|(i, v)| *v = i as u8); + buf.iter_mut() + .enumerate() + .for_each(|(i, v): (usize, &mut u8)| *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 { + if plaintext != buf { return Err("decrypt_inout_detached: plaintext mismatch"); } @@ -64,13 +103,27 @@ pub fn run_pass_test( } /// Run AEAD test for the provided failing test vector -pub fn run_fail_test( - cipher: &C, - nonce: &Nonce, - aad: &[u8], - ct: &[u8], +pub fn fail_test( + &TestVector { + key, + nonce, + aad, + ciphertext, + pass, + .. + }: &TestVector, ) -> Result<(), &'static str> { - let res = cipher.decrypt(nonce, Payload { aad, msg: ct }); + assert_eq!(pass, &[0]); + let nonce = nonce.try_into().expect("wrong nonce size"); + let cipher = ::new_from_slice(key).expect("failed to initialize the cipher"); + + let res = cipher.decrypt( + nonce, + Payload { + aad, + msg: ciphertext, + }, + ); if res.is_ok() { Err("decryption must return error") } else { @@ -84,33 +137,29 @@ macro_rules! new_test { ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { - 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 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"), + use $crate::dev::TestVector; + + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, nonce, aad, plaintext, ciphertext, pass } + ]; + ); + + for (i, tv) in TEST_VECTORS.iter().enumerate() { + let pass = tv.pass[0] == 1; + let res = if pass { + $crate::dev::pass_test::<$cipher>(tv) + } else { + $crate::dev::fail_test::<$cipher>(tv) }; - let mut pass = status[0] == 1; + if let Err(reason) = res { panic!( "\n\ 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" + test vector:\t{tv:?}\n" ); } } diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 817ab945c..8fabf4630 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -1,5 +1,6 @@ //! This module defines dummy (horribly insecure!) AEAD implementations //! to test implementation of the AEAD traits and helper macros in the `dev` module. +#![cfg(feature = "dev")] use aead::{ AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, array::Array, consts::U8, @@ -169,7 +170,5 @@ impl AeadInOut for PostfixDummyAead { } } -#[cfg(feature = "dev")] aead::new_test!(dummy_prefix, "prefix", PrefixDummyAead); -#[cfg(feature = "dev")] aead::new_test!(dummy_postfix, "postfix", PostfixDummyAead); diff --git a/cipher/src/dev.rs b/cipher/src/dev.rs index 6bedbe989..efd050267 100644 --- a/cipher/src/dev.rs +++ b/cipher/src/dev.rs @@ -1,2 +1,5 @@ -mod block; -mod stream; +//! Development-related functionality +pub mod block; +pub mod stream; + +pub use blobby; diff --git a/cipher/src/dev/block.rs b/cipher/src/dev/block.rs index 6a7f90426..7234535fb 100644 --- a/cipher/src/dev/block.rs +++ b/cipher/src/dev/block.rs @@ -1,213 +1,154 @@ -//! Development-related functionality - -/// Define block cipher test -#[macro_export] -macro_rules! block_cipher_test { - ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { - #[test] - fn $name() { - use cipher::{ - BlockSizeUser, KeyInit, - array::Array, - blobby::Blob3Iterator, - block::{BlockCipherDecrypt, BlockCipherEncrypt}, - typenum::Unsigned, - }; - - fn run_test(key: &[u8], pt: &[u8], ct: &[u8]) -> bool { - let mut state = <$cipher as KeyInit>::new_from_slice(key).unwrap(); - - let mut block = Array::try_from(pt).unwrap(); - state.encrypt_block(&mut block); - if ct != block.as_slice() { - return false; - } - - state.decrypt_block(&mut block); - if pt != block.as_slice() { - return false; - } - - true - } - - fn run_par_test(key: &[u8], pt: &[u8]) -> bool { - type Block = cipher::Block<$cipher>; - - let mut state = <$cipher as KeyInit>::new_from_slice(key).unwrap(); - - let block = Block::try_from(pt).unwrap(); - let mut blocks1 = vec![block; 101]; - for (i, b) in blocks1.iter_mut().enumerate() { - *b = block; - b[0] = b[0].wrapping_add(i as u8); - } - let mut blocks2 = blocks1.clone(); +//! Development-related functionality for block ciphers + +use crate::{Block, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}; + +/// Block cipher test vector +#[derive(Debug, Clone, Copy)] +pub struct TestVector { + /// Initialization key + pub key: &'static [u8], + /// Plaintext block + pub plaintext: &'static [u8], + /// Ciphertext block + pub ciphertext: &'static [u8], +} - // check that `encrypt_blocks` and `encrypt_block` - // result in the same ciphertext - state.encrypt_blocks(&mut blocks1); - for b in blocks2.iter_mut() { - state.encrypt_block(b); - } - if blocks1 != blocks2 { - return false; - } +/// Block cipher encryption test +pub fn block_cipher_enc_test( + tv: &TestVector, +) -> Result<(), &'static str> { + let Ok(state) = C::new_from_slice(tv.key) else { + return Err("cipher initialization failure"); + }; - // check that `encrypt_blocks` and `encrypt_block` - // result in the same plaintext - state.decrypt_blocks(&mut blocks1); - for b in blocks2.iter_mut() { - state.decrypt_block(b); - } - if blocks1 != blocks2 { - return false; - } + let Ok(pt_block) = Block::::try_from(tv.plaintext) else { + return Err("unexpected size of plaintext block"); + }; + let Ok(ct_block) = Block::::try_from(tv.ciphertext) else { + return Err("unexpected size of ciphertext block"); + }; - true - } + let mut block = pt_block.clone(); + state.encrypt_block(&mut block); + if block != ct_block { + return Err("single block encryption failure"); + } + + let mut blocks1: [Block; 101] = core::array::from_fn(|i| { + let mut block = pt_block.clone(); + block[0] ^= i as u8; + block + }); + let mut blocks2 = blocks1.clone(); + + // Check that `encrypt_blocks` and `encrypt_block` result in the same ciphertext + state.encrypt_blocks(&mut blocks1); + for b in blocks2.iter_mut() { + state.encrypt_block(b); + } + if blocks1 != blocks2 { + return Err("multi-block encryption failure"); + } + + Ok(()) +} - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, pt, ct] = row.unwrap(); - if !run_test(key, pt, ct) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, pt, ct, - ); - } +/// Block cipher encryption test +pub fn block_cipher_dec_test( + tv: &TestVector, +) -> Result<(), &'static str> { + let Ok(state) = C::new_from_slice(tv.key) else { + return Err("cipher initialization failure"); + }; - // test parallel blocks encryption/decryption - if !run_par_test(key, pt) { - panic!( - "\n\ - Failed parallel test №{}\n\ - key:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, pt, ct, - ); - } - } - } + let Ok(pt_block) = Block::::try_from(tv.plaintext) else { + return Err("unexpected size of plaintext block"); + }; + let Ok(ct_block) = Block::::try_from(tv.ciphertext) else { + return Err("unexpected size of ciphertext block"); }; + + let mut block = ct_block.clone(); + state.decrypt_block(&mut block); + if block != pt_block { + return Err("single block decryption failure"); + } + + let mut blocks1: [Block; 101] = core::array::from_fn(|i| { + let mut block = ct_block.clone(); + block[0] ^= i as u8; + block + }); + let mut blocks2 = blocks1.clone(); + + // Check that `encrypt_blocks` and `encrypt_block` result in the same ciphertext + state.decrypt_blocks(&mut blocks1); + for b in blocks2.iter_mut() { + state.decrypt_block(b); + } + if blocks1 != blocks2 { + return Err("multi-block decryption failure"); + } + + Ok(()) } -/// Define block mode encryption test +/// Define block cipher test #[macro_export] -macro_rules! block_mode_enc_test { - ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { - #[test] - fn $name() { - use cipher::{ - BlockCipherEncrypt, BlockModeEncrypt, BlockSizeUser, KeyIvInit, array::Array, - blobby::Blob4Iterator, inout::InOutBuf, typenum::Unsigned, - }; - - fn run_test(key: &[u8], iv: &[u8], pt: &[u8], ct: &[u8]) -> bool { - assert_eq!(pt.len(), ct.len()); - // test block-by-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - - let mut out = vec![0u8; ct.len()]; - let mut buf = InOutBuf::new(pt, &mut out).unwrap(); - let (blocks, tail) = buf.reborrow().into_chunks(); - assert_eq!(tail.len(), 0); - for block in blocks { - state.encrypt_block_inout(block); - } - if buf.get_out() != ct { - return false; - } - - // test multi-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - buf.get_out().iter_mut().for_each(|b| *b = 0); - let (blocks, _) = buf.reborrow().into_chunks(); - state.encrypt_blocks_inout(blocks); - if buf.get_out() != ct { - return false; - } - - true - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let [key, iv, pt, ct] = row.unwrap(); - if !run_test(key, iv, pt, ct) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, iv, pt, ct, - ); - } - } - } +macro_rules! block_cipher_test { + ( + encdec: $name:ident, $test_name:expr, $cipher:ty $(,)? + ) => { + $crate::block_cipher_test!( + inner: $name, $test_name, $cipher, + "encryption" block_cipher_enc_test, + "decryption" block_cipher_dec_test, + ); + }; + ( + enc: $name:ident, $test_name:expr, $cipher:ty $(,)? + ) => { + $crate::block_cipher_test!( + inner: $name, $test_name, $cipher, + "encryption" block_cipher_enc_test, + ); + }; + ( + dec: $name:ident, $test_name:expr, $cipher:ty $(,)? + ) => { + $crate::block_cipher_test!( + inner: $name, $test_name, $cipher, + "decryption" block_cipher_dec_test, + ); }; -} -/// Define block mode decryption test -#[macro_export] -macro_rules! block_mode_dec_test { - ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + ( + inner: $name:ident, $test_name:expr, $cipher:ty, + $($test_desc:literal $test_fn:ident,)* + ) => { #[test] fn $name() { - use cipher::{ - BlockCipherDecrypt, BlockModeDecrypt, BlockSizeUser, KeyIvInit, array::Array, - blobby::Blob4Iterator, inout::InOutBuf, typenum::Unsigned, - }; - - fn run_test(key: &[u8], iv: &[u8], pt: &[u8], ct: &[u8]) -> bool { - assert_eq!(pt.len(), ct.len()); - // test block-by-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - - let mut out = vec![0u8; pt.len()]; - let mut buf = InOutBuf::new(ct, &mut out).unwrap(); - let (blocks, tail) = buf.reborrow().into_chunks(); - assert_eq!(tail.len(), 0); - for block in blocks { - state.decrypt_block_inout(block); - } - if buf.get_out() != pt { - return false; - } - - // test multi-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - buf.get_out().iter_mut().for_each(|b| *b = 0); - let (blocks, _) = buf.reborrow().into_chunks(); - state.decrypt_blocks_inout(blocks); - if buf.get_out() != pt { - return false; - } - - true - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let [key, iv, pt, ct] = row.unwrap(); - if !run_test(key, iv, pt, ct) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, iv, pt, ct, - ); - } + use $crate::dev::block::TestVector; + + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, plaintext, ciphertext } + ]; + ); + + for (i, tv) in TEST_VECTORS.iter().enumerate() { + $( + let res = $test_fn(tv); + if let Err(reason) = res { + panic!(concat!( + "\n\ + Failed ", $test_desc, " test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + )); + } + )* } } }; diff --git a/cipher/src/dev/stream.rs b/cipher/src/dev/stream.rs index ff530f4cd..00dc14f54 100644 --- a/cipher/src/dev/stream.rs +++ b/cipher/src/dev/stream.rs @@ -1,4 +1,45 @@ -//! Development-related functionality +//! Development-related functionality for stream ciphers +use crate::{KeyIvInit, StreamCipher}; + +/// Stream cipher test vector +#[derive(Clone, Copy, Debug)] +pub struct TestVector { + /// Initialization key + pub key: &'static [u8], + /// Initialization vector + pub iv: &'static [u8], + /// Plaintext + pub plaintext: &'static [u8], + /// Ciphertext + pub ciphertext: &'static [u8], +} + +/// Run stream cipher test +pub fn stream_cipher_test( + tv: &TestVector, +) -> Result<(), &'static str> { + if tv.plaintext.len() != tv.ciphertext.len() { + return Err("mismatch of plaintext and ciphertext lengths"); + } + let mut buf = [0u8; 256]; + for chunk_len in 1..256 { + let Ok(mut mode) = C::new_from_slices(tv.key, tv.iv) else { + return Err("cipher initialization failure"); + }; + let pt_chunks = tv.plaintext.chunks(chunk_len); + let ct_chunks = tv.ciphertext.chunks(chunk_len); + for (pt_chunk, ct_chunk) in pt_chunks.zip(ct_chunks) { + let buf = &mut buf[..pt_chunk.len()]; + buf.copy_from_slice(pt_chunk); + mode.apply_keystream(buf); + + if buf != ct_chunk { + return Err("ciphertext mismatch"); + } + } + } + Ok(()) +} /// Test core functionality of synchronous stream cipher #[macro_export] @@ -6,29 +47,24 @@ macro_rules! stream_cipher_test { ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { - use cipher::array::Array; - use cipher::{KeyIvInit, StreamCipher, blobby::Blob4Iterator}; + use $crate::dev::stream::TestVector; - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let [key, iv, pt, ct] = row.unwrap(); + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, iv, plaintext, ciphertext } + ]; + ); - for chunk_n in 1..256 { - let mut mode = <$cipher>::new_from_slices(key, iv).unwrap(); - let mut pt = pt.to_vec(); - for chunk in pt.chunks_mut(chunk_n) { - mode.apply_keystream(chunk); - } - if pt != &ct[..] { - panic!( - "Failed main test №{}, chunk size: {}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, chunk_n, key, iv, pt, ct, - ); - } + for (i, tv) in TEST_VECTORS.iter().enumerate() { + let res = $crate::dev::stream::stream_cipher_test(tv); + if Err(reason) = res { + panic!( + "\n\ + Failed test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ); } } } diff --git a/cipher/src/lib.rs b/cipher/src/lib.rs index 7771e5973..009a8b8a9 100644 --- a/cipher/src/lib.rs +++ b/cipher/src/lib.rs @@ -18,7 +18,7 @@ missing_debug_implementations )] -#[cfg(all(feature = "block-padding", feature = "alloc"))] +#[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "dev")] @@ -34,7 +34,7 @@ pub use zeroize; pub mod block; #[cfg(feature = "dev")] -mod dev; +pub mod dev; pub mod stream; pub mod tweak; diff --git a/digest/src/dev.rs b/digest/src/dev.rs index b3d0203dc..85118387a 100644 --- a/digest/src/dev.rs +++ b/digest/src/dev.rs @@ -3,33 +3,47 @@ pub use blobby; mod fixed; +#[cfg(feature = "mac")] mod mac; mod rng; mod variable; mod xof; pub use fixed::*; +#[cfg(feature = "mac")] +pub use mac::*; pub use variable::*; pub use xof::*; +/// Test vector for hash functions +#[derive(Debug, Clone, Copy)] +pub struct TestVector { + /// Input data + input: &'static [u8], + /// Output hash + output: &'static [u8], +} + /// Define hash function test #[macro_export] macro_rules! new_test { - ($name:ident, $test_name:expr, $hasher:ty, $test_func:ident $(,)?) => { + ($name:ident, $test_name:expr, $hasher:ty, $test_fn:ident $(,)?) => { #[test] fn $name() { - use digest::dev::blobby::Blob2Iterator; - let data = include_bytes!(concat!("data/", $test_name, ".blb")); + use $crate::dev::TestVector; + + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[TestVector { input, output }]; + ); - for (i, row) in Blob2Iterator::new(data).unwrap().enumerate() { - let [input, output] = row.unwrap(); - if let Some(desc) = $test_func::<$hasher>(input, output) { + for (i, tv) in TEST_VECTORS.iter().enumerate() { + if let Some(reason) = $test_fn::<$hasher>(tv) { panic!( "\n\ - Failed test №{}: {}\n\ - input:\t{:?}\n\ - output:\t{:?}\n", - i, desc, input, output, + Failed test #{i}:\n\ + reason:\t{reason}\n\ + test vector:\t{tv:?}\n" ); } } diff --git a/digest/src/dev/fixed.rs b/digest/src/dev/fixed.rs index 24f380112..ad57e7741 100644 --- a/digest/src/dev/fixed.rs +++ b/digest/src/dev/fixed.rs @@ -1,24 +1,22 @@ -use crate::{Digest, FixedOutput, FixedOutputReset, HashMarker, Update}; -use core::fmt::Debug; +use crate::{Digest, FixedOutput, FixedOutputReset, HashMarker, dev::TestVector}; /// Fixed-output resettable digest test via the `Digest` trait -pub fn fixed_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: FixedOutputReset + Debug + Clone + Default + Update + HashMarker, -{ +pub fn fixed_reset_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::new(); // Test that it works when accepting the message all at once hasher.update(input); let mut hasher2 = hasher.clone(); if hasher.finalize()[..] != output[..] { - return Some("whole message"); + return Err("whole message"); } // Test if reset works correctly hasher2.reset(); hasher2.update(input); if hasher2.finalize_reset()[..] != output[..] { - return Some("whole message after reset"); + return Err("whole message after reset"); } // Test that it works when accepting the message in chunks @@ -29,26 +27,25 @@ where hasher2.update(chunk); } if hasher.finalize()[..] != output[..] { - return Some("message in chunks"); + return Err("message in chunks"); } if hasher2.finalize_reset()[..] != output[..] { - return Some("message in chunks"); + return Err("message in chunks"); } } - None + Ok(()) } /// Variable-output resettable digest test -pub fn fixed_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: FixedOutput + Default + Debug + Clone, -{ +pub fn fixed_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::default(); // Test that it works when accepting the message all at once hasher.update(input); if hasher.finalize_fixed()[..] != output[..] { - return Some("whole message"); + return Err("whole message"); } // Test that it works when accepting the message in chunks @@ -58,8 +55,8 @@ where hasher.update(chunk); } if hasher.finalize_fixed()[..] != output[..] { - return Some("message in chunks"); + return Err("message in chunks"); } } - None + Ok(()) } diff --git a/digest/src/dev/mac.rs b/digest/src/dev/mac.rs index 969a18b03..0b81dcf7d 100644 --- a/digest/src/dev/mac.rs +++ b/digest/src/dev/mac.rs @@ -1,154 +1,150 @@ -/// Define MAC test -#[macro_export] -#[cfg(feature = "mac")] -macro_rules! new_mac_test { - ($name:ident, $test_name:expr, $mac:ty $(,)?) => { - digest::new_mac_test!($name, $test_name, $mac, ""); - }; - ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { - digest::new_mac_test!($name, $test_name, $mac, "left"); +use crate::{FixedOutputReset, Mac, crypto_common::KeyInit}; + +/// Tag truncation side used in MAC tests +#[derive(Clone, Copy, Debug)] +pub enum MacTruncSide { + /// Tag truncated from left (i.e. `tag[..n]`) + Left, + /// Tag truncated from right (i.e. `tag[n..]`) + Right, + /// Tag is not truncated + None, +} + +/// MAC test vector +#[derive(Debug, Clone, Copy)] +pub struct MacTestVector { + /// Initialization key + pub key: &'static [u8], + /// Input message + input: &'static [u8], + /// Output tag + tag: &'static [u8], +} + +/// Run MAC test +pub fn mac_test( + &MacTestVector { key, input, tag }: &MacTestVector, + trunc_side: MacTruncSide, +) -> Result<(), &'static str> { + let Ok(mac0) = ::new_from_slice(key) else { + return Err("Failed to initialize MAC instance"); }; - ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { - digest::new_mac_test!($name, $test_name, $mac, "right"); + + let mut mac = mac0.clone(); + mac.update(input); + let result = mac.finalize().into_bytes(); + let n = tag.len(); + let result_bytes = match trunc_side { + MacTruncSide::Left => &result[..n], + MacTruncSide::Right => &result[result.len() - n..], + MacTruncSide::None => &result[..], }; - ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { - #[test] - fn $name() { - use core::cmp::min; - use digest::dev::blobby::Blob3Iterator; - use digest::{KeyInit, Mac}; + if result_bytes != tag { + return Err("whole message"); + } - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mac0 = <$mac as KeyInit>::new_from_slice(key).unwrap(); + // test reading different chunk sizes + for chunk_size in 1..core::cmp::min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + mac.update(chunk); + } + let res = match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + }; + if res.is_err() { + return Err("chunked message"); + } + } - let mut mac = mac0.clone(); - mac.update(input); - let result = mac.finalize().into_bytes(); - let n = tag.len(); - let result_bytes = match $trunc { - "left" => &result[..n], - "right" => &result[result.len() - n..], - _ => &result[..], - }; - if result_bytes != tag { - return Some("whole message"); - } + Ok(()) +} - // test reading different chunk sizes - for chunk_size in 1..min(64, input.len()) { - let mut mac = mac0.clone(); - for chunk in input.chunks(chunk_size) { - mac.update(chunk); - } - let res = match $trunc { - "left" => mac.verify_truncated_left(tag), - "right" => mac.verify_truncated_right(tag), - _ => mac.verify_slice(tag), - }; - if res.is_err() { - return Some("chunked message"); - } - } +/// Run resettable MAC test +pub fn reset_mac_test( + &MacTestVector { key, input, tag }: &MacTestVector, + trunc_side: MacTruncSide, +) -> Result<(), &'static str> { + let Ok(mac0) = ::new_from_slice(key) else { + return Err("Failed to initialize MAC instance"); + }; - None - } + let mut mac = mac0.clone(); + Mac::update(&mut mac, input); + let result = mac.finalize_reset().into_bytes(); + let n = tag.len(); + let result_bytes = match trunc_side { + MacTruncSide::Left => &result[..n], + MacTruncSide::Right => &result[result.len() - n..], + MacTruncSide::None => &result[..], + }; + if result_bytes != tag { + return Err("whole message"); + } - let data = include_bytes!(concat!("data/", $test_name, ".blb")); + // test if reset worked correctly + Mac::update(&mut mac, input); + let res = match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + }; + if res.is_err() { + return Err("after reset"); + } - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, input, tag] = row.unwrap(); - if let Some(desc) = run_test(key, input, tag) { - panic!( - "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, - ); - } - } + // test reading different chunk sizes + for chunk_size in 1..core::cmp::min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + Mac::update(&mut mac, chunk); } - }; + let res = match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + }; + if res.is_err() { + return Err("chunked message"); + } + } + + Ok(()) } -/// Define resettable MAC test +/// Define MAC test #[macro_export] -#[cfg(feature = "mac")] -macro_rules! new_resettable_mac_test { - ($name:ident, $test_name:expr, $mac:ty $(,)?) => { - digest::new_resettable_mac_test!($name, $test_name, $mac, ""); +macro_rules! new_mac_test { + ($name:ident, $test_name:expr, $mac:ty, $test_fn:ident $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, $test_fn, $crate::dev::MacTruncSide::None); }; - ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { - digest::new_resettable_mac_test!($name, $test_name, $mac, "left"); + ($name:ident, $test_name:expr, $mac:ty, $test_fn:ident, trunc_left $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, $test_fn, $crate::dev::MacTruncSide::Left); }; - ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { - digest::new_resettable_mac_test!($name, $test_name, $mac, "right"); + ($name:ident, $test_name:expr, $mac:ty, $test_fn:ident, trunc_right $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, $test_fn, $crate::dev::MacTruncSide::Right); }; - ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { + ($name:ident, $test_name:expr, $mac:ty, $test_fn:ident, $trunc:expr $(,)?) => { #[test] fn $name() { - use core::cmp::min; - use digest::dev::blobby::Blob3Iterator; - use digest::{KeyInit, Mac}; - - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mac0 = <$mac as KeyInit>::new_from_slice(key).unwrap(); - - let mut mac = mac0.clone(); - mac.update(input); - let result = mac.finalize_reset().into_bytes(); - let n = tag.len(); - let result_bytes = match $trunc { - "left" => &result[..n], - "right" => &result[result.len() - n..], - _ => &result[..], - }; - if result_bytes != tag { - return Some("whole message"); - } - - // test if reset worked correctly - mac.update(input); - let res = match $trunc { - "left" => mac.verify_truncated_left(tag), - "right" => mac.verify_truncated_right(tag), - _ => mac.verify_slice(tag), - }; - if res.is_err() { - return Some("after reset"); - } - - // test reading different chunk sizes - for chunk_size in 1..min(64, input.len()) { - let mut mac = mac0.clone(); - for chunk in input.chunks(chunk_size) { - mac.update(chunk); - } - let res = match $trunc { - "left" => mac.verify_truncated_left(tag), - "right" => mac.verify_truncated_right(tag), - _ => mac.verify_slice(tag), - }; - if res.is_err() { - return Some("chunked message"); - } - } - None - } + use digest::dev::MacTestVector; - let data = include_bytes!(concat!("data/", $test_name, ".blb")); + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[MacTestVector { key, input, tag }]; + ); - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { + for (i, tv) in TEST_VECTORS.iter().enumerate() { let [key, input, tag] = row.unwrap(); - if let Some(desc) = run_test(key, input, tag) { + if let Some(reason) = $test_fn(tv, $trunc) { panic!( "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, + Failed test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" ); } } diff --git a/digest/src/dev/variable.rs b/digest/src/dev/variable.rs index ed8ff8828..aab77a38b 100644 --- a/digest/src/dev/variable.rs +++ b/digest/src/dev/variable.rs @@ -1,11 +1,9 @@ -use crate::{VariableOutput, VariableOutputReset}; -use core::fmt::Debug; +use crate::{VariableOutput, VariableOutputReset, dev::TestVector}; /// Variable-output resettable digest test -pub fn variable_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: VariableOutputReset + Debug + Clone, -{ +pub fn variable_reset_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::new(output.len()).unwrap(); let mut buf = [0u8; 128]; let buf = &mut buf[..output.len()]; @@ -14,7 +12,7 @@ where let mut hasher2 = hasher.clone(); hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("whole message"); + return Err("whole message"); } buf.iter_mut().for_each(|b| *b = 0); @@ -23,7 +21,7 @@ where hasher2.update(input); hasher2.finalize_variable_reset(buf).unwrap(); if buf != output { - return Some("whole message after reset"); + return Err("whole message after reset"); } buf.iter_mut().for_each(|b| *b = 0); @@ -36,25 +34,24 @@ where } hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); hasher2.finalize_variable_reset(buf).unwrap(); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); } - None + Ok(()) } /// Variable-output resettable digest test -pub fn variable_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: VariableOutput + Debug + Clone, -{ +pub fn variable_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::new(output.len()).unwrap(); let mut buf = [0u8; 128]; let buf = &mut buf[..output.len()]; @@ -62,7 +59,7 @@ where hasher.update(input); hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("whole message"); + return Err("whole message"); } buf.iter_mut().for_each(|b| *b = 0); @@ -74,9 +71,9 @@ where } hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); } - None + Ok(()) } diff --git a/digest/src/dev/xof.rs b/digest/src/dev/xof.rs index 9e5d07a09..acd4c8769 100644 --- a/digest/src/dev/xof.rs +++ b/digest/src/dev/xof.rs @@ -1,11 +1,10 @@ -use crate::ExtendableOutputReset; +use crate::{ExtendableOutputReset, dev::TestVector}; use core::fmt::Debug; /// Resettable XOF test -pub fn xof_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: ExtendableOutputReset + Default + Debug + Clone, -{ +pub fn xof_reset_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::default(); let mut buf = [0u8; 1024]; let buf = &mut buf[..output.len()]; @@ -14,7 +13,7 @@ where let mut hasher2 = hasher.clone(); hasher.finalize_xof_into(buf); if buf != output { - return Some("whole message"); + return Err("whole message"); } buf.iter_mut().for_each(|b| *b = 0); @@ -23,7 +22,7 @@ where hasher2.update(input); hasher2.finalize_xof_reset_into(buf); if buf != output { - return Some("whole message after reset"); + return Err("whole message after reset"); } buf.iter_mut().for_each(|b| *b = 0); @@ -36,16 +35,16 @@ where } hasher.finalize_xof_into(buf); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); hasher2.finalize_xof_reset_into(buf); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); } - None + Ok(()) }