use rand::rngs::OsRng;
use rand::RngCore;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fmt;
use std::sync::RwLock;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum KeyAlgorithm {
Aes256Gcm,
HmacSha256,
Rsa2048,
Rsa4096,
}
impl KeyAlgorithm {
pub fn as_str(&self) -> &'static str {
match self {
KeyAlgorithm::Aes256Gcm => "aes-256-gcm",
KeyAlgorithm::HmacSha256 => "hmac-sha256",
KeyAlgorithm::Rsa2048 => "rsa-2048",
KeyAlgorithm::Rsa4096 => "rsa-4096",
}
}
pub fn key_length(&self) -> usize {
match self {
KeyAlgorithm::Aes256Gcm => 32,
KeyAlgorithm::HmacSha256 => 32,
KeyAlgorithm::Rsa2048 => 256,
KeyAlgorithm::Rsa4096 => 512,
}
}
pub fn is_symmetric(&self) -> bool {
matches!(self, KeyAlgorithm::Aes256Gcm | KeyAlgorithm::HmacSha256)
}
}
impl fmt::Display for KeyAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum KeyPurpose {
Encryption,
Signing,
EncryptionAndSigning,
}
impl KeyPurpose {
pub fn as_str(&self) -> &'static str {
match self {
KeyPurpose::Encryption => "encryption",
KeyPurpose::Signing => "signing",
KeyPurpose::EncryptionAndSigning => "encryption+signing",
}
}
pub fn can_encrypt(&self) -> bool {
matches!(
self,
KeyPurpose::Encryption | KeyPurpose::EncryptionAndSigning
)
}
pub fn can_sign(&self) -> bool {
matches!(self, KeyPurpose::Signing | KeyPurpose::EncryptionAndSigning)
}
}
impl fmt::Display for KeyPurpose {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum KeyStatus {
Active,
Deprecated,
Revoked,
Expired,
}
impl KeyStatus {
pub fn as_str(&self) -> &'static str {
match self {
KeyStatus::Active => "active",
KeyStatus::Deprecated => "deprecated",
KeyStatus::Revoked => "revoked",
KeyStatus::Expired => "expired",
}
}
pub fn is_usable_for_new(&self) -> bool {
matches!(self, KeyStatus::Active)
}
pub fn is_usable_for_old(&self) -> bool {
matches!(
self,
KeyStatus::Active | KeyStatus::Deprecated | KeyStatus::Expired
)
}
pub fn is_dead(&self) -> bool {
matches!(self, KeyStatus::Revoked)
}
}
impl fmt::Display for KeyStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyMetadata {
pub key_id: String,
pub name: String,
pub algorithm: KeyAlgorithm,
pub purpose: KeyPurpose,
pub version: u32,
pub created_at: SystemTime,
pub expires_at: Option<SystemTime>,
pub status: KeyStatus,
pub description: String,
pub tags: Vec<String>,
}
impl KeyMetadata {
fn new(
key_id: String,
name: String,
algorithm: KeyAlgorithm,
purpose: KeyPurpose,
version: u32,
) -> Self {
Self {
key_id,
name,
algorithm,
purpose,
version,
created_at: SystemTime::now(),
expires_at: None,
status: KeyStatus::Active,
description: String::new(),
tags: Vec::new(),
}
}
pub fn with_expiry(mut self, expires_at: SystemTime) -> Self {
self.expires_at = Some(expires_at);
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn is_expired(&self) -> bool {
match self.expires_at {
Some(expiry) => SystemTime::now() >= expiry,
None => false,
}
}
pub fn age(&self) -> Duration {
SystemTime::now()
.duration_since(self.created_at)
.unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct KeyEntry {
pub metadata: KeyMetadata,
pub material: Vec<u8>,
pub fingerprint: String,
}
impl KeyEntry {
pub fn compute_fingerprint(material: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(material);
let result = hasher.finalize();
result.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn verify_fingerprint(&self) -> bool {
Self::compute_fingerprint(&self.material) == self.fingerprint
}
}
#[derive(Debug, Default)]
pub struct KeyGenerator;
impl KeyGenerator {
pub fn new() -> Self {
Self
}
pub fn generate_material(&self, algorithm: KeyAlgorithm) -> Vec<u8> {
let mut material = vec![0u8; algorithm.key_length()];
OsRng.fill_bytes(&mut material);
material
}
pub fn generate(
&self,
name: impl Into<String>,
algorithm: KeyAlgorithm,
purpose: KeyPurpose,
version: u32,
) -> KeyEntry {
let material = self.generate_material(algorithm);
let fingerprint = KeyEntry::compute_fingerprint(&material);
let key_id = Self::generate_key_id();
let metadata = KeyMetadata::new(key_id, name.into(), algorithm, purpose, version);
KeyEntry {
metadata,
material,
fingerprint,
}
}
pub fn generate_key_id() -> String {
let mut bytes = [0u8; 16];
OsRng.fill_bytes(&mut bytes);
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
}
#[derive(Debug, Clone, Default)]
pub enum RotationPolicy {
TimeInterval(Duration),
UsageCount(u64),
TimeIntervalOrUsage(Duration, u64),
#[default]
Never,
}
impl RotationPolicy {
pub fn needs_rotation(&self, age: Duration, usage: u64) -> bool {
match self {
RotationPolicy::TimeInterval(interval) => age >= *interval,
RotationPolicy::UsageCount(count) => usage >= *count,
RotationPolicy::TimeIntervalOrUsage(interval, count) => {
age >= *interval || usage >= *count
}
RotationPolicy::Never => false,
}
}
pub fn as_str(&self) -> &'static str {
match self {
RotationPolicy::TimeInterval(_) => "time-interval",
RotationPolicy::UsageCount(_) => "usage-count",
RotationPolicy::TimeIntervalOrUsage(_, _) => "time-or-usage",
RotationPolicy::Never => "never",
}
}
}
#[derive(Debug, Default)]
pub struct KeyStore {
keys: HashMap<String, KeyEntry>,
name_index: HashMap<String, Vec<String>>,
}
impl KeyStore {
pub fn new() -> Self {
Self::default()
}
pub fn put(&mut self, entry: KeyEntry) -> String {
let key_id = entry.metadata.key_id.clone();
let name = entry.metadata.name.clone();
self.keys.insert(key_id.clone(), entry);
let list = self.name_index.entry(name).or_default();
list.push(key_id.clone());
list.sort_by_key(|id| {
std::cmp::Reverse(self.keys.get(id).map(|e| e.metadata.version).unwrap_or(0))
});
key_id
}
pub fn get(&self, key_id: &str) -> Option<&KeyEntry> {
self.keys.get(key_id)
}
pub fn get_mut(&mut self, key_id: &str) -> Option<&mut KeyEntry> {
self.keys.get_mut(key_id)
}
pub fn get_latest_by_name(&self, name: &str) -> Option<&KeyEntry> {
let list = self.name_index.get(name)?;
let latest_id = list.first()?;
self.keys.get(latest_id)
}
pub fn get_by_name_and_version(&self, name: &str, version: u32) -> Option<&KeyEntry> {
let list = self.name_index.get(name)?;
for id in list {
if let Some(entry) = self.keys.get(id) {
if entry.metadata.version == version {
return Some(entry);
}
}
}
None
}
pub fn get_all_versions(&self, name: &str) -> Vec<&KeyEntry> {
let list = match self.name_index.get(name) {
Some(l) => l,
None => return Vec::new(),
};
list.iter().filter_map(|id| self.keys.get(id)).collect()
}
pub fn get_by_status(&self, status: KeyStatus) -> Vec<&KeyEntry> {
self.keys
.values()
.filter(|e| e.metadata.status == status)
.collect()
}
pub fn get_by_tag(&self, tag: &str) -> Vec<&KeyEntry> {
self.keys
.values()
.filter(|e| e.metadata.tags.iter().any(|t| t == tag))
.collect()
}
pub fn remove(&mut self, key_id: &str) -> Option<KeyEntry> {
let entry = self.keys.remove(key_id)?;
let list = self.name_index.get_mut(&entry.metadata.name)?;
list.retain(|id| id != key_id);
if list.is_empty() {
self.name_index.remove(&entry.metadata.name);
}
Some(entry)
}
pub fn count(&self) -> usize {
self.keys.len()
}
pub fn version_count(&self, name: &str) -> usize {
self.name_index.get(name).map_or(0, |l| l.len())
}
pub fn names(&self) -> Vec<String> {
self.name_index.keys().cloned().collect()
}
pub fn set_status(&mut self, key_id: &str, status: KeyStatus) -> bool {
if let Some(entry) = self.keys.get_mut(key_id) {
entry.metadata.status = status;
true
} else {
false
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeyEvent {
Created,
Rotated,
Deprecated,
Revoked,
Expired,
Retrieved,
Deleted,
}
impl KeyEvent {
pub fn as_str(&self) -> &'static str {
match self {
KeyEvent::Created => "created",
KeyEvent::Rotated => "rotated",
KeyEvent::Deprecated => "deprecated",
KeyEvent::Revoked => "revoked",
KeyEvent::Expired => "expired",
KeyEvent::Retrieved => "retrieved",
KeyEvent::Deleted => "deleted",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyAuditEntry {
pub timestamp: SystemTime,
pub event: KeyEvent,
pub key_id: String,
pub key_name: String,
pub details: String,
}
impl KeyAuditEntry {
fn new(event: KeyEvent, key_id: String, key_name: String, details: impl Into<String>) -> Self {
Self {
timestamp: SystemTime::now(),
event,
key_id,
key_name,
details: details.into(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct KeyAuditLog {
entries: Vec<KeyAuditEntry>,
}
impl KeyAuditLog {
pub fn new() -> Self {
Self::default()
}
pub fn record(
&mut self,
event: KeyEvent,
key_id: String,
key_name: String,
details: impl Into<String>,
) {
self.entries
.push(KeyAuditEntry::new(event, key_id, key_name, details));
}
pub fn entries(&self) -> &[KeyAuditEntry] {
&self.entries
}
pub fn by_event(&self, event: KeyEvent) -> Vec<&KeyAuditEntry> {
self.entries.iter().filter(|e| e.event == event).collect()
}
pub fn by_key_name(&self, name: &str) -> Vec<&KeyAuditEntry> {
self.entries.iter().filter(|e| e.key_name == name).collect()
}
pub fn count(&self) -> usize {
self.entries.len()
}
}
pub struct KeyVault {
store: RwLock<KeyStore>,
rotation_policy: RotationPolicy,
usage_counters: RwLock<HashMap<String, u64>>,
audit_log: RwLock<KeyAuditLog>,
generator: KeyGenerator,
}
impl KeyVault {
pub fn new(rotation_policy: RotationPolicy) -> Self {
Self {
store: RwLock::new(KeyStore::new()),
rotation_policy,
usage_counters: RwLock::new(HashMap::new()),
audit_log: RwLock::new(KeyAuditLog::new()),
generator: KeyGenerator::new(),
}
}
pub fn generate_key(
&self,
name: &str,
algorithm: KeyAlgorithm,
purpose: KeyPurpose,
) -> Result<String, KeyError> {
let store = self.store.read().expect("store lock");
let version = store.version_count(name) as u32 + 1;
drop(store);
let entry = self.generator.generate(name, algorithm, purpose, version);
let key_id = entry.metadata.key_id.clone();
let fingerprint = entry.fingerprint.clone();
let mut store = self.store.write().expect("store lock");
store.put(entry);
self.audit_log.write().expect("audit lock").record(
KeyEvent::Created,
key_id.clone(),
name.to_string(),
format!(
"algorithm={}, version={}, fingerprint={}",
algorithm, version, fingerprint
),
);
Ok(key_id)
}
pub fn rotate_key(&self, name: &str) -> Result<String, KeyError> {
let mut store = self.store.write().expect("store lock");
let current = store
.get_latest_by_name(name)
.ok_or(KeyError::KeyNotFound {
name: name.to_string(),
})?;
if !current.metadata.status.is_usable_for_new() {
return Err(KeyError::KeyNotActive {
name: name.to_string(),
status: current.metadata.status,
});
}
let algorithm = current.metadata.algorithm;
let purpose = current.metadata.purpose;
let old_id = current.metadata.key_id.clone();
let old_version = current.metadata.version;
store.set_status(&old_id, KeyStatus::Deprecated);
drop(store);
let new_id = self.generate_key(name, algorithm, purpose)?;
self.audit_log.write().expect("audit lock").record(
KeyEvent::Rotated,
new_id.clone(),
name.to_string(),
format!(
"rotated from version {} to {}",
old_version,
old_version + 1
),
);
self.audit_log.write().expect("audit lock").record(
KeyEvent::Deprecated,
old_id,
name.to_string(),
format!("deprecated by rotation to version {}", old_version + 1),
);
Ok(new_id)
}
pub fn get_key(&self, key_id: &str) -> Option<KeyEntry> {
let entry = self
.store
.read()
.expect("store lock")
.get(key_id)
.cloned()?;
*self
.usage_counters
.write()
.expect("counters lock")
.entry(key_id.to_string())
.or_insert(0) += 1;
self.audit_log.write().expect("audit lock").record(
KeyEvent::Retrieved,
key_id.to_string(),
entry.metadata.name.clone(),
"retrieved by id",
);
Some(entry)
}
pub fn get_latest_key(&self, name: &str) -> Option<KeyEntry> {
let entry = self
.store
.read()
.expect("store lock")
.get_latest_by_name(name)
.cloned()?;
let key_id = entry.metadata.key_id.clone();
*self
.usage_counters
.write()
.expect("counters lock")
.entry(key_id)
.or_insert(0) += 1;
self.audit_log.write().expect("audit lock").record(
KeyEvent::Retrieved,
entry.metadata.key_id.clone(),
name.to_string(),
"retrieved latest by name",
);
Some(entry)
}
pub fn get_key_by_version(&self, name: &str, version: u32) -> Option<KeyEntry> {
self.store
.read()
.expect("store lock")
.get_by_name_and_version(name, version)
.cloned()
}
pub fn get_all_versions(&self, name: &str) -> Vec<KeyEntry> {
self.store
.read()
.expect("store lock")
.get_all_versions(name)
.into_iter()
.cloned()
.collect()
}
pub fn revoke_key(&self, key_id: &str) -> Result<(), KeyError> {
let mut store = self.store.write().expect("store lock");
let entry = store.get(key_id).ok_or(KeyError::KeyIdNotFound {
key_id: key_id.to_string(),
})?;
let name = entry.metadata.name.clone();
store.set_status(key_id, KeyStatus::Revoked);
drop(store);
self.audit_log.write().expect("audit lock").record(
KeyEvent::Revoked,
key_id.to_string(),
name,
"key revoked",
);
Ok(())
}
pub fn deprecate_key(&self, key_id: &str) -> Result<(), KeyError> {
let mut store = self.store.write().expect("store lock");
let entry = store.get(key_id).ok_or(KeyError::KeyIdNotFound {
key_id: key_id.to_string(),
})?;
let name = entry.metadata.name.clone();
store.set_status(key_id, KeyStatus::Deprecated);
drop(store);
self.audit_log.write().expect("audit lock").record(
KeyEvent::Deprecated,
key_id.to_string(),
name,
"key deprecated",
);
Ok(())
}
pub fn needs_rotation(&self, name: &str) -> bool {
let store = self.store.read().expect("store lock");
let entry = match store.get_latest_by_name(name) {
Some(e) => e,
None => return false,
};
let key_id = &entry.metadata.key_id;
let age = entry.metadata.age();
let usage = self
.usage_counters
.read()
.expect("counters lock")
.get(key_id)
.copied()
.unwrap_or(0);
self.rotation_policy.needs_rotation(age, usage)
}
pub fn auto_rotate(&self, name: &str) -> Option<String> {
if self.needs_rotation(name) {
self.rotate_key(name).ok()
} else {
None
}
}
pub fn key_count(&self) -> usize {
self.store.read().expect("store lock").count()
}
pub fn version_count(&self, name: &str) -> usize {
self.store.read().expect("store lock").version_count(name)
}
pub fn key_names(&self) -> Vec<String> {
self.store.read().expect("store lock").names()
}
pub fn audit_log(&self) -> KeyAuditLog {
self.audit_log.read().expect("audit lock").clone()
}
pub fn usage_count(&self, key_id: &str) -> u64 {
self.usage_counters
.read()
.expect("counters lock")
.get(key_id)
.copied()
.unwrap_or(0)
}
pub fn get_by_tag(&self, tag: &str) -> Vec<KeyEntry> {
self.store
.read()
.expect("store lock")
.get_by_tag(tag)
.into_iter()
.cloned()
.collect()
}
pub fn get_by_status(&self, status: KeyStatus) -> Vec<KeyEntry> {
self.store
.read()
.expect("store lock")
.get_by_status(status)
.into_iter()
.cloned()
.collect()
}
pub fn delete_key(&self, key_id: &str) -> Result<KeyEntry, KeyError> {
let mut store = self.store.write().expect("store lock");
let entry = store.remove(key_id).ok_or(KeyError::KeyIdNotFound {
key_id: key_id.to_string(),
})?;
let name = entry.metadata.name.clone();
drop(store);
self.audit_log.write().expect("audit lock").record(
KeyEvent::Deleted,
key_id.to_string(),
name,
"key deleted",
);
Ok(entry)
}
}
impl fmt::Debug for KeyVault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeyVault")
.field("key_count", &self.key_count())
.field("rotation_policy", &self.rotation_policy.as_str())
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyError {
KeyNotFound { name: String },
KeyIdNotFound { key_id: String },
KeyNotActive { name: String, status: KeyStatus },
PurposeMismatch {
expected: KeyPurpose,
actual: KeyPurpose,
},
KeyExpired { key_id: String },
KeyRevoked { key_id: String },
}
impl fmt::Display for KeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyError::KeyNotFound { name } => write!(f, "key '{}' not found", name),
KeyError::KeyIdNotFound { key_id } => write!(f, "key id '{}' not found", key_id),
KeyError::KeyNotActive { name, status } => {
write!(f, "key '{}' is not active (status: {})", name, status)
}
KeyError::PurposeMismatch { expected, actual } => {
write!(
f,
"key purpose mismatch: expected {}, actual {}",
expected, actual
)
}
KeyError::KeyExpired { key_id } => write!(f, "key '{}' is expired", key_id),
KeyError::KeyRevoked { key_id } => write!(f, "key '{}' is revoked", key_id),
}
}
}
impl std::error::Error for KeyError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyDerivationConfig {
pub algorithm: String,
pub iterations: u32,
pub salt_len: usize,
pub key_len: usize,
pub memory_kb: Option<u32>,
pub parallelism: Option<u32>,
}
impl Default for KeyDerivationConfig {
fn default() -> Self {
Self {
algorithm: "pbkdf2".to_string(),
iterations: 600_000,
salt_len: 32,
key_len: 32,
memory_kb: None,
parallelism: None,
}
}
}
impl KeyDerivationConfig {
pub fn pbkdf2(iterations: u32) -> Self {
Self {
algorithm: "pbkdf2".to_string(),
iterations,
..Default::default()
}
}
pub fn argon2id(iterations: u32, memory_kb: u32, parallelism: u32) -> Self {
Self {
algorithm: "argon2id".to_string(),
iterations,
memory_kb: Some(memory_kb),
parallelism: Some(parallelism),
..Default::default()
}
}
pub fn scrypt(iterations: u32) -> Self {
Self {
algorithm: "scrypt".to_string(),
iterations,
..Default::default()
}
}
pub fn is_argon2(&self) -> bool {
self.algorithm == "argon2id"
}
pub fn is_pbkdf2(&self) -> bool {
self.algorithm == "pbkdf2"
}
pub fn generate_salt(&self) -> Vec<u8> {
let mut salt = vec![0u8; self.salt_len];
OsRng.fill_bytes(&mut salt);
salt
}
}
pub struct NonceGenerator {
nonce_len: usize,
}
impl NonceGenerator {
pub fn for_gcm() -> Self {
Self { nonce_len: 12 }
}
pub fn for_chacha20() -> Self {
Self { nonce_len: 16 }
}
pub fn new(nonce_len: usize) -> Self {
Self { nonce_len }
}
pub fn generate(&self) -> Vec<u8> {
let mut nonce = vec![0u8; self.nonce_len];
OsRng.fill_bytes(&mut nonce);
nonce
}
pub fn len(&self) -> usize {
self.nonce_len
}
pub fn is_empty(&self) -> bool {
self.nonce_len == 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EncryptionContext {
pub aad: Vec<u8>,
pub label: String,
pub tenant_id: Option<String>,
}
impl EncryptionContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_aad(mut self, aad: Vec<u8>) -> Self {
self.aad = aad;
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = label.to_string();
self
}
pub fn with_tenant(mut self, tenant_id: &str) -> Self {
self.tenant_id = Some(tenant_id.to_string());
self
}
pub fn has_aad(&self) -> bool {
!self.aad.is_empty()
}
pub fn has_tenant(&self) -> bool {
self.tenant_id.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct KeyFingerprint {
pub sha256_hex: String,
pub algorithm: KeyAlgorithm,
}
impl KeyFingerprint {
pub fn from_key_material(key: &[u8], algorithm: KeyAlgorithm) -> Self {
let mut hasher = Sha256::new();
hasher.update(key);
let hash = hasher.finalize();
let hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
Self {
sha256_hex: hex,
algorithm,
}
}
pub fn short(&self) -> &str {
&self.sha256_hex[..8.min(self.sha256_hex.len())]
}
pub fn matches(&self, other: &Self) -> bool {
self.sha256_hex == other.sha256_hex && self.algorithm == other.algorithm
}
}
impl fmt::Display for KeyFingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.algorithm.as_str(), self.short())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityPolicy {
pub min_key_length: usize,
pub max_key_usage: u64,
pub max_key_age_secs: u64,
pub require_encryption_at_rest: bool,
pub require_key_rotation: bool,
pub allowed_algorithms: Vec<KeyAlgorithm>,
}
impl Default for SecurityPolicy {
fn default() -> Self {
Self {
min_key_length: 32,
max_key_usage: 1_000_000,
max_key_age_secs: 86400 * 90,
require_encryption_at_rest: true,
require_key_rotation: true,
allowed_algorithms: vec![KeyAlgorithm::Aes256Gcm, KeyAlgorithm::HmacSha256],
}
}
}
impl SecurityPolicy {
pub fn strict() -> Self {
Self {
min_key_length: 32,
max_key_usage: 100_000,
max_key_age_secs: 86400 * 30,
require_encryption_at_rest: true,
require_key_rotation: true,
allowed_algorithms: vec![KeyAlgorithm::Aes256Gcm],
}
}
pub fn is_algorithm_allowed(&self, alg: KeyAlgorithm) -> bool {
self.allowed_algorithms.contains(&alg)
}
pub fn is_key_length_ok(&self, key_len: usize) -> bool {
key_len >= self.min_key_length
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_algorithm_as_str() {
assert_eq!(KeyAlgorithm::Aes256Gcm.as_str(), "aes-256-gcm");
assert_eq!(KeyAlgorithm::HmacSha256.as_str(), "hmac-sha256");
assert_eq!(KeyAlgorithm::Rsa2048.as_str(), "rsa-2048");
assert_eq!(KeyAlgorithm::Rsa4096.as_str(), "rsa-4096");
}
#[test]
fn test_key_algorithm_key_length() {
assert_eq!(KeyAlgorithm::Aes256Gcm.key_length(), 32);
assert_eq!(KeyAlgorithm::HmacSha256.key_length(), 32);
assert_eq!(KeyAlgorithm::Rsa2048.key_length(), 256);
assert_eq!(KeyAlgorithm::Rsa4096.key_length(), 512);
}
#[test]
fn test_key_algorithm_is_symmetric() {
assert!(KeyAlgorithm::Aes256Gcm.is_symmetric());
assert!(KeyAlgorithm::HmacSha256.is_symmetric());
assert!(!KeyAlgorithm::Rsa2048.is_symmetric());
assert!(!KeyAlgorithm::Rsa4096.is_symmetric());
}
#[test]
fn test_key_purpose_can_encrypt() {
assert!(KeyPurpose::Encryption.can_encrypt());
assert!(!KeyPurpose::Signing.can_encrypt());
assert!(KeyPurpose::EncryptionAndSigning.can_encrypt());
}
#[test]
fn test_key_purpose_can_sign() {
assert!(!KeyPurpose::Encryption.can_sign());
assert!(KeyPurpose::Signing.can_sign());
assert!(KeyPurpose::EncryptionAndSigning.can_sign());
}
#[test]
fn test_key_status_is_usable_for_new() {
assert!(KeyStatus::Active.is_usable_for_new());
assert!(!KeyStatus::Deprecated.is_usable_for_new());
assert!(!KeyStatus::Revoked.is_usable_for_new());
assert!(!KeyStatus::Expired.is_usable_for_new());
}
#[test]
fn test_key_status_is_usable_for_old() {
assert!(KeyStatus::Active.is_usable_for_old());
assert!(KeyStatus::Deprecated.is_usable_for_old());
assert!(!KeyStatus::Revoked.is_usable_for_old());
assert!(KeyStatus::Expired.is_usable_for_old());
}
#[test]
fn test_key_status_is_dead() {
assert!(!KeyStatus::Active.is_dead());
assert!(KeyStatus::Revoked.is_dead());
}
#[test]
fn test_key_metadata_with_expiry() {
let now = SystemTime::now();
let meta = KeyMetadata::new(
"id1".to_string(),
"test".to_string(),
KeyAlgorithm::Aes256Gcm,
KeyPurpose::Encryption,
1,
)
.with_expiry(now + Duration::from_secs(3600));
assert!(!meta.is_expired());
assert_eq!(meta.expires_at, Some(now + Duration::from_secs(3600)));
}
#[test]
fn test_key_metadata_is_expired() {
let now = SystemTime::now();
let meta = KeyMetadata::new(
"id1".to_string(),
"test".to_string(),
KeyAlgorithm::Aes256Gcm,
KeyPurpose::Encryption,
1,
)
.with_expiry(now - Duration::from_secs(1));
assert!(meta.is_expired());
}
#[test]
fn test_key_metadata_with_tag_and_description() {
let meta = KeyMetadata::new(
"id1".to_string(),
"test".to_string(),
KeyAlgorithm::Aes256Gcm,
KeyPurpose::Encryption,
1,
)
.with_description("test key")
.with_tag("production")
.with_tag("critical");
assert_eq!(meta.description, "test key");
assert_eq!(meta.tags, vec!["production", "critical"]);
}
#[test]
fn test_key_entry_compute_fingerprint() {
let material = b"test-key-material";
let fp = KeyEntry::compute_fingerprint(material);
assert_eq!(fp.len(), 64); }
#[test]
fn test_key_entry_verify_fingerprint() {
let material = b"test-key-material";
let entry = KeyEntry {
metadata: KeyMetadata::new(
"id1".to_string(),
"test".to_string(),
KeyAlgorithm::Aes256Gcm,
KeyPurpose::Encryption,
1,
),
material: material.to_vec(),
fingerprint: KeyEntry::compute_fingerprint(material),
};
assert!(entry.verify_fingerprint());
}
#[test]
fn test_key_entry_verify_fingerprint_mismatch() {
let entry = KeyEntry {
metadata: KeyMetadata::new(
"id1".to_string(),
"test".to_string(),
KeyAlgorithm::Aes256Gcm,
KeyPurpose::Encryption,
1,
),
material: b"actual-material".to_vec(),
fingerprint: "0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
};
assert!(!entry.verify_fingerprint());
}
#[test]
fn test_key_generator_generate_material_length() {
let gen = KeyGenerator::new();
let material = gen.generate_material(KeyAlgorithm::Aes256Gcm);
assert_eq!(material.len(), 32);
}
#[test]
fn test_key_generator_generate_material_random() {
let gen = KeyGenerator::new();
let a = gen.generate_material(KeyAlgorithm::Aes256Gcm);
let b = gen.generate_material(KeyAlgorithm::Aes256Gcm);
assert_ne!(a, b, "随机生成的密钥应不同");
}
#[test]
fn test_key_generator_generate_key_id() {
let id1 = KeyGenerator::generate_key_id();
let id2 = KeyGenerator::generate_key_id();
assert_eq!(id1.len(), 32); assert_ne!(id1, id2);
}
#[test]
fn test_key_generator_generate_full_entry() {
let gen = KeyGenerator::new();
let entry = gen.generate("test-key", KeyAlgorithm::HmacSha256, KeyPurpose::Signing, 1);
assert_eq!(entry.metadata.name, "test-key");
assert_eq!(entry.metadata.algorithm, KeyAlgorithm::HmacSha256);
assert_eq!(entry.metadata.version, 1);
assert_eq!(entry.material.len(), 32);
assert!(entry.verify_fingerprint());
}
#[test]
fn test_rotation_policy_time_interval() {
let policy = RotationPolicy::TimeInterval(Duration::from_secs(100));
assert!(!policy.needs_rotation(Duration::from_secs(50), 0));
assert!(policy.needs_rotation(Duration::from_secs(100), 0));
assert!(policy.needs_rotation(Duration::from_secs(150), 0));
}
#[test]
fn test_rotation_policy_usage_count() {
let policy = RotationPolicy::UsageCount(1000);
assert!(!policy.needs_rotation(Duration::ZERO, 500));
assert!(policy.needs_rotation(Duration::ZERO, 1000));
assert!(policy.needs_rotation(Duration::ZERO, 1500));
}
#[test]
fn test_rotation_policy_time_or_usage() {
let policy = RotationPolicy::TimeIntervalOrUsage(Duration::from_secs(100), 1000);
assert!(!policy.needs_rotation(Duration::from_secs(50), 500));
assert!(policy.needs_rotation(Duration::from_secs(100), 500));
assert!(policy.needs_rotation(Duration::from_secs(50), 1000));
}
#[test]
fn test_rotation_policy_never() {
let policy = RotationPolicy::Never;
assert!(!policy.needs_rotation(Duration::from_secs(999999), 999999));
}
#[test]
fn test_key_store_put_and_get() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
let entry = gen.generate("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 1);
let key_id = store.put(entry);
assert!(store.get(&key_id).is_some());
assert_eq!(store.count(), 1);
}
#[test]
fn test_key_store_get_latest_by_name() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
let e1 = gen.generate("key", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 1);
let e2 = gen.generate("key", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 2);
store.put(e1);
store.put(e2);
let latest = store.get_latest_by_name("key").unwrap();
assert_eq!(latest.metadata.version, 2);
}
#[test]
fn test_key_store_get_by_name_and_version() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
let e1 = gen.generate("key", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 1);
let e2 = gen.generate("key", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 2);
store.put(e1);
store.put(e2);
assert!(store.get_by_name_and_version("key", 1).is_some());
assert!(store.get_by_name_and_version("key", 2).is_some());
assert!(store.get_by_name_and_version("key", 3).is_none());
}
#[test]
fn test_key_store_get_all_versions() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
for v in 1..=3 {
let entry = gen.generate("key", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, v);
store.put(entry);
}
let versions = store.get_all_versions("key");
assert_eq!(versions.len(), 3);
}
#[test]
fn test_key_store_remove() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
let entry = gen.generate("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 1);
let key_id = store.put(entry);
assert_eq!(store.count(), 1);
let removed = store.remove(&key_id);
assert!(removed.is_some());
assert_eq!(store.count(), 0);
}
#[test]
fn test_key_store_set_status() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
let entry = gen.generate("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 1);
let key_id = store.put(entry);
assert!(store.set_status(&key_id, KeyStatus::Revoked));
assert_eq!(
store.get(&key_id).unwrap().metadata.status,
KeyStatus::Revoked
);
}
#[test]
fn test_key_store_get_by_tag() {
let mut store = KeyStore::new();
let gen = KeyGenerator::new();
let mut entry = gen.generate("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption, 1);
entry.metadata.tags = vec!["production".to_string()];
store.put(entry);
let results = store.get_by_tag("production");
assert_eq!(results.len(), 1);
assert_eq!(store.get_by_tag("staging").len(), 0);
}
#[test]
fn test_key_audit_log_record_and_query() {
let mut log = KeyAuditLog::new();
log.record(
KeyEvent::Created,
"id1".to_string(),
"key1".to_string(),
"created",
);
log.record(
KeyEvent::Rotated,
"id2".to_string(),
"key1".to_string(),
"rotated",
);
assert_eq!(log.count(), 2);
assert_eq!(log.by_event(KeyEvent::Created).len(), 1);
assert_eq!(log.by_key_name("key1").len(), 2);
}
#[test]
fn test_key_vault_generate_key() {
let vault = KeyVault::new(RotationPolicy::Never);
let key_id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
assert!(vault.get_key(&key_id).is_some());
assert_eq!(vault.key_count(), 1);
assert_eq!(vault.version_count("test"), 1);
}
#[test]
fn test_key_vault_rotate_key() {
let vault = KeyVault::new(RotationPolicy::Never);
let old_id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
let new_id = vault.rotate_key("test").unwrap();
assert_ne!(old_id, new_id);
assert_eq!(vault.version_count("test"), 2);
let old = vault.get_key(&old_id).unwrap();
assert_eq!(old.metadata.status, KeyStatus::Deprecated);
let new_key = vault.get_key(&new_id).unwrap();
assert_eq!(new_key.metadata.status, KeyStatus::Active);
}
#[test]
fn test_key_vault_rotate_nonexistent_key() {
let vault = KeyVault::new(RotationPolicy::Never);
let result = vault.rotate_key("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_key_vault_get_latest_key() {
let vault = KeyVault::new(RotationPolicy::Never);
vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
vault.rotate_key("test").unwrap();
let latest = vault.get_latest_key("test").unwrap();
assert_eq!(latest.metadata.version, 2);
}
#[test]
fn test_key_vault_revoke_key() {
let vault = KeyVault::new(RotationPolicy::Never);
let key_id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
vault.revoke_key(&key_id).unwrap();
let entry = vault.get_key(&key_id).unwrap();
assert_eq!(entry.metadata.status, KeyStatus::Revoked);
}
#[test]
fn test_key_vault_deprecate_key() {
let vault = KeyVault::new(RotationPolicy::Never);
let key_id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
vault.deprecate_key(&key_id).unwrap();
let entry = vault.get_key(&key_id).unwrap();
assert_eq!(entry.metadata.status, KeyStatus::Deprecated);
}
#[test]
fn test_key_vault_audit_log() {
let vault = KeyVault::new(RotationPolicy::Never);
vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
let log = vault.audit_log();
assert!(log.count() >= 1);
assert_eq!(log.by_event(KeyEvent::Created).len(), 1);
}
#[test]
fn test_key_vault_usage_count() {
let vault = KeyVault::new(RotationPolicy::Never);
let key_id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
assert_eq!(vault.usage_count(&key_id), 0);
vault.get_key(&key_id);
vault.get_key(&key_id);
assert_eq!(vault.usage_count(&key_id), 2);
}
#[test]
fn test_key_vault_needs_rotation_time() {
let vault = KeyVault::new(RotationPolicy::TimeInterval(Duration::from_millis(0)));
vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
std::thread::sleep(Duration::from_millis(1));
assert!(vault.needs_rotation("test"));
}
#[test]
fn test_key_vault_needs_rotation_never() {
let vault = KeyVault::new(RotationPolicy::Never);
vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
assert!(!vault.needs_rotation("test"));
}
#[test]
fn test_key_vault_auto_rotate() {
let vault = KeyVault::new(RotationPolicy::TimeInterval(Duration::from_millis(0)));
vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
std::thread::sleep(Duration::from_millis(1));
let new_id = vault.auto_rotate("test");
assert!(new_id.is_some());
assert_eq!(vault.version_count("test"), 2);
}
#[test]
fn test_key_vault_auto_rotate_no_rotation_needed() {
let vault = KeyVault::new(RotationPolicy::Never);
vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
let result = vault.auto_rotate("test");
assert!(result.is_none());
}
#[test]
fn test_key_vault_delete_key() {
let vault = KeyVault::new(RotationPolicy::Never);
let key_id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
assert_eq!(vault.key_count(), 1);
vault.delete_key(&key_id).unwrap();
assert_eq!(vault.key_count(), 0);
}
#[test]
fn test_key_vault_get_by_status() {
let vault = KeyVault::new(RotationPolicy::Never);
let id1 = vault
.generate_key("k1", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
vault
.generate_key("k2", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
vault.revoke_key(&id1).unwrap();
let active = vault.get_by_status(KeyStatus::Active);
let revoked = vault.get_by_status(KeyStatus::Revoked);
assert_eq!(active.len(), 1);
assert_eq!(revoked.len(), 1);
}
#[test]
fn test_key_vault_concurrent_access() {
use std::sync::Arc;
use std::thread;
let vault = Arc::new(KeyVault::new(RotationPolicy::Never));
let id = vault
.generate_key("test", KeyAlgorithm::Aes256Gcm, KeyPurpose::Encryption)
.unwrap();
let mut handles = vec![];
for _ in 0..4 {
let v = vault.clone();
let kid = id.clone();
handles.push(thread::spawn(move || {
v.get_key(&kid);
}));
}
for h in handles {
h.join().expect("thread panicked");
}
assert_eq!(vault.usage_count(&id), 4);
}
#[test]
fn test_key_error_display() {
let err = KeyError::KeyNotFound {
name: "test".to_string(),
};
assert!(err.to_string().contains("test"));
let err2 = KeyError::KeyRevoked {
key_id: "id1".to_string(),
};
assert!(err2.to_string().contains("id1"));
}
#[test]
fn test_kdf_default() {
let cfg = KeyDerivationConfig::default();
assert_eq!(cfg.algorithm, "pbkdf2");
assert_eq!(cfg.key_len, 32);
}
#[test]
fn test_kdf_pbkdf2() {
let cfg = KeyDerivationConfig::pbkdf2(100_000);
assert!(cfg.is_pbkdf2());
assert!(!cfg.is_argon2());
assert_eq!(cfg.iterations, 100_000);
}
#[test]
fn test_kdf_argon2id() {
let cfg = KeyDerivationConfig::argon2id(3, 65536, 4);
assert!(cfg.is_argon2());
assert!(!cfg.is_pbkdf2());
assert_eq!(cfg.memory_kb, Some(65536));
assert_eq!(cfg.parallelism, Some(4));
}
#[test]
fn test_kdf_scrypt() {
let cfg = KeyDerivationConfig::scrypt(1024);
assert_eq!(cfg.algorithm, "scrypt");
assert_eq!(cfg.iterations, 1024);
}
#[test]
fn test_kdf_generate_salt() {
let cfg = KeyDerivationConfig::default();
let salt = cfg.generate_salt();
assert_eq!(salt.len(), cfg.salt_len);
}
#[test]
fn test_nonce_generator_gcm() {
let gen = NonceGenerator::for_gcm();
let nonce = gen.generate();
assert_eq!(nonce.len(), 12);
}
#[test]
fn test_nonce_generator_chacha20() {
let gen = NonceGenerator::for_chacha20();
let nonce = gen.generate();
assert_eq!(nonce.len(), 16);
}
#[test]
fn test_nonce_generator_custom() {
let gen = NonceGenerator::new(32);
assert_eq!(gen.len(), 32);
let nonce = gen.generate();
assert_eq!(nonce.len(), 32);
}
#[test]
fn test_nonce_generator_unique() {
let gen = NonceGenerator::for_gcm();
let n1 = gen.generate();
let n2 = gen.generate();
assert_ne!(n1, n2);
}
#[test]
fn test_encryption_context_default() {
let ctx = EncryptionContext::default();
assert!(!ctx.has_aad());
assert!(!ctx.has_tenant());
}
#[test]
fn test_encryption_context_builder() {
let ctx = EncryptionContext::new()
.with_aad(b"associated".to_vec())
.with_label("db")
.with_tenant("tenant1");
assert!(ctx.has_aad());
assert!(ctx.has_tenant());
assert_eq!(ctx.label, "db");
assert_eq!(ctx.tenant_id, Some("tenant1".to_string()));
}
#[test]
fn test_key_fingerprint() {
let key = b"my-secret-key-1234567890123456";
let fp = KeyFingerprint::from_key_material(key, KeyAlgorithm::Aes256Gcm);
assert_eq!(fp.sha256_hex.len(), 64);
assert_eq!(fp.short().len(), 8);
}
#[test]
fn test_key_fingerprint_matches() {
let key = b"my-secret-key-1234567890123456";
let fp1 = KeyFingerprint::from_key_material(key, KeyAlgorithm::Aes256Gcm);
let fp2 = KeyFingerprint::from_key_material(key, KeyAlgorithm::Aes256Gcm);
assert!(fp1.matches(&fp2));
}
#[test]
fn test_key_fingerprint_not_matches() {
let fp1 = KeyFingerprint::from_key_material(b"key1", KeyAlgorithm::Aes256Gcm);
let fp2 = KeyFingerprint::from_key_material(b"key2", KeyAlgorithm::Aes256Gcm);
assert!(!fp1.matches(&fp2));
}
#[test]
fn test_key_fingerprint_display() {
let fp = KeyFingerprint::from_key_material(b"key", KeyAlgorithm::Aes256Gcm);
let s = format!("{}", fp);
assert!(s.contains("aes-256-gcm"));
}
#[test]
fn test_security_policy_default() {
let policy = SecurityPolicy::default();
assert!(policy.require_encryption_at_rest);
assert!(policy.require_key_rotation);
assert!(policy.is_algorithm_allowed(KeyAlgorithm::Aes256Gcm));
}
#[test]
fn test_security_policy_strict() {
let policy = SecurityPolicy::strict();
assert!(policy.is_algorithm_allowed(KeyAlgorithm::Aes256Gcm));
assert!(!policy.is_algorithm_allowed(KeyAlgorithm::HmacSha256));
}
#[test]
fn test_security_policy_key_length() {
let policy = SecurityPolicy::default();
assert!(policy.is_key_length_ok(32));
assert!(!policy.is_key_length_ok(16));
}
}