// #[cfg(not(target_arch = "wasm32"))] // mod fallback; // #[cfg(target_arch = "wasm32")] // mod wasm; use std::collections::HashMap; #[cfg(not(target_arch = "wasm32"))] use std::path::Path; use crate::{ error::Error, key::{Key, symmetric::SymmetricKey}, }; use kv::KVStore; /// Configuration to use for bincode en/decoding. const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); // #[cfg(not(target_arch = "wasm32"))] // use fallback::KeySpace as Ks; // #[cfg(target_arch = "wasm32")] // use wasm::KeySpace as Ks; #[cfg(not(target_arch = "wasm32"))] use kv::native::NativeStore; #[cfg(target_arch = "wasm32")] use kv::wasm::WasmStore; const KEYSPACE_NAME: &str = "vault_keyspace"; /// A keyspace represents a group of stored cryptographic keys. The storage is encrypted, a /// password must be provided when opening the KeySpace to decrypt the keys. pub struct KeySpace { // store: Ks, #[cfg(not(target_arch = "wasm32"))] store: NativeStore, #[cfg(target_arch = "wasm32")] store: WasmStore, /// A collection of all keys stored in the KeySpace, in decrypted form. keys: HashMap, /// The encryption key used to encrypt/decrypt this keyspace. encryption_key: SymmetricKey, } /// Wasm32 constructor #[cfg(target_arch = "wasm32")] impl KeySpace {} /// Non-wasm constructor #[cfg(not(target_arch = "wasm32"))] impl KeySpace { /// Open the keyspace at the provided path using the given key for encryption. pub async fn open(path: &Path, encryption_key: SymmetricKey) -> Result { let store = NativeStore::open(&path.display().to_string())?; let mut ks = Self { store, keys: HashMap::new(), encryption_key, }; ks.load_keyspace().await?; Ok(ks) } } #[cfg(target_arch = "wasm32")] impl KeySpace { pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result { let store = WasmStore::open(name).await?; let mut ks = Self { store, keys: HashMap::new(), encryption_key, }; ks.load_keyspace().await?; Ok(ks) } } /// Exposed methods, platform independant impl KeySpace { /// Get a [`Key`] previously stored under the provided name. pub async fn get(&self, key: &str) -> Result, Error> { Ok(self.keys.get(key).cloned()) } /// Store a [`Key`] under the provided name. /// /// This overwrites the existing key if one is already stored with the same name. pub async fn set(&mut self, key: String, value: Key) -> Result<(), Error> { self.keys.insert(key, value); self.save_keyspace().await } /// Delete the [`Key`] stored under the provided name. pub async fn delete(&mut self, key: &str) -> Result<(), Error> { self.keys.remove(key); self.save_keyspace().await } /// Iterate over all stored [`keys`](Key) in the KeySpace pub async fn iter(&self) -> Result, Error> { Ok(self.keys.iter()) } /// Encrypt all keys and save them to the underlying store async fn save_keyspace(&self) -> Result<(), Error> { let encoded_keys = bincode::serde::encode_to_vec(&self.keys, BINCODE_CONFIG)?; let value = self.encryption_key.encrypt(&encoded_keys)?; // Put in store Ok(self.store.set(KEYSPACE_NAME, &value).await?) } /// Loads the encrypted keyspace from the underlying storage async fn load_keyspace(&mut self) -> Result<(), Error> { let Some(ks) = self.store.get(KEYSPACE_NAME).await? else { // Keyspace doesn't exist yet, nothing to do here return Ok(()); }; let raw = self.encryption_key.decrypt(&ks)?; let (decoded_keys, _): (HashMap, _) = bincode::serde::decode_from_slice(&raw, BINCODE_CONFIG)?; self.keys = decoded_keys; Ok(()) } }