chromium_crypto/linux.rs
1use std::sync::LazyLock;
2
3use aes::cipher::{block_padding, BlockDecryptMut, KeyIvInit};
4use pbkdf2::pbkdf2_hmac;
5use secret_service::{EncryptionType, SecretService};
6use tinyufo::TinyUfo;
7
8use crate::error::{CryptoError, Result};
9
10// https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=32
11/// Key size required for 128 bit AES.
12// const K_DERIVED_KEY_SIZE_IN_BITS: u32 = 128;
13type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
14
15#[expect(clippy::empty_line_after_doc_comments, reason = "it not use")]
16// https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=50
17/// The UMA metric name for whether the false was decryptable with an empty key.
18// const K_METRIC_DECRYPTED_WITH_EMPTY_KEY: &[u8] = b"OSCrypt.Linux.DecryptedWithEmptyKey";
19
20#[derive(Clone)]
21#[derive(Debug)]
22#[derive(Default)]
23#[derive(PartialEq, Eq)]
24pub struct Decrypter {
25 pass_v11: &'static [u8],
26}
27
28impl Decrypter {
29 pub const fn pass_v11(&self) -> &[u8] {
30 self.pass_v11
31 }
32}
33
34static CACHE_PASSWD: LazyLock<TinyUfo<&str, &'static [u8]>> =
35 LazyLock::new(|| TinyUfo::new_compact(10, 10));
36
37impl Decrypter {
38 /// `safe_storage` example: Brave Safe Storage
39 pub async fn build<F, N>(safe_storage: &str, need: N) -> Result<Self>
40 where
41 N: Into<Option<F>> + Send,
42 F: Fn(&str) -> bool + Send,
43 {
44 let pass_v11 = Self::get_pass(safe_storage, need)
45 .await
46 .unwrap_or(Self::PASSWORD_V10);
47 Ok(Self { pass_v11 })
48 }
49
50 async fn get_pass<F, N>(safe_storage: &str, need: N) -> Result<&'static [u8]>
51 where
52 N: Into<Option<F>> + Send,
53 F: Fn(&str) -> bool + Send,
54 {
55 if let Some(v) = CACHE_PASSWD.get(&safe_storage) {
56 return Ok(v);
57 }
58
59 // initialize secret service (dbus connection and encryption session)
60 let ss = SecretService::connect(EncryptionType::Dh).await?;
61 let collection = ss.get_default_collection().await?;
62
63 if collection.is_locked().await? {
64 collection.unlock().await?;
65 }
66 let coll = collection.get_all_items().await?;
67
68 let predicate: Option<F> = need.into();
69
70 for item in coll {
71 let Ok(label) = item.get_label().await
72 else {
73 continue;
74 };
75 // TODO: use 1.88 let_chains
76 if let Some(cache_it) = &predicate {
77 if cache_it(&label) || label == safe_storage {
78 let Ok(s) = item.get_secret().await
79 else {
80 continue;
81 };
82
83 let s = s.leak();
84 CACHE_PASSWD.put(&label, s, 1);
85 }
86 }
87 else if label == safe_storage {
88 let Ok(s) = item.get_secret().await
89 else {
90 continue;
91 };
92 let s = s.leak();
93 CACHE_PASSWD.put(&label, s, 1);
94 }
95 }
96
97 if let Some(v) = CACHE_PASSWD.get(&safe_storage) {
98 return Ok(v);
99 }
100
101 Ok(Self::PASSWORD_V10)
102 }
103 // pub fn decrypt_yandex_password(&self, ciphertext: &mut [u8]) -> Result<String> {
104 // use aes_gcm::{
105 // aead::{generic_array::GenericArray, Aead},
106 // Aes256Gcm, KeyInit,
107 // };
108 //
109 // let cipher = Aes256Gcm::new(GenericArray::from_slice(Self::PASSWORD_V10));
110 //
111 // miette::bail!("decrypt failed")
112 // }
113
114 // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=72
115 pub fn decrypt(&self, ciphertext: &mut [u8]) -> Result<String> {
116 let (pass, prefix_len) = if ciphertext.starts_with(Self::K_OBFUSCATION_PREFIX_V11) {
117 (self.pass_v11, Self::K_OBFUSCATION_PREFIX_V11.len())
118 }
119 else if ciphertext.starts_with(Self::K_OBFUSCATION_PREFIX_V10) {
120 (Self::PASSWORD_V10, Self::K_OBFUSCATION_PREFIX_V10.len())
121 }
122 else {
123 return Ok(String::from_utf8_lossy(ciphertext).to_string());
124 };
125
126 let mut key = [0_u8; 16];
127 let iv = [b' '; Self::K_IVBLOCK_SIZE_AES128];
128
129 pbkdf2_hmac::<sha1::Sha1>(pass, Self::K_SALT, Self::K_ENCRYPTION_ITERATIONS, &mut key);
130 let decrypter = Aes128CbcDec::new(&key.into(), &iv.into());
131
132 decrypter
133 .decrypt_padded_mut::<block_padding::Pkcs7>(&mut ciphertext[prefix_len..])
134 .map(|res| {
135 String::from_utf8(res.to_vec()).unwrap_or_else(|e| {
136 tracing::info!("Decoding for chromium 130.x: {e}");
137 String::from_utf8_lossy(&res[32..]).to_string()
138 })
139 })
140 .map_err(CryptoError::Unpadding)
141 }
142}
143
144impl Decrypter {
145 // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=313
146 /// v10: hardcoded/default password
147 const PASSWORD_V10: &'static [u8] = b"peanuts";
148
149 // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=40
150 /// Prefixes for cypher text returned by obfuscation version.
151 /// We prefix the ciphertext with this string so that future data migration can detect
152 /// this and migrate to full encryption without data loss.
153 /// `K_OBFUSCATION_PREFIX_V10` means that the hardcoded password will be used.
154 /// `K_OBFUSCATION_PREFIX_V11` means that a password is/will be stored using an OS-level library (e.g Libsecret).
155 /// V11 will not be used if such a library is not available.
156 const K_OBFUSCATION_PREFIX_V10: &'static [u8] = b"v10";
157 const K_OBFUSCATION_PREFIX_V11: &'static [u8] = b"v11";
158
159 // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=38
160 /// Size of initialization vector for AES 128-bit.
161 const K_IVBLOCK_SIZE_AES128: usize = 16;
162
163 // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=29
164 /// Salt for Symmetric key derivation.
165 const K_SALT: &'static [u8] = b"saltysalt";
166 // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=35
167 /// Constant for Symmetric key derivation.
168 const K_ENCRYPTION_ITERATIONS: u32 = 1;
169}
170
171// #[cfg(test)]
172// mod tests {
173// use std::path::PathBuf;
174//
175// use base64::{engine::general_purpose, Engine};
176// use tokio::fs::read_to_string;
177//
178// use super::*;
179// use crate::chromium::local_state::YandexLocalState;
180// async fn yandex_passwd(path: PathBuf) -> Result<Vec<u8>> {
181// let string_str = read_to_string(path)
182// .await
183// ?;
184// let local_state: YandexLocalState = serde_json::from_str(&string_str)?;
185// let encrypted_key = general_purpose::STANDARD
186// .decode(
187// local_state
188// .os_crypt
189// .checker_state
190// .encrypted_data,
191// )
192// ?;
193// Ok(encrypted_key)
194// }
195// #[ignore = "need realy environment"]
196// #[tokio::test]
197// async fn yandex_passwd_work() {
198// use crate::{browser::info::ChromiumInfo, ChromiumBuilder};
199// let yandex_getter = ChromiumBuilder::new(Browser::Yandex)
200// .build()
201// .await
202// .unwrap();
203// let mut pss = yandex_passwd(yandex_getter.info().local_state())
204// .await
205// .unwrap();
206// let pass = yandex_getter
207// .decrypt(&mut pss)
208// .unwrap();
209//
210// assert_eq!(&pass, "0");
211// }
212// }