100 lines
3.3 KiB
Rust
100 lines
3.3 KiB
Rust
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<u8>
|
|
},
|
|
Empty,
|
|
}
|
|
|
|
impl AppSession {
|
|
pub fn new(passphrase: &str) -> Result<Self, CryptoError> {
|
|
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<Self, LoadCredentialsError> {
|
|
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),
|
|
}
|
|
}
|
|
} |