1use hex_literal::hex;
3use thiserror::Error;
4
5use std::convert::TryFrom;
6
7pub use crate::format::DatabaseVersion;
8
9#[cfg(feature = "save_kdbx4")]
10use crate::crypt::ciphers::Cipher;
11use crate::{
12 compression,
13 crypt::{
14 ciphers::{self},
15 kdf, CryptographyError,
16 },
17 format::{variant_dictionary::VariantDictionaryError, KDBX4_CURRENT_MINOR_VERSION},
18};
19
20pub use crate::format::variant_dictionary::{VariantDictionary, VariantDictionaryValue};
21
22const _CIPHERSUITE_AES128: [u8; 16] = hex!("61ab05a1946441c38d743a563df8dd35");
23const CIPHERSUITE_AES256: [u8; 16] = hex!("31c1f2e6bf714350be5805216afc5aff");
24const CIPHERSUITE_TWOFISH: [u8; 16] = hex!("ad68f29f576f4bb9a36ad47af965346c");
25const CIPHERSUITE_CHACHA20: [u8; 16] = hex!("d6038a2b8b6f4cb5a524339a31dbb59a");
26
27const PLAIN: u32 = 0;
29const SALSA_20: u32 = 2;
30const CHA_CHA_20: u32 = 3;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
35#[non_exhaustive]
36pub struct DatabaseConfig {
37 pub version: DatabaseVersion,
39
40 pub outer_cipher_config: OuterCipherConfig,
42
43 pub compression_config: CompressionConfig,
45
46 pub inner_cipher_config: InnerCipherConfig,
48
49 pub kdf_config: KdfConfig,
51
52 pub public_custom_data: Option<VariantDictionary>,
54}
55
56impl Default for DatabaseConfig {
58 fn default() -> Self {
59 Self {
60 version: DatabaseVersion::KDB4(KDBX4_CURRENT_MINOR_VERSION),
61 outer_cipher_config: OuterCipherConfig::AES256,
62 compression_config: CompressionConfig::GZip,
63 inner_cipher_config: InnerCipherConfig::ChaCha20,
64 kdf_config: KdfConfig::Argon2 {
65 iterations: 50,
66 memory: 1024 * 1024,
67 parallelism: 4,
68 version: argon2::Version::Version13,
69 },
70 public_custom_data: None,
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
78#[non_exhaustive]
79pub enum OuterCipherConfig {
80 AES256,
82
83 Twofish,
85
86 ChaCha20,
88}
89
90impl OuterCipherConfig {
91 pub(crate) fn get_cipher(
92 &self,
93 key: &[u8],
94 iv: &[u8],
95 ) -> Result<Box<dyn ciphers::Cipher>, CryptographyError> {
96 match self {
97 OuterCipherConfig::AES256 => Ok(Box::new(ciphers::AES256Cipher::new(key, iv)?)),
98 OuterCipherConfig::Twofish => Ok(Box::new(ciphers::TwofishCipher::new(key, iv)?)),
99 OuterCipherConfig::ChaCha20 => Ok(Box::new(ciphers::ChaCha20Cipher::new_key_iv(key, iv)?)),
100 }
101 }
102
103 #[cfg(feature = "save_kdbx4")]
104 pub(crate) fn get_iv_size(&self) -> usize {
105 match self {
106 OuterCipherConfig::AES256 => ciphers::AES256Cipher::iv_size(),
107 OuterCipherConfig::Twofish => ciphers::TwofishCipher::iv_size(),
108 OuterCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::iv_size(),
109 }
110 }
111
112 #[cfg(feature = "save_kdbx4")]
113 pub(crate) fn dump(&self) -> [u8; 16] {
114 match self {
115 OuterCipherConfig::AES256 => CIPHERSUITE_AES256,
116 OuterCipherConfig::Twofish => CIPHERSUITE_TWOFISH,
117 OuterCipherConfig::ChaCha20 => CIPHERSUITE_CHACHA20,
118 }
119 }
120}
121
122impl TryFrom<&[u8]> for OuterCipherConfig {
123 type Error = OuterCipherConfigError;
124 fn try_from(v: &[u8]) -> Result<OuterCipherConfig, Self::Error> {
125 if v == CIPHERSUITE_AES256 {
126 Ok(OuterCipherConfig::AES256)
127 } else if v == CIPHERSUITE_TWOFISH {
128 Ok(OuterCipherConfig::Twofish)
129 } else if v == CIPHERSUITE_CHACHA20 {
130 Ok(OuterCipherConfig::ChaCha20)
131 } else {
132 Err(OuterCipherConfigError::InvalidOuterCipherID { cid: v.to_vec() })
133 }
134 }
135}
136
137#[derive(Debug, Error)]
139#[non_exhaustive]
140pub enum OuterCipherConfigError {
141 #[error(transparent)]
143 Cryptography(#[from] CryptographyError),
144
145 #[error("Invalid outer cipher ID: {:?}", cid)]
147 InvalidOuterCipherID {
148 cid: Vec<u8>,
150 },
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
156#[non_exhaustive]
157pub enum InnerCipherConfig {
158 Plain,
160
161 Salsa20,
163
164 ChaCha20,
166}
167
168impl InnerCipherConfig {
169 pub(crate) fn get_cipher(
170 &self,
171 protected_stream_key: &[u8],
172 ) -> Result<Box<dyn ciphers::Cipher>, CryptographyError> {
173 match self {
174 InnerCipherConfig::Plain => Ok(Box::new(ciphers::PlainCipher::new(protected_stream_key)?)),
175 InnerCipherConfig::Salsa20 => Ok(Box::new(ciphers::Salsa20Cipher::new(protected_stream_key)?)),
176 InnerCipherConfig::ChaCha20 => Ok(Box::new(ciphers::ChaCha20Cipher::new(protected_stream_key)?)),
177 }
178 }
179
180 #[cfg(feature = "save_kdbx4")]
181 pub(crate) fn dump(&self) -> u32 {
182 match self {
183 InnerCipherConfig::Plain => PLAIN,
184 InnerCipherConfig::Salsa20 => SALSA_20,
185 InnerCipherConfig::ChaCha20 => CHA_CHA_20,
186 }
187 }
188
189 #[cfg(feature = "save_kdbx4")]
190 pub(crate) fn get_key_size(&self) -> usize {
191 match self {
192 InnerCipherConfig::Plain => ciphers::PlainCipher::key_size(),
193 InnerCipherConfig::Salsa20 => ciphers::Salsa20Cipher::key_size(),
194 InnerCipherConfig::ChaCha20 => ciphers::ChaCha20Cipher::key_size(),
195 }
196 }
197}
198
199impl TryFrom<u32> for InnerCipherConfig {
200 type Error = InnerCipherConfigError;
201
202 fn try_from(v: u32) -> Result<InnerCipherConfig, Self::Error> {
203 match v {
204 PLAIN => Ok(InnerCipherConfig::Plain),
205 SALSA_20 => Ok(InnerCipherConfig::Salsa20),
206 CHA_CHA_20 => Ok(InnerCipherConfig::ChaCha20),
207 _ => Err(InnerCipherConfigError::InvalidInnerCipherID { cid: v }),
208 }
209 }
210}
211
212#[derive(Debug, Error)]
214#[non_exhaustive]
215pub enum InnerCipherConfigError {
216 #[error(transparent)]
218 Cryptography(#[from] CryptographyError),
219
220 #[error("Invalid inner cipher ID: {}", cid)]
222 InvalidInnerCipherID {
223 cid: u32,
225 },
226}
227
228const KDF_ID: &str = "$UUID";
230const KDF_MEMORY: &str = "M";
232const KDF_SALT: &str = "S";
233const KDF_ITERATIONS: &str = "I";
234const KDF_PARALLELISM: &str = "P";
235const KDF_VERSION: &str = "V";
236const KDF_SEED: &str = "S";
238const KDF_ROUNDS: &str = "R";
239
240#[derive(Debug, Clone, PartialEq, Eq)]
242#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
243#[non_exhaustive]
244pub enum KdfConfig {
245 Aes {
247 rounds: u64,
249 },
250 Argon2 {
252 iterations: u64,
254
255 memory: u64,
260
261 parallelism: u32,
263
264 #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
266 version: argon2::Version,
267 },
268
269 Argon2id {
271 iterations: u64,
273
274 memory: u64,
279
280 parallelism: u32,
282
283 #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
285 version: argon2::Version,
286 },
287}
288
289#[cfg(feature = "serialization")]
290fn serialize_argon2_version<S: serde::Serializer>(
291 version: &argon2::Version,
292 serializer: S,
293) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error> {
294 serializer.serialize_u32(version.as_u32())
295}
296
297impl KdfConfig {
298 #[cfg(feature = "save_kdbx4")]
299 fn seed_size(&self) -> usize {
300 match self {
301 KdfConfig::Aes { .. } => 32,
302 KdfConfig::Argon2 { .. } => 32,
303 KdfConfig::Argon2id { .. } => 32,
304 }
305 }
306
307 #[cfg(feature = "save_kdbx4")]
310 pub(crate) fn get_kdf_and_seed(&self) -> Result<(Box<dyn kdf::Kdf>, Vec<u8>), getrandom::Error> {
311 let mut kdf_seed = vec![0; self.seed_size()];
312 getrandom::fill(&mut kdf_seed)?;
313
314 let kdf = self.get_kdf_seeded(&kdf_seed);
315
316 Ok((kdf, kdf_seed))
317 }
318
319 pub(crate) fn get_kdf_seeded(&self, seed: &[u8]) -> Box<dyn kdf::Kdf> {
321 match self {
322 KdfConfig::Aes { rounds } => Box::new(kdf::AesKdf {
323 seed: seed.to_vec(),
324 rounds: *rounds,
325 }),
326 KdfConfig::Argon2 {
327 memory,
328 iterations,
329 parallelism,
330 version,
331 } => Box::new(kdf::Argon2Kdf {
332 memory: *memory,
333 salt: seed.to_vec(),
334 iterations: *iterations,
335 parallelism: *parallelism,
336 version: *version,
337 variant: argon2::Variant::Argon2d,
338 }),
339 KdfConfig::Argon2id {
340 memory,
341 iterations,
342 parallelism,
343 version,
344 } => Box::new(kdf::Argon2Kdf {
345 memory: *memory,
346 salt: seed.to_vec(),
347 iterations: *iterations,
348 parallelism: *parallelism,
349 version: *version,
350 variant: argon2::Variant::Argon2id,
351 }),
352 }
353 }
354
355 #[cfg(feature = "save_kdbx4")]
356 pub(crate) fn to_variant_dictionary(&self, seed: &[u8]) -> VariantDictionary {
357 let mut vd = VariantDictionary::new();
358
359 match self {
360 KdfConfig::Aes { rounds } => {
361 vd.set(KDF_ID, KDF_AES_KDBX3.to_vec());
363 vd.set(KDF_ROUNDS, *rounds);
364 vd.set(KDF_SEED, seed.to_vec());
365 }
366 KdfConfig::Argon2 {
367 memory,
368 iterations,
369 parallelism,
370 version,
371 } => {
372 vd.set(KDF_ID, KDF_ARGON2.to_vec());
373 vd.set(KDF_MEMORY, *memory);
374 vd.set(KDF_SALT, seed.to_vec());
375 vd.set(KDF_ITERATIONS, *iterations);
376 vd.set(KDF_PARALLELISM, *parallelism);
377 vd.set(KDF_VERSION, version.as_u32());
378 }
379 KdfConfig::Argon2id {
380 memory,
381 iterations,
382 parallelism,
383 version,
384 } => {
385 vd.set(KDF_ID, KDF_ARGON2ID.to_vec());
386 vd.set(KDF_MEMORY, *memory);
387 vd.set(KDF_SALT, seed.to_vec());
388 vd.set(KDF_ITERATIONS, *iterations);
389 vd.set(KDF_PARALLELISM, *parallelism);
390 vd.set(KDF_VERSION, version.as_u32());
391 }
392 }
393
394 vd
395 }
396}
397
398const KDF_AES_KDBX3: [u8; 16] = hex!("c9d9f39a628a4460bf740d08c18a4fea");
399const KDF_AES_KDBX4: [u8; 16] = hex!("7c02bb8279a74ac0927d114a00648238");
400const KDF_ARGON2: [u8; 16] = hex!("ef636ddf8c29444b91f7a9a403e30a0c");
401const KDF_ARGON2ID: [u8; 16] = hex!("9e298b1956db4773b23dfc3ec6f0a1e6");
402
403impl TryFrom<VariantDictionary> for (KdfConfig, Vec<u8>) {
404 type Error = KdfConfigError;
405
406 fn try_from(vd: VariantDictionary) -> Result<(KdfConfig, Vec<u8>), Self::Error> {
407 let uuid = vd.get_typed::<Vec<u8>>(KDF_ID)?;
408
409 if uuid == &KDF_ARGON2ID {
410 let memory: u64 = *vd.get_typed(KDF_MEMORY)?;
411 let salt: Vec<u8> = vd.get_typed::<Vec<u8>>(KDF_SALT)?.clone();
412 let iterations: u64 = *vd.get_typed(KDF_ITERATIONS)?;
413 let parallelism: u32 = *vd.get_typed(KDF_PARALLELISM)?;
414 let version: u32 = *vd.get_typed(KDF_VERSION)?;
415
416 let version = match version {
417 0x10 => argon2::Version::Version10,
418 0x13 => argon2::Version::Version13,
419 _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
420 };
421
422 Ok((
423 KdfConfig::Argon2id {
424 memory,
425 iterations,
426 parallelism,
427 version,
428 },
429 salt,
430 ))
431 } else if uuid == &KDF_ARGON2 {
432 let memory: u64 = *vd.get_typed(KDF_MEMORY)?;
433 let salt: Vec<u8> = vd.get_typed::<Vec<u8>>(KDF_SALT)?.clone();
434 let iterations: u64 = *vd.get_typed(KDF_ITERATIONS)?;
435 let parallelism: u32 = *vd.get_typed(KDF_PARALLELISM)?;
436 let version: u32 = *vd.get_typed(KDF_VERSION)?;
437
438 let version = match version {
439 0x10 => argon2::Version::Version10,
440 0x13 => argon2::Version::Version13,
441 _ => return Err(KdfConfigError::InvalidKDFVersion { version }),
442 };
443
444 Ok((
445 KdfConfig::Argon2 {
446 memory,
447 iterations,
448 parallelism,
449 version,
450 },
451 salt,
452 ))
453 } else if uuid == &KDF_AES_KDBX4 || uuid == &KDF_AES_KDBX3 {
454 let rounds: u64 = *vd.get_typed(KDF_ROUNDS)?;
455 let seed: Vec<u8> = vd.get_typed::<Vec<u8>>(KDF_SEED)?.clone();
456
457 Ok((KdfConfig::Aes { rounds }, seed))
458 } else {
459 Err(KdfConfigError::InvalidKDFUUID { uuid: uuid.clone() })
460 }
461 }
462}
463
464#[derive(Debug, Error)]
466#[non_exhaustive]
467pub enum KdfConfigError {
468 #[error("Invalid KDF version: {}", version)]
470 InvalidKDFVersion {
471 version: u32,
473 },
474
475 #[error("Invalid KDF UUID: {:?}", uuid)]
477 InvalidKDFUUID {
478 uuid: Vec<u8>,
480 },
481
482 #[error(transparent)]
484 VariantDictionary(#[from] VariantDictionaryError),
485}
486
487#[derive(Debug, Clone, PartialEq, Eq)]
489#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
490#[non_exhaustive]
491pub enum CompressionConfig {
492 None,
494
495 GZip,
497}
498
499impl CompressionConfig {
500 pub(crate) fn get_compression(&self) -> Box<dyn compression::Compression> {
501 match self {
502 CompressionConfig::None => Box::new(compression::NoCompression),
503 CompressionConfig::GZip => Box::new(compression::GZipCompression),
504 }
505 }
506
507 #[cfg(feature = "save_kdbx4")]
508 pub(crate) fn dump(&self) -> [u8; 4] {
509 match self {
510 CompressionConfig::None => [0, 0, 0, 0],
511 CompressionConfig::GZip => [1, 0, 0, 0],
512 }
513 }
514}
515
516impl TryFrom<u32> for CompressionConfig {
517 type Error = CompressionConfigError;
518
519 fn try_from(v: u32) -> Result<CompressionConfig, Self::Error> {
520 match v {
521 0 => Ok(CompressionConfig::None),
522 1 => Ok(CompressionConfig::GZip),
523 _ => Err(CompressionConfigError::InvalidCompressionSuite { cid: v }),
524 }
525 }
526}
527
528#[derive(Debug, Error)]
530#[non_exhaustive]
531pub enum CompressionConfigError {
532 #[error("Invalid compression algorithm: {}", cid)]
534 InvalidCompressionSuite {
535 cid: u32,
537 },
538}