1use crate::{CryptoError, SecureKey};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::{SystemTime, UNIX_EPOCH};
7use zeroize::Zeroizing;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct KeyMetadata {
12 pub key_id: String,
13 pub created_at: u64,
14 pub expires_at: Option<u64>,
15 pub algorithm: String,
16 pub purpose: String,
17 pub rotation_count: u32,
18}
19
20impl KeyMetadata {
21 pub fn new(key_id: String, algorithm: String, purpose: String) -> Self {
23 let created_at = SystemTime::now()
24 .duration_since(UNIX_EPOCH)
25 .unwrap()
26 .as_secs();
27
28 Self {
29 key_id,
30 created_at,
31 expires_at: None,
32 algorithm,
33 purpose,
34 rotation_count: 0,
35 }
36 }
37
38 pub fn with_expiration(mut self, seconds: u64) -> Self {
40 let now = SystemTime::now()
41 .duration_since(UNIX_EPOCH)
42 .unwrap()
43 .as_secs();
44 self.expires_at = Some(now + seconds);
45 self
46 }
47
48 pub fn is_expired(&self) -> bool {
50 if let Some(expires_at) = self.expires_at {
51 let now = SystemTime::now()
52 .duration_since(UNIX_EPOCH)
53 .unwrap()
54 .as_secs();
55 now >= expires_at
56 } else {
57 false
58 }
59 }
60
61 pub fn age_seconds(&self) -> u64 {
63 let now = SystemTime::now()
64 .duration_since(UNIX_EPOCH)
65 .unwrap()
66 .as_secs();
67 now.saturating_sub(self.created_at)
68 }
69}
70
71pub struct KeyStore {
73 keys: HashMap<String, (SecureKey, KeyMetadata)>,
74}
75
76impl KeyStore {
77 pub fn new() -> Self {
79 Self {
80 keys: HashMap::new(),
81 }
82 }
83
84 pub fn store_key(
86 &mut self,
87 key_id: String,
88 key: SecureKey,
89 metadata: KeyMetadata,
90 ) -> Result<(), CryptoError> {
91 if self.keys.contains_key(&key_id) {
92 return Err(CryptoError::HashingError(
93 "Key ID already exists".to_string(),
94 ));
95 }
96 self.keys.insert(key_id, (key, metadata));
97 Ok(())
98 }
99
100 pub fn get_key(&self, key_id: &str) -> Option<(&SecureKey, KeyMetadata)> {
102 self.keys
103 .get(key_id)
104 .map(|(key, meta)| (key, meta.clone()))
105 }
106
107 pub fn remove_key(&mut self, key_id: &str) -> Option<(SecureKey, KeyMetadata)> {
109 self.keys.remove(key_id)
110 }
111
112 pub fn list_keys(&self) -> Vec<String> {
114 self.keys.keys().cloned().collect()
115 }
116
117 pub fn cleanup_expired(&mut self) -> usize {
119 let expired: Vec<String> = self
120 .keys
121 .iter()
122 .filter(|(_, (_, meta))| meta.is_expired())
123 .map(|(id, _)| id.clone())
124 .collect();
125
126 let count = expired.len();
127 for key_id in expired {
128 self.keys.remove(&key_id);
129 }
130 count
131 }
132
133 pub fn count(&self) -> usize {
135 self.keys.len()
136 }
137
138 pub fn rotate_key(&mut self, key_id: &str) -> Result<(), CryptoError> {
140 if let Some((_, meta)) = self.keys.get(key_id) {
141 let new_key = SecureKey::generate();
142 let mut new_meta = meta.clone();
143 new_meta.rotation_count += 1;
144 new_meta.created_at = SystemTime::now()
145 .duration_since(UNIX_EPOCH)
146 .unwrap()
147 .as_secs();
148
149 self.keys.insert(key_id.to_string(), (new_key, new_meta));
150 Ok(())
151 } else {
152 Err(CryptoError::HashingError("Key not found".to_string()))
153 }
154 }
155
156 pub fn find_by_purpose(&self, purpose: &str) -> Vec<(String, KeyMetadata)> {
158 self.keys
159 .iter()
160 .filter(|(_, (_, meta))| meta.purpose == purpose)
161 .map(|(id, (_, meta))| (id.clone(), meta.clone()))
162 .collect()
163 }
164
165 pub fn keys_requiring_rotation(&self, max_age_seconds: u64) -> Vec<String> {
167 self.keys
168 .iter()
169 .filter(|(_, (_, meta))| meta.age_seconds() > max_age_seconds)
170 .map(|(id, _)| id.clone())
171 .collect()
172 }
173}
174
175impl Default for KeyStore {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct RotationPolicy {
184 pub max_age_seconds: u64,
186 pub auto_rotate: bool,
188}
189
190impl RotationPolicy {
191 pub fn ninety_days() -> Self {
193 Self {
194 max_age_seconds: 90 * 24 * 60 * 60,
195 auto_rotate: false,
196 }
197 }
198
199 pub fn thirty_days() -> Self {
201 Self {
202 max_age_seconds: 30 * 24 * 60 * 60,
203 auto_rotate: false,
204 }
205 }
206
207 pub fn custom_days(days: u64) -> Self {
209 Self {
210 max_age_seconds: days * 24 * 60 * 60,
211 auto_rotate: false,
212 }
213 }
214
215 pub fn with_auto_rotate(mut self) -> Self {
217 self.auto_rotate = true;
218 self
219 }
220
221 pub fn needs_rotation(&self, metadata: &KeyMetadata) -> bool {
223 metadata.age_seconds() > self.max_age_seconds
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_key_metadata_creation() {
233 let meta = KeyMetadata::new(
234 "key-001".to_string(),
235 "AES-256-GCM".to_string(),
236 "encryption".to_string(),
237 );
238
239 assert_eq!(meta.key_id, "key-001");
240 assert_eq!(meta.algorithm, "AES-256-GCM");
241 assert_eq!(meta.rotation_count, 0);
242 assert!(!meta.is_expired());
243 }
244
245 #[test]
246 fn test_key_metadata_expiration() {
247 let meta = KeyMetadata::new(
248 "key-002".to_string(),
249 "AES-256-GCM".to_string(),
250 "encryption".to_string(),
251 )
252 .with_expiration(1); assert!(!meta.is_expired());
255 std::thread::sleep(std::time::Duration::from_secs(2));
256 assert!(meta.is_expired());
257 }
258
259 #[test]
260 fn test_key_store_operations() {
261 let mut store = KeyStore::new();
262 let key = SecureKey::generate();
263 let meta = KeyMetadata::new(
264 "test-key".to_string(),
265 "AES-256".to_string(),
266 "encryption".to_string(),
267 );
268
269 assert!(store.store_key("test-key".to_string(), key, meta).is_ok());
271 assert_eq!(store.count(), 1);
272
273 assert!(store.get_key("test-key").is_some());
275
276 let keys = store.list_keys();
278 assert_eq!(keys.len(), 1);
279 assert!(keys.contains(&"test-key".to_string()));
280 }
281
282 #[test]
283 fn test_key_store_duplicate_prevention() {
284 let mut store = KeyStore::new();
285 let key1 = SecureKey::generate();
286 let key2 = SecureKey::generate();
287 let meta = KeyMetadata::new(
288 "dup-key".to_string(),
289 "AES-256".to_string(),
290 "encryption".to_string(),
291 );
292
293 assert!(store
294 .store_key("dup-key".to_string(), key1, meta.clone())
295 .is_ok());
296 assert!(store.store_key("dup-key".to_string(), key2, meta).is_err());
297 }
298
299 #[test]
300 fn test_key_cleanup() {
301 let mut store = KeyStore::new();
302
303 let meta_expired = KeyMetadata::new(
305 "expired".to_string(),
306 "AES-256".to_string(),
307 "encryption".to_string(),
308 )
309 .with_expiration(1);
310 store
311 .store_key(
312 "expired".to_string(),
313 SecureKey::generate(),
314 meta_expired,
315 )
316 .unwrap();
317
318 let meta_valid = KeyMetadata::new(
320 "valid".to_string(),
321 "AES-256".to_string(),
322 "encryption".to_string(),
323 )
324 .with_expiration(3600);
325 store
326 .store_key("valid".to_string(), SecureKey::generate(), meta_valid)
327 .unwrap();
328
329 std::thread::sleep(std::time::Duration::from_secs(2));
330
331 let removed = store.cleanup_expired();
332 assert_eq!(removed, 1);
333 assert_eq!(store.count(), 1);
334 assert!(store.get_key("valid").is_some());
335 assert!(store.get_key("expired").is_none());
336 }
337
338 #[test]
339 fn test_key_rotation() {
340 let mut store = KeyStore::new();
341 let key = SecureKey::generate();
342 let meta = KeyMetadata::new(
343 "rotate-key".to_string(),
344 "AES-256".to_string(),
345 "encryption".to_string(),
346 );
347
348 store
349 .store_key("rotate-key".to_string(), key, meta)
350 .unwrap();
351
352 assert!(store.rotate_key("rotate-key").is_ok());
354
355 let (_, meta) = store.get_key("rotate-key").unwrap();
357 assert_eq!(meta.rotation_count, 1);
358 }
359
360 #[test]
361 fn test_find_by_purpose() {
362 let mut store = KeyStore::new();
363
364 let meta1 = KeyMetadata::new(
365 "enc-1".to_string(),
366 "AES-256".to_string(),
367 "encryption".to_string(),
368 );
369 let meta2 = KeyMetadata::new(
370 "sig-1".to_string(),
371 "HMAC".to_string(),
372 "signing".to_string(),
373 );
374 let meta3 = KeyMetadata::new(
375 "enc-2".to_string(),
376 "AES-256".to_string(),
377 "encryption".to_string(),
378 );
379
380 store
381 .store_key("enc-1".to_string(), SecureKey::generate(), meta1)
382 .unwrap();
383 store
384 .store_key("sig-1".to_string(), SecureKey::generate(), meta2)
385 .unwrap();
386 store
387 .store_key("enc-2".to_string(), SecureKey::generate(), meta3)
388 .unwrap();
389
390 let encryption_keys = store.find_by_purpose("encryption");
391 assert_eq!(encryption_keys.len(), 2);
392
393 let signing_keys = store.find_by_purpose("signing");
394 assert_eq!(signing_keys.len(), 1);
395 }
396
397 #[test]
398 fn test_rotation_policy() {
399 let policy = RotationPolicy::ninety_days();
400 assert_eq!(policy.max_age_seconds, 90 * 24 * 60 * 60);
401
402 let meta = KeyMetadata::new(
403 "key".to_string(),
404 "AES-256".to_string(),
405 "encryption".to_string(),
406 );
407 assert!(!policy.needs_rotation(&meta));
408 }
409
410 #[test]
411 fn test_keys_requiring_rotation() {
412 let mut store = KeyStore::new();
413
414 let meta = KeyMetadata::new(
415 "old-key".to_string(),
416 "AES-256".to_string(),
417 "encryption".to_string(),
418 );
419 store
420 .store_key("old-key".to_string(), SecureKey::generate(), meta)
421 .unwrap();
422
423 std::thread::sleep(std::time::Duration::from_secs(2));
424
425 let keys = store.keys_requiring_rotation(1); assert_eq!(keys.len(), 1);
427 }
428}