chie_crypto/
key_rotation_scheduler.rs1use serde::{Deserialize, Serialize};
41use std::collections::HashMap;
42use std::time::{Duration, SystemTime};
43
44#[derive(Clone, Debug, Serialize, Deserialize)]
46pub enum KeyRotationPolicy {
47 TimeBased {
49 max_age: Duration,
51 },
52 UsageBased {
54 max_operations: u64,
56 },
57 Hybrid {
59 max_age: Duration,
61 max_operations: u64,
63 },
64 Manual,
66}
67
68impl KeyRotationPolicy {
69 pub fn time_based(max_age: Duration) -> Self {
71 Self::TimeBased { max_age }
72 }
73
74 pub fn usage_based(max_operations: u64) -> Self {
76 Self::UsageBased { max_operations }
77 }
78
79 pub fn hybrid(max_age: Duration, max_operations: u64) -> Self {
81 Self::Hybrid {
82 max_age,
83 max_operations,
84 }
85 }
86
87 pub fn manual() -> Self {
89 Self::Manual
90 }
91}
92
93#[derive(Clone, Debug, Serialize, Deserialize)]
95pub struct KeyMetadata {
96 pub key_id: String,
98 pub created_at: SystemTime,
100 pub operation_count: u64,
102 pub active: bool,
104 pub expires_at: Option<SystemTime>,
106}
107
108impl KeyMetadata {
109 pub fn new(key_id: String) -> Self {
111 Self {
112 key_id,
113 created_at: SystemTime::now(),
114 operation_count: 0,
115 active: true,
116 expires_at: None,
117 }
118 }
119
120 pub fn age(&self) -> Duration {
122 SystemTime::now()
123 .duration_since(self.created_at)
124 .unwrap_or(Duration::ZERO)
125 }
126
127 pub fn is_expired(&self) -> bool {
129 if let Some(expires_at) = self.expires_at {
130 SystemTime::now() >= expires_at
131 } else {
132 false
133 }
134 }
135
136 pub fn increment_operations(&mut self) {
138 self.operation_count += 1;
139 }
140
141 pub fn mark_rotated(&mut self) {
143 self.created_at = SystemTime::now();
144 self.operation_count = 0;
145 }
146
147 pub fn deactivate(&mut self) {
149 self.active = false;
150 }
151}
152
153pub struct KeyRotationScheduler {
155 policy: KeyRotationPolicy,
157 keys: HashMap<String, KeyMetadata>,
159 grace_period: Option<Duration>,
161}
162
163impl KeyRotationScheduler {
164 pub fn new(policy: KeyRotationPolicy) -> Self {
166 Self {
167 policy,
168 keys: HashMap::new(),
169 grace_period: None,
170 }
171 }
172
173 pub fn with_grace_period(mut self, grace_period: Duration) -> Self {
175 self.grace_period = Some(grace_period);
176 self
177 }
178
179 pub fn register_key(&mut self, key_id: String) -> &KeyMetadata {
181 self.keys
182 .entry(key_id.clone())
183 .or_insert_with(|| KeyMetadata::new(key_id.clone()));
184 &self.keys[&key_id]
185 }
186
187 pub fn register_key_with_metadata(&mut self, metadata: KeyMetadata) {
189 self.keys.insert(metadata.key_id.clone(), metadata);
190 }
191
192 pub fn record_operation(&mut self, key_id: &str) -> Result<(), String> {
194 let metadata = self
195 .keys
196 .get_mut(key_id)
197 .ok_or_else(|| format!("Key not registered: {}", key_id))?;
198
199 if !metadata.active {
200 return Err(format!("Key is inactive: {}", key_id));
201 }
202
203 metadata.increment_operations();
204 Ok(())
205 }
206
207 pub fn should_rotate(&self, key_id: &str) -> bool {
209 let metadata = match self.keys.get(key_id) {
210 Some(m) => m,
211 None => return false,
212 };
213
214 if !metadata.active {
216 return false;
217 }
218
219 if metadata.is_expired() {
221 return true;
222 }
223
224 match &self.policy {
226 KeyRotationPolicy::TimeBased { max_age } => metadata.age() >= *max_age,
227 KeyRotationPolicy::UsageBased { max_operations } => {
228 metadata.operation_count >= *max_operations
229 }
230 KeyRotationPolicy::Hybrid {
231 max_age,
232 max_operations,
233 } => metadata.age() >= *max_age || metadata.operation_count >= *max_operations,
234 KeyRotationPolicy::Manual => false,
235 }
236 }
237
238 pub fn must_rotate(&self, key_id: &str) -> bool {
240 if !self.should_rotate(key_id) {
241 return false;
242 }
243
244 if let Some(grace_period) = self.grace_period {
246 let metadata = self.keys.get(key_id).unwrap();
247 let time_since_trigger = match &self.policy {
248 KeyRotationPolicy::TimeBased { max_age } => metadata.age().saturating_sub(*max_age),
249 KeyRotationPolicy::Hybrid { max_age, .. } => {
250 metadata.age().saturating_sub(*max_age)
251 }
252 _ => Duration::ZERO,
253 };
254 time_since_trigger >= grace_period
255 } else {
256 true
257 }
258 }
259
260 pub fn mark_rotated(&mut self, key_id: &str) -> Result<(), String> {
262 let metadata = self
263 .keys
264 .get_mut(key_id)
265 .ok_or_else(|| format!("Key not registered: {}", key_id))?;
266
267 metadata.mark_rotated();
268 Ok(())
269 }
270
271 pub fn deactivate_key(&mut self, key_id: &str) -> Result<(), String> {
273 let metadata = self
274 .keys
275 .get_mut(key_id)
276 .ok_or_else(|| format!("Key not registered: {}", key_id))?;
277
278 metadata.deactivate();
279 Ok(())
280 }
281
282 pub fn keys_needing_rotation(&self) -> Vec<String> {
284 self.keys
285 .iter()
286 .filter(|(key_id, _)| self.should_rotate(key_id))
287 .map(|(key_id, _)| key_id.clone())
288 .collect()
289 }
290
291 pub fn keys_requiring_immediate_rotation(&self) -> Vec<String> {
293 self.keys
294 .iter()
295 .filter(|(key_id, _)| self.must_rotate(key_id))
296 .map(|(key_id, _)| key_id.clone())
297 .collect()
298 }
299
300 pub fn get_metadata(&self, key_id: &str) -> Option<&KeyMetadata> {
302 self.keys.get(key_id)
303 }
304
305 pub fn active_keys(&self) -> Vec<String> {
307 self.keys
308 .iter()
309 .filter(|(_, metadata)| metadata.active)
310 .map(|(key_id, _)| key_id.clone())
311 .collect()
312 }
313
314 pub fn policy(&self) -> &KeyRotationPolicy {
316 &self.policy
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use std::thread::sleep;
324
325 #[test]
326 fn test_time_based_policy() {
327 let policy = KeyRotationPolicy::time_based(Duration::from_millis(100));
328 let mut scheduler = KeyRotationScheduler::new(policy);
329
330 let key_id = "test-key".to_string();
331 scheduler.register_key(key_id.clone());
332
333 assert!(!scheduler.should_rotate(&key_id));
335
336 sleep(Duration::from_millis(150));
338
339 assert!(scheduler.should_rotate(&key_id));
341 }
342
343 #[test]
344 fn test_usage_based_policy() {
345 let policy = KeyRotationPolicy::usage_based(5);
346 let mut scheduler = KeyRotationScheduler::new(policy);
347
348 let key_id = "test-key".to_string();
349 scheduler.register_key(key_id.clone());
350
351 for _ in 0..4 {
353 scheduler.record_operation(&key_id).unwrap();
354 }
355
356 assert!(!scheduler.should_rotate(&key_id));
358
359 scheduler.record_operation(&key_id).unwrap();
361
362 assert!(scheduler.should_rotate(&key_id));
364 }
365
366 #[test]
367 fn test_hybrid_policy() {
368 let policy = KeyRotationPolicy::hybrid(Duration::from_millis(100), 10);
369 let mut scheduler = KeyRotationScheduler::new(policy);
370
371 let key_id = "test-key".to_string();
372 scheduler.register_key(key_id.clone());
373
374 sleep(Duration::from_millis(150));
376 assert!(scheduler.should_rotate(&key_id));
377
378 scheduler.mark_rotated(&key_id).unwrap();
380
381 for _ in 0..10 {
383 scheduler.record_operation(&key_id).unwrap();
384 }
385 assert!(scheduler.should_rotate(&key_id));
386 }
387
388 #[test]
389 fn test_manual_policy() {
390 let policy = KeyRotationPolicy::manual();
391 let mut scheduler = KeyRotationScheduler::new(policy);
392
393 let key_id = "test-key".to_string();
394 scheduler.register_key(key_id.clone());
395
396 for _ in 0..1000 {
398 scheduler.record_operation(&key_id).unwrap();
399 }
400
401 assert!(!scheduler.should_rotate(&key_id));
403 }
404
405 #[test]
406 fn test_grace_period() {
407 let policy = KeyRotationPolicy::time_based(Duration::from_millis(50));
408 let mut scheduler =
409 KeyRotationScheduler::new(policy).with_grace_period(Duration::from_millis(50));
410
411 let key_id = "test-key".to_string();
412 scheduler.register_key(key_id.clone());
413
414 sleep(Duration::from_millis(75));
415
416 assert!(scheduler.should_rotate(&key_id));
418 assert!(!scheduler.must_rotate(&key_id));
419
420 sleep(Duration::from_millis(50));
421
422 assert!(scheduler.must_rotate(&key_id));
424 }
425
426 #[test]
427 fn test_deactivate_key() {
428 let policy = KeyRotationPolicy::manual();
429 let mut scheduler = KeyRotationScheduler::new(policy);
430
431 let key_id = "test-key".to_string();
432 scheduler.register_key(key_id.clone());
433
434 let metadata = scheduler.get_metadata(&key_id).unwrap();
436 assert!(metadata.active);
437
438 scheduler.deactivate_key(&key_id).unwrap();
440
441 let metadata = scheduler.get_metadata(&key_id).unwrap();
443 assert!(!metadata.active);
444
445 assert!(scheduler.record_operation(&key_id).is_err());
447 }
448
449 #[test]
450 fn test_keys_needing_rotation() {
451 let policy = KeyRotationPolicy::usage_based(5);
452 let mut scheduler = KeyRotationScheduler::new(policy);
453
454 scheduler.register_key("key1".to_string());
455 scheduler.register_key("key2".to_string());
456 scheduler.register_key("key3".to_string());
457
458 for _ in 0..5 {
460 scheduler.record_operation("key1").unwrap();
461 scheduler.record_operation("key3").unwrap();
462 }
463
464 let keys = scheduler.keys_needing_rotation();
465 assert_eq!(keys.len(), 2);
466 assert!(keys.contains(&"key1".to_string()));
467 assert!(keys.contains(&"key3".to_string()));
468 }
469
470 #[test]
471 fn test_mark_rotated_resets_counters() {
472 let policy = KeyRotationPolicy::usage_based(5);
473 let mut scheduler = KeyRotationScheduler::new(policy);
474
475 let key_id = "test-key".to_string();
476 scheduler.register_key(key_id.clone());
477
478 for _ in 0..5 {
480 scheduler.record_operation(&key_id).unwrap();
481 }
482 assert!(scheduler.should_rotate(&key_id));
483
484 scheduler.mark_rotated(&key_id).unwrap();
486
487 assert!(!scheduler.should_rotate(&key_id));
489
490 let metadata = scheduler.get_metadata(&key_id).unwrap();
492 assert_eq!(metadata.operation_count, 0);
493 }
494
495 #[test]
496 fn test_active_keys() {
497 let policy = KeyRotationPolicy::manual();
498 let mut scheduler = KeyRotationScheduler::new(policy);
499
500 scheduler.register_key("key1".to_string());
501 scheduler.register_key("key2".to_string());
502 scheduler.register_key("key3".to_string());
503
504 assert_eq!(scheduler.active_keys().len(), 3);
505
506 scheduler.deactivate_key("key2").unwrap();
507
508 let active = scheduler.active_keys();
509 assert_eq!(active.len(), 2);
510 assert!(!active.contains(&"key2".to_string()));
511 }
512
513 #[test]
514 fn test_key_expiration() {
515 let mut metadata = KeyMetadata::new("test-key".to_string());
516
517 metadata.expires_at = Some(SystemTime::now() - Duration::from_secs(1));
519
520 assert!(metadata.is_expired());
521
522 metadata.expires_at = Some(SystemTime::now() + Duration::from_secs(3600));
524
525 assert!(!metadata.is_expired());
526 }
527
528 #[test]
529 fn test_policy_serialization() {
530 let policy = KeyRotationPolicy::hybrid(Duration::from_secs(3600), 1000);
531
532 let serialized = crate::codec::encode(&policy).unwrap();
533 let deserialized: KeyRotationPolicy = crate::codec::decode(&serialized).unwrap();
534
535 match deserialized {
536 KeyRotationPolicy::Hybrid {
537 max_age,
538 max_operations,
539 } => {
540 assert_eq!(max_age, Duration::from_secs(3600));
541 assert_eq!(max_operations, 1000);
542 }
543 _ => panic!("Wrong policy type"),
544 }
545 }
546
547 #[test]
548 fn test_metadata_serialization() {
549 let metadata = KeyMetadata::new("test-key".to_string());
550
551 let serialized = crate::codec::encode(&metadata).unwrap();
552 let deserialized: KeyMetadata = crate::codec::decode(&serialized).unwrap();
553
554 assert_eq!(metadata.key_id, deserialized.key_id);
555 assert_eq!(metadata.active, deserialized.active);
556 }
557}