1use crate::FileKey;
2use cbc::cipher::BlockEncryptMut;
3use cbc::cipher::KeyIvInit;
4
5type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
6
7#[derive(Debug, Clone)]
9pub struct FileValidationError {
10 pub actual_mac: [u8; 8],
12
13 pub expected_mac: [u8; 8],
15}
16
17impl std::fmt::Display for FileValidationError {
18 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19 write!(
20 f,
21 "file mac mismatch, expected {} but got {}",
22 HexSlice(&self.expected_mac),
23 HexSlice(&self.actual_mac)
24 )
25 }
26}
27
28impl std::error::Error for FileValidationError {}
29
30struct HexSlice<'a>(&'a [u8]);
32
33impl std::fmt::Display for HexSlice<'_> {
34 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
35 write!(f, "0x")?;
36 for byte in self.0.iter() {
37 write!(f, "{:X}", byte)?;
38 }
39 Ok(())
40 }
41}
42
43pub struct FileValidator {
45 file_key: FileKey,
46 chunk_iter: ChunkIter,
47 left_in_chunk: usize,
48 file_mac: u128,
49 chunk_mac: u128,
50 buffer: [u8; 16],
51 buffer_end: usize,
52 done: bool,
53}
54
55impl FileValidator {
56 pub fn new(file_key: FileKey) -> Self {
58 let mut chunk_iter = ChunkIter::new();
59 let (_, left_in_chunk) = chunk_iter.next().unwrap();
61 let left_in_chunk = usize::try_from(left_in_chunk).unwrap();
63 let chunk_mac = create_chunk_mac(&file_key);
64
65 Self {
66 file_key,
67 chunk_iter,
68 left_in_chunk,
69 file_mac: 0,
70 chunk_mac,
71 buffer: [0; 16],
72 buffer_end: 0,
73 done: false,
74 }
75 }
76
77 fn process_block(&mut self, block: [u8; 16]) {
79 self.chunk_mac ^= u128::from_be_bytes(block);
80 let mut chunk_mac_bytes = self.chunk_mac.to_be_bytes();
81 aes_cbc_encrypt_u128(self.file_key.key, &mut chunk_mac_bytes);
82 self.chunk_mac = u128::from_be_bytes(chunk_mac_bytes);
83
84 self.left_in_chunk -= 16;
85 if self.left_in_chunk == 0 {
86 self.begin_new_chunk();
87 }
88 }
89
90 fn begin_new_chunk(&mut self) {
92 self.file_mac ^= self.chunk_mac;
93 let mut file_mac_bytes = self.file_mac.to_be_bytes();
94 aes_cbc_encrypt_u128(self.file_key.key, &mut file_mac_bytes);
95 self.file_mac = u128::from_be_bytes(file_mac_bytes);
96
97 self.chunk_mac = create_chunk_mac(&self.file_key);
99 let (_, left_in_chunk) = self.chunk_iter.next().unwrap();
101 self.left_in_chunk = usize::try_from(left_in_chunk).unwrap();
103 }
104
105 pub fn feed(&mut self, mut input: &[u8]) {
112 assert!(!self.done, "cannot feed a completed file validator");
113
114 if self.buffer_end != 0 {
115 let need_to_consume = self.buffer.len() - self.buffer_end;
117 let consume_input_len = std::cmp::min(need_to_consume, input.len());
118
119 self.buffer[self.buffer_end..(self.buffer_end + consume_input_len)]
120 .copy_from_slice(&input[..consume_input_len]);
121 if consume_input_len < need_to_consume {
122 self.buffer_end += consume_input_len;
123 return;
125 }
126
127 self.process_block(self.buffer);
128 input = &input[need_to_consume..];
129 self.buffer_end = 0;
130 }
131
132 let mut block_iter = input.chunks_exact(16);
134 for block in block_iter.by_ref() {
135 let block = block.try_into().unwrap();
137 self.process_block(block);
138 }
139
140 let remainder = block_iter.remainder();
142 let remainder_len = remainder.len();
143 if remainder_len != 0 {
144 self.buffer[..remainder_len].copy_from_slice(remainder);
145 self.buffer_end = remainder_len;
146 }
147 }
148
149 pub fn finish(&mut self) -> Result<(), FileValidationError> {
154 assert!(!self.done, "cannot finish a completed file validator");
155 self.done = true;
156
157 if self.buffer_end != 0 {
158 self.buffer[self.buffer_end..].fill(0);
160 self.process_block(self.buffer);
161 }
162
163 let mut file_mac = self.file_mac ^ self.chunk_mac;
164 let mut file_mac_bytes = file_mac.to_be_bytes();
165 aes_cbc_encrypt_u128(self.file_key.key, &mut file_mac_bytes);
166 file_mac = u128::from_be_bytes(file_mac_bytes);
167
168 let file_mac_bytes = file_mac.to_be_bytes();
169 let file_mac_u32_0 = u32::from_be_bytes(file_mac_bytes[..4].try_into().unwrap());
170 let file_mac_u32_1 = u32::from_be_bytes(file_mac_bytes[4..8].try_into().unwrap());
171 let file_mac_u32_2 = u32::from_be_bytes(file_mac_bytes[8..12].try_into().unwrap());
172 let file_mac_u32_3 = u32::from_be_bytes(file_mac_bytes[12..].try_into().unwrap());
173
174 let final_file_mac_u32_0 = file_mac_u32_0 ^ file_mac_u32_1;
175 let final_file_mac_u32_1 = file_mac_u32_2 ^ file_mac_u32_3;
176
177 let mut final_file_mac_bytes = [0; 8];
178 final_file_mac_bytes[..4].copy_from_slice(&final_file_mac_u32_0.to_be_bytes());
179 final_file_mac_bytes[4..].copy_from_slice(&final_file_mac_u32_1.to_be_bytes());
180 let final_file_mac = u64::from_be_bytes(final_file_mac_bytes);
181
182 if final_file_mac != self.file_key.meta_mac {
183 return Err(FileValidationError {
184 expected_mac: self.file_key.meta_mac.to_be_bytes(),
185 actual_mac: final_file_mac.to_be_bytes(),
186 });
187 }
188
189 Ok(())
190 }
191
192 pub fn is_finished(&self) -> bool {
194 self.done
195 }
196}
197
198fn create_chunk_mac(file_key: &FileKey) -> u128 {
199 let mut chunk_mac_bytes = [0; 16];
200 let iv_bytes = file_key.iv.to_be_bytes();
201 chunk_mac_bytes[..8].copy_from_slice(&iv_bytes[..8]);
202 chunk_mac_bytes[8..].copy_from_slice(&iv_bytes[..8]);
203 u128::from_be_bytes(chunk_mac_bytes)
204}
205
206fn aes_cbc_encrypt_u128(key: u128, data: &mut [u8; 16]) {
207 let mut cipher = Aes128CbcEnc::new(&key.to_be_bytes().into(), &[0; 16].into());
208 cipher.encrypt_block_mut((data).into());
209}
210
211#[derive(Debug)]
213struct ChunkIter {
214 offset: u64,
216 delta: u64,
217}
218
219impl ChunkIter {
220 fn new() -> Self {
221 Self {
222 delta: 0,
223 offset: 0,
224 }
225 }
226}
227
228impl Iterator for ChunkIter {
229 type Item = (u64, u64);
230
231 fn next(&mut self) -> Option<Self::Item> {
232 self.delta += 128 * 1024;
233 self.delta = std::cmp::min(self.delta, 1024 * 1024);
234
235 let old_offset = self.offset;
236 self.offset += self.delta;
237
238 Some((old_offset, self.delta))
239 }
240}
241
242#[cfg(test)]
243mod test {
244 use super::*;
245 use crate::test::*;
246
247 #[test]
248 #[expect(clippy::erasing_op, clippy::identity_op)]
249 fn chunk_iter() {
250 let mut iter = ChunkIter::new();
251 assert!(iter.next() == Some((128 * 0 * 2014, 128 * 1 * 1024)));
252 assert!(iter.next() == Some((128 * 1 * 1024, 128 * 2 * 1024)));
253 assert!(iter.next() == Some((128 * 3 * 1024, 128 * 3 * 1024)));
254 assert!(iter.next() == Some((128 * 6 * 1024, 128 * 4 * 1024)));
255 assert!(iter.next() == Some((128 * 10 * 1024, 128 * 5 * 1024)));
256 assert!(iter.next() == Some((128 * 15 * 1024, 128 * 6 * 1024)));
257 assert!(iter.next() == Some((128 * 21 * 1024, 128 * 7 * 1024)));
258 assert!(iter.next() == Some((128 * 28 * 1024, 128 * 8 * 1024)));
259 assert!(iter.next() == Some((128 * 36 * 1024, 128 * 8 * 1024)));
260 assert!(iter.next() == Some((128 * 44 * 1024, 128 * 8 * 1024)));
261 }
262
263 #[test]
264 fn file_validator() {
265 let file_key = FileKey {
266 key: TEST_FILE_KEY_KEY_DECODED,
267 iv: TEST_FILE_KEY_IV_DECODED,
268 meta_mac: TEST_FILE_META_MAC_DECODED,
269 };
270
271 let mut validator = FileValidator::new(file_key);
272 validator.feed(TEST_FILE_BYTES);
273 validator.finish().expect("invalid mac");
274 }
275}