Skip to main content

seher/crypto/
mod.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum CryptoError {
5    #[error("Failed to decrypt: {0}")]
6    DecryptionFailed(String),
7
8    #[error("Unsupported encryption version: {0}")]
9    UnsupportedVersion(String),
10
11    #[cfg(target_os = "macos")]
12    #[error("Keychain error: {0}")]
13    KeychainError(String),
14
15    #[cfg(target_os = "linux")]
16    #[error("Secret service error: {0}")]
17    SecretServiceError(String),
18
19    #[cfg(target_os = "windows")]
20    #[error("DPAPI error: {0}")]
21    DpapiError(String),
22}
23
24pub type Result<T> = std::result::Result<T, CryptoError>;
25
26#[cfg(target_os = "macos")]
27pub mod macos;
28
29#[cfg(target_os = "linux")]
30pub mod linux;
31
32#[cfg(target_os = "windows")]
33pub mod windows;
34
35/// # Errors
36///
37/// Returns an error if decryption fails, the OS is unsupported, or the result is not valid UTF-8.
38pub fn decrypt_cookie_value(encrypted_value: &[u8]) -> Result<String> {
39    if encrypted_value.is_empty() {
40        return Ok(String::new());
41    }
42
43    #[cfg(target_os = "macos")]
44    let value = macos::decrypt(encrypted_value)?;
45
46    #[cfg(target_os = "linux")]
47    let value = linux::decrypt(encrypted_value)?;
48
49    #[cfg(target_os = "windows")]
50    let value = windows::decrypt(encrypted_value)?;
51
52    #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
53    return Err(CryptoError::UnsupportedVersion(
54        "Unsupported OS".to_string(),
55    ));
56
57    Ok(strip_chrome_value_prefix(&value))
58}
59
60/// Strip Chrome's cookie value format prefix (Chrome 130+).
61///
62/// Chrome stores cookie values with a prefix indicating the format:
63/// - `[digit]t` prefix: plaintext value (e.g. "0tyes" -> "yes")
64/// - `[digit]e`` prefix: encoded value (e.g. "1e`token" -> "token")
65fn strip_chrome_value_prefix(value: &str) -> String {
66    let bytes = value.as_bytes();
67    if bytes.len() >= 2 && bytes[0].is_ascii_digit() {
68        if bytes[1] == b't' {
69            return value[2..].to_string();
70        }
71        if bytes[1] == b'e' && bytes.len() >= 3 && bytes[2] == b'`' {
72            return value[3..].to_string();
73        }
74    }
75    value.to_string()
76}