|
@@ -1,39 +1,30 @@
|
|
|
-use std::fs::File;
|
|
|
-use std::io::BufReader;
|
|
|
-use std::path::{Path, PathBuf};
|
|
|
-use std::sync::{Arc, Mutex};
|
|
|
+use std::path::Path;
|
|
|
|
|
|
use mime_guess::{get_mime_type, Mime};
|
|
|
-use openssl::symm::encrypt_aead;
|
|
|
+use openssl::symm::{decrypt_aead, encrypt_aead};
|
|
|
use reqwest::{
|
|
|
Client,
|
|
|
Error as ReqwestError,
|
|
|
- Request,
|
|
|
};
|
|
|
use reqwest::header::Authorization;
|
|
|
-use reqwest::mime::APPLICATION_OCTET_STREAM;
|
|
|
-use reqwest::multipart::{Form, Part};
|
|
|
-use url::Url;
|
|
|
+use serde_json;
|
|
|
|
|
|
+use crypto::b64;
|
|
|
use crypto::key_set::KeySet;
|
|
|
-use reader::{
|
|
|
- EncryptedFileReaderTagged,
|
|
|
- ExactLengthReader,
|
|
|
- ProgressReader,
|
|
|
- ProgressReporter,
|
|
|
-};
|
|
|
use file::file::DownloadFile;
|
|
|
-use file::metadata::{Metadata, XFileMetadata};
|
|
|
+use file::metadata::Metadata;
|
|
|
|
|
|
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
|
|
|
|
|
/// The name of the header that is used for the authentication nonce.
|
|
|
const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
|
|
|
|
|
|
+// TODO: experiment with `iv` of `None` in decrypt logic
|
|
|
+
|
|
|
/// A file upload action to a Send server.
|
|
|
pub struct Download<'a> {
|
|
|
/// The Send file to download.
|
|
|
- file: &DownloadFile,
|
|
|
+ file: &'a DownloadFile,
|
|
|
}
|
|
|
|
|
|
impl<'a> Download<'a> {
|
|
@@ -48,7 +39,7 @@ impl<'a> Download<'a> {
|
|
|
pub fn invoke(
|
|
|
self,
|
|
|
client: &Client,
|
|
|
- ) -> Result<SendFile> {
|
|
|
+ ) -> Result<()> {
|
|
|
// Create a key set for the file
|
|
|
let key = KeySet::from(self.file);
|
|
|
|
|
@@ -68,7 +59,7 @@ impl<'a> Download<'a> {
|
|
|
|
|
|
// Get the download url, and parse the nonce
|
|
|
// TODO: do not unwrap here, return error
|
|
|
- let download_url = file.download_url(false);
|
|
|
+ let download_url = self.file.download_url(false);
|
|
|
let response = client.get(download_url)
|
|
|
.send()
|
|
|
.expect("failed to get nonce, failed to send file request");
|
|
@@ -95,158 +86,213 @@ impl<'a> Download<'a> {
|
|
|
.skip(1)
|
|
|
.next()
|
|
|
.expect("missing authentication nonce")
|
|
|
- );
|
|
|
-
|
|
|
- // TODO: set the input vector
|
|
|
+ ).expect("failed to decode authentication nonce");
|
|
|
+
|
|
|
+ // Determine the signature
|
|
|
+ // TODO: use a tag length const here
|
|
|
+ // TODO: do not unwrap, return an error
|
|
|
+ let mut sig = vec![0u8; 16];
|
|
|
+ encrypt_aead(
|
|
|
+ KeySet::cipher(),
|
|
|
+ key.auth_key().unwrap(),
|
|
|
+ None,
|
|
|
+ &[],
|
|
|
+ &nonce,
|
|
|
+ &mut sig,
|
|
|
+ ).expect("failed to derive signature");
|
|
|
+ let sig_encoded = b64::encode(&sig);
|
|
|
+
|
|
|
+ // Get the meta URL, fetch the metadata
|
|
|
+ // TODO: do not unwrap here, return error
|
|
|
+ let meta_url = self.file.api_meta_url();
|
|
|
+ let mut response = client.get(meta_url)
|
|
|
+ .header(Authorization(
|
|
|
+ format!("send-v1 {}", sig_encoded)
|
|
|
+ ))
|
|
|
+ .send()
|
|
|
+ .expect("failed to fetch metadata, failed to send request");
|
|
|
|
|
|
- // Crpate metadata and a file reader
|
|
|
- let metadata = self.create_metadata(&key, &file)?;
|
|
|
- let reader = self.create_reader(&key, reporter.clone())?;
|
|
|
- let reader_len = reader.len().unwrap();
|
|
|
+ // Validate the status code
|
|
|
+ // TODO: allow redirects here?
|
|
|
+ if !response.status().is_success() {
|
|
|
+ // TODO: return error here
|
|
|
+ panic!("failed to fetch metadata, request status is not successful");
|
|
|
+ }
|
|
|
|
|
|
- // Create the request to send
|
|
|
- let req = self.create_request(
|
|
|
- client,
|
|
|
- &key,
|
|
|
- metadata,
|
|
|
- reader,
|
|
|
+ // Get the metadata nonce
|
|
|
+ // TODO: don't unwrap here, return an error
|
|
|
+ let nonce = b64::decode(
|
|
|
+ response.headers()
|
|
|
+ .get_raw(HEADER_AUTH_NONCE)
|
|
|
+ .expect("missing authenticate header")
|
|
|
+ .one()
|
|
|
+ .map(|line| String::from_utf8(line.to_vec())
|
|
|
+ .expect("invalid authentication header contents")
|
|
|
+ )
|
|
|
+ .expect("authentication header is empty")
|
|
|
+ .split_terminator(" ")
|
|
|
+ .skip(1)
|
|
|
+ .next()
|
|
|
+ .expect("missing metadata nonce")
|
|
|
);
|
|
|
|
|
|
- // Start the reporter
|
|
|
- reporter.lock()
|
|
|
- .expect("unable to start progress, failed to get lock")
|
|
|
- .start(reader_len);
|
|
|
-
|
|
|
- // Execute the request
|
|
|
- let result = self.execute_request(req, client, &key);
|
|
|
-
|
|
|
- // Mark the reporter as finished
|
|
|
- reporter.lock()
|
|
|
- .expect("unable to finish progress, failed to get lock")
|
|
|
- .finish();
|
|
|
-
|
|
|
- result
|
|
|
- }
|
|
|
-
|
|
|
- /// Create a blob of encrypted metadata.
|
|
|
- fn create_metadata(&self, key: &KeySet, file: &FileData)
|
|
|
- -> Result<Vec<u8>>
|
|
|
- {
|
|
|
- // Construct the metadata
|
|
|
- let metadata = Metadata::from(
|
|
|
- key.iv(),
|
|
|
- file.name().to_owned(),
|
|
|
- file.mime().clone(),
|
|
|
- ).to_json().into_bytes();
|
|
|
-
|
|
|
- // Encrypt the metadata
|
|
|
- let mut metadata_tag = vec![0u8; 16];
|
|
|
- let mut metadata = match encrypt_aead(
|
|
|
- KeySet::cipher(),
|
|
|
- key.meta_key().unwrap(),
|
|
|
- Some(&[0u8; 12]),
|
|
|
- &[],
|
|
|
- &metadata,
|
|
|
- &mut metadata_tag,
|
|
|
- ) {
|
|
|
- Ok(metadata) => metadata,
|
|
|
- Err(_) => return Err(DownloadError::EncryptionError),
|
|
|
- };
|
|
|
-
|
|
|
- // Append the encryption tag
|
|
|
- metadata.append(&mut metadata_tag);
|
|
|
+ // Parse the metadata response
|
|
|
+ let meta_response: MetadataResponse = response.json()
|
|
|
+ .expect("failed to parse metadata response");
|
|
|
|
|
|
- Ok(metadata)
|
|
|
- }
|
|
|
+ // Decrypt the metadata
|
|
|
+ let metadata = meta_response.decrypt_metadata(&key);
|
|
|
|
|
|
- /// Create a reader that reads the file as encrypted stream.
|
|
|
- fn create_reader(
|
|
|
- &self,
|
|
|
- key: &KeySet,
|
|
|
- reporter: Arc<Mutex<ProgressReporter>>,
|
|
|
- ) -> Result<EncryptedReader> {
|
|
|
- // Open the file
|
|
|
- let file = match File::open(self.path.as_path()) {
|
|
|
- Ok(file) => file,
|
|
|
- Err(_) => return Err(DownloadError::FileError),
|
|
|
- };
|
|
|
+ println!("GOT METADATA: {:?}", metadata);
|
|
|
|
|
|
- // Create an encrypted reader
|
|
|
- let reader = match EncryptedFileReaderTagged::new(
|
|
|
- file,
|
|
|
- KeySet::cipher(),
|
|
|
- key.file_key().unwrap(),
|
|
|
- key.iv(),
|
|
|
- ) {
|
|
|
- Ok(reader) => reader,
|
|
|
- Err(_) => return Err(DownloadError::EncryptionError),
|
|
|
- };
|
|
|
+ // // Crpate metadata and a file reader
|
|
|
+ // let metadata = self.create_metadata(&key, &file)?;
|
|
|
+ // let reader = self.create_reader(&key, reporter.clone())?;
|
|
|
+ // let reader_len = reader.len().unwrap();
|
|
|
|
|
|
- // Buffer the encrypted reader
|
|
|
- let reader = BufReader::new(reader);
|
|
|
+ // // Create the request to send
|
|
|
+ // let req = self.create_request(
|
|
|
+ // client,
|
|
|
+ // &key,
|
|
|
+ // metadata,
|
|
|
+ // reader,
|
|
|
+ // );
|
|
|
|
|
|
- // Wrap into the encrypted reader
|
|
|
- let mut reader = ProgressReader::new(reader)
|
|
|
- .expect("failed to create progress reader");
|
|
|
+ // // Start the reporter
|
|
|
+ // reporter.lock()
|
|
|
+ // .expect("unable to start progress, failed to get lock")
|
|
|
+ // .start(reader_len);
|
|
|
|
|
|
- // Initialize and attach the reporter
|
|
|
- reader.set_reporter(reporter);
|
|
|
+ // // Execute the request
|
|
|
+ // let result = self.execute_request(req, client, &key);
|
|
|
|
|
|
- Ok(reader)
|
|
|
- }
|
|
|
+ // // Mark the reporter as finished
|
|
|
+ // reporter.lock()
|
|
|
+ // .expect("unable to finish progress, failed to get lock")
|
|
|
+ // .finish();
|
|
|
|
|
|
- /// Build the request that will be send to the server.
|
|
|
- fn create_request(
|
|
|
- &self,
|
|
|
- client: &Client,
|
|
|
- key: &KeySet,
|
|
|
- metadata: Vec<u8>,
|
|
|
- reader: EncryptedReader,
|
|
|
- ) -> Request {
|
|
|
- // Get the reader length
|
|
|
- let len = reader.len().expect("failed to get reader length");
|
|
|
-
|
|
|
- // Configure a form to send
|
|
|
- let part = Part::reader_with_length(reader, len)
|
|
|
- // .file_name(file.name())
|
|
|
- .mime(APPLICATION_OCTET_STREAM);
|
|
|
- let form = Form::new()
|
|
|
- .part("data", part);
|
|
|
-
|
|
|
- // Define the URL to call
|
|
|
- let url = self.host.join("api/upload").expect("invalid host");
|
|
|
-
|
|
|
- // Build the request
|
|
|
- client.post(url.as_str())
|
|
|
- .header(Authorization(
|
|
|
- format!("send-v1 {}", key.auth_key_encoded().unwrap())
|
|
|
- ))
|
|
|
- .header(XFileMetadata::from(&metadata))
|
|
|
- .multipart(form)
|
|
|
- .build()
|
|
|
- .expect("failed to build an API request")
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- /// Execute the given request, and create a file object that represents the
|
|
|
- /// uploaded file.
|
|
|
- fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
|
|
- -> Result<SendFile>
|
|
|
- {
|
|
|
- // Execute the request
|
|
|
- let mut res = match client.execute(req) {
|
|
|
- Ok(res) => res,
|
|
|
- Err(err) => return Err(DownloadError::RequestError(err)),
|
|
|
- };
|
|
|
-
|
|
|
- // Decode the response
|
|
|
- let res: DownloadResponse = match res.json() {
|
|
|
- Ok(res) => res,
|
|
|
- Err(_) => return Err(DownloadError::DecodeError),
|
|
|
- };
|
|
|
-
|
|
|
- // Transform the responce into a file object
|
|
|
- Ok(res.into_file(self.host.clone(), &key))
|
|
|
- }
|
|
|
+ // /// Create a blob of encrypted metadata.
|
|
|
+ // fn create_metadata(&self, key: &KeySet, file: &FileData)
|
|
|
+ // -> Result<Vec<u8>>
|
|
|
+ // {
|
|
|
+ // // Construct the metadata
|
|
|
+ // let metadata = Metadata::from(
|
|
|
+ // key.iv(),
|
|
|
+ // file.name().to_owned(),
|
|
|
+ // file.mime().clone(),
|
|
|
+ // ).to_json().into_bytes();
|
|
|
+
|
|
|
+ // // Encrypt the metadata
|
|
|
+ // let mut metadata_tag = vec![0u8; 16];
|
|
|
+ // let mut metadata = match encrypt_aead(
|
|
|
+ // KeySet::cipher(),
|
|
|
+ // key.meta_key().unwrap(),
|
|
|
+ // Some(&[0u8; 12]),
|
|
|
+ // &[],
|
|
|
+ // &metadata,
|
|
|
+ // &mut metadata_tag,
|
|
|
+ // ) {
|
|
|
+ // Ok(metadata) => metadata,
|
|
|
+ // Err(_) => return Err(DownloadError::EncryptionError),
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // Append the encryption tag
|
|
|
+ // metadata.append(&mut metadata_tag);
|
|
|
+
|
|
|
+ // Ok(metadata)
|
|
|
+ // }
|
|
|
+
|
|
|
+ // /// Create a reader that reads the file as encrypted stream.
|
|
|
+ // fn create_reader(
|
|
|
+ // &self,
|
|
|
+ // key: &KeySet,
|
|
|
+ // reporter: Arc<Mutex<ProgressReporter>>,
|
|
|
+ // ) -> Result<EncryptedReader> {
|
|
|
+ // // Open the file
|
|
|
+ // let file = match File::open(self.path.as_path()) {
|
|
|
+ // Ok(file) => file,
|
|
|
+ // Err(_) => return Err(DownloadError::FileError),
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // Create an encrypted reader
|
|
|
+ // let reader = match EncryptedFileReaderTagged::new(
|
|
|
+ // file,
|
|
|
+ // KeySet::cipher(),
|
|
|
+ // key.file_key().unwrap(),
|
|
|
+ // key.iv(),
|
|
|
+ // ) {
|
|
|
+ // Ok(reader) => reader,
|
|
|
+ // Err(_) => return Err(DownloadError::EncryptionError),
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // Buffer the encrypted reader
|
|
|
+ // let reader = BufReader::new(reader);
|
|
|
+
|
|
|
+ // // Wrap into the encrypted reader
|
|
|
+ // let mut reader = ProgressReader::new(reader)
|
|
|
+ // .expect("failed to create progress reader");
|
|
|
+
|
|
|
+ // // Initialize and attach the reporter
|
|
|
+ // reader.set_reporter(reporter);
|
|
|
+
|
|
|
+ // Ok(reader)
|
|
|
+ // }
|
|
|
+
|
|
|
+ // /// Build the request that will be send to the server.
|
|
|
+ // fn create_request(
|
|
|
+ // &self,
|
|
|
+ // client: &Client,
|
|
|
+ // key: &KeySet,
|
|
|
+ // metadata: Vec<u8>,
|
|
|
+ // reader: EncryptedReader,
|
|
|
+ // ) -> Request {
|
|
|
+ // // Get the reader length
|
|
|
+ // let len = reader.len().expect("failed to get reader length");
|
|
|
+
|
|
|
+ // // Configure a form to send
|
|
|
+ // let part = Part::reader_with_length(reader, len)
|
|
|
+ // // .file_name(file.name())
|
|
|
+ // .mime(APPLICATION_OCTET_STREAM);
|
|
|
+ // let form = Form::new()
|
|
|
+ // .part("data", part);
|
|
|
+
|
|
|
+ // // Define the URL to call
|
|
|
+ // let url = self.host.join("api/upload").expect("invalid host");
|
|
|
+
|
|
|
+ // // Build the request
|
|
|
+ // client.post(url.as_str())
|
|
|
+ // .header(Authorization(
|
|
|
+ // format!("send-v1 {}", key.auth_key_encoded().unwrap())
|
|
|
+ // ))
|
|
|
+ // .header(XFileMetadata::from(&metadata))
|
|
|
+ // .multipart(form)
|
|
|
+ // .build()
|
|
|
+ // .expect("failed to build an API request")
|
|
|
+ // }
|
|
|
+
|
|
|
+ // /// Execute the given request, and create a file object that represents the
|
|
|
+ // /// uploaded file.
|
|
|
+ // fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
|
|
+ // -> Result<SendFile>
|
|
|
+ // {
|
|
|
+ // // Execute the request
|
|
|
+ // let mut res = match client.execute(req) {
|
|
|
+ // Ok(res) => res,
|
|
|
+ // Err(err) => return Err(DownloadError::RequestError(err)),
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // Decode the response
|
|
|
+ // let res: DownloadResponse = match res.json() {
|
|
|
+ // Ok(res) => res,
|
|
|
+ // Err(_) => return Err(DownloadError::DecodeError),
|
|
|
+ // };
|
|
|
+
|
|
|
+ // // Transform the responce into a file object
|
|
|
+ // Ok(res.into_file(self.host.clone(), &key))
|
|
|
+ // }
|
|
|
}
|
|
|
|
|
|
/// Errors that may occur in the upload action.
|
|
@@ -270,39 +316,50 @@ pub enum DownloadError {
|
|
|
DecodeError,
|
|
|
}
|
|
|
|
|
|
-/// The response from the server after a file has been uploaded.
|
|
|
-/// This response contains the file ID and owner key, to manage the file.
|
|
|
-///
|
|
|
-/// It also contains the download URL, although an additional secret is
|
|
|
-/// required.
|
|
|
-///
|
|
|
-/// The download URL can be generated using `download_url()` which will
|
|
|
-/// include the required secret in the URL.
|
|
|
+/// The metadata response from the server, when fetching the data through
|
|
|
+/// the API.
|
|
|
+///
|
|
|
+/// This metadata is required to successfully download and decrypt the
|
|
|
+/// corresponding file.
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
-struct DownloadResponse {
|
|
|
- /// The file ID.
|
|
|
- id: String,
|
|
|
-
|
|
|
- /// The URL the file is reachable at.
|
|
|
- /// This includes the file ID, but does not include the secret.
|
|
|
- url: String,
|
|
|
-
|
|
|
- /// The owner key, used to do further file modifications.
|
|
|
- owner: String,
|
|
|
+struct MetadataResponse {
|
|
|
+ /// The encrypted metadata.
|
|
|
+ #[serde(rename="metadata")]
|
|
|
+ meta: String,
|
|
|
}
|
|
|
|
|
|
-impl DownloadResponse {
|
|
|
- /// Convert this response into a file object.
|
|
|
+impl MetadataResponse {
|
|
|
+ /// Get and decrypt the metadata, based on the raw data in this response.
|
|
|
///
|
|
|
- /// The `host` and `key` must be given.
|
|
|
- pub fn into_file(self, host: Url, key: &KeySet) -> SendFile {
|
|
|
- SendFile::new_now(
|
|
|
- self.id,
|
|
|
- host,
|
|
|
- Url::parse(&self.url)
|
|
|
- .expect("upload response URL parse error"),
|
|
|
- key.secret().to_vec(),
|
|
|
- self.owner,
|
|
|
+ /// The decrypted data is verified using an included tag.
|
|
|
+ /// If verification failed, an error is returned.
|
|
|
+ // TODO: do not unwrap, return a proper error
|
|
|
+ pub fn decrypt_metadata(&self, key_set: &KeySet) -> Result<Metadata> {
|
|
|
+ // Decode the metadata
|
|
|
+ let raw = b64::decode(&self.meta)
|
|
|
+ .expect("failed to decode metadata from server");
|
|
|
+
|
|
|
+ // Get the encrypted metadata, and it's tag
|
|
|
+ let (encrypted, tag) = raw.split_at(raw.len() - 16);
|
|
|
+ // TODO: is the tag length correct, remove assert if it is
|
|
|
+ assert_eq!(tag.len(), 16);
|
|
|
+
|
|
|
+ // Decrypt the metadata
|
|
|
+ // TODO: is the tag verified here?
|
|
|
+ // TODO: do not unwrap, return an error
|
|
|
+ let meta = decrypt_aead(
|
|
|
+ KeySet::cipher(),
|
|
|
+ key_set.meta_key().unwrap(),
|
|
|
+ Some(key_set.iv()),
|
|
|
+ &[],
|
|
|
+ encrypted,
|
|
|
+ &tag,
|
|
|
+ ).expect("failed to decrypt metadata");
|
|
|
+
|
|
|
+ // Parse the metadata, and return
|
|
|
+ Ok(
|
|
|
+ serde_json::from_slice(&meta)
|
|
|
+ .expect("failed to parse decrypted metadata as JSON")
|
|
|
)
|
|
|
}
|
|
|
}
|