1use crate::prelude::*;
3use argon2::{
4 password_hash::{rand_core::OsRng, Salt, SaltString},
5 Algorithm, Argon2, Params, PasswordHasher, Version,
6};
7use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
8use chacha20poly1305::{
9 aead::{Aead, KeyInit},
10 AeadCore, ChaCha20Poly1305, Key,
11};
12use secrecy::*;
13use std::collections::{HashMap, VecDeque};
14use std::sync::{LazyLock, RwLock};
15use zeroize::Zeroize;
16
17static KEY_CACHE: LazyLock<RwLock<SecretBox<SimpleCache>>> =
19 LazyLock::new(|| RwLock::new(SecretBox::new(Box::new(SimpleCache::new()))));
20
21const DERIVED_KEY_LENGTH: usize = 32;
24const SALT_SIZE: usize = 16;
26const NONCE_SIZE: usize = 12;
27const MAX_CACHE_SIZE: usize = 50;
29
30type CacheKey = blake3::Hash; struct SimpleCache {
34 map: HashMap<CacheKey, [u8; 32]>,
35 order: VecDeque<CacheKey>,
36}
37
38impl SimpleCache {
39 fn new() -> Self {
40 Self {
41 map: HashMap::new(),
42 order: VecDeque::new(),
43 }
44 }
45
46 fn get(&mut self, key: CacheKey) -> Option<[u8; 32]> {
47 if let Some(value) = self.map.get(&key) {
48 self.order.retain(|&k| k != key);
49 self.order.push_back(key);
50 Some(*value)
51 } else {
52 None
53 }
54 }
55
56 fn put(&mut self, key: CacheKey, value: [u8; 32]) {
57 if !self.map.contains_key(&key) && self.map.len() >= MAX_CACHE_SIZE {
58 if let Some(oldest_key) = self.order.pop_front() {
59 self.map.remove(&oldest_key);
60 }
61 }
62 self.order.retain(|&k| k != key);
63 self.order.push_back(key);
64 self.map.insert(key, value);
65 }
66}
67
68impl Zeroize for SimpleCache {
70 fn zeroize(&mut self) {
71 for value in self.map.values_mut() {
72 value.zeroize();
73 }
74 self.map.clear();
75 self.order.clear();
76 }
77}
78
79impl Drop for SimpleCache {
81 fn drop(&mut self) {
82 self.zeroize();
83 }
84}
85
86fn make_cache_key(password: &str, salt: &[u8]) -> CacheKey {
88 blake3::hash([password.as_bytes(), salt].concat().as_slice())
89}
90
91pub struct CryptoUtils;
92
93impl CryptoUtils {
94 pub fn derive_key(password: &str, salt: &SaltString) -> Result<Vec<u8>, ServiceError> {
96 let params = Params::new(
98 Params::DEFAULT_M_COST,
99 Params::DEFAULT_T_COST,
100 Params::DEFAULT_P_COST * 2,
101 Some(Params::DEFAULT_OUTPUT_LEN),
102 )
103 .map_err(|_| ServiceError::EncryptionError("Error creating params".to_string()))?;
104
105 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
106 let password_hash = argon2
107 .hash_password(password.as_bytes(), salt)
108 .map_err(|_| ServiceError::EncryptionError("Error hashing password".to_string()))?;
109
110 let key = password_hash
111 .hash
112 .ok_or_else(|| ServiceError::EncryptionError("Error getting hash".to_string()))?;
113 let key_bytes = key.as_bytes();
114 if key_bytes.len() != DERIVED_KEY_LENGTH {
115 return Err(ServiceError::EncryptionError(
116 "Key length is not 32 bytes".to_string(),
117 ));
118 }
119 Ok(key_bytes.to_vec())
120 }
121
122 pub fn encrypt(data: &[u8], key: &[u8], salt: &[u8]) -> Result<String, ServiceError> {
124 if key.len() != DERIVED_KEY_LENGTH {
127 return Err(ServiceError::EncryptionError(
128 "Key length is not 32 bytes".to_string(),
129 ));
130 }
131 if salt.len() != SALT_SIZE {
133 return Err(ServiceError::EncryptionError(
134 "Salt length is not 16 bytes".to_string(),
135 ));
136 }
137 let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
139 let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); let ciphertext = cipher
144 .encrypt(&nonce, data)
145 .map_err(|e| ServiceError::EncryptionError(e.to_string()))?;
146
147 let mut encrypted = Vec::with_capacity(NONCE_SIZE + SALT_SIZE + ciphertext.len());
149 encrypted.extend_from_slice(&nonce);
150 encrypted.extend_from_slice(salt);
151 encrypted.extend_from_slice(&ciphertext);
152
153 let ciphertext_base64 = BASE64_STANDARD.encode(&encrypted);
156 Ok(ciphertext_base64)
157 }
158
159 fn decrypt(data: Vec<u8>, password: &str) -> Result<Vec<u8>, ServiceError> {
162 let (nonce, data) = data.split_at(NONCE_SIZE);
164
165 let nonce: [u8; NONCE_SIZE] = nonce
166 .try_into()
167 .map_err(|e| ServiceError::DecryptionError(format!("Error converting nonce: {}", e)))?;
168
169 let (salt, ciphertext) = data.split_at(SALT_SIZE);
170
171 let salt = SaltString::encode_b64(salt)
173 .map_err(|e| ServiceError::DecryptionError(format!("Error decoding salt: {}", e)))?;
174
175 let cache_key = make_cache_key(password, salt.as_str().as_bytes());
177
178 let mut cache = KEY_CACHE
179 .write()
180 .map_err(|_| ServiceError::DecryptionError("Error in key cache".to_string()))?;
181 let key_bytes = if let Some(cached_key) = cache.expose_secret_mut().get(cache_key) {
184 cached_key
185 } else {
186 let key_bytes = CryptoUtils::derive_key(password, &salt)
188 .map_err(|_| ServiceError::DecryptionError("Error deriving key".to_string()))?;
189 let mut key_array = [0u8; 32];
190 key_array.copy_from_slice(&key_bytes);
191 cache.expose_secret_mut().put(cache_key, key_array);
192 key_array
193 };
194
195 let cipher = ChaCha20Poly1305::new(Key::from_slice(&key_bytes));
197
198 let decrypted = cipher
200 .decrypt(&nonce.into(), ciphertext)
201 .map_err(|e| ServiceError::DecryptionError(e.to_string()))?;
202
203 Ok(decrypted)
204 }
205
206 pub fn decrypt_data(
208 data: String,
209 password: Option<&SecretString>,
210 ) -> Result<String, ServiceError> {
211 let password = match password {
213 Some(password) => password,
214 None => return Ok(data),
215 };
216 let encrypted_bytes = BASE64_STANDARD.decode(&data).map_err(|_| {
218 ServiceError::DecryptionError("Error decoding encrypted data".to_string())
219 })?;
220
221 if encrypted_bytes.len() < NONCE_SIZE + SALT_SIZE {
223 return Err(ServiceError::DecryptionError(
224 "Invalid encrypted data: too short for nonce and salt".to_string(),
225 ));
226 }
227
228 let decrypted_data = CryptoUtils::decrypt(encrypted_bytes, password.expose_secret())?;
230
231 String::from_utf8(decrypted_data).map_err(|_| {
233 ServiceError::DecryptionError("Error converting encrypted data to string".to_string())
234 })
235 }
236
237 pub fn store_encrypted(
250 idkey: &str,
251 password: Option<&SecretString>,
252 fixed_salt: Option<SaltString>,
253 ) -> Result<String, ServiceError> {
254 let password = match password {
256 Some(password) => password,
257 None => return Ok(idkey.to_string()),
258 };
259
260 let salt = match fixed_salt {
262 Some(salt) => salt,
263 None => SaltString::generate(&mut OsRng),
264 };
265
266 let buf = &mut [0u8; Salt::RECOMMENDED_LENGTH];
268 let salt_decoded = salt
270 .decode_b64(buf)
271 .map_err(|e| ServiceError::EncryptionError(format!("Error decoding salt: {}", e)))?;
272
273 let key_bytes = CryptoUtils::derive_key(password.expose_secret(), &salt)
275 .map_err(|e| ServiceError::EncryptionError(format!("Error deriving key: {}", e)))?;
276
277 let ciphertext_base64 = CryptoUtils::encrypt(idkey.as_bytes(), &key_bytes, salt_decoded)
279 .map_err(|e| ServiceError::EncryptionError(format!("Error encrypting data: {}", e)))?;
280
281 Ok(ciphertext_base64)
282 }
283}