Basic API
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
parent
7b1908b676
commit
2adda10664
@ -18,3 +18,5 @@ chacha20poly1305 = "0.10.1"
|
|||||||
k256 = { version = "0.13.4", features = ["ecdh"] }
|
k256 = { version = "0.13.4", features = ["ecdh"] }
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" }
|
kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" }
|
||||||
|
bincode = { version = "2.0.1", features = ["serde"] }
|
||||||
|
pbkdf2 = "0.12.2"
|
||||||
|
@ -9,6 +9,8 @@ pub enum Error {
|
|||||||
CorruptKeyspace,
|
CorruptKeyspace,
|
||||||
/// An error in the used key value store
|
/// An error in the used key value store
|
||||||
KV(kv::error::KVError),
|
KV(kv::error::KVError),
|
||||||
|
/// An error while encoding/decoding the keyspace.
|
||||||
|
Coding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for Error {
|
impl core::fmt::Display for Error {
|
||||||
@ -18,6 +20,7 @@ impl core::fmt::Display for Error {
|
|||||||
Error::IOError(e) => f.write_fmt(format_args!("io: {e}")),
|
Error::IOError(e) => f.write_fmt(format_args!("io: {e}")),
|
||||||
Error::CorruptKeyspace => f.write_str("corrupt keyspace"),
|
Error::CorruptKeyspace => f.write_str("corrupt keyspace"),
|
||||||
Error::KV(e) => f.write_fmt(format_args!("kv: {e}")),
|
Error::KV(e) => f.write_fmt(format_args!("kv: {e}")),
|
||||||
|
Error::Coding => f.write_str("keyspace coding failed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,3 +80,15 @@ impl From<kv::error::KVError> for Error {
|
|||||||
Self::KV(value)
|
Self::KV(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<bincode::error::DecodeError> for Error {
|
||||||
|
fn from(_: bincode::error::DecodeError) -> Self {
|
||||||
|
Self::Coding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bincode::error::EncodeError> for Error {
|
||||||
|
fn from(_: bincode::error::EncodeError) -> Self {
|
||||||
|
Self::Coding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -83,4 +83,22 @@ impl SymmetricKey {
|
|||||||
.decrypt(nonce, ciphertext)
|
.decrypt(nonce, ciphertext)
|
||||||
.map_err(|_| CryptoError::DecryptionFailed)
|
.map_err(|_| CryptoError::DecryptionFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives a new symmetric key from a password.
|
||||||
|
///
|
||||||
|
/// Derivation is done using pbkdf2 with Sha256 hashing.
|
||||||
|
pub fn derive_from_password(password: &str) -> Self {
|
||||||
|
/// Salt to use for PBKDF2. This needs to be consistent accross runs to generate the same
|
||||||
|
/// key. Additionally, it does not really matter what this is, as long as its unique.
|
||||||
|
const SALT: &[u8; 10] = b"vault_salt";
|
||||||
|
/// Amount of rounds to use for key generation. More rounds => more cpu time. Changing this
|
||||||
|
/// also chagnes the generated keys.
|
||||||
|
const ROUNDS: u32 = 100_000;
|
||||||
|
|
||||||
|
let mut key = [0; 32];
|
||||||
|
|
||||||
|
pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password.as_bytes(), SALT, ROUNDS, &mut key);
|
||||||
|
|
||||||
|
Self(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,21 @@
|
|||||||
// #[cfg(target_arch = "wasm32")]
|
// #[cfg(target_arch = "wasm32")]
|
||||||
// mod wasm;
|
// mod wasm;
|
||||||
|
|
||||||
use std::{collections::HashMap, path::Path};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
key::{Key, symmetric::SymmetricKey},
|
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"))]
|
// #[cfg(not(target_arch = "wasm32"))]
|
||||||
// use fallback::KeySpace as Ks;
|
// use fallback::KeySpace as Ks;
|
||||||
// #[cfg(target_arch = "wasm32")]
|
// #[cfg(target_arch = "wasm32")]
|
||||||
@ -44,14 +52,14 @@ impl KeySpace {}
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
impl KeySpace {
|
impl KeySpace {
|
||||||
/// Open the keyspace at the provided path using the given key for encryption.
|
/// Open the keyspace at the provided path using the given key for encryption.
|
||||||
pub fn open(path: &Path, encryption_key: SymmetricKey) -> Result<Self, Error> {
|
pub async fn open(path: &Path, encryption_key: SymmetricKey) -> Result<Self, Error> {
|
||||||
let store = NativeStore::open(&path.display().to_string())?;
|
let store = NativeStore::open(&path.display().to_string())?;
|
||||||
let mut ks = Self {
|
let mut ks = Self {
|
||||||
store,
|
store,
|
||||||
keys: HashMap::new(),
|
keys: HashMap::new(),
|
||||||
encryption_key,
|
encryption_key,
|
||||||
};
|
};
|
||||||
ks.load_keyspace()?;
|
ks.load_keyspace().await?;
|
||||||
Ok(ks)
|
Ok(ks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,8 +68,13 @@ impl KeySpace {
|
|||||||
impl KeySpace {
|
impl KeySpace {
|
||||||
pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result<Self, Error> {
|
pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result<Self, Error> {
|
||||||
let store = WasmStore::open(name).await?;
|
let store = WasmStore::open(name).await?;
|
||||||
todo!();
|
let mut ks = Self {
|
||||||
// Ok(Self { store })
|
store,
|
||||||
|
keys: HashMap::new(),
|
||||||
|
encryption_key,
|
||||||
|
};
|
||||||
|
ks.load_keyspace().await?;
|
||||||
|
Ok(ks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +90,13 @@ impl KeySpace {
|
|||||||
/// This overwrites the existing key if one is already stored with the same 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> {
|
pub async fn set(&mut self, key: String, value: Key) -> Result<(), Error> {
|
||||||
self.keys.insert(key, value);
|
self.keys.insert(key, value);
|
||||||
self.save_keyspace()
|
self.save_keyspace().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete the [`Key`] stored under the provided name.
|
/// Delete the [`Key`] stored under the provided name.
|
||||||
pub async fn delete(&mut self, key: &str) -> Result<(), Error> {
|
pub async fn delete(&mut self, key: &str) -> Result<(), Error> {
|
||||||
self.keys.remove(key);
|
self.keys.remove(key);
|
||||||
self.save_keyspace()
|
self.save_keyspace().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all stored [`keys`](Key) in the KeySpace
|
/// Iterate over all stored [`keys`](Key) in the KeySpace
|
||||||
@ -93,9 +106,10 @@ impl KeySpace {
|
|||||||
|
|
||||||
/// Encrypt all keys and save them to the underlying store
|
/// Encrypt all keys and save them to the underlying store
|
||||||
async fn save_keyspace(&self) -> Result<(), Error> {
|
async fn save_keyspace(&self) -> Result<(), Error> {
|
||||||
// Bincode encode keys
|
let encoded_keys = bincode::serde::encode_to_vec(&self.keys, BINCODE_CONFIG)?;
|
||||||
//
|
let value = self.encryption_key.encrypt(&encoded_keys)?;
|
||||||
// Put in store
|
// Put in store
|
||||||
|
Ok(self.store.set(KEYSPACE_NAME, &value).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the encrypted keyspace from the underlying storage
|
/// Loads the encrypted keyspace from the underlying storage
|
||||||
@ -105,8 +119,13 @@ impl KeySpace {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: bincode decode
|
let raw = self.encryption_key.decrypt(&ks)?;
|
||||||
|
|
||||||
todo!();
|
let (decoded_keys, _): (HashMap<String, Key>, _) =
|
||||||
|
bincode::serde::decode_from_slice(&raw, BINCODE_CONFIG)?;
|
||||||
|
|
||||||
|
self.keys = decoded_keys;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,47 @@
|
|||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace};
|
||||||
|
|
||||||
/// Store is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where
|
/// Store is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where
|
||||||
/// each [`space`](KeySpace) is itself an encrypted key-value store
|
/// each [`space`](KeySpace) is itself an encrypted key-value store
|
||||||
pub struct Store {}
|
pub struct Store {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl Store {
|
||||||
|
/// Create a new store at the given path, creating the path if it does not exist yet.
|
||||||
|
pub async fn new(path: &Path) -> Result<Self, Error> {
|
||||||
|
if path.exists() {
|
||||||
|
if !path.is_dir() {
|
||||||
|
return Err(Error::IOError(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
"expected directory",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::fs::create_dir_all(path)?;
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
/// Open a keyspace with the given name
|
||||||
|
pub async fn open_keyspace(&self, name: &str, password: &str) -> Result<KeySpace, Error> {
|
||||||
|
let encryption_key = SymmetricKey::derive_from_password(password);
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
let path = self.path.join(name);
|
||||||
|
KeySpace::open(&path, encryption_key).await
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
KeySpace::open(name, encryption_key).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user