use anyhow::{Context, Result, anyhow};
use std::fs;
use std::path::PathBuf;
use super::fs::{create_restricted_dir, write_sensitive_file};
fn get_pubkey_cache_dir() -> Result<PathBuf> {
Ok(auths_sdk::paths::auths_home()
.map_err(|e| anyhow!(e))?
.join("pubkeys"))
}
fn get_cache_path(alias: &str) -> Result<PathBuf> {
let dir = get_pubkey_cache_dir()?;
let safe_alias = alias.replace(['/', '\\', '\0'], "_");
Ok(dir.join(format!("{}.pub", safe_alias)))
}
pub fn cache_pubkey(alias: &str, pubkey: &[u8], curve: auths_crypto::CurveType) -> Result<()> {
let cache_dir = get_pubkey_cache_dir()?;
create_restricted_dir(&cache_dir)
.with_context(|| format!("Failed to create pubkey cache directory: {:?}", cache_dir))?;
let cache_path = get_cache_path(alias)?;
let content = format!("{}:{}", curve, hex::encode(pubkey));
write_sensitive_file(&cache_path, &content)
.with_context(|| format!("Failed to write pubkey cache file: {:?}", cache_path))?;
Ok(())
}
pub fn get_cached_pubkey(alias: &str) -> Result<Option<(Vec<u8>, auths_crypto::CurveType)>> {
let cache_path = get_cache_path(alias)?;
if !cache_path.exists() {
return Ok(None);
}
let content = fs::read_to_string(&cache_path)
.with_context(|| format!("Failed to read pubkey cache file: {:?}", cache_path))?;
let trimmed = content.trim();
let (curve, hex_str) = if let Some((curve_tag, hex_part)) = trimmed.split_once(':') {
let curve = match curve_tag {
"ed25519" => auths_crypto::CurveType::Ed25519,
"p256" => auths_crypto::CurveType::P256,
other => {
return Err(anyhow!(
"Unknown curve in cache file {:?}: {other}",
cache_path
));
}
};
(curve, hex_part)
} else {
(auths_crypto::CurveType::Ed25519, trimmed)
};
let pubkey = hex::decode(hex_str)
.with_context(|| format!("Invalid hex in pubkey cache file: {:?}", cache_path))?;
Ok(Some((pubkey, curve)))
}
pub fn clear_cached_pubkey(alias: &str) -> Result<bool> {
let cache_path = get_cache_path(alias)?;
if !cache_path.exists() {
return Ok(false);
}
fs::remove_file(&cache_path)
.with_context(|| format!("Failed to remove pubkey cache file: {:?}", cache_path))?;
Ok(true)
}
pub fn clear_all_cached_pubkeys() -> Result<usize> {
let cache_dir = get_pubkey_cache_dir()?;
if !cache_dir.exists() {
return Ok(0);
}
let mut count = 0;
for entry in fs::read_dir(&cache_dir)
.with_context(|| format!("Failed to read pubkey cache directory: {:?}", cache_dir))?
{
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "pub") {
fs::remove_file(&path)
.with_context(|| format!("Failed to remove cache file: {:?}", path))?;
count += 1;
}
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_cache_path_sanitizes_alias() {
let path = get_cache_path("test/alias").unwrap();
assert!(path.to_string_lossy().contains("test_alias.pub"));
}
#[test]
fn test_get_cache_path_returns_pub_extension() {
let path = get_cache_path("mykey").unwrap();
assert!(path.to_string_lossy().ends_with("mykey.pub"));
}
}