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