auths_cli/core/
pubkey_cache.rs1use anyhow::{Context, Result, anyhow};
9use std::fs;
10use std::path::PathBuf;
11
12use super::fs::{create_restricted_dir, write_sensitive_file};
13
14fn get_pubkey_cache_dir() -> Result<PathBuf> {
16 Ok(auths_sdk::paths::auths_home()
17 .map_err(|e| anyhow!(e))?
18 .join("pubkeys"))
19}
20
21fn get_cache_path(alias: &str) -> Result<PathBuf> {
23 let dir = get_pubkey_cache_dir()?;
24 let safe_alias = alias.replace(['/', '\\', '\0'], "_");
26 Ok(dir.join(format!("{}.pub", safe_alias)))
27}
28
29pub fn cache_pubkey(alias: &str, pubkey: &[u8], curve: auths_crypto::CurveType) -> Result<()> {
39 let cache_dir = get_pubkey_cache_dir()?;
40 create_restricted_dir(&cache_dir)
41 .with_context(|| format!("Failed to create pubkey cache directory: {:?}", cache_dir))?;
42
43 let cache_path = get_cache_path(alias)?;
44 let content = format!("{}:{}", curve, hex::encode(pubkey));
45
46 write_sensitive_file(&cache_path, &content)
47 .with_context(|| format!("Failed to write pubkey cache file: {:?}", cache_path))?;
48
49 Ok(())
50}
51
52pub fn get_cached_pubkey(alias: &str) -> Result<Option<(Vec<u8>, auths_crypto::CurveType)>> {
60 let cache_path = get_cache_path(alias)?;
61
62 if !cache_path.exists() {
63 return Ok(None);
64 }
65
66 let content = fs::read_to_string(&cache_path)
67 .with_context(|| format!("Failed to read pubkey cache file: {:?}", cache_path))?;
68
69 let trimmed = content.trim();
70
71 let (curve, hex_str) = if let Some((curve_tag, hex_part)) = trimmed.split_once(':') {
72 let curve = match curve_tag {
73 "ed25519" => auths_crypto::CurveType::Ed25519,
74 "p256" => auths_crypto::CurveType::P256,
75 other => {
76 return Err(anyhow!(
77 "Unknown curve in cache file {:?}: {other}",
78 cache_path
79 ));
80 }
81 };
82 (curve, hex_part)
83 } else {
84 (auths_crypto::CurveType::Ed25519, trimmed)
86 };
87
88 let pubkey = hex::decode(hex_str)
89 .with_context(|| format!("Invalid hex in pubkey cache file: {:?}", cache_path))?;
90
91 Ok(Some((pubkey, curve)))
92}
93
94pub fn clear_cached_pubkey(alias: &str) -> Result<bool> {
106 let cache_path = get_cache_path(alias)?;
107
108 if !cache_path.exists() {
109 return Ok(false);
110 }
111
112 fs::remove_file(&cache_path)
113 .with_context(|| format!("Failed to remove pubkey cache file: {:?}", cache_path))?;
114
115 Ok(true)
116}
117
118pub fn clear_all_cached_pubkeys() -> Result<usize> {
126 let cache_dir = get_pubkey_cache_dir()?;
127
128 if !cache_dir.exists() {
129 return Ok(0);
130 }
131
132 let mut count = 0;
133 for entry in fs::read_dir(&cache_dir)
134 .with_context(|| format!("Failed to read pubkey cache directory: {:?}", cache_dir))?
135 {
136 let entry = entry?;
137 let path = entry.path();
138 if path.extension().is_some_and(|ext| ext == "pub") {
139 fs::remove_file(&path)
140 .with_context(|| format!("Failed to remove cache file: {:?}", path))?;
141 count += 1;
142 }
143 }
144
145 Ok(count)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
156 fn test_get_cache_path_sanitizes_alias() {
157 let path = get_cache_path("test/alias").unwrap();
158 assert!(path.to_string_lossy().contains("test_alias.pub"));
159 }
160
161 #[test]
162 fn test_get_cache_path_returns_pub_extension() {
163 let path = get_cache_path("mykey").unwrap();
164 assert!(path.to_string_lossy().ends_with("mykey.pub"));
165 }
166}