kdbx_rs/binary/
header_fields.rs

1use super::errors::HeaderError;
2use super::header::{HeaderField, InnerHeaderId, OuterHeaderId};
3use super::variant_dict::{self, VariantDict};
4use crate::utils;
5use std::convert::{TryFrom, TryInto};
6use uuid::Uuid;
7
8pub const KEEPASS_MAGIC_NUMBER: u32 = 0x9AA2_D903;
9pub const KDBX_MAGIC_NUMBER: u32 = 0xB54B_FB67;
10
11const AES128_UUID: &str = "61ab05a1-9464-41c3-8d74-3a563df8dd35";
12const AES256_UUID: &str = "31c1f2e6-bf71-4350-be58-05216afc5aff";
13const TWOFISH_UUID: &str = "ad68f29f-576f-4bb9-a36a-d47af965346c";
14const CHACHA20_UUID: &str = "d6038a2b-8b6f-4cb5-a524-339a31dbb59a";
15const AES_3_1_UUID: &str = "c9d9f39a-628a-4460-bf74-0d08c18a4fea";
16const AES_4_UUID: &str = "7c02bb82-79a7-4ac0-927d-114a00648238";
17const ARGON2D_UUID: &str = "ef636ddf-8c29-444b-91f7-a9a403e30a0c";
18const ARGON2ID_UUID: &str = "9e298b19-56db-4773-b23d-fc3ec6f0a1e6";
19const COMPRESSION_TYPE_NONE: u32 = 0;
20const COMPRESSION_TYPE_GZIP: u32 = 1;
21
22#[derive(PartialEq, Eq, Debug, Copy, Clone)]
23/// Encryption cipher used for decryption the main database data
24pub enum Cipher {
25    /// AES 128 in CBC mode
26    Aes128,
27    /// AES 256 in CBC mode
28    Aes256,
29    /// TwoFish in CBC mode
30    TwoFish,
31    /// ChaCha20 in streaming mode
32    ChaCha20,
33    /// Cipher unknown to this library
34    Unknown(uuid::Uuid),
35}
36
37const CIPHER_TABLE: [(&str, Cipher); 4] = [
38    (AES128_UUID, Cipher::Aes128),
39    (AES256_UUID, Cipher::Aes256),
40    (TWOFISH_UUID, Cipher::TwoFish),
41    (CHACHA20_UUID, Cipher::ChaCha20),
42];
43
44impl From<uuid::Uuid> for Cipher {
45    fn from(uuid: uuid::Uuid) -> Cipher {
46        utils::value_from_uuid_table(&CIPHER_TABLE, uuid).unwrap_or(Cipher::Unknown(uuid))
47    }
48}
49
50impl From<Cipher> for uuid::Uuid {
51    fn from(cipher: Cipher) -> uuid::Uuid {
52        match cipher {
53            Cipher::Unknown(uuid) => uuid,
54            _ => utils::uuid_from_uuid_table(&CIPHER_TABLE, cipher).unwrap(),
55        }
56    }
57}
58
59impl From<Cipher> for HeaderField<OuterHeaderId> {
60    fn from(cipher: Cipher) -> HeaderField<OuterHeaderId> {
61        let uuid: uuid::Uuid = cipher.into();
62        HeaderField::new(OuterHeaderId::CipherId, uuid.as_bytes().to_vec())
63    }
64}
65
66/// Inner stream cipher identifier used for encrypting protected fields
67#[derive(Debug, PartialEq, Eq, Clone, Copy)]
68pub enum InnerStreamCipherAlgorithm {
69    /// ArcFour algorithm
70    ArcFour,
71    /// Salsa20 stream cipher
72    Salsa20,
73    /// ChaCha20 stream cipher
74    ChaCha20,
75    /// Unknown stream cipher
76    Unknown(u32),
77}
78
79impl From<InnerStreamCipherAlgorithm> for HeaderField<InnerHeaderId> {
80    fn from(cipher: InnerStreamCipherAlgorithm) -> HeaderField<InnerHeaderId> {
81        HeaderField::new(
82            InnerHeaderId::InnerRandomStreamCipherId,
83            u32::from(cipher).to_le_bytes().as_ref().to_vec(),
84        )
85    }
86}
87
88impl From<u32> for InnerStreamCipherAlgorithm {
89    fn from(id: u32) -> InnerStreamCipherAlgorithm {
90        match id {
91            1 => InnerStreamCipherAlgorithm::ArcFour,
92            2 => InnerStreamCipherAlgorithm::Salsa20,
93            3 => InnerStreamCipherAlgorithm::ChaCha20,
94            x => InnerStreamCipherAlgorithm::Unknown(x),
95        }
96    }
97}
98
99impl From<InnerStreamCipherAlgorithm> for u32 {
100    fn from(id: InnerStreamCipherAlgorithm) -> u32 {
101        match id {
102            InnerStreamCipherAlgorithm::ArcFour => 1,
103            InnerStreamCipherAlgorithm::Salsa20 => 2,
104            InnerStreamCipherAlgorithm::ChaCha20 => 3,
105            InnerStreamCipherAlgorithm::Unknown(x) => x,
106        }
107    }
108}
109
110#[derive(Debug, PartialEq, Eq, Clone, Copy)]
111#[allow(non_camel_case_types)]
112/// Algorithm used for converting from credentials to crypto keys
113pub enum KdfAlgorithm {
114    /// Argon2d KDF
115    Argon2d,
116    /// Argon2id KDF
117    Argon2id,
118    /// AES 256 as used in KDBX4+
119    Aes256_Kdbx4,
120    /// AES 256 as used in KDBX3.1
121    Aes256_Kdbx3_1,
122    /// Unknown key derivation function
123    Unknown(uuid::Uuid),
124}
125
126pub(crate) const KDF_TABLE: [(&str, KdfAlgorithm); 4] = [
127    (AES_3_1_UUID, KdfAlgorithm::Aes256_Kdbx3_1),
128    (AES_4_UUID, KdfAlgorithm::Aes256_Kdbx4),
129    (ARGON2D_UUID, KdfAlgorithm::Argon2d),
130    (ARGON2ID_UUID, KdfAlgorithm::Argon2id),
131];
132
133impl From<uuid::Uuid> for KdfAlgorithm {
134    fn from(uuid: uuid::Uuid) -> KdfAlgorithm {
135        utils::value_from_uuid_table(&KDF_TABLE, uuid).unwrap_or(KdfAlgorithm::Unknown(uuid))
136    }
137}
138
139impl From<KdfAlgorithm> for uuid::Uuid {
140    fn from(algo: KdfAlgorithm) -> uuid::Uuid {
141        match algo {
142            KdfAlgorithm::Unknown(uuid) => uuid,
143            _ => utils::uuid_from_uuid_table(&KDF_TABLE, algo).unwrap(),
144        }
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149/// Options for converting credentials to crypto keys
150pub enum KdfParams {
151    /// Argon 2 KDF
152    Argon2 {
153        /// Argon2 variant
154        variant: argon2::Variant,
155        /// Amount of memory to use for key gen
156        memory_bytes: u64,
157        /// Argon2 version used (this library supports v19/0x13)
158        version: u32,
159        /// Random seed data to use for the KDF
160        salt: Vec<u8>,
161        /// Number of parallel tasks to use
162        lanes: u32,
163        /// Passes of the KDF to use for key gen
164        iterations: u64,
165    },
166    /// AES256 KDF
167    Aes {
168        /// Rounds of AES to use for key generation
169        rounds: u64,
170        /// Random seed data to use for the KDF
171        salt: Vec<u8>,
172    },
173    /// Some KDF unknown to this library
174    Unknown {
175        /// UUID to identify the KDF used
176        uuid: uuid::Uuid,
177        /// Parameters to the KDF
178        params: variant_dict::VariantDict,
179    },
180}
181
182impl TryFrom<VariantDict> for KdfParams {
183    type Error = HeaderError;
184    fn try_from(mut vdict: VariantDict) -> Result<Self, HeaderError> {
185        let uuid_val = vdict.remove("$UUID").ok_or_else(|| {
186            HeaderError::MalformedField(
187                OuterHeaderId::KdfParameters,
188                "No UUID for kdf parameters".into(),
189            )
190        })?;
191        let uuid_array: Vec<u8> = uuid_val.try_into().map_err(|_| {
192            HeaderError::MalformedField(
193                OuterHeaderId::KdfParameters,
194                "KDF UUID not a byte array".into(),
195            )
196        })?;
197        let uuid = Uuid::from_slice(&uuid_array).map_err(|_| {
198            HeaderError::MalformedField(
199                OuterHeaderId::KdfParameters,
200                "KDF UUID not a valid UUID".into(),
201            )
202        })?;
203
204        let kdf_algorithm = KdfAlgorithm::from(uuid);
205
206        match kdf_algorithm {
207            KdfAlgorithm::Argon2d | KdfAlgorithm::Argon2id => {
208                let memory_bytes =
209                    KdfParams::opt_from_vdict("M", KdfAlgorithm::Argon2d, &mut vdict)?;
210                let version = KdfParams::opt_from_vdict("V", KdfAlgorithm::Argon2d, &mut vdict)?;
211                let salt = KdfParams::opt_from_vdict("S", KdfAlgorithm::Argon2d, &mut vdict)?;
212                let iterations = KdfParams::opt_from_vdict("I", KdfAlgorithm::Argon2d, &mut vdict)?;
213                let lanes = KdfParams::opt_from_vdict("P", KdfAlgorithm::Argon2d, &mut vdict)?;
214                Ok(KdfParams::Argon2 {
215                    variant: utils::argon2_algo_to_variant(kdf_algorithm),
216                    memory_bytes,
217                    version,
218                    salt,
219                    iterations,
220                    lanes,
221                })
222            }
223            KdfAlgorithm::Aes256_Kdbx3_1 | KdfAlgorithm::Aes256_Kdbx4 => {
224                let rounds =
225                    KdfParams::opt_from_vdict("R", KdfAlgorithm::Aes256_Kdbx4, &mut vdict)?;
226                let salt = KdfParams::opt_from_vdict("S", KdfAlgorithm::Aes256_Kdbx4, &mut vdict)?;
227                Ok(KdfParams::Aes { rounds, salt })
228            }
229            _ => Ok(KdfParams::Unknown {
230                uuid,
231                params: vdict,
232            }),
233        }
234    }
235}
236
237impl From<KdfParams> for VariantDict {
238    fn from(params: KdfParams) -> VariantDict {
239        let mut vdict = variant_dict::VariantDict::new();
240        match params {
241            KdfParams::Argon2 {
242                variant,
243                memory_bytes,
244                version,
245                salt,
246                lanes,
247                iterations,
248            } => {
249                vdict.insert(
250                    "$UUID".into(),
251                    variant_dict::Value::Array(
252                        uuid::Uuid::from(utils::argon2_variant_to_algo(variant))
253                            .as_bytes()
254                            .to_vec(),
255                    ),
256                );
257                vdict.insert("M".into(), variant_dict::Value::Uint64(memory_bytes));
258                vdict.insert("V".into(), variant_dict::Value::Uint32(version));
259                vdict.insert("S".into(), variant_dict::Value::Array(salt));
260                vdict.insert("I".into(), variant_dict::Value::Uint64(iterations));
261                vdict.insert("P".into(), variant_dict::Value::Uint32(lanes));
262            }
263            KdfParams::Aes { rounds, salt } => {
264                vdict.insert(
265                    "$UUID".into(),
266                    variant_dict::Value::Array(
267                        uuid::Uuid::from(KdfAlgorithm::Aes256_Kdbx4)
268                            .as_bytes()
269                            .to_vec(),
270                    ),
271                );
272                vdict.insert("R".into(), variant_dict::Value::Uint64(rounds));
273                vdict.insert("S".into(), variant_dict::Value::Array(salt));
274            }
275            KdfParams::Unknown { uuid, params } => {
276                vdict.insert(
277                    "$UUID".into(),
278                    variant_dict::Value::Array(uuid.as_bytes().to_vec()),
279                );
280                vdict.extend(params)
281            }
282        }
283        vdict
284    }
285}
286
287impl From<KdfParams> for HeaderField<OuterHeaderId> {
288    fn from(params: KdfParams) -> HeaderField<OuterHeaderId> {
289        let mut buf = Vec::new();
290        let vdict: VariantDict = params.into();
291        variant_dict::write_variant_dict(&mut buf, &vdict).unwrap();
292        HeaderField::new(OuterHeaderId::KdfParameters, buf)
293    }
294}
295
296impl KdfParams {
297    fn opt_from_vdict<T>(
298        key: &str,
299        algo: KdfAlgorithm,
300        vdict: &mut variant_dict::VariantDict,
301    ) -> Result<T, HeaderError>
302    where
303        T: TryFrom<variant_dict::Value>,
304    {
305        vdict
306            .remove(key)
307            .and_then(|val| val.try_into().ok())
308            .ok_or_else(|| HeaderError::MissingKdfParam(key.to_string(), algo))
309    }
310}
311
312#[derive(Debug, PartialEq, Eq, Clone, Copy)]
313/// Compression method used prior to encryption
314pub enum CompressionType {
315    /// The encrypted data is uncompressed
316    None,
317    /// The encrypted data uses gzip compression
318    Gzip,
319    /// The crypted data uses a compression method unsupported by this library
320    Unknown(u32),
321}
322
323impl From<CompressionType> for u32 {
324    fn from(compression_type: CompressionType) -> u32 {
325        match compression_type {
326            CompressionType::None => COMPRESSION_TYPE_NONE,
327            CompressionType::Gzip => COMPRESSION_TYPE_GZIP,
328            CompressionType::Unknown(val) => val,
329        }
330    }
331}
332
333impl From<u32> for CompressionType {
334    fn from(id: u32) -> CompressionType {
335        match id {
336            COMPRESSION_TYPE_NONE => CompressionType::None,
337            COMPRESSION_TYPE_GZIP => CompressionType::Gzip,
338            _ => CompressionType::Unknown(id),
339        }
340    }
341}
342
343impl From<CompressionType> for HeaderField<OuterHeaderId> {
344    fn from(compression_type: CompressionType) -> HeaderField<OuterHeaderId> {
345        let compression_type_id: u32 = compression_type.into();
346        HeaderField::new(
347            OuterHeaderId::CompressionFlags,
348            Vec::from(compression_type_id.to_le_bytes().as_ref()),
349        )
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    use uuid::Uuid;
358    #[test]
359    fn kdf_from_slice() {
360        let aes31 = Uuid::parse_str(AES_3_1_UUID).unwrap();
361        let aes4 = Uuid::parse_str(AES_4_UUID).unwrap();
362        let argon2 = Uuid::parse_str(ARGON2D_UUID).unwrap();
363        let invalid = Uuid::parse_str(AES128_UUID).unwrap();
364
365        assert_eq!(KdfAlgorithm::from(aes31), KdfAlgorithm::Aes256_Kdbx3_1);
366        assert_eq!(KdfAlgorithm::from(aes4), KdfAlgorithm::Aes256_Kdbx4);
367        assert_eq!(KdfAlgorithm::from(argon2), KdfAlgorithm::Argon2d);
368        assert_eq!(KdfAlgorithm::from(invalid), KdfAlgorithm::Unknown(invalid));
369    }
370}