keeper_secrets_manager_core/
caching.rs1use crate::crypto::CryptoUtils;
42use crate::custom_error::KSMRError;
43use crate::dto::{EncryptedPayload, KsmHttpResponse, TransmissionKey};
44use log::{debug, warn};
45use reqwest::blocking::Client;
46use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
47use std::env;
48use std::fs::{File, OpenOptions};
49use std::io::{Read, Write};
50use std::path::{Path, PathBuf};
51use std::str::FromStr;
52
53const DEFAULT_CACHE_FILE: &str = "ksm_cache.bin";
55
56pub fn get_cache_file_path() -> PathBuf {
58 let cache_dir = env::var("KSM_CACHE_DIR").unwrap_or_else(|_| ".".to_string());
59 Path::new(&cache_dir).join(DEFAULT_CACHE_FILE)
60}
61
62pub fn save_cache(data: &[u8]) -> Result<(), KSMRError> {
70 let cache_path = get_cache_file_path();
71
72 let mut file = OpenOptions::new()
73 .write(true)
74 .create(true)
75 .truncate(true)
76 .open(&cache_path)
77 .map_err(|e| KSMRError::CacheSaveError(format!("Failed to open cache file: {}", e)))?;
78
79 file.write_all(data)
80 .map_err(|e| KSMRError::CacheSaveError(format!("Failed to write cache: {}", e)))?;
81
82 file.sync_all()
84 .map_err(|e| KSMRError::CacheSaveError(format!("Failed to sync cache: {}", e)))?;
85
86 debug!("Cache saved to {:?}", cache_path);
87 Ok(())
88}
89
90pub fn get_cached_data() -> Option<Vec<u8>> {
95 let cache_path = get_cache_file_path();
96
97 if !cache_path.exists() {
98 return None;
99 }
100
101 let mut file = File::open(&cache_path).ok()?;
102 let mut data = Vec::new();
103 file.read_to_end(&mut data).ok()?;
104
105 debug!("Cache loaded from {:?}", cache_path);
106 Some(data)
107}
108
109pub fn clear_cache() -> Result<(), KSMRError> {
111 let cache_path = get_cache_file_path();
112
113 if cache_path.exists() {
114 std::fs::remove_file(&cache_path)
115 .map_err(|e| KSMRError::CacheRetrieveError(format!("Failed to delete cache: {}", e)))?;
116 }
117
118 Ok(())
119}
120
121pub fn cache_exists() -> bool {
123 get_cache_file_path().exists()
124}
125
126pub fn make_caching_post_function(
150 client: reqwest::blocking::Client,
151) -> impl Fn(String, TransmissionKey, EncryptedPayload) -> Result<KsmHttpResponse, KSMRError>
152 + Send
153 + Sync
154 + 'static {
155 move |url, transmission_key, encrypted_payload| {
156 run_caching_logic(url, transmission_key, encrypted_payload, &client)
157 }
158}
159
160pub fn caching_post_function(
175 url: String,
176 transmission_key: TransmissionKey,
177 encrypted_payload: EncryptedPayload,
178) -> Result<KsmHttpResponse, KSMRError> {
179 let proxy_url = std::env::var("KSM_PROXY_URL").ok();
180 let mut client_builder = Client::builder();
181 if let Some(ref proxy) = proxy_url {
182 if let Ok(p) = reqwest::Proxy::all(proxy) {
183 client_builder = client_builder.proxy(p);
184 }
185 }
186 let client = client_builder
187 .build()
188 .map_err(|e| KSMRError::HTTPError(format!("Failed to build client: {}", e)))?;
189 run_caching_logic(url, transmission_key, encrypted_payload, &client)
190}
191
192fn run_caching_logic(
193 url: String,
194 transmission_key: TransmissionKey,
195 encrypted_payload: EncryptedPayload,
196 client: &Client,
197) -> Result<KsmHttpResponse, KSMRError> {
198 match make_http_request(client, url, transmission_key.clone(), encrypted_payload) {
200 Ok(response) if response.status_code == 200 => {
201 let mut cache_data = transmission_key.key.clone();
203 cache_data.extend_from_slice(&response.data);
204
205 if let Err(e) = save_cache(&cache_data) {
207 warn!("Failed to save cache: {}", e);
208 }
209
210 Ok(response)
211 }
212 Ok(response) => {
213 Ok(response)
215 }
216 Err(network_error) => {
217 warn!(
219 "Network request failed: {}, attempting to use cached data",
220 network_error
221 );
222
223 if let Some(cached_data) = get_cached_data() {
224 if cached_data.len() > 32 {
225 let cached_transmission_key = cached_data[0..32].to_vec();
228 let cached_response_data = cached_data[32..].to_vec();
229
230 debug!("Using cached data ({} bytes)", cached_response_data.len());
231
232 let decrypted_data =
234 CryptoUtils::decrypt_aes(&cached_response_data, &cached_transmission_key)
235 .map_err(|e| {
236 warn!("Failed to decrypt cached data: {}", e);
237 KSMRError::CryptoError(format!("Cache decryption failed: {}", e))
238 })?;
239
240 let re_encrypted_data =
242 CryptoUtils::encrypt_aes_gcm(&decrypted_data, &transmission_key.key, None)?;
243
244 debug!(
245 "Successfully decrypted cached data and re-encrypted with current transmission key"
246 );
247
248 return Ok(KsmHttpResponse {
250 status_code: 200,
251 data: re_encrypted_data,
252 http_response: Some("Cached response (re-encrypted)".to_string()),
253 });
254 }
255 }
256
257 Err(network_error)
259 }
260 }
261}
262
263fn make_http_request(
264 client: &Client,
265 url: String,
266 transmission_key: TransmissionKey,
267 encrypted_payload: EncryptedPayload,
268) -> Result<KsmHttpResponse, KSMRError> {
269 let mut headers = HeaderMap::new();
271 headers.insert(
272 HeaderName::from_str("Content-Type").unwrap(),
273 HeaderValue::from_str("application/octet-stream").unwrap(),
274 );
275 headers.insert(
276 HeaderName::from_str("PublicKeyId").unwrap(),
277 HeaderValue::from_str(&transmission_key.public_key_id).unwrap(),
278 );
279 headers.insert(
280 HeaderName::from_str("TransmissionKey").unwrap(),
281 HeaderValue::from_str(&crate::utils::bytes_to_base64(
282 &transmission_key.encrypted_key,
283 ))
284 .unwrap(),
285 );
286 headers.insert(
287 HeaderName::from_str("Authorization").unwrap(),
288 HeaderValue::from_str(&format!(
289 "Signature {}",
290 crate::utils::bytes_to_base64(&encrypted_payload.signature.to_bytes())
291 ))
292 .unwrap(),
293 );
294
295 let response = client
297 .post(&url)
298 .headers(headers)
299 .body(encrypted_payload.encrypted_payload.clone())
300 .send()
301 .map_err(|e| KSMRError::HTTPError(format!("HTTP request failed: {}", e)))?;
302
303 let status_code = response.status().as_u16();
304 let response_body = response
305 .bytes()
306 .map_err(|e| KSMRError::HTTPError(format!("Failed to read response: {}", e)))?
307 .to_vec();
308
309 Ok(KsmHttpResponse {
310 status_code,
311 data: response_body,
312 http_response: None,
313 })
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_cache_file_path() {
322 let path = get_cache_file_path();
323 assert!(path.to_str().unwrap().contains("ksm_cache.bin"));
324 }
325
326 #[test]
327 fn test_cache_operations() {
328 let _ = clear_cache();
330
331 assert!(!cache_exists());
333 assert!(get_cached_data().is_none());
334
335 let test_data = b"test cache data";
337 save_cache(test_data).ok();
338
339 assert!(cache_exists());
341
342 let loaded = get_cached_data();
344 assert!(loaded.is_some());
345 assert_eq!(loaded.unwrap(), test_data);
346
347 clear_cache().ok();
349 assert!(!cache_exists());
350 }
351}