1use crate::{
2 config::{CompressionConfig, DatabaseConfig, InnerCipherConfig, KdfConfig, OuterCipherConfig},
3 crypt::{calculate_sha256, ciphers::Cipher},
4 db::{Database, DatabaseFormatError, DatabaseOpenError},
5 format::DatabaseVersion,
6 key::{DatabaseKey, DatabaseKeyError},
7};
8
9use byteorder::{ByteOrder, LittleEndian};
10use thiserror::Error;
11
12use std::convert::TryFrom;
13
14#[derive(Debug)]
15struct KDBX3Header {
16 outer_cipher: OuterCipherConfig,
18 compression: CompressionConfig,
19 master_seed: Vec<u8>,
20
21 transform_seed: Vec<u8>,
22 kdf_config: KdfConfig,
23
24 outer_iv: Vec<u8>,
25 protected_stream_key: Vec<u8>,
26 stream_start: Vec<u8>,
27 inner_cipher: InnerCipherConfig,
28 body_start: usize,
29}
30
31fn parse_outer_header(data: &[u8]) -> Result<KDBX3Header, Kdbx3OuterHeaderError> {
32 let mut outer_cipher: Option<OuterCipherConfig> = None;
33 let mut compression: Option<CompressionConfig> = None;
34 let mut master_seed: Option<Vec<u8>> = None;
35 let mut transform_seed: Option<Vec<u8>> = None;
36 let mut transform_rounds: Option<u64> = None;
37 let mut outer_iv: Option<Vec<u8>> = None;
38 let mut protected_stream_key: Option<Vec<u8>> = None;
39 let mut stream_start: Option<Vec<u8>> = None;
40 let mut inner_cipher: Option<InnerCipherConfig> = None;
41
42 let mut pos = DatabaseVersion::get_version_header_size();
44
45 loop {
47 let entry_type = *data.get(pos).ok_or(Kdbx3OuterHeaderError::UnexpectedEof)?;
58
59 let entry_length = data
60 .get((pos + 1)..(pos + 3))
61 .ok_or(Kdbx3OuterHeaderError::UnexpectedEof)?;
62 let entry_length: usize = LittleEndian::read_u16(entry_length) as usize;
63
64 let entry_buffer = data
65 .get((pos + 3)..(pos + 3 + entry_length))
66 .ok_or(Kdbx3OuterHeaderError::UnexpectedEof)?;
67
68 pos += 3 + entry_length;
69
70 match entry_type {
71 0 => {
73 break;
74 }
75
76 1 => {}
78
79 2 => {
82 outer_cipher = Some(OuterCipherConfig::try_from(entry_buffer)?);
83 }
84
85 3 => {
87 compression = Some(CompressionConfig::try_from(LittleEndian::read_u32(entry_buffer))?);
88 }
89
90 4 => master_seed = Some(entry_buffer.to_vec()),
92
93 5 => transform_seed = Some(entry_buffer.to_vec()),
95
96 6 => transform_rounds = Some(LittleEndian::read_u64(entry_buffer)),
98
99 7 => outer_iv = Some(entry_buffer.to_vec()),
101
102 8 => protected_stream_key = Some(entry_buffer.to_vec()),
104
105 9 => stream_start = Some(entry_buffer.to_vec()),
107
108 10 => {
111 inner_cipher = Some(InnerCipherConfig::try_from(LittleEndian::read_u32(entry_buffer))?);
112 }
113
114 _ => {
115 return Err(Kdbx3OuterHeaderError::InvalidOuterHeaderEntry(entry_type));
116 }
117 };
118 }
119
120 fn get_or_err<T>(v: Option<T>, err: &'static str) -> Result<T, Kdbx3OuterHeaderError> {
124 v.ok_or(Kdbx3OuterHeaderError::IncompleteOuterHeader(err))
125 }
126
127 let outer_cipher = get_or_err(outer_cipher, "Outer Cipher ID")?;
128 let compression = get_or_err(compression, "Compression ID")?;
129 let master_seed = get_or_err(master_seed, "Master seed")?;
130 let transform_seed = get_or_err(transform_seed, "Transform seed")?;
131 let transform_rounds = get_or_err(transform_rounds, "Number of transformation rounds")?;
132 let outer_iv = get_or_err(outer_iv, "Outer cipher IV")?;
133 let protected_stream_key = get_or_err(protected_stream_key, "Protected stream key")?;
134 let stream_start = get_or_err(stream_start, "Stream start bytes")?;
135 let inner_cipher = get_or_err(inner_cipher, "Inner cipher ID")?;
136
137 let kdf_config = KdfConfig::Aes {
139 rounds: transform_rounds,
140 };
141
142 Ok(KDBX3Header {
143 outer_cipher,
144 compression,
145 master_seed,
146 transform_seed,
147 kdf_config,
148 outer_iv,
149 protected_stream_key,
150 stream_start,
151 inner_cipher,
152 body_start: pos,
153 })
154}
155
156#[derive(Debug, Error)]
158#[non_exhaustive]
159pub enum Kdbx3OuterHeaderError {
160 #[error(transparent)]
162 InnerCipher(#[from] crate::config::InnerCipherConfigError),
163
164 #[error(transparent)]
166 OuterCipher(#[from] crate::config::OuterCipherConfigError),
167
168 #[error(transparent)]
170 Compression(#[from] crate::config::CompressionConfigError),
171
172 #[error("Encountered invalid outer header entry with type {0}")]
174 InvalidOuterHeaderEntry(u8),
175
176 #[error("Outer header is missing {0}")]
178 IncompleteOuterHeader(&'static str),
179
180 #[error("Unexpected end of file while reading outer header")]
182 UnexpectedEof,
183}
184
185pub(crate) fn parse_kdbx3(data: &[u8], db_key: &DatabaseKey) -> Result<Database, DatabaseOpenError> {
187 let (config, mut inner_decryptor, xml) = decrypt_kdbx3(data, db_key)?;
188
189 let mut db = crate::format::xml_db::parse_xml(&xml, &[], &mut *inner_decryptor)
191 .map_err(|e| DatabaseOpenError::Format(DatabaseFormatError::Kdbx3(Kdbx3OpenError::Xml(e))))?;
192
193 db.config = config;
194
195 Ok(db)
196}
197
198#[allow(clippy::type_complexity)]
200pub(crate) fn decrypt_kdbx3(
201 data: &[u8],
202 db_key: &DatabaseKey,
203) -> Result<(DatabaseConfig, Box<dyn Cipher>, Vec<u8>), DatabaseOpenError> {
204 let version = DatabaseVersion::parse(data)?;
205 let header = parse_outer_header(data)
206 .map_err(|e| DatabaseOpenError::Format(DatabaseFormatError::Kdbx3(Kdbx3OpenError::OuterHeader(e))))?;
207
208 let inner_decryptor = header.inner_cipher.get_cipher(&header.protected_stream_key)?;
209
210 let config = DatabaseConfig {
211 version,
212 outer_cipher_config: header.outer_cipher,
213 compression_config: header.compression,
214 inner_cipher_config: header.inner_cipher,
215 kdf_config: header.kdf_config,
216 public_custom_data: Default::default(),
217 };
218
219 let mut pos = header.body_start;
220
221 let compression = config.compression_config.get_compression();
223
224 let payload_encrypted = data.get(pos..).ok_or(DatabaseOpenError::UnexpectedEof)?;
226
227 let key_elements = db_key.get_key_elements()?;
229 let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
230 let composite_key = calculate_sha256(&key_elements);
231
232 let transformed_key = config
234 .kdf_config
235 .get_kdf_seeded(&header.transform_seed)
236 .transform_key(&composite_key)?;
237
238 let master_key = calculate_sha256(&[header.master_seed.as_ref(), &transformed_key]);
239
240 let payload = config
242 .outer_cipher_config
243 .get_cipher(&master_key, header.outer_iv.as_ref())?
244 .decrypt(payload_encrypted)?;
245
246 let payload_start = payload
248 .get(0..header.stream_start.len())
249 .ok_or(DatabaseOpenError::UnexpectedEof)?;
250 if payload_start != header.stream_start.as_slice() {
251 return Err(DatabaseKeyError::IncorrectKey.into());
252 }
253
254 let mut buf = Vec::new();
255
256 pos = 32;
257 let mut block_index = 0;
258 loop {
259 let block_hash = payload
272 .get((pos + 4)..(pos + 36))
273 .ok_or(DatabaseOpenError::UnexpectedEof)?;
274
275 let block_size = payload
276 .get((pos + 36)..(pos + 40))
277 .ok_or(DatabaseOpenError::UnexpectedEof)?;
278 let block_size = LittleEndian::read_u32(block_size) as usize;
279
280 if block_size == 0 {
282 break;
283 }
284
285 let block_buffer_compressed = payload
286 .get((pos + 40)..(pos + 40 + block_size))
287 .ok_or(DatabaseOpenError::UnexpectedEof)?;
288
289 let block_hash_check = calculate_sha256(&[block_buffer_compressed]);
291 if block_hash != block_hash_check.as_slice() {
292 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdbx3(
293 Kdbx3OpenError::BlockHashMismatch(block_index),
294 )));
295 }
296
297 buf.append(&mut block_buffer_compressed.to_vec());
299
300 pos += 40 + block_size;
301 block_index += 1;
302 }
303
304 let xml = compression.decompress(&buf)?;
305
306 Ok((config, inner_decryptor, xml))
307}
308
309#[derive(Debug, Error)]
311#[non_exhaustive]
312pub enum Kdbx3OpenError {
313 #[error(transparent)]
315 OuterHeader(#[from] Kdbx3OuterHeaderError),
316
317 #[error("block hash mismatch at block index {0}")]
319 BlockHashMismatch(usize),
320
321 #[error(transparent)]
323 Xml(#[from] crate::format::xml_db::ParseXmlError),
324}