Auto generate passwords; fix resume

This commit is contained in:
Donald Knuth 2022-09-06 11:59:33 -04:00
parent 09a67f9944
commit 5d2a216693
11 changed files with 101 additions and 40 deletions

37
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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 <SUBCOMMAND>
## 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

View file

@ -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<String>,
/// Paths to files to be sent.
#[clap(value_parser, required = true)]
paths: Vec<PathBuf>,
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 {},
}

View file

@ -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<PathBuf>, password: &String) -> Result<()> {
pub async fn send(file_paths: &Vec<PathBuf>, password: &Option<String>) -> 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<PathBuf>, 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<FileOffer>) -> Result<Vec<S
let mut v = Vec::new();
for desired_file in desired_files {
let filename = desired_file.path;
// filename.push_str(".part");
let file = match File::open(filename.clone()).await {
let file = match OpenOptions::new().append(true).open(&filename).await {
Ok(file) => {
println!(
"File {:?} already exists. Attempting to resume download.",

View file

@ -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

View file

@ -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,

View file

@ -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);
}
}
}

View file

@ -13,8 +13,8 @@ pub struct Handshake {
}
impl Handshake {
pub fn from_password(pw: &String) -> (Handshake, spake2::Spake2<spake2::Ed25519Group>) {
let password = Bytes::from(pw.to_string());
pub fn from_password(pw: &String) -> Result<(Handshake, spake2::Spake2<spake2::Ed25519Group>)> {
let password = Bytes::from(pw.clone());
let id = Handshake::pass_to_bytes(&pw);
let (s1, outbound_msg) =
Spake2::<Ed25519Group>::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()
}
}

View file

@ -6,6 +6,7 @@ mod crypto;
mod file;
mod handshake;
mod message;
mod password;
mod server;
mod ui;

24
src/password.rs Normal file
View file

@ -0,0 +1,24 @@
use crate::conf::PASSWORD_LEN;
use anyhow::{anyhow, Result};
use names::{Generator, Name};
pub fn validate_generate_pw(pw: Option<String>) -> Result<String> {
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(),
}
}