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::{CryptographyError, VariantDictionaryError},
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
79/// Errors with the configuration of the outer encryption.
80#[derive(Debug, thiserror::Error)]
81pub enum OuterCipherConfigError {
82    #[error(transparent)]
83    Cryptography(#[from] CryptographyError),
84
85    #[error("Invalid outer cipher ID: {:?}", cid)]
86    InvalidOuterCipherID { cid: Vec<u8> },
87}
88
89impl OuterCipherConfig {
90    pub(crate) fn get_cipher(&self, key: &[u8], iv: &[u8]) -> Result<Box<dyn ciphers::Cipher>, CryptographyError> {
91        match self {
92            OuterCipherConfig::AES256 => Ok(Box::new(ciphers::AES256Cipher::new(key, iv))),
93            OuterCipherConfig::Twofish => Ok(Box::new(ciphers::TwofishCipher::new(key, iv))),
94            OuterCipherConfig::ChaCha20 => Ok(Box::new(ciphers::ChaCha20Cipher::new_key_iv(key, iv)?)),
95        }
96    }
97
98    #[cfg(feature = "save_kdbx4")]
99    pub(crate) fn get_iv_size(&self) -> usize {
100        match self {
101            OuterCipherConfig::AES256 => ciphers::AES256Cipher::iv_size(),
102            OuterCipherConfig::Twofish => ciphers::TwofishCipher::iv_size(),
103            OuterCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::iv_size(),
104        }
105    }
106
107    #[cfg(feature = "save_kdbx4")]
108    pub(crate) fn dump(&self) -> [u8; 16] {
109        match self {
110            OuterCipherConfig::AES256 => CIPHERSUITE_AES256,
111            OuterCipherConfig::Twofish => CIPHERSUITE_TWOFISH,
112            OuterCipherConfig::ChaCha20 => CIPHERSUITE_CHACHA20,
113        }
114    }
115}
116
117impl TryFrom<&[u8]> for OuterCipherConfig {
118    type Error = OuterCipherConfigError;
119    fn try_from(v: &[u8]) -> Result<OuterCipherConfig, Self::Error> {
120        if v == CIPHERSUITE_AES256 {
121            Ok(OuterCipherConfig::AES256)
122        } else if v == CIPHERSUITE_TWOFISH {
123            Ok(OuterCipherConfig::Twofish)
124        } else if v == CIPHERSUITE_CHACHA20 {
125            Ok(OuterCipherConfig::ChaCha20)
126        } else {
127            Err(OuterCipherConfigError::InvalidOuterCipherID { cid: v.to_vec() })
128        }
129    }
130}
131
132/// Choices for encrypting protected values inside of databases
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
135pub enum InnerCipherConfig {
136    Plain,
137    Salsa20,
138    ChaCha20,
139}
140
141/// Errors with the configuration of the inner encryption.
142#[derive(Debug, thiserror::Error)]
143pub enum InnerCipherConfigError {
144    #[error(transparent)]
145    Cryptography(#[from] CryptographyError),
146
147    #[error("Invalid inner cipher ID: {}", cid)]
148    InvalidInnerCipherID { cid: u32 },
149}
150
151impl InnerCipherConfig {
152    pub(crate) fn get_cipher(&self, protected_stream_key: &[u8]) -> Result<Box<dyn ciphers::Cipher>, CryptographyError> {
153        match self {
154            InnerCipherConfig::Plain => Ok(Box::new(ciphers::PlainCipher::new(protected_stream_key))),
155            InnerCipherConfig::Salsa20 => Ok(Box::new(ciphers::Salsa20Cipher::new(protected_stream_key))),
156            InnerCipherConfig::ChaCha20 => Ok(Box::new(ciphers::ChaCha20Cipher::new(protected_stream_key)?)),
157        }
158    }
159
160    #[cfg(feature = "save_kdbx4")]
161    pub(crate) fn dump(&self) -> u32 {
162        match self {
163            InnerCipherConfig::Plain => PLAIN,
164            InnerCipherConfig::Salsa20 => SALSA_20,
165            InnerCipherConfig::ChaCha20 => CHA_CHA_20,
166        }
167    }
168
169    #[cfg(feature = "save_kdbx4")]
170    pub(crate) fn get_key_size(&self) -> usize {
171        match self {
172            InnerCipherConfig::Plain => ciphers::PlainCipher::key_size(),
173            InnerCipherConfig::Salsa20 => ciphers::Salsa20Cipher::key_size(),
174            InnerCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::key_size(),
175        }
176    }
177}
178
179impl TryFrom<u32> for InnerCipherConfig {
180    type Error = InnerCipherConfigError;
181
182    fn try_from(v: u32) -> Result<InnerCipherConfig, Self::Error> {
183        match v {
184            PLAIN => Ok(InnerCipherConfig::Plain),
185            SALSA_20 => Ok(InnerCipherConfig::Salsa20),
186            CHA_CHA_20 => Ok(InnerCipherConfig::ChaCha20),
187            _ => Err(InnerCipherConfigError::InvalidInnerCipherID { cid: v }),
188        }
189    }
190}
191
192// Name of the KDF fields in the variant dictionaries.
193const KDF_ID: &str = "$UUID";
194// KDF fields used by Argon2.
195const KDF_MEMORY: &str = "M";
196const KDF_SALT: &str = "S";
197const KDF_ITERATIONS: &str = "I";
198const KDF_PARALLELISM: &str = "P";
199const KDF_VERSION: &str = "V";
200// KDF fields used by AES.
201const KDF_SEED: &str = "S";
202const KDF_ROUNDS: &str = "R";
203
204/// Choices for Key Derivation Functions (KDFs)
205#[derive(Debug, Clone, PartialEq, Eq)]
206#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
207pub enum KdfConfig {
208    /// Derive keys with repeated AES encryption
209    Aes { rounds: u64 },
210    /// Derive keys with Argon2d
211    Argon2 {
212        /// The number of iterations to perform when deriving keys
213        iterations: u64,
214
215        /// The amount of memory (in bytes) to use when deriving keys
216        ///
217        /// KDBX stores this value in bytes, while the underlying Argon2 implementation
218        /// expects KiB and converts internally when deriving keys.
219        memory: u64,
220
221        /// The degree of parallelism to use when deriving keys
222        parallelism: u32,
223
224        /// The version of the Argon2 algorithm to use when deriving keys
225        #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
226        version: argon2::Version,
227    },
228    /// Derive keys with Argon2id
229    Argon2id {
230        /// The number of iterations to perform when deriving keys
231        iterations: u64,
232
233        /// The amount of memory (in bytes) to use when deriving keys
234        ///
235        /// KDBX stores this value in bytes, while the underlying Argon2 implementation
236        /// expects KiB and converts internally when deriving keys.
237        memory: u64,
238
239        /// The degree of parallelism to use when deriving keys
240        parallelism: u32,
241
242        /// The version of the Argon2 algorithm to use when deriving keys
243        #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
244        version: argon2::Version,
245    },
246}
247
248/// Errors with the configuration of the key derivation function.
249#[derive(Debug, thiserror::Error)]
250pub enum KdfConfigError {
251    #[error("Invalid KDF version: {}", version)]
252    InvalidKDFVersion { version: u32 },
253
254    #[error("Invalid KDF UUID: {:?}", uuid)]
255    InvalidKDFUUID { uuid: Vec<u8> },
256
257    #[error(transparent)]
258    VariantDictionary(#[from] VariantDictionaryError),
259}
260
261#[cfg(feature = "serialization")]
262fn serialize_argon2_version<S: serde::Serializer>(
263    version: &argon2::Version,
264    serializer: S,
265) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error> {
266    serializer.serialize_u32(version.as_u32())
267}
268
269impl KdfConfig {
270    #[cfg(feature = "save_kdbx4")]
271    fn seed_size(&self) -> usize {
272        match self {
273            KdfConfig::Aes { .. } => 32,
274            KdfConfig::Argon2 { .. } => 32,
275            KdfConfig::Argon2id { .. } => 32,
276        }
277    }
278
279    /// For writing out a database, generate a new KDF seed from the config and return the KDF
280    /// and the generated seed
281    #[cfg(feature = "save_kdbx4")]
282    pub(crate) fn get_kdf_and_seed(&self) -> Result<(Box<dyn kdf::Kdf>, Vec<u8>), getrandom::Error> {
283        let mut kdf_seed = vec![0; self.seed_size()];
284        getrandom::fill(&mut kdf_seed)?;
285
286        let kdf = self.get_kdf_seeded(&kdf_seed);
287
288        Ok((kdf, kdf_seed))
289    }
290
291    /// For reading a database, generate a KDF from the KDF config and a provided seed
292    pub(crate) fn get_kdf_seeded(&self, seed: &[u8]) -> Box<dyn kdf::Kdf> {
293        match self {
294            KdfConfig::Aes { rounds } => Box::new(kdf::AesKdf {
295                seed: seed.to_vec(),
296                rounds: *rounds,
297            }),
298            KdfConfig::Argon2 {
299                memory,
300                iterations,
301                parallelism,
302                version,
303            } => Box::new(kdf::Argon2Kdf {
304                memory: *memory,
305                salt: seed.to_vec(),
306                iterations: *iterations,
307                parallelism: *parallelism,
308                version: *version,
309                variant: argon2::Variant::Argon2d,
310            }),
311            KdfConfig::Argon2id {
312                memory,
313                iterations,
314                parallelism,
315                version,
316            } => Box::new(kdf::Argon2Kdf {
317                memory: *memory,
318                salt: seed.to_vec(),
319                iterations: *iterations,
320                parallelism: *parallelism,
321                version: *version,
322                variant: argon2::Variant::Argon2id,
323            }),
324        }
325    }
326
327    #[cfg(feature = "save_kdbx4")]
328    pub(crate) fn to_variant_dictionary(&self, seed: &[u8]) -> VariantDictionary {
329        let mut vd = VariantDictionary::new();
330
331        match self {
332            KdfConfig::Aes { rounds } => {
333                // always use the KDBX3 AES KDF UUID for compatibility with other libraries
334                vd.set(KDF_ID, KDF_AES_KDBX3.to_vec());
335                vd.set(KDF_ROUNDS, *rounds);
336                vd.set(KDF_SEED, seed.to_vec());
337            }
338            KdfConfig::Argon2 {
339                memory,
340                iterations,
341                parallelism,
342                version,
343            } => {
344                vd.set(KDF_ID, KDF_ARGON2.to_vec());
345                vd.set(KDF_MEMORY, *memory);
346                vd.set(KDF_SALT, seed.to_vec());
347                vd.set(KDF_ITERATIONS, *iterations);
348                vd.set(KDF_PARALLELISM, *parallelism);
349                vd.set(KDF_VERSION, version.as_u32());
350            }
351            KdfConfig::Argon2id {
352                memory,
353                iterations,
354                parallelism,
355                version,
356            } => {
357                vd.set(KDF_ID, KDF_ARGON2ID.to_vec());
358                vd.set(KDF_MEMORY, *memory);
359                vd.set(KDF_SALT, seed.to_vec());
360                vd.set(KDF_ITERATIONS, *iterations);
361                vd.set(KDF_PARALLELISM, *parallelism);
362                vd.set(KDF_VERSION, version.as_u32());
363            }
364        }
365
366        vd
367    }
368}
369
370const KDF_AES_KDBX3: [u8; 16] = hex!("c9d9f39a628a4460bf740d08c18a4fea");
371const KDF_AES_KDBX4: [u8; 16] = hex!("7c02bb8279a74ac0927d114a00648238");
372const KDF_ARGON2: [u8; 16] = hex!("ef636ddf8c29444b91f7a9a403e30a0c");
373const KDF_ARGON2ID: [u8; 16] = hex!("9e298b1956db4773b23dfc3ec6f0a1e6");
374
375impl TryFrom<VariantDictionary> for (KdfConfig, Vec<u8>) {
376    type Error = KdfConfigError;
377
378    fn try_from(vd: VariantDictionary) -> Result<(KdfConfig, Vec<u8>), Self::Error> {
379        let uuid = vd.get_typed::<Vec<u8>>(KDF_ID)?;
380
381        if uuid == &KDF_ARGON2ID {
382            let memory: u64 = *vd.get_typed(KDF_MEMORY)?;
383            let salt: Vec<u8> = vd.get_typed::<Vec<u8>>(KDF_SALT)?.clone();
384            let iterations: u64 = *vd.get_typed(KDF_ITERATIONS)?;
385            let parallelism: u32 = *vd.get_typed(KDF_PARALLELISM)?;
386            let version: u32 = *vd.get_typed(KDF_VERSION)?;
387
388            let version = match version {
389                0x10 => argon2::Version::Version10,
390                0x13 => argon2::Version::Version13,
391                _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
392            };
393
394            Ok((
395                KdfConfig::Argon2id {
396                    memory,
397                    iterations,
398                    parallelism,
399                    version,
400                },
401                salt,
402            ))
403        } else if uuid == &KDF_ARGON2 {
404            let memory: u64 = *vd.get_typed(KDF_MEMORY)?;
405            let salt: Vec<u8> = vd.get_typed::<Vec<u8>>(KDF_SALT)?.clone();
406            let iterations: u64 = *vd.get_typed(KDF_ITERATIONS)?;
407            let parallelism: u32 = *vd.get_typed(KDF_PARALLELISM)?;
408            let version: u32 = *vd.get_typed(KDF_VERSION)?;
409
410            let version = match version {
411                0x10 => argon2::Version::Version10,
412                0x13 => argon2::Version::Version13,
413                _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
414            };
415
416            Ok((
417                KdfConfig::Argon2 {
418                    memory,
419                    iterations,
420                    parallelism,
421                    version,
422                },
423                salt,
424            ))
425        } else if uuid == &KDF_AES_KDBX4 || uuid == &KDF_AES_KDBX3 {
426            let rounds: u64 = *vd.get_typed(KDF_ROUNDS)?;
427            let seed: Vec<u8> = vd.get_typed::<Vec<u8>>(KDF_SEED)?.clone();
428
429            Ok((KdfConfig::Aes { rounds }, seed))
430        } else {
431            Err(KdfConfigError::InvalidKDFUUID { uuid: uuid.clone() })
432        }
433    }
434}
435
436/// Choices of compression algorithm
437#[derive(Debug, Clone, PartialEq, Eq)]
438#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
439pub enum CompressionConfig {
440    None,
441    GZip,
442}
443
444/// Errors with the configuration of the compression algorithm.
445#[derive(Debug, thiserror::Error)]
446pub enum CompressionConfigError {
447    #[error("Invalid compression algorithm: {}", cid)]
448    InvalidCompressionSuite { cid: u32 },
449}
450
451impl CompressionConfig {
452    pub(crate) fn get_compression(&self) -> Box<dyn compression::Compression> {
453        match self {
454            CompressionConfig::None => Box::new(compression::NoCompression),
455            CompressionConfig::GZip => Box::new(compression::GZipCompression),
456        }
457    }
458
459    #[cfg(feature = "save_kdbx4")]
460    pub(crate) fn dump(&self) -> [u8; 4] {
461        match self {
462            CompressionConfig::None => [0, 0, 0, 0],
463            CompressionConfig::GZip => [1, 0, 0, 0],
464        }
465    }
466}
467
468impl TryFrom<u32> for CompressionConfig {
469    type Error = CompressionConfigError;
470
471    fn try_from(v: u32) -> Result<CompressionConfig, Self::Error> {
472        match v {
473            0 => Ok(CompressionConfig::None),
474            1 => Ok(CompressionConfig::GZip),
475            _ => Err(CompressionConfigError::InvalidCompressionSuite { cid: v }),
476        }
477    }
478}