mirror of
https://github.com/CompeyDev/ruck.git
synced 2025-01-07 11:29:10 +00:00
Auto generate passwords; fix resume
This commit is contained in:
parent
09a67f9944
commit
5d2a216693
11 changed files with 101 additions and 40 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -131,16 +131,16 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.0.14"
|
version = "3.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62"
|
checksum = "8e538f9ee5aa3b3963f09a997035f883677966ed50fce0292611927ce6f6d8c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
"clap_lex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"os_str_bytes",
|
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
|
@ -148,9 +148,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "3.0.14"
|
version = "3.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85"
|
checksum = "a7f98063cac4652f23ccda556b8d04347a7fc4b2cff1f7577cc8c6546e0d8078"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
|
@ -159,6 +159,15 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -495,6 +504,16 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "names"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -531,9 +550,6 @@ name = "os_str_bytes"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
|
@ -702,6 +718,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
|
"names",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"spake2",
|
"spake2",
|
||||||
|
@ -813,9 +830,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.14.2"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "ruck"
|
name = "ruck"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "A croc-inspired tool for hosting relay servers and sending e2e encrypted files."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ bincode = "1.3.3"
|
||||||
clap = { version = "3.0.14", features = ["derive"] }
|
clap = { version = "3.0.14", features = ["derive"] }
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
futures = { version = "0.3.0", features = ["thread-pool"]}
|
futures = { version = "0.3.0", features = ["thread-pool"]}
|
||||||
|
names = "0.14.0"
|
||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
spake2 = "0.3.1"
|
spake2 = "0.3.1"
|
||||||
|
|
21
README.md
21
README.md
|
@ -4,15 +4,22 @@
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```
|
||||||
## tab 1
|
ruck 0.1.0
|
||||||
cargo run relay # this starts the server
|
A croc-inspired tool for hosting relay servers and sending e2e encrypted files.
|
||||||
|
|
||||||
## tab 2
|
USAGE:
|
||||||
cargo run send /path/to/file1.md /path/to/file2.md supersecretpassword
|
ruck <SUBCOMMAND>
|
||||||
|
|
||||||
## tab 3
|
OPTIONS:
|
||||||
cargo run receive supersecretpassword
|
-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
|
## Protocol
|
||||||
|
|
22
src/cli.rs
22
src/cli.rs
|
@ -1,27 +1,33 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{AppSettings, Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
/// A fictional versioning CLI
|
/// A fictional versioning CLI
|
||||||
#[derive(Parser)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(name = "ruck")]
|
#[clap(name = "ruck")]
|
||||||
#[clap(about = "Croc in rust", long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
#[clap(setting(AppSettings::ArgRequiredElseHelp))]
|
/// Send file(s). Can provide optional password
|
||||||
Send {
|
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>,
|
paths: Vec<PathBuf>,
|
||||||
password: String,
|
|
||||||
},
|
},
|
||||||
#[clap(setting(AppSettings::ArgRequiredElseHelp))]
|
/// Receive file(s). Must provide password shared out of band
|
||||||
Receive {
|
Receive {
|
||||||
|
/// Password shared out
|
||||||
|
#[clap(value_parser, required = true)]
|
||||||
password: String,
|
password: String,
|
||||||
},
|
},
|
||||||
|
/// Start relay server
|
||||||
Relay {},
|
Relay {},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,29 @@ use crate::connection::Connection;
|
||||||
use crate::file::{ChunkHeader, FileHandle, FileOffer, StdFileHandle};
|
use crate::file::{ChunkHeader, FileHandle, FileOffer, StdFileHandle};
|
||||||
use crate::handshake::Handshake;
|
use crate::handshake::Handshake;
|
||||||
use crate::message::{FileOfferPayload, FileRequestPayload, Message};
|
use crate::message::{FileOfferPayload, FileRequestPayload, Message};
|
||||||
|
use crate::password::validate_generate_pw;
|
||||||
use crate::ui::prompt_user_for_file_confirmation;
|
use crate::ui::prompt_user_for_file_confirmation;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::fs::File;
|
use tokio::fs::{File, OpenOptions};
|
||||||
|
|
||||||
use tokio::net::TcpStream;
|
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
|
// Fail early if there are problems generating file handles
|
||||||
let handles = FileHandle::get_file_handles(file_paths).await?;
|
let handles = FileHandle::get_file_handles(file_paths).await?;
|
||||||
|
|
||||||
// Establish connection to server
|
// Establish connection to server
|
||||||
let socket = TcpStream::connect("127.0.0.1:8080").await?;
|
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
|
// Complete handshake, returning key used for encryption
|
||||||
let (socket, key) = handshake.negotiate(socket, s1).await?;
|
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<()> {
|
pub async fn receive(password: &String) -> Result<()> {
|
||||||
// Establish connection to server
|
// Establish connection to server
|
||||||
let socket = TcpStream::connect("127.0.0.1:8080").await?;
|
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
|
// Complete handshake, returning key used for encryption
|
||||||
let (socket, key) = handshake.negotiate(socket, s1).await?;
|
let (socket, key) = handshake.negotiate(socket, s1).await?;
|
||||||
let mut connection = Connection::new(socket, key);
|
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();
|
let mut v = Vec::new();
|
||||||
for desired_file in desired_files {
|
for desired_file in desired_files {
|
||||||
let filename = desired_file.path;
|
let filename = desired_file.path;
|
||||||
// filename.push_str(".part");
|
let file = match OpenOptions::new().append(true).open(&filename).await {
|
||||||
let file = match File::open(filename.clone()).await {
|
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
println!(
|
println!(
|
||||||
"File {:?} already exists. Attempting to resume download.",
|
"File {:?} already exists. Attempting to resume download.",
|
||||||
|
|
|
@ -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 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 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 NONCE_SIZE: usize = 96 / 8; // used for every encrypted message
|
||||||
|
pub const PASSWORD_LEN: usize = 12; // min size of password
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl Connection {
|
||||||
let elapsed = before.elapsed();
|
let elapsed = before.elapsed();
|
||||||
let mb_sent = bytes_sent / 1_048_576;
|
let mb_sent = bytes_sent / 1_048_576;
|
||||||
println!(
|
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,
|
handle.name,
|
||||||
mb_sent,
|
mb_sent,
|
||||||
count,
|
count,
|
||||||
|
|
|
@ -82,7 +82,7 @@ impl FileHandle {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
println!("Skipping file b/c not in requested chunks");
|
println!("Skipping {:?} b/c not in requested chunks.", handle.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ pub struct Handshake {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handshake {
|
impl Handshake {
|
||||||
pub fn from_password(pw: &String) -> (Handshake, spake2::Spake2<spake2::Ed25519Group>) {
|
pub fn from_password(pw: &String) -> Result<(Handshake, spake2::Spake2<spake2::Ed25519Group>)> {
|
||||||
let password = Bytes::from(pw.to_string());
|
let password = Bytes::from(pw.clone());
|
||||||
let id = Handshake::pass_to_bytes(&pw);
|
let id = Handshake::pass_to_bytes(&pw);
|
||||||
let (s1, outbound_msg) =
|
let (s1, outbound_msg) =
|
||||||
Spake2::<Ed25519Group>::start_symmetric(&Password::new(&password), &Identity::new(&id));
|
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]);
|
buffer.extend_from_slice(&outbound_msg[..HANDSHAKE_MSG_SIZE]);
|
||||||
let outbound_msg = buffer.freeze();
|
let outbound_msg = buffer.freeze();
|
||||||
let handshake = Handshake { id, outbound_msg };
|
let handshake = Handshake { id, outbound_msg };
|
||||||
(handshake, s1)
|
Ok((handshake, s1))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_socket(socket: TcpStream) -> Result<(Handshake, TcpStream)> {
|
pub async fn from_socket(socket: TcpStream) -> Result<(Handshake, TcpStream)> {
|
||||||
|
@ -64,9 +64,7 @@ impl Handshake {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pass_to_bytes(password: &String) -> Bytes {
|
fn pass_to_bytes(password: &String) -> Bytes {
|
||||||
let mut hasher = Blake2s256::new();
|
let bytes = Blake2s256::digest(password.as_bytes());
|
||||||
hasher.update(password.as_bytes());
|
BytesMut::from(&bytes[..]).freeze()
|
||||||
let res = hasher.finalize();
|
|
||||||
BytesMut::from(&res[..]).freeze()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ mod crypto;
|
||||||
mod file;
|
mod file;
|
||||||
mod handshake;
|
mod handshake;
|
||||||
mod message;
|
mod message;
|
||||||
|
mod password;
|
||||||
mod server;
|
mod server;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
|
24
src/password.rs
Normal file
24
src/password.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue