use crate::client::key_locker::key_locker::{KeyLocker, KeyLockerManager, GUEST_CLIENT_ID};
use crate::tools::client_id::ClientId;
use crate::tools::keys::Keys;
use crate::tools::signing;
use crate::tools::types::Signature;
use crate::tools::tools::TempDirHandle;
use anyhow::anyhow;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use parking_lot::RwLock;
pub struct DiskKeyLocker {
keys: Keys,
client_id: ClientId,
}
#[async_trait::async_trait]
impl KeyLocker for DiskKeyLocker {
fn client_id(&self) -> &ClientId {
&self.client_id
}
async fn sign(&self, data: &[u8]) -> anyhow::Result<Signature> {
Ok(signing::sign(&self.keys.signature_key, data))
}
}
pub struct DiskKeyLockerManager {
key_locker_dir: PathBuf,
passphrase: String,
loaded_keys: Arc<RwLock<HashMap<String, Arc<DiskKeyLocker>>>>,
_temp_dir_handle: Option<TempDirHandle>,
}
impl DiskKeyLockerManager {
pub fn with_data_dir(data_dir: PathBuf, passphrase: String) -> anyhow::Result<Arc<Self>> {
let key_locker_dir = data_dir.join("key_locker");
std::fs::create_dir_all(&key_locker_dir)?;
let manager = Arc::new(Self {
key_locker_dir,
passphrase,
loaded_keys: Arc::new(RwLock::new(HashMap::new())),
_temp_dir_handle: None,
});
Ok(manager)
}
fn key_file_path(&self, key_public_hex: &str) -> PathBuf {
self.key_locker_dir.join(format!("{}.key", key_public_hex))
}
}
impl KeyLockerManager<DiskKeyLocker> for DiskKeyLockerManager {
async fn new() -> anyhow::Result<Arc<Self>> {
let (temp_dir_handle, temp_dir_path) = crate::tools::tools::get_temp_dir()?;
let key_locker_dir = PathBuf::from(&temp_dir_path).join("key_locker");
std::fs::create_dir_all(&key_locker_dir)?;
Ok(Arc::new(Self {
key_locker_dir,
passphrase: String::new(),
loaded_keys: Arc::new(RwLock::new(HashMap::new())),
_temp_dir_handle: Some(temp_dir_handle),
}))
}
async fn list(&self) -> anyhow::Result<Vec<String>> {
let mut key_ids = Vec::new();
for entry in std::fs::read_dir(&self.key_locker_dir)? {
let entry = entry?;
let file_name = entry.file_name().to_string_lossy().to_string();
if let Some(key_public_hex) = file_name.strip_suffix(".key") {
if key_public_hex != GUEST_CLIENT_ID {
key_ids.push(key_public_hex.to_string());
}
}
}
Ok(key_ids)
}
async fn create(&self, key_phrase: String) -> anyhow::Result<Arc<DiskKeyLocker>> {
let keys = Keys::from_phrase(&key_phrase)?;
let client_id = ClientId::new(keys.verification_key_bytes, keys.pq_commitment_bytes)?;
let client_id_hex = client_id.id_hex();
let persistence_data = keys.to_persistence(&self.passphrase)?;
let key_file_path = self.key_file_path(&client_id_hex);
std::fs::write(&key_file_path, persistence_data)?;
let native_key_locker = Arc::new(DiskKeyLocker { keys, client_id });
let mut loaded_keys = self.loaded_keys.write();
loaded_keys.insert(client_id_hex, native_key_locker.clone());
Ok(native_key_locker)
}
async fn switch(&self, client_id_hex: String) -> anyhow::Result<Arc<DiskKeyLocker>> {
{
let loaded_keys = self.loaded_keys.read();
if let Some(native_key_locker) = loaded_keys.get(&client_id_hex) {
return Ok(native_key_locker.clone());
}
}
let key_file_path = self.key_file_path(&client_id_hex);
let persistence_data = std::fs::read_to_string(&key_file_path)
.map_err(|_| anyhow!("Key file not found for {}", client_id_hex))?;
let keys = Keys::from_persistence(&self.passphrase, &persistence_data)?;
let client_id = ClientId::new(keys.verification_key_bytes, keys.pq_commitment_bytes)?;
let native_key_locker = Arc::new(DiskKeyLocker { keys, client_id });
let mut loaded_keys = self.loaded_keys.write();
loaded_keys.insert(client_id_hex, native_key_locker.clone());
Ok(native_key_locker)
}
async fn delete(&self, client_id_hex: String) -> anyhow::Result<()> {
{
let mut loaded_keys = self.loaded_keys.write();
loaded_keys.remove(&client_id_hex);
}
let key_file_path = self.key_file_path(&client_id_hex);
if key_file_path.exists() {
std::fs::remove_file(&key_file_path)?;
}
Ok(())
}
async fn reset(&self) -> anyhow::Result<()> {
{
let mut loaded_keys = self.loaded_keys.write();
loaded_keys.clear();
}
for entry in std::fs::read_dir(&self.key_locker_dir)? {
let entry = entry?;
let file_name = entry.file_name().to_string_lossy().to_string();
if file_name.ends_with(".key") {
std::fs::remove_file(entry.path())?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::client::key_locker::disk_key_locker::{DiskKeyLocker, DiskKeyLockerManager};
use crate::tools::tools::get_temp_dir;
use std::sync::Arc;
use crate::client::key_locker::key_locker;
#[tokio::test]
async fn add_test() {
key_locker::tests::add_test::<DiskKeyLocker, DiskKeyLockerManager>().await;
}
#[tokio::test]
async fn sign_test() {
key_locker::tests::sign_test::<DiskKeyLocker, DiskKeyLockerManager>().await;
}
#[tokio::test]
async fn guest_client_id_excluded_from_list_test() {
key_locker::tests::guest_client_id_excluded_from_list_test::<DiskKeyLocker, DiskKeyLockerManager>().await;
}
#[tokio::test]
async fn persistence_roundtrip_test() {
use crate::client::key_locker::key_locker::KeyLockerManager;
let (_temp_dir, temp_dir_path) = get_temp_dir().unwrap();
let data_dir = std::path::PathBuf::from(temp_dir_path);
let manager_1 = DiskKeyLockerManager::with_data_dir(data_dir.clone(), "test_passphrase".to_string()).unwrap();
manager_1.reset().await.unwrap();
let key_locker: Arc<DiskKeyLocker> = manager_1.create("my_key_phrase".to_string()).await.unwrap();
use crate::client::key_locker::key_locker::KeyLocker;
let original_client_id = key_locker.client_id().clone();
let key_public_hex = original_client_id.id.to_hex_str();
let manager_2 = DiskKeyLockerManager::with_data_dir(data_dir, "test_passphrase".to_string()).unwrap();
let restored_key_locker = manager_2.switch(key_public_hex).await.unwrap();
assert_eq!(&original_client_id, restored_key_locker.client_id());
}
}