Implement upload request data building and encrypt file

This commit is contained in:
timvisee 2018-03-05 17:38:46 +01:00
parent 158ef6fad7
commit 5d45cce720
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
3 changed files with 137 additions and 5 deletions

1
Cargo.lock generated
View file

@ -94,6 +94,7 @@ name = "ffsend"
version = "0.1.0"
dependencies = [
"base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.11.21 (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)",
"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)",

View file

@ -5,6 +5,7 @@ authors = ["timvisee <timvisee@gmail.com>"]
[dependencies]
base64 = "0.9"
hyper = "0.11.9" # same as reqwest
mime_guess = "2.0.0-alpha.2"
rand = "0.4"
reqwest = "0.8"

View file

@ -1,5 +1,6 @@
extern crate base64;
extern crate crypto;
extern crate hyper;
extern crate mime_guess;
extern crate rand;
extern crate reqwest;
@ -7,7 +8,11 @@ extern crate reqwest;
extern crate serde_derive;
extern crate serde_json;
use std::fmt;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::Path;
use std::sync::{Arc, Mutex};
use crypto::aead::AeadEncryptor;
use crypto::aes::KeySize;
@ -15,8 +20,17 @@ use crypto::aes_gcm::AesGcm;
use crypto::digest::Digest;
use crypto::hkdf::{hkdf_extract, hkdf_expand};
use crypto::sha2::Sha256;
use hyper::error::Error as HyperError;
use mime_guess::Mime;
use rand::{Rng, thread_rng};
use reqwest::header::{
Authorization,
Formatter as HeaderFormatter,
Header,
Raw
};
use reqwest::mime::APPLICATION_OCTET_STREAM;
use reqwest::multipart::Part;
fn main() {
// TODO: a fixed path for now, as upload test
@ -40,14 +54,14 @@ fn main() {
// Generate a file and meta cipher
// TODO: use the proper key size here, and the proper aad
let mut file_cipher = AesGcm::new(KeySize::KeySize128, &encrypt_key, &iv, b"");
let file_cipher = AesGcm::new(KeySize::KeySize128, &encrypt_key, &iv, b"");
let mut meta_cipher = AesGcm::new(KeySize::KeySize128, &meta_key, &[0u8; 12], b"");
// 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, file_mime);
let metadata = Metadata::from(&iv, file_name.clone(), file_mime);
// Encrypt the metadata, append the tag
let metadata = metadata.to_json().into_bytes();
@ -56,11 +70,21 @@ fn main() {
meta_cipher.encrypt(&metadata, &mut metadata_encrypted, &mut metadata_tag);
metadata_encrypted.append(&mut metadata_tag);
let form = reqwest::multipart::Form::new()
.file("data", path)
.unwrap();
// Open the file and create an encrypted file reader
let file = File::open(path).unwrap();
let reader = EncryptedFileReaderTagged::new(file, file_cipher);
// Build the file part, configure the form to send
let part = Part::reader(reader)
.file_name(file_name)
.mime(APPLICATION_OCTET_STREAM);
let form = reqwest::multipart::Form::new()
.part("data", part);
// Make the request
let mut res = client.post("http://localhost:8080/api/upload")
.header(Authorization(format!("send-v1 {}", base64::encode(&auth_key))))
.header(XFileMetadata::from(&metadata_encrypted))
.multipart(form)
.send()
.unwrap();
@ -71,6 +95,112 @@ fn main() {
println!("TEXT: {}", text);
}
const TAG_LEN: usize = 16;
/// A file reader, that encrypts the file with the given cipher, and appends
/// the raw cipher tag.
///
/// This reader is lazy, and reads/encrypts the file on the fly.
struct EncryptedFileReaderTagged<'a> {
/// The file that is being read.
file: File,
/// The cipher to use.
cipher: Arc<Mutex<AesGcm<'a>>>,
/// The crypto tag.
tag: [u8; TAG_LEN],
/// A tag cursor, used as reader for the appended tag.
tag_cursor: Option<Cursor<Vec<u8>>>,
}
impl<'a: 'static> EncryptedFileReaderTagged<'a> {
/// Construct a new reader.
// TODO: try to borrow here
pub fn new(file: File, cipher: AesGcm<'a>) -> Self {
EncryptedFileReaderTagged {
file,
cipher: Arc::new(Mutex::new(cipher)),
tag: [0u8; TAG_LEN],
tag_cursor: None,
}
}
/// Get the length.
pub fn len(&self) -> Result<u64, io::Error> {
Ok(self.file.metadata()?.len() + TAG_LEN as u64)
}
}
impl<'a: 'static> Read for EncryptedFileReaderTagged<'a> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
// Create a buffer with the same size, for the raw data
let mut raw = vec![0u8; buf.len()];
// Read from the file
let len = self.file.read(&mut raw)?;
// Encrypt the read data
if len > 0 {
// Lock the cipher mutex
let mut cipher = self.cipher.lock().unwrap();
// Encrypt the raw slice, put it into the user buffer
println!("DEBUG: Tag (from): {:?}", self.tag);
cipher.encrypt(&raw[..len], buf, &mut self.tag);
println!("DEBUG: Tag (to): {:?}", self.tag);
Ok(len)
} else {
// Initialise the tag cursor
if self.tag_cursor.is_none() {
self.tag_cursor = Some(Cursor::new(self.tag.to_vec()));
}
// Read from the tag cursor
self.tag_cursor.as_mut().unwrap().read(buf)
}
}
}
// TODO: do not implement, make the reader send!
unsafe impl<'a: 'static> ::std::marker::Send for EncryptedFileReaderTagged<'a> {}
#[derive(Clone)]
struct XFileMetadata {
/// The metadata, as a base64 encoded string.
metadata: String,
}
impl XFileMetadata {
pub fn new(metadata: String) -> Self {
XFileMetadata {
metadata,
}
}
pub fn from(bytes: &[u8]) -> Self {
XFileMetadata::new(base64::encode(bytes))
}
}
impl Header for XFileMetadata {
fn header_name() -> &'static str {
"X-File-Metadata"
}
fn parse_header(raw: &Raw) -> Result<Self, HyperError> {
// TODO: implement this some time
unimplemented!();
}
fn fmt_header(&self, f: &mut HeaderFormatter) -> fmt::Result {
// TODO: is this encoding base64 for us?
f.fmt_line(&self.metadata)
}
}
#[derive(Serialize)]
struct Metadata {
/// The input vector