use crate::account::{Account, Role};
use crate::crypto;
use crate::error::{Error, Result};
use crate::store;
use neuxdb::Database;
use rand::RngCore;
use secrecy::{ExposeSecret, SecretString};
use std::path::PathBuf;
use uuid::Uuid;
pub struct Vault {
db: Database,
kek: SecretString,
salt: Vec<u8>,
}
impl Vault {
pub fn create(db_path: impl Into<PathBuf>, master_password: &str) -> Result<Self> {
let db = Database::create(db_path.into(), master_password)?;
let mut salt = vec![0u8; 16];
rand::thread_rng().fill_bytes(&mut salt);
let kek = crypto::derive_kek(master_password, &salt)?;
let mut vault = Self { db, kek, salt };
store::init_accounts_table(&mut vault.db)?;
store::init_metadata_table(&mut vault.db)?;
store::save_metadata_salt(&mut vault.db, &vault.salt)?;
vault.db.commit()?;
Ok(vault)
}
pub fn open(db_path: impl Into<PathBuf>, master_password: &str) -> Result<Self> {
let mut db = Database::open(db_path.into(), master_password)?;
store::init_metadata_table(&mut db)?;
let salt = store::load_metadata_salt(&db)?;
let kek = crypto::derive_kek(master_password, &salt)?;
let mut vault = Self { db, kek, salt };
store::init_accounts_table(&mut vault.db)?;
Ok(vault)
}
pub fn add_account(&mut self, name: &str, role: Role) -> Result<Account> {
if self.get_account_by_name(name).is_ok() {
return Err(Error::AccountExists(name.into()));
}
let keypair = age_setup::build_keypair()?;
let public_key = keypair.public.expose().to_string();
let secret_key = keypair.secret.expose_secret().to_string();
let encrypted_secret_key =
crypto::encrypt_secret_key(&secret_key, self.kek.expose_secret())?;
let account = Account {
id: Uuid::new_v4().to_string(),
name: name.to_string(),
role,
public_key,
encrypted_secret_key,
enabled: true,
};
store::save_account(&mut self.db, &account)?;
Ok(account)
}
pub fn remove_account(&mut self, name: &str) -> Result<()> {
let account = self.get_account_by_name(name)?;
self.db
.delete("accounts", &|row| row[0].to_string() == account.id)?;
self.db.commit()?;
Ok(())
}
pub fn list_accounts(&self) -> Result<Vec<Account>> {
store::load_accounts(&self.db)
}
pub fn encrypt_for(&self, account_names: &[&str], plaintext: &[u8]) -> Result<Vec<u8>> {
let recipients: Vec<String> = account_names
.iter()
.map(|n| {
let acc = self.get_account_by_name(n)?;
Ok(acc.public_key)
})
.collect::<Result<Vec<_>>>()?;
let recipient_strs: Vec<&str> = recipients.iter().map(|s| s.as_str()).collect();
Ok(age_crypto::encrypt(plaintext, &recipient_strs)?.to_vec())
}
pub fn decrypt_with(&self, account_name: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
let acc = self.get_account_by_name(account_name)?;
let secret_key =
crypto::decrypt_secret_key(&acc.encrypted_secret_key, self.kek.expose_secret())?;
age_crypto::decrypt(ciphertext, &secret_key)
.map_err(|e| Error::DecryptionFailed(e.to_string()))
}
fn get_account_by_name(&self, name: &str) -> Result<Account> {
let accounts = store::load_accounts(&self.db)?;
accounts
.into_iter()
.find(|a| a.name == name)
.ok_or_else(|| Error::AccountNotFound(name.to_string()))
}
}