1use std::convert::{TryFrom, TryInto};
4
5use byteorder::{ByteOrder, LittleEndian};
6use thiserror::Error;
7
8use crate::{
9 config::{CompressionConfig, DatabaseConfig, InnerCipherConfig, KdfConfig, OuterCipherConfig},
10 crypt::{self, ciphers::Cipher},
11 db::{Database, DatabaseFormatError, DatabaseOpenError, Value},
12 format::{
13 hmac_block_stream,
14 kdbx4::{
15 KDBX4OuterHeader, HEADER_COMMENT, HEADER_COMPRESSION_ID, HEADER_ENCRYPTION_IV, HEADER_END,
16 HEADER_KDF_PARAMS, HEADER_MASTER_SEED, HEADER_OUTER_ENCRYPTION_ID, HEADER_PUBLIC_CUSTOM_DATA,
17 INNER_HEADER_BINARY_ATTACHMENTS, INNER_HEADER_END, INNER_HEADER_RANDOM_STREAM_ID,
18 INNER_HEADER_RANDOM_STREAM_KEY,
19 },
20 variant_dictionary::VariantDictionary,
21 DatabaseVersion,
22 },
23 key::{DatabaseKey, DatabaseKeyError},
24};
25
26use super::KDBX4InnerHeader;
27
28pub(crate) fn parse_kdbx4(data: &[u8], db_key: &DatabaseKey) -> Result<Database, DatabaseOpenError> {
30 let (config, header_attachments, mut inner_decryptor, xml) = decrypt_kdbx4(data, db_key)?;
31
32 let mut db = crate::format::xml_db::parse_xml(&xml, &header_attachments, &mut *inner_decryptor)
33 .map_err(|e| DatabaseOpenError::Format(DatabaseFormatError::Kdbx4(Kdbx4OpenError::Xml(e))))?;
34
35 db.config = config;
36
37 Ok(db)
38}
39
40#[allow(clippy::type_complexity)]
42pub(crate) fn decrypt_kdbx4(
43 data: &[u8],
44 db_key: &DatabaseKey,
45) -> Result<(DatabaseConfig, Vec<Value<Vec<u8>>>, Box<dyn Cipher>, Vec<u8>), DatabaseOpenError> {
46 let version = DatabaseVersion::parse(data)?;
47
48 let (outer_header, inner_header_start) = parse_outer_header(data)
50 .map_err(|e| DatabaseOpenError::Format(DatabaseFormatError::Kdbx4(Kdbx4OpenError::OuterHeader(e))))?;
51
52 #[allow(clippy::indexing_slicing)] let header_data = &data[0..inner_header_start];
59
60 let header_sha256 = data
61 .get(inner_header_start..(inner_header_start + 32))
62 .ok_or(DatabaseOpenError::UnexpectedEof)?;
63
64 let header_hmac = data
65 .get((inner_header_start + 32)..(inner_header_start + 64))
66 .ok_or(DatabaseOpenError::UnexpectedEof)?;
67
68 let hmac_block_stream = data
69 .get((inner_header_start + 64)..)
70 .ok_or(DatabaseOpenError::UnexpectedEof)?;
71
72 if header_sha256 != crypt::calculate_sha256(&[header_data]).as_slice() {
74 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdbx4(
75 Kdbx4OpenError::HeaderHashMismatch,
76 )));
77 }
78
79 #[cfg(feature = "challenge_response")]
80 let db_key = db_key.clone().perform_challenge(&outer_header.kdf_seed)?;
81
82 let key_elements = db_key.get_key_elements()?;
84 let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
85 let composite_key = crypt::calculate_sha256(&key_elements);
86 let transformed_key = outer_header
87 .kdf_config
88 .get_kdf_seeded(&outer_header.kdf_seed)
89 .transform_key(&composite_key)?;
90 let master_key = crypt::calculate_sha256(&[outer_header.master_seed.as_ref(), &transformed_key]);
91
92 let hmac_key = crypt::calculate_sha512(&[
94 &outer_header.master_seed,
95 &transformed_key,
96 &hmac_block_stream::HMAC_KEY_END,
97 ]);
98 let header_hmac_key = hmac_block_stream::get_hmac_block_key(u64::MAX, &hmac_key);
99
100 #[allow(clippy::expect_used)] if header_hmac
102 != crypt::calculate_hmac(&[header_data], &header_hmac_key)
103 .expect("HMAC block key always correctly sized")
104 .as_slice()
105 {
106 return Err(DatabaseKeyError::IncorrectKey.into());
107 }
108
109 let payload_encrypted = hmac_block_stream::read_hmac_block_stream(hmac_block_stream, &hmac_key)
111 .map_err(|e| DatabaseOpenError::Format(DatabaseFormatError::Kdbx4(Kdbx4OpenError::BlockStream(e))))?;
112
113 let payload_compressed = outer_header
115 .outer_cipher_config
116 .get_cipher(&master_key, &outer_header.outer_iv)?
117 .decrypt(&payload_encrypted)?;
118
119 let payload = outer_header
120 .compression_config
121 .get_compression()
122 .decompress(&payload_compressed)?;
123
124 let (header_attachments, inner_header, body_start) = parse_inner_header(&payload)
126 .map_err(|e| DatabaseOpenError::Format(DatabaseFormatError::Kdbx4(Kdbx4OpenError::InnerHeader(e))))?;
127
128 let xml = payload
130 .get(body_start..)
131 .ok_or(DatabaseOpenError::UnexpectedEof)?;
132
133 let inner_decryptor = inner_header
135 .inner_random_stream
136 .get_cipher(&inner_header.inner_random_stream_key)?;
137
138 let config = DatabaseConfig {
139 version,
140 outer_cipher_config: outer_header.outer_cipher_config,
141 compression_config: outer_header.compression_config,
142 inner_cipher_config: inner_header.inner_random_stream,
143 kdf_config: outer_header.kdf_config,
144 public_custom_data: outer_header.public_custom_data,
145 };
146
147 Ok((config, header_attachments, inner_decryptor, xml.to_vec()))
148}
149
150fn parse_outer_header(data: &[u8]) -> Result<(KDBX4OuterHeader, usize), Kdbx4OuterHeaderError> {
151 let mut pos = DatabaseVersion::get_version_header_size();
153
154 let mut outer_cipher: Option<OuterCipherConfig> = None;
155 let mut compression_config: Option<CompressionConfig> = None;
156 let mut master_seed: Option<Vec<u8>> = None;
157 let mut outer_iv: Option<Vec<u8>> = None;
158 let mut kdf_config: Option<KdfConfig> = None;
159 let mut kdf_seed: Option<Vec<u8>> = None;
160 let mut public_custom_data: Option<VariantDictionary> = None;
161
162 loop {
164 let entry_type = data.get(pos).ok_or(Kdbx4OuterHeaderError::UnexpectedEof)?;
175 let entry_length = data
176 .get(pos + 1..(pos + 5))
177 .ok_or(Kdbx4OuterHeaderError::UnexpectedEof)?;
178
179 let entry_length: usize = LittleEndian::read_u32(entry_length) as usize;
180
181 let entry_buffer = data
182 .get((pos + 5)..(pos + 5 + entry_length))
183 .ok_or(Kdbx4OuterHeaderError::UnexpectedEof)?;
184
185 pos += 5 + entry_length;
186
187 match *entry_type {
188 HEADER_END => {
189 break;
190 }
191
192 HEADER_COMMENT => {}
193
194 HEADER_OUTER_ENCRYPTION_ID => {
195 outer_cipher = Some(OuterCipherConfig::try_from(entry_buffer)?);
196 }
197
198 HEADER_COMPRESSION_ID => {
199 let id = entry_buffer
200 .get(0..4)
201 .ok_or(Kdbx4OuterHeaderError::UnexpectedEof)?;
202 compression_config = Some(CompressionConfig::try_from(LittleEndian::read_u32(id))?);
203 }
204
205 HEADER_MASTER_SEED => master_seed = Some(entry_buffer.to_vec()),
206
207 HEADER_ENCRYPTION_IV => outer_iv = Some(entry_buffer.to_vec()),
208
209 HEADER_KDF_PARAMS => {
210 let vd =
211 VariantDictionary::parse(entry_buffer).map_err(Kdbx4OuterHeaderError::ParseKdfConfig)?;
212
213 let (kconf, kseed) = vd.try_into()?;
214 kdf_config = Some(kconf);
215 kdf_seed = Some(kseed)
216 }
217
218 HEADER_PUBLIC_CUSTOM_DATA => {
219 let vd =
220 VariantDictionary::parse(entry_buffer).map_err(Kdbx4OuterHeaderError::ParseCustomData)?;
221
222 public_custom_data = Some(vd)
223 }
224
225 _ => return Err(Kdbx4OuterHeaderError::InvalidEntry(*entry_type)),
226 };
227 }
228
229 fn get_or_err<T>(v: Option<T>, err: &'static str) -> Result<T, Kdbx4OuterHeaderError> {
233 v.ok_or(Kdbx4OuterHeaderError::Incomplete(err))
234 }
235
236 let outer_cipher_config = get_or_err(outer_cipher, "Outer Cipher ID")?;
237 let compression_config = get_or_err(compression_config, "Compression ID")?;
238 let master_seed = get_or_err(master_seed, "Master seed")?;
239 let outer_iv = get_or_err(outer_iv, "Outer IV")?;
240 let kdf_config = get_or_err(kdf_config, "Key Derivation Function Parameters")?;
241 let kdf_seed = get_or_err(kdf_seed, "Key Derivation Function Seed")?;
242
243 Ok((
244 KDBX4OuterHeader {
245 outer_cipher_config,
246 compression_config,
247 master_seed,
248 outer_iv,
249 kdf_config,
250 kdf_seed,
251 public_custom_data,
252 },
253 pos,
254 ))
255}
256
257#[derive(Debug, Error)]
259#[non_exhaustive]
260pub enum Kdbx4OuterHeaderError {
261 #[error("Unexpected end of file while parsing outer header")]
263 UnexpectedEof,
264
265 #[error(transparent)]
267 OuterCipherConfig(#[from] crate::config::OuterCipherConfigError),
268
269 #[error(transparent)]
271 CompressionConfig(#[from] crate::config::CompressionConfigError),
272
273 #[error("error parsing KDF config: {0}")]
275 ParseKdfConfig(#[source] crate::format::variant_dictionary::VariantDictionaryError),
276
277 #[error("error parsing public custom data: {0}")]
279 ParseCustomData(#[source] crate::format::variant_dictionary::VariantDictionaryError),
280
281 #[error(transparent)]
283 KdfConfig(#[from] crate::config::KdfConfigError),
284
285 #[error("Invalid outer header entry: {0}")]
287 InvalidEntry(u8),
288
289 #[error("Outer header incomplete - missing {0}")]
291 Incomplete(&'static str),
292}
293
294#[allow(clippy::type_complexity)]
295fn parse_inner_header(
296 data: &[u8],
297) -> Result<(Vec<Value<Vec<u8>>>, KDBX4InnerHeader, usize), Kdbx4InnerHeaderError> {
298 let mut pos = 0;
299
300 let mut inner_random_stream = None;
301 let mut inner_random_stream_key = None;
302 let mut header_attachments = Vec::new();
303
304 loop {
305 let entry_type = *data.get(pos).ok_or(Kdbx4InnerHeaderError::UnexpectedEof)?;
306
307 let entry_length = data
308 .get(pos + 1..(pos + 5))
309 .ok_or(Kdbx4InnerHeaderError::UnexpectedEof)?;
310 let entry_length: usize = LittleEndian::read_u32(entry_length) as usize;
311
312 let entry_buffer = data
313 .get((pos + 5)..(pos + 5 + entry_length))
314 .ok_or(Kdbx4InnerHeaderError::UnexpectedEof)?;
315
316 pos += 5 + entry_length;
317
318 match entry_type {
319 INNER_HEADER_END => break,
320
321 INNER_HEADER_RANDOM_STREAM_ID => {
322 let id = entry_buffer
323 .get(0..4)
324 .ok_or(Kdbx4InnerHeaderError::UnexpectedEof)?;
325 inner_random_stream = Some(InnerCipherConfig::try_from(LittleEndian::read_u32(id))?);
326 }
327
328 INNER_HEADER_RANDOM_STREAM_KEY => inner_random_stream_key = Some(entry_buffer.to_vec()),
329
330 #[allow(clippy::indexing_slicing)] INNER_HEADER_BINARY_ATTACHMENTS => {
332 if entry_buffer.is_empty() {
333 return Err(Kdbx4InnerHeaderError::UnexpectedEof);
334 }
335
336 let flags = entry_buffer[0];
337 let data = entry_buffer[1..].to_vec();
338
339 let protected = flags & 0x01 != 0;
340
341 let data = if protected {
342 Value::protected(data)
343 } else {
344 Value::unprotected(data)
345 };
346
347 header_attachments.push(data);
348 }
349
350 _ => {
351 return Err(Kdbx4InnerHeaderError::InvalidEntry(entry_type));
352 }
353 }
354 }
355
356 fn get_or_err<T>(v: Option<T>, err: &'static str) -> Result<T, Kdbx4InnerHeaderError> {
357 v.ok_or(Kdbx4InnerHeaderError::Incomplete(err))
358 }
359
360 let inner_random_stream = get_or_err(inner_random_stream, "Inner random stream")?;
361 let inner_random_stream_key = get_or_err(inner_random_stream_key, "Inner random stream key")?;
362
363 let inner_header = KDBX4InnerHeader {
364 inner_random_stream,
365 inner_random_stream_key,
366 };
367
368 Ok((header_attachments, inner_header, pos))
369}
370
371#[derive(Debug, Error)]
373#[non_exhaustive]
374pub enum Kdbx4InnerHeaderError {
375 #[error(transparent)]
377 InnerCipherConfig(#[from] crate::config::InnerCipherConfigError),
378
379 #[error("Invalid inner header entry: {0}")]
381 InvalidEntry(u8),
382
383 #[error("Inner header incomplete - missing {0}")]
385 Incomplete(&'static str),
386
387 #[error("Unexpected end of file while parsing inner header")]
389 UnexpectedEof,
390}
391
392#[derive(Debug, Error)]
394#[non_exhaustive]
395pub enum Kdbx4OpenError {
396 #[error(transparent)]
398 Xml(#[from] crate::format::xml_db::ParseXmlError),
399
400 #[error(transparent)]
402 OuterHeader(#[from] Kdbx4OuterHeaderError),
403
404 #[error(transparent)]
406 InnerHeader(#[from] Kdbx4InnerHeaderError),
407
408 #[error("Header hash mismatch - the header may be corrupted")]
411 HeaderHashMismatch,
412
413 #[error(transparent)]
415 BlockStream(#[from] hmac_block_stream::BlockStreamError),
416}