auths_cli/core/
pubkey_cache.rs1use anyhow::{Context, Result, anyhow};
9use std::fs;
10use std::path::PathBuf;
11
12fn get_pubkey_cache_dir() -> Result<PathBuf> {
14 Ok(auths_core::paths::auths_home()
15 .map_err(|e| anyhow!(e))?
16 .join("pubkeys"))
17}
18
19fn get_cache_path(alias: &str) -> Result<PathBuf> {
21 let dir = get_pubkey_cache_dir()?;
22 let safe_alias = alias.replace(['/', '\\', '\0'], "_");
24 Ok(dir.join(format!("{}.pub", safe_alias)))
25}
26
27pub fn cache_pubkey(alias: &str, pubkey: &[u8]) -> Result<()> {
39 if pubkey.len() != 32 {
40 return Err(anyhow!(
41 "Invalid public key length: expected 32 bytes, got {}",
42 pubkey.len()
43 ));
44 }
45
46 let cache_dir = get_pubkey_cache_dir()?;
47 fs::create_dir_all(&cache_dir)
48 .with_context(|| format!("Failed to create pubkey cache directory: {:?}", cache_dir))?;
49
50 let cache_path = get_cache_path(alias)?;
51 let hex_pubkey = hex::encode(pubkey);
52
53 fs::write(&cache_path, &hex_pubkey)
54 .with_context(|| format!("Failed to write pubkey cache file: {:?}", cache_path))?;
55
56 Ok(())
57}
58
59pub fn get_cached_pubkey(alias: &str) -> Result<Option<Vec<u8>>> {
69 let cache_path = get_cache_path(alias)?;
70
71 if !cache_path.exists() {
72 return Ok(None);
73 }
74
75 let hex_pubkey = fs::read_to_string(&cache_path)
76 .with_context(|| format!("Failed to read pubkey cache file: {:?}", cache_path))?;
77
78 let pubkey = hex::decode(hex_pubkey.trim())
79 .with_context(|| format!("Invalid hex in pubkey cache file: {:?}", cache_path))?;
80
81 if pubkey.len() != 32 {
82 return Err(anyhow!(
83 "Invalid cached public key length in {:?}: expected 32 bytes, got {}",
84 cache_path,
85 pubkey.len()
86 ));
87 }
88
89 Ok(Some(pubkey))
90}
91
92pub fn clear_cached_pubkey(alias: &str) -> Result<bool> {
104 let cache_path = get_cache_path(alias)?;
105
106 if !cache_path.exists() {
107 return Ok(false);
108 }
109
110 fs::remove_file(&cache_path)
111 .with_context(|| format!("Failed to remove pubkey cache file: {:?}", cache_path))?;
112
113 Ok(true)
114}
115
116pub fn clear_all_cached_pubkeys() -> Result<usize> {
124 let cache_dir = get_pubkey_cache_dir()?;
125
126 if !cache_dir.exists() {
127 return Ok(0);
128 }
129
130 let mut count = 0;
131 for entry in fs::read_dir(&cache_dir)
132 .with_context(|| format!("Failed to read pubkey cache directory: {:?}", cache_dir))?
133 {
134 let entry = entry?;
135 let path = entry.path();
136 if path.extension().is_some_and(|ext| ext == "pub") {
137 fs::remove_file(&path)
138 .with_context(|| format!("Failed to remove cache file: {:?}", path))?;
139 count += 1;
140 }
141 }
142
143 Ok(count)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
154 fn test_get_cache_path_sanitizes_alias() {
155 let path = get_cache_path("test/alias").unwrap();
156 assert!(path.to_string_lossy().contains("test_alias.pub"));
157 }
158
159 #[test]
160 fn test_cache_pubkey_validates_length() {
161 let result = cache_pubkey("test", &[0u8; 16]);
162 assert!(result.is_err());
163 assert!(
164 result
165 .unwrap_err()
166 .to_string()
167 .contains("expected 32 bytes")
168 );
169 }
170}