working basic flow

This commit is contained in:
Joseph Montanaro
2022-12-19 15:26:44 -08:00
parent 10fd1d6028
commit 3d5cbedae1
11 changed files with 700 additions and 89 deletions

View File

@ -36,7 +36,7 @@ pub fn get_clients(local_port: u16) -> Result<Vec<Client>, ClientInfoError> {
let mut clients = Vec::new();
let mut sys = System::new();
for p in get_associated_pids(local_port)? {
let pid = Pid::from(p as usize);
let pid = Pid::from(p as i32);
sys.refresh_process(pid);
let proc = sys.process(pid)
.ok_or(ClientInfoError::PidNotFound)?;

View File

@ -58,6 +58,8 @@ pub enum RequestError {
RequestTooLarge,
NoCredentials(GetCredentialsError),
ClientInfo(ClientInfoError),
Tauri(tauri::Error),
NoMainWindow,
}
impl From<tokio::io::Error> for RequestError {
fn from(e: std::io::Error) -> RequestError {
@ -79,6 +81,11 @@ impl From<ClientInfoError> for RequestError {
RequestError::ClientInfo(e)
}
}
impl From<tauri::Error> for RequestError {
fn from(e: tauri::Error) -> RequestError {
RequestError::Tauri(e)
}
}
impl Display for RequestError {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
@ -92,6 +99,8 @@ impl Display for RequestError {
NoCredentials(GetCredentialsError::Empty) => write!(f, "Received go-ahead but no credentials are known"),
ClientInfo(ClientInfoError::PidNotFound) => write!(f, "Could not resolve PID of client process."),
ClientInfo(ClientInfoError::NetstatError(e)) => write!(f, "Error getting client socket details: {e}"),
Tauri(e) => write!(f, "Tauri error: {e}"),
NoMainWindow => write!(f, "No main application window found"),
}
}
}
@ -102,6 +111,26 @@ pub enum GetCredentialsError {
Empty,
}
pub type AwsTokenError = aws_sdk_sts::types::SdkError<aws_sdk_sts::error::GetSessionTokenError>;
pub enum GetSessionError {
NoCredentials, // SDK returned successfully but credentials are None
SdkError(AwsTokenError),
}
impl From<AwsTokenError> for GetSessionError {
fn from(e: AwsTokenError) -> GetSessionError {
GetSessionError::SdkError(e)
}
}
impl Display for GetSessionError {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
match self {
GetSessionError::NoCredentials => write!(f, "Request completed successfully but no credentials were returned"),
GetSessionError::SdkError(e) => write!(f, "Error response from AWS: {e}")
}
}
}
pub enum UnlockError {
NotLocked,
@ -109,6 +138,7 @@ pub enum UnlockError {
BadPassphrase,
InvalidUtf8, // Somehow we got invalid utf-8 even though decryption succeeded
DbError(SqlxError),
GetSession(GetSessionError),
}
impl From<SqlxError> for UnlockError {
fn from (e: SqlxError) -> UnlockError {
@ -118,6 +148,11 @@ impl From<SqlxError> for UnlockError {
}
}
}
impl From<GetSessionError> for UnlockError {
fn from(e: GetSessionError) -> UnlockError {
UnlockError::GetSession(e)
}
}
impl Display for UnlockError {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
use UnlockError::*;
@ -127,6 +162,7 @@ impl Display for UnlockError {
BadPassphrase => write!(f, "Invalid passphrase"),
InvalidUtf8 => write!(f, "Decrypted data was corrupted"),
DbError(e) => write!(f, "Database error: {e}"),
GetSession(e) => write!(f, "Failed to create AWS session: {e}")
}
}
}

View File

@ -10,7 +10,6 @@ mod clientinfo;
mod ipc;
mod state;
mod server;
mod storage;
fn main() {

View File

@ -53,9 +53,11 @@ async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), Requ
};
let clients = clientinfo::get_clients(peer_addr.port())?;
// Do we want to panic if this fails? Does that mean the frontend is dead?
let req = Request {id: request_id, clients};
app_handle.emit_all("credentials-request", req).unwrap();
app_handle.emit_all("credentials-request", req)?;
let window = app_handle.get_window("main").ok_or(RequestError::NoMainWindow)?;
window.show()?;
window.set_focus()?;
let mut buf = [0; 8192]; // it's what tokio's BufReader uses
let mut n = 0;

View File

@ -49,14 +49,6 @@ pub enum Session {
}
// #[derive(Serialize, Deserialize)]
// pub enum SessionStatus {
// Unlocked,
// Locked,
// Empty,
// }
pub struct AppState {
pub session: RwLock<Session>,
pub request_count: RwLock<u64>,
@ -110,7 +102,7 @@ impl AppState {
Ok(Session::Locked(creds))
}
pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), sqlx::error::Error> {
pub async fn save_creds(&self, creds: Credentials, passphrase: &str) -> Result<(), UnlockError> {
let (key_id, secret_key) = match creds {
Credentials::LongLived {access_key_id, secret_access_key} => {
(access_key_id, secret_access_key)
@ -124,16 +116,21 @@ impl AppState {
// 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);
let secret_key_enc = secretbox::seal(secret_key.as_bytes(), &nonce, &key);
// insert into database
// eventually replace this with a temporary session
let mut session = self.session.write().unwrap();
*session = Session::Unlocked(Credentials::LongLived {
access_key_id: key_id,
secret_access_key: secret_key,
});
sqlx::query(
"INSERT INTO credentials (access_key_id, secret_key_enc, salt, nonce)
VALUES (?, ?, ?, ?)"
)
.bind(&key_id)
.bind(&secret_key_enc)
.bind(&salt.0[0..])
.bind(&nonce.0[0..])
.execute(&self.pool)
.await?;
self.new_session(&key_id, &secret_key).await?;
Ok(())
}
@ -162,26 +159,27 @@ impl AppState {
}
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 (key_id, secret) = {
// do this all in a block so rustc doesn't complain about holding a lock across an await
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)?;
(locked.access_key_id.clone(), secret_str)
};
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)?;
self.new_session(&key_id, &secret).await?;
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(())
}
@ -193,4 +191,54 @@ impl AppState {
Session::Empty => Err(GetCredentialsError::Empty),
}
}
async fn new_session(&self, key_id: &str, secret_key: &str) -> Result<(), GetSessionError> {
let creds = aws_sdk_sts::Credentials::new(
key_id,
secret_key,
None, // token
None, // expiration
"creddy", // "provider name" apparently
);
let config = aws_config::from_env()
.credentials_provider(creds)
.load()
.await;
let client = aws_sdk_sts::Client::new(&config);
let resp = client.get_session_token()
.duration_seconds(43_200)
.send()
.await?;
let aws_session = resp.credentials().ok_or(GetSessionError::NoCredentials)?;
let access_key_id = aws_session.access_key_id()
.ok_or(GetSessionError::NoCredentials)?
.to_string();
let secret_access_key = aws_session.secret_access_key()
.ok_or(GetSessionError::NoCredentials)?
.to_string();
let token = aws_session.session_token()
.ok_or(GetSessionError::NoCredentials)?
.to_string();
let expiration = aws_session.expiration()
.ok_or(GetSessionError::NoCredentials)?
.fmt(aws_smithy_types::date_time::Format::DateTime)
.unwrap(); // only fails if the d/t is out of range, which it can't be for this format
let mut app_session = self.session.write().unwrap();
let session_creds = Credentials::ShortLived {
access_key_id,
secret_access_key,
token,
expiration,
};
println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap());
*app_session = Session::Unlocked(session_creds);
Ok(())
}
}

View File

@ -1,42 +0,0 @@
use sodiumoxide::crypto::{pwhash, secretbox};
use crate::state;
pub fn save(data: &str, passphrase: &str) {
let salt = pwhash::Salt([0; 32]); // yes yes, just for now
let mut kbuf = [0; secretbox::KEYBYTES];
pwhash::derive_key_interactive(&mut kbuf, passphrase.as_bytes(), &salt)
.expect("Couldn't compute password hash. Are you out of memory?");
let key = secretbox::Key(kbuf);
let nonce = secretbox::Nonce([0; 24]); // we don't care about e.g. replay attacks so this might be safe?
let encrypted = secretbox::seal(data.as_bytes(), &nonce, &key);
//todo: store in a database, along with salt, nonce, and hash parameters
std::fs::write("credentials.enc", &encrypted).expect("Failed to write file.");
//todo: key is automatically zeroed, but we should use 'zeroize' or something to zero out passphrase and data
}
// pub fn load(passphrase: &str) -> String {
// let salt = pwhash::Salt([0; 32]);
// let mut kbuf = [0; secretbox::KEYBYTES];
// pwhash::derive_key_interactive(&mut kbuf, passphrase.as_bytes(), &salt)
// .expect("Couldn't compute password hash. Are you out of memory?");
// let key = secretbox::Key(kbuf);
// let nonce = secretbox::Nonce([0; 24]);
// let encrypted = std::fs::read("credentials.enc").expect("Failed to read file.");
// let decrypted = secretbox::open(&encrypted, &nonce, &key).expect("Failed to decrypt.");
// String::from_utf8(decrypted).expect("Invalid utf-8")
// }
pub fn load(passphrase: &str) -> state::Credentials {
state::Credentials::ShortLived {
access_key_id: "ASIAZ7WSVLORKQI27QGB".to_string(),
secret_access_key: "blah".to_string(),
token: "gah".to_string(),
expiration: "2022-11-29T10:45:12Z".to_string(),
}
}