diff --git a/Cargo.lock b/Cargo.lock index be1143e..4685e89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,16 +131,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.14" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +checksum = "8e538f9ee5aa3b3963f09a997035f883677966ed50fce0292611927ce6f6d8c6" dependencies = [ "atty", "bitflags", "clap_derive", + "clap_lex", "indexmap", "lazy_static", - "os_str_bytes", "strsim", "termcolor", "textwrap", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.0.14" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85" +checksum = "a7f98063cac4652f23ccda556b8d04347a7fc4b2cff1f7577cc8c6546e0d8078" dependencies = [ "heck", "proc-macro-error", @@ -159,6 +159,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "cpufeatures" version = "0.2.1" @@ -495,6 +504,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "names" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" +dependencies = [ + "clap", + "rand", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -531,9 +550,6 @@ name = "os_str_bytes" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] [[package]] name = "parking_lot" @@ -702,6 +718,7 @@ dependencies = [ "clap", "flate2", "futures", + "names", "rand", "serde", "spake2", @@ -813,9 +830,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "tokio" diff --git a/Cargo.toml b/Cargo.toml index 3fba138..549d655 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "ruck" version = "0.1.0" edition = "2021" +description = "A croc-inspired tool for hosting relay servers and sending e2e encrypted files." [dependencies] @@ -13,6 +14,7 @@ bincode = "1.3.3" clap = { version = "3.0.14", features = ["derive"] } flate2 = "1.0" futures = { version = "0.3.0", features = ["thread-pool"]} +names = "0.14.0" rand = "0.8.4" serde = { version = "1.0", features = ["derive"] } spake2 = "0.3.1" diff --git a/README.md b/README.md index 843f9c6..9648324 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,22 @@ ## Usage -```bash -## tab 1 -cargo run relay # this starts the server +``` +ruck 0.1.0 +A croc-inspired tool for hosting relay servers and sending e2e encrypted files. -## tab 2 -cargo run send /path/to/file1.md /path/to/file2.md supersecretpassword +USAGE: + ruck -## tab 3 -cargo run receive supersecretpassword +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + help Print this message or the help of the given subcommand(s) + receive Receive file(s). Must provide password shared out of band + relay Start relay server + send Send file(s). Can provide optional password ``` ## Protocol diff --git a/src/cli.rs b/src/cli.rs index 5aecaf1..cd2c616 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,27 +1,33 @@ use std::path::PathBuf; -use clap::{AppSettings, Parser, Subcommand}; +use clap::{Parser, Subcommand}; /// A fictional versioning CLI -#[derive(Parser)] +#[derive(Parser, Debug)] #[clap(name = "ruck")] -#[clap(about = "Croc in rust", long_about = None)] +#[clap(author, version, about, long_about = None)] pub struct Cli { #[clap(subcommand)] pub command: Commands, } -#[derive(Subcommand)] +#[derive(Subcommand, Debug)] pub enum Commands { - #[clap(setting(AppSettings::ArgRequiredElseHelp))] + /// Send file(s). Can provide optional password Send { - #[clap(required = true, parse(from_os_str))] + // Optional password to use, must be 16 characters at least. If none provided, one will be generated. + #[clap(long, value_parser, required = false)] + password: Option, + /// Paths to files to be sent. + #[clap(value_parser, required = true)] paths: Vec, - password: String, }, - #[clap(setting(AppSettings::ArgRequiredElseHelp))] + /// Receive file(s). Must provide password shared out of band Receive { + /// Password shared out + #[clap(value_parser, required = true)] password: String, }, + /// Start relay server Relay {}, } diff --git a/src/client.rs b/src/client.rs index 461c530..3410b92 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,23 +2,29 @@ use crate::connection::Connection; use crate::file::{ChunkHeader, FileHandle, FileOffer, StdFileHandle}; use crate::handshake::Handshake; use crate::message::{FileOfferPayload, FileRequestPayload, Message}; +use crate::password::validate_generate_pw; use crate::ui::prompt_user_for_file_confirmation; use anyhow::{anyhow, Result}; use std::path::PathBuf; -use tokio::fs::File; +use tokio::fs::{File, OpenOptions}; use tokio::net::TcpStream; -pub async fn send(file_paths: &Vec, password: &String) -> Result<()> { +pub async fn send(file_paths: &Vec, password: &Option) -> Result<()> { // Fail early if there are problems generating file handles let handles = FileHandle::get_file_handles(file_paths).await?; // Establish connection to server let socket = TcpStream::connect("127.0.0.1:8080").await?; - let (handshake, s1) = Handshake::from_password(password); + let pw = validate_generate_pw(password.clone())?; + println!( + "Type `ruck receive {}` on other end to receive file(s).", + pw + ); + let (handshake, s1) = Handshake::from_password(&pw)?; // Complete handshake, returning key used for encryption let (socket, key) = handshake.negotiate(socket, s1).await?; @@ -38,7 +44,7 @@ pub async fn send(file_paths: &Vec, password: &String) -> Result<()> { pub async fn receive(password: &String) -> Result<()> { // Establish connection to server let socket = TcpStream::connect("127.0.0.1:8080").await?; - let (handshake, s1) = Handshake::from_password(password); + let (handshake, s1) = Handshake::from_password(password)?; // Complete handshake, returning key used for encryption let (socket, key) = handshake.negotiate(socket, s1).await?; let mut connection = Connection::new(socket, key); @@ -97,8 +103,7 @@ pub async fn create_or_find_files(desired_files: Vec) -> Result { println!( "File {:?} already exists. Attempting to resume download.", diff --git a/src/conf.rs b/src/conf.rs index cf506ec..383ca92 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -3,3 +3,4 @@ pub const HANDSHAKE_MSG_SIZE: usize = 33; // generated by Spake2 pub const PER_CLIENT_BUFFER: usize = 1024 * 64; // buffer size allocated by server for each client pub const BUFFER_SIZE: usize = 1024 * 64; // chunk size for files sent over wire pub const NONCE_SIZE: usize = 96 / 8; // used for every encrypted message +pub const PASSWORD_LEN: usize = 12; // min size of password diff --git a/src/connection.rs b/src/connection.rs index a02caa4..2499a4d 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -81,7 +81,7 @@ impl Connection { let elapsed = before.elapsed(); let mb_sent = bytes_sent / 1_048_576; println!( - "{:?}: {:?} mb sent (compressed), {:?} iterations. {:?} total time, {:?} avg per iteration, {:?} avg mb/sec", + "{:?}: {:?} mb sent (compressed), {:?} messages. {:?} total time, {:?} avg per msg, {:?} avg mb/sec", handle.name, mb_sent, count, diff --git a/src/file.rs b/src/file.rs index ba47a6f..d492512 100644 --- a/src/file.rs +++ b/src/file.rs @@ -82,7 +82,7 @@ impl FileHandle { }; } None => { - println!("Skipping file b/c not in requested chunks"); + println!("Skipping {:?} b/c not in requested chunks.", handle.path); } } } diff --git a/src/handshake.rs b/src/handshake.rs index c695d29..fb3ab20 100644 --- a/src/handshake.rs +++ b/src/handshake.rs @@ -13,8 +13,8 @@ pub struct Handshake { } impl Handshake { - pub fn from_password(pw: &String) -> (Handshake, spake2::Spake2) { - let password = Bytes::from(pw.to_string()); + pub fn from_password(pw: &String) -> Result<(Handshake, spake2::Spake2)> { + let password = Bytes::from(pw.clone()); let id = Handshake::pass_to_bytes(&pw); let (s1, outbound_msg) = Spake2::::start_symmetric(&Password::new(&password), &Identity::new(&id)); @@ -22,7 +22,7 @@ impl Handshake { buffer.extend_from_slice(&outbound_msg[..HANDSHAKE_MSG_SIZE]); let outbound_msg = buffer.freeze(); let handshake = Handshake { id, outbound_msg }; - (handshake, s1) + Ok((handshake, s1)) } pub async fn from_socket(socket: TcpStream) -> Result<(Handshake, TcpStream)> { @@ -64,9 +64,7 @@ impl Handshake { } fn pass_to_bytes(password: &String) -> Bytes { - let mut hasher = Blake2s256::new(); - hasher.update(password.as_bytes()); - let res = hasher.finalize(); - BytesMut::from(&res[..]).freeze() + let bytes = Blake2s256::digest(password.as_bytes()); + BytesMut::from(&bytes[..]).freeze() } } diff --git a/src/main.rs b/src/main.rs index f880896..d79afe9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod crypto; mod file; mod handshake; mod message; +mod password; mod server; mod ui; diff --git a/src/password.rs b/src/password.rs new file mode 100644 index 0000000..e4e11a3 --- /dev/null +++ b/src/password.rs @@ -0,0 +1,24 @@ +use crate::conf::PASSWORD_LEN; + +use anyhow::{anyhow, Result}; +use names::{Generator, Name}; + +pub fn validate_generate_pw(pw: Option) -> Result { + let pass = pw.unwrap_or_else(generate_random_password); + match validate_pw(&pass) { + true => Ok(pass), + false => Err(anyhow!("Password too short.")), + } +} + +pub fn validate_pw(pw: &String) -> bool { + PASSWORD_LEN <= pw.len() +} + +pub fn generate_random_password() -> String { + let mut generator = Generator::with_naming(Name::Numbered); + match generator.next() { + Some(s) if PASSWORD_LEN <= s.len() => s, + _ => generate_random_password(), + } +}