kdbx_rs/binary/
header.rs

1use super::errors::HeaderError as Error;
2use super::header_fields;
3use super::variant_dict;
4use crate::crypto;
5use crate::utils;
6use rand::{rngs::OsRng, RngCore};
7use sha2::{Digest, Sha256};
8use std::convert::TryInto;
9use std::io::{Read, Write};
10use std::marker::PhantomData;
11use uuid::Uuid;
12
13type Result<T> = std::result::Result<T, Error>;
14
15pub trait HeaderId: From<u8> + Into<u8> {
16    fn is_final(&self) -> bool;
17}
18
19#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
20/// Field types for unencrypted header
21pub enum OuterHeaderId {
22    /// Last header field
23    EndOfHeader,
24    /// Custom comment to describe the database
25    Comment,
26    /// UUID indicating the cipher for the database
27    CipherId,
28    /// Compression algorithm in use
29    CompressionFlags,
30    /// Seed to make database keys unique
31    MasterSeed,
32    /// KDBX3 only - Seed used for converting passwords to keys
33    LegacyTransformSeed,
34    /// KDBX3 only - Number of rounds of aes256 to use to generate keys
35    LegacyTransformRounds,
36    /// Initial value for encrypting/decrypting the stream
37    EncryptionIv,
38    /// KDBX3 only - Key used for decrypting inner streams
39    ProtectedStreamKey,
40    /// KDBX3 only - First 32 bytes of decrypted data, newer databases have a HMAC
41    StreamStartBytes,
42    /// KDBX3 only - Cipher identifer for data encrypted in memory
43    InnerRandomStreamId,
44    /// KDBX4 only - Parameters used to convert credentials to keys
45    KdfParameters,
46    /// KDBX4 only - Unencrypted custom data for plugins
47    PublicCustomData,
48    /// Some header field not supported by this library
49    Unknown(u8),
50}
51
52impl From<u8> for OuterHeaderId {
53    fn from(id: u8) -> OuterHeaderId {
54        match id {
55            0 => OuterHeaderId::EndOfHeader,
56            0x1 => OuterHeaderId::Comment,
57            0x2 => OuterHeaderId::CipherId,
58            0x3 => OuterHeaderId::CompressionFlags,
59            0x4 => OuterHeaderId::MasterSeed,
60            0x5 => OuterHeaderId::LegacyTransformSeed,
61            0x6 => OuterHeaderId::LegacyTransformRounds,
62            0x7 => OuterHeaderId::EncryptionIv,
63            0x8 => OuterHeaderId::ProtectedStreamKey,
64            0x9 => OuterHeaderId::StreamStartBytes,
65            0xA => OuterHeaderId::InnerRandomStreamId,
66            0xB => OuterHeaderId::KdfParameters,
67            0xC => OuterHeaderId::PublicCustomData,
68            x => OuterHeaderId::Unknown(x),
69        }
70    }
71}
72
73impl From<OuterHeaderId> for u8 {
74    fn from(id: OuterHeaderId) -> u8 {
75        match id {
76            OuterHeaderId::EndOfHeader => 0,
77            OuterHeaderId::Comment => 0x1,
78            OuterHeaderId::CipherId => 0x2,
79            OuterHeaderId::CompressionFlags => 0x3,
80            OuterHeaderId::MasterSeed => 0x4,
81            OuterHeaderId::LegacyTransformSeed => 0x5,
82            OuterHeaderId::LegacyTransformRounds => 0x6,
83            OuterHeaderId::EncryptionIv => 0x7,
84            OuterHeaderId::ProtectedStreamKey => 0x8,
85            OuterHeaderId::StreamStartBytes => 0x9,
86            OuterHeaderId::InnerRandomStreamId => 0xA,
87            OuterHeaderId::KdfParameters => 0xB,
88            OuterHeaderId::PublicCustomData => 0xC,
89            OuterHeaderId::Unknown(x) => x,
90        }
91    }
92}
93
94impl HeaderId for OuterHeaderId {
95    fn is_final(&self) -> bool {
96        *self == OuterHeaderId::EndOfHeader
97    }
98}
99
100#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
101/// Field types for encrypted inner header
102pub enum InnerHeaderId {
103    /// Last field in the header
104    EndOfHeader,
105    /// Cipher identifier for data encrypted in memory
106    InnerRandomStreamCipherId,
107    /// Cipher key for data encrypted in memory
108    InnerRandomStreamKey,
109    /// Binary data in the header
110    Binary,
111    /// Header unknown to this library
112    Unknown(u8),
113}
114
115impl From<u8> for InnerHeaderId {
116    fn from(id: u8) -> InnerHeaderId {
117        match id {
118            0 => InnerHeaderId::EndOfHeader,
119            1 => InnerHeaderId::InnerRandomStreamCipherId,
120            2 => InnerHeaderId::InnerRandomStreamKey,
121            3 => InnerHeaderId::Binary,
122            x => InnerHeaderId::Unknown(x),
123        }
124    }
125}
126
127impl From<InnerHeaderId> for u8 {
128    fn from(id: InnerHeaderId) -> u8 {
129        match id {
130            InnerHeaderId::EndOfHeader => 0,
131            InnerHeaderId::InnerRandomStreamCipherId => 1,
132            InnerHeaderId::InnerRandomStreamKey => 2,
133            InnerHeaderId::Binary => 3,
134            InnerHeaderId::Unknown(x) => x,
135        }
136    }
137}
138
139impl HeaderId for InnerHeaderId {
140    fn is_final(&self) -> bool {
141        *self == InnerHeaderId::EndOfHeader
142    }
143}
144
145#[derive(Clone, Debug, PartialEq, Eq)]
146pub struct HeaderField<T> {
147    ty: T,
148    data: Vec<u8>,
149}
150
151impl<T> HeaderField<T> {
152    pub(crate) fn new(ty: T, data: Vec<u8>) -> HeaderField<T> {
153        HeaderField { ty, data }
154    }
155}
156pub struct HeaderParser<'a, R: Read + 'a, T: HeaderId> {
157    _id: PhantomData<T>,
158    reader: &'a mut R,
159}
160
161impl<'a, R, T> HeaderParser<'a, R, T>
162where
163    R: Read + 'a,
164    T: HeaderId,
165{
166    pub(crate) fn new(reader: &'a mut R) -> HeaderParser<'a, R, T> {
167        HeaderParser {
168            _id: PhantomData,
169            reader,
170        }
171    }
172
173    pub(crate) fn read_one_header(&mut self, major_version: u16) -> Result<HeaderField<T>> {
174        let mut ty_buffer = [0u8];
175        self.reader.read_exact(&mut ty_buffer)?;
176        let ty = T::from(ty_buffer[0]);
177
178        let len = if major_version >= 4 {
179            let mut len_buffer = [0u8; 4];
180            self.reader.read_exact(&mut len_buffer)?;
181            u32::from_le_bytes(len_buffer)
182        } else {
183            let mut len_buffer = [0u8; 2];
184            self.reader.read_exact(&mut len_buffer)?;
185            u16::from_le_bytes(len_buffer) as u32
186        };
187        let mut header_buffer = utils::buffer(len as usize);
188        self.reader.read_exact(&mut header_buffer)?;
189
190        Ok(HeaderField {
191            ty,
192            data: header_buffer,
193        })
194    }
195
196    pub(crate) fn read_all_headers(&mut self, major_version: u16) -> Result<Vec<HeaderField<T>>> {
197        let mut headers = Vec::new();
198        let mut header = self.read_one_header(major_version)?;
199        while !header.ty.is_final() {
200            headers.push(header);
201            header = self.read_one_header(major_version)?;
202        }
203
204        Ok(headers)
205    }
206}
207
208#[derive(Default)]
209pub struct KdbxHeaderBuilder {
210    pub cipher: Option<header_fields::Cipher>,
211    pub kdf_params: Option<header_fields::KdfParams>,
212    pub compression_type: Option<header_fields::CompressionType>,
213    pub stream_start_bytes: Option<Vec<u8>>,
214    pub other_headers: Vec<HeaderField<OuterHeaderId>>,
215    pub master_seed: Option<Vec<u8>>,
216    pub encryption_iv: Option<Vec<u8>>,
217}
218
219impl KdbxHeaderBuilder {
220    fn add_header(&mut self, header: HeaderField<OuterHeaderId>) -> Result<()> {
221        match header.ty {
222            OuterHeaderId::CipherId => {
223                let cipher = Uuid::from_slice(&header.data)
224                    .map(From::from)
225                    .map_err(|_e| {
226                        Error::MalformedField(header.ty, "Cipher UUID not valid".into())
227                    })?;
228
229                self.cipher = Some(cipher);
230            }
231            OuterHeaderId::StreamStartBytes => {
232                self.stream_start_bytes = Some(header.data);
233            }
234            OuterHeaderId::KdfParameters => {
235                self.kdf_params = match variant_dict::parse_variant_dict(&*header.data) {
236                    Ok(vdict) => Some(vdict.try_into()?),
237                    Err(e) => {
238                        println!("Malformed field: {}", e);
239                        return Err(Error::MalformedField(
240                            OuterHeaderId::KdfParameters,
241                            "Corrupt variant dictionary".into(),
242                        ));
243                    }
244                };
245            }
246            OuterHeaderId::CompressionFlags => {
247                if header.data.len() != 4 {
248                    return Err(Error::MalformedField(
249                        OuterHeaderId::CompressionFlags,
250                        "Wrong size for compression ID".into(),
251                    ));
252                }
253                self.compression_type =
254                    Some(header_fields::CompressionType::from(u32::from_le_bytes([
255                        header.data[0],
256                        header.data[1],
257                        header.data[2],
258                        header.data[3],
259                    ])))
260            }
261            OuterHeaderId::EncryptionIv => self.encryption_iv = Some(header.data),
262            OuterHeaderId::MasterSeed => self.master_seed = Some(header.data),
263            _ => self.other_headers.push(header),
264        }
265
266        Ok(())
267    }
268
269    fn get_kdf_params(&mut self) -> Option<header_fields::KdfParams> {
270        if self.kdf_params.is_some() {
271            self.kdf_params.take()
272        } else {
273            let rounds = self
274                .other_headers
275                .iter()
276                .find(|h| h.ty == OuterHeaderId::LegacyTransformRounds)
277                .map(|h| {
278                    let mut buf = [0u8; 8];
279                    buf.clone_from_slice(&h.data[0..8]);
280                    u64::from_le_bytes(buf)
281                });
282            let seed = self
283                .other_headers
284                .iter()
285                .find(|h| h.ty == OuterHeaderId::LegacyTransformSeed)
286                .map(|h| h.data.clone());
287
288            match (rounds, seed) {
289                (Some(r), Some(s)) => Some(header_fields::KdfParams::Aes { rounds: r, salt: s }),
290                _ => None,
291            }
292        }
293    }
294
295    fn build(mut self) -> Result<KdbxHeader> {
296        let kdf_params = self.get_kdf_params();
297        Ok(KdbxHeader {
298            cipher: self
299                .cipher
300                .ok_or(Error::MissingRequiredField(OuterHeaderId::CipherId))?,
301            compression_type: self
302                .compression_type
303                .ok_or(Error::MissingRequiredField(OuterHeaderId::CompressionFlags))?,
304            master_seed: self
305                .master_seed
306                .ok_or(Error::MissingRequiredField(OuterHeaderId::MasterSeed))?,
307            encryption_iv: self
308                .encryption_iv
309                .ok_or(Error::MissingRequiredField(OuterHeaderId::EncryptionIv))?,
310            kdf_params: kdf_params
311                .ok_or(Error::MissingRequiredField(OuterHeaderId::KdfParameters))?,
312            stream_start_bytes: self.stream_start_bytes,
313            other_headers: self.other_headers,
314        })
315    }
316}
317
318#[derive(Debug, PartialEq, Eq)]
319/// Unencrypted database configuration and custom data
320///
321/// [`KdbxHeader::from_os_random()`] will provide a header with
322/// the default encryption settings and new random keys
323/// from the OS secure RNG
324pub struct KdbxHeader {
325    /// Encryption cipher used for decryption the database
326    pub cipher: header_fields::Cipher,
327    /// Options for converting credentials to crypto keys
328    pub kdf_params: header_fields::KdfParams,
329    /// Compression applied prior to encryption
330    pub compression_type: header_fields::CompressionType,
331    /// First 32 bytes, used to check kdbx3 archives
332    pub stream_start_bytes: Option<Vec<u8>>,
333    /// Custom and unrecognized header types
334    pub other_headers: Vec<HeaderField<OuterHeaderId>>,
335    /// Master seed used to make crypto keys DB specific
336    pub master_seed: Vec<u8>,
337    /// IV used for initializing crypto
338    pub encryption_iv: Vec<u8>,
339}
340
341impl KdbxHeader {
342    /// Create a new header to encrypt a database with keys from the OS Secure RNG.
343    ///
344    /// Under the hood this uses the [`rand`] crate to access the [`OsRng`],
345    /// the actual mechanism used to get random numbers is detailed in that
346    /// crate's documentation.
347    ///
348    /// The default encryption is currently to use AES256 as a stream cipher,
349    /// and Argon2d v19 with 64 MiB memory factor, and 10 iterations as the KDF.
350    /// This is subject to change in future crate versions
351    ///
352    /// [`rand`]: https://docs.rs/rand/
353    /// [`OsRng`]: https://docs.rs/rand/0.7/rand/rngs/struct.OsRng.html
354    pub fn from_os_random() -> KdbxHeader {
355        let mut master_seed = vec![0u8; 32];
356        let mut encryption_iv = vec![0u8; 16];
357        let mut cipher_salt = vec![0u8; 32];
358        OsRng.fill_bytes(&mut master_seed);
359        OsRng.fill_bytes(&mut encryption_iv);
360        OsRng.fill_bytes(&mut cipher_salt);
361        KdbxHeader {
362            cipher: header_fields::Cipher::Aes256,
363            kdf_params: header_fields::KdfParams::Argon2 {
364                variant: argon2::Variant::Argon2d,
365                iterations: 10,
366                memory_bytes: 0xFFFF * 1024,
367                salt: cipher_salt,
368                version: 19,
369                lanes: 2,
370            },
371            other_headers: Vec::new(),
372            compression_type: super::CompressionType::None,
373            stream_start_bytes: None,
374            master_seed,
375            encryption_iv,
376        }
377    }
378
379    pub(crate) fn read<R: Read>(
380        mut caching_reader: utils::CachingReader<R>,
381        major_version: u16,
382    ) -> Result<(KdbxHeader, Vec<u8>)> {
383        let mut header_builder = KdbxHeaderBuilder::default();
384        let headers = HeaderParser::new(&mut caching_reader).read_all_headers(major_version)?;
385        for header in headers {
386            header_builder.add_header(header)?;
387        }
388
389        let (header_bin, input) = caching_reader.into_inner();
390
391        if major_version < 4 {
392            return Ok((header_builder.build()?, header_bin));
393        }
394
395        let mut sha = utils::buffer(Sha256::output_size());
396        input.read_exact(&mut sha)?;
397
398        if crypto::verify_sha256(&header_bin, &sha) {
399            Ok((header_builder.build()?, header_bin))
400        } else {
401            Err(Error::ChecksumFailed)
402        }
403    }
404
405    pub(crate) fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
406        use std::iter::once;
407        let headers = self
408            .other_headers
409            .iter()
410            .cloned()
411            .chain(once(self.cipher.into()))
412            .chain(once(self.compression_type.into()))
413            .chain(once(HeaderField::new(
414                OuterHeaderId::MasterSeed,
415                self.master_seed.clone(),
416            )))
417            .chain(once(HeaderField::new(
418                OuterHeaderId::EncryptionIv,
419                self.encryption_iv.clone(),
420            )))
421            .chain(once(self.kdf_params.clone().into()))
422            .chain(once(HeaderField::new(
423                OuterHeaderId::EndOfHeader,
424                Vec::new(),
425            )));
426
427        for header in headers {
428            writer.write_all(&[header.ty.into()])?;
429            writer.write_all(&(header.data.len() as u32).to_le_bytes())?;
430            writer.write_all(&header.data)?;
431        }
432        Ok(())
433    }
434}
435
436#[derive(Default)]
437pub struct KdbxInnerHeaderBuilder {
438    pub inner_stream_cipher: Option<header_fields::InnerStreamCipherAlgorithm>,
439    pub inner_stream_key: Option<Vec<u8>>,
440    /// Custom and unrecognized header types
441    pub other_headers: Vec<HeaderField<InnerHeaderId>>,
442}
443
444impl KdbxInnerHeaderBuilder {
445    fn add_header(&mut self, header: HeaderField<InnerHeaderId>) -> Result<()> {
446        match header.ty {
447            InnerHeaderId::InnerRandomStreamCipherId => {
448                let d = header.data;
449                self.inner_stream_cipher =
450                    Some(u32::from_le_bytes([d[0], d[1], d[2], d[3]]).into());
451            }
452            InnerHeaderId::InnerRandomStreamKey => self.inner_stream_key = Some(header.data),
453            _ => self.other_headers.push(header),
454        }
455
456        Ok(())
457    }
458
459    fn build(self) -> Result<KdbxInnerHeader> {
460        Ok(KdbxInnerHeader {
461            inner_stream_cipher: self.inner_stream_cipher.ok_or(
462                Error::MissingRequiredInnerField(InnerHeaderId::InnerRandomStreamCipherId),
463            )?,
464            inner_stream_key: self
465                .inner_stream_key
466                .ok_or(Error::MissingRequiredInnerField(
467                    InnerHeaderId::InnerRandomStreamKey,
468                ))?,
469            other_headers: self.other_headers,
470        })
471    }
472}
473
474/// Encrypted database information and custom data
475#[derive(Debug, PartialEq, Eq)]
476pub struct KdbxInnerHeader {
477    /// Cipher identifier for data encrypted in memory
478    pub inner_stream_cipher: header_fields::InnerStreamCipherAlgorithm,
479    /// Cipher key for data encrypted in memory
480    pub inner_stream_key: Vec<u8>,
481    /// Headers not handled by this library
482    pub other_headers: Vec<HeaderField<InnerHeaderId>>,
483}
484
485impl KdbxInnerHeader {
486    pub(crate) fn from_legacy_fields(header: &KdbxHeader) -> Result<KdbxInnerHeader> {
487        let cipher = &header
488            .other_headers
489            .iter()
490            .find(|h| h.ty == OuterHeaderId::InnerRandomStreamId)
491            .ok_or(Error::MissingRequiredField(
492                OuterHeaderId::InnerRandomStreamId,
493            ))?
494            .data;
495        let key = header
496            .other_headers
497            .iter()
498            .find(|h| h.ty == OuterHeaderId::ProtectedStreamKey)
499            .ok_or(Error::MissingRequiredField(
500                OuterHeaderId::ProtectedStreamKey,
501            ))?
502            .data
503            .clone();
504        let mut cipher_id_buf = [0u8; 4];
505        cipher_id_buf.clone_from_slice(&cipher[0..4]);
506        let cipher_id = u32::from_le_bytes(cipher_id_buf);
507
508        Ok(KdbxInnerHeader {
509            inner_stream_cipher: cipher_id.into(),
510            inner_stream_key: key,
511            other_headers: Vec::new(),
512        })
513    }
514
515    /// Returns an inner header setup for a default stream cipher and OS random keys
516    ///
517    /// Currently the default stream cipher is ChaCha20
518    ///
519    /// Under the hood this uses the [`rand`] crate to access the [`OsRng`],
520    /// the actual mechanism used to get random numbers is detailed in that
521    /// crate's documentation.
522    ///
523    /// [`rand`]: https://docs.rs/rand/
524    /// [`OsRng`]: https://docs.rs/rand/0.7/rand/rngs/struct.OsRng.html
525    pub fn from_os_random() -> KdbxInnerHeader {
526        let inner_stream_cipher = header_fields::InnerStreamCipherAlgorithm::ChaCha20;
527        let mut inner_stream_key = vec![0u8; 44]; // 32 bit key + 12 bit nonce for chacha20
528        OsRng.fill_bytes(&mut inner_stream_key);
529
530        KdbxInnerHeader {
531            inner_stream_cipher,
532            inner_stream_key,
533            other_headers: Vec::new(),
534        }
535    }
536
537    pub(crate) fn read<R: Read>(reader: &mut R, major_version: u16) -> Result<KdbxInnerHeader> {
538        let mut header_builder = KdbxInnerHeaderBuilder::default();
539        let headers = HeaderParser::new(reader).read_all_headers(major_version)?;
540        for header in headers {
541            header_builder.add_header(header)?;
542        }
543
544        header_builder.build()
545    }
546
547    pub(crate) fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
548        use std::iter::once;
549        let headers = self
550            .other_headers
551            .iter()
552            .cloned()
553            .chain(once(self.inner_stream_cipher.into()))
554            .chain(once(HeaderField::new(
555                InnerHeaderId::InnerRandomStreamKey,
556                self.inner_stream_key.clone(),
557            )))
558            .chain(once(HeaderField::new(
559                InnerHeaderId::EndOfHeader,
560                Vec::new(),
561            )));
562
563        for header in headers {
564            writer.write_all(&[header.ty.into()])?;
565            writer.write_all(&(header.data.len() as i32).to_le_bytes())?;
566            writer.write_all(&header.data)?;
567        }
568        Ok(())
569    }
570}