1use crate::{
2 config::{CompressionConfig, DatabaseConfig, InnerCipherConfig, KdfConfig, OuterCipherConfig},
3 crypt::calculate_sha256,
4 db::*,
5 error::{DatabaseIntegrityError, DatabaseKeyError, DatabaseOpenError},
6 format::DatabaseVersion,
7 key::DatabaseKey,
8};
9
10use byteorder::{ByteOrder, LittleEndian};
11use cipher::Array;
12
13use std::{collections::HashMap, convert::TryInto, str};
14
15#[derive(Debug)]
16struct KDBHeader {
17 pub flags: u32,
19 pub subversion: u32,
20 pub master_seed: Vec<u8>, pub encryption_iv: Vec<u8>, pub num_groups: u32,
23 pub num_entries: u32,
24 pub contents_hash: Vec<u8>, pub transform_seed: Vec<u8>, pub transform_rounds: u32,
27}
28
29const HEADER_SIZE: usize = 4 + 4 + 4 + 4 + 16 + 16 + 4 + 4 + 32 + 32 + 4; fn parse_header(data: &[u8]) -> Result<KDBHeader, DatabaseIntegrityError> {
32 if data.len() < HEADER_SIZE {
33 return Err(DatabaseIntegrityError::InvalidFixedHeader { size: data.len() });
34 }
35
36 Ok(KDBHeader {
37 flags: LittleEndian::read_u32(&data[8..]),
38 subversion: LittleEndian::read_u32(&data[12..]),
39 master_seed: data[16..32].to_vec(),
40 encryption_iv: data[32..48].to_vec(),
41 num_groups: LittleEndian::read_u32(&data[48..]),
42 num_entries: LittleEndian::read_u32(&data[52..]),
43 contents_hash: data[56..88].to_vec(),
44 transform_seed: data[88..120].to_vec(),
45 transform_rounds: LittleEndian::read_u32(&data[120..]),
46 })
47}
48
49fn from_utf8(data: &[u8]) -> String {
50 String::from_utf8_lossy(data).trim_end_matches('\0').to_owned()
51}
52
53fn ensure_length(field_type: u16, field_size: u32, expected_field_size: u32) -> Result<(), KdbOpenError> {
54 if field_size == expected_field_size {
55 Ok(())
56 } else {
57 Err(KdbOpenError::InvalidFieldLength {
58 field_type,
59 field_size,
60 expected_field_size,
61 })
62 }
63}
64
65fn entry_name(field_type: u16) -> &'static str {
66 match field_type {
67 0x0004 => "Title",
68 0x0005 => "URL",
69 0x0006 => "UserName",
70 0x0008 => "Additional",
71 0x000d => "BinaryDesc",
72 _ => {
73 panic!("Unsupported field type!");
74 }
75 }
76}
77
78fn collapse_tail_groups(branch: &mut Vec<NodePtr>, level: usize, root: &NodePtr) -> Option<()> {
80 while level < branch.len() {
81 let leaf = branch.pop()?; let parent = match branch.last() {
83 Some(parent) => parent,
84 None => root,
85 };
86 let count = group_get_children(parent)?.len();
87 group_add_child(parent, leaf, count).ok()?;
88 }
89 Some(())
90}
91
92type GidMap = HashMap<u32, Vec<String>>;
94
95fn parse_groups(root: &NodePtr, header_num_groups: u32, data: &mut &[u8]) -> Result<GidMap, KdbOpenError> {
96 let mut gid_map: HashMap<u32, Vec<String>> = HashMap::new(); let mut branch: Vec<NodePtr> = Vec::new(); let mut group = rc_refcell_node(Group::new("")); let mut level: Option<u16> = None; let mut gid: Option<u32> = None; let mut group_path: Vec<String> = Vec::new(); let mut num_groups = 0; while num_groups < header_num_groups as usize {
105 let field_type = data.get(0..2).ok_or(KdbOpenError::UnexpectedEof).map(LittleEndian::read_u16)?;
107 let field_size = data.get(2..6).ok_or(KdbOpenError::UnexpectedEof).map(LittleEndian::read_u32)?;
108 let field_value = data.get(6..6 + field_size as usize).ok_or(KdbOpenError::UnexpectedEof)?;
109
110 match field_type {
111 0x0000 => {} 0x0001 => {
113 ensure_length(field_type, field_size, 4)?;
115 gid = Some(LittleEndian::read_u32(field_value));
116 }
117 0x0002 => group.borrow_mut().set_title(Some(&from_utf8(field_value))), 0x0003..=0x0006 => {
119 ensure_length(field_type, field_size, 5)?;
121 }
122 0x0007 | 0x0009 => {
123 ensure_length(field_type, field_size, 4)?;
125 }
126 0x0008 => {
127 ensure_length(field_type, field_size, 2)?;
129 level = Some(LittleEndian::read_u16(field_value));
130 }
131 0xffff => {
132 ensure_length(field_type, field_size, 0)?;
133
134 let level = level.ok_or(KdbOpenError::InvalidGroupLevel {
135 current: None,
136 expected: branch.len() as u16,
137 })? as usize;
138
139 if level < branch.len() {
142 group_path.truncate(level);
143 collapse_tail_groups(&mut branch, level, root).ok_or(KdbOpenError::IncompleteGroup)?;
144 }
145 if level == branch.len() {
146 group_path.push(group.borrow().get_title().unwrap_or("").to_string());
147 branch.push(group);
148 } else {
149 #[allow(clippy::cast_possible_truncation)]
151 return Err(KdbOpenError::InvalidGroupLevel {
152 current: Some(level as u16),
153 expected: branch.len() as u16,
154 });
155 }
156
157 let group_id = gid.ok_or(KdbOpenError::InvalidGroupId(None))?;
159 gid_map.insert(group_id, group_path.clone());
160 group = rc_refcell_node(Group::new(""));
161 gid = None;
162 num_groups += 1;
163 }
164 _ => {
165 return Err(KdbOpenError::InvalidGroupFieldType(field_type));
166 }
167 }
168
169 *data = data.get(6 + field_size as usize..).ok_or(KdbOpenError::UnexpectedEof)?;
170 }
171 if gid.is_some() {
172 return Err(KdbOpenError::IncompleteGroup);
173 }
174 collapse_tail_groups(&mut branch, 0, root).ok_or(KdbOpenError::IncompleteGroup)?;
176
177 Ok(gid_map)
178}
179
180fn parse_entries(root: &NodePtr, gid_map: &GidMap, header_num_entries: u32, data: &mut &[u8]) -> Result<(), KdbOpenError> {
181 let mut entry = Entry::default(); let mut gid: Option<u32> = None; let mut num_entries = 0;
185 while num_entries < header_num_entries {
186 let field_type = data.get(0..2).ok_or(KdbOpenError::UnexpectedEof).map(LittleEndian::read_u16)?;
188 let field_size = data.get(2..6).ok_or(KdbOpenError::UnexpectedEof).map(LittleEndian::read_u32)?;
189 let field_value = data.get(6..6 + field_size as usize).ok_or(KdbOpenError::UnexpectedEof)?;
190
191 match field_type {
192 0x0000 => {} 0x0001 => {
194 ensure_length(field_type, field_size, 16)?;
196 }
197 0x0002 => {
198 ensure_length(field_type, field_size, 4)?;
200 gid = Some(LittleEndian::read_u32(field_value));
201 }
202 0x0003 => {
203 ensure_length(field_type, field_size, 4)?;
205 }
206 0x0004 | 0x0005 | 0x0006 | 0x0008 | 0x000d => {
207 entry.set_unprotected_field_pair(entry_name(field_type), Some(&from_utf8(field_value)));
209 }
210 0x0007 => {
211 entry.set_protected_field_pair("Password", Some(field_value));
213 }
214 0x0009..=0x000c => {
215 ensure_length(field_type, field_size, 5)?;
217 }
218 0x000e => {
219 entry.set_binary_field_pair("BinaryData", Some(field_value));
221 }
222 0xffff => {
223 ensure_length(field_type, field_size, 0)?;
224
225 let group_id = gid.ok_or(KdbOpenError::EntryMissingGroupId)?;
226 let group_path: Vec<&str> = gid_map
227 .get(&group_id)
228 .ok_or(KdbOpenError::InvalidGroupId(Some(group_id)))?
229 .iter()
230 .map(std::string::String::as_str)
231 .collect();
232
233 let group = Group::get(root, group_path.as_slice()).ok_or(KdbOpenError::IncompleteGroup)?;
234 with_node_mut::<Group, _, _>(&group, |group| {
235 let count = group.get_children().len();
236 group.add_child(rc_refcell_node(entry), count);
237 Ok::<(), KdbOpenError>(())
238 })
239 .ok_or(KdbOpenError::IncompleteGroup)??;
240
241 entry = Entry::default();
242 gid = None;
243 num_entries += 1;
244 }
245 _ => {
246 return Err(KdbOpenError::InvalidEntryFieldType(field_type));
247 }
248 }
249
250 *data = data.get(6 + field_size as usize..).ok_or(KdbOpenError::UnexpectedEof)?;
251 }
252 if gid.is_some() {
253 return Err(KdbOpenError::IncompleteEntry);
254 }
255
256 Ok(())
257}
258
259fn parse_db(header: &KDBHeader, data: &[u8]) -> Result<NodePtr, KdbOpenError> {
260 let root = rc_refcell_node(Group::new("Root"));
261
262 let mut pos = data;
263
264 let gid_map = parse_groups(&root, header.num_groups, &mut pos)?;
265
266 parse_entries(&root, &gid_map, header.num_entries, &mut pos)?;
267
268 Ok(root)
269}
270
271pub(crate) fn parse_kdb(data: &[u8], db_key: &DatabaseKey) -> Result<Database, DatabaseOpenError> {
272 let header = parse_header(data)?;
273 #[allow(clippy::cast_possible_truncation)]
274 let version = DatabaseVersion::KDB(header.subversion as u16);
275
276 let payload_encrypted = &data[HEADER_SIZE..];
278
279 let key_elements = db_key.get_key_elements()?;
281 let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
282 let composite_key = if key_elements.len() == 1 {
283 let key_element: [u8; 32] = key_elements[0].try_into().unwrap();
284 Array::from(key_element) } else {
286 calculate_sha256(&key_elements) };
288
289 let kdf_config = KdfConfig::Aes {
291 rounds: u64::from(header.transform_rounds),
292 };
293
294 let transformed_key = kdf_config.get_kdf_seeded(&header.transform_seed).transform_key(&composite_key)?;
295
296 let master_key = calculate_sha256(&[&header.master_seed, transformed_key.as_slice()]);
297
298 let outer_cipher_config = if header.flags & 2 != 0 {
299 OuterCipherConfig::AES256
300 } else if header.flags & 8 != 0 {
301 OuterCipherConfig::Twofish
302 } else {
303 return Err(DatabaseIntegrityError::from(KdbOpenError::InvalidFixedCipherID(header.flags)).into());
304 };
305
306 let payload_padded = outer_cipher_config
308 .get_cipher(master_key.as_slice(), header.encryption_iv.as_ref())?
309 .decrypt(payload_encrypted)?;
310 let padlen = payload_padded[payload_padded.len() - 1] as usize;
311 let payload = &payload_padded[..payload_padded.len() - padlen];
312
313 let hash = calculate_sha256(&[payload]);
315 if header.contents_hash != hash.as_slice() {
316 return Err(DatabaseKeyError::IncorrectKey.into());
317 }
318
319 let root_group = parse_db(&header, payload).map_err(DatabaseIntegrityError::from)?;
320
321 let config = DatabaseConfig {
322 version,
323 outer_cipher_config,
324 compression_config: CompressionConfig::None,
325 inner_cipher_config: InnerCipherConfig::Plain,
326 kdf_config,
327 public_custom_data: Default::default(),
328 };
329
330 Ok(Database {
331 config,
332 root: root_group.into(),
333 deleted_objects: Default::default(),
334 meta: Meta::new(),
335 })
336}
337
338#[derive(Debug, thiserror::Error)]
340pub enum KdbOpenError {
341 #[error("Unexpected end of file while reading KDB data")]
342 UnexpectedEof,
343
344 #[error("Field of type {field_type} has invalid length {field_size}, expected {expected_field_size}")]
345 InvalidFieldLength {
346 field_type: u16,
347 field_size: u32,
348 expected_field_size: u32,
349 },
350
351 #[error("Invalid group level: got {current:?}, expected {expected}")]
352 InvalidGroupLevel { current: Option<u16>, expected: u16 },
353
354 #[error("Invalid group ID: {0:?}")]
355 InvalidGroupId(Option<u32>),
356
357 #[error("Invalid group field type: {0}")]
358 InvalidGroupFieldType(u16),
359
360 #[error("Group was not terminated before end of file")]
361 IncompleteGroup,
362
363 #[error("Entry is missing group ID")]
364 EntryMissingGroupId,
365
366 #[error("Invalid entry field type: {0}")]
367 InvalidEntryFieldType(u16),
368
369 #[error("Entry was not terminated before end of file")]
370 IncompleteEntry,
371
372 #[error("Invalid fixed cipher ID: {0}")]
373 InvalidFixedCipherID(u32),
374}