use chacha20poly1305::XNonce; use sqlx::SqlitePool; use crate::errors::*; use crate::kv; use super::Crypto; #[derive(Clone, Debug)] pub enum AppSession { Unlocked { salt: [u8; 32], crypto: Crypto, }, Locked { salt: [u8; 32], verify_nonce: XNonce, verify_blob: Vec }, Empty, } impl AppSession { pub fn new(passphrase: &str) -> Result { let salt = Crypto::salt(); let crypto = Crypto::new(passphrase, &salt)?; Ok(Self::Unlocked {salt, crypto}) } pub fn unlock(&mut self, passphrase: &str) -> Result<(), UnlockError> { let (salt, nonce, blob) = match self { Self::Empty => return Err(UnlockError::NoCredentials), Self::Unlocked {..} => return Err(UnlockError::NotLocked), Self::Locked {salt, verify_nonce, verify_blob} => (salt, verify_nonce, verify_blob), }; let crypto = Crypto::new(passphrase, salt) .map_err(|e| CryptoError::Argon2(e))?; // if passphrase is incorrect, this will fail let _verify = crypto.decrypt(&nonce, &blob)?; *self = Self::Unlocked {crypto, salt: *salt}; Ok(()) } pub async fn load(pool: &SqlitePool) -> Result { match kv::load_bytes_multi!(pool, "salt", "verify_nonce", "verify_blob").await? { Some((salt, nonce, blob)) => { Ok(Self::Locked { salt: salt.try_into().map_err(|_| LoadCredentialsError::InvalidData)?, // note: replace this with try_from at some point verify_nonce: XNonce::clone_from_slice(&nonce), verify_blob: blob, }) }, None => Ok(Self::Empty), } } pub async fn save(&self, pool: &SqlitePool) -> Result<(), SaveCredentialsError> { match self { Self::Unlocked {salt, crypto} => { let (nonce, blob) = crypto.encrypt(b"correct horse battery staple")?; kv::save_bytes(pool, "salt", salt).await?; kv::save_bytes(pool, "verify_nonce", &nonce.as_slice()).await?; kv::save_bytes(pool, "verify_blob", &blob).await?; }, Self::Locked {salt, verify_nonce, verify_blob} => { kv::save_bytes(pool, "salt", salt).await?; kv::save_bytes(pool, "verify_nonce", &verify_nonce.as_slice()).await?; kv::save_bytes(pool, "verify_blob", verify_blob).await?; }, // "saving" an empty session just means doing nothing Self::Empty => (), }; Ok(()) } pub async fn reset(&mut self, pool: &SqlitePool) -> Result<(), SaveCredentialsError> { match self { Self::Unlocked {..} | Self::Locked {..} => { kv::delete_multi(pool, &["salt", "verify_nonce", "verify_blob"]).await?; *self = Self::Empty; }, Self::Empty => (), } Ok(()) } pub fn try_get_crypto(&self) -> Result<&Crypto, GetCredentialsError> { match self { Self::Empty => Err(GetCredentialsError::Empty), Self::Locked {..} => Err(GetCredentialsError::Locked), Self::Unlocked {crypto, ..} => Ok(crypto), } } }