1use std::{collections::HashSet, error::Error, fs, path::PathBuf};
2use bt_file_cache::folder_manager::get_local_usr_data_path;
3use bt_logger::{get_error, get_fatal, log_error};
4use bt_string_utils::generate_url_safe_string;
5use keyring::Entry;
6
7use aes_gcm::{AeadCore, Aes256Gcm, Key, KeyInit, Nonce, aead::{Aead, OsRng}};
9use base91::{slice_decode, slice_encode};
10
11use pbkdf2::pbkdf2_hmac;
12use whoami::username;
14
15use crate::hashing::get_hash_string_base64;
16
17const MAX_SIZE: usize = 256;
19
20const PATH_PREFIX: &str = "BT_PATH:";
21
22pub struct SecretVault{
24 service_prefix: String,
26
27 user_name: String,
29}
30
31impl SecretVault {
32 fn get_service_id(&self, service: &str) -> String{
37 let svr_name = format!("{}{}",self.service_prefix,service);
38 let svr = get_hash_string_base64(&svr_name);
39 svr.chars().take(MAX_SIZE).collect()
40 }
41
42 pub fn new(srv_prefix: &str) -> Self{
47 let usr_name = username().chars().take(MAX_SIZE).collect();
48 Self { service_prefix: srv_prefix.to_owned(), user_name: usr_name }
49 }
50
51 pub fn store_secret(&self, service: &str, secret: &str) -> Result<(), Box<dyn Error>> {
60 let entry = Entry::new(&self.get_service_id(service), &self.user_name)?;
61 let set_result = entry.set_password(secret);
62 if set_result.is_ok(){
63 return Ok(())
64 }
65
66 #[cfg(target_os = "windows")]
67 {
68 use std::{fs, path::PathBuf};
69 use windows_dpapi::{encrypt_data, Scope};
70
71 let file_name = self.get_service_id(service);
72 let secrets_path = get_local_usr_data_path( Some(option_env!("CARGO_PKG_NAME").unwrap_or("bts_storage")),Some(".ces"),true)?;
73 let encrypted_secret = encrypt_data(secret.as_bytes(), Scope::User)?;
75 let mut file = PathBuf::from(secrets_path);
77 file.push(file_name);
78 fs::write(file.clone(), &encrypted_secret)?;
79 entry.set_password(&format!("{}{}",PATH_PREFIX, file.to_str().unwrap_or(".bt_stored_secret")))?;
80 return Ok(())
81 }
82
83 #[cfg(not(any(target_os = "windows")))]
85 {
86 return Err(get_error!("store_secret","Cannot save Secret. Error: {}",set_result.unwrap_err()).into())
87 }
88 }
89
90 pub fn retrieve_secret(&self, service: &str) -> Result<String, Box<dyn Error>> {
98 let entry = Entry::new(&self.get_service_id(service), &self.user_name)?;
99 let secret = entry.get_password()?;
100 let result = secret.strip_prefix(PATH_PREFIX);
101 if result.is_none(){
102 return Ok(secret)
103 }
104
105
106 #[cfg(target_os = "windows")]
108 {
109 use windows_dpapi::{Scope, decrypt_data};
110 let file = result.unwrap_or(&secret);
111 let path = PathBuf::from(file);
112 let encrypted_blob = fs::read(path)?;
114 let decrypt_secret = decrypt_data(&encrypted_blob, Scope::User)?;
115 return Ok(String::from_utf8(decrypt_secret)?)
116 }
117
118 Ok(secret)
119 }
120
121 pub fn store_attribute_set(&self, service: &str, attributes: &HashSet<String>) -> Result<(), Box<dyn Error>> {
130 let map = serde_json::to_string(attributes)?;
131 self.store_secret(service, &map)
132 }
133
134 pub fn retrieve_attribute_set(&self,service: &str) -> Result<HashSet<String>, Box<dyn Error>>{
142 let map =self.retrieve_secret(service)?;
143 let set_from_values: HashSet<String> = serde_json::from_str(&map)?;
144 Ok(set_from_values)
145 }
146
147
148 pub fn delete_secret(&self, service: &str) -> Result<(), Box<dyn Error>> {
156 let entry = Entry::new(&self.get_service_id(service), &self.user_name)?;
157 let secret = entry.get_password()?;
158 let result = secret.strip_prefix(PATH_PREFIX);
159 let _r = entry.delete_credential()?;
160 if result.is_none(){
161 return Ok(())
162 }
163
164 let _r = fs::remove_file(result.unwrap_or(&secret))?;
165 Ok(())
166 }
167}
168
169const ITERATIONS: u32 = 100_000;
171
172const KEY_LEN: usize = 32;
174
175#[derive(Debug)]
177pub struct SecretCipher{
178 salt: String,
179}
180
181impl SecretCipher {
182 pub fn new() -> Self{
187 const SECRET_CIPHER_SERVICE_PREFIX: &str = "secret_cipher_service";
188 const SALT: &str = "SALT";
189 let sv = SecretVault::new(SECRET_CIPHER_SERVICE_PREFIX);
190 let s = sv.retrieve_secret(SALT);
191
192 let cipher_salt = if s.is_ok() {
193 s.unwrap()
194 } else {
195 let new_salt = generate_url_safe_string(12);
196 sv.store_secret(SALT, &new_salt).expect(&get_fatal!("new","Cannot securely save encryption data!"));
197 new_salt
198 };
199 SecretCipher{ salt:cipher_salt }
200 }
201
202 fn derive_key(&self, password: &str) -> [u8; KEY_LEN] {
210 let mut key = [0u8; KEY_LEN];
211 pbkdf2_hmac::<sha2::Sha256>(password.as_bytes(), self.salt.as_bytes(), ITERATIONS, &mut key);
212 key
213 }
214
215 pub fn encrypt_secret(&self, secret: &str, key_str: &str) -> Result<(String, String),Box< dyn Error>> {
224 let key_hash: &[u8; 32] = &self.derive_key(key_str);
226 let key: &Key<Aes256Gcm> = key_hash.into();
227 let cipher = Aes256Gcm::new(key);
228
229 let nonce = Aes256Gcm::generate_nonce(&mut OsRng); let ciphertext = match cipher.encrypt(&nonce, secret.as_bytes()){
235 Ok(c) => c,
236 Err(e) => return Err(get_error!("encrypt_secret","Unable to encrypt secret. Error: {}",e).into()),
237 };
238
239 Ok( ( String::from_utf8(slice_encode(&ciphertext))?, String::from_utf8(slice_encode(&nonce))? ) )
243 }
244
245 pub fn decrypt_secret(&self, ciphertext_b91: &str, nonce_b91: &str, key_str: &str) -> Result<String, Box< dyn Error>>{
255 let key_hash: &[u8; 32] = &self.derive_key(key_str);
256 let key: &Key<Aes256Gcm> = key_hash.into();
257 let cipher = Aes256Gcm::new(key);
258
259 let nonce_bytes = slice_decode(nonce_b91.as_bytes()); let nonce = Nonce::from_slice(&nonce_bytes);
261
262 let ciphertext = slice_decode(ciphertext_b91.as_bytes()); match cipher.decrypt(nonce, ciphertext.as_ref()){
264 Ok(plaintext) => Ok(String::from_utf8(plaintext)?),
265 Err(e) => {
266 log_error!("decrypt_secret","");
267 Err(get_error!("decrypt_secret","Decryption failed. Error: {}", e).into())
268 },
269 }
270 }
271}
272
273
274#[cfg(test)]
275mod enc_dec_tests {
276 use bt_logger::{build_logger, log_verbose};
277
278 use super::*;
279
280 #[test]
281 fn test_encrypt_decrypt_roundtrip() {
282 build_logger("BACHUETECH", "enc_dec_tests", bt_logger::LogLevel::VERBOSE, bt_logger::LogTarget::STD_ERROR, None);
283
284 let cipher = SecretCipher::new();
285 let key = "my-auth0-client-secret";
286 let secret = "super-sensitive-token";
287
288 let (encrypted, nonce) = cipher.encrypt_secret(secret, key).unwrap();
289 let decrypted = cipher.decrypt_secret(&encrypted, &nonce, key);
290
291 assert_eq!(decrypted.unwrap(), secret);
292 }
293
294 #[test]
295 fn test_decrypt_with_wrong_key_fails() {
296 build_logger("BACHUETECH", "enc_dec_tests", bt_logger::LogLevel::VERBOSE, bt_logger::LogTarget::STD_OUT, None);
297 let cipher = SecretCipher::new();
298 let key = "correct-key";
299 let wrong_key = "wrong-key";
300 let secret = "top-secret";
301
302 let (encrypted, nonce) = cipher.encrypt_secret(secret, key).unwrap();
303 let result = cipher.decrypt_secret(&encrypted, &nonce, wrong_key);
304 log_verbose!("test_decrypt_with_wrong_key_fails","DEC: {:?}",result);
305 assert!(result.is_err(), "Decryption should fail with wrong key");
306 }
307
308 #[test]
309 fn test_decrypt_outputs_different_times_success() {
310 build_logger("BACHUETECH", "enc_dec_tests", bt_logger::LogLevel::VERBOSE, bt_logger::LogTarget::STD_ERROR, None);
311 let cipher = SecretCipher::new();
312 let key = "same-key";
313 let secret = "same-secret";
314
315 let (enc1, _) = cipher.encrypt_secret(secret, key).unwrap();
316 let (enc2, _) = cipher.encrypt_secret(secret, key).unwrap();
317
318 assert_ne!(enc1, enc2, "Ciphertext should not differ due to dec times");
319 }
320
321
322 #[test]
323 fn test_decrypt_outputs_different_ciphertext_each_time() {
324 build_logger("BACHUETECH", "enc_dec_tests", bt_logger::LogLevel::VERBOSE, bt_logger::LogTarget::STD_ERROR, None);
325 let cipher = SecretCipher::new();
326 let key = "same-key";
327 let secret = "same-secret";
328
329 let (enc1, nonce) = cipher.encrypt_secret(secret, key).unwrap();
330 let cipher = SecretCipher::new();
331 let dec1 = cipher.decrypt_secret(&enc1, &nonce, key);
332 let cipher = SecretCipher::new();
333 let dec2 = cipher.decrypt_secret(&enc1, &nonce, key);
334
335 log_verbose!("test_decrypt_outputs_different_ciphertext_each_time","dec1: {:?} dec2: {:?}",dec1,dec2);
336 let d1 = dec1.unwrap();
337 assert_eq!(d1, secret);
338 let d2 = dec2.unwrap();
339 log_verbose!("test_decrypt_outputs_different_ciphertext_each_time","d1: '{}'. d2: '{}'",d1,d2);
340 assert_eq!(d1, d2, "Ciphertext should not differ secret");
341 }
342}
343
344
345#[cfg(test)]
346mod secret_vault_tests {
347 use super::*;
348 use std::{collections::HashSet, sync::Once};
349
350 use bt_logger::{build_logger, log_verbose, LogLevel, LogTarget};
351 use ctor::dtor;
352
353
354 static INIT: Once = Once::new();
355 fn ini_log() {
356 INIT.call_once(|| {
357 build_logger("BACHUETECH", "UNIT TEST RUST JOB_SEEK_TRK_DX", LogLevel::VERBOSE, LogTarget::STD_ERROR, None );
358 });
359 }
360
361 #[test]
362 fn test_new_secret_vault() {
363 let vault = SecretVault::new("test_prefix");
364 assert_eq!(vault.service_prefix, "test_prefix");
365 assert!(!vault.user_name.is_empty());
368 }
369
370 #[test]
371 fn test_get_service_id() {
372 let vault = SecretVault::new("test_prefix");
373 let service_name = "test_service";
374 let service_id = vault.get_service_id(service_name);
375
376 assert!(!service_id.is_empty());
378
379 assert!(service_id.len() <= MAX_SIZE);
381 }
382
383 #[test]
384 fn test_get_service_id_with_long_service_name() {
385 ini_log();
386 let vault = SecretVault::new("test_prefix");
387 let long_service_name = "a".repeat(MAX_SIZE + 300); let service_id = vault.get_service_id(&long_service_name);
389 assert!(service_id.len() <= MAX_SIZE);
391 }
392
393 #[test]
394 fn test_store_secret() {
395 let vault = SecretVault::new("test_prefix");
397 let service_name = "test_service";
398 let secret = "test_secret";
399
400 let result = vault.store_secret(service_name, secret);
401 assert!(result.is_ok()); }
403
404
405 #[test]
406 fn test_store_long_secret() {
407 ini_log();
408 let vault = SecretVault::new("test_prefix");
410 let service_name = format!("{}{}","bt_secure_storage_token","test_service_long");
411 let secret = "easyasdfAiOiJKV1QiLCJub25jZSI6I1234GN5NUlYVmI3eG9hMEZJOUdjMnVJVnIzRjZ5anhhTlRQd3dhQTIwYkkiLCJhbGciOiJSUzI1NiIsIng1dCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hMDA1ZWExNi1lZmZkLTRiMWMtYWRjMi00Njc2NDE4NjEwYzIvIiwiaWF0IjoxNzYzOTkxMDI4LCJuYmYiOjE3NjM5OTEwMjgsImV4cCI6MTc2Mzk5NjEwOSwiYWNjdCI6MCwiYWNyIjoiMSIsImFpbyI6IkFZUUFlLzhhQUFBQTZ2a3ZPZFJrSXNteUtIdEcvakYxd2dTRGpZeFl6c3hGNGl3WDN2QndrU1QrT0NRMEhJckdMR3Y2R0RzRU9jRm5DTkdBS1pOOStwRzNJd1RLUXlEdEdqaDExUWVwSmI3cDhzQWRFenVIRHVlbHFaUFpwaUh1RTBlY09SdTBheW9DUDVDMjRnNFVDWkMrSSs2Sy9tbDZxQW04aHE2Z3Rma0U1dk92R3N6cCtjaz0iLCJhbXIiOlsib3RwIl0sImFwcF9kaXNwbGF5bmFtZSI6IkpvYiBUYWNrbGUiLCJhcHBpZCI6ImE5NmJmNzFjLTNhYWEtNDc4NS05YjUxLWQ3OThmYWM1MWViYiIsImFwcGlkYWNyIjoiMCIsImVtYWlsIjoiY2VhbHZhcmV6cm9AaG90bWFpbC5jb20iLCJpZHAiOiJtYWlsIiwiaWR0eXAiOiJ1c2VyIiwiaXBhZGRyIjoiMTczLjIzMy4yLjI3IiwibmFtZSI6IkNlc2FyIiwib2lkIjoiMjY2NjUzMGUtOTJmOC00ZTQzLTk2OTQtOGVhM2Y1OGZlYzc0IiwicGxhdGYiOiIzIiwicHVpZCI6IjEwMDMyMDA1NThFM0Y4NTUiLCJyaCI6IjEuQWJnQUZ1b0ZvUDN2SEV1dHdrWjJRWVlRd2dNQUFBQUFBQUFBd0FBQUFBQUFBQURWQWNPNEFBLiIsInNjcCI6ImVtYWlsIG9wZW5pZCBwcm9maWxlIiwic2lkIjoiMDBhYjhhMjktNWQwOC01MTYxLTdmNzEtNWI5NTQ5MGY1NmU1Iiwic3ViIjoiWFFlWTFpV0NWRTJRa21rSndycllTVElVUGhQNXY0eGM3anphcEQwR1M3MCIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJOQSIsInRpZCI6ImEwMDVlYTE2LWVmZmQtNGIxYy1hZGMyLTQ2NzY0MTg2MTBjMiIsInVuaXF1ZV9uYW1lIjoibWFpbCNjZWFsdmFyZXpyb0Bob3RtYWlsLmNvbSIsInV0aSI6Ik1tQnZPR0g4aDBlRGpGb2tiQzREQUEiLCJ2ZXIiOiIxLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfYWNkIjoxNzYzNzM2MjgwLCJ4bXNfYWN0X2ZjdCI6IjMgOSIsInhtc19mdGQiOiJUNUxkQnppS0VSU1pibWZLMTFhcWEycDltWmFIXzRrUmJlX0Z5bjZnbGpzQmRYTmxZWE4wTFdSemJYTSIsInhtc19pZHJlbCI6IjE0IDEiLCJ4bXNfc3QiOnsic3ViIjoiWjA4ZW5uM1c5VW5ac3dvR1R0NERWTDB5U2Y1Q2dfLXhIbGNDRTlNdGZhWSJ9LCJ4bXNfc3ViX2ZjdCI6IjMgMTYiLCJ4bXNfdGNkdCI6MTc2MzY4MjQ3MSwieG1zX3RudF9mY3QiOiIzIDgifQ.NrvFEXwMEpublzKJToNkbj6x8QXzOmOHKfzIxLEkq4b_cjLEPx4Y5qr32AmIQ3n-xysWO93sZDO7tBFeAMi90hso9LkGJ321O7WsjGFaQ4fBog_UsX-f2NII4TyS1soEshlL1odQM-2WItPV-EyF9iuiJqhdsd-6dC38_0V1G4EZz0IrmlIfYlqRBHfEtf7Mam9KHgZ5Q7fUc2bJA1sfl61SzADaAsa51vo1b8v87Db86NqfdedviEI3ktjrZrJ8eMX0Ekx8sNM9o2jfUkNMb445kd4J4VUiCBrgVOB5loMD6Zrc9DtmjJjKeHSjXbLp-r1ECDnJm0EvLzigj555555";
412
413 let result = vault.store_secret(&service_name, secret);
414 assert!(result.is_ok()); }
416
417 #[test]
418 fn test_retrieve_unknown_secret() {
419 let vault = SecretVault::new("test_prefix");
421 let service_name = "invalid_test_service_unknown";
422
423 let result = vault.retrieve_secret(service_name);
425 assert!(result.is_err()); }
427
428 #[test]
429 fn test_store_attribute_set() {
430 let vault = SecretVault::new("test_prefix");
432 let mut attributes = HashSet::new();
433 attributes.insert("attr1".to_string());
434 attributes.insert("attr2".to_string());
435 let service_name = "test_service";
436
437 let result = vault.store_attribute_set(service_name, &attributes);
438 assert!(result.is_ok());
439 }
440
441 #[test]
442 fn test_delete_secret() {
443 ini_log();
444 let vault = SecretVault::new("test_prefix");
446 let service_name = "test_service_delete";
447 let _ = vault.store_secret(service_name, "delete");
448
449 let result = vault.delete_secret(service_name);
450 log_verbose!("test_delete_secret","delete rest: {:?}",result);
451 assert!(result.is_ok());
452 }
453
454
455 #[test]
456 fn test_store_and_retrieve_secret() {
457 ini_log();
458 let vault = SecretVault::new("test_prefix");
460 let service_name = "test_service_str";
461 let secret = "test_secret";
462
463 let store_result = vault.store_secret(service_name, secret);
465 assert!(store_result.is_ok());
466
467 let retrieve_result = vault.retrieve_secret(service_name);
469log_verbose!("test_store_and_retrieve_secret","Retrieved Secret: {:?}",retrieve_result);
470 assert!(retrieve_result.is_ok());
471 assert_eq!(retrieve_result.unwrap(), secret);
472 }
473
474 #[test]
475 fn test_store_and_retrieve_attribute_set() {
476 ini_log();
477 let vault = SecretVault::new("test_prefix");
479 let mut attributes = HashSet::new();
480 attributes.insert("attr1".to_string());
481 attributes.insert("attr2".to_string());
482 let service_name = "test_service_set";
483
484 let store_result = vault.store_attribute_set(service_name, &attributes);
486 assert!(store_result.is_ok());
487
488 let retrieve_result = vault.retrieve_attribute_set(service_name);
490 log_verbose!("test_store_and_retrieve_attribute_set","Retrieve Result: {:?}",retrieve_result);
491 assert!(retrieve_result.is_ok());
492 assert_eq!(retrieve_result.unwrap(), attributes);
493 }
494
495 #[dtor]
496 fn after_all() {
497 ini_log();
498 let vault = SecretVault::new("test_prefix");
499 vault.delete_secret("test_service_set");
500 vault.delete_secret("test_service_str");
501 vault.delete_secret("test_service");
502 vault.delete_secret("invalid_test_service_unknown");
503 log_verbose!("clean_storage","Done Cleaning");
504 }
505}