use std::sync::LazyLock;
use aes::cipher::{block_padding, BlockDecryptMut, KeyIvInit};
use pbkdf2::pbkdf2_hmac;
use secret_service::{EncryptionType, SecretService};
use tinyufo::TinyUfo;
use crate::error::{CryptoError, Result};
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
#[expect(clippy::empty_line_after_doc_comments, reason = "it not use")]
#[derive(Clone)]
#[derive(Debug)]
#[derive(Default)]
#[derive(PartialEq, Eq)]
pub struct Decrypter {
pass_v11: &'static [u8],
}
impl Decrypter {
pub const fn pass_v11(&self) -> &[u8] {
self.pass_v11
}
}
static CACHE_PASSWD: LazyLock<TinyUfo<&str, &'static [u8]>> =
LazyLock::new(|| TinyUfo::new_compact(10, 10));
impl Decrypter {
pub async fn build<F, N>(safe_storage: &str, need: N) -> Result<Self>
where
N: Into<Option<F>> + Send,
F: Fn(&str) -> bool + Send,
{
let pass_v11 = Self::get_pass(safe_storage, need)
.await
.unwrap_or(Self::PASSWORD_V10);
Ok(Self { pass_v11 })
}
async fn get_pass<F, N>(safe_storage: &str, need: N) -> Result<&'static [u8]>
where
N: Into<Option<F>> + Send,
F: Fn(&str) -> bool + Send,
{
if let Some(v) = CACHE_PASSWD.get(&safe_storage) {
return Ok(v);
}
let ss = SecretService::connect(EncryptionType::Dh).await?;
let collection = ss.get_default_collection().await?;
if collection.is_locked().await? {
collection.unlock().await?;
}
let coll = collection.get_all_items().await?;
let predicate: Option<F> = need.into();
for item in coll {
let Ok(label) = item.get_label().await
else {
continue;
};
if let Some(cache_it) = &predicate {
if cache_it(&label) || label == safe_storage {
let Ok(s) = item.get_secret().await
else {
continue;
};
let s = s.leak();
CACHE_PASSWD.put(&label, s, 1);
}
}
else if label == safe_storage {
let Ok(s) = item.get_secret().await
else {
continue;
};
let s = s.leak();
CACHE_PASSWD.put(&label, s, 1);
}
}
if let Some(v) = CACHE_PASSWD.get(&safe_storage) {
return Ok(v);
}
Ok(Self::PASSWORD_V10)
}
pub fn decrypt(&self, ciphertext: &mut [u8]) -> Result<String> {
let (pass, prefix_len) = if ciphertext.starts_with(Self::K_OBFUSCATION_PREFIX_V11) {
(self.pass_v11, Self::K_OBFUSCATION_PREFIX_V11.len())
}
else if ciphertext.starts_with(Self::K_OBFUSCATION_PREFIX_V10) {
(Self::PASSWORD_V10, Self::K_OBFUSCATION_PREFIX_V10.len())
}
else {
return Ok(String::from_utf8_lossy(ciphertext).to_string());
};
let mut key = [0_u8; 16];
let iv = [b' '; Self::K_IVBLOCK_SIZE_AES128];
pbkdf2_hmac::<sha1::Sha1>(pass, Self::K_SALT, Self::K_ENCRYPTION_ITERATIONS, &mut key);
let decrypter = Aes128CbcDec::new(&key.into(), &iv.into());
decrypter
.decrypt_padded_mut::<block_padding::Pkcs7>(&mut ciphertext[prefix_len..])
.map(|res| {
String::from_utf8(res.to_vec()).unwrap_or_else(|_e| {
#[cfg(feature = "tracing")]
tracing::info!("Decoding for chromium >= 130.x: {_e}");
String::from_utf8_lossy(&res[32..]).to_string()
})
})
.map_err(CryptoError::Unpadding)
}
}
impl Decrypter {
const PASSWORD_V10: &'static [u8] = b"peanuts";
const K_OBFUSCATION_PREFIX_V10: &'static [u8] = b"v10";
const K_OBFUSCATION_PREFIX_V11: &'static [u8] = b"v11";
const K_IVBLOCK_SIZE_AES128: usize = 16;
const K_SALT: &'static [u8] = b"saltysalt";
const K_ENCRYPTION_ITERATIONS: u32 = 1;
}