1use crate::crypto::{CryptoError, Result};
2use aes::cipher::{BlockDecryptMut, KeyIvInit};
3use hmac::{Hmac, KeyInit, Mac};
4use sha1::Sha1;
5
6type HmacSha1 = Hmac<Sha1>;
7
8type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
9
10const SALT: &[u8] = b"saltysalt";
11const IV: &[u8] = b" "; const KEY_LENGTH: usize = 16;
13const ITERATIONS: u32 = 1;
14
15pub fn decrypt(encrypted_value: &[u8]) -> Result<String> {
19 if encrypted_value.len() < 3 {
20 return Ok(String::new());
21 }
22
23 let version = &encrypted_value[..3];
24 match version {
25 b"v10" | b"v11" => {
26 let encrypted = &encrypted_value[3..];
27 let key = get_encryption_key();
28 decrypt_aes_cbc(&key, encrypted)
29 }
30 _ => String::from_utf8(encrypted_value.to_vec())
31 .map_err(|e| CryptoError::DecryptionFailed(e.to_string())),
32 }
33}
34
35fn get_encryption_key() -> Vec<u8> {
36 if let Ok(key) = get_key_from_secret_service() {
37 key
38 } else {
39 let mut key = vec![0u8; KEY_LENGTH];
41 pbkdf2_hmac_sha1(b"peanuts", SALT, ITERATIONS, &mut key);
42 key
43 }
44}
45
46fn get_key_from_secret_service() -> Result<Vec<u8>> {
47 use secret_service::blocking::SecretService;
48
49 let service = SecretService::connect(secret_service::EncryptionType::Dh)
50 .map_err(|e| CryptoError::SecretServiceError(format!("Failed to connect: {e}")))?;
51
52 let collection = service
53 .get_default_collection()
54 .map_err(|e| CryptoError::SecretServiceError(format!("Failed to get collection: {e}")))?;
55
56 let items = collection
57 .search_items(std::collections::HashMap::from([("application", "chrome")]))
58 .map_err(|e| CryptoError::SecretServiceError(format!("Failed to search items: {e}")))?;
59
60 if let Some(item) = items.first() {
61 let password = item
62 .get_secret()
63 .map_err(|e| CryptoError::SecretServiceError(format!("Failed to get secret: {e}")))?;
64
65 let mut key = vec![0u8; KEY_LENGTH];
66 pbkdf2_hmac_sha1(&password, SALT, ITERATIONS, &mut key);
67 return Ok(key);
68 }
69
70 Err(CryptoError::SecretServiceError(
71 "Chrome password not found".to_string(),
72 ))
73}
74
75#[expect(clippy::expect_used)]
76fn pbkdf2_hmac_sha1(password: &[u8], salt: &[u8], iterations: u32, output: &mut [u8]) {
77 const SHA1_LEN: usize = 20;
78 let block_count = output.len().div_ceil(SHA1_LEN);
79
80 for block_idx in 1..=block_count {
81 let mut mac = HmacSha1::new_from_slice(password).expect("HMAC accepts any key size");
83 mac.update(salt);
84 mac.update(
85 &u32::try_from(block_idx)
86 .expect("block index overflow")
87 .to_be_bytes(),
88 );
89 let mut u = mac.finalize().into_bytes();
90 let mut t = u;
91
92 for _ in 1..iterations {
93 let mut mac = HmacSha1::new_from_slice(password).expect("HMAC accepts any key size");
94 mac.update(&u);
95 u = mac.finalize().into_bytes();
96 for (t_val, u_val) in t.iter_mut().zip(u.iter()) {
97 *t_val ^= u_val;
98 }
99 }
100
101 let start = (block_idx - 1) * SHA1_LEN;
102 let end = (start + SHA1_LEN).min(output.len());
103 output[start..end].copy_from_slice(&t[..end - start]);
104 }
105}
106
107fn decrypt_aes_cbc(key: &[u8], encrypted: &[u8]) -> Result<String> {
108 let cipher = Aes128CbcDec::new(key.into(), IV.into());
109
110 let mut buffer = encrypted.to_vec();
111 let decrypted = cipher
112 .decrypt_padded_mut::<aes::cipher::block_padding::Pkcs7>(&mut buffer)
113 .map_err(|e| CryptoError::DecryptionFailed(format!("AES-CBC decryption failed: {e:?}")))?;
114
115 String::from_utf8(decrypted.to_vec())
116 .map_err(|e| CryptoError::DecryptionFailed(format!("UTF-8 conversion failed: {e}")))
117}