Skip to main content

seher/crypto/
linux.rs

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"                "; // 16 spaces
12const KEY_LENGTH: usize = 16;
13const ITERATIONS: u32 = 1;
14
15/// # Errors
16///
17/// Returns an error if decryption fails or if the value is not valid UTF-8.
18pub 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        // Fallback to default password "peanuts"
40        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        // U1 = HMAC(password, salt || INT(block_idx))
82        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}