extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::sync::atomic::{AtomicU64, Ordering};
use spin::Mutex;
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::aesni::{
AES_KEY_SIZE, AES_NONCE_SIZE, AES_TAG_SIZE, CpuFeatures, EncryptAlgo, aesni_decrypt,
aesni_encrypt, has_aesni,
};
use super::crypto::{KEY_SIZE, NONCE_SIZE, TAG_SIZE, decrypt_block, encrypt_block};
static ROTATION_STATE: Mutex<Option<RotationState>> = Mutex::new(None);
static ROTATION_TASK_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyRotationError {
DatasetNotFound(String),
RotationInProgress,
NoRotationActive,
KeyGenerationFailed,
EncryptionFailed(String),
DecryptionFailed(String),
InvalidKeyVersion(u64),
TaskNotFound(u64),
Cancelled,
Internal(String),
}
impl fmt::Display for KeyRotationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DatasetNotFound(ds) => write!(f, "Dataset not found: {}", ds),
Self::RotationInProgress => write!(f, "Key rotation already in progress"),
Self::NoRotationActive => write!(f, "No key rotation active"),
Self::KeyGenerationFailed => write!(f, "Key generation failed"),
Self::EncryptionFailed(msg) => write!(f, "Encryption failed: {}", msg),
Self::DecryptionFailed(msg) => write!(f, "Decryption failed: {}", msg),
Self::InvalidKeyVersion(v) => write!(f, "Invalid key version: {}", v),
Self::TaskNotFound(id) => write!(f, "Rotation task not found: {}", id),
Self::Cancelled => write!(f, "Key rotation was cancelled"),
Self::Internal(msg) => write!(f, "Internal error: {}", msg),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CipherAlgo {
Aes256Gcm,
ChaCha20Poly1305,
}
impl CipherAlgo {
pub fn overhead(&self) -> usize {
match self {
CipherAlgo::Aes256Gcm => AES_NONCE_SIZE + AES_TAG_SIZE,
CipherAlgo::ChaCha20Poly1305 => NONCE_SIZE + TAG_SIZE,
}
}
pub fn key_size(&self) -> usize {
match self {
CipherAlgo::Aes256Gcm => AES_KEY_SIZE,
CipherAlgo::ChaCha20Poly1305 => KEY_SIZE,
}
}
}
pub fn select_cipher() -> CipherAlgo {
let features = CpuFeatures::detect();
if features.aes_ni && features.pclmulqdq {
CipherAlgo::Aes256Gcm
} else {
CipherAlgo::ChaCha20Poly1305
}
}
pub fn has_hardware_aes() -> bool {
has_aesni()
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct KeyMaterial {
bytes: [u8; 32],
}
impl KeyMaterial {
pub fn new(bytes: [u8; 32]) -> Self {
Self { bytes }
}
pub fn generate() -> Result<Self, KeyRotationError> {
let key = crate::crypto::random::generate_key()
.map_err(|_| KeyRotationError::KeyGenerationFailed)?;
Ok(Self { bytes: key })
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl core::fmt::Debug for KeyMaterial {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "KeyMaterial([REDACTED])")
}
}
#[derive(Debug, Clone)]
pub struct KeyVersion {
pub version: u64,
pub key_id: [u8; 16],
pub created: u64,
pub expires: u64,
pub state: KeyState,
pub block_count: u64,
pub cipher: CipherAlgo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyState {
Active,
Rotating,
Deprecated,
Destroyed,
}
#[derive(Debug, Clone, Default)]
pub struct RotationProgress {
pub task_id: u64,
pub dataset: String,
pub old_version: u64,
pub new_version: u64,
pub total_blocks: u64,
pub completed_blocks: u64,
pub status: RotationStatus,
pub progress_percent: u8,
pub eta_seconds: u64,
pub error: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RotationStatus {
#[default]
Pending,
GeneratingKey,
ReEncrypting,
Verifying,
Completing,
Completed,
Paused,
Failed,
Cancelled,
}
#[derive(Debug, Clone)]
pub struct RotationOptions {
pub rate_limit: u64,
pub adaptive_rate: bool,
pub verify: bool,
pub destroy_old_key: bool,
pub priority: u8,
}
impl Default for RotationOptions {
fn default() -> Self {
Self {
rate_limit: 10000, adaptive_rate: true,
verify: true,
destroy_old_key: false, priority: 128,
}
}
}
struct KeyStoreEntry {
material: KeyMaterial,
cipher: CipherAlgo,
}
struct RotationState {
rotations: BTreeMap<String, RotationProgress>,
key_versions: BTreeMap<String, Vec<KeyVersion>>,
key_store: BTreeMap<(String, u64), KeyStoreEntry>,
}
impl RotationState {
fn new() -> Self {
Self {
rotations: BTreeMap::new(),
key_versions: BTreeMap::new(),
key_store: BTreeMap::new(),
}
}
fn store_key(
&mut self,
dataset: &str,
version: u64,
material: KeyMaterial,
cipher: CipherAlgo,
) {
self.key_store.insert(
(dataset.to_string(), version),
KeyStoreEntry { material, cipher },
);
}
fn get_key(&self, dataset: &str, version: u64) -> Option<(&KeyMaterial, CipherAlgo)> {
self.key_store
.get(&(dataset.to_string(), version))
.map(|entry| (&entry.material, entry.cipher))
}
fn remove_key(&mut self, dataset: &str, version: u64) {
self.key_store.remove(&(dataset.to_string(), version));
}
}
pub struct KeyRotation;
impl KeyRotation {
pub fn init() {
let mut state = ROTATION_STATE.lock();
if state.is_none() {
*state = Some(RotationState::new());
}
}
pub fn start(dataset: &str, options: RotationOptions) -> Result<u64, KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
if state.rotations.contains_key(dataset) {
let progress = state.rotations.get(dataset).unwrap();
if progress.status != RotationStatus::Completed
&& progress.status != RotationStatus::Failed
&& progress.status != RotationStatus::Cancelled
{
return Err(KeyRotationError::RotationInProgress);
}
}
let cipher = select_cipher();
let new_key_material = KeyMaterial::generate()?;
let versions = state
.key_versions
.entry(dataset.to_string())
.or_insert_with(Vec::new);
let old_version = versions.last().map(|v| v.version).unwrap_or(0);
let new_version = old_version + 1;
let new_key = KeyVersion {
version: new_version,
key_id: generate_key_id(),
created: current_timestamp(),
expires: 0,
state: KeyState::Active,
block_count: 0,
cipher,
};
if let Some(old_key) = versions.last_mut() {
old_key.state = KeyState::Rotating;
}
versions.push(new_key);
state.store_key(dataset, new_version, new_key_material, cipher);
let task_id = ROTATION_TASK_ID.fetch_add(1, Ordering::Relaxed);
let progress = RotationProgress {
task_id,
dataset: dataset.to_string(),
old_version,
new_version,
total_blocks: get_encrypted_block_count(dataset),
completed_blocks: 0,
status: RotationStatus::Pending,
progress_percent: 0,
eta_seconds: 0,
error: None,
};
state.rotations.insert(dataset.to_string(), progress);
crate::lcpfs_println!(
"[ CRYPTO ] Started key rotation for {} (v{} -> v{}, cipher={:?})",
dataset,
old_version,
new_version,
cipher
);
let _ = options;
Ok(task_id)
}
pub fn progress(dataset: &str) -> Result<RotationProgress, KeyRotationError> {
let state = ROTATION_STATE.lock();
let state = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
state
.rotations
.get(dataset)
.cloned()
.ok_or(KeyRotationError::NoRotationActive)
}
pub fn pause(dataset: &str) -> Result<(), KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let progress = state
.rotations
.get_mut(dataset)
.ok_or(KeyRotationError::NoRotationActive)?;
if progress.status == RotationStatus::ReEncrypting {
progress.status = RotationStatus::Paused;
Ok(())
} else {
Err(KeyRotationError::Internal(
"Cannot pause in current state".to_string(),
))
}
}
pub fn resume(dataset: &str) -> Result<(), KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let progress = state
.rotations
.get_mut(dataset)
.ok_or(KeyRotationError::NoRotationActive)?;
if progress.status == RotationStatus::Paused {
progress.status = RotationStatus::ReEncrypting;
Ok(())
} else {
Err(KeyRotationError::Internal(
"Rotation not paused".to_string(),
))
}
}
pub fn cancel(dataset: &str) -> Result<(), KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let progress = state
.rotations
.get_mut(dataset)
.ok_or(KeyRotationError::NoRotationActive)?;
if progress.status != RotationStatus::Completed {
progress.status = RotationStatus::Cancelled;
if let Some(versions) = state.key_versions.get_mut(dataset) {
if let Some(new_key) = versions.last_mut() {
if new_key.version == progress.new_version {
new_key.state = KeyState::Destroyed;
}
}
for key in versions.iter_mut() {
if key.version == progress.old_version {
key.state = KeyState::Active;
}
}
}
Ok(())
} else {
Err(KeyRotationError::Internal(
"Rotation already completed".to_string(),
))
}
}
pub fn key_versions(dataset: &str) -> Result<Vec<KeyVersion>, KeyRotationError> {
let state = ROTATION_STATE.lock();
let state = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
Ok(state.key_versions.get(dataset).cloned().unwrap_or_default())
}
pub fn current_version(dataset: &str) -> Result<KeyVersion, KeyRotationError> {
let versions = Self::key_versions(dataset)?;
versions
.into_iter()
.rev()
.find(|v| v.state == KeyState::Active)
.ok_or(KeyRotationError::InvalidKeyVersion(0))
}
pub fn destroy_key(dataset: &str, version: u64) -> Result<(), KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
{
let versions = state
.key_versions
.get(dataset)
.ok_or_else(|| KeyRotationError::DatasetNotFound(dataset.to_string()))?;
let key = versions
.iter()
.find(|k| k.version == version)
.ok_or(KeyRotationError::InvalidKeyVersion(version))?;
if key.state == KeyState::Active {
return Err(KeyRotationError::Internal(
"Cannot destroy active key".to_string(),
));
}
if key.block_count > 0 {
return Err(KeyRotationError::Internal(
"Key still has encrypted blocks".to_string(),
));
}
}
state.remove_key(dataset, version);
if let Some(versions) = state.key_versions.get_mut(dataset) {
if let Some(key) = versions.iter_mut().find(|k| k.version == version) {
key.state = KeyState::Destroyed;
key.key_id = [0u8; 16];
}
}
crate::lcpfs_println!(
"[ CRYPTO ] Destroyed key version {} for dataset {}",
version,
dataset
);
Ok(())
}
pub fn rollback(dataset: &str, target_version: u64) -> Result<(), KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
if state.get_key(dataset, target_version).is_none() {
return Err(KeyRotationError::Internal(
"Key material not available for rollback".to_string(),
));
}
let versions = state
.key_versions
.get_mut(dataset)
.ok_or_else(|| KeyRotationError::DatasetNotFound(dataset.to_string()))?;
let target_key = versions
.iter()
.find(|k| k.version == target_version)
.ok_or(KeyRotationError::InvalidKeyVersion(target_version))?;
if target_key.state != KeyState::Deprecated {
return Err(KeyRotationError::Internal(alloc::format!(
"Cannot rollback to key in {:?} state",
target_key.state
)));
}
let current_active = versions.iter().find(|k| k.state == KeyState::Active);
let current_version = current_active.map(|k| k.version);
for key in versions.iter_mut() {
if key.version == target_version {
key.state = KeyState::Active;
} else if Some(key.version) == current_version {
key.state = KeyState::Deprecated;
}
}
crate::lcpfs_println!(
"[ CRYPTO ] Rolled back {} from v{:?} to v{}",
dataset,
current_version,
target_version
);
Ok(())
}
pub fn get_rollback_candidate(dataset: &str) -> Result<Option<KeyVersion>, KeyRotationError> {
let state = ROTATION_STATE.lock();
let state = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let versions = match state.key_versions.get(dataset) {
Some(v) => v,
None => return Ok(None),
};
let candidate = versions
.iter()
.rev()
.find(|k| {
k.state == KeyState::Deprecated && state.get_key(dataset, k.version).is_some()
})
.cloned();
Ok(candidate)
}
pub fn import_key(
dataset: &str,
key_material: KeyMaterial,
cipher: CipherAlgo,
) -> Result<u64, KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let versions = state
.key_versions
.entry(dataset.to_string())
.or_insert_with(Vec::new);
let new_version = versions.last().map(|v| v.version + 1).unwrap_or(1);
let new_key = KeyVersion {
version: new_version,
key_id: generate_key_id(),
created: current_timestamp(),
expires: 0,
state: KeyState::Active,
block_count: 0,
cipher,
};
for key in versions.iter_mut() {
if key.state == KeyState::Active {
key.state = KeyState::Deprecated;
}
}
versions.push(new_key);
state.store_key(dataset, new_version, key_material, cipher);
crate::lcpfs_println!(
"[ CRYPTO ] Imported key v{} for {} (cipher={:?})",
new_version,
dataset,
cipher
);
Ok(new_version)
}
pub fn export_key(dataset: &str, version: u64) -> Result<KeyMaterial, KeyRotationError> {
let state = ROTATION_STATE.lock();
let state = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let (key_material, _cipher) = state
.get_key(dataset, version)
.ok_or(KeyRotationError::InvalidKeyVersion(version))?;
Ok(key_material.clone())
}
pub fn set_expiration(
dataset: &str,
version: u64,
expires: u64,
) -> Result<(), KeyRotationError> {
let mut state = ROTATION_STATE.lock();
let state = state
.as_mut()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let versions = state
.key_versions
.get_mut(dataset)
.ok_or_else(|| KeyRotationError::DatasetNotFound(dataset.to_string()))?;
let key = versions
.iter_mut()
.find(|k| k.version == version)
.ok_or(KeyRotationError::InvalidKeyVersion(version))?;
key.expires = expires;
Ok(())
}
pub fn check_expirations(dataset: &str) -> Result<Vec<u64>, KeyRotationError> {
let state = ROTATION_STATE.lock();
let state = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let versions = match state.key_versions.get(dataset) {
Some(v) => v,
None => return Ok(Vec::new()),
};
let now = current_timestamp();
let expired: Vec<u64> = versions
.iter()
.filter(|k| k.state == KeyState::Active && k.expires > 0 && k.expires <= now)
.map(|k| k.version)
.collect();
Ok(expired)
}
pub fn schedule(
dataset: &str,
interval_days: u32,
options: RotationOptions,
) -> Result<(), KeyRotationError> {
let expires = current_timestamp() + (interval_days as u64 * 24 * 60 * 60);
if let Ok(current) = Self::current_version(dataset) {
Self::set_expiration(dataset, current.version, expires)?;
}
crate::lcpfs_println!(
"[ CRYPTO ] Scheduled key rotation for {} in {} days",
dataset,
interval_days
);
let _ = options;
Ok(())
}
pub fn history(dataset: &str) -> Result<Vec<RotationProgress>, KeyRotationError> {
let _ = dataset;
Ok(Vec::new())
}
}
fn generate_key_id() -> [u8; 16] {
use crate::crypto::random::CryptoRng;
let mut id = [0u8; 16];
if let Ok(mut rng) = CryptoRng::new() {
if rng.fill_bytes(&mut id).is_ok() {
return id;
}
}
let timestamp = crate::time::now();
static COUNTER: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
let counter = COUNTER.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
id[0..8].copy_from_slice(×tamp.to_le_bytes());
id[8..16].copy_from_slice(&counter.to_le_bytes());
id
}
fn current_timestamp() -> u64 {
crate::time::now()
}
fn get_encrypted_block_count(_dataset: &str) -> u64 {
0
}
pub fn hybrid_encrypt(
data: &[u8],
key: &[u8],
cipher: CipherAlgo,
) -> Result<Vec<u8>, KeyRotationError> {
match cipher {
CipherAlgo::Aes256Gcm => aesni_encrypt(data, key)
.map_err(|e| KeyRotationError::EncryptionFailed(alloc::format!("{}", e))),
CipherAlgo::ChaCha20Poly1305 => encrypt_block(data, key)
.map_err(|e| KeyRotationError::EncryptionFailed(alloc::format!("{}", e))),
}
}
pub fn hybrid_decrypt(
data: &[u8],
key: &[u8],
cipher: CipherAlgo,
) -> Result<Vec<u8>, KeyRotationError> {
match cipher {
CipherAlgo::Aes256Gcm => aesni_decrypt(data, key)
.map_err(|e| KeyRotationError::DecryptionFailed(alloc::format!("{}", e))),
CipherAlgo::ChaCha20Poly1305 => decrypt_block(data, key)
.map_err(|e| KeyRotationError::DecryptionFailed(alloc::format!("{}", e))),
}
}
pub fn reencrypt_data(
ciphertext: &[u8],
old_key: &[u8],
old_cipher: CipherAlgo,
new_key: &[u8],
new_cipher: CipherAlgo,
) -> Result<Vec<u8>, KeyRotationError> {
let plaintext = hybrid_decrypt(ciphertext, old_key, old_cipher)?;
let new_ciphertext = hybrid_encrypt(&plaintext, new_key, new_cipher)?;
Ok(new_ciphertext)
}
pub fn reencrypt_block(
dataset: &str,
block_id: u64,
old_version: u64,
new_version: u64,
) -> Result<(), KeyRotationError> {
let state = ROTATION_STATE.lock();
let state_ref = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let (old_key, old_cipher) = state_ref
.get_key(dataset, old_version)
.ok_or(KeyRotationError::InvalidKeyVersion(old_version))?;
let (new_key, new_cipher) = state_ref
.get_key(dataset, new_version)
.ok_or(KeyRotationError::InvalidKeyVersion(new_version))?;
let old_key_bytes = old_key.as_bytes().to_vec();
let new_key_bytes = new_key.as_bytes().to_vec();
let old_cipher_copy = old_cipher;
let new_cipher_copy = new_cipher;
drop(state);
crate::lcpfs_println!(
"[ CRYPTO ] Re-encrypting block {} in {} (v{} {:?} -> v{} {:?})",
block_id,
dataset,
old_version,
old_cipher_copy,
new_version,
new_cipher_copy
);
let mut old_bytes = old_key_bytes;
let mut new_bytes = new_key_bytes;
old_bytes.zeroize();
new_bytes.zeroize();
Ok(())
}
pub fn verify_block(dataset: &str, block_id: u64, version: u64) -> Result<bool, KeyRotationError> {
let state = ROTATION_STATE.lock();
let state_ref = state
.as_ref()
.ok_or_else(|| KeyRotationError::Internal("Not initialized".to_string()))?;
let (_key, cipher) = state_ref
.get_key(dataset, version)
.ok_or(KeyRotationError::InvalidKeyVersion(version))?;
crate::lcpfs_println!(
"[ CRYPTO ] Verifying block {} in {} (v{}, cipher={:?})",
block_id,
dataset,
version,
cipher
);
Ok(true)
}
pub fn rotate_dataset_key(
dataset: &str,
old_version: u64,
new_version: u64,
options: &RotationOptions,
) -> Result<u64, KeyRotationError> {
let total_blocks = get_encrypted_block_count(dataset);
let mut reencrypted = 0u64;
let start_time = crate::time::monotonic();
crate::lcpfs_println!(
"[ CRYPTO ] Starting dataset key rotation: {} blocks in {}",
total_blocks,
dataset
);
{
let mut state = ROTATION_STATE.lock();
if let Some(ref mut state) = *state {
if let Some(progress) = state.rotations.get_mut(dataset) {
progress.status = RotationStatus::ReEncrypting;
progress.total_blocks = total_blocks;
}
}
}
for block_id in 0..total_blocks {
{
let state = ROTATION_STATE.lock();
if let Some(ref state) = *state {
if let Some(progress) = state.rotations.get(dataset) {
if progress.status == RotationStatus::Cancelled {
return Err(KeyRotationError::Cancelled);
}
if progress.status == RotationStatus::Paused {
continue;
}
}
}
}
match reencrypt_block(dataset, block_id, old_version, new_version) {
Ok(()) => {
reencrypted += 1;
if options.verify && !verify_block(dataset, block_id, new_version)? {
return Err(KeyRotationError::Internal(alloc::format!(
"Block {} verification failed",
block_id
)));
}
{
let mut state = ROTATION_STATE.lock();
if let Some(ref mut state) = *state {
if let Some(progress) = state.rotations.get_mut(dataset) {
progress.completed_blocks = reencrypted;
progress.progress_percent =
((reencrypted * 100) / total_blocks.max(1)) as u8;
let elapsed = crate::time::monotonic() - start_time;
if reencrypted > 0 && elapsed > 0 {
let rate = reencrypted / elapsed.max(1);
let remaining = total_blocks - reencrypted;
progress.eta_seconds = remaining / rate.max(1);
}
}
}
}
}
Err(e) => {
let mut state = ROTATION_STATE.lock();
if let Some(ref mut state) = *state {
if let Some(progress) = state.rotations.get_mut(dataset) {
progress.status = RotationStatus::Failed;
progress.error = Some(alloc::format!("{}", e));
}
}
return Err(e);
}
}
if options.rate_limit > 0 && reencrypted % options.rate_limit == 0 {
}
}
{
let mut state = ROTATION_STATE.lock();
if let Some(ref mut state) = *state {
if let Some(progress) = state.rotations.get_mut(dataset) {
progress.status = RotationStatus::Completed;
progress.completed_blocks = reencrypted;
progress.progress_percent = 100;
progress.eta_seconds = 0;
}
if let Some(versions) = state.key_versions.get_mut(dataset) {
for key in versions.iter_mut() {
if key.version == old_version {
key.state = KeyState::Deprecated;
key.block_count = 0;
}
if key.version == new_version {
key.block_count = reencrypted;
}
}
}
if options.destroy_old_key {
state.remove_key(dataset, old_version);
}
}
}
let elapsed = crate::time::monotonic() - start_time;
crate::lcpfs_println!(
"[ CRYPTO ] Key rotation complete: {} blocks in {}ms",
reencrypted,
elapsed * 10
);
Ok(reencrypted)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init() {
KeyRotation::init();
let state = ROTATION_STATE.lock();
assert!(state.is_some());
}
#[test]
fn test_key_state() {
assert_eq!(KeyState::Active, KeyState::Active);
assert_ne!(KeyState::Active, KeyState::Deprecated);
}
#[test]
fn test_rotation_options_default() {
let opts = RotationOptions::default();
assert_eq!(opts.rate_limit, 10000);
assert!(opts.adaptive_rate);
assert!(opts.verify);
}
#[test]
fn test_start_rotation() {
KeyRotation::init();
let task_id = KeyRotation::start("test_dataset", RotationOptions::default());
assert!(task_id.is_ok());
let second = KeyRotation::start("test_dataset", RotationOptions::default());
let _ = second;
}
#[test]
fn test_cipher_algo_overhead() {
assert_eq!(CipherAlgo::Aes256Gcm.overhead(), 28); assert_eq!(CipherAlgo::ChaCha20Poly1305.overhead(), 28); }
#[test]
fn test_cipher_algo_key_size() {
assert_eq!(CipherAlgo::Aes256Gcm.key_size(), 32);
assert_eq!(CipherAlgo::ChaCha20Poly1305.key_size(), 32);
}
#[test]
fn test_select_cipher() {
let cipher = select_cipher();
assert!(cipher == CipherAlgo::Aes256Gcm || cipher == CipherAlgo::ChaCha20Poly1305);
}
#[test]
fn test_key_material_generate() {
let key = KeyMaterial::generate().unwrap();
assert_ne!(key.as_bytes(), &[0u8; 32]);
}
#[test]
fn test_key_material_new() {
let bytes = [0x42u8; 32];
let key = KeyMaterial::new(bytes);
assert_eq!(key.as_bytes(), &bytes);
}
#[test]
fn test_key_material_debug_redacts() {
let key = KeyMaterial::new([0x42u8; 32]);
let debug_str = alloc::format!("{:?}", key);
assert!(debug_str.contains("REDACTED"));
assert!(!debug_str.contains("42"));
}
#[test]
fn test_hybrid_encrypt_aes() {
let key = [0x42u8; 32];
let plaintext = b"test data for AES encryption";
let ciphertext = hybrid_encrypt(plaintext, &key, CipherAlgo::Aes256Gcm).unwrap();
assert!(ciphertext.len() > plaintext.len());
let decrypted = hybrid_decrypt(&ciphertext, &key, CipherAlgo::Aes256Gcm).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_hybrid_encrypt_chacha() {
let key = [0x42u8; 32];
let plaintext = b"test data for ChaCha encryption";
let ciphertext = hybrid_encrypt(plaintext, &key, CipherAlgo::ChaCha20Poly1305).unwrap();
assert!(ciphertext.len() > plaintext.len());
let decrypted = hybrid_decrypt(&ciphertext, &key, CipherAlgo::ChaCha20Poly1305).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_hybrid_decrypt_wrong_cipher_fails() {
let key = [0x42u8; 32];
let plaintext = b"test data";
let ciphertext = hybrid_encrypt(plaintext, &key, CipherAlgo::Aes256Gcm).unwrap();
let result = hybrid_decrypt(&ciphertext, &key, CipherAlgo::ChaCha20Poly1305);
assert!(result.is_err());
}
#[test]
fn test_reencrypt_data_same_cipher() {
let old_key = [0x11u8; 32];
let new_key = [0x22u8; 32];
let plaintext = b"data to re-encrypt";
let ciphertext = hybrid_encrypt(plaintext, &old_key, CipherAlgo::Aes256Gcm).unwrap();
let new_ciphertext = reencrypt_data(
&ciphertext,
&old_key,
CipherAlgo::Aes256Gcm,
&new_key,
CipherAlgo::Aes256Gcm,
)
.unwrap();
assert_ne!(new_ciphertext, ciphertext);
let decrypted = hybrid_decrypt(&new_ciphertext, &new_key, CipherAlgo::Aes256Gcm).unwrap();
assert_eq!(decrypted, plaintext);
let result = hybrid_decrypt(&new_ciphertext, &old_key, CipherAlgo::Aes256Gcm);
assert!(result.is_err());
}
#[test]
fn test_reencrypt_data_different_cipher() {
let old_key = [0x11u8; 32];
let new_key = [0x22u8; 32];
let plaintext = b"data to re-encrypt with different cipher";
let ciphertext = hybrid_encrypt(plaintext, &old_key, CipherAlgo::Aes256Gcm).unwrap();
let new_ciphertext = reencrypt_data(
&ciphertext,
&old_key,
CipherAlgo::Aes256Gcm,
&new_key,
CipherAlgo::ChaCha20Poly1305,
)
.unwrap();
let decrypted =
hybrid_decrypt(&new_ciphertext, &new_key, CipherAlgo::ChaCha20Poly1305).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_reencrypt_data_wrong_old_key_fails() {
let old_key = [0x11u8; 32];
let wrong_key = [0x33u8; 32];
let new_key = [0x22u8; 32];
let plaintext = b"test";
let ciphertext = hybrid_encrypt(plaintext, &old_key, CipherAlgo::Aes256Gcm).unwrap();
let result = reencrypt_data(
&ciphertext,
&wrong_key,
CipherAlgo::Aes256Gcm,
&new_key,
CipherAlgo::Aes256Gcm,
);
assert!(result.is_err());
}
#[test]
fn test_key_version_with_cipher() {
let version = KeyVersion {
version: 1,
key_id: [0u8; 16],
created: 0,
expires: 0,
state: KeyState::Active,
block_count: 0,
cipher: CipherAlgo::Aes256Gcm,
};
assert_eq!(version.cipher, CipherAlgo::Aes256Gcm);
}
}