initial commit

This commit is contained in:
2022-08-14 13:27:41 -07:00
commit b30e8bcea1
38 changed files with 6919 additions and 0 deletions

View File

@ -0,0 +1,42 @@
use std::fmt::{Display, Formatter};
use std::convert::From;
use std::str::Utf8Error;
// use tokio::sync::oneshot::error::RecvError;
// Represents errors encountered while handling an HTTP request
pub enum RequestError {
StreamIOError(std::io::Error),
InvalidUtf8,
MalformedHttpRequest,
RequestTooLarge,
}
impl From<tokio::io::Error> for RequestError {
fn from(e: std::io::Error) -> RequestError {
RequestError::StreamIOError(e)
}
}
impl From<Utf8Error> for RequestError {
fn from(_e: Utf8Error) -> RequestError {
RequestError::InvalidUtf8
}
}
// impl From<RecvError> for RequestError {
// fn from (_e: RecvError) -> RequestError {
// RequestError::
// }
// }
impl Display for RequestError {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
use RequestError::*;
match self {
StreamIOError(e) => write!(f, "Stream IO error: {e}"),
InvalidUtf8 => write!(f, "Could not decode UTF-8 from bytestream"),
MalformedHttpRequest => write!(f, "Maformed HTTP request"),
RequestTooLarge => write!(f, "HTTP request too large"),
}
}
}

99
src-tauri/src/http/mod.rs Normal file
View File

@ -0,0 +1,99 @@
use std::io;
use std::net::SocketAddrV4;
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tauri::{AppHandle, Manager};
mod errors;
use errors::RequestError;
pub async fn serve(addr: SocketAddrV4, app_handle: AppHandle) -> io::Result<()> {
let listener = TcpListener::bind(&addr).await?;
println!("Listening on {addr}");
loop {
let new_handle = app_handle.app_handle();
match listener.accept().await {
Ok((stream, _)) => {
tokio::spawn(async {
if let Err(e) = handle(stream, new_handle).await {
eprintln!("{e}");
}
});
},
Err(e) => {
println!("Error accepting connection: {e}");
}
}
}
}
// it doesn't really return a String, we just need to placate the compiler
async fn stall(stream: &mut TcpStream) -> Result<String, tokio::io::Error> {
let delay = std::time::Duration::from_secs(1);
loop {
tokio::time::sleep(delay).await;
stream.write(b"x").await?;
}
}
async fn handle(mut stream: TcpStream, app_handle: AppHandle) -> Result<(), RequestError> {
let mut buf = [0; 8192]; // it's what tokio's BufReader uses
let mut n = 0;
loop {
n += stream.read(&mut buf[n..]).await?;
if &buf[(n - 4)..n] == b"\r\n\r\n" {break;}
if n == buf.len() {return Err(RequestError::RequestTooLarge);}
}
println!("{}", std::str::from_utf8(&buf).unwrap());
stream.write(b"HTTP/1.0 200 OK\r\n").await?;
stream.write(b"Content-Type: application/json\r\n").await?;
stream.write(b"X-Creddy-delaying-tactic: ").await?;
let creds = tokio::select!{
r = stall(&mut stream) => r?, // this will never return Ok, just Err if it can't write to the stream
c = get_creds(&app_handle) => c?,
};
stream.write(b"\r\nContent-Length: ").await?;
stream.write(creds.as_bytes().len().to_string().as_bytes()).await?;
stream.write(b"\r\n\r\n").await?;
stream.write(creds.as_bytes()).await?;
stream.write(b"\r\n\r\n").await?;
Ok(())
}
use tokio::io::{stdin, stdout, BufReader, AsyncBufReadExt};
use crate::storage;
use tokio::sync::oneshot;
async fn get_creds(app_handle: &AppHandle) -> io::Result<String> {
app_handle.emit_all("credentials-request", ()).unwrap();
// let mut out = stdout();
// out.write_all(b"Enter passphrase: ").await?;
// out.flush().await?;
let (tx, rx) = oneshot::channel();
app_handle.once_global("passphrase-entered", |event| {
match event.payload() {
Some(p) => {tx.send(p.to_string());}
None => {tx.send("".to_string());} // will fail decryption, we just need to unblock the outer function
}
});
// Error is only returned if the rx is closed/dropped before receiving, which should never happen
let passphrase = rx.await.unwrap();
// let mut passphrase = String::new();
// let mut reader = BufReader::new(stdin());
// reader.read_line(&mut passphrase).await?;
Ok(storage::load(&passphrase.trim()))
}

30
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,30 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use std::str::FromStr;
// use tokio::runtime::Runtime;
mod storage;
mod http;
fn main() {
tauri::Builder::default()
.setup(|app| {
let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
tauri::async_runtime::spawn(http::serve(addr, app.handle()));
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
// let addr = std::net::SocketAddrV4::from_str("127.0.0.1:12345").unwrap();
// let rt = Runtime::new().unwrap();
// rt.block_on(http::serve(addr)).unwrap();
// let creds = std::fs::read_to_string("credentials.json").unwrap();
// storage::save(&creds, "correct horse battery staple");
}

31
src-tauri/src/storage.rs Normal file
View File

@ -0,0 +1,31 @@
use sodiumoxide::crypto::{pwhash, secretbox};
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")
}