use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use sha2::{Sha256, Digest};
use zeroize::{Zeroize, Zeroizing};
use std::collections::HashMap;
use std::path::PathBuf;
use super::encryption::AesGcmCipher;
pub struct KeyStore {
keys: HashMap<String, EncryptedKey>,
master_key: Option<Zeroizing<[u8; 32]>>,
path: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedKey {
pub id: String,
pub encrypted_data: Vec<u8>,
pub salt: [u8; 32],
pub created_at: chrono::DateTime<chrono::Utc>,
pub key_type: KeyType,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum KeyType {
DataEncryption,
NetworkEncryption,
SigningKey,
APIKey,
SessionKey,
}
impl KeyStore {
pub fn new() -> Result<Self> {
Ok(Self {
keys: HashMap::new(),
master_key: None,
path: None,
})
}
pub fn init_with_password(&mut self, password: &str) -> Result<()> {
let salt = super::secure_random_bytes(32);
let key = derive_key(password, &salt, 100_000)?;
self.master_key = Some(key);
Ok(())
}
pub fn unlock(&mut self, password: &str, salt: &[u8; 32]) -> Result<()> {
let key = derive_key(password, salt, 100_000)?;
self.master_key = Some(key);
Ok(())
}
pub fn lock(&mut self) {
self.master_key = None;
}
pub fn is_unlocked(&self) -> bool {
self.master_key.is_some()
}
pub fn store_key(&mut self, id: &str, key: &[u8], key_type: KeyType) -> Result<()> {
let master = self.master_key.as_ref()
.ok_or_else(|| anyhow!("KeyStore is locked"))?;
let cipher = AesGcmCipher::with_key(**master);
let encrypted_data = cipher.encrypt(key)?;
let salt = {
let mut s = [0u8; 32];
let random = super::secure_random_bytes(32);
s.copy_from_slice(&random);
s
};
let encrypted_key = EncryptedKey {
id: id.to_string(),
encrypted_data,
salt,
created_at: chrono::Utc::now(),
key_type,
};
self.keys.insert(id.to_string(), encrypted_key);
Ok(())
}
pub fn get_key(&self, id: &str) -> Result<Zeroizing<Vec<u8>>> {
let master = self.master_key.as_ref()
.ok_or_else(|| anyhow!("KeyStore is locked"))?;
let encrypted = self.keys.get(id)
.ok_or_else(|| anyhow!("Key not found: {}", id))?;
let cipher = AesGcmCipher::with_key(**master);
let decrypted = cipher.decrypt(&encrypted.encrypted_data)?;
Ok(Zeroizing::new(decrypted))
}
pub fn delete_key(&mut self, id: &str) -> Result<()> {
self.keys.remove(id)
.ok_or_else(|| anyhow!("Key not found: {}", id))?;
Ok(())
}
pub fn list_keys(&self) -> Vec<&str> {
self.keys.keys().map(|s| s.as_str()).collect()
}
pub fn generate_key(&mut self, id: &str, key_type: KeyType, size: usize) -> Result<()> {
let key = super::secure_random_bytes(size);
self.store_key(id, &key, key_type)?;
Ok(())
}
pub fn save(&self, path: &std::path::Path) -> Result<()> {
let data = serde_json::to_vec_pretty(&self.keys)?;
std::fs::write(path, data)?;
Ok(())
}
pub fn load(&mut self, path: &std::path::Path) -> Result<()> {
let data = std::fs::read(path)?;
self.keys = serde_json::from_slice(&data)?;
self.path = Some(path.to_owned());
Ok(())
}
}
pub fn derive_key(password: &str, salt: &[u8], iterations: u32) -> Result<Zeroizing<[u8; 32]>> {
use argon2::{
Argon2,
password_hash::{PasswordHasher, SaltString},
Params,
};
let params = Params::new(
65536, iterations.min(10), 4, Some(32), ).map_err(|e| anyhow!("Argon2 params error: {}", e))?;
let argon2 = Argon2::new(
argon2::Algorithm::Argon2id,
argon2::Version::V0x13,
params,
);
let salt_b64 = base64::Engine::encode(
&base64::engine::general_purpose::STANDARD_NO_PAD,
&salt[..22] );
let salt_string = SaltString::from_b64(&salt_b64)
.map_err(|e| anyhow!("Salt error: {}", e))?;
let hash = argon2.hash_password(password.as_bytes(), &salt_string)
.map_err(|e| anyhow!("Password hashing failed: {}", e))?;
let hash_output = hash.hash
.ok_or_else(|| anyhow!("No hash output"))?;
let bytes = hash_output.as_bytes();
let mut key = Zeroizing::new([0u8; 32]);
key.copy_from_slice(&bytes[..32]);
Ok(key)
}
pub fn derive_key_pbkdf2(password: &str, salt: &[u8], iterations: u32) -> Result<Zeroizing<[u8; 32]>> {
use ring::pbkdf2;
let mut key = Zeroizing::new([0u8; 32]);
pbkdf2::derive(
pbkdf2::PBKDF2_HMAC_SHA256,
std::num::NonZeroU32::new(iterations).unwrap(),
salt,
password.as_bytes(),
&mut *key,
);
Ok(key)
}