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