From 3a51fef71337f9e6683a3fe972e69cee92e1c097 Mon Sep 17 00:00:00 2001 From: bnewbold Date: Mon, 30 May 2016 01:49:18 -0400 Subject: BROKEN: initial implementation of crypto I think it's going to be necessary to implement buffered reading after all. --- Cargo.lock | 26 ++++++++++++++++++++ Cargo.toml | 6 +++-- README.md | 13 ++++++++++ src/client.rs | 11 ++++++--- src/crypto.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 +++ src/server.rs | 13 +++++++--- 7 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 src/crypto.rs diff --git a/Cargo.lock b/Cargo.lock index 65a7d65..9dbbb37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", ] @@ -61,6 +63,15 @@ name = "libc" 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" @@ -149,6 +160,11 @@ name = "num-traits" 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" @@ -179,6 +195,16 @@ name = "rustc-serialize" 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" diff --git a/Cargo.toml b/Cargo.toml index 183e38d..ad343e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" authors = ["bnewbold "] [dependencies] -getopts = "0.2" -daemonize = "0.2" +getopts = "^0.2" +daemonize = "^0.2" utp = "*" +sodiumoxide = "*" +rustc-serialize = "0.3" diff --git a/README.md b/README.md index dd4f753..fd59a5f 100644 --- a/README.md +++ b/README.md @@ -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 { + read_nonce: Nonce, + write_nonce: Nonce, + pub key: Key, + inner: S, +} + +impl SecretStream { + pub fn new(stream: S) -> SecretStream { + SecretStream { + inner: stream, + read_nonce: secretbox::gen_nonce(), + write_nonce: secretbox::gen_nonce(), + key: secretbox::gen_key(), + } + } +} + +impl Read for SecretStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + 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 Write for SecretStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + 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 { + 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 = 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, ""); + 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) { -- cgit v1.2.3