Просмотр исходного кода

Allow key set derivation from download URL, start on download logic

Tim Visée 7 лет назад
Родитель
Сommit
8101bb3d19
4 измененных файлов с 116 добавлено и 72 удалено
  1. 61 17
      api/src/action/download.rs
  2. 2 50
      api/src/crypto/key_set.rs
  3. 52 4
      api/src/file/file.rs
  4. 1 1
      cli/src/action/upload.rs

+ 61 - 17
api/src/action/download.rs

@@ -22,38 +22,82 @@ use reader::{
     ProgressReader,
     ProgressReporter,
 };
-use file::file::File as SendFile;
+use file::file::DownloadFile;
 use file::metadata::{Metadata, XFileMetadata};
 
 pub type Result<T> = ::std::result::Result<T, DownloadError>;
 
-/// A file upload action to a Send server.
-pub struct Download {
-    /// The Send host to upload the file to.
-    host: Url,
+/// The name of the header that is used for the authentication nonce.
+const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
 
-    /// The file to upload.
-    path: PathBuf,
+/// A file upload action to a Send server.
+pub struct Download<'a> {
+    /// The Send file to download.
+    file: &DownloadFile,
 }
 
-impl Download {
-    /// Construct a new upload action.
-    pub fn new(host: Url, path: PathBuf) -> Self {
+impl<'a> Download<'a> {
+    /// Construct a new download action for the given file.
+    pub fn new(file: &'a DownloadFile) -> Self {
         Self {
-            host,
-            path,
+            file,
         }
     }
 
-    /// Invoke the upload action.
+    /// Invoke the download action.
     pub fn invoke(
         self,
         client: &Client,
-        reporter: Arc<Mutex<ProgressReporter>>,
     ) -> Result<SendFile> {
-        // Create file data, generate a key
-        let file = FileData::from(Box::new(&self.path))?;
-        let key = KeySet::generate(true);
+        // Create a key set for the file
+        let 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,
+        // };
+
+        // Get the download url, and parse the nonce
+        // TODO: do not unwrap here, return error
+        let download_url = file.download_url(false);
+        let response = client.get(download_url)
+            .send()
+            .expect("failed to get nonce, failed to send file request");
+
+        // Validate the status code
+        // TODO: allow redirects here?
+        if !response.status().is_success() {
+            // TODO: return error here
+            panic!("failed to get nonce, request status is not successful");
+        }
+
+        // Get the authentication 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 authentication nonce")
+        );
+
+        // TODO: set the input vector
 
         // Crpate metadata and a file reader
         let metadata = self.create_metadata(&key, &file)?;

+ 2 - 50
api/src/crypto/key_set.rs

@@ -1,5 +1,6 @@
 use openssl::symm::Cipher;
 
+use file::file::DownloadFile;
 use super::{b64, rand_bytes};
 use super::hdkf::{derive_auth_key, derive_file_key, derive_meta_key};
 
@@ -51,56 +52,6 @@ impl KeySet {
         // Derive all keys
         set.derive();
 
-        // 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,
-        // };
-
-        // Create a reqwest client
-        let client = Client::new();
-
-        // Get the download url, and parse the nonce
-        // TODO: do not unwrap here, return error
-        let download_url = file.download_url(false);
-        let response = client.get(download_url)
-            .send()
-            .expect("failed to get nonce, failed to send file request");
-
-        // Validate the status code
-        // TODO: allow redirects here?
-        if !response.status().is_success() {
-            // TODO: return error here
-            panic!("failed to get nonce, request status is not successful");
-        }
-
-        // Get the authentication nonce
-        // TODO: don't unwrap here, return an error
-        let nonce = b64::decode(
-            response.headers()
-                .get_raw("WWW-Authenticate")
-                .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 authentication nonce")
-        );
-
-        // TODO: set the input vector
-
         set
     }
 
@@ -131,6 +82,7 @@ impl KeySet {
     }
 
     /// Derive a file, authentication and metadata key.
+    // TODO: add support for deriving with a password and URL
     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));

+ 52 - 4
api/src/file/file.rs

@@ -85,6 +85,12 @@ impl File {
 
     /// Get the raw secret.
     pub fn secret_raw(&self) -> &Vec<u8> {
+        // A secret must have been set
+        if !self.has_secret() {
+            // TODO: don't panic, return an error instead
+            panic!("missing secret");
+        }
+
         &self.secret
     }
 
@@ -93,11 +99,22 @@ impl File {
         b64::encode(self.secret_raw())
     }
 
-    /// Get the download URL of the file, with the secret key included.
-    pub fn download_url(&self) -> Url {
+    /// Check whether a file secret is set.
+    /// This secret must be set to decrypt a downloaded Send file.
+    pub fn has_secret(&self) -> bool {
+        !self.secret.is_empty()
+    }
+
+    /// Get the download URL of the file.
+    /// Set `secret` to `true`, to include it in the URL if known.
+    pub fn download_url(&self, secret: bool) -> Url {
         // Get the download URL, and add the secret fragment
         let mut url = self.url.clone();
-        url.set_fragment(Some(&self.secret()));
+        if secret && self.has_secret() {
+            url.set_fragment(Some(&self.secret()));
+        } else {
+            url.set_fragment(None);
+        }
 
         url
     }
@@ -160,6 +177,7 @@ impl DownloadFile {
         let re_path = Regex::new(DOWNLOAD_PATH_PATTERN).unwrap();
         let id = re_path.captures(url.path())
             .ok_or(FileParseError::InvalidDownloadUrl)?[1]
+            .trim()
             .to_owned();
 
         // Get the file secret
@@ -170,7 +188,7 @@ impl DownloadFile {
                 .ok_or(FileParseError::InvalidSecret)?
                 .get(1)
             {
-                secret = b64::decode(raw.as_str())
+                secret = b64::decode(raw.as_str().trim())
                         .map_err(|_| FileParseError::InvalidSecret)?
             }
         }
@@ -184,6 +202,22 @@ impl DownloadFile {
         ))
     }
 
+    /// Get the raw secret.
+    pub fn secret_raw(&self) -> &Vec<u8> {
+        // A secret must have been set
+        if !self.has_secret() {
+            // TODO: don't panic, return an error instead
+            panic!("missing secret");
+        }
+
+        &self.secret
+    }
+
+    /// Get the secret as base64 encoded string.
+    pub fn secret(&self) -> String {
+        b64::encode(self.secret_raw())
+    }
+
     /// Check whether a file secret is set.
     /// This secret must be set to decrypt a downloaded Send file.
     pub fn has_secret(&self) -> bool {
@@ -195,6 +229,20 @@ impl DownloadFile {
     pub fn set_secret(&mut self, secret: Vec<u8>) {
         self.secret = secret;
     }
+
+    /// Get the download URL of the file.
+    /// Set `secret` to `true`, to include it in the URL if known.
+    pub fn download_url(&self, secret: bool) -> Url {
+        // Get the download URL, and add the secret fragment
+        let mut url = self.url.clone();
+        if secret && self.has_secret() {
+            url.set_fragment(Some(&self.secret()));
+        } else {
+            url.set_fragment(None);
+        }
+
+        url
+    }
 }
 
 pub enum FileParseError {

+ 1 - 1
cli/src/action/upload.rs

@@ -41,7 +41,7 @@ impl<'a> Upload<'a> {
         let file = ApiUpload::new(host, path).invoke(&client, bar).unwrap();
 
         // Get the download URL, and report it in the console
-        let url = file.download_url();
+        let url = file.download_url(true);
         println!("Download URL: {}", url);
 
         // Open the URL in the browser