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;
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
23const PLAIN: u32 = 0;
25const SALSA_20: u32 = 2;
26const CHA_CHA_20: u32 = 3;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
31pub struct DatabaseConfig {
32 pub version: DatabaseVersion,
34
35 pub outer_cipher_config: OuterCipherConfig,
37
38 pub compression_config: CompressionConfig,
40
41 pub inner_cipher_config: InnerCipherConfig,
43
44 pub kdf_config: KdfConfig,
46
47 pub public_custom_data: Option<VariantDictionary>,
49}
50
51impl 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#[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#[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#[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#[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
192const KDF_ID: &str = "$UUID";
194const KDF_MEMORY: &str = "M";
196const KDF_SALT: &str = "S";
197const KDF_ITERATIONS: &str = "I";
198const KDF_PARALLELISM: &str = "P";
199const KDF_VERSION: &str = "V";
200const KDF_SEED: &str = "S";
202const KDF_ROUNDS: &str = "R";
203
204#[derive(Debug, Clone, PartialEq, Eq)]
206#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
207pub enum KdfConfig {
208 Aes { rounds: u64 },
210 Argon2 {
212 iterations: u64,
214
215 memory: u64,
220
221 parallelism: u32,
223
224 #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
226 version: argon2::Version,
227 },
228 Argon2id {
230 iterations: u64,
232
233 memory: u64,
238
239 parallelism: u32,
241
242 #[cfg_attr(feature = "serialization", serde(serialize_with = "serialize_argon2_version"))]
244 version: argon2::Version,
245 },
246}
247
248#[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 #[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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
438#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
439pub enum CompressionConfig {
440 None,
441 GZip,
442}
443
444#[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}