1use super::errors::HeaderError;
2use super::header::{HeaderField, InnerHeaderId, OuterHeaderId};
3use super::variant_dict::{self, VariantDict};
4use crate::utils;
5use std::convert::{TryFrom, TryInto};
6use uuid::Uuid;
7
8pub const KEEPASS_MAGIC_NUMBER: u32 = 0x9AA2_D903;
9pub const KDBX_MAGIC_NUMBER: u32 = 0xB54B_FB67;
10
11const AES128_UUID: &str = "61ab05a1-9464-41c3-8d74-3a563df8dd35";
12const AES256_UUID: &str = "31c1f2e6-bf71-4350-be58-05216afc5aff";
13const TWOFISH_UUID: &str = "ad68f29f-576f-4bb9-a36a-d47af965346c";
14const CHACHA20_UUID: &str = "d6038a2b-8b6f-4cb5-a524-339a31dbb59a";
15const AES_3_1_UUID: &str = "c9d9f39a-628a-4460-bf74-0d08c18a4fea";
16const AES_4_UUID: &str = "7c02bb82-79a7-4ac0-927d-114a00648238";
17const ARGON2D_UUID: &str = "ef636ddf-8c29-444b-91f7-a9a403e30a0c";
18const ARGON2ID_UUID: &str = "9e298b19-56db-4773-b23d-fc3ec6f0a1e6";
19const COMPRESSION_TYPE_NONE: u32 = 0;
20const COMPRESSION_TYPE_GZIP: u32 = 1;
21
22#[derive(PartialEq, Eq, Debug, Copy, Clone)]
23pub enum Cipher {
25 Aes128,
27 Aes256,
29 TwoFish,
31 ChaCha20,
33 Unknown(uuid::Uuid),
35}
36
37const CIPHER_TABLE: [(&str, Cipher); 4] = [
38 (AES128_UUID, Cipher::Aes128),
39 (AES256_UUID, Cipher::Aes256),
40 (TWOFISH_UUID, Cipher::TwoFish),
41 (CHACHA20_UUID, Cipher::ChaCha20),
42];
43
44impl From<uuid::Uuid> for Cipher {
45 fn from(uuid: uuid::Uuid) -> Cipher {
46 utils::value_from_uuid_table(&CIPHER_TABLE, uuid).unwrap_or(Cipher::Unknown(uuid))
47 }
48}
49
50impl From<Cipher> for uuid::Uuid {
51 fn from(cipher: Cipher) -> uuid::Uuid {
52 match cipher {
53 Cipher::Unknown(uuid) => uuid,
54 _ => utils::uuid_from_uuid_table(&CIPHER_TABLE, cipher).unwrap(),
55 }
56 }
57}
58
59impl From<Cipher> for HeaderField<OuterHeaderId> {
60 fn from(cipher: Cipher) -> HeaderField<OuterHeaderId> {
61 let uuid: uuid::Uuid = cipher.into();
62 HeaderField::new(OuterHeaderId::CipherId, uuid.as_bytes().to_vec())
63 }
64}
65
66#[derive(Debug, PartialEq, Eq, Clone, Copy)]
68pub enum InnerStreamCipherAlgorithm {
69 ArcFour,
71 Salsa20,
73 ChaCha20,
75 Unknown(u32),
77}
78
79impl From<InnerStreamCipherAlgorithm> for HeaderField<InnerHeaderId> {
80 fn from(cipher: InnerStreamCipherAlgorithm) -> HeaderField<InnerHeaderId> {
81 HeaderField::new(
82 InnerHeaderId::InnerRandomStreamCipherId,
83 u32::from(cipher).to_le_bytes().as_ref().to_vec(),
84 )
85 }
86}
87
88impl From<u32> for InnerStreamCipherAlgorithm {
89 fn from(id: u32) -> InnerStreamCipherAlgorithm {
90 match id {
91 1 => InnerStreamCipherAlgorithm::ArcFour,
92 2 => InnerStreamCipherAlgorithm::Salsa20,
93 3 => InnerStreamCipherAlgorithm::ChaCha20,
94 x => InnerStreamCipherAlgorithm::Unknown(x),
95 }
96 }
97}
98
99impl From<InnerStreamCipherAlgorithm> for u32 {
100 fn from(id: InnerStreamCipherAlgorithm) -> u32 {
101 match id {
102 InnerStreamCipherAlgorithm::ArcFour => 1,
103 InnerStreamCipherAlgorithm::Salsa20 => 2,
104 InnerStreamCipherAlgorithm::ChaCha20 => 3,
105 InnerStreamCipherAlgorithm::Unknown(x) => x,
106 }
107 }
108}
109
110#[derive(Debug, PartialEq, Eq, Clone, Copy)]
111#[allow(non_camel_case_types)]
112pub enum KdfAlgorithm {
114 Argon2d,
116 Argon2id,
118 Aes256_Kdbx4,
120 Aes256_Kdbx3_1,
122 Unknown(uuid::Uuid),
124}
125
126pub(crate) const KDF_TABLE: [(&str, KdfAlgorithm); 4] = [
127 (AES_3_1_UUID, KdfAlgorithm::Aes256_Kdbx3_1),
128 (AES_4_UUID, KdfAlgorithm::Aes256_Kdbx4),
129 (ARGON2D_UUID, KdfAlgorithm::Argon2d),
130 (ARGON2ID_UUID, KdfAlgorithm::Argon2id),
131];
132
133impl From<uuid::Uuid> for KdfAlgorithm {
134 fn from(uuid: uuid::Uuid) -> KdfAlgorithm {
135 utils::value_from_uuid_table(&KDF_TABLE, uuid).unwrap_or(KdfAlgorithm::Unknown(uuid))
136 }
137}
138
139impl From<KdfAlgorithm> for uuid::Uuid {
140 fn from(algo: KdfAlgorithm) -> uuid::Uuid {
141 match algo {
142 KdfAlgorithm::Unknown(uuid) => uuid,
143 _ => utils::uuid_from_uuid_table(&KDF_TABLE, algo).unwrap(),
144 }
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub enum KdfParams {
151 Argon2 {
153 variant: argon2::Variant,
155 memory_bytes: u64,
157 version: u32,
159 salt: Vec<u8>,
161 lanes: u32,
163 iterations: u64,
165 },
166 Aes {
168 rounds: u64,
170 salt: Vec<u8>,
172 },
173 Unknown {
175 uuid: uuid::Uuid,
177 params: variant_dict::VariantDict,
179 },
180}
181
182impl TryFrom<VariantDict> for KdfParams {
183 type Error = HeaderError;
184 fn try_from(mut vdict: VariantDict) -> Result<Self, HeaderError> {
185 let uuid_val = vdict.remove("$UUID").ok_or_else(|| {
186 HeaderError::MalformedField(
187 OuterHeaderId::KdfParameters,
188 "No UUID for kdf parameters".into(),
189 )
190 })?;
191 let uuid_array: Vec<u8> = uuid_val.try_into().map_err(|_| {
192 HeaderError::MalformedField(
193 OuterHeaderId::KdfParameters,
194 "KDF UUID not a byte array".into(),
195 )
196 })?;
197 let uuid = Uuid::from_slice(&uuid_array).map_err(|_| {
198 HeaderError::MalformedField(
199 OuterHeaderId::KdfParameters,
200 "KDF UUID not a valid UUID".into(),
201 )
202 })?;
203
204 let kdf_algorithm = KdfAlgorithm::from(uuid);
205
206 match kdf_algorithm {
207 KdfAlgorithm::Argon2d | KdfAlgorithm::Argon2id => {
208 let memory_bytes =
209 KdfParams::opt_from_vdict("M", KdfAlgorithm::Argon2d, &mut vdict)?;
210 let version = KdfParams::opt_from_vdict("V", KdfAlgorithm::Argon2d, &mut vdict)?;
211 let salt = KdfParams::opt_from_vdict("S", KdfAlgorithm::Argon2d, &mut vdict)?;
212 let iterations = KdfParams::opt_from_vdict("I", KdfAlgorithm::Argon2d, &mut vdict)?;
213 let lanes = KdfParams::opt_from_vdict("P", KdfAlgorithm::Argon2d, &mut vdict)?;
214 Ok(KdfParams::Argon2 {
215 variant: utils::argon2_algo_to_variant(kdf_algorithm),
216 memory_bytes,
217 version,
218 salt,
219 iterations,
220 lanes,
221 })
222 }
223 KdfAlgorithm::Aes256_Kdbx3_1 | KdfAlgorithm::Aes256_Kdbx4 => {
224 let rounds =
225 KdfParams::opt_from_vdict("R", KdfAlgorithm::Aes256_Kdbx4, &mut vdict)?;
226 let salt = KdfParams::opt_from_vdict("S", KdfAlgorithm::Aes256_Kdbx4, &mut vdict)?;
227 Ok(KdfParams::Aes { rounds, salt })
228 }
229 _ => Ok(KdfParams::Unknown {
230 uuid,
231 params: vdict,
232 }),
233 }
234 }
235}
236
237impl From<KdfParams> for VariantDict {
238 fn from(params: KdfParams) -> VariantDict {
239 let mut vdict = variant_dict::VariantDict::new();
240 match params {
241 KdfParams::Argon2 {
242 variant,
243 memory_bytes,
244 version,
245 salt,
246 lanes,
247 iterations,
248 } => {
249 vdict.insert(
250 "$UUID".into(),
251 variant_dict::Value::Array(
252 uuid::Uuid::from(utils::argon2_variant_to_algo(variant))
253 .as_bytes()
254 .to_vec(),
255 ),
256 );
257 vdict.insert("M".into(), variant_dict::Value::Uint64(memory_bytes));
258 vdict.insert("V".into(), variant_dict::Value::Uint32(version));
259 vdict.insert("S".into(), variant_dict::Value::Array(salt));
260 vdict.insert("I".into(), variant_dict::Value::Uint64(iterations));
261 vdict.insert("P".into(), variant_dict::Value::Uint32(lanes));
262 }
263 KdfParams::Aes { rounds, salt } => {
264 vdict.insert(
265 "$UUID".into(),
266 variant_dict::Value::Array(
267 uuid::Uuid::from(KdfAlgorithm::Aes256_Kdbx4)
268 .as_bytes()
269 .to_vec(),
270 ),
271 );
272 vdict.insert("R".into(), variant_dict::Value::Uint64(rounds));
273 vdict.insert("S".into(), variant_dict::Value::Array(salt));
274 }
275 KdfParams::Unknown { uuid, params } => {
276 vdict.insert(
277 "$UUID".into(),
278 variant_dict::Value::Array(uuid.as_bytes().to_vec()),
279 );
280 vdict.extend(params)
281 }
282 }
283 vdict
284 }
285}
286
287impl From<KdfParams> for HeaderField<OuterHeaderId> {
288 fn from(params: KdfParams) -> HeaderField<OuterHeaderId> {
289 let mut buf = Vec::new();
290 let vdict: VariantDict = params.into();
291 variant_dict::write_variant_dict(&mut buf, &vdict).unwrap();
292 HeaderField::new(OuterHeaderId::KdfParameters, buf)
293 }
294}
295
296impl KdfParams {
297 fn opt_from_vdict<T>(
298 key: &str,
299 algo: KdfAlgorithm,
300 vdict: &mut variant_dict::VariantDict,
301 ) -> Result<T, HeaderError>
302 where
303 T: TryFrom<variant_dict::Value>,
304 {
305 vdict
306 .remove(key)
307 .and_then(|val| val.try_into().ok())
308 .ok_or_else(|| HeaderError::MissingKdfParam(key.to_string(), algo))
309 }
310}
311
312#[derive(Debug, PartialEq, Eq, Clone, Copy)]
313pub enum CompressionType {
315 None,
317 Gzip,
319 Unknown(u32),
321}
322
323impl From<CompressionType> for u32 {
324 fn from(compression_type: CompressionType) -> u32 {
325 match compression_type {
326 CompressionType::None => COMPRESSION_TYPE_NONE,
327 CompressionType::Gzip => COMPRESSION_TYPE_GZIP,
328 CompressionType::Unknown(val) => val,
329 }
330 }
331}
332
333impl From<u32> for CompressionType {
334 fn from(id: u32) -> CompressionType {
335 match id {
336 COMPRESSION_TYPE_NONE => CompressionType::None,
337 COMPRESSION_TYPE_GZIP => CompressionType::Gzip,
338 _ => CompressionType::Unknown(id),
339 }
340 }
341}
342
343impl From<CompressionType> for HeaderField<OuterHeaderId> {
344 fn from(compression_type: CompressionType) -> HeaderField<OuterHeaderId> {
345 let compression_type_id: u32 = compression_type.into();
346 HeaderField::new(
347 OuterHeaderId::CompressionFlags,
348 Vec::from(compression_type_id.to_le_bytes().as_ref()),
349 )
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 use uuid::Uuid;
358 #[test]
359 fn kdf_from_slice() {
360 let aes31 = Uuid::parse_str(AES_3_1_UUID).unwrap();
361 let aes4 = Uuid::parse_str(AES_4_UUID).unwrap();
362 let argon2 = Uuid::parse_str(ARGON2D_UUID).unwrap();
363 let invalid = Uuid::parse_str(AES128_UUID).unwrap();
364
365 assert_eq!(KdfAlgorithm::from(aes31), KdfAlgorithm::Aes256_Kdbx3_1);
366 assert_eq!(KdfAlgorithm::from(aes4), KdfAlgorithm::Aes256_Kdbx4);
367 assert_eq!(KdfAlgorithm::from(argon2), KdfAlgorithm::Argon2d);
368 assert_eq!(KdfAlgorithm::from(invalid), KdfAlgorithm::Unknown(invalid));
369 }
370}