cipher/dev/
block_mode.rs

1//! Development-related functionality for block modes
2
3use crate::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, inout::InOutBuf};
4
5const MAX_MSG_LEN: usize = 1 << 12;
6
7/// Block mode test vector
8#[derive(Debug, Clone, Copy)]
9pub struct TestVector {
10    /// Initialization key
11    pub key: &'static [u8],
12    /// Initialization vector
13    pub iv: &'static [u8],
14    /// Plaintext block
15    pub plaintext: &'static [u8],
16    /// Ciphertext block
17    pub ciphertext: &'static [u8],
18}
19
20/// Block mode encryption test
21pub fn encrypt<C: BlockModeEncrypt + KeyIvInit>(tv: &TestVector) -> Result<(), &'static str> {
22    let mut buf = [0u8; MAX_MSG_LEN];
23    let Some(out) = buf.get_mut(..tv.ciphertext.len()) else {
24        return Err("ciphertext is bigger than MAX_MSG_LEN bytes");
25    };
26    let Ok(mut buf) = InOutBuf::new(tv.plaintext, out) else {
27        return Err("plaintext/ciphertext length mismatch");
28    };
29    let (blocks, tail) = buf.reborrow().into_chunks();
30    if !tail.is_empty() {
31        return Err("plaintext/ciphertext length is not multiple of block size");
32    }
33
34    let Ok(mut cipher) = C::new_from_slices(tv.key, tv.iv) else {
35        return Err("cipher initialization failure");
36    };
37    for block in blocks {
38        cipher.encrypt_block_inout(block);
39    }
40    if buf.get_out() != tv.ciphertext {
41        return Err("single block encryption failure");
42    }
43
44    // test multi-block processing
45    let Ok(mut cipher) = C::new_from_slices(tv.key, tv.iv) else {
46        return Err("cipher initialization failure");
47    };
48    buf.get_out().fill(0);
49    let (blocks, _) = buf.reborrow().into_chunks();
50    cipher.encrypt_blocks_inout(blocks);
51    if buf.get_out() != tv.ciphertext {
52        return Err("multi-block encryption failure");
53    }
54    Ok(())
55}
56
57/// Block mode decryption test
58pub fn decrypt<C: BlockModeDecrypt + KeyIvInit>(tv: &TestVector) -> Result<(), &'static str> {
59    let mut buf = [0u8; MAX_MSG_LEN];
60    let Some(out) = buf.get_mut(..tv.plaintext.len()) else {
61        return Err("plaintext is bigger than MAX_MSG_LEN bytes");
62    };
63    let Ok(mut buf) = InOutBuf::new(tv.ciphertext, out) else {
64        return Err("plaintext/ciphertext length mismatch");
65    };
66    let (blocks, tail) = buf.reborrow().into_chunks();
67    if !tail.is_empty() {
68        return Err("plaintext/ciphertext length is not multiple of block size");
69    }
70
71    let Ok(mut cipher) = C::new_from_slices(tv.key, tv.iv) else {
72        return Err("cipher initialization failure");
73    };
74    for block in blocks {
75        cipher.decrypt_block_inout(block);
76    }
77    if buf.get_out() != tv.plaintext {
78        return Err("single block decryption failure");
79    }
80
81    // test multi-block processing
82    let Ok(mut cipher) = C::new_from_slices(tv.key, tv.iv) else {
83        return Err("cipher initialization failure");
84    };
85    buf.get_out().fill(0);
86    let (blocks, _) = buf.reborrow().into_chunks();
87    cipher.decrypt_blocks_inout(blocks);
88    if buf.get_out() != tv.plaintext {
89        return Err("multi-block decryption failure");
90    }
91    Ok(())
92}
93
94/// Define block mode test
95#[macro_export]
96macro_rules! block_mode_test {
97    // Test with matching test and file names
98    ($name:ident, $cipher:ty, $test_fn:ident $(,)?) => {
99        $crate::block_cipher_test!($name, stringify!($name), $cipher);
100    };
101    // Test with custom test function and test name
102    ($test_name:ident, $file_name:expr, $cipher:ty, $test_fn:ident $(,)?) => {
103        #[test]
104        fn $test_name() {
105            use $crate::dev::block_mode::TestVector;
106
107            $crate::dev::blobby::parse_into_structs!(
108                include_bytes!(concat!("data/", $file_name, ".blb"));
109                static TEST_VECTORS: &[
110                    TestVector { key, iv, plaintext, ciphertext }
111                ];
112            );
113
114            for (i, tv) in TEST_VECTORS.iter().enumerate() {
115                if let Err(reason) = $crate::dev::block_mode::$test_fn::<$cipher>(tv) {
116                    panic!(
117                        "\n\
118                        Failed test #{i}\n\
119                        reason:\t{reason:?}\n\
120                        test vector:\t{tv:?}\n",
121                    );
122                }
123            }
124        }
125    };
126}
127
128/// Define `IvState` test
129#[macro_export]
130macro_rules! iv_state_test {
131    ($name:ident, $cipher:ty, encrypt $(,)?) => {
132        $crate::iv_state_test!($name, $cipher, encrypt_blocks);
133    };
134    ($name:ident, $cipher:ty, decrypt $(,)?) => {
135        $crate::iv_state_test!($name, $cipher, decrypt_blocks);
136    };
137    ($name:ident, $cipher:ty, apply_ks $(,)?) => {
138        $crate::iv_state_test!($name, $cipher, apply_keystream_blocks);
139    };
140    ($name:ident, $cipher:ty, $method:ident $(,)?) => {
141        #[test]
142        fn $name() {
143            use cipher::*;
144
145            let mut blocks = [Block::<$cipher>::default(); 32];
146
147            for (i, block) in blocks.iter_mut().enumerate() {
148                for (j, b) in block.iter_mut().enumerate() {
149                    *b = (i + j) as u8;
150                }
151            }
152
153            let mut key = Key::<$cipher>::default();
154            let mut iv = Iv::<$cipher>::default();
155            key.iter_mut().for_each(|b| *b = 0x42);
156            iv.iter_mut().for_each(|b| *b = 0x24);
157
158            let mut cipher = <$cipher>::new(&key, &iv);
159            let mut target = blocks.clone();
160            cipher.$method(&mut target);
161
162            for i in 0..32 {
163                let mut blocks = blocks.clone();
164                let (b1, b2) = blocks.split_at_mut(i);
165                let mut cipher1 = <$cipher>::new(&key, &iv);
166                cipher1.$method(b1);
167                let temp_iv = cipher1.iv_state();
168                let mut cipher2 = <$cipher>::new(&key, &temp_iv);
169                cipher2.$method(b2);
170                assert_eq!(blocks, target);
171            }
172        }
173    };
174}