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::{
10    error::{self, Result, Utf8Snafu},
11    Which,
12};
13
14// https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=32
15/// Key size required for 128 bit AES.
16// const K_DERIVED_KEY_SIZE_IN_BITS: u32 = 128;
17type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
18
19#[expect(clippy::empty_line_after_doc_comments, reason = "it not use")]
20// https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=50
21/// The UMA metric name for whether the false was decryptable with an empty key.
22// const K_METRIC_DECRYPTED_WITH_EMPTY_KEY: &[u8] = b"OSCrypt.Linux.DecryptedWithEmptyKey";
23
24#[derive(Clone)]
25#[derive(Debug)]
26#[derive(Default)]
27#[derive(PartialEq, Eq)]
28pub struct Decrypter {
29    pass_v11: &'static [u8],
30}
31
32impl Decrypter {
33    pub const fn pass_v11(&self) -> &[u8] {
34        self.pass_v11
35    }
36}
37
38static CACHE_PASSWD: LazyLock<TinyUfo<&str, &'static [u8]>> =
39    LazyLock::new(|| TinyUfo::new_compact(10, 10));
40
41impl Decrypter {
42    /// `safe_storage` example: Brave Safe Storage
43    pub async fn build<F, N>(safe_storage: &str, need: N) -> Result<Self>
44    where
45        N: Into<Option<F>> + Send,
46        F: Fn(&str) -> bool + Send,
47    {
48        let pass_v11 = Self::get_pass(safe_storage, need)
49            .await
50            .unwrap_or(Self::PASSWORD_V10);
51        Ok(Self { pass_v11 })
52    }
53
54    async fn get_pass<F, N>(safe_storage: &str, need: N) -> Result<&'static [u8]>
55    where
56        N: Into<Option<F>> + Send,
57        F: Fn(&str) -> bool + Send,
58    {
59        if let Some(v) = CACHE_PASSWD.get(&safe_storage) {
60            return Ok(v);
61        }
62
63        // initialize secret service (dbus connection and encryption session)
64        let ss = SecretService::connect(EncryptionType::Dh)
65            .await
66            .context(error::GetPassSnafu)?;
67        let collection = ss
68            .get_default_collection()
69            .await
70            .context(error::GetPassSnafu)?;
71
72        if collection
73            .is_locked()
74            .await
75            .context(error::GetPassSnafu)?
76        {
77            collection
78                .unlock()
79                .await
80                .context(error::GetPassSnafu)?;
81        }
82        let coll = collection
83            .get_all_items()
84            .await
85            .context(error::GetPassSnafu)?;
86
87        let predicate: Option<F> = need.into();
88
89        for item in coll {
90            let Ok(label) = item.get_label().await
91            else {
92                continue;
93            };
94            // TODO: use 1.88 let_chains
95            if let Some(cache_it) = &predicate {
96                if cache_it(&label) || label == safe_storage {
97                    let Ok(s) = item.get_secret().await
98                    else {
99                        continue;
100                    };
101
102                    let s = s.leak();
103                    CACHE_PASSWD.put(&label, s, 1);
104                }
105            }
106            else if label == safe_storage {
107                let Ok(s) = item.get_secret().await
108                else {
109                    continue;
110                };
111                let s = s.leak();
112                CACHE_PASSWD.put(&label, s, 1);
113            }
114        }
115
116        if let Some(v) = CACHE_PASSWD.get(&safe_storage) {
117            return Ok(v);
118        }
119
120        Ok(Self::PASSWORD_V10)
121    }
122
123    // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=72
124    pub fn decrypt(&self, ciphertext: &mut [u8], which: Which) -> Result<String> {
125        let (pass, prefix_len) = if ciphertext.starts_with(Self::K_OBFUSCATION_PREFIX_V11) {
126            (self.pass_v11, Self::K_OBFUSCATION_PREFIX_V11.len())
127        }
128        else if ciphertext.starts_with(Self::K_OBFUSCATION_PREFIX_V10) {
129            (Self::PASSWORD_V10, Self::K_OBFUSCATION_PREFIX_V10.len())
130        }
131        else {
132            return Ok(String::from_utf8_lossy(ciphertext).to_string());
133        };
134
135        let mut key = [0_u8; 16];
136        let iv = [b' '; Self::K_IVBLOCK_SIZE_AES128];
137
138        pbkdf2_hmac::<sha1::Sha1>(pass, Self::K_SALT, Self::K_ENCRYPTION_ITERATIONS, &mut key);
139        let decrypter = Aes128CbcDec::new(&key.into(), &iv.into());
140
141        decrypter
142            .decrypt_padded_mut::<block_padding::Pkcs7>(&mut ciphertext[prefix_len..])
143            .context(error::UnpaddingSnafu)
144            .map(|res| match which {
145                Which::Cookie => {
146                    if res.len() > 32 {
147                        String::from_utf8(res[32..].to_vec())
148                            .or_else(|_| crate::from_utf8_cold(res))
149                    }
150                    else {
151                        crate::from_utf8_cold(res)
152                    }
153                },
154                Which::Login => String::from_utf8(res.to_vec()),
155            })?
156            .context(Utf8Snafu)
157    }
158}
159
160impl Decrypter {
161    // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=313
162    /// v10: hardcoded/default password
163    const PASSWORD_V10: &'static [u8] = b"peanuts";
164
165    // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=40
166    /// Prefixes for cypher text returned by obfuscation version.
167    /// We prefix the ciphertext with this string so that future data migration can detect
168    /// this and migrate to full encryption without data loss.
169    /// `K_OBFUSCATION_PREFIX_V10` means that the hardcoded password will be used.
170    /// `K_OBFUSCATION_PREFIX_V11` means that a password is/will be stored using an OS-level library (e.g Libsecret).
171    /// V11 will not be used if such a library is not available.
172    const K_OBFUSCATION_PREFIX_V10: &'static [u8] = b"v10";
173    const K_OBFUSCATION_PREFIX_V11: &'static [u8] = b"v11";
174
175    // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=38
176    /// Size of initialization vector for AES 128-bit.
177    const K_IVBLOCK_SIZE_AES128: usize = 16;
178
179    // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=29
180    /// Salt for Symmetric key derivation.
181    const K_SALT: &'static [u8] = b"saltysalt";
182    // https://source.chromium.org/chromium/chromium/src/+/main:components/os_crypt/sync/os_crypt_linux.cc;l=35
183    /// Constant for Symmetric key derivation.
184    const K_ENCRYPTION_ITERATIONS: u32 = 1;
185}