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#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
106#[serde(rename_all = "camelCase", deny_unknown_fields)]
107#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
108#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
109pub enum Kdf {
110 PBKDF2 {
111 iterations: NonZeroU32,
112 },
113 Argon2id {
114 iterations: NonZeroU32,
115 memory: NonZeroU32,
116 parallelism: NonZeroU32,
117 },
118}
119
120impl Default for Kdf {
121 fn default() -> Self {
123 Kdf::PBKDF2 {
124 iterations: default_pbkdf2_iterations(),
125 }
126 }
127}
128
129pub fn default_pbkdf2_iterations() -> NonZeroU32 {
131 NonZeroU32::new(600_000).expect("Non-zero number")
132}
133pub fn default_argon2_iterations() -> NonZeroU32 {
135 NonZeroU32::new(3).expect("Non-zero number")
136}
137pub fn default_argon2_memory() -> NonZeroU32 {
139 NonZeroU32::new(64).expect("Non-zero number")
140}
141pub fn default_argon2_parallelism() -> NonZeroU32 {
143 NonZeroU32::new(4).expect("Non-zero number")
144}
145
146#[cfg(test)]
147mod tests {
148 use std::num::{NonZero, NonZeroU32};
149
150 use crate::keys::kdf::{Kdf, KdfDerivedKeyMaterial};
151
152 #[test]
153 fn test_derive_kdf_minimums() {
154 fn nz(n: u32) -> NonZero<u32> {
155 NonZero::new(n).unwrap()
156 }
157
158 let secret = [0u8; 32];
159 let salt = [0u8; 32];
160
161 for kdf in [
162 Kdf::PBKDF2 {
163 iterations: nz(4999),
164 },
165 Kdf::Argon2id {
166 iterations: nz(1),
167 memory: nz(16),
168 parallelism: nz(1),
169 },
170 Kdf::Argon2id {
171 iterations: nz(2),
172 memory: nz(15),
173 parallelism: nz(1),
174 },
175 Kdf::Argon2id {
176 iterations: nz(1),
177 memory: nz(15),
178 parallelism: nz(1),
179 },
180 ] {
181 assert_eq!(
182 KdfDerivedKeyMaterial::derive_kdf_key(&secret, &salt, &kdf)
183 .err()
184 .unwrap()
185 .to_string(),
186 "Insufficient KDF parameters"
187 );
188 }
189 }
190
191 #[test]
192 fn test_master_key_derive_pbkdf2() {
193 let kdf_key = KdfDerivedKeyMaterial::derive(
194 "67t9b5g67$%Dh89n",
195 "test_key",
196 &Kdf::PBKDF2 {
197 iterations: NonZeroU32::new(10000).unwrap(),
198 },
199 )
200 .unwrap();
201
202 assert_eq!(
203 [
204 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
205 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75
206 ],
207 kdf_key.0.as_slice()
208 );
209 }
210
211 #[test]
212 fn test_master_key_derive_argon2() {
213 let kdf_key = KdfDerivedKeyMaterial::derive(
214 "67t9b5g67$%Dh89n",
215 "test_key",
216 &Kdf::Argon2id {
217 iterations: NonZeroU32::new(4).unwrap(),
218 memory: NonZeroU32::new(32).unwrap(),
219 parallelism: NonZeroU32::new(2).unwrap(),
220 },
221 )
222 .unwrap();
223
224 assert_eq!(
225 [
226 207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147,
227 237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242
228 ],
229 kdf_key.0.as_slice()
230 );
231 }
232}