creddy/src-tauri/src/state.rs

193 lines
6.0 KiB
Rust
Raw Normal View History

2022-11-28 16:16:33 -08:00
use std::collections::HashMap;
use std::sync::RwLock;
use serde::{Serialize, Deserialize};
use tokio::sync::oneshot::Sender;
2022-12-02 22:59:13 -08:00
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, sqlite::SqliteConnectOptions};
2022-12-03 21:47:09 -08:00
use sodiumoxide::crypto::{
pwhash,
pwhash::Salt,
secretbox,
secretbox::{Nonce, Key}
};
use tauri::async_runtime as runtime;
use crate::ipc;
2022-11-28 16:16:33 -08:00
use crate::errors::*;
#[derive(Serialize, Deserialize)]
2022-11-28 16:16:33 -08:00
#[serde(rename_all = "PascalCase")]
#[serde(untagged)]
pub enum Credentials {
LongLived {
2022-12-13 21:50:34 -08:00
#[serde(rename = "AccessKeyId")]
2022-11-28 16:16:33 -08:00
access_key_id: String,
2022-12-13 21:50:34 -08:00
#[serde(rename = "SecretAccessKey")]
2022-11-28 16:16:33 -08:00
secret_access_key: String,
},
ShortLived {
2022-12-13 21:50:34 -08:00
#[serde(rename = "AccessKeyId")]
2022-11-28 16:16:33 -08:00
access_key_id: String,
2022-12-13 21:50:34 -08:00
#[serde(rename = "SecretAccessKey")]
2022-11-28 16:16:33 -08:00
secret_access_key: String,
2022-12-13 21:50:34 -08:00
#[serde(rename = "Token")]
2022-11-28 16:16:33 -08:00
token: String,
2022-12-13 21:50:34 -08:00
#[serde(rename = "Expiration")]
2022-11-28 16:16:33 -08:00
expiration: String,
},
}
2022-12-03 21:47:09 -08:00
pub struct LockedCredentials {
access_key_id: String,
secret_key_enc: Vec<u8>,
salt: Salt,
nonce: Nonce,
}
2022-12-03 21:47:09 -08:00
pub enum Session {
Unlocked(Credentials),
Locked(LockedCredentials),
Empty,
}
// #[derive(Serialize, Deserialize)]
// pub enum SessionStatus {
// Unlocked,
// Locked,
// Empty,
// }
pub struct AppState {
2022-12-03 21:47:09 -08:00
pub session: RwLock<Session>,
pub request_count: RwLock<u64>,
pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
2022-12-02 22:59:13 -08:00
pool: SqlitePool,
}
impl AppState {
2022-12-03 21:47:09 -08:00
pub fn new() -> Result<Self, SetupError> {
2022-12-02 22:59:13 -08:00
let conn_opts = SqliteConnectOptions::new()
.filename("creddy.db")
.create_if_missing(true);
let pool_opts = SqlitePoolOptions::new();
2022-12-03 21:47:09 -08:00
let pool: SqlitePool = runtime::block_on(pool_opts.connect_with(conn_opts))?;
runtime::block_on(sqlx::migrate!().run(&pool))?;
let creds = runtime::block_on(Self::load_creds(&pool))?;
2022-12-02 22:59:13 -08:00
let state = AppState {
2022-12-03 21:47:09 -08:00
session: RwLock::new(creds),
2022-11-28 16:16:33 -08:00
request_count: RwLock::new(0),
open_requests: RwLock::new(HashMap::new()),
2022-12-02 22:59:13 -08:00
pool,
};
2022-12-03 21:47:09 -08:00
2022-12-02 22:59:13 -08:00
Ok(state)
}
2022-12-03 21:47:09 -08:00
async fn load_creds(pool: &SqlitePool) -> Result<Session, SetupError> {
let res = sqlx::query!("SELECT * FROM credentials")
.fetch_optional(pool)
2022-12-02 22:59:13 -08:00
.await?;
2022-12-03 21:47:09 -08:00
let row = match res {
Some(r) => r,
None => {return Ok(Session::Empty);}
};
2022-12-02 22:59:13 -08:00
2022-12-03 21:47:09 -08:00
let salt_buf: [u8; 32] = row.salt
.try_into()
.map_err(|_e| SetupError::InvalidRecord)?;
let nonce_buf: [u8; 24] = row.nonce
.try_into()
.map_err(|_e| SetupError::InvalidRecord)?;
let creds = LockedCredentials {
access_key_id: row.access_key_id,
secret_key_enc: row.secret_key_enc,
salt: Salt(salt_buf),
nonce: Nonce(nonce_buf),
};
Ok(Session::Locked(creds))
}
pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), sqlx::error::Error> {
let (key_id, secret_key) = match creds {
Credentials::LongLived {access_key_id, secret_access_key} => {
(access_key_id, secret_access_key)
},
_ => unreachable!(),
};
let salt = pwhash::gen_salt();
let mut key_buf = [0; secretbox::KEYBYTES];
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &salt).unwrap();
let key = Key(key_buf);
// not sure we need both salt AND nonce given that we generate a
// fresh salt every time we encrypt, but better safe than sorry
let nonce = secretbox::gen_nonce();
let key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key);
// insert into database
2022-12-02 22:59:13 -08:00
Ok(())
}
2022-11-28 16:16:33 -08:00
pub fn register_request(&self, chan: Sender<ipc::Approval>) -> u64 {
let count = {
2022-11-29 16:13:09 -08:00
let mut c = self.request_count.write().unwrap();
*c += 1;
c
};
2022-11-29 16:13:09 -08:00
let mut open_requests = self.open_requests.write().unwrap();
2022-12-03 21:47:09 -08:00
open_requests.insert(*count, chan); // `count` is the request id
2022-11-28 16:16:33 -08:00
*count
}
2022-11-28 16:16:33 -08:00
pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
let mut open_requests = self.open_requests.write().unwrap();
2022-11-28 16:16:33 -08:00
let chan = open_requests
.remove(&response.id)
.ok_or(SendResponseError::NotFound)
?;
2022-11-28 16:16:33 -08:00
chan.send(response.approval)
.map_err(|_e| SendResponseError::Abandoned)
}
2022-12-03 21:47:09 -08:00
pub async fn decrypt(&self, passphrase: &str) -> Result<(), UnlockError> {
let session = self.session.read().unwrap();
let locked = match *session {
Session::Empty => {return Err(UnlockError::NoCredentials);},
Session::Unlocked(_) => {return Err(UnlockError::NotLocked);},
Session::Locked(ref c) => c,
};
let mut key_buf = [0; secretbox::KEYBYTES];
// pretty sure this only fails if we're out of memory
pwhash::derive_key_interactive(&mut key_buf, passphrase.as_bytes(), &locked.salt).unwrap();
let decrypted = secretbox::open(&locked.secret_key_enc, &locked.nonce, &Key(key_buf))
.map_err(|_e| UnlockError::BadPassphrase)?;
let secret_str = String::from_utf8(decrypted).map_err(|_e| UnlockError::InvalidUtf8)?;
let mut session = self.session.write().unwrap();
let creds = Credentials::LongLived {
access_key_id: locked.access_key_id.clone(),
secret_access_key: secret_str,
};
*session = Session::Unlocked(creds);
Ok(())
2022-11-29 16:13:09 -08:00
}
2022-12-03 21:47:09 -08:00
pub fn get_creds_serialized(&self) -> Result<String, GetCredentialsError> {
let session = self.session.read().unwrap();
match *session {
Session::Unlocked(ref creds) => Ok(serde_json::to_string(creds).unwrap()),
Session::Locked(_) => Err(GetCredentialsError::Locked),
Session::Empty => Err(GetCredentialsError::Empty),
}
2022-11-28 16:16:33 -08:00
}
}