working basic flow
This commit is contained in:
@ -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)?;
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ mod clientinfo;
|
||||
mod ipc;
|
||||
mod state;
|
||||
mod server;
|
||||
mod storage;
|
||||
|
||||
|
||||
fn main() {
|
||||
|
@ -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;
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user