Skip to main content

keepass_ng/
config.rs

1//! Configuration options for how to compress and encrypt databases
2use hex_literal::hex;
3
4use std::convert::TryFrom;
5
6pub use crate::format::DatabaseVersion;
7
8#[cfg(feature = "save_kdbx4")]
9use crate::crypt::ciphers::Cipher;
10
11use crate::{
12    compression,
13    crypt::{ciphers, kdf},
14    error::{CompressionConfigError, CryptographyError, InnerCipherConfigError, KdfConfigError, OuterCipherConfigError},
15    format::{KDBX4_CURRENT_MINOR_VERSION, variant_dictionary::VariantDictionary},
16};
17
18const _CIPHERSUITE_AES128: [u8; 16] = hex!("61ab05a1946441c38d743a563df8dd35");
19const CIPHERSUITE_AES256: [u8; 16] = hex!("31c1f2e6bf714350be5805216afc5aff");
20const CIPHERSUITE_TWOFISH: [u8; 16] = hex!("ad68f29f576f4bb9a36ad47af965346c");
21const CIPHERSUITE_CHACHA20: [u8; 16] = hex!("d6038a2b8b6f4cb5a524339a31dbb59a");
22
23// Internal IDs for the ciphers
24const PLAIN: u32 = 0;
25const SALSA_20: u32 = 2;
26const CHA_CHA_20: u32 = 3;
27
28/// Configuration of how a database should be stored
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
31pub struct DatabaseConfig {
32    /// Version of the outer database file
33    pub version: DatabaseVersion,
34
35    /// What encryption to use for the outer encryption
36    pub outer_cipher_config: OuterCipherConfig,
37
38    /// What algorithm to use to compress the inner data
39    pub compression_config: CompressionConfig,
40
41    /// What encryption to use for protected fields inside the database
42    pub inner_cipher_config: InnerCipherConfig,
43
44    /// Settings for the Key Derivation Function (KDF)
45    pub kdf_config: KdfConfig,
46
47    /// Custom data of plugins/ports.
48    pub public_custom_data: Option<VariantDictionary>,
49}
50
51/// Sensible default configuration for new databases
52impl Default for DatabaseConfig {
53    fn default() -> Self {
54        Self {
55            version: DatabaseVersion::KDB4(KDBX4_CURRENT_MINOR_VERSION),
56            outer_cipher_config: OuterCipherConfig::AES256,
57            compression_config: CompressionConfig::GZip,
58            inner_cipher_config: InnerCipherConfig::ChaCha20,
59            kdf_config: KdfConfig::Argon2 {
60                iterations: 50,
61                memory: 1024 * 1024,
62                parallelism: 4,
63                version: argon2::Version::Version13,
64            },
65            public_custom_data: None,
66        }
67    }
68}
69
70/// Choices for outer encryption
71#[derive(Debug, Clone, PartialEq, Eq)]
72#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
73pub enum OuterCipherConfig {
74    AES256,
75    Twofish,
76    ChaCha20,
77}
78
79impl OuterCipherConfig {
80    pub(crate) fn get_cipher(&self, key: &[u8], iv: &[u8]) -> Result<Box<dyn ciphers::Cipher>, CryptographyError> {
81        match self {
82            OuterCipherConfig::AES256 => Ok(Box::new(ciphers::AES256Cipher::new(key, iv))),
83            OuterCipherConfig::Twofish => Ok(Box::new(ciphers::TwofishCipher::new(key, iv))),
84            OuterCipherConfig::ChaCha20 => Ok(Box::new(ciphers::ChaCha20Cipher::new_key_iv(key, iv)?)),
85        }
86    }
87
88    #[cfg(feature = "save_kdbx4")]
89    pub(crate) fn get_iv_size(&self) -> usize {
90        match self {
91            OuterCipherConfig::AES256 => ciphers::AES256Cipher::iv_size(),
92            OuterCipherConfig::Twofish => ciphers::TwofishCipher::iv_size(),
93            OuterCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::iv_size(),
94        }
95    }
96
97    #[cfg(feature = "save_kdbx4")]
98    pub(crate) fn dump(&self) -> [u8; 16] {
99        match self {
100            OuterCipherConfig::AES256 => CIPHERSUITE_AES256,
101            OuterCipherConfig::Twofish => CIPHERSUITE_TWOFISH,
102            OuterCipherConfig::ChaCha20 => CIPHERSUITE_CHACHA20,
103        }
104    }
105}
106
107impl TryFrom<&[u8]> for OuterCipherConfig {
108    type Error = OuterCipherConfigError;
109    fn try_from(v: &[u8]) -> Result<OuterCipherConfig, Self::Error> {
110        if v == CIPHERSUITE_AES256 {
111            Ok(OuterCipherConfig::AES256)
112        } else if v == CIPHERSUITE_TWOFISH {
113            Ok(OuterCipherConfig::Twofish)
114        } else if v == CIPHERSUITE_CHACHA20 {
115            Ok(OuterCipherConfig::ChaCha20)
116        } else {
117            Err(OuterCipherConfigError::InvalidOuterCipherID { cid: v.to_vec() })
118        }
119    }
120}
121
122/// Choices for encrypting protected values inside of databases
123#[derive(Debug, Clone, PartialEq, Eq)]
124#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
125pub enum InnerCipherConfig {
126    Plain,
127    Salsa20,
128    ChaCha20,
129}
130
131impl InnerCipherConfig {
132    pub(crate) fn get_cipher(&self, key: &[u8]) -> Box<dyn ciphers::Cipher> {
133        match self {
134            InnerCipherConfig::Plain => Box::new(ciphers::PlainCipher::new(key)),
135            InnerCipherConfig::Salsa20 => Box::new(ciphers::Salsa20Cipher::new(key)),
136            InnerCipherConfig::ChaCha20 => Box::new(ciphers::ChaCha20Cipher::new(key)),
137        }
138    }
139
140    #[cfg(feature = "save_kdbx4")]
141    pub(crate) fn dump(&self) -> u32 {
142        match self {
143            InnerCipherConfig::Plain => PLAIN,
144            InnerCipherConfig::Salsa20 => SALSA_20,
145            InnerCipherConfig::ChaCha20 => CHA_CHA_20,
146        }
147    }
148
149    #[cfg(feature = "save_kdbx4")]
150    pub(crate) fn get_key_size(&self) -> usize {
151        match self {
152            InnerCipherConfig::Plain => ciphers::PlainCipher::key_size(),
153            InnerCipherConfig::Salsa20 => ciphers::Salsa20Cipher::key_size(),
154            InnerCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::key_size(),
155        }
156    }
157}
158
159impl TryFrom<u32> for InnerCipherConfig {
160    type Error = InnerCipherConfigError;
161
162    fn try_from(v: u32) -> Result<InnerCipherConfig, Self::Error> {
163        match v {
164            PLAIN => Ok(InnerCipherConfig::Plain),
165            SALSA_20 => Ok(InnerCipherConfig::Salsa20),
166            CHA_CHA_20 => Ok(InnerCipherConfig::ChaCha20),
167            _ => Err(InnerCipherConfigError::InvalidInnerCipherID { cid: v }),
168        }
169    }
170}
171
172// Name of the KDF fields in the variant dictionaries.
173const KDF_ID: &str = "$UUID";
174// KDF fields used by Argon2.
175const KDF_MEMORY: &str = "M";
176const KDF_SALT: &str = "S";
177const KDF_ITERATIONS: &str = "I";
178const KDF_PARALLELISM: &str = "P";
179const KDF_VERSION: &str = "V";
180// KDF fields used by AES.
181const KDF_SEED: &str = "S";
182const KDF_ROUNDS: &str = "R";
183
184/// Choices for Key Derivation Functions (KDFs)
185#[derive(Debug, Clone, PartialEq, Eq)]
186#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
187pub enum KdfConfig {
188    /// Derive keys with repeated AES encryption
189    Aes { rounds: u64 },
190    /// Derive keys with Argon2d
191    Argon2 {
192        iterations: u64,
193        memory: u64,
194        parallelism: u32,
195
196        #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
197        version: argon2::Version,
198    },
199    /// Derive keys with Argon2id
200    Argon2id {
201        iterations: u64,
202        memory: u64,
203        parallelism: u32,
204
205        #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
206        version: argon2::Version,
207    },
208}
209
210#[cfg(feature = "serialization")]
211fn serialize_argon2_version<S: serde::Serializer>(
212    version: &argon2::Version,
213    serializer: S,
214) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error> {
215    serializer.serialize_u32(version.as_u32())
216}
217
218impl KdfConfig {
219    #[cfg(feature = "save_kdbx4")]
220    fn seed_size(&self) -> usize {
221        match self {
222            KdfConfig::Aes { .. } => 32,
223            KdfConfig::Argon2 { .. } => 32,
224            KdfConfig::Argon2id { .. } => 32,
225        }
226    }
227
228    /// For writing out a database, generate a new KDF seed from the config and return the KDF
229    /// and the generated seed
230    #[cfg(feature = "save_kdbx4")]
231    pub(crate) fn get_kdf_and_seed(&self) -> Result<(Box<dyn kdf::Kdf>, Vec<u8>), getrandom::Error> {
232        let mut kdf_seed = vec![0; self.seed_size()];
233        getrandom::fill(&mut kdf_seed)?;
234
235        let kdf = self.get_kdf_seeded(&kdf_seed);
236
237        Ok((kdf, kdf_seed))
238    }
239
240    /// For reading a database, generate a KDF from the KDF config and a provided seed
241    pub(crate) fn get_kdf_seeded(&self, seed: &[u8]) -> Box<dyn kdf::Kdf> {
242        match self {
243            KdfConfig::Aes { rounds } => Box::new(kdf::AesKdf {
244                seed: seed.to_vec(),
245                rounds: *rounds,
246            }),
247            KdfConfig::Argon2 {
248                memory,
249                iterations,
250                parallelism,
251                version,
252            } => Box::new(kdf::Argon2Kdf {
253                memory: *memory,
254                salt: seed.to_vec(),
255                iterations: *iterations,
256                parallelism: *parallelism,
257                version: *version,
258                variant: argon2::Variant::Argon2d,
259            }),
260            KdfConfig::Argon2id {
261                memory,
262                iterations,
263                parallelism,
264                version,
265            } => Box::new(kdf::Argon2Kdf {
266                memory: *memory,
267                salt: seed.to_vec(),
268                iterations: *iterations,
269                parallelism: *parallelism,
270                version: *version,
271                variant: argon2::Variant::Argon2id,
272            }),
273        }
274    }
275
276    #[cfg(feature = "save_kdbx4")]
277    pub(crate) fn to_variant_dictionary(&self, seed: &[u8]) -> VariantDictionary {
278        let mut vd = VariantDictionary::new();
279
280        match self {
281            KdfConfig::Aes { rounds } => {
282                vd.set(KDF_ID, KDF_AES_KDBX4.to_vec());
283                vd.set(KDF_ROUNDS, *rounds);
284                vd.set(KDF_SEED, seed.to_vec());
285            }
286            KdfConfig::Argon2 {
287                memory,
288                iterations,
289                parallelism,
290                version,
291            } => {
292                vd.set(KDF_ID, KDF_ARGON2.to_vec());
293                vd.set(KDF_MEMORY, *memory);
294                vd.set(KDF_SALT, seed.to_vec());
295                vd.set(KDF_ITERATIONS, *iterations);
296                vd.set(KDF_PARALLELISM, *parallelism);
297                vd.set(KDF_VERSION, version.as_u32());
298            }
299            KdfConfig::Argon2id {
300                memory,
301                iterations,
302                parallelism,
303                version,
304            } => {
305                vd.set(KDF_ID, KDF_ARGON2ID.to_vec());
306                vd.set(KDF_MEMORY, *memory);
307                vd.set(KDF_SALT, seed.to_vec());
308                vd.set(KDF_ITERATIONS, *iterations);
309                vd.set(KDF_PARALLELISM, *parallelism);
310                vd.set(KDF_VERSION, version.as_u32());
311            }
312        }
313
314        vd
315    }
316}
317
318const KDF_AES_KDBX3: [u8; 16] = hex!("c9d9f39a628a4460bf740d08c18a4fea");
319const KDF_AES_KDBX4: [u8; 16] = hex!("7c02bb8279a74ac0927d114a00648238");
320const KDF_ARGON2: [u8; 16] = hex!("ef636ddf8c29444b91f7a9a403e30a0c");
321const KDF_ARGON2ID: [u8; 16] = hex!("9e298b1956db4773b23dfc3ec6f0a1e6");
322
323impl TryFrom<VariantDictionary> for (KdfConfig, Vec<u8>) {
324    type Error = KdfConfigError;
325
326    fn try_from(vd: VariantDictionary) -> Result<(KdfConfig, Vec<u8>), Self::Error> {
327        let uuid = vd.get::<Vec<u8>>(KDF_ID)?;
328
329        if uuid == &KDF_ARGON2ID {
330            let memory: u64 = *vd.get(KDF_MEMORY)?;
331            let salt: Vec<u8> = vd.get::<Vec<u8>>(KDF_SALT)?.clone();
332            let iterations: u64 = *vd.get(KDF_ITERATIONS)?;
333            let parallelism: u32 = *vd.get(KDF_PARALLELISM)?;
334            let version: u32 = *vd.get(KDF_VERSION)?;
335
336            let version = match version {
337                0x10 => argon2::Version::Version10,
338                0x13 => argon2::Version::Version13,
339                _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
340            };
341
342            Ok((
343                KdfConfig::Argon2id {
344                    memory,
345                    iterations,
346                    parallelism,
347                    version,
348                },
349                salt,
350            ))
351        } else if uuid == &KDF_ARGON2 {
352            let memory: u64 = *vd.get(KDF_MEMORY)?;
353            let salt: Vec<u8> = vd.get::<Vec<u8>>(KDF_SALT)?.clone();
354            let iterations: u64 = *vd.get(KDF_ITERATIONS)?;
355            let parallelism: u32 = *vd.get(KDF_PARALLELISM)?;
356            let version: u32 = *vd.get(KDF_VERSION)?;
357
358            let version = match version {
359                0x10 => argon2::Version::Version10,
360                0x13 => argon2::Version::Version13,
361                _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
362            };
363
364            Ok((
365                KdfConfig::Argon2 {
366                    memory,
367                    iterations,
368                    parallelism,
369                    version,
370                },
371                salt,
372            ))
373        } else if uuid == &KDF_AES_KDBX4 || uuid == &KDF_AES_KDBX3 {
374            let rounds: u64 = *vd.get(KDF_ROUNDS)?;
375            let seed: Vec<u8> = vd.get::<Vec<u8>>(KDF_SEED)?.clone();
376
377            Ok((KdfConfig::Aes { rounds }, seed))
378        } else {
379            Err(KdfConfigError::InvalidKDFUUID { uuid: uuid.clone() })
380        }
381    }
382}
383
384/// Choices of compression algorithm
385#[derive(Debug, Clone, PartialEq, Eq)]
386#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
387pub enum CompressionConfig {
388    None,
389    GZip,
390}
391
392impl CompressionConfig {
393    pub(crate) fn get_compression(&self) -> Box<dyn compression::Compression> {
394        match self {
395            CompressionConfig::None => Box::new(compression::NoCompression),
396            CompressionConfig::GZip => Box::new(compression::GZipCompression),
397        }
398    }
399
400    #[cfg(feature = "save_kdbx4")]
401    pub(crate) fn dump(&self) -> [u8; 4] {
402        match self {
403            CompressionConfig::None => [0, 0, 0, 0],
404            CompressionConfig::GZip => [1, 0, 0, 0],
405        }
406    }
407}
408
409impl TryFrom<u32> for CompressionConfig {
410    type Error = CompressionConfigError;
411
412    fn try_from(v: u32) -> Result<CompressionConfig, Self::Error> {
413        match v {
414            0 => Ok(CompressionConfig::None),
415            1 => Ok(CompressionConfig::GZip),
416            _ => Err(CompressionConfigError::InvalidCompressionSuite { cid: v }),
417        }
418    }
419}