1use crate::{
2 config::{CompressionConfig, DatabaseConfig, InnerCipherConfig, KdfConfig, OuterCipherConfig},
3 crypt::{calculate_sha256, ciphers::Cipher},
4 db::{Database, rc_refcell_node},
5 error::{DatabaseIntegrityError, DatabaseKeyError, DatabaseOpenError},
6 format::{DatabaseVersion, DatabaseVersionParseError, kdbx_header_field_id::KDBXHeaderFieldID},
7 key::DatabaseKey,
8};
9use byteorder::{ByteOrder, LittleEndian};
10use std::convert::TryFrom;
11
12#[derive(Debug)]
13struct KDBX3Header {
14 outer_cipher: OuterCipherConfig,
16 compression: CompressionConfig,
17 master_seed: Vec<u8>,
18
19 transform_seed: Vec<u8>,
20 kdf_config: KdfConfig,
21
22 encryption_iv: Vec<u8>,
23 protected_stream_key: Vec<u8>,
24 stream_start: Vec<u8>,
25 inner_random_stream_id: InnerCipherConfig,
26 body_start: usize,
27}
28
29fn parse_outer_header(data: &[u8]) -> Result<KDBX3Header, Kdbx3OuterHeaderError> {
30 let mut outer_cipher: Option<OuterCipherConfig> = None;
31 let mut compression: Option<CompressionConfig> = None;
32 let mut master_seed: Option<Vec<u8>> = None;
33 let mut transform_seed: Option<Vec<u8>> = None;
34 let mut transform_rounds: Option<u64> = None;
35 let mut encryption_iv: Option<Vec<u8>> = None;
36 let mut inner_random_stream_key: Option<Vec<u8>> = None;
37 let mut stream_start: Option<Vec<u8>> = None;
38 let mut inner_random_stream_id: Option<InnerCipherConfig> = None;
39
40 let mut pos = DatabaseVersion::get_version_header_size();
42
43 loop {
45 let field_id: KDBXHeaderFieldID = data
56 .get(pos)
57 .ok_or(Kdbx3OuterHeaderError::IncompleteOuterHeader("Header field"))
58 .and_then(|field_id| {
59 KDBXHeaderFieldID::try_from(field_id).map_err(|_| Kdbx3OuterHeaderError::InvalidOuterHeaderEntry(*field_id))
60 })?;
61
62 let field_length = data
63 .get((pos + 1)..(pos + 3))
64 .ok_or(Kdbx3OuterHeaderError::IncompleteOuterHeader("Field length"))
65 .map(LittleEndian::read_u16)? as usize;
66
67 let field_buffer = data
68 .get((pos + 3)..(pos + 3 + field_length))
69 .ok_or(Kdbx3OuterHeaderError::IncompleteOuterHeader("Field value"))?;
70
71 pos += 3 + field_length;
72
73 match field_id {
74 KDBXHeaderFieldID::EndOfHeader => {
76 break;
77 }
78
79 KDBXHeaderFieldID::Comment => {}
81
82 KDBXHeaderFieldID::CipherID => {
85 outer_cipher = Some(OuterCipherConfig::try_from(field_buffer)?);
86 }
87
88 KDBXHeaderFieldID::CompressionFlags => {
90 compression = Some(CompressionConfig::try_from(LittleEndian::read_u32(field_buffer))?);
91 }
92
93 KDBXHeaderFieldID::MasterSeed => master_seed = Some(field_buffer.to_vec()),
95
96 KDBXHeaderFieldID::TransformSeed => transform_seed = Some(field_buffer.to_vec()),
98
99 KDBXHeaderFieldID::TransformRounds => transform_rounds = Some(LittleEndian::read_u64(field_buffer)),
101
102 KDBXHeaderFieldID::EncryptionIV => encryption_iv = Some(field_buffer.to_vec()),
104
105 KDBXHeaderFieldID::InnerRandomStreamKey => inner_random_stream_key = Some(field_buffer.to_vec()),
107
108 KDBXHeaderFieldID::StreamStartBytes => stream_start = Some(field_buffer.to_vec()),
110
111 KDBXHeaderFieldID::InnerRandomStreamID => {
114 inner_random_stream_id = Some(InnerCipherConfig::try_from(LittleEndian::read_u32(field_buffer))?);
115 }
116
117 _ => {
118 return Err(Kdbx3OuterHeaderError::InvalidOuterHeaderEntry(field_id.into()));
119 }
120 };
121 }
122
123 fn get_or_err<T>(v: Option<T>, err: &'static str) -> Result<T, Kdbx3OuterHeaderError> {
127 v.ok_or(Kdbx3OuterHeaderError::IncompleteOuterHeader(err))
128 }
129
130 let outer_cipher = get_or_err(outer_cipher, "Outer Cipher ID")?;
131 let compression = get_or_err(compression, "Compression ID")?;
132 let master_seed = get_or_err(master_seed, "Master seed")?;
133 let transform_seed = get_or_err(transform_seed, "Transform seed")?;
134 let transform_rounds = get_or_err(transform_rounds, "Number of transformation rounds")?;
135 let encryption_iv = get_or_err(encryption_iv, "Outer cipher IV")?;
136 let protected_stream_key = get_or_err(inner_random_stream_key, "Protected stream key")?;
137 let stream_start = get_or_err(stream_start, "Stream start bytes")?;
138 let inner_random_stream_id = get_or_err(inner_random_stream_id, "Inner cipher ID")?;
139
140 let kdf_config = KdfConfig::Aes { rounds: transform_rounds };
142
143 Ok(KDBX3Header {
144 outer_cipher,
145 compression,
146 master_seed,
147 transform_seed,
148 kdf_config,
149 encryption_iv,
150 protected_stream_key,
151 stream_start,
152 inner_random_stream_id,
153 body_start: pos,
154 })
155}
156
157#[derive(Debug, thiserror::Error)]
158pub enum Kdbx3OuterHeaderError {
159 #[error(transparent)]
160 InnerCipher(#[from] crate::config::InnerCipherConfigError),
161
162 #[error(transparent)]
163 OuterCipher(#[from] crate::config::OuterCipherConfigError),
164
165 #[error(transparent)]
166 Compression(#[from] crate::config::CompressionConfigError),
167
168 #[error("Encountered invalid outer header entry with type {0}")]
169 InvalidOuterHeaderEntry(u8),
170
171 #[error("Outer header is missing {0}")]
172 IncompleteOuterHeader(&'static str),
173}
174
175pub(crate) fn parse_kdbx3(data: &[u8], db_key: &DatabaseKey) -> Result<Database, DatabaseOpenError> {
177 let (config, mut inner_decryptor, xml) = decrypt_kdbx3(data, db_key)?;
178
179 let database_content = crate::format::xml_db::parse_xml_bytes(&xml, &[], &mut *inner_decryptor)
181 .map_err(Kdbx3OpenError::from)
182 .map_err(DatabaseIntegrityError::from)?;
183
184 let db = Database {
185 config,
186 root: rc_refcell_node(database_content.root_group).into(),
187 deleted_objects: database_content.deleted_objects,
188 meta: database_content.meta,
189 };
190
191 Ok(db)
192}
193
194#[allow(clippy::type_complexity)]
196pub(crate) fn decrypt_kdbx3(data: &[u8], db_key: &DatabaseKey) -> Result<(DatabaseConfig, Box<dyn Cipher>, Vec<u8>), DatabaseOpenError> {
197 let version = DatabaseVersion::parse(data)?;
198 let header = parse_outer_header(data)
199 .map_err(Kdbx3OpenError::from)
200 .map_err(DatabaseIntegrityError::from)?;
201
202 let inner_decryptor = header.inner_random_stream_id.get_cipher(&header.protected_stream_key)?;
203
204 let config = DatabaseConfig {
205 version,
206 outer_cipher_config: header.outer_cipher,
207 compression_config: header.compression,
208 inner_cipher_config: header.inner_random_stream_id,
209 kdf_config: header.kdf_config,
210 public_custom_data: Default::default(),
211 };
212
213 let mut pos = header.body_start;
214
215 let compression = config.compression_config.get_compression();
217
218 let payload_encrypted = data.get(pos..).ok_or_else(|| DatabaseIntegrityError::IncompleteOuterHeader {
220 missing_field: "Payload".into(),
221 })?;
222
223 let key_elements = db_key.get_key_elements()?;
225 let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
226 let composite_key = calculate_sha256(&key_elements);
227
228 let transformed_key = config
230 .kdf_config
231 .get_kdf_seeded(&header.transform_seed)
232 .transform_key(&composite_key)?;
233
234 let master_key = calculate_sha256(&[header.master_seed.as_ref(), transformed_key.as_slice()]);
235
236 let payload = config
238 .outer_cipher_config
239 .get_cipher(master_key.as_slice(), header.encryption_iv.as_ref())?
240 .decrypt(payload_encrypted)?;
241
242 let stream_start = payload.get(0..header.stream_start.len()).ok_or(DatabaseKeyError::IncorrectKey)?;
244 if stream_start != header.stream_start.as_slice() {
245 return Err(DatabaseKeyError::IncorrectKey.into());
246 }
247
248 let mut buf = Vec::new();
249
250 pos = 32;
251 let mut block_index = 0;
252 loop {
253 let block_hash = &payload[(pos + 4)..(pos + 36)];
266 let block_size = LittleEndian::read_u32(&payload[(pos + 36)..(pos + 40)]) as usize;
267
268 if block_size == 0 {
270 break;
271 }
272
273 let block_buffer_compressed = &payload[(pos + 40)..(pos + 40 + block_size)];
274
275 let block_hash_check = calculate_sha256(&[block_buffer_compressed]);
277 if block_hash != block_hash_check.as_slice() {
278 return Err(DatabaseIntegrityError::from(Kdbx3OpenError::BlockHashMismatch(block_index)).into());
279 }
280
281 buf.append(&mut block_buffer_compressed.to_vec());
283
284 pos += 40 + block_size;
285 block_index += 1;
286 }
287
288 let xml = compression.decompress(&buf)?;
289
290 Ok((config, inner_decryptor, xml))
291}
292
293#[derive(Debug, thiserror::Error)]
294pub enum Kdbx3OpenError {
295 #[error(transparent)]
296 Version(#[from] DatabaseVersionParseError),
297
298 #[error(transparent)]
299 OuterHeader(#[from] Kdbx3OuterHeaderError),
300
301 #[error("block hash mismatch at block index {0}")]
302 BlockHashMismatch(usize),
303
304 #[error(transparent)]
305 Xml(#[from] crate::format::xml_db::parse::XmlParseError),
306}