Implement upload request data building and encrypt file
This commit is contained in:
parent
158ef6fad7
commit
5d45cce720
3 changed files with 137 additions and 5 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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)",
|
||||
|
|
|
@ -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"
|
||||
|
|
140
src/main.rs
140
src/main.rs
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue