rust_crypto_utils/
keymanagement.rs

1//! Secure key management and storage
2
3use crate::{CryptoError, SecureKey};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::{SystemTime, UNIX_EPOCH};
7use zeroize::Zeroizing;
8
9/// Key metadata
10#[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    /// Create new metadata
22    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    /// Set expiration (seconds from now)
39    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    /// Check if key has expired
49    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    /// Get age in seconds
62    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
71/// In-memory key store with automatic cleanup
72pub struct KeyStore {
73    keys: HashMap<String, (SecureKey, KeyMetadata)>,
74}
75
76impl KeyStore {
77    /// Create new key store
78    pub fn new() -> Self {
79        Self {
80            keys: HashMap::new(),
81        }
82    }
83
84    /// Store a key with metadata
85    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    /// Retrieve a key (returns clone of metadata but reference to key)
101    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    /// Remove a key
108    pub fn remove_key(&mut self, key_id: &str) -> Option<(SecureKey, KeyMetadata)> {
109        self.keys.remove(key_id)
110    }
111
112    /// List all key IDs
113    pub fn list_keys(&self) -> Vec<String> {
114        self.keys.keys().cloned().collect()
115    }
116
117    /// Clean up expired keys
118    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    /// Get count of stored keys
134    pub fn count(&self) -> usize {
135        self.keys.len()
136    }
137
138    /// Rotate a key (generate new key, increment rotation counter)
139    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    /// Find keys by purpose
157    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    /// Get keys requiring rotation (older than specified age in seconds)
166    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/// Key rotation policy
182#[derive(Debug, Clone)]
183pub struct RotationPolicy {
184    /// Maximum key age in seconds before rotation required
185    pub max_age_seconds: u64,
186    /// Automatically rotate keys when max age is reached
187    pub auto_rotate: bool,
188}
189
190impl RotationPolicy {
191    /// Create policy with 90-day rotation
192    pub fn ninety_days() -> Self {
193        Self {
194            max_age_seconds: 90 * 24 * 60 * 60,
195            auto_rotate: false,
196        }
197    }
198
199    /// Create policy with 30-day rotation
200    pub fn thirty_days() -> Self {
201        Self {
202            max_age_seconds: 30 * 24 * 60 * 60,
203            auto_rotate: false,
204        }
205    }
206
207    /// Create policy with custom days
208    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    /// Enable auto-rotation
216    pub fn with_auto_rotate(mut self) -> Self {
217        self.auto_rotate = true;
218        self
219    }
220
221    /// Check if key needs rotation
222    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); // Expires in 1 second
253
254        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        // Store key
270        assert!(store.store_key("test-key".to_string(), key, meta).is_ok());
271        assert_eq!(store.count(), 1);
272
273        // Retrieve key
274        assert!(store.get_key("test-key").is_some());
275
276        // List keys
277        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        // Add expired key
304        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        // Add non-expired key
319        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        // Rotate
353        assert!(store.rotate_key("rotate-key").is_ok());
354
355        // Check rotation count increased
356        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); // Keys older than 1 second
426        assert_eq!(keys.len(), 1);
427    }
428}