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::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
20#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
29pub struct KdfDerivedKeyMaterial(pub(super) Pin<Box<GenericArray<u8, U32>>>);
30
31impl KdfDerivedKeyMaterial {
32 pub(super) fn derive_kdf_key(
34 secret: &[u8],
35 salt: &[u8],
36 kdf: &Kdf,
37 ) -> Result<Self, CryptoError> {
38 match kdf {
39 Kdf::PBKDF2 { iterations } => {
40 let iterations = iterations.get();
41 if iterations < PBKDF2_MIN_ITERATIONS {
42 return Err(CryptoError::InsufficientKdfParameters);
43 }
44
45 let mut hash = crate::util::pbkdf2(secret, salt, iterations);
46
47 let key_material = Box::pin(hash.into());
48 hash.zeroize();
49 Ok(KdfDerivedKeyMaterial(key_material))
50 }
51 Kdf::Argon2id {
52 iterations,
53 memory,
54 parallelism,
55 } => {
56 let memory = memory.get() * 1024; let iterations = iterations.get();
58 let parallelism = parallelism.get();
59
60 if memory < ARGON2ID_MIN_MEMORY
61 || iterations < ARGON2ID_MIN_ITERATIONS
62 || parallelism < ARGON2ID_MIN_PARALLELISM
63 {
64 return Err(CryptoError::InsufficientKdfParameters);
65 }
66
67 let salt_sha = sha2::Sha256::new().chain_update(salt).finalize();
68
69 let mut hash = Box::pin(GenericArray::<u8, U32>::default());
70
71 use argon2::*;
72 let params = Params::new(memory, iterations, parallelism, Some(32))?;
73 let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
74 argon.hash_password_into(secret, &salt_sha, hash.as_mut_slice())?;
75
76 #[inline(never)]
80 fn clear_stack() {
81 std::hint::black_box([0u8; 4096]);
82 }
83 clear_stack();
84
85 Ok(KdfDerivedKeyMaterial(hash))
86 }
87 }
88 }
89
90 pub(super) fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
94 Self::derive_kdf_key(
95 password.as_bytes(),
96 email.trim().to_lowercase().as_bytes(),
97 kdf,
98 )
99 }
100}
101
102#[deprecated(
103 note = "This function is only meant as a temporary stop-gap to expose KDF derivation in PureCrypto until the higher-level consumers are moved to the SDK directly. DO NOT USE THIS OUTSIDE OF PureCrypto!"
104)]
105pub fn dangerous_derive_kdf_material(
111 password: &[u8],
112 salt: &[u8],
113 kdf: &Kdf,
114) -> Result<Vec<u8>, CryptoError> {
115 KdfDerivedKeyMaterial::derive_kdf_key(password, salt, kdf).map(|kdf_key| kdf_key.0.to_vec())
116}
117
118#[allow(missing_docs)]
123#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq)]
124#[serde(rename_all = "camelCase", deny_unknown_fields)]
125#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
126#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
127pub enum Kdf {
128 PBKDF2 {
129 iterations: NonZeroU32,
130 },
131 Argon2id {
132 iterations: NonZeroU32,
133 memory: NonZeroU32,
134 parallelism: NonZeroU32,
135 },
136}
137
138impl Kdf {
139 pub fn default_pbkdf2() -> Kdf {
141 Kdf::PBKDF2 {
142 iterations: default_pbkdf2_iterations(),
143 }
144 }
145
146 pub fn default_argon2() -> Kdf {
148 Kdf::Argon2id {
149 iterations: default_argon2_iterations(),
150 memory: default_argon2_memory(),
151 parallelism: default_argon2_parallelism(),
152 }
153 }
154}
155
156fn default_pbkdf2_iterations() -> NonZeroU32 {
158 NonZeroU32::new(600_000).expect("Non-zero number")
159}
160fn default_argon2_iterations() -> NonZeroU32 {
162 NonZeroU32::new(6).expect("Non-zero number")
163}
164fn default_argon2_memory() -> NonZeroU32 {
166 NonZeroU32::new(32).expect("Non-zero number")
167}
168fn default_argon2_parallelism() -> NonZeroU32 {
170 NonZeroU32::new(3).expect("Non-zero number")
171}
172
173#[cfg(test)]
174mod tests {
175 use std::num::{NonZero, NonZeroU32};
176
177 use crate::keys::kdf::{Kdf, KdfDerivedKeyMaterial};
178
179 #[test]
180 fn test_derive_kdf_minimums() {
181 fn nz(n: u32) -> NonZero<u32> {
182 NonZero::new(n).unwrap()
183 }
184
185 let secret = [0u8; 32];
186 let salt = [0u8; 32];
187
188 for kdf in [
189 Kdf::PBKDF2 {
190 iterations: nz(4999),
191 },
192 Kdf::Argon2id {
193 iterations: nz(1),
194 memory: nz(16),
195 parallelism: nz(1),
196 },
197 Kdf::Argon2id {
198 iterations: nz(2),
199 memory: nz(15),
200 parallelism: nz(1),
201 },
202 Kdf::Argon2id {
203 iterations: nz(1),
204 memory: nz(15),
205 parallelism: nz(1),
206 },
207 ] {
208 assert_eq!(
209 KdfDerivedKeyMaterial::derive_kdf_key(&secret, &salt, &kdf)
210 .err()
211 .unwrap()
212 .to_string(),
213 "Insufficient KDF parameters"
214 );
215 }
216 }
217
218 #[test]
219 fn test_master_key_derive_pbkdf2() {
220 let kdf_key = KdfDerivedKeyMaterial::derive(
221 "67t9b5g67$%Dh89n",
222 "test_key",
223 &Kdf::PBKDF2 {
224 iterations: NonZeroU32::new(10000).unwrap(),
225 },
226 )
227 .unwrap();
228
229 assert_eq!(
230 [
231 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
232 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75
233 ],
234 kdf_key.0.as_slice()
235 );
236 }
237
238 #[test]
239 fn test_master_key_derive_argon2() {
240 let kdf_key = KdfDerivedKeyMaterial::derive(
241 "67t9b5g67$%Dh89n",
242 "test_key",
243 &Kdf::Argon2id {
244 iterations: NonZeroU32::new(4).unwrap(),
245 memory: NonZeroU32::new(32).unwrap(),
246 parallelism: NonZeroU32::new(2).unwrap(),
247 },
248 )
249 .unwrap();
250
251 assert_eq!(
252 [
253 207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147,
254 237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242
255 ],
256 kdf_key.0.as_slice()
257 );
258 }
259}