mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-03 09:39:15 +02:00
Extract crypto keys to a key set module
This commit is contained in:
parent
8ffea0410d
commit
a4ccf395b9
8 changed files with 122 additions and 26 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -189,7 +189,6 @@ dependencies = [
|
||||||
"mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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 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)",
|
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -13,7 +13,6 @@ lazy_static = "1.0"
|
||||||
mime_guess = "2.0.0-alpha.2"
|
mime_guess = "2.0.0-alpha.2"
|
||||||
open = "1"
|
open = "1"
|
||||||
openssl = "0.10"
|
openssl = "0.10"
|
||||||
rand = "0.4"
|
|
||||||
reqwest = "0.8"
|
reqwest = "0.8"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use super::super::url::Url;
|
||||||
|
|
||||||
use super::super::send::file::File;
|
use super::super::send::file::File;
|
||||||
|
|
||||||
/// The response from the server after a file has been uploaded.
|
/// The response from the server after a file has been uploaded.
|
||||||
|
@ -25,7 +27,7 @@ impl UploadResponse {
|
||||||
/// Convert this response into a file object.
|
/// Convert this response into a file object.
|
||||||
///
|
///
|
||||||
/// The `host` and `secret` must be given.
|
/// The `host` and `secret` must be given.
|
||||||
pub fn into_file(self, host: String, secret: Vec<u8>) -> File {
|
pub fn into_file(self, host: Url, secret: Vec<u8>) -> File {
|
||||||
File::new_now(
|
File::new_now(
|
||||||
self.id,
|
self.id,
|
||||||
host,
|
host,
|
||||||
|
|
|
@ -4,6 +4,9 @@ extern crate sha2;
|
||||||
use self::hkdf::Hkdf;
|
use self::hkdf::Hkdf;
|
||||||
use self::sha2::Sha256;
|
use self::sha2::Sha256;
|
||||||
|
|
||||||
|
// Reexport the cryptographically secure random bytes generator
|
||||||
|
pub use super::openssl::rand::rand_bytes;
|
||||||
|
|
||||||
/// Derive a HKDF key.
|
/// Derive a HKDF key.
|
||||||
///
|
///
|
||||||
/// No _salt_ bytes are used in this function.
|
/// No _salt_ bytes are used in this function.
|
||||||
|
|
30
src/main.rs
30
src/main.rs
|
@ -3,7 +3,6 @@ extern crate lazy_static;
|
||||||
extern crate mime_guess;
|
extern crate mime_guess;
|
||||||
extern crate open;
|
extern crate open;
|
||||||
extern crate openssl;
|
extern crate openssl;
|
||||||
extern crate rand;
|
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
@ -24,7 +23,6 @@ use std::io::BufReader;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use openssl::symm::{Cipher, encrypt_aead};
|
use openssl::symm::{Cipher, encrypt_aead};
|
||||||
use rand::{Rng, thread_rng};
|
|
||||||
use reqwest::header::Authorization;
|
use reqwest::header::Authorization;
|
||||||
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
||||||
use reqwest::multipart::Part;
|
use reqwest::multipart::Part;
|
||||||
|
@ -32,9 +30,9 @@ use reqwest::multipart::Part;
|
||||||
use action::upload::UploadResponse;
|
use action::upload::UploadResponse;
|
||||||
use cmd::Handler;
|
use cmd::Handler;
|
||||||
use cmd::cmd_upload::CmdUpload;
|
use cmd::cmd_upload::CmdUpload;
|
||||||
use crypto::{derive_auth_key, derive_file_key, derive_meta_key};
|
|
||||||
use metadata::{Metadata, XFileMetadata};
|
use metadata::{Metadata, XFileMetadata};
|
||||||
use reader::EncryptedFileReaderTagged;
|
use reader::EncryptedFileReaderTagged;
|
||||||
|
use send::key_set::KeySet;
|
||||||
|
|
||||||
/// Application entrypoint.
|
/// Application entrypoint.
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -79,22 +77,14 @@ fn action_upload(cmd_upload: &CmdUpload) {
|
||||||
// Create a new reqwest client
|
// Create a new reqwest client
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
// Generate a secret and iv
|
// Generate a key
|
||||||
let mut secret = [0u8; 16];
|
let key = KeySet::generate(true);
|
||||||
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);
|
|
||||||
|
|
||||||
// Guess the mimetype of the file
|
// Guess the mimetype of the file
|
||||||
let file_mime = mime_guess::get_mime_type(file_ext);
|
let file_mime = mime_guess::get_mime_type(file_ext);
|
||||||
|
|
||||||
// Construct the metadata
|
// 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
|
// Convert the metadata to JSON bytes
|
||||||
let metadata = metadata.to_json().into_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_tag = vec![0u8; 16];
|
||||||
let mut metadata = encrypt_aead(
|
let mut metadata = encrypt_aead(
|
||||||
cipher,
|
cipher,
|
||||||
&meta_key,
|
key.meta_key().unwrap(),
|
||||||
Some(&[0u8; 12]),
|
Some(&[0u8; 12]),
|
||||||
&[],
|
&[],
|
||||||
&metadata,
|
&metadata,
|
||||||
|
@ -119,8 +109,8 @@ fn action_upload(cmd_upload: &CmdUpload) {
|
||||||
let reader = EncryptedFileReaderTagged::new(
|
let reader = EncryptedFileReaderTagged::new(
|
||||||
file,
|
file,
|
||||||
cipher,
|
cipher,
|
||||||
&encrypt_key,
|
key.file_key().unwrap(),
|
||||||
&iv,
|
key.iv(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
// Buffer the encrypted reader, and determine the length
|
// Buffer the encrypted reader, and determine the length
|
||||||
|
@ -138,7 +128,7 @@ fn action_upload(cmd_upload: &CmdUpload) {
|
||||||
// TODO: properly format an URL here
|
// TODO: properly format an URL here
|
||||||
let url = host.join("api/upload").expect("invalid host");
|
let url = host.join("api/upload").expect("invalid host");
|
||||||
let mut res = client.post(url.as_str())
|
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))
|
.header(XFileMetadata::from(&metadata))
|
||||||
.multipart(form)
|
.multipart(form)
|
||||||
.send()
|
.send()
|
||||||
|
@ -148,10 +138,10 @@ fn action_upload(cmd_upload: &CmdUpload) {
|
||||||
let upload_res: UploadResponse = res.json().unwrap();
|
let upload_res: UploadResponse = res.json().unwrap();
|
||||||
|
|
||||||
// Print the response
|
// 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();
|
let url = file.download_url();
|
||||||
println!("File: {:#?}", file);
|
println!("File: {:#?}", file);
|
||||||
println!("Secret key: {}", b64::encode(&secret));
|
println!("Secret key: {}", key.secret_encoded());
|
||||||
println!("Download URL: {}", url);
|
println!("Download URL: {}", url);
|
||||||
|
|
||||||
// Open the URL in the browser
|
// Open the URL in the browser
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
|
|
||||||
use self::chrono::{DateTime, Utc};
|
use self::chrono::{DateTime, Utc};
|
||||||
|
use super::super::url::Url;
|
||||||
|
|
||||||
use super::super::b64;
|
use super::super::b64;
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ pub struct File {
|
||||||
time: DateTime<Utc>,
|
time: DateTime<Utc>,
|
||||||
|
|
||||||
/// The host the file was uploaded to.
|
/// The host the file was uploaded to.
|
||||||
host: String,
|
host: Url,
|
||||||
|
|
||||||
/// The file URL that was provided by the server.
|
/// The file URL that was provided by the server.
|
||||||
url: String,
|
url: String,
|
||||||
|
@ -34,7 +35,7 @@ impl File {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: String,
|
id: String,
|
||||||
time: DateTime<Utc>,
|
time: DateTime<Utc>,
|
||||||
host: String,
|
host: Url,
|
||||||
url: String,
|
url: String,
|
||||||
secret: Vec<u8>,
|
secret: Vec<u8>,
|
||||||
owner_key: String,
|
owner_key: String,
|
||||||
|
@ -52,7 +53,7 @@ impl File {
|
||||||
/// Construct a new file, that was created at this exact time.
|
/// Construct a new file, that was created at this exact time.
|
||||||
pub fn new_now(
|
pub fn new_now(
|
||||||
id: String,
|
id: String,
|
||||||
host: String,
|
host: Url,
|
||||||
url: String,
|
url: String,
|
||||||
secret: Vec<u8>,
|
secret: Vec<u8>,
|
||||||
owner_key: String,
|
owner_key: String,
|
||||||
|
|
101
src/send/key_set.rs
Normal file
101
src/send/key_set.rs
Normal file
|
@ -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<Vec<u8>>,
|
||||||
|
|
||||||
|
/// A derived authentication key.
|
||||||
|
auth_key: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// A derived metadata key.
|
||||||
|
meta_key: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<u8>> {
|
||||||
|
self.file_key.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the authentication encryption key, if derived.
|
||||||
|
pub fn auth_key(&self) -> Option<&Vec<u8>> {
|
||||||
|
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<String> {
|
||||||
|
self.auth_key().map(|key| b64::encode(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the metadata encryption key, if derived.
|
||||||
|
pub fn meta_key(&self) -> Option<&Vec<u8>> {
|
||||||
|
self.meta_key.as_ref()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,4 @@
|
||||||
//! to and from a secure Firefox Send server.
|
//! to and from a secure Firefox Send server.
|
||||||
|
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
pub mod key_set;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue