1use crate::{
2 config::{CompressionConfig, DatabaseConfig, InnerCipherConfig, KdfConfig, OuterCipherConfig},
3 crypt::calculate_sha256,
4 db::{fields, Database, DatabaseFormatError, DatabaseOpenError, GroupId, Value},
5 format::DatabaseVersion,
6 key::{DatabaseKey, DatabaseKeyError},
7};
8
9use byteorder::{ByteOrder, LittleEndian};
10use hybrid_array::Array as GenericArray;
11use thiserror::Error;
12
13use std::{
14 collections::HashMap,
15 convert::{TryFrom, TryInto},
16};
17
18#[derive(Debug)]
19struct KDBHeader {
20 pub flags: u32,
22 pub subversion: u32,
23 pub master_seed: Vec<u8>, pub encryption_iv: Vec<u8>, pub num_groups: u32,
26 pub num_entries: u32,
27 pub contents_hash: Vec<u8>, pub transform_seed: Vec<u8>, pub transform_rounds: u32,
30}
31
32const HEADER_SIZE: usize = 4 + 4 + 4 + 4 + 16 + 16 + 4 + 4 + 32 + 32 + 4; impl TryFrom<&[u8]> for KDBHeader {
35 type Error = DatabaseOpenError;
36
37 #[allow(clippy::indexing_slicing)] fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
39 if data.len() < HEADER_SIZE {
40 return Err(DatabaseOpenError::UnexpectedEof);
41 }
42
43 Ok(KDBHeader {
44 flags: LittleEndian::read_u32(&data[8..]),
45 subversion: LittleEndian::read_u32(&data[12..]),
46 master_seed: data[16..32].to_vec(),
47 encryption_iv: data[32..48].to_vec(),
48 num_groups: LittleEndian::read_u32(&data[48..]),
49 num_entries: LittleEndian::read_u32(&data[52..]),
50 contents_hash: data[56..88].to_vec(),
51 transform_seed: data[88..120].to_vec(),
52 transform_rounds: LittleEndian::read_u32(&data[120..]),
53 })
54 }
55}
56
57fn from_utf8(data: &[u8]) -> String {
58 String::from_utf8_lossy(data).trim_end_matches('\0').to_owned()
59}
60
61fn expected_group_field_size(ftype: u16) -> Option<u32> {
62 match ftype {
63 0x0001 => Some(4), 0x0002 => None, 0x0003 => Some(5), 0x0004 => Some(5), 0x0005 => Some(5), 0x0006 => Some(5), 0x0007 => Some(4), 0x0008 => Some(2), 0x0009 => Some(4), 0xffff => Some(0), _ => None, }
75}
76
77fn parse_groups(
78 db: &mut Database,
79 header_num_groups: u32,
80 data: &mut &[u8],
81) -> Result<HashMap<u32, GroupId>, DatabaseOpenError> {
82 let mut gid_map: HashMap<u32, GroupId> = HashMap::new();
83 gid_map.insert(0, db.root);
84
85 let mut branch: Vec<GroupId> = Vec::new();
87 branch.push(db.root);
88
89 let mut parsing_name: Option<String> = None;
91 let mut parsing_level: Option<u16> = None;
92 let mut parsing_gid: Option<u32> = None;
93
94 let mut num_groups = 0;
96 while num_groups < header_num_groups as usize {
97 let field_type = data
99 .get(0..2)
100 .map(LittleEndian::read_u16)
101 .ok_or(DatabaseOpenError::UnexpectedEof)?;
102
103 let field_size = data
104 .get(2..6)
105 .map(LittleEndian::read_u32)
106 .ok_or(DatabaseOpenError::UnexpectedEof)?;
107
108 let field_value = data
109 .get(6..6 + field_size as usize)
110 .ok_or(DatabaseOpenError::UnexpectedEof)?;
111
112 if let Some(expected_field_size) = expected_group_field_size(field_type) {
113 if expected_field_size != field_size {
114 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
115 KdbOpenError::InvalidFieldLength {
116 field_type,
117 field_size,
118 expected_field_size,
119 },
120 )));
121 }
122 }
123
124 match field_type {
125 0x0000 => {} 0x0001 => parsing_gid = Some(LittleEndian::read_u32(field_value)),
129
130 0x0002 => parsing_name = Some(from_utf8(field_value)),
132
133 0x0003..=0x0006 => {}
135
136 0x0007 => {}
138
139 0x0008 => parsing_level = Some(LittleEndian::read_u16(field_value)),
141
142 0x0009 => {}
144
145 0xffff => {
147 let group_id = parsing_gid.ok_or(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
148 KdbOpenError::InvalidGroupId(None),
149 )))?;
150
151 let level = parsing_level.ok_or(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
152 KdbOpenError::InvalidGroupLevel {
153 current: None,
154 expected: branch.len() as u16,
155 },
156 )))? as usize;
157
158 let name = parsing_name.clone().unwrap_or_else(|| String::from(""));
159
160 let parent_id: GroupId = if level <= branch.len() {
161 branch.truncate(level);
162 *branch.last().unwrap_or(&db.root().id())
163 } else {
164 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
166 KdbOpenError::InvalidGroupLevel {
167 current: Some(level as u16),
168 expected: branch.len() as u16,
169 },
170 )));
171 };
172
173 #[allow(clippy::expect_used)] let mut parent = db.group_mut(parent_id).expect("parent group must exist");
175
176 let mut group = parent.add_group();
177 group.name = name;
178
179 parsing_gid = None;
180 parsing_name = None;
181 parsing_level = None;
182
183 gid_map.insert(group_id, group.id());
184
185 branch.push(group.id());
186
187 num_groups += 1;
188 }
189 _ => {
190 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
191 KdbOpenError::InvalidGroupFieldType(field_type),
192 )));
193 }
194 }
195
196 *data = data
197 .get(6 + field_size as usize..)
198 .ok_or(DatabaseOpenError::UnexpectedEof)?;
199 }
200
201 if parsing_gid.is_some() {
202 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
203 KdbOpenError::IncompleteGroup,
204 )));
205 }
206
207 Ok(gid_map)
208}
209
210fn expected_entry_field_size(ftype: u16) -> Option<u32> {
211 match ftype {
212 0x0000 => None, 0x0001 => Some(16), 0x0002 => Some(4), 0x0003 => Some(4), 0x0004 => None, 0x0005 => None, 0x0006 => None, 0x0007 => None, 0x0008 => None, 0x0009 => Some(5), 0x000a => Some(5), 0x000b => Some(5), 0x000c => Some(5), 0x000d => None, 0x000e => None, 0xffff => Some(0), _ => None, }
230}
231
232fn parse_entries(
233 db: &mut Database,
234 gid_map: HashMap<u32, GroupId>,
235 header_num_entries: u32,
236 data: &mut &[u8],
237) -> Result<(), DatabaseOpenError> {
238 let mut parsing_gid: Option<u32> = None;
239 let mut parsing_fields: HashMap<String, Value<String>> = HashMap::new();
240
241 let mut parsing_binary_desc: Option<String> = None;
242 let mut parsing_binary_data: Option<Vec<u8>> = None;
243
244 let mut entry_attachments: HashMap<String, Vec<u8>> = HashMap::new();
245
246 let mut num_entries = 0;
247 while num_entries < header_num_entries {
248 let field_type = data.get(0..2).ok_or(DatabaseOpenError::UnexpectedEof)?;
249 let field_type = LittleEndian::read_u16(field_type);
250
251 let field_size = data.get(2..6).ok_or(DatabaseOpenError::UnexpectedEof)?;
252 let field_size = LittleEndian::read_u32(field_size);
253
254 let field_value = data
255 .get(6..6 + field_size as usize)
256 .ok_or(DatabaseOpenError::UnexpectedEof)?;
257
258 if let Some(expected_field_size) = expected_entry_field_size(field_type) {
259 if expected_field_size != field_size {
260 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
261 KdbOpenError::InvalidFieldLength {
262 field_type,
263 field_size,
264 expected_field_size,
265 },
266 )));
267 }
268 }
269
270 match field_type {
271 0x0000 => {} 0x0001 => {}
276
277 0x0002 => parsing_gid = Some(LittleEndian::read_u32(field_value)),
279
280 0x0003 => {}
282
283 0x0004 => {
285 parsing_fields.insert(
286 String::from(fields::TITLE),
287 Value::unprotected(from_utf8(field_value)),
288 );
289 }
290
291 0x0005 => {
293 parsing_fields.insert(
294 String::from(fields::URL),
295 Value::unprotected(from_utf8(field_value)),
296 );
297 }
298
299 0x0006 => {
301 parsing_fields.insert(
302 String::from(fields::USERNAME),
303 Value::unprotected(from_utf8(field_value)),
304 );
305 }
306
307 0x0007 => {
309 parsing_fields.insert(
310 String::from(fields::PASSWORD),
311 Value::protected(from_utf8(field_value)),
312 );
313 }
314
315 0x0008 => {
317 parsing_fields.insert(
318 String::from(fields::NOTES),
319 Value::unprotected(from_utf8(field_value)),
320 );
321 }
322
323 0x0009..=0x000c => {}
325
326 0x000d => {
328 if let Some(ref data) = parsing_binary_data {
329 entry_attachments.insert(from_utf8(field_value), data.clone());
330 parsing_binary_desc = None;
331 } else {
332 parsing_binary_desc = Some(from_utf8(field_value));
333 }
334 }
335
336 0x000e => {
338 if let Some(ref desc) = parsing_binary_desc {
339 entry_attachments.insert(desc.clone(), field_value.to_vec());
340 parsing_binary_data = None;
341 } else {
342 parsing_binary_data = Some(field_value.to_vec());
343 }
344 }
345
346 0xffff => {
347 let gid = parsing_gid.ok_or(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
348 KdbOpenError::InvalidGroupId(None),
349 )))?;
350 let group_id =
351 *gid_map
352 .get(&gid)
353 .ok_or(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
354 KdbOpenError::InvalidGroupId(Some(gid)),
355 )))?;
356
357 #[allow(clippy::expect_used)] let mut group = db.group_mut(group_id).expect("group must exist");
359
360 let mut entry = group.add_entry();
361 entry.fields = parsing_fields.clone();
362
363 for (desc, data) in entry_attachments.drain() {
364 entry.add_attachment(desc, Value::protected(data));
365 }
366
367 parsing_fields.clear();
368
369 parsing_gid = None;
370 num_entries += 1;
371 }
372
373 _ => {
374 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
375 KdbOpenError::InvalidEntryFieldType(field_type),
376 )));
377 }
378 }
379
380 *data = data
381 .get(6 + field_size as usize..)
382 .ok_or(DatabaseOpenError::UnexpectedEof)?;
383 }
384
385 if parsing_gid.is_some() {
386 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
387 KdbOpenError::IncompleteEntry,
388 )));
389 }
390
391 Ok(())
392}
393
394pub(crate) fn parse_kdb(data: &[u8], db_key: &DatabaseKey) -> Result<Database, DatabaseOpenError> {
395 let header = KDBHeader::try_from(data)?;
396 let version = DatabaseVersion::KDB(header.subversion as u16);
397
398 let payload_encrypted = data.get(HEADER_SIZE..).ok_or(DatabaseOpenError::UnexpectedEof)?;
400
401 let key_elements = db_key.get_key_elements()?;
403 let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
404 let composite_key = if key_elements.len() == 1 {
405 #[allow(clippy::indexing_slicing, clippy::expect_used)] let key_element: [u8; 32] = key_elements[0]
407 .try_into()
408 .expect("initializing from single element should always succeed");
409 GenericArray::from(key_element) } else {
411 calculate_sha256(&key_elements) };
413
414 let kdf_config = KdfConfig::Aes {
416 rounds: u64::from(header.transform_rounds),
417 };
418
419 let transformed_key = kdf_config
420 .get_kdf_seeded(&header.transform_seed)
421 .transform_key(&composite_key)?;
422
423 let master_key = calculate_sha256(&[&header.master_seed, &transformed_key]);
424
425 let outer_cipher_config = if header.flags & 2 != 0 {
426 OuterCipherConfig::AES256
427 } else if header.flags & 8 != 0 {
428 OuterCipherConfig::Twofish
429 } else {
430 return Err(DatabaseOpenError::Format(DatabaseFormatError::Kdb(
431 KdbOpenError::InvalidFixedCipherID(header.flags),
432 )));
433 };
434
435 #[allow(clippy::expect_used)] let payload_padded = outer_cipher_config
438 .get_cipher(&master_key, header.encryption_iv.as_ref())
439 .expect("Database key correctly derived")
440 .decrypt(payload_encrypted)?;
441
442 let padlen = payload_padded
443 .last()
444 .copied()
445 .ok_or(DatabaseOpenError::UnexpectedEof)? as usize;
446 let payload = payload_padded
447 .get(..payload_padded.len() - padlen)
448 .ok_or(DatabaseOpenError::UnexpectedEof)?;
449
450 let hash = calculate_sha256(&[payload]);
452 if header.contents_hash != hash.as_slice() {
453 return Err(DatabaseOpenError::Key(DatabaseKeyError::IncorrectKey));
454 }
455
456 let config = DatabaseConfig {
457 version,
458 outer_cipher_config,
459 compression_config: CompressionConfig::None,
460 inner_cipher_config: InnerCipherConfig::Plain,
461 kdf_config,
462 public_custom_data: Default::default(),
463 };
464
465 let mut db = Database::with_data(config, GroupId::new());
466 db.root_mut().name = String::from("Root");
467
468 let mut pos = payload;
469
470 let gid_map = parse_groups(&mut db, header.num_groups, &mut pos)?;
471 parse_entries(&mut db, gid_map, header.num_entries, &mut pos)?;
472
473 Ok(db)
474}
475
476#[derive(Debug, Error)]
478#[non_exhaustive]
479pub enum KdbOpenError {
480 #[error("Field of type {field_type} has invalid length {field_size}, expected {expected_field_size}")]
483 InvalidFieldLength {
484 field_type: u16,
486
487 field_size: u32,
489
490 expected_field_size: u32,
492 },
493
494 #[error("Invalid group level: got {current:?}, expected {expected}")]
497 InvalidGroupLevel {
498 current: Option<u16>,
501
502 expected: u16,
505 },
506
507 #[error("Invalid group ID: {0:?}")]
510 InvalidGroupId(Option<u32>),
511
512 #[error("Invalid group field type: {0}")]
515 InvalidGroupFieldType(u16),
516
517 #[error("Group was not terminated before end of file")]
520 IncompleteGroup,
521
522 #[error("Entry is missing group ID")]
524 EntryMissingGroupId,
525
526 #[error("Invalid entry field type: {0}")]
528 InvalidEntryFieldType(u16),
529
530 #[error("Entry was not terminated before end of file")]
532 IncompleteEntry,
533
534 #[error("Invalid fixed cipher ID: {0}")]
536 InvalidFixedCipherID(u32),
537}