2022-11-28 16:16:33 -08:00
|
|
|
use std::collections::HashMap;
|
2022-11-27 22:03:15 -08:00
|
|
|
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;
|
2022-11-27 22:03:15 -08:00
|
|
|
|
|
|
|
use crate::ipc;
|
2022-11-28 16:16:33 -08:00
|
|
|
use crate::errors::*;
|
2022-11-27 22:03:15 -08:00
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
2022-11-28 16:16:33 -08:00
|
|
|
#[serde(rename_all = "PascalCase")]
|
|
|
|
#[serde(untagged)]
|
2022-11-27 22:03:15 -08:00
|
|
|
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,
|
2022-11-27 22:03:15 -08:00
|
|
|
},
|
|
|
|
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-11-27 22:03:15 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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-11-27 22:03:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
2022-11-27 22:03:15 -08:00
|
|
|
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,
|
2022-11-27 22:03:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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-27 22:03:15 -08:00
|
|
|
}
|
|
|
|
|
2022-11-28 16:16:33 -08:00
|
|
|
pub fn register_request(&self, chan: Sender<ipc::Approval>) -> u64 {
|
2022-11-27 22:03:15 -08:00
|
|
|
let count = {
|
2022-11-29 16:13:09 -08:00
|
|
|
let mut c = self.request_count.write().unwrap();
|
2022-11-27 22:03:15 -08:00
|
|
|
*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-27 22:03:15 -08:00
|
|
|
}
|
|
|
|
|
2022-11-28 16:16:33 -08:00
|
|
|
pub fn send_response(&self, response: ipc::RequestResponse) -> Result<(), SendResponseError> {
|
2022-11-27 22:03:15 -08:00
|
|
|
let mut open_requests = self.open_requests.write().unwrap();
|
2022-11-28 16:16:33 -08:00
|
|
|
let chan = open_requests
|
|
|
|
.remove(&response.id)
|
2022-11-27 22:03:15 -08:00
|
|
|
.ok_or(SendResponseError::NotFound)
|
|
|
|
?;
|
|
|
|
|
2022-11-28 16:16:33 -08:00
|
|
|
chan.send(response.approval)
|
2022-11-27 22:03:15 -08:00
|
|
|
.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
|
|
|
}
|
2022-11-27 22:03:15 -08:00
|
|
|
}
|