use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum KeyRotationPolicy {
TimeBased {
max_age: Duration,
},
UsageBased {
max_operations: u64,
},
Hybrid {
max_age: Duration,
max_operations: u64,
},
Manual,
}
impl KeyRotationPolicy {
pub fn time_based(max_age: Duration) -> Self {
Self::TimeBased { max_age }
}
pub fn usage_based(max_operations: u64) -> Self {
Self::UsageBased { max_operations }
}
pub fn hybrid(max_age: Duration, max_operations: u64) -> Self {
Self::Hybrid {
max_age,
max_operations,
}
}
pub fn manual() -> Self {
Self::Manual
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyMetadata {
pub key_id: String,
pub created_at: SystemTime,
pub operation_count: u64,
pub active: bool,
pub expires_at: Option<SystemTime>,
}
impl KeyMetadata {
pub fn new(key_id: String) -> Self {
Self {
key_id,
created_at: SystemTime::now(),
operation_count: 0,
active: true,
expires_at: None,
}
}
pub fn age(&self) -> Duration {
SystemTime::now()
.duration_since(self.created_at)
.unwrap_or(Duration::ZERO)
}
pub fn is_expired(&self) -> bool {
if let Some(expires_at) = self.expires_at {
SystemTime::now() >= expires_at
} else {
false
}
}
pub fn increment_operations(&mut self) {
self.operation_count += 1;
}
pub fn mark_rotated(&mut self) {
self.created_at = SystemTime::now();
self.operation_count = 0;
}
pub fn deactivate(&mut self) {
self.active = false;
}
}
pub struct KeyRotationScheduler {
policy: KeyRotationPolicy,
keys: HashMap<String, KeyMetadata>,
grace_period: Option<Duration>,
}
impl KeyRotationScheduler {
pub fn new(policy: KeyRotationPolicy) -> Self {
Self {
policy,
keys: HashMap::new(),
grace_period: None,
}
}
pub fn with_grace_period(mut self, grace_period: Duration) -> Self {
self.grace_period = Some(grace_period);
self
}
pub fn register_key(&mut self, key_id: String) -> &KeyMetadata {
self.keys
.entry(key_id.clone())
.or_insert_with(|| KeyMetadata::new(key_id.clone()));
&self.keys[&key_id]
}
pub fn register_key_with_metadata(&mut self, metadata: KeyMetadata) {
self.keys.insert(metadata.key_id.clone(), metadata);
}
pub fn record_operation(&mut self, key_id: &str) -> Result<(), String> {
let metadata = self
.keys
.get_mut(key_id)
.ok_or_else(|| format!("Key not registered: {}", key_id))?;
if !metadata.active {
return Err(format!("Key is inactive: {}", key_id));
}
metadata.increment_operations();
Ok(())
}
pub fn should_rotate(&self, key_id: &str) -> bool {
let metadata = match self.keys.get(key_id) {
Some(m) => m,
None => return false,
};
if !metadata.active {
return false;
}
if metadata.is_expired() {
return true;
}
match &self.policy {
KeyRotationPolicy::TimeBased { max_age } => metadata.age() >= *max_age,
KeyRotationPolicy::UsageBased { max_operations } => {
metadata.operation_count >= *max_operations
}
KeyRotationPolicy::Hybrid {
max_age,
max_operations,
} => metadata.age() >= *max_age || metadata.operation_count >= *max_operations,
KeyRotationPolicy::Manual => false,
}
}
pub fn must_rotate(&self, key_id: &str) -> bool {
if !self.should_rotate(key_id) {
return false;
}
if let Some(grace_period) = self.grace_period {
let metadata = self.keys.get(key_id).unwrap();
let time_since_trigger = match &self.policy {
KeyRotationPolicy::TimeBased { max_age } => metadata.age().saturating_sub(*max_age),
KeyRotationPolicy::Hybrid { max_age, .. } => {
metadata.age().saturating_sub(*max_age)
}
_ => Duration::ZERO,
};
time_since_trigger >= grace_period
} else {
true
}
}
pub fn mark_rotated(&mut self, key_id: &str) -> Result<(), String> {
let metadata = self
.keys
.get_mut(key_id)
.ok_or_else(|| format!("Key not registered: {}", key_id))?;
metadata.mark_rotated();
Ok(())
}
pub fn deactivate_key(&mut self, key_id: &str) -> Result<(), String> {
let metadata = self
.keys
.get_mut(key_id)
.ok_or_else(|| format!("Key not registered: {}", key_id))?;
metadata.deactivate();
Ok(())
}
pub fn keys_needing_rotation(&self) -> Vec<String> {
self.keys
.iter()
.filter(|(key_id, _)| self.should_rotate(key_id))
.map(|(key_id, _)| key_id.clone())
.collect()
}
pub fn keys_requiring_immediate_rotation(&self) -> Vec<String> {
self.keys
.iter()
.filter(|(key_id, _)| self.must_rotate(key_id))
.map(|(key_id, _)| key_id.clone())
.collect()
}
pub fn get_metadata(&self, key_id: &str) -> Option<&KeyMetadata> {
self.keys.get(key_id)
}
pub fn active_keys(&self) -> Vec<String> {
self.keys
.iter()
.filter(|(_, metadata)| metadata.active)
.map(|(key_id, _)| key_id.clone())
.collect()
}
pub fn policy(&self) -> &KeyRotationPolicy {
&self.policy
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
#[test]
fn test_time_based_policy() {
let policy = KeyRotationPolicy::time_based(Duration::from_millis(100));
let mut scheduler = KeyRotationScheduler::new(policy);
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
assert!(!scheduler.should_rotate(&key_id));
sleep(Duration::from_millis(150));
assert!(scheduler.should_rotate(&key_id));
}
#[test]
fn test_usage_based_policy() {
let policy = KeyRotationPolicy::usage_based(5);
let mut scheduler = KeyRotationScheduler::new(policy);
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
for _ in 0..4 {
scheduler.record_operation(&key_id).unwrap();
}
assert!(!scheduler.should_rotate(&key_id));
scheduler.record_operation(&key_id).unwrap();
assert!(scheduler.should_rotate(&key_id));
}
#[test]
fn test_hybrid_policy() {
let policy = KeyRotationPolicy::hybrid(Duration::from_millis(100), 10);
let mut scheduler = KeyRotationScheduler::new(policy);
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
sleep(Duration::from_millis(150));
assert!(scheduler.should_rotate(&key_id));
scheduler.mark_rotated(&key_id).unwrap();
for _ in 0..10 {
scheduler.record_operation(&key_id).unwrap();
}
assert!(scheduler.should_rotate(&key_id));
}
#[test]
fn test_manual_policy() {
let policy = KeyRotationPolicy::manual();
let mut scheduler = KeyRotationScheduler::new(policy);
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
for _ in 0..1000 {
scheduler.record_operation(&key_id).unwrap();
}
assert!(!scheduler.should_rotate(&key_id));
}
#[test]
fn test_grace_period() {
let policy = KeyRotationPolicy::time_based(Duration::from_millis(50));
let mut scheduler =
KeyRotationScheduler::new(policy).with_grace_period(Duration::from_millis(50));
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
sleep(Duration::from_millis(75));
assert!(scheduler.should_rotate(&key_id));
assert!(!scheduler.must_rotate(&key_id));
sleep(Duration::from_millis(50));
assert!(scheduler.must_rotate(&key_id));
}
#[test]
fn test_deactivate_key() {
let policy = KeyRotationPolicy::manual();
let mut scheduler = KeyRotationScheduler::new(policy);
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
let metadata = scheduler.get_metadata(&key_id).unwrap();
assert!(metadata.active);
scheduler.deactivate_key(&key_id).unwrap();
let metadata = scheduler.get_metadata(&key_id).unwrap();
assert!(!metadata.active);
assert!(scheduler.record_operation(&key_id).is_err());
}
#[test]
fn test_keys_needing_rotation() {
let policy = KeyRotationPolicy::usage_based(5);
let mut scheduler = KeyRotationScheduler::new(policy);
scheduler.register_key("key1".to_string());
scheduler.register_key("key2".to_string());
scheduler.register_key("key3".to_string());
for _ in 0..5 {
scheduler.record_operation("key1").unwrap();
scheduler.record_operation("key3").unwrap();
}
let keys = scheduler.keys_needing_rotation();
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"key1".to_string()));
assert!(keys.contains(&"key3".to_string()));
}
#[test]
fn test_mark_rotated_resets_counters() {
let policy = KeyRotationPolicy::usage_based(5);
let mut scheduler = KeyRotationScheduler::new(policy);
let key_id = "test-key".to_string();
scheduler.register_key(key_id.clone());
for _ in 0..5 {
scheduler.record_operation(&key_id).unwrap();
}
assert!(scheduler.should_rotate(&key_id));
scheduler.mark_rotated(&key_id).unwrap();
assert!(!scheduler.should_rotate(&key_id));
let metadata = scheduler.get_metadata(&key_id).unwrap();
assert_eq!(metadata.operation_count, 0);
}
#[test]
fn test_active_keys() {
let policy = KeyRotationPolicy::manual();
let mut scheduler = KeyRotationScheduler::new(policy);
scheduler.register_key("key1".to_string());
scheduler.register_key("key2".to_string());
scheduler.register_key("key3".to_string());
assert_eq!(scheduler.active_keys().len(), 3);
scheduler.deactivate_key("key2").unwrap();
let active = scheduler.active_keys();
assert_eq!(active.len(), 2);
assert!(!active.contains(&"key2".to_string()));
}
#[test]
fn test_key_expiration() {
let mut metadata = KeyMetadata::new("test-key".to_string());
metadata.expires_at = Some(SystemTime::now() - Duration::from_secs(1));
assert!(metadata.is_expired());
metadata.expires_at = Some(SystemTime::now() + Duration::from_secs(3600));
assert!(!metadata.is_expired());
}
#[test]
fn test_policy_serialization() {
let policy = KeyRotationPolicy::hybrid(Duration::from_secs(3600), 1000);
let serialized = crate::codec::encode(&policy).unwrap();
let deserialized: KeyRotationPolicy = crate::codec::decode(&serialized).unwrap();
match deserialized {
KeyRotationPolicy::Hybrid {
max_age,
max_operations,
} => {
assert_eq!(max_age, Duration::from_secs(3600));
assert_eq!(max_operations, 1000);
}
_ => panic!("Wrong policy type"),
}
}
#[test]
fn test_metadata_serialization() {
let metadata = KeyMetadata::new("test-key".to_string());
let serialized = crate::codec::encode(&metadata).unwrap();
let deserialized: KeyMetadata = crate::codec::decode(&serialized).unwrap();
assert_eq!(metadata.key_id, deserialized.key_id);
assert_eq!(metadata.active, deserialized.active);
}
}