use std::collections::HashMap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use crate::browser::browser_cookie_credentials::{
read_safe_storage_password, read_windows_encryption_key,
};
use crate::browser::browser_cookie_crypto::derive_chromium_cookie_key;
pub type SafeStoragePasswordReader = Arc<dyn Fn(&str, &str) -> Result<String> + Send + Sync>;
pub type WindowsKeyReader = Arc<dyn Fn(&Path) -> Result<Vec<u8>> + Send + Sync>;
#[derive(Clone)]
pub struct KeystoreHooks {
pub read_safe_storage_password: SafeStoragePasswordReader,
pub read_windows_encryption_key: WindowsKeyReader,
}
impl Default for KeystoreHooks {
fn default() -> Self {
Self {
read_safe_storage_password: Arc::new(read_safe_storage_password),
read_windows_encryption_key: Arc::new(read_windows_encryption_key),
}
}
}
impl fmt::Debug for KeystoreHooks {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("KeystoreHooks")
.finish_non_exhaustive()
}
}
pub type SourceKeyResolver = Arc<dyn Fn(&str) -> Result<Vec<u8>> + Send + Sync>;
pub(crate) fn create_source_key_resolver(
browser: &str,
platform: &str,
local_state_path: Option<PathBuf>,
hooks: KeystoreHooks,
) -> SourceKeyResolver {
let browser = browser.to_string();
let platform = platform.to_string();
let cache: Mutex<HashMap<String, Result<Vec<u8>, String>>> = Mutex::new(HashMap::new());
Arc::new(move |prefix: &str| {
let mut cache = cache
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let entry = cache.entry(prefix.to_string()).or_insert_with(|| {
resolve_source_key(
&browser,
&platform,
prefix,
local_state_path.as_deref(),
&hooks,
)
.map_err(|error| format!("{error:#}"))
});
entry.clone().map_err(|message| anyhow!(message))
})
}
fn resolve_source_key(
browser: &str,
platform: &str,
prefix: &str,
local_state_path: Option<&Path>,
hooks: &KeystoreHooks,
) -> Result<Vec<u8>> {
match platform {
"win32" => {
let path = local_state_path
.ok_or_else(|| anyhow!("The source Local State path is unknown"))?;
(hooks.read_windows_encryption_key)(path)
}
"linux" if prefix == "v10" => derive_chromium_cookie_key("peanuts", "linux"),
"linux" | "darwin" => {
let password = (hooks.read_safe_storage_password)(browser, platform)?;
derive_chromium_cookie_key(&password, platform)
}
_ => Err(anyhow!("OSCrypt keys are unsupported on {platform}")),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetKey {
pub key: Vec<u8>,
pub prefix: String,
}
pub(crate) fn resolve_target_key(
browser: &str,
platform: &str,
hooks: &KeystoreHooks,
) -> Result<TargetKey> {
if platform == "darwin" || platform == "linux" {
let password = (hooks.read_safe_storage_password)(browser, platform)?;
return Ok(TargetKey {
key: derive_chromium_cookie_key(&password, platform)?,
prefix: "v11".to_string(),
});
}
Err(anyhow!(
"resolve_target_key does not derive a Windows key; use create_windows_profile_key"
))
}
pub(crate) fn local_state_path_for_profile(profile_dir: &Path) -> PathBuf {
profile_dir
.parent()
.unwrap_or_else(|| Path::new(""))
.join("Local State")
}