hashiverse_lib/client/key_locker/
disk_key_locker.rs1use crate::client::key_locker::key_locker::{KeyLocker, KeyLockerManager, GUEST_CLIENT_ID};
13use crate::tools::client_id::ClientId;
14use crate::tools::keys::Keys;
15use crate::tools::signing;
16use crate::tools::types::Signature;
17use crate::tools::tools::TempDirHandle;
18use anyhow::anyhow;
19use std::collections::HashMap;
20use std::path::PathBuf;
21use std::sync::Arc;
22use parking_lot::RwLock;
23
24pub struct DiskKeyLocker {
26 keys: Keys,
27 client_id: ClientId,
28}
29
30#[async_trait::async_trait]
31impl KeyLocker for DiskKeyLocker {
32 fn client_id(&self) -> &ClientId {
33 &self.client_id
34 }
35
36 async fn sign(&self, data: &[u8]) -> anyhow::Result<Signature> {
37 Ok(signing::sign(&self.keys.signature_key, data))
38 }
39}
40
41pub struct DiskKeyLockerManager {
47 key_locker_dir: PathBuf,
48 passphrase: String,
49 loaded_keys: Arc<RwLock<HashMap<String, Arc<DiskKeyLocker>>>>,
50 _temp_dir_handle: Option<TempDirHandle>,
52}
53
54impl DiskKeyLockerManager {
55 pub fn with_data_dir(data_dir: PathBuf, passphrase: String) -> anyhow::Result<Arc<Self>> {
56 let key_locker_dir = data_dir.join("key_locker");
57 std::fs::create_dir_all(&key_locker_dir)?;
58 let manager = Arc::new(Self {
59 key_locker_dir,
60 passphrase,
61 loaded_keys: Arc::new(RwLock::new(HashMap::new())),
62 _temp_dir_handle: None,
63 });
64 Ok(manager)
65 }
66
67 fn key_file_path(&self, key_public_hex: &str) -> PathBuf {
68 self.key_locker_dir.join(format!("{}.key", key_public_hex))
69 }
70}
71
72impl KeyLockerManager<DiskKeyLocker> for DiskKeyLockerManager {
73 async fn new() -> anyhow::Result<Arc<Self>> {
76 let (temp_dir_handle, temp_dir_path) = crate::tools::tools::get_temp_dir()?;
77 let key_locker_dir = PathBuf::from(&temp_dir_path).join("key_locker");
78 std::fs::create_dir_all(&key_locker_dir)?;
79 Ok(Arc::new(Self {
80 key_locker_dir,
81 passphrase: String::new(),
82 loaded_keys: Arc::new(RwLock::new(HashMap::new())),
83 _temp_dir_handle: Some(temp_dir_handle),
84 }))
85 }
86
87 async fn list(&self) -> anyhow::Result<Vec<String>> {
88 let mut key_ids = Vec::new();
89 for entry in std::fs::read_dir(&self.key_locker_dir)? {
90 let entry = entry?;
91 let file_name = entry.file_name().to_string_lossy().to_string();
92 if let Some(key_public_hex) = file_name.strip_suffix(".key") {
93 if key_public_hex != GUEST_CLIENT_ID {
94 key_ids.push(key_public_hex.to_string());
95 }
96 }
97 }
98 Ok(key_ids)
99 }
100
101 async fn create(&self, key_phrase: String) -> anyhow::Result<Arc<DiskKeyLocker>> {
102 let keys = Keys::from_phrase(&key_phrase)?;
103 let client_id = ClientId::new(keys.verification_key_bytes, keys.pq_commitment_bytes)?;
104 let key_public_hex = client_id.id_hex();
105
106 let persistence_data = keys.to_persistence(&self.passphrase)?;
108 let key_file_path = self.key_file_path(&key_public_hex);
109 std::fs::write(&key_file_path, persistence_data)?;
110
111 let native_key_locker = Arc::new(DiskKeyLocker { keys, client_id });
112
113 let mut loaded_keys = self.loaded_keys.write();
114 loaded_keys.insert(key_public_hex, native_key_locker.clone());
115
116 Ok(native_key_locker)
117 }
118
119 async fn switch(&self, key_public: String) -> anyhow::Result<Arc<DiskKeyLocker>> {
120 {
122 let loaded_keys = self.loaded_keys.read();
123 if let Some(native_key_locker) = loaded_keys.get(&key_public) {
124 return Ok(native_key_locker.clone());
125 }
126 }
127
128 let key_file_path = self.key_file_path(&key_public);
130 let persistence_data = std::fs::read_to_string(&key_file_path)
131 .map_err(|_| anyhow!("Key file not found for {}", key_public))?;
132 let keys = Keys::from_persistence(&self.passphrase, &persistence_data)?;
133 let client_id = ClientId::new(keys.verification_key_bytes, keys.pq_commitment_bytes)?;
134
135 let native_key_locker = Arc::new(DiskKeyLocker { keys, client_id });
136
137 let mut loaded_keys = self.loaded_keys.write();
138 loaded_keys.insert(key_public, native_key_locker.clone());
139
140 Ok(native_key_locker)
141 }
142
143 async fn delete(&self, key_public: String) -> anyhow::Result<()> {
144 {
146 let mut loaded_keys = self.loaded_keys.write();
147 loaded_keys.remove(&key_public);
148 }
149
150 let key_file_path = self.key_file_path(&key_public);
152 if key_file_path.exists() {
153 std::fs::remove_file(&key_file_path)?;
154 }
155
156 Ok(())
157 }
158
159 async fn reset(&self) -> anyhow::Result<()> {
160 {
162 let mut loaded_keys = self.loaded_keys.write();
163 loaded_keys.clear();
164 }
165
166 for entry in std::fs::read_dir(&self.key_locker_dir)? {
168 let entry = entry?;
169 let file_name = entry.file_name().to_string_lossy().to_string();
170 if file_name.ends_with(".key") {
171 std::fs::remove_file(entry.path())?;
172 }
173 }
174
175 Ok(())
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use crate::client::key_locker::disk_key_locker::{DiskKeyLocker, DiskKeyLockerManager};
182 use crate::tools::tools::get_temp_dir;
183 use std::sync::Arc;
184 use crate::client::key_locker::key_locker;
185
186 #[tokio::test]
187 async fn add_test() {
188 key_locker::tests::add_test::<DiskKeyLocker, DiskKeyLockerManager>().await;
189 }
190 #[tokio::test]
191 async fn sign_test() {
192 key_locker::tests::sign_test::<DiskKeyLocker, DiskKeyLockerManager>().await;
193 }
194 #[tokio::test]
195 async fn guest_client_id_excluded_from_list_test() {
196 key_locker::tests::guest_client_id_excluded_from_list_test::<DiskKeyLocker, DiskKeyLockerManager>().await;
197 }
198
199 #[tokio::test]
200 async fn persistence_roundtrip_test() {
201 use crate::client::key_locker::key_locker::KeyLockerManager;
202
203 let (_temp_dir, temp_dir_path) = get_temp_dir().unwrap();
204 let data_dir = std::path::PathBuf::from(temp_dir_path);
205
206 let manager_1 = DiskKeyLockerManager::with_data_dir(data_dir.clone(), "test_passphrase".to_string()).unwrap();
208 manager_1.reset().await.unwrap();
209 let key_locker: Arc<DiskKeyLocker> = manager_1.create("my_key_phrase".to_string()).await.unwrap();
210
211 use crate::client::key_locker::key_locker::KeyLocker;
212 let original_client_id = key_locker.client_id().clone();
213 let key_public_hex = original_client_id.id.to_hex_str();
214
215 let manager_2 = DiskKeyLockerManager::with_data_dir(data_dir, "test_passphrase".to_string()).unwrap();
217 let restored_key_locker = manager_2.switch(key_public_hex).await.unwrap();
218
219 assert_eq!(&original_client_id, restored_key_locker.client_id());
220 }
221}