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
14type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
18
19#[expect(clippy::empty_line_after_doc_comments, reason = "it not use")]
20#[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 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 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 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 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 const PASSWORD_V10: &'static [u8] = b"peanuts";
164
165 const K_OBFUSCATION_PREFIX_V10: &'static [u8] = b"v10";
173 const K_OBFUSCATION_PREFIX_V11: &'static [u8] = b"v11";
174
175 const K_IVBLOCK_SIZE_AES128: usize = 16;
178
179 const K_SALT: &'static [u8] = b"saltysalt";
182 const K_ENCRYPTION_ITERATIONS: u32 = 1;
185}