use aes_gcm::{
Nonce,
aead::{Aead, consts::U12},
};
use anyhow::Result;
use rand::Rng;
use sha2::{Digest, Sha256};
use zeroize::Zeroize;
use super::entry::KeyEntry;
use super::store::{KeyRecord, KeyStore};
use ed25519_dalek::SigningKey;
fn archived_key_name(key_id: &str) -> String {
format!("{key_id}#archived-{}", uuid::Uuid::now_v7())
}
impl KeyStore {
pub fn archive_key(&self, key_id: &str) -> Result<()> {
if *self.needs_migration.read().expect("lock") {
anyhow::bail!(
"key store requires KDF migration before writes are allowed — \
call migrate_if_needed() first"
);
}
let mut map = self.records.write().expect("key store write lock poisoned");
if let Some(record) = map.get(key_id) {
let archived = record.clone();
let archive_key = archived_key_name(key_id);
map.insert(archive_key, archived);
self.persist_envelope(&map)?;
}
Ok(())
}
pub fn rotate_key(&self, key_id: &str) -> Result<KeyEntry> {
self.rotate_inner(key_id, false)
}
pub fn revoke_and_rotate(&self, key_id: &str) -> Result<KeyEntry> {
self.rotate_inner(key_id, true)
}
fn rotate_inner(&self, key_id: &str, revoke_old: bool) -> Result<KeyEntry> {
if *self.needs_migration.read().expect("lock") {
anyhow::bail!(
"key store requires KDF migration before writes are allowed — \
call migrate_if_needed() first"
);
}
let signing_key = SigningKey::generate(&mut crate::os_rng());
let verifying_key = signing_key.verifying_key();
let fingerprint = hex::encode(Sha256::digest(verifying_key.as_bytes()));
let verifying_key_hex = hex::encode(verifying_key.as_bytes());
let mut nonce_bytes = [0u8; 12];
crate::os_rng().fill_bytes(&mut nonce_bytes);
let nonce = <&Nonce<U12>>::from(&nonce_bytes);
let mut raw = signing_key.to_bytes();
let encrypted = self
.cipher
.encrypt(nonce, raw.as_ref())
.map_err(|_| anyhow::anyhow!("AES-GCM encrypt failed"))?;
raw.zeroize();
let new_record = KeyRecord::new(
encrypted,
nonce_bytes.to_vec(),
fingerprint.clone(),
verifying_key_hex,
);
{
let mut map = self.records.write().expect("key store write lock poisoned");
if let Some(record) = map.get(key_id) {
let archive_name = archived_key_name(key_id);
let archived = KeyRecord {
revoked: revoke_old,
..record.clone()
};
map.insert(archive_name, archived);
}
map.insert(key_id.to_owned(), new_record);
self.persist_envelope(&map)?;
}
Ok(KeyEntry {
signing_key,
verifying_key,
fingerprint,
revoked: false,
algorithm: super::store::default_algorithm(),
})
}
}