Skip to main content

fraiseql_server/encryption/
credential_rotation.rs

1// Phase 12.4 Cycle 1: Credential Rotation - GREEN
2//! Credential rotation and key lifecycle management including versioning,
3//! TTL tracking, automatic refresh, and multi-version decryption support.
4
5use std::{
6    collections::HashMap,
7    sync::{
8        Arc,
9        atomic::{AtomicU64, Ordering},
10    },
11};
12
13use chrono::{DateTime, Duration, Utc};
14
15/// Key version identifier (0 = unversioned/legacy, 1-65535 = versioned)
16pub type KeyVersion = u16;
17
18/// Status of a key version in its lifecycle
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum KeyVersionStatus {
21    /// Active and available for encryption and decryption
22    Active,
23    /// Approaching expiry, cannot encrypt but can decrypt
24    Expiring,
25    /// Expired, cannot encrypt but can decrypt (archival)
26    Expired,
27    /// Compromised, should not be used but retained for decryption
28    Compromised,
29}
30
31impl std::fmt::Display for KeyVersionStatus {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Self::Active => write!(f, "active"),
35            Self::Expiring => write!(f, "expiring"),
36            Self::Expired => write!(f, "expired"),
37            Self::Compromised => write!(f, "compromised"),
38        }
39    }
40}
41
42/// Metadata for a versioned encryption key
43#[derive(Debug, Clone)]
44pub struct KeyVersionMetadata {
45    /// Version identifier
46    pub version:           KeyVersion,
47    /// When this version was issued
48    pub issued_at:         DateTime<Utc>,
49    /// When this version expires (TTL)
50    pub expires_at:        DateTime<Utc>,
51    /// Current status in lifecycle
52    pub status:            KeyVersionStatus,
53    /// Is this the current version for new encryptions?
54    pub is_current:        bool,
55    /// Reason for compromised status (if applicable)
56    pub compromise_reason: Option<String>,
57}
58
59impl KeyVersionMetadata {
60    /// Create new key version metadata
61    pub fn new(version: KeyVersion, ttl_days: u32) -> Self {
62        let now = Utc::now();
63        Self {
64            version,
65            issued_at: now,
66            expires_at: now + Duration::days(ttl_days as i64),
67            status: KeyVersionStatus::Active,
68            is_current: false,
69            compromise_reason: None,
70        }
71    }
72
73    /// Check if version is expired
74    pub fn is_expired(&self) -> bool {
75        Utc::now() > self.expires_at
76    }
77
78    /// Check if version is expiring soon (< 14 days)
79    pub fn is_expiring_soon(&self) -> bool {
80        let remaining = self.expires_at - Utc::now();
81        remaining < Duration::days(14) && !self.is_expired()
82    }
83
84    /// Get time until expiration
85    pub fn time_until_expiry(&self) -> Duration {
86        self.expires_at - Utc::now()
87    }
88
89    /// Get percentage of TTL consumed
90    pub fn ttl_consumed_percent(&self) -> u32 {
91        let total_ttl = self.expires_at - self.issued_at;
92        let elapsed = Utc::now() - self.issued_at;
93        if total_ttl.num_seconds() <= 0 {
94            100
95        } else {
96            let percent = (elapsed.num_seconds() as f64 / total_ttl.num_seconds() as f64) * 100.0;
97            percent.min(100.0) as u32
98        }
99    }
100
101    /// Check if refresh should trigger (80% of TTL consumed)
102    pub fn should_refresh(&self) -> bool {
103        self.status == KeyVersionStatus::Active && self.ttl_consumed_percent() >= 80
104    }
105
106    /// Update status based on current time
107    pub fn update_status(&mut self) {
108        match self.status {
109            KeyVersionStatus::Compromised => {}, // Never change compromised status
110            KeyVersionStatus::Active => {
111                if self.is_expired() {
112                    self.status = KeyVersionStatus::Expired;
113                } else if self.is_expiring_soon() {
114                    self.status = KeyVersionStatus::Expiring;
115                }
116            },
117            KeyVersionStatus::Expiring => {
118                if self.is_expired() {
119                    self.status = KeyVersionStatus::Expired;
120                }
121            },
122            KeyVersionStatus::Expired => {}, // Remains expired
123        }
124    }
125
126    /// Mark key as compromised
127    pub fn mark_compromised(&mut self, reason: impl Into<String>) {
128        self.status = KeyVersionStatus::Compromised;
129        self.compromise_reason = Some(reason.into());
130    }
131}
132
133/// Rotation schedule configuration
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum RotationSchedule {
136    /// Manual rotation only (no automatic schedule)
137    Manual,
138    /// Automatic rotation at cron expression (e.g., "0 2 1 * *" for monthly)
139    Cron(String),
140    /// Automatic rotation every N days
141    Interval(u32),
142}
143
144impl std::fmt::Display for RotationSchedule {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Self::Manual => write!(f, "manual"),
148            Self::Cron(expr) => write!(f, "cron: {}", expr),
149            Self::Interval(days) => write!(f, "every {} days", days),
150        }
151    }
152}
153
154/// Credential rotation configuration
155#[derive(Debug, Clone)]
156pub struct RotationConfig {
157    /// TTL for each key version (days)
158    pub ttl_days:                  u32,
159    /// When to trigger refresh (percentage of TTL consumed)
160    pub refresh_threshold_percent: u32,
161    /// Rotation schedule
162    pub schedule:                  RotationSchedule,
163    /// Maximum number of historical versions to retain
164    pub max_retained_versions:     usize,
165}
166
167impl RotationConfig {
168    /// Create default rotation config (annual rotation, 80% refresh)
169    pub fn new() -> Self {
170        Self {
171            ttl_days:                  365,
172            refresh_threshold_percent: 80,
173            schedule:                  RotationSchedule::Manual,
174            max_retained_versions:     10,
175        }
176    }
177
178    /// Set TTL in days
179    pub fn with_ttl_days(mut self, days: u32) -> Self {
180        self.ttl_days = days;
181        self
182    }
183
184    /// Set refresh threshold percentage
185    pub fn with_refresh_threshold(mut self, percent: u32) -> Self {
186        self.refresh_threshold_percent = percent.min(99);
187        self
188    }
189
190    /// Set rotation schedule
191    pub fn with_schedule(mut self, schedule: RotationSchedule) -> Self {
192        self.schedule = schedule;
193        self
194    }
195}
196
197impl Default for RotationConfig {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203/// Metrics for credential rotation tracking
204#[derive(Debug, Clone)]
205pub struct RotationMetrics {
206    /// Total number of rotations
207    total_rotations:           Arc<AtomicU64>,
208    /// Number of failed rotations
209    failed_rotations:          Arc<AtomicU64>,
210    /// Last rotation timestamp
211    last_rotation:             Arc<std::sync::Mutex<Option<DateTime<Utc>>>>,
212    /// Rotation duration (milliseconds)
213    last_rotation_duration_ms: Arc<AtomicU64>,
214}
215
216impl RotationMetrics {
217    /// Create new rotation metrics
218    pub fn new() -> Self {
219        Self {
220            total_rotations:           Arc::new(AtomicU64::new(0)),
221            failed_rotations:          Arc::new(AtomicU64::new(0)),
222            last_rotation:             Arc::new(std::sync::Mutex::new(None)),
223            last_rotation_duration_ms: Arc::new(AtomicU64::new(0)),
224        }
225    }
226
227    /// Record successful rotation
228    pub fn record_rotation(&self, duration_ms: u64) {
229        self.total_rotations.fetch_add(1, Ordering::Relaxed);
230        self.last_rotation_duration_ms.store(duration_ms, Ordering::Relaxed);
231        if let Ok(mut last) = self.last_rotation.lock() {
232            *last = Some(Utc::now());
233        }
234    }
235
236    /// Record failed rotation
237    pub fn record_failure(&self) {
238        self.failed_rotations.fetch_add(1, Ordering::Relaxed);
239    }
240
241    /// Get total rotations count
242    pub fn total_rotations(&self) -> u64 {
243        self.total_rotations.load(Ordering::Relaxed)
244    }
245
246    /// Get failed rotations count
247    pub fn failed_rotations(&self) -> u64 {
248        self.failed_rotations.load(Ordering::Relaxed)
249    }
250
251    /// Get success rate percentage
252    pub fn success_rate_percent(&self) -> u32 {
253        let total = self.total_rotations();
254        if total == 0 {
255            100
256        } else {
257            let failed = self.failed_rotations();
258            let successful = total - failed;
259            ((successful as f64 / total as f64) * 100.0) as u32
260        }
261    }
262
263    /// Get last rotation timestamp
264    pub fn last_rotation(&self) -> Option<DateTime<Utc>> {
265        if let Ok(last) = self.last_rotation.lock() {
266            *last
267        } else {
268            None
269        }
270    }
271
272    /// Get last rotation duration in milliseconds
273    pub fn last_rotation_duration_ms(&self) -> u64 {
274        self.last_rotation_duration_ms.load(Ordering::Relaxed)
275    }
276}
277
278impl Default for RotationMetrics {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284/// Versioned encryption key storage
285#[derive(Debug, Clone)]
286pub struct VersionedKeyStorage {
287    /// Map of version ID to key metadata
288    versions:        Arc<std::sync::Mutex<HashMap<KeyVersion, KeyVersionMetadata>>>,
289    /// Current active version
290    current_version: Arc<std::sync::Mutex<KeyVersion>>,
291    /// Next version number to assign
292    next_version:    Arc<AtomicU64>,
293}
294
295impl VersionedKeyStorage {
296    /// Create new versioned key storage
297    pub fn new() -> Self {
298        Self {
299            versions:        Arc::new(std::sync::Mutex::new(HashMap::new())),
300            current_version: Arc::new(std::sync::Mutex::new(0)),
301            next_version:    Arc::new(AtomicU64::new(1)),
302        }
303    }
304
305    /// Add a new key version
306    pub fn add_version(&self, metadata: KeyVersionMetadata) -> Result<KeyVersion, String> {
307        let mut versions =
308            self.versions.lock().map_err(|e| format!("Failed to lock versions: {}", e))?;
309
310        let version = metadata.version;
311        versions.insert(version, metadata);
312        Ok(version)
313    }
314
315    /// Set current version
316    pub fn set_current_version(&self, version: KeyVersion) -> Result<(), String> {
317        let versions =
318            self.versions.lock().map_err(|e| format!("Failed to lock versions: {}", e))?;
319
320        if !versions.contains_key(&version) {
321            return Err(format!("Version {} not found", version));
322        }
323
324        let mut current = self
325            .current_version
326            .lock()
327            .map_err(|e| format!("Failed to lock current version: {}", e))?;
328        *current = version;
329        Ok(())
330    }
331
332    /// Get current version
333    pub fn get_current_version(&self) -> Result<KeyVersion, String> {
334        let current = self
335            .current_version
336            .lock()
337            .map_err(|e| format!("Failed to lock current version: {}", e))?;
338        Ok(*current)
339    }
340
341    /// Get version metadata by ID
342    pub fn get_version(&self, version: KeyVersion) -> Result<Option<KeyVersionMetadata>, String> {
343        let versions =
344            self.versions.lock().map_err(|e| format!("Failed to lock versions: {}", e))?;
345        Ok(versions.get(&version).cloned())
346    }
347
348    /// Get all versions sorted by issue date (newest first)
349    pub fn get_all_versions(&self) -> Result<Vec<KeyVersionMetadata>, String> {
350        let versions =
351            self.versions.lock().map_err(|e| format!("Failed to lock versions: {}", e))?;
352
353        let mut all_versions: Vec<_> = versions.values().cloned().collect();
354        all_versions.sort_by(|a, b| b.issued_at.cmp(&a.issued_at));
355        Ok(all_versions)
356    }
357
358    /// Get next version number
359    pub fn next_version_number(&self) -> KeyVersion {
360        let next = self.next_version.fetch_add(1, Ordering::Relaxed);
361        next as KeyVersion
362    }
363}
364
365impl Default for VersionedKeyStorage {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371/// Credential rotation manager for key lifecycle
372#[derive(Debug, Clone)]
373pub struct CredentialRotationManager {
374    /// Rotation configuration
375    config:  Arc<RotationConfig>,
376    /// Versioned key storage
377    storage: Arc<VersionedKeyStorage>,
378    /// Rotation metrics
379    metrics: Arc<RotationMetrics>,
380}
381
382impl CredentialRotationManager {
383    /// Create new credential rotation manager
384    pub fn new(config: RotationConfig) -> Self {
385        Self {
386            config:  Arc::new(config),
387            storage: Arc::new(VersionedKeyStorage::new()),
388            metrics: Arc::new(RotationMetrics::new()),
389        }
390    }
391
392    /// Initialize with first key version
393    pub fn initialize_key(&self) -> Result<KeyVersion, String> {
394        let version = self.storage.next_version_number();
395        let metadata = KeyVersionMetadata::new(version, self.config.ttl_days);
396        self.storage.add_version(metadata)?;
397        self.storage.set_current_version(version)?;
398        Ok(version)
399    }
400
401    /// Trigger key rotation
402    pub fn rotate_key(&self) -> Result<KeyVersion, String> {
403        let start = std::time::Instant::now();
404
405        let new_version = self.storage.next_version_number();
406        let mut metadata = KeyVersionMetadata::new(new_version, self.config.ttl_days);
407
408        // New version is immediately current
409        metadata.is_current = true;
410        self.storage.add_version(metadata)?;
411        self.storage.set_current_version(new_version)?;
412
413        let duration_ms = start.elapsed().as_millis() as u64;
414        self.metrics.record_rotation(duration_ms);
415
416        Ok(new_version)
417    }
418
419    /// Get current version number
420    pub fn get_current_version(&self) -> Result<KeyVersion, String> {
421        self.storage.get_current_version()
422    }
423
424    /// Check if refresh is needed for any version
425    pub fn needs_refresh(&self) -> Result<bool, String> {
426        let current_version = self.storage.get_current_version()?;
427        if let Some(metadata) = self.storage.get_version(current_version)? {
428            Ok(metadata.should_refresh())
429        } else {
430            Ok(false)
431        }
432    }
433
434    /// Get current version metadata
435    pub fn get_current_metadata(&self) -> Result<Option<KeyVersionMetadata>, String> {
436        let current_version = self.storage.get_current_version()?;
437        self.storage.get_version(current_version)
438    }
439
440    /// Get version from ciphertext (first 2 bytes as big-endian u16)
441    pub fn extract_version_from_ciphertext(ciphertext: &[u8]) -> Result<KeyVersion, String> {
442        if ciphertext.len() < 2 {
443            return Err("Ciphertext too short for version".to_string());
444        }
445        let version = u16::from_be_bytes([ciphertext[0], ciphertext[1]]);
446        Ok(version)
447    }
448
449    /// Check if version exists and is usable for decryption
450    pub fn can_decrypt_with_version(&self, version: KeyVersion) -> Result<bool, String> {
451        if let Some(metadata) = self.storage.get_version(version)? {
452            // Can decrypt with any non-compromised version
453            Ok(metadata.status != KeyVersionStatus::Compromised)
454        } else {
455            Ok(false)
456        }
457    }
458
459    /// Get rotation metrics
460    pub fn metrics(&self) -> Arc<RotationMetrics> {
461        Arc::clone(&self.metrics)
462    }
463
464    /// Get all version history
465    pub fn get_version_history(&self) -> Result<Vec<KeyVersionMetadata>, String> {
466        self.storage.get_all_versions()
467    }
468
469    // ========== REFACTOR ENHANCEMENTS ==========
470
471    /// Check if any version needs attention (expiring or expired)
472    pub fn has_versions_needing_attention(&self) -> Result<bool, String> {
473        let history = self.get_version_history()?;
474        Ok(history
475            .iter()
476            .any(|m| m.is_expiring_soon() || m.status == KeyVersionStatus::Compromised))
477    }
478
479    /// Get active versions count
480    pub fn active_versions_count(&self) -> Result<usize, String> {
481        let history = self.get_version_history()?;
482        Ok(history.iter().filter(|m| m.status == KeyVersionStatus::Active).count())
483    }
484
485    /// Get expired versions count
486    pub fn expired_versions_count(&self) -> Result<usize, String> {
487        let history = self.get_version_history()?;
488        Ok(history.iter().filter(|m| m.status == KeyVersionStatus::Expired).count())
489    }
490
491    /// Get compromised versions count
492    pub fn compromised_versions_count(&self) -> Result<usize, String> {
493        let history = self.get_version_history()?;
494        Ok(history.iter().filter(|m| m.status == KeyVersionStatus::Compromised).count())
495    }
496
497    /// Check if current version needs refresh
498    pub fn current_version_needs_refresh(&self) -> Result<bool, String> {
499        let current_version = self.get_current_version()?;
500        if let Some(metadata) = self.storage.get_version(current_version)? {
501            Ok(metadata.should_refresh())
502        } else {
503            Ok(false)
504        }
505    }
506
507    /// Perform emergency rotation due to compromise
508    pub fn emergency_rotate(&self, reason: impl Into<String>) -> Result<KeyVersion, String> {
509        let current_version = self.get_current_version()?;
510        if let Some(mut metadata) = self.storage.get_version(current_version)? {
511            metadata.mark_compromised(reason);
512            self.storage.add_version(metadata)?;
513        }
514
515        // Trigger immediate rotation
516        self.rotate_key()
517    }
518
519    /// Get next scheduled rotation time (for manual schedule)
520    pub fn last_rotation_time(&self) -> Option<DateTime<Utc>> {
521        self.metrics.last_rotation()
522    }
523
524    /// Get time since last rotation
525    pub fn time_since_last_rotation(&self) -> Option<Duration> {
526        self.metrics.last_rotation().map(|last| Utc::now() - last)
527    }
528
529    /// Mark specific version as compromised
530    pub fn mark_version_compromised(
531        &self,
532        version: KeyVersion,
533        reason: impl Into<String>,
534    ) -> Result<(), String> {
535        if let Some(mut metadata) = self.storage.get_version(version)? {
536            metadata.mark_compromised(reason);
537            // Update in storage
538            self.storage.add_version(metadata)?;
539            Ok(())
540        } else {
541            Err(format!("Version {} not found", version))
542        }
543    }
544
545    /// Check compliance for HIPAA (annual rotation)
546    pub fn check_hipaa_compliance(&self) -> Result<bool, String> {
547        let metadata = self.get_current_metadata()?;
548        if let Some(m) = metadata {
549            // HIPAA requires rotation at least annually
550            Ok(m.ttl_consumed_percent() < 100)
551        } else {
552            Ok(false)
553        }
554    }
555
556    /// Check compliance for PCI-DSS (annual rotation)
557    pub fn check_pci_compliance(&self) -> Result<bool, String> {
558        let metadata = self.get_current_metadata()?;
559        if let Some(m) = metadata {
560            // PCI-DSS requires rotation at least annually
561            Ok(m.ttl_consumed_percent() < 100)
562        } else {
563            Ok(false)
564        }
565    }
566}
567
568#[cfg(test)]
569mod tests {
570    use super::*;
571
572    #[test]
573    fn test_key_version_metadata_creation() {
574        let metadata = KeyVersionMetadata::new(1, 365);
575        assert_eq!(metadata.version, 1);
576        assert_eq!(metadata.status, KeyVersionStatus::Active);
577        assert!(!metadata.is_current);
578        assert!(!metadata.is_expired());
579    }
580
581    #[test]
582    fn test_key_version_is_expiring_soon() {
583        let mut metadata = KeyVersionMetadata::new(1, 365);
584        // Manually set expires_at to near future for testing
585        metadata.expires_at = Utc::now() + Duration::days(7);
586        assert!(metadata.is_expiring_soon());
587    }
588
589    #[test]
590    fn test_key_version_ttl_consumed() {
591        let now = Utc::now();
592        let mut metadata = KeyVersionMetadata::new(1, 10);
593        // Simulate time passage: 8 days out of 10 = 80% consumed
594        metadata.issued_at = now - Duration::days(8);
595        metadata.expires_at = now + Duration::days(2);
596        let percent = metadata.ttl_consumed_percent();
597        assert!(percent >= 75);
598    }
599
600    #[test]
601    fn test_key_version_should_refresh() {
602        let now = Utc::now();
603        let mut metadata = KeyVersionMetadata::new(1, 100);
604        // Simulate 81 days out of 100 = 81% consumed
605        metadata.issued_at = now - Duration::days(81);
606        metadata.expires_at = now + Duration::days(19);
607        assert!(metadata.should_refresh());
608    }
609
610    #[test]
611    fn test_key_version_mark_compromised() {
612        let mut metadata = KeyVersionMetadata::new(1, 365);
613        metadata.mark_compromised("Leaked in incident");
614        assert_eq!(metadata.status, KeyVersionStatus::Compromised);
615        assert!(metadata.compromise_reason.is_some());
616    }
617
618    #[test]
619    fn test_rotation_config_default() {
620        let config = RotationConfig::new();
621        assert_eq!(config.ttl_days, 365);
622        assert_eq!(config.refresh_threshold_percent, 80);
623        assert_eq!(config.schedule, RotationSchedule::Manual);
624    }
625
626    #[test]
627    fn test_rotation_config_builder() {
628        let config = RotationConfig::new().with_ttl_days(90).with_refresh_threshold(75);
629        assert_eq!(config.ttl_days, 90);
630        assert_eq!(config.refresh_threshold_percent, 75);
631    }
632
633    #[test]
634    fn test_rotation_metrics_record() {
635        let metrics = RotationMetrics::new();
636        metrics.record_rotation(100);
637        assert_eq!(metrics.total_rotations(), 1);
638        assert_eq!(metrics.failed_rotations(), 0);
639        assert_eq!(metrics.success_rate_percent(), 100);
640    }
641
642    #[test]
643    fn test_rotation_metrics_failure() {
644        let metrics = RotationMetrics::new();
645        metrics.record_rotation(100);
646        metrics.record_rotation(100);
647        metrics.record_failure();
648        assert_eq!(metrics.total_rotations(), 2);
649        assert_eq!(metrics.failed_rotations(), 1);
650        assert_eq!(metrics.success_rate_percent(), 50);
651    }
652
653    #[test]
654    fn test_versioned_key_storage_add_version() {
655        let storage = VersionedKeyStorage::new();
656        let metadata = KeyVersionMetadata::new(1, 365);
657        let result = storage.add_version(metadata);
658        assert!(result.is_ok());
659        assert_eq!(result.unwrap(), 1);
660    }
661
662    #[test]
663    fn test_versioned_key_storage_current_version() {
664        let storage = VersionedKeyStorage::new();
665        let metadata = KeyVersionMetadata::new(1, 365);
666        storage.add_version(metadata).unwrap();
667        storage.set_current_version(1).unwrap();
668        assert_eq!(storage.get_current_version().unwrap(), 1);
669    }
670
671    #[test]
672    fn test_credential_rotation_manager_initialize() {
673        let config = RotationConfig::new();
674        let manager = CredentialRotationManager::new(config);
675        let version = manager.initialize_key().unwrap();
676        assert_eq!(version, 1);
677    }
678
679    #[test]
680    fn test_credential_rotation_manager_rotate() {
681        let config = RotationConfig::new();
682        let manager = CredentialRotationManager::new(config);
683        manager.initialize_key().unwrap();
684        let new_version = manager.rotate_key().unwrap();
685        assert_eq!(new_version, 2);
686        assert_eq!(manager.get_current_version().unwrap(), 2);
687    }
688
689    #[test]
690    fn test_credential_rotation_manager_extract_version() {
691        let version_bytes = [0u8, 5u8]; // Version 5 in big-endian
692        let version =
693            CredentialRotationManager::extract_version_from_ciphertext(&version_bytes).unwrap();
694        assert_eq!(version, 5);
695    }
696
697    #[test]
698    fn test_credential_rotation_manager_extract_version_short() {
699        let version_bytes = [0u8]; // Too short
700        let result = CredentialRotationManager::extract_version_from_ciphertext(&version_bytes);
701        assert!(result.is_err());
702    }
703
704    #[test]
705    fn test_credential_rotation_manager_active_versions_count() {
706        let config = RotationConfig::new();
707        let manager = CredentialRotationManager::new(config);
708        manager.initialize_key().unwrap();
709        manager.rotate_key().unwrap();
710        let count = manager.active_versions_count().unwrap();
711        assert!(count > 0);
712    }
713
714    #[test]
715    fn test_credential_rotation_manager_current_needs_refresh() {
716        let config = RotationConfig::new();
717        let manager = CredentialRotationManager::new(config);
718        manager.initialize_key().unwrap();
719        let needs_refresh = manager.current_version_needs_refresh().unwrap();
720        assert!(!needs_refresh); // New key shouldn't need refresh
721    }
722
723    #[test]
724    fn test_credential_rotation_manager_emergency_rotate() {
725        let config = RotationConfig::new();
726        let manager = CredentialRotationManager::new(config);
727        manager.initialize_key().unwrap();
728        let old_version = manager.get_current_version().unwrap();
729        let new_version = manager.emergency_rotate("Suspected compromise").unwrap();
730        assert!(new_version > old_version);
731    }
732
733    #[test]
734    fn test_credential_rotation_manager_mark_compromised() {
735        let config = RotationConfig::new();
736        let manager = CredentialRotationManager::new(config);
737        manager.initialize_key().unwrap();
738        let version = manager.get_current_version().unwrap();
739        let result = manager.mark_version_compromised(version, "Test compromise");
740        assert!(result.is_ok());
741    }
742
743    #[test]
744    fn test_credential_rotation_manager_hipaa_compliance() {
745        let config = RotationConfig::new();
746        let manager = CredentialRotationManager::new(config);
747        manager.initialize_key().unwrap();
748        let compliant = manager.check_hipaa_compliance().unwrap();
749        assert!(compliant); // New key should be compliant
750    }
751
752    #[test]
753    fn test_credential_rotation_manager_pci_compliance() {
754        let config = RotationConfig::new();
755        let manager = CredentialRotationManager::new(config);
756        manager.initialize_key().unwrap();
757        let compliant = manager.check_pci_compliance().unwrap();
758        assert!(compliant); // New key should be compliant
759    }
760
761    #[test]
762    fn test_credential_rotation_manager_versions_needing_attention() {
763        let config = RotationConfig::new();
764        let manager = CredentialRotationManager::new(config);
765        manager.initialize_key().unwrap();
766        let needs_attention = manager.has_versions_needing_attention().unwrap();
767        assert!(!needs_attention); // New key shouldn't need attention
768    }
769}