1use std::{num::NonZeroU32, pin::Pin};
2
3use generic_array::GenericArray;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use sha2::Digest;
7#[cfg(feature = "wasm")]
8use tsify_next::Tsify;
9use typenum::U32;
10use zeroize::Zeroize;
11
12use crate::CryptoError;
13
14const PBKDF2_MIN_ITERATIONS: u32 = 5000;
15
16const ARGON2ID_MIN_MEMORY: u32 = 16 * 1024;
17const ARGON2ID_MIN_ITERATIONS: u32 = 2;
18const ARGON2ID_MIN_PARALLELISM: u32 = 1;
19
20pub struct KdfDerivedKeyMaterial(pub(super) Pin<Box<GenericArray<u8, U32>>>);
29
30impl KdfDerivedKeyMaterial {
31 pub(super) fn derive_kdf_key(
33 secret: &[u8],
34 salt: &[u8],
35 kdf: &Kdf,
36 ) -> Result<Self, CryptoError> {
37 let mut hash = match kdf {
38 Kdf::PBKDF2 { iterations } => {
39 let iterations = iterations.get();
40 if iterations < PBKDF2_MIN_ITERATIONS {
41 return Err(CryptoError::InsufficientKdfParameters);
42 }
43
44 crate::util::pbkdf2(secret, salt, iterations)
45 }
46 Kdf::Argon2id {
47 iterations,
48 memory,
49 parallelism,
50 } => {
51 let memory = memory.get() * 1024; let iterations = iterations.get();
53 let parallelism = parallelism.get();
54
55 if memory < ARGON2ID_MIN_MEMORY
56 || iterations < ARGON2ID_MIN_ITERATIONS
57 || parallelism < ARGON2ID_MIN_PARALLELISM
58 {
59 return Err(CryptoError::InsufficientKdfParameters);
60 }
61
62 use argon2::*;
63
64 let params = Params::new(memory, iterations, parallelism, Some(32))?;
65 let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
66
67 let salt_sha = sha2::Sha256::new().chain_update(salt).finalize();
68
69 let mut hash = [0u8; 32];
70 argon.hash_password_into(secret, &salt_sha, &mut hash)?;
71
72 #[inline(never)]
76 fn clear_stack() {
77 std::hint::black_box([0u8; 4096]);
78 }
79 clear_stack();
80
81 hash
82 }
83 };
84 let key_material = Box::pin(GenericArray::clone_from_slice(&hash));
85 hash.zeroize();
86 Ok(KdfDerivedKeyMaterial(key_material))
87 }
88
89 pub(super) fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
93 Self::derive_kdf_key(
94 password.as_bytes(),
95 email.trim().to_lowercase().as_bytes(),
96 kdf,
97 )
98 }
99}
100
101#[allow(missing_docs)]
106#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
107#[serde(rename_all = "camelCase", deny_unknown_fields)]
108#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
109#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
110pub enum Kdf {
111 PBKDF2 {
112 iterations: NonZeroU32,
113 },
114 Argon2id {
115 iterations: NonZeroU32,
116 memory: NonZeroU32,
117 parallelism: NonZeroU32,
118 },
119}
120
121impl Default for Kdf {
122 fn default() -> Self {
124 Kdf::PBKDF2 {
125 iterations: default_pbkdf2_iterations(),
126 }
127 }
128}
129
130pub fn default_pbkdf2_iterations() -> NonZeroU32 {
132 NonZeroU32::new(600_000).expect("Non-zero number")
133}
134pub fn default_argon2_iterations() -> NonZeroU32 {
136 NonZeroU32::new(3).expect("Non-zero number")
137}
138pub fn default_argon2_memory() -> NonZeroU32 {
140 NonZeroU32::new(64).expect("Non-zero number")
141}
142pub fn default_argon2_parallelism() -> NonZeroU32 {
144 NonZeroU32::new(4).expect("Non-zero number")
145}
146
147#[cfg(test)]
148mod tests {
149 use std::num::{NonZero, NonZeroU32};
150
151 use crate::keys::kdf::{Kdf, KdfDerivedKeyMaterial};
152
153 #[test]
154 fn test_derive_kdf_minimums() {
155 fn nz(n: u32) -> NonZero<u32> {
156 NonZero::new(n).unwrap()
157 }
158
159 let secret = [0u8; 32];
160 let salt = [0u8; 32];
161
162 for kdf in [
163 Kdf::PBKDF2 {
164 iterations: nz(4999),
165 },
166 Kdf::Argon2id {
167 iterations: nz(1),
168 memory: nz(16),
169 parallelism: nz(1),
170 },
171 Kdf::Argon2id {
172 iterations: nz(2),
173 memory: nz(15),
174 parallelism: nz(1),
175 },
176 Kdf::Argon2id {
177 iterations: nz(1),
178 memory: nz(15),
179 parallelism: nz(1),
180 },
181 ] {
182 assert_eq!(
183 KdfDerivedKeyMaterial::derive_kdf_key(&secret, &salt, &kdf)
184 .err()
185 .unwrap()
186 .to_string(),
187 "Insufficient KDF parameters"
188 );
189 }
190 }
191
192 #[test]
193 fn test_master_key_derive_pbkdf2() {
194 let kdf_key = KdfDerivedKeyMaterial::derive(
195 "67t9b5g67$%Dh89n",
196 "test_key",
197 &Kdf::PBKDF2 {
198 iterations: NonZeroU32::new(10000).unwrap(),
199 },
200 )
201 .unwrap();
202
203 assert_eq!(
204 [
205 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
206 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75
207 ],
208 kdf_key.0.as_slice()
209 );
210 }
211
212 #[test]
213 fn test_master_key_derive_argon2() {
214 let kdf_key = KdfDerivedKeyMaterial::derive(
215 "67t9b5g67$%Dh89n",
216 "test_key",
217 &Kdf::Argon2id {
218 iterations: NonZeroU32::new(4).unwrap(),
219 memory: NonZeroU32::new(32).unwrap(),
220 parallelism: NonZeroU32::new(2).unwrap(),
221 },
222 )
223 .unwrap();
224
225 assert_eq!(
226 [
227 207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147,
228 237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242
229 ],
230 kdf_key.0.as_slice()
231 );
232 }
233}