keepass/format/
hmac_block_stream.rs1use byteorder::{ByteOrder, LittleEndian};
2use hex_literal::hex;
3use hybrid_array::{typenum::U64, Array as GenericArray};
4use thiserror::Error;
5
6pub const HMAC_KEY_END: [u8; 1] = hex!("01");
7
8pub(crate) fn read_hmac_block_stream(
10 data: &[u8],
11 key: &GenericArray<u8, U64>,
12) -> Result<Vec<u8>, BlockStreamError> {
13 let mut out = Vec::new();
16
17 let mut pos = 0;
18 let mut block_index: u64 = 0;
19
20 while pos < data.len() {
21 let hmac = data.get(pos..(pos + 32)).ok_or(BlockStreamError::Eof)?;
22 let size_bytes = data.get((pos + 32)..(pos + 36)).ok_or(BlockStreamError::Eof)?;
23 let size = LittleEndian::read_u32(size_bytes) as usize;
24 let block = data
25 .get((pos + 36)..(pos + 36 + size))
26 .ok_or(BlockStreamError::Eof)?;
27
28 let hmac_block_key = get_hmac_block_key(block_index, key);
30 let mut block_index_buf = [0u8; 8];
31 LittleEndian::write_u64(&mut block_index_buf, block_index);
32
33 #[allow(clippy::expect_used)] if hmac
35 != crate::crypt::calculate_hmac(&[&block_index_buf, size_bytes, block], &hmac_block_key)
36 .expect("Block stream key always correctly sized")
37 .as_slice()
38 {
39 return Err(BlockStreamError::BlockHashMismatch { block_index });
40 }
41
42 pos += 36 + size;
43 block_index += 1;
44
45 if size == 0 {
46 break;
47 }
48
49 out.extend_from_slice(block);
50 }
51
52 Ok(out)
53}
54
55#[cfg(feature = "save_kdbx4")]
56pub(crate) fn write_hmac_block_stream(data: &[u8], key: &GenericArray<u8, U64>) -> Vec<u8> {
58 let mut out = Vec::new();
59
60 let mut pos = 0;
61 let mut block_index = 0;
62
63 while pos < data.len() {
64 let size = data.len() - pos;
65
66 #[allow(clippy::indexing_slicing)] let block = &data[pos..(pos + size)];
68
69 let mut size_bytes: Vec<u8> = vec![0; 4];
70 LittleEndian::write_u32(&mut size_bytes, size as u32);
71
72 let hmac_block_key = get_hmac_block_key(block_index, key);
74 let mut block_index_buf = [0u8; 8];
75 LittleEndian::write_u64(&mut block_index_buf, block_index);
76
77 #[allow(clippy::expect_used)] let hmac = crate::crypt::calculate_hmac(&[&block_index_buf, &size_bytes, block], &hmac_block_key)
79 .expect("Block stream key always correctly sized");
80
81 pos += 36 + size;
82 block_index += 1;
83
84 out.extend_from_slice(&hmac);
85 out.extend_from_slice(&size_bytes);
86 out.extend_from_slice(block);
87 }
88
89 let hmac_block_key = get_hmac_block_key(block_index, key);
91 let mut block_index_buf = [0u8; 8];
92 LittleEndian::write_u64(&mut block_index_buf, block_index);
93
94 let size_bytes = vec![0; 4];
95
96 #[allow(clippy::expect_used)] let hmac = crate::crypt::calculate_hmac(&[&block_index_buf, &size_bytes, &[]], &hmac_block_key)
98 .expect("Block stream key always correctly sized");
99
100 out.extend_from_slice(&hmac);
101 out.extend_from_slice(&size_bytes);
102
103 out
104}
105
106pub(crate) fn get_hmac_block_key(block_index: u64, key: &GenericArray<u8, U64>) -> GenericArray<u8, U64> {
107 let mut buf = [0u8; 8];
108 LittleEndian::write_u64(&mut buf, block_index);
109 crate::crypt::calculate_sha512(&[&buf, key])
110}
111
112#[derive(Debug, Error)]
114#[non_exhaustive]
115pub enum BlockStreamError {
116 #[error("Block hash mismatch for block {}", block_index)]
119 BlockHashMismatch {
120 block_index: u64,
122 },
123
124 #[error("unexpected end of file")]
127 Eof,
128}