Skip to main content

kdbx_rs/binary/
kdbx.rs

1use super::{errors, header};
2use crate::{crypto, database, stream};
3use std::io::{Read, Write};
4use std::ops::{Deref, DerefMut};
5
6pub trait KdbxState: std::fmt::Debug {
7    fn header(&self) -> &header::KdbxHeader;
8    fn header_mut(&mut self) -> &mut header::KdbxHeader;
9    fn major_version(&self) -> u16;
10    fn minor_version(&self) -> u16;
11    fn write<W: Write>(&self, output: W) -> Result<(), errors::WriteError>;
12}
13
14#[derive(Debug)]
15/// A KeePass 2 archive wrapping a password database
16///
17/// Most methods are available on a specific state like `Kdbx<Locked>`
18/// or `Kdbx<Unlocked>`.
19///
20/// A keepass 2 archive can be obtained in one of two ways. You may read
21/// an existing archive using [`kdbx_rs::open`][crate::open] or
22/// [`kdbx_rs::from_reader`][crate::from_reader].
23///
24/// You can also create a password database using [`Database`][crate::Database],
25/// then turn it into a KeePass 2 archive using [`Kdbx::from_database`].
26pub struct Kdbx<S>
27where
28    S: KdbxState,
29{
30    pub(super) state: S,
31}
32
33impl<T: KdbxState> Kdbx<T> {
34    /// Encryption configuration and unencrypted custom data
35    pub fn header(&self) -> &header::KdbxHeader {
36        self.state.header()
37    }
38
39    /// Mutable encryption configuration and unencrypted custom data
40    pub fn header_mut(&mut self) -> &mut header::KdbxHeader {
41        self.state.header_mut()
42    }
43
44    /// Major archive version
45    pub fn major_version(&self) -> u16 {
46        self.state.major_version()
47    }
48
49    /// Major archive version
50    pub fn minor_version(&self) -> u16 {
51        self.state.minor_version()
52    }
53
54    /// Write this archive to the given output stream
55    pub fn write<W: Write>(&self, output: W) -> Result<(), errors::WriteError> {
56        self.state.write(output)?;
57        Ok(())
58    }
59}
60
61/// Represents a failed attempt at unlocking a database
62///
63/// Includes the locked database and the reason the unlock failed.
64/// This allows you to keep the database for interactive user and
65/// e.g. promt the user for a new password if the error is key related
66///
67/// However, for unscripted use, `FailedUnlock` implements
68/// `Into<[kdbx_rs::Error]>` and `Into<[kdbx_rs::errors::UnlockError]>`
69/// for easy use with the `?` operatior.
70pub struct FailedUnlock(pub Kdbx<Locked>, pub errors::UnlockError);
71
72impl From<FailedUnlock> for errors::UnlockError {
73    fn from(funlock: FailedUnlock) -> errors::UnlockError {
74        funlock.1
75    }
76}
77
78#[derive(Debug)]
79/// An unlocked kdbx file, allowing access to stored credentials
80pub struct Unlocked {
81    /// Header data of the kdbx archive, includes unencrypted metadata
82    pub(crate) header: header::KdbxHeader,
83    /// Inner header data that is stored encrypted, not present on kdbx3
84    pub(crate) inner_header: header::KdbxInnerHeader,
85    /// Major version of the database file format
86    pub(crate) major_version: u16,
87    /// Minor version of the database file format
88    pub(crate) minor_version: u16,
89    /// Master key used to derive other keys
90    pub(crate) composed_key: Option<crypto::ComposedKey>,
91    /// Master key used to derive other keys
92    pub(crate) master_key: Option<crypto::MasterKey>,
93    /// Unencrypted unparsed XML data
94    pub(crate) xml_data: Option<Vec<u8>>,
95    /// Actual password database data
96    pub(crate) database: crate::Database,
97}
98
99impl Unlocked {
100    fn encrypt_inner(&self, key: &crypto::MasterKey) -> Result<Vec<u8>, super::errors::WriteError> {
101        let mut encrypted_buf = Vec::new();
102        let mut encrypted_stream = crate::stream::kdbx4_write_stream(
103            &mut encrypted_buf,
104            key.hmac_key(&self.header.master_seed),
105            key.cipher_key(&self.header.master_seed),
106            self.header.cipher,
107            &self.header.encryption_iv,
108            self.header.compression_type,
109        )?;
110        self.inner_header.write(&mut encrypted_stream)?;
111        let mut stream_cipher = self
112            .inner_header
113            .inner_stream_cipher
114            .stream_cipher(&self.inner_header.inner_stream_key)?;
115        crate::xml::write_xml(
116            &mut encrypted_stream,
117            &self.database,
118            stream_cipher.as_mut(),
119        )?;
120
121        encrypted_stream.finish()?;
122        Ok(encrypted_buf)
123    }
124}
125
126impl KdbxState for Unlocked {
127    fn header(&self) -> &header::KdbxHeader {
128        &self.header
129    }
130
131    fn header_mut(&mut self) -> &mut header::KdbxHeader {
132        &mut self.header
133    }
134
135    fn major_version(&self) -> u16 {
136        self.major_version
137    }
138
139    fn minor_version(&self) -> u16 {
140        self.minor_version
141    }
142
143    fn write<W: Write>(&self, mut output: W) -> Result<(), errors::WriteError> {
144        let master_key = self
145            .master_key
146            .as_ref()
147            .ok_or(errors::WriteError::MissingKeys)?;
148        let mut header_buf = Vec::new();
149        let header_writer = &mut header_buf as &mut dyn Write;
150        header_writer.write_all(&super::KEEPASS_MAGIC_NUMBER.to_le_bytes())?;
151        header_writer.write_all(&super::KDBX_MAGIC_NUMBER.to_le_bytes())?;
152        header_writer.write_all(&self.minor_version.to_le_bytes())?;
153        header_writer.write_all(&self.major_version.to_le_bytes())?;
154        self.header.write(&mut header_buf)?;
155        output.write_all(&header_buf)?;
156        output.write_all(&crypto::sha256(&header_buf))?;
157        let hmac_key = master_key.hmac_key(&self.header.master_seed);
158        let hmac = hmac_key
159            .block_key(u64::MAX)
160            .calculate_header_hmac(&header_buf)
161            .map_err(|_| errors::WriteError::MissingKeys)?;
162        output.write_all(&hmac.into_bytes())?;
163        let encrypted_xml = self.encrypt_inner(master_key)?;
164        output.write_all(&encrypted_xml)?;
165        Ok(())
166    }
167}
168
169impl Kdbx<Unlocked> {
170    /// Encrypted binaries and database options
171    pub fn inner_header(&self) -> &header::KdbxInnerHeader {
172        &self.state.inner_header
173    }
174
175    /// Mutable encrypted binaries and database options
176    pub fn inner_header_mut(&mut self) -> &mut header::KdbxInnerHeader {
177        &mut self.state.inner_header
178    }
179
180    /// Use the given composite key to encrypt the database
181    pub fn set_key(
182        &mut self,
183        key: crypto::CompositeKey,
184    ) -> Result<(), crate::errors::KeyGenerationError> {
185        self.state.composed_key = Some(key.composed());
186        let composed_key = self.state.composed_key.as_ref().unwrap();
187        self.state.master_key = Some(composed_key.master_key(&self.header().kdf_params)?);
188
189        Ok(())
190    }
191
192    /// Raw parsed XML data to handle fields not supported by this plugin
193    ///
194    /// Only present from databases loaded from existing sources
195    pub fn raw_xml(&self) -> Option<&[u8]> {
196        self.state.xml_data.as_deref()
197    }
198
199    /// Password database stored in this kdbx archive
200    pub fn database(&self) -> &crate::Database {
201        &self.state.database
202    }
203
204    /// Mutable password database stored in this kdbx archive
205    pub fn database_mut(&mut self) -> &mut crate::Database {
206        &mut self.state.database
207    }
208
209    /// Generate a new .kdbx from the given database
210    ///
211    /// Uses OS randomness provided by the `rand` crates's [`OsRng`] to
212    /// generate all required seeds and IVs.
213    ///
214    /// Note that you need to set a key with [`Kdbx::set_key`]
215    /// to be able to write the database
216    ///
217    /// [`OsRng`]: https://docs.rs/rand/0.7/rand/rngs/struct.OsRng.html
218    pub fn from_database(database: crate::Database) -> Kdbx<Unlocked> {
219        let header = header::KdbxHeader::from_os_random();
220        let inner_header = header::KdbxInnerHeader::from_os_random();
221        let unlocked = Unlocked {
222            header,
223            inner_header,
224            major_version: 4,
225            minor_version: 0,
226            xml_data: None,
227            composed_key: None,
228            master_key: None,
229            database,
230        };
231        Kdbx { state: unlocked }
232    }
233}
234
235impl Deref for Kdbx<Unlocked> {
236    type Target = database::Database;
237
238    fn deref(&self) -> &database::Database {
239        &self.state.database
240    }
241}
242
243impl DerefMut for Kdbx<Unlocked> {
244    fn deref_mut(&mut self) -> &mut database::Database {
245        &mut self.state.database
246    }
247}
248
249#[derive(Debug, PartialEq, Eq)]
250/// A locked kdbx file, use unlock(composite_key) to unlock
251pub struct Locked {
252    /// Header data of the kdbx archive, includes unencrypted metadata
253    pub(crate) header: header::KdbxHeader,
254    /// Raw bytes of header data, useful for checksums
255    pub(crate) header_data: Vec<u8>,
256    /// Major version of the database file format
257    pub(crate) major_version: u16,
258    /// Minor version of the database file format
259    pub(crate) minor_version: u16,
260    /// hmac code to verify keys and header integrity
261    pub(crate) hmac: Option<Vec<u8>>,
262    /// Encrypted vault data
263    pub(crate) encrypted_data: Vec<u8>,
264}
265
266impl KdbxState for Locked {
267    fn header(&self) -> &header::KdbxHeader {
268        &self.header
269    }
270
271    fn header_mut(&mut self) -> &mut header::KdbxHeader {
272        &mut self.header
273    }
274
275    fn major_version(&self) -> u16 {
276        self.major_version
277    }
278
279    fn minor_version(&self) -> u16 {
280        self.minor_version
281    }
282
283    fn write<W: Write>(&self, mut output: W) -> Result<(), errors::WriteError> {
284        let mut header_buf = Vec::new();
285        let header_writer = &mut header_buf as &mut dyn Write;
286        header_writer.write_all(&super::KEEPASS_MAGIC_NUMBER.to_le_bytes())?;
287        header_writer.write_all(&super::KDBX_MAGIC_NUMBER.to_le_bytes())?;
288        header_writer.write_all(&self.minor_version.to_le_bytes())?;
289        header_writer.write_all(&self.major_version.to_le_bytes())?;
290        self.header.write(&mut header_buf)?;
291        output.write_all(&header_buf)?;
292        if self.major_version >= 4 {
293            output.write_all(&crypto::sha256(&header_buf))?;
294            output.write_all(self.hmac.as_ref().unwrap())?;
295        }
296        output.write_all(&self.encrypted_data)?;
297        Ok(())
298    }
299}
300
301impl Kdbx<Locked> {
302    fn decrypt_v4(
303        &self,
304        master_key: &crypto::MasterKey,
305    ) -> Result<(header::KdbxInnerHeader, Vec<u8>), errors::UnlockError> {
306        let hmac_key = master_key.hmac_key(&self.state.header.master_seed);
307        let cipher_key = master_key.cipher_key(&self.state.header.master_seed);
308        let mut input_stream = stream::kdbx4_read_stream(
309            &*self.state.encrypted_data,
310            hmac_key,
311            cipher_key,
312            self.state.header.cipher,
313            &self.state.header.encryption_iv,
314            self.state.header.compression_type,
315        )?;
316        let inner_header =
317            header::KdbxInnerHeader::read(&mut input_stream, self.state.major_version)?;
318        let mut output_buffer = Vec::new();
319        input_stream.read_to_end(&mut output_buffer)?;
320        Ok((inner_header, output_buffer))
321    }
322
323    /// Unlocks the kdbx file
324    ///
325    /// If unlock fails, returns the locked kdbx file along with the error
326    #[allow(clippy::result_large_err)]
327    pub fn unlock(self, key: &crypto::CompositeKey) -> Result<Kdbx<Unlocked>, FailedUnlock> {
328        if self.state.major_version >= 4 {
329            self.unlock_v4(key)
330        } else {
331            self.unlock_v3(key)
332        }
333    }
334
335    fn decrypt_v3(
336        &self,
337        master_key: &crypto::MasterKey,
338    ) -> Result<(header::KdbxInnerHeader, Vec<u8>), errors::UnlockError> {
339        let cipher_key = master_key.cipher_key(&self.state.header.master_seed);
340        let mut input_stream = stream::kdbx3_read_stream(
341            &*self.state.encrypted_data,
342            cipher_key,
343            self.state.header.cipher,
344            &self.state.header.encryption_iv,
345            self.state.header.compression_type,
346            self.header().stream_start_bytes.as_ref().unwrap(),
347        )?;
348        let inner_header = header::KdbxInnerHeader::from_legacy_fields(&self.state.header)?;
349        let mut output_buffer = Vec::new();
350        input_stream.read_to_end(&mut output_buffer)?;
351        Ok((inner_header, output_buffer))
352    }
353
354    #[allow(clippy::result_large_err)]
355    fn unlock_v3(self, key: &crypto::CompositeKey) -> Result<Kdbx<Unlocked>, FailedUnlock> {
356        let composed_key = key.composed();
357        let master_key = match composed_key.master_key(&self.header().kdf_params) {
358            Ok(master_key) => master_key,
359            Err(e) => return Err(FailedUnlock(self, errors::UnlockError::from(e))),
360        };
361
362        let parsed = self
363            .decrypt_v3(&master_key)
364            .and_then(|(inner_header, data)| {
365                let mut stream_cipher = inner_header
366                    .inner_stream_cipher
367                    .stream_cipher(inner_header.inner_stream_key.as_ref())?;
368                let parsed = crate::xml::parse_xml(data.as_slice(), stream_cipher.as_mut())?;
369                Ok((inner_header, data, parsed))
370            });
371        match parsed {
372            Ok((inner_header, data, db)) => Ok(Kdbx {
373                state: Unlocked {
374                    inner_header,
375                    header: self.state.header,
376                    major_version: self.state.major_version,
377                    minor_version: self.state.minor_version,
378                    composed_key: Some(composed_key),
379                    master_key: Some(master_key),
380                    database: db,
381                    xml_data: Some(data),
382                },
383            }),
384            Err(e) => Err(FailedUnlock(self, e)),
385        }
386    }
387
388    #[allow(clippy::result_large_err)]
389    fn unlock_v4(self, key: &crypto::CompositeKey) -> Result<Kdbx<Unlocked>, FailedUnlock> {
390        let composed_key = key.composed();
391        let master_key = match composed_key.master_key(&self.header().kdf_params) {
392            Ok(master_key) => master_key,
393            Err(e) => return Err(FailedUnlock(self, errors::UnlockError::from(e))),
394        };
395        let hmac_key = master_key.hmac_key(&self.state.header.master_seed);
396        let header_block_key = hmac_key.block_key(u64::MAX);
397
398        let hmac = self.state.hmac.clone().unwrap();
399
400        if header_block_key.verify_header_block(hmac.as_ref(), &self.state.header_data) {
401            let parsed = self
402                .decrypt_v4(&master_key)
403                .and_then(|(inner_header, data)| {
404                    let mut stream_cipher = inner_header
405                        .inner_stream_cipher
406                        .stream_cipher(inner_header.inner_stream_key.as_ref())?;
407                    let parsed = crate::xml::parse_xml(data.as_slice(), stream_cipher.as_mut())?;
408                    Ok((inner_header, data, parsed))
409                });
410
411            match parsed {
412                Ok((inner_header, data, db)) => Ok(Kdbx {
413                    state: Unlocked {
414                        inner_header,
415                        header: self.state.header,
416                        major_version: self.state.major_version,
417                        minor_version: self.state.minor_version,
418                        composed_key: Some(composed_key),
419                        master_key: Some(master_key),
420                        database: db,
421                        xml_data: Some(data),
422                    },
423                }),
424                Err(e) => Err(FailedUnlock(self, e)),
425            }
426        } else {
427            Err(FailedUnlock(self, errors::UnlockError::HmacInvalid))
428        }
429    }
430}