use chacha20poly1305::{
self,
aead::{AeadInPlace, NewAead},
XChaCha20Poly1305,
};
use rand::RngCore;
use sled::IVec;
use zeroize::Zeroize;
use super::{
constants::*,
record::EncryptedRecord,
result::{EncryptedDbError::*, EncryptedDbResult},
};
pub struct EncryptedDb {
kv: sled::Db,
cipher: XChaCha20Poly1305,
}
impl EncryptedDb {
pub fn open<P>(db_name: P, mut key: [u8; 32]) -> EncryptedDbResult<Self>
where
P: AsRef<std::path::Path>,
{
let kv = sled::open(db_name).map_err(CorruptedKv)?;
let cipher = XChaCha20Poly1305::new(&key.into());
key.zeroize();
let encrypted_db = EncryptedDb { kv, cipher };
if encrypted_db.kv.was_recovered() {
encrypted_db.get(PASSWORD_VERIFICATION_KEY).map_err(|_| WrongPassword)?;
} else {
encrypted_db.insert(PASSWORD_VERIFICATION_KEY, PASSWORD_VERIFICATION_VALUE)?;
}
Ok(encrypted_db)
}
fn generate_nonce() -> chacha20poly1305::XNonce {
let mut bytes = chacha20poly1305::XNonce::default();
rand::thread_rng().fill_bytes(bytes.as_mut_slice());
bytes
}
fn encrypt<V>(&self, value: V) -> EncryptedDbResult<EncryptedRecord>
where
V: Into<IVec>,
{
let nonce = Self::generate_nonce();
let mut value = value.into().to_vec();
self.cipher
.encrypt_in_place(&nonce, b"", &mut value)
.map_err(|e| Encryption(e.to_string()))?;
Ok(EncryptedRecord::new(value, nonce))
}
fn decrypt_record_value(&self, record: EncryptedRecord) -> EncryptedDbResult<IVec> {
let (mut value, nonce) = record.into();
self.cipher
.decrypt_in_place(&nonce, b"", &mut value)
.map_err(|e| Decryption(e.to_string()))?;
Ok(value.into())
}
fn decrypt(&self, record_bytes: Option<IVec>) -> EncryptedDbResult<Option<IVec>> {
let res = match record_bytes {
Some(record_bytes) => {
let record = EncryptedRecord::from_bytes(&record_bytes)?;
let decrypted_value_bytes = self.decrypt_record_value(record)?;
Some(decrypted_value_bytes)
},
None => None,
};
Ok(res)
}
pub fn insert<K, V>(&self, key: K, value: V) -> EncryptedDbResult<Option<IVec>>
where
K: AsRef<[u8]>,
V: Into<IVec>,
{
let record = self.encrypt(value)?;
let prev_record_bytes_opt = self.kv.insert(&key, record.to_bytes()?)?;
self.decrypt(prev_record_bytes_opt)
}
pub fn get<K>(&self, key: K) -> EncryptedDbResult<Option<IVec>>
where
K: AsRef<[u8]>,
{
let bytes_opt = self.kv.get(&key)?;
self.decrypt(bytes_opt)
}
pub fn contains_key<K>(&self, key: K) -> EncryptedDbResult<bool>
where
K: AsRef<[u8]>,
{
Ok(self.kv.contains_key(&key)?)
}
pub fn remove<K>(&self, key: K) -> EncryptedDbResult<Option<IVec>>
where
K: AsRef<[u8]>,
{
let prev_val = self.kv.remove(&key)?;
self.decrypt(prev_val)
}
pub fn was_recovered(&self) -> bool {
self.kv.was_recovered()
}
}