From 268da48d9ccbe4b01b73de58c727e8200fa32e6a Mon Sep 17 00:00:00 2001 From: timvisee Date: Fri, 23 Mar 2018 01:17:59 +0100 Subject: [PATCH] Extract methods in API download logic --- api/src/action/download.rs | 341 +++++++++++++++---------------------- 1 file changed, 142 insertions(+), 199 deletions(-) diff --git a/api/src/action/download.rs b/api/src/action/download.rs index 26e549d..e1c855d 100644 --- a/api/src/action/download.rs +++ b/api/src/action/download.rs @@ -1,11 +1,15 @@ use std::fs::File; -use std::io; +use std::io::{ + self, + Read, +}; use std::sync::{Arc, Mutex}; use openssl::symm::decrypt_aead; use reqwest::{ Client, Error as ReqwestError, + Response, }; use reqwest::header::Authorization; use reqwest::header::ContentLength; @@ -48,20 +52,39 @@ impl<'a> Download<'a> { // Create a key set for the file let mut key = KeySet::from(self.file); - // Build the meta cipher - // let mut metadata_tag = vec![0u8; 16]; - // let mut meta_cipher = match encrypt_aead( - // KeySet::cipher(), - // self.meta_key().unwrap(), - // self.iv, - // &[], - // &metadata, - // &mut metadata_tag, - // ) { - // Ok(cipher) => cipher, - // Err(_) => // TODO: return error here, - // }; + // Fetch the authentication nonce + let auth_nonce = self.fetch_auth_nonce(client); + // Fetch the meta nonce, set the input vector + let meta_nonce = self.fetch_meta_nonce(&client, &mut key, auth_nonce); + + // Open the file we will write to + // TODO: this should become a temporary file first + let out = File::create("downloaded.zip") + .expect("failed to open file"); + + // Create the file reader for downloading + let (reader, len) = self.create_file_reader(&key, meta_nonce, &client); + + // Create the file writer + let writer = self.create_file_writer( + out, + len, + &key, + reporter.clone(), + ); + + // Download the file + self.download(reader, writer, len, reporter); + + // TODO: return the file path + // TODO: return the new remote state (does it still exist remote) + + Ok(()) + } + + /// Fetch the authentication nonce for the file from the Send server. + fn fetch_auth_nonce(&self, client: &Client) -> Vec { // Get the download url, and parse the nonce // TODO: do not unwrap here, return error let download_url = self.file.download_url(false); @@ -78,7 +101,7 @@ impl<'a> Download<'a> { // Get the authentication nonce // TODO: don't unwrap here, return an error - let nonce = b64::decode( + b64::decode( response.headers() .get_raw(HEADER_AUTH_NONCE) .expect("missing authenticate header") @@ -91,17 +114,49 @@ impl<'a> Download<'a> { .skip(1) .next() .expect("missing authentication nonce") - ).expect("failed to decode authentication nonce"); + ).expect("failed to decode authentication nonce") + } - // Compute the cryptographic signature + /// Fetch the metadata nonce. + /// This method also sets the input vector on the given key set, + /// extracted from the metadata. + /// + /// The key set, along with the authentication nonce must be given. + /// The meta nonce is returned. + fn fetch_meta_nonce( + &self, + client: &Client, + key: &mut KeySet, + auth_nonce: Vec, + ) -> Vec { + // Fetch the metadata and the nonce + let (metadata, meta_nonce) = self.fetch_metadata(client, key, auth_nonce); + + // Set the input vector, and return the nonce + key.set_iv(metadata.iv()); + meta_nonce + } + + /// Create a metadata nonce, and fetch the metadata for the file from the + /// Send server. + /// + /// The key set, along with the authentication nonce must be given. + /// + /// The metadata, with the meta nonce is returned. + fn fetch_metadata( + &self, + client: &Client, + key: &KeySet, + auth_nonce: Vec, + ) -> (Metadata, Vec) { + // Compute the cryptographic signature for authentication // TODO: do not unwrap, return an error - let sig = signature_encoded(key.auth_key().unwrap(), &nonce) + let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce) .expect("failed to compute metadata signature"); - // Get the meta URL, fetch the metadata + // Buidl the request, fetch the encrypted metadata // TODO: do not unwrap here, return error - let meta_url = self.file.api_meta_url(); - let mut response = client.get(meta_url) + let mut response = client.get(self.file.api_meta_url()) .header(Authorization( format!("send-v1 {}", sig) )) @@ -132,24 +187,36 @@ impl<'a> Download<'a> { .expect("missing metadata nonce") ).expect("failed to decode metadata nonce"); - // Parse the metadata response - let meta_response: MetadataResponse = response.json() - .expect("failed to parse metadata response"); - - // Decrypt the metadata, set the input vector - let metadata = meta_response.decrypt_metadata(&key) - .expect("failed to decrypt metadata"); - key.set_iv(metadata.iv()); + // Parse the metadata response, and decrypt it + ( + response.json::() + .expect("failed to parse metadata response") + .decrypt_metadata(&key) + .expect("failed to decrypt metadata"), + nonce, + ) + } + /// Make a download request, and create a reader that downloads the + /// encrypted file. + /// + /// The response representing the file reader is returned along with the + /// length of the reader content. + fn create_file_reader( + &self, + key: &KeySet, + meta_nonce: Vec, + client: &Client, + ) -> (Response, u64) { // Compute the cryptographic signature + // TODO: use the metadata nonce here? // TODO: do not unwrap, return an error - let sig = signature_encoded(key.auth_key().unwrap(), &nonce) + let sig = signature_encoded(key.auth_key().unwrap(), &meta_nonce) .expect("failed to compute file signature"); - // Get the download URL, build the download request + // Build and send the download request // TODO: do not unwrap here, return error - let download_url = self.file.api_download_url(); - let mut response = client.get(download_url) + let response = client.get(self.file.api_download_url()) .header(Authorization( format!("send-v1 {}", sig) )) @@ -164,32 +231,59 @@ impl<'a> Download<'a> { } // Get the content length - let response_len = response.headers().get::() + // TODO: make sure there is enough disk space + let len = response.headers().get::() .expect("failed to fetch file, missing content length header") .0; - // Open a file to write to, and build an encrypted writer - // TODO: this should become a temporary file first - let out = File::create("downloaded.zip") - .expect("failed to open file"); - let writer = EncryptedFileWriter::new( - out, - response_len as usize, - KeySet::cipher(), - key.file_key().unwrap(), - key.iv(), + (response, len) + } + + /// Create a file writer. + /// + /// This writer will will decrypt the input on the fly, and writes the + /// decrypted data to the given file. + fn create_file_writer( + &self, + file: File, + len: u64, + key: &KeySet, + reporter: Arc>, + ) -> ProgressWriter { + // Build an encrypted writer + let mut writer = ProgressWriter::new( + EncryptedFileWriter::new( + file, + len as usize, + KeySet::cipher(), + key.file_key().unwrap(), + key.iv(), + ).expect("failed to create encrypted writer") ).expect("failed to create encrypted writer"); - let mut writer = ProgressWriter::new(writer) - .expect("failed to create encrypted writer"); + + // Set the reporter writer.set_reporter(reporter.clone()); + writer + } + + /// Download the file from the reader, and write it to the writer. + /// The length of the file must also be given. + /// The status will be reported to the given progress reporter. + fn download( + &self, + mut reader: R, + mut writer: ProgressWriter, + len: u64, + reporter: Arc>, + ) { // Start the writer reporter.lock() .expect("unable to start progress, failed to get lock") - .start(response_len); + .start(len); // Write to the output file - io::copy(&mut response, &mut writer) + io::copy(&mut reader, &mut writer) .expect("failed to download and decrypt file"); // Finish @@ -200,158 +294,7 @@ impl<'a> Download<'a> { // Verify the writer // TODO: delete the file if verification failed, show a proper error assert!(writer.unwrap().verified(), "downloaded and decrypted file could not be verified"); - - // // 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(); - - // // Create the request to send - // let req = self.create_request( - // client, - // &key, - // metadata, - // reader, - // ); - - // // 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(); - - // TODO: return the file path - // TODO: return the new remote state (does it still exist remote) - - Ok(()) } - - // /// Create a blob of encrypted metadata. - // fn create_metadata(&self, key: &KeySet, file: &FileData) - // -> Result> - // { - // // 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>, - // ) -> Result { - // // 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, - // 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 - // { - // // 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.