1use 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;
10use crate::{
11    compression,
12    crypt::{
13        ciphers::{self},
14        kdf,
15    },
16    error::{
17        CompressionConfigError, CryptographyError, InnerCipherConfigError, KdfConfigError,
18        OuterCipherConfigError,
19    },
20    format::KDBX4_CURRENT_MINOR_VERSION,
21    variant_dictionary::VariantDictionary,
22};
23
24const _CIPHERSUITE_AES128: [u8; 16] = hex!("61ab05a1946441c38d743a563df8dd35");
25const CIPHERSUITE_AES256: [u8; 16] = hex!("31c1f2e6bf714350be5805216afc5aff");
26const CIPHERSUITE_TWOFISH: [u8; 16] = hex!("ad68f29f576f4bb9a36ad47af965346c");
27const CIPHERSUITE_CHACHA20: [u8; 16] = hex!("d6038a2b8b6f4cb5a524339a31dbb59a");
28
29const PLAIN: u32 = 0;
31const SALSA_20: u32 = 2;
32const CHA_CHA_20: u32 = 3;
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
37pub struct DatabaseConfig {
38    pub version: DatabaseVersion,
40
41    pub outer_cipher_config: OuterCipherConfig,
43
44    pub compression_config: CompressionConfig,
46
47    pub inner_cipher_config: InnerCipherConfig,
49
50    pub kdf_config: KdfConfig,
52
53    pub public_custom_data: Option<VariantDictionary>,
55}
56
57impl Default for DatabaseConfig {
59    fn default() -> Self {
60        Self {
61            version: DatabaseVersion::KDB4(KDBX4_CURRENT_MINOR_VERSION),
62            outer_cipher_config: OuterCipherConfig::AES256,
63            compression_config: CompressionConfig::GZip,
64            inner_cipher_config: InnerCipherConfig::ChaCha20,
65            kdf_config: KdfConfig::Argon2 {
66                iterations: 50,
67                memory: 1024 * 1024,
68                parallelism: 4,
69                version: argon2::Version::Version13,
70            },
71            public_custom_data: None,
72        }
73    }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
79pub enum OuterCipherConfig {
80    AES256,
81    Twofish,
82    ChaCha20,
83}
84
85impl OuterCipherConfig {
86    pub(crate) fn get_cipher(
87        &self,
88        key: &[u8],
89        iv: &[u8],
90    ) -> 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#[derive(Debug, Clone, PartialEq, Eq)]
134#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
135pub enum InnerCipherConfig {
136    Plain,
137    Salsa20,
138    ChaCha20,
139}
140
141impl InnerCipherConfig {
142    pub(crate) fn get_cipher(&self, key: &[u8]) -> Result<Box<dyn ciphers::Cipher>, CryptographyError> {
143        match self {
144            InnerCipherConfig::Plain => Ok(Box::new(ciphers::PlainCipher::new(key)?)),
145            InnerCipherConfig::Salsa20 => Ok(Box::new(ciphers::Salsa20Cipher::new(key)?)),
146            InnerCipherConfig::ChaCha20 => Ok(Box::new(ciphers::ChaCha20Cipher::new(key)?)),
147        }
148    }
149
150    #[cfg(feature = "save_kdbx4")]
151    pub(crate) fn dump(&self) -> u32 {
152        match self {
153            InnerCipherConfig::Plain => PLAIN,
154            InnerCipherConfig::Salsa20 => SALSA_20,
155            InnerCipherConfig::ChaCha20 => CHA_CHA_20,
156        }
157    }
158
159    #[cfg(feature = "save_kdbx4")]
160    pub(crate) fn get_key_size(&self) -> usize {
161        match self {
162            InnerCipherConfig::Plain => ciphers::PlainCipher::key_size(),
163            InnerCipherConfig::Salsa20 => ciphers::Salsa20Cipher::key_size(),
164            InnerCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::key_size(),
165        }
166    }
167}
168
169impl TryFrom<u32> for InnerCipherConfig {
170    type Error = InnerCipherConfigError;
171
172    fn try_from(v: u32) -> Result<InnerCipherConfig, Self::Error> {
173        match v {
174            PLAIN => Ok(InnerCipherConfig::Plain),
175            SALSA_20 => Ok(InnerCipherConfig::Salsa20),
176            CHA_CHA_20 => Ok(InnerCipherConfig::ChaCha20),
177            _ => Err(InnerCipherConfigError::InvalidInnerCipherID { cid: v }),
178        }
179    }
180}
181
182const KDF_ID: &str = "$UUID";
184const KDF_MEMORY: &str = "M";
186const KDF_SALT: &str = "S";
187const KDF_ITERATIONS: &str = "I";
188const KDF_PARALLELISM: &str = "P";
189const KDF_VERSION: &str = "V";
190const KDF_SEED: &str = "S";
192const KDF_ROUNDS: &str = "R";
193
194#[derive(Debug, Clone, PartialEq, Eq)]
196#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
197pub enum KdfConfig {
198    Aes { rounds: u64 },
200    Argon2 {
202        iterations: u64,
203        memory: u64,
204        parallelism: u32,
205
206        #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
207        version: argon2::Version,
208    },
209    Argon2id {
211        iterations: u64,
212        memory: u64,
213        parallelism: u32,
214
215        #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
216        version: argon2::Version,
217    },
218}
219
220#[cfg(feature = "serialization")]
221fn serialize_argon2_version<S: serde::Serializer>(
222    version: &argon2::Version,
223    serializer: S,
224) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error> {
225    serializer.serialize_u32(version.as_u32())
226}
227
228impl KdfConfig {
229    #[cfg(feature = "save_kdbx4")]
230    fn seed_size(&self) -> usize {
231        match self {
232            KdfConfig::Aes { .. } => 32,
233            KdfConfig::Argon2 { .. } => 32,
234            KdfConfig::Argon2id { .. } => 32,
235        }
236    }
237
238    #[cfg(feature = "save_kdbx4")]
241    pub(crate) fn get_kdf_and_seed(&self) -> Result<(Box<dyn kdf::Kdf>, Vec<u8>), getrandom::Error> {
242        let mut kdf_seed = vec![0; self.seed_size()];
243        getrandom::fill(&mut kdf_seed)?;
244
245        let kdf = self.get_kdf_seeded(&kdf_seed);
246
247        Ok((kdf, kdf_seed))
248    }
249
250    pub(crate) fn get_kdf_seeded(&self, seed: &[u8]) -> Box<dyn kdf::Kdf> {
252        match self {
253            KdfConfig::Aes { rounds } => Box::new(kdf::AesKdf {
254                seed: seed.to_vec(),
255                rounds: *rounds,
256            }),
257            KdfConfig::Argon2 {
258                memory,
259                iterations,
260                parallelism,
261                version,
262            } => Box::new(kdf::Argon2Kdf {
263                memory: *memory,
264                salt: seed.to_vec(),
265                iterations: *iterations,
266                parallelism: *parallelism,
267                version: *version,
268                variant: argon2::Variant::Argon2d,
269            }),
270            KdfConfig::Argon2id {
271                memory,
272                iterations,
273                parallelism,
274                version,
275            } => Box::new(kdf::Argon2Kdf {
276                memory: *memory,
277                salt: seed.to_vec(),
278                iterations: *iterations,
279                parallelism: *parallelism,
280                version: *version,
281                variant: argon2::Variant::Argon2id,
282            }),
283        }
284    }
285
286    #[cfg(feature = "save_kdbx4")]
287    pub(crate) fn to_variant_dictionary(&self, seed: &[u8]) -> VariantDictionary {
288        let mut vd = VariantDictionary::new();
289
290        match self {
291            KdfConfig::Aes { rounds } => {
292                vd.set(KDF_ID, KDF_AES_KDBX4.to_vec());
293                vd.set(KDF_ROUNDS, *rounds);
294                vd.set(KDF_SEED, seed.to_vec());
295            }
296            KdfConfig::Argon2 {
297                memory,
298                iterations,
299                parallelism,
300                version,
301            } => {
302                vd.set(KDF_ID, KDF_ARGON2.to_vec());
303                vd.set(KDF_MEMORY, *memory);
304                vd.set(KDF_SALT, seed.to_vec());
305                vd.set(KDF_ITERATIONS, *iterations);
306                vd.set(KDF_PARALLELISM, *parallelism);
307                vd.set(KDF_VERSION, version.as_u32());
308            }
309            KdfConfig::Argon2id {
310                memory,
311                iterations,
312                parallelism,
313                version,
314            } => {
315                vd.set(KDF_ID, KDF_ARGON2ID.to_vec());
316                vd.set(KDF_MEMORY, *memory);
317                vd.set(KDF_SALT, seed.to_vec());
318                vd.set(KDF_ITERATIONS, *iterations);
319                vd.set(KDF_PARALLELISM, *parallelism);
320                vd.set(KDF_VERSION, version.as_u32());
321            }
322        }
323
324        vd
325    }
326}
327
328const KDF_AES_KDBX3: [u8; 16] = hex!("c9d9f39a628a4460bf740d08c18a4fea");
329const KDF_AES_KDBX4: [u8; 16] = hex!("7c02bb8279a74ac0927d114a00648238");
330const KDF_ARGON2: [u8; 16] = hex!("ef636ddf8c29444b91f7a9a403e30a0c");
331const KDF_ARGON2ID: [u8; 16] = hex!("9e298b1956db4773b23dfc3ec6f0a1e6");
332
333impl TryFrom<VariantDictionary> for (KdfConfig, Vec<u8>) {
334    type Error = KdfConfigError;
335
336    fn try_from(vd: VariantDictionary) -> Result<(KdfConfig, Vec<u8>), Self::Error> {
337        let uuid = vd.get::<Vec<u8>>(KDF_ID)?;
338
339        if uuid == &KDF_ARGON2ID {
340            let memory: u64 = *vd.get(KDF_MEMORY)?;
341            let salt: Vec<u8> = vd.get::<Vec<u8>>(KDF_SALT)?.clone();
342            let iterations: u64 = *vd.get(KDF_ITERATIONS)?;
343            let parallelism: u32 = *vd.get(KDF_PARALLELISM)?;
344            let version: u32 = *vd.get(KDF_VERSION)?;
345
346            let version = match version {
347                0x10 => argon2::Version::Version10,
348                0x13 => argon2::Version::Version13,
349                _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
350            };
351
352            Ok((
353                KdfConfig::Argon2id {
354                    memory,
355                    iterations,
356                    parallelism,
357                    version,
358                },
359                salt,
360            ))
361        } else if uuid == &KDF_ARGON2 {
362            let memory: u64 = *vd.get(KDF_MEMORY)?;
363            let salt: Vec<u8> = vd.get::<Vec<u8>>(KDF_SALT)?.clone();
364            let iterations: u64 = *vd.get(KDF_ITERATIONS)?;
365            let parallelism: u32 = *vd.get(KDF_PARALLELISM)?;
366            let version: u32 = *vd.get(KDF_VERSION)?;
367
368            let version = match version {
369                0x10 => argon2::Version::Version10,
370                0x13 => argon2::Version::Version13,
371                _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
372            };
373
374            Ok((
375                KdfConfig::Argon2 {
376                    memory,
377                    iterations,
378                    parallelism,
379                    version,
380                },
381                salt,
382            ))
383        } else if uuid == &KDF_AES_KDBX4 || uuid == &KDF_AES_KDBX3 {
384            let rounds: u64 = *vd.get(KDF_ROUNDS)?;
385            let seed: Vec<u8> = vd.get::<Vec<u8>>(KDF_SEED)?.clone();
386
387            Ok((KdfConfig::Aes { rounds }, seed))
388        } else {
389            Err(KdfConfigError::InvalidKDFUUID { uuid: uuid.clone() })
390        }
391    }
392}
393
394#[derive(Debug, Clone, PartialEq, Eq)]
396#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
397pub enum CompressionConfig {
398    None,
399    GZip,
400}
401
402impl CompressionConfig {
403    pub(crate) fn get_compression(&self) -> Box<dyn compression::Compression> {
404        match self {
405            CompressionConfig::None => Box::new(compression::NoCompression),
406            CompressionConfig::GZip => Box::new(compression::GZipCompression),
407        }
408    }
409
410    #[cfg(feature = "save_kdbx4")]
411    pub(crate) fn dump(&self) -> [u8; 4] {
412        match self {
413            CompressionConfig::None => [0, 0, 0, 0],
414            CompressionConfig::GZip => [1, 0, 0, 0],
415        }
416    }
417}
418
419impl TryFrom<u32> for CompressionConfig {
420    type Error = CompressionConfigError;
421
422    fn try_from(v: u32) -> Result<CompressionConfig, Self::Error> {
423        match v {
424            0 => Ok(CompressionConfig::None),
425            1 => Ok(CompressionConfig::GZip),
426            _ => Err(CompressionConfigError::InvalidCompressionSuite { cid: v }),
427        }
428    }
429}