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