use std::collections::HashMap;
use crate::error::DataError;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyVersionStatus {
Active,
DecryptOnly,
Deactivated,
}
#[derive(Debug, Clone)]
pub struct KeyVersionEntry {
pub version_id: String,
pub status: KeyVersionStatus,
}
#[derive(Debug, Default)]
struct KeyEntry {
versions: Vec<KeyVersionEntry>,
next_version_counter: u32,
}
impl KeyEntry {
fn active_version(&self) -> Option<&KeyVersionEntry> {
self.versions
.iter()
.find(|v| v.status == KeyVersionStatus::Active)
}
fn usable_count(&self) -> usize {
self.versions
.iter()
.filter(|v| {
v.status == KeyVersionStatus::Active || v.status == KeyVersionStatus::DecryptOnly
})
.count()
}
}
#[derive(Debug, Default)]
pub struct KeyRing {
entries: HashMap<String, KeyEntry>,
}
impl KeyRing {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_key(&mut self, alias: String, initial_version: String) {
let entry = self.entries.entry(alias).or_default();
entry.versions.push(KeyVersionEntry {
version_id: initial_version,
status: KeyVersionStatus::Active,
});
entry.next_version_counter += 1;
}
pub fn rotate(&mut self, alias: &str) -> Result<String, DataError> {
let entry = self
.entries
.get_mut(alias)
.ok_or_else(|| DataError::UnknownAlias {
alias: alias.to_string(),
})?;
for v in entry.versions.iter_mut() {
if v.status == KeyVersionStatus::Active {
v.status = KeyVersionStatus::DecryptOnly;
}
}
entry.next_version_counter += 1;
let new_version_id = format!("v{}", entry.next_version_counter);
entry.versions.push(KeyVersionEntry {
version_id: new_version_id.clone(),
status: KeyVersionStatus::Active,
});
Ok(new_version_id)
}
pub fn deactivate(&mut self, alias: &str, version_id: &str) -> Result<(), DataError> {
let entry = self
.entries
.get_mut(alias)
.ok_or_else(|| DataError::UnknownAlias {
alias: alias.to_string(),
})?;
if entry.usable_count() <= 1 {
return Err(DataError::CannotDeactivateLastVersion {
alias: alias.to_string(),
});
}
for v in entry.versions.iter_mut() {
if v.version_id == version_id {
v.status = KeyVersionStatus::Deactivated;
return Ok(());
}
}
Err(DataError::KeyVersionUnavailable {
alias: alias.to_string(),
version: version_id.to_string(),
})
}
pub fn version_status(&self, alias: &str, version_id: &str) -> Option<KeyVersionStatus> {
let entry = self.entries.get(alias)?;
entry
.versions
.iter()
.find(|v| v.version_id == version_id)
.map(|v| v.status.clone())
}
pub fn active_version(&self, alias: &str) -> Option<&str> {
self.entries
.get(alias)
.and_then(|e| e.active_version())
.map(|v| v.version_id.as_str())
}
}