use std::time::{Duration, Instant};
use tracing::{debug, info};
use aivpn_common::crypto::{KeyPair, X25519_PUBLIC_KEY_SIZE};
use aivpn_common::error::Result;
use aivpn_common::protocol::ControlPayload;
#[derive(Debug, Clone)]
pub struct KeyRotationConfig {
pub time_interval_secs: u64,
pub data_interval_bytes: u64,
pub enable_auto_rotation: bool,
}
impl Default for KeyRotationConfig {
fn default() -> Self {
Self {
time_interval_secs: 120, data_interval_bytes: 1_000_000, enable_auto_rotation: true,
}
}
}
#[derive(Debug)]
pub struct KeyRotator {
config: KeyRotationConfig,
current_keypair: KeyPair,
next_keypair: Option<KeyPair>,
last_rotation: Instant,
bytes_since_rotation: u64,
rotation_count: u64,
}
impl KeyRotator {
pub fn new(config: KeyRotationConfig) -> Result<Self> {
let current_keypair = KeyPair::generate();
Ok(Self {
config,
current_keypair,
next_keypair: None,
last_rotation: Instant::now(),
bytes_since_rotation: 0,
rotation_count: 0,
})
}
pub fn needs_rotation(&self) -> bool {
if !self.config.enable_auto_rotation {
return false;
}
let time_expired =
self.last_rotation.elapsed() >= Duration::from_secs(self.config.time_interval_secs);
let data_expired = self.bytes_since_rotation >= self.config.data_interval_bytes;
time_expired || data_expired
}
pub fn rotate_keys(&mut self) -> Result<KeyRotationEvent> {
info!(
"Rotating session keys (rotation #{}).",
self.rotation_count + 1
);
let new_keypair = KeyPair::generate();
let event = KeyRotationEvent {
new_eph_pub: new_keypair.public_key_bytes(),
rotation_count: self.rotation_count,
};
self.next_keypair = Some(new_keypair);
self.last_rotation = Instant::now();
self.bytes_since_rotation = 0;
self.rotation_count += 1;
Ok(event)
}
pub fn commit_rotation(&mut self) {
if let Some(next) = self.next_keypair.take() {
self.current_keypair = next;
debug!("Key rotation committed successfully");
}
}
pub fn record_bytes(&mut self, bytes: u64) {
self.bytes_since_rotation += bytes;
}
pub fn current_public_key(&self) -> [u8; X25519_PUBLIC_KEY_SIZE] {
self.current_keypair.public_key_bytes()
}
pub fn next_public_key(&self) -> Option<[u8; X25519_PUBLIC_KEY_SIZE]> {
self.next_keypair.as_ref().map(|k| k.public_key_bytes())
}
pub fn create_rotation_message(&self) -> Option<ControlPayload> {
self.next_public_key()
.map(|new_eph_pub| ControlPayload::KeyRotate { new_eph_pub })
}
pub fn stats(&self) -> KeyRotationStats {
KeyRotationStats {
rotation_count: self.rotation_count,
bytes_since_rotation: self.bytes_since_rotation,
time_since_rotation: self.last_rotation.elapsed(),
next_rotation_in: Duration::from_secs(self.config.time_interval_secs)
.saturating_sub(self.last_rotation.elapsed()),
}
}
}
#[derive(Debug, Clone)]
pub struct KeyRotationEvent {
pub new_eph_pub: [u8; X25519_PUBLIC_KEY_SIZE],
pub rotation_count: u64,
}
#[derive(Debug, Clone)]
pub struct KeyRotationStats {
pub rotation_count: u64,
pub bytes_since_rotation: u64,
pub time_since_rotation: Duration,
pub next_rotation_in: Duration,
}