diff options
-rw-r--r-- | Cargo.lock | 26 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | src/client.rs | 11 | ||||
-rw-r--r-- | src/crypto.rs | 78 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/server.rs | 13 |
7 files changed, 141 insertions, 10 deletions
@@ -4,6 +4,8 @@ version = "0.1.0" dependencies = [ "daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "sodiumoxide 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "utp 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -62,6 +64,15 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "libsodium-sys" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "log" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -150,6 +161,11 @@ version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "pkg-config" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "rand" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -180,6 +196,16 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "sodiumoxide" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libsodium-sys 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "thread-id" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4,6 +4,8 @@ version = "0.1.0" authors = ["bnewbold <bnewbold@robocracy.org>"] [dependencies] -getopts = "0.2" -daemonize = "0.2" +getopts = "^0.2" +daemonize = "^0.2" utp = "*" +sodiumoxide = "*" +rustc-serialize = "0.3" @@ -17,6 +17,19 @@ that. There is a bunch of overhead sending small files, so if you have a lot of those and a high-latency link you should probably `tar` things up first. There also isn't any compression, so you might want to `gzip` that tarball. +### Dependencies + +You need the Sodium (wrapper for NaCl) library and headers installed. On Linux, +this is, eg, libsodium-dev. + +Uses sodiumoxide instead of rust-crypto because there aren't online docs for +rust-crypto, and AFAIK Sodium and NaCl have been reviewed and rust-crypto has +not. + +Uses rustc-serialize instead of serde, because serde seems more complicated +(both nightly/compiler API and a non-macro API?) and doesn't seem to support +base64. + ### Usage The command must be installed on both the local and remote machines. diff --git a/src/client.rs b/src/client.rs index 8827a11..1458998 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,4 @@ -extern crate getopts; -extern crate utp; - use super::common; use std::string::String; @@ -11,6 +8,8 @@ use std::process::exit; use std::process::Command; use getopts::Options; use utp::{UtpSocket, UtpStream}; +use crypto::{SecretStream, key2string, string2key}; +use sodiumoxide::crypto::secretbox; pub fn run_client(host: &str, local_file: &str, remote_file: &str, remote_is_dir: bool, is_recv: bool) { println!("\thost: {}", host); @@ -53,12 +52,16 @@ pub fn run_client(host: &str, local_file: &str, remote_file: &str, remote_is_dir let mut socket = UtpSocket::connect((remote_host, remote_port)).unwrap();; let mut stream: UtpStream = socket.into(); + let mut stream = SecretStream::new(stream); + stream.key = string2key(remote_secret).unwrap(); + if is_recv { common::sink_files(&mut stream, local_file, remote_is_dir); } else { common::source_files(&mut stream, local_file, remote_is_dir); } - stream.close().unwrap(); + // XXX: does Drop do this well enough? + //stream.close().unwrap(); } fn usage_client(opts: Options) { diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..726a420 --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,78 @@ + +use std::{u8, u32}; +use std::io; +use std::io::{Read,Write, ErrorKind}; +use sodiumoxide::crypto::secretbox; +use sodiumoxide::crypto::secretbox::{Key, Nonce}; +use rustc_serialize::base64::{ToBase64, FromBase64, STANDARD}; +use std::mem::transmute; + +// TODO: handle case of splitting up writes > 2^32 bytes into multiple small writes + +pub struct SecretStream<S: Read+Write> { + read_nonce: Nonce, + write_nonce: Nonce, + pub key: Key, + inner: S, +} + +impl<S: Read+Write> SecretStream<S> { + pub fn new(stream: S) -> SecretStream<S> { + SecretStream { + inner: stream, + read_nonce: secretbox::gen_nonce(), + write_nonce: secretbox::gen_nonce(), + key: secretbox::gen_key(), + } + } +} + +impl<S: Read+Write> Read for SecretStream<S> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + let mut header_buf = [0; 4]; + try!(self.inner.read_exact(&mut header_buf)); + let len: u32 = unsafe { transmute(header_buf) }; + let len = len.to_be(); + if len as usize > buf.len() { + return Err(io::Error::new(ErrorKind::Other, + format!("Buffer not big enough ({} < {})", buf.len(), len))); + } + try!(self.inner.read_exact(buf)); + let cleartext = match secretbox::open(buf, &self.read_nonce, &self.key) { + Ok(cleartext) => cleartext, + Err(_) => { return Err(io::Error::new(ErrorKind::InvalidData, + "Failed to decrypt message (could mean corruption or malicious attack"))}, + }; + self.read_nonce.increment_le_inplace(); + let len = len as usize; + buf.clone_from_slice(&cleartext[..len]); + return Ok(len as usize); + } +} + +impl<S: Read+Write> Write for SecretStream<S> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + assert!(buf.len() < u32::MAX as usize); + let len = buf.len() as u32; + let header_buf: [u8; 4] = unsafe { transmute(len.to_be()) }; + try!(self.inner.write_all(&header_buf)); + let ciphertext = secretbox::seal(buf, &self.write_nonce, &self.key); + self.write_nonce.increment_le_inplace(); + try!(self.inner.write_all(&ciphertext[..])); + return Ok(len as usize); + } + + fn flush(&mut self) -> io::Result<()> { + return self.inner.flush(); + } +} + +pub fn key2string(key: &Key) -> String { + return (&(key[..])).to_base64(STANDARD); +} + +pub fn string2key(s: &str) -> Result<Key, String> { + println!("KEYBYTES: {}", secretbox::KEYBYTES); + return Ok(Key::from_slice(&s.as_bytes().from_base64().unwrap()).unwrap()); +} + diff --git a/src/main.rs b/src/main.rs index b5a9123..a1002c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,13 @@ extern crate getopts; extern crate utp; +extern crate sodiumoxide; +extern crate rustc_serialize; mod client; mod server; mod common; +mod crypto; use std::str; use std::env; @@ -23,6 +26,7 @@ fn usage(opts: Options) { fn main() { let args: Vec<String> = env::args().collect(); + sodiumoxide::init(); // First check for "hidden" server and client modes if args.len() > 1 && args[1] == "server" { diff --git a/src/server.rs b/src/server.rs index 3c79984..62d4b19 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,4 @@ -extern crate getopts; -extern crate utp; extern crate daemonize; use super::common; @@ -11,6 +9,8 @@ use std::env::home_dir; use std::process::exit; use getopts::Options; use utp::{UtpSocket, UtpStream, UtpListener}; +use crypto::{SecretStream, key2string, string2key}; +use sodiumoxide::crypto::secretbox; fn run_server(path: &str, is_recv: bool, recursive: bool, daemonize: bool) { @@ -26,8 +26,10 @@ fn run_server(path: &str, is_recv: bool, recursive: bool, daemonize: bool) { let listen_port = listener.local_addr().unwrap().port(); let listen_addr = listener.local_addr().unwrap().ip(); + let secret_key = secretbox::gen_key(); + // Send back details so client can connect - println!("UCP CONNECT {} {} {}", listen_addr, listen_port, "<SECRET>"); + println!("UCP CONNECT {} {} {}", listen_addr, listen_port, key2string(&secret_key)); // TODO: maybe wait for an ACK of some sort here before daemonizing? @@ -52,13 +54,16 @@ fn run_server(path: &str, is_recv: bool, recursive: bool, daemonize: bool) { let (mut socket, _src) = listener.accept().unwrap(); println!("Got connection from {}", socket.peer_addr().unwrap()); let mut stream: UtpStream = socket.into(); + let mut stream = SecretStream::new(stream); + stream.key = secret_key; if is_recv { common::sink_files(&mut stream, path, recursive); } else { common::source_files(&mut stream, path, recursive); } - stream.close().unwrap(); + // XXX: does Drop do this well enough? + //stream.close().unwrap(); } fn usage_server(opts: Options) { |