diff --git a/Cargo.lock b/Cargo.lock index cb35c98..482e565 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,7 +189,6 @@ dependencies = [ "mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 7d75208..d7ae975 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ lazy_static = "1.0" mime_guess = "2.0.0-alpha.2" open = "1" openssl = "0.10" -rand = "0.4" reqwest = "0.8" serde = "1.0" serde_derive = "1.0" diff --git a/src/action/upload.rs b/src/action/upload.rs index bd1bbfa..efe9531 100644 --- a/src/action/upload.rs +++ b/src/action/upload.rs @@ -1,3 +1,5 @@ +use super::super::url::Url; + use super::super::send::file::File; /// The response from the server after a file has been uploaded. @@ -25,7 +27,7 @@ impl UploadResponse { /// Convert this response into a file object. /// /// The `host` and `secret` must be given. - pub fn into_file(self, host: String, secret: Vec) -> File { + pub fn into_file(self, host: Url, secret: Vec) -> File { File::new_now( self.id, host, diff --git a/src/crypto.rs b/src/crypto.rs index 06ee32f..181a218 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,6 +4,9 @@ extern crate sha2; use self::hkdf::Hkdf; use self::sha2::Sha256; +// Reexport the cryptographically secure random bytes generator +pub use super::openssl::rand::rand_bytes; + /// Derive a HKDF key. /// /// No _salt_ bytes are used in this function. diff --git a/src/main.rs b/src/main.rs index a505b9d..60d4da8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ extern crate lazy_static; extern crate mime_guess; extern crate open; extern crate openssl; -extern crate rand; extern crate reqwest; #[macro_use] extern crate serde_derive; @@ -24,7 +23,6 @@ use std::io::BufReader; use std::path::Path; use openssl::symm::{Cipher, encrypt_aead}; -use rand::{Rng, thread_rng}; use reqwest::header::Authorization; use reqwest::mime::APPLICATION_OCTET_STREAM; use reqwest::multipart::Part; @@ -32,9 +30,9 @@ use reqwest::multipart::Part; use action::upload::UploadResponse; use cmd::Handler; use cmd::cmd_upload::CmdUpload; -use crypto::{derive_auth_key, derive_file_key, derive_meta_key}; use metadata::{Metadata, XFileMetadata}; use reader::EncryptedFileReaderTagged; +use send::key_set::KeySet; /// Application entrypoint. fn main() { @@ -79,22 +77,14 @@ fn action_upload(cmd_upload: &CmdUpload) { // Create a new reqwest client let client = reqwest::Client::new(); - // Generate a secret and iv - let mut secret = [0u8; 16]; - let mut iv = [0u8; 12]; - thread_rng().fill_bytes(&mut secret); - thread_rng().fill_bytes(&mut iv); - - // Derive keys - let encrypt_key = derive_file_key(&secret); - let auth_key = derive_auth_key(&secret, None, None); - let meta_key = derive_meta_key(&secret); + // Generate a key + let key = KeySet::generate(true); // Guess the mimetype of the file let file_mime = mime_guess::get_mime_type(file_ext); // Construct the metadata - let metadata = Metadata::from(&iv, file_name.clone(), file_mime); + let metadata = Metadata::from(key.iv(), file_name.clone(), file_mime); // Convert the metadata to JSON bytes let metadata = metadata.to_json().into_bytes(); @@ -106,7 +96,7 @@ fn action_upload(cmd_upload: &CmdUpload) { let mut metadata_tag = vec![0u8; 16]; let mut metadata = encrypt_aead( cipher, - &meta_key, + key.meta_key().unwrap(), Some(&[0u8; 12]), &[], &metadata, @@ -119,8 +109,8 @@ fn action_upload(cmd_upload: &CmdUpload) { let reader = EncryptedFileReaderTagged::new( file, cipher, - &encrypt_key, - &iv, + key.file_key().unwrap(), + key.iv(), ).unwrap(); // Buffer the encrypted reader, and determine the length @@ -138,7 +128,7 @@ fn action_upload(cmd_upload: &CmdUpload) { // TODO: properly format an URL here let url = host.join("api/upload").expect("invalid host"); let mut res = client.post(url.as_str()) - .header(Authorization(format!("send-v1 {}", b64::encode(&auth_key)))) + .header(Authorization(format!("send-v1 {}", key.auth_key_encoded().unwrap()))) .header(XFileMetadata::from(&metadata)) .multipart(form) .send() @@ -148,10 +138,10 @@ fn action_upload(cmd_upload: &CmdUpload) { let upload_res: UploadResponse = res.json().unwrap(); // Print the response - let file = upload_res.into_file(host.into_string(), secret.to_vec()); + let file = upload_res.into_file(host, key.secret().to_vec()); let url = file.download_url(); println!("File: {:#?}", file); - println!("Secret key: {}", b64::encode(&secret)); + println!("Secret key: {}", key.secret_encoded()); println!("Download URL: {}", url); // Open the URL in the browser diff --git a/src/send/file.rs b/src/send/file.rs index 323572a..846bc59 100644 --- a/src/send/file.rs +++ b/src/send/file.rs @@ -1,6 +1,7 @@ extern crate chrono; use self::chrono::{DateTime, Utc}; +use super::super::url::Url; use super::super::b64; @@ -17,7 +18,7 @@ pub struct File { time: DateTime, /// The host the file was uploaded to. - host: String, + host: Url, /// The file URL that was provided by the server. url: String, @@ -34,7 +35,7 @@ impl File { pub fn new( id: String, time: DateTime, - host: String, + host: Url, url: String, secret: Vec, owner_key: String, @@ -52,7 +53,7 @@ impl File { /// Construct a new file, that was created at this exact time. pub fn new_now( id: String, - host: String, + host: Url, url: String, secret: Vec, owner_key: String, diff --git a/src/send/key_set.rs b/src/send/key_set.rs new file mode 100644 index 0000000..0876836 --- /dev/null +++ b/src/send/key_set.rs @@ -0,0 +1,101 @@ +use b64; +use crypto::{derive_auth_key, derive_file_key, derive_meta_key, rand_bytes}; + +pub struct KeySet { + /// A secret. + secret: [u8; 16], + + /// Input vector. + iv: [u8; 12], + + /// A derived file encryption key. + file_key: Option>, + + /// A derived authentication key. + auth_key: Option>, + + /// A derived metadata key. + meta_key: Option>, +} + +impl KeySet { + /// Construct a new key, with the given `secret` and `iv`. + pub fn new(secret: [u8; 16], iv: [u8; 12]) -> Self { + Self { + secret, + iv, + file_key: None, + auth_key: None, + meta_key: None, + } + } + + /// Generate a secure new key. + /// + /// If `derive` is `true`, file, authentication and metadata keys will be + /// derived from the generated secret. + pub fn generate(derive: bool) -> Self { + // Allocate two keys + let mut secret = [0u8; 16]; + let mut iv = [0u8; 12]; + + // Generate the secrets + rand_bytes(&mut secret) + .expect("failed to generate crypto secure random secret"); + rand_bytes(&mut iv) + .expect("failed to generate crypto secure random input vector"); + + // Create the key + let mut key = Self::new(secret, iv); + + // Derive + if derive { + key.derive(); + } + + key + } + + /// Derive a file, authentication and metadata key. + pub fn derive(&mut self) { + self.file_key = Some(derive_file_key(&self.secret)); + self.auth_key = Some(derive_auth_key(&self.secret, None, None)); + self.meta_key = Some(derive_meta_key(&self.secret)); + } + + /// Get the secret key. + pub fn secret(&self) -> &[u8] { + &self.secret + } + + /// Get the secret key as URL-safe base64 encoded string. + pub fn secret_encoded(&self) -> String { + b64::encode(self.secret()) + } + + /// Get the input vector. + pub fn iv(&self) -> &[u8] { + &self.iv + } + + /// Get the file encryption key, if derived. + pub fn file_key(&self) -> Option<&Vec> { + self.file_key.as_ref() + } + + /// Get the authentication encryption key, if derived. + pub fn auth_key(&self) -> Option<&Vec> { + self.auth_key.as_ref() + } + + /// Get the authentication encryption key, if derived, + /// as URL-safe base64 encoded string. + pub fn auth_key_encoded(&self) -> Option { + self.auth_key().map(|key| b64::encode(key)) + } + + /// Get the metadata encryption key, if derived. + pub fn meta_key(&self) -> Option<&Vec> { + self.meta_key.as_ref() + } +} diff --git a/src/send/mod.rs b/src/send/mod.rs index e62a6df..9c5096c 100644 --- a/src/send/mod.rs +++ b/src/send/mod.rs @@ -4,3 +4,4 @@ //! to and from a secure Firefox Send server. pub mod file; +pub mod key_set;