Browse Source

Implement first OpenSSL crypto basic, make encrypted reader lazy (WIP)

timvisee 7 years ago
parent
commit
b2b2aeb306
4 changed files with 121 additions and 46 deletions
  1. 1 0
      .gitignore
  2. 14 0
      Cargo.lock
  3. 1 0
      Cargo.toml
  4. 105 46
      src/main.rs

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 .*.sw[po]
 .*.sw[po]
 /target
 /target
 **/*.rs.bk
 **/*.rs.bk
+.idea/

+ 14 - 0
Cargo.lock

@@ -127,6 +127,7 @@ dependencies = [
  "hyper 0.11.21 (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)",
  "mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.4.2 (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)",
  "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -415,6 +416,18 @@ dependencies = [
  "openssl-sys 0.9.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl-sys 0.9.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 ]
 
 
+[[package]]
+name = "openssl"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 [[package]]
 name = "openssl-sys"
 name = "openssl-sys"
 version = "0.9.27"
 version = "0.9.27"
@@ -958,6 +971,7 @@ dependencies = [
 "checksum num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3c2bd9b9d21e48e956b763c9f37134dc62d9e95da6edb3f672cacb6caf3cd3"
 "checksum num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3c2bd9b9d21e48e956b763c9f37134dc62d9e95da6edb3f672cacb6caf3cd3"
 "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
 "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
 "checksum open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c281318d992e4432cfa799969467003d05921582a7489a8325e37f8a450d5113"
 "checksum open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c281318d992e4432cfa799969467003d05921582a7489a8325e37f8a450d5113"
+"checksum openssl 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1636c9f1d78af9cbcc50e523bfff4a30274108aad5e86761afd4d31e4e184fa7"
 "checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985"
 "checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985"
 "checksum openssl-sys 0.9.27 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdc5c4a02e69ce65046f1763a0181107038e02176233acb0b3351d7cc588f9"
 "checksum openssl-sys 0.9.27 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdc5c4a02e69ce65046f1763a0181107038e02176233acb0b3351d7cc588f9"
 "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
 "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"

+ 1 - 0
Cargo.toml

@@ -9,6 +9,7 @@ clap = "2.30"
 hyper = "0.11.9" # same as reqwest
 hyper = "0.11.9" # same as reqwest
 mime_guess = "2.0.0-alpha.2"
 mime_guess = "2.0.0-alpha.2"
 open = "1"
 open = "1"
+openssl = "0.10"
 rand = "0.4"
 rand = "0.4"
 reqwest = "0.8"
 reqwest = "0.8"
 rust-crypto = "0.2"
 rust-crypto = "0.2"

+ 105 - 46
src/main.rs

@@ -4,6 +4,7 @@ extern crate crypto;
 extern crate hyper;
 extern crate hyper;
 extern crate mime_guess;
 extern crate mime_guess;
 extern crate open;
 extern crate open;
+extern crate openssl;
 extern crate rand;
 extern crate rand;
 extern crate reqwest;
 extern crate reqwest;
 #[macro_use]
 #[macro_use]
@@ -12,7 +13,7 @@ extern crate serde_json;
 
 
 use std::fmt;
 use std::fmt;
 use std::fs::File;
 use std::fs::File;
-use std::io::{self, Cursor, Read};
+use std::io::{self, BufReader, Cursor, Read};
 use std::path::Path;
 use std::path::Path;
 
 
 use clap::{App, Arg};
 use clap::{App, Arg};
@@ -24,6 +25,11 @@ use crypto::hkdf::{hkdf_extract, hkdf_expand};
 use crypto::sha2::Sha256;
 use crypto::sha2::Sha256;
 use hyper::error::Error as HyperError;
 use hyper::error::Error as HyperError;
 use mime_guess::Mime;
 use mime_guess::Mime;
+use openssl::symm::{
+    Cipher,
+    Crypter,
+    Mode as CrypterMode,
+};
 use rand::{Rng, thread_rng};
 use rand::{Rng, thread_rng};
 use reqwest::header::{
 use reqwest::header::{
     Authorization,
     Authorization,
@@ -77,9 +83,10 @@ fn main() {
     let auth_key = derive_auth_key(&secret, None, None);
     let auth_key = derive_auth_key(&secret, None, None);
     let meta_key = derive_meta_key(&secret);
     let meta_key = derive_meta_key(&secret);
 
 
-    // Generate a file and meta cipher
-    // TODO: use the proper key size here, and the proper aad
-    let file_cipher = AesGcm::new(KeySize::KeySize128, &encrypt_key, &iv, b"");
+    // Choose a file and meta cipher type
+    let cipher = Cipher::aes_128_gcm();
+
+    // Generate a meta cipher
     let mut meta_cipher = AesGcm::new(KeySize::KeySize128, &meta_key, &[0u8; 12], b"");
     let mut meta_cipher = AesGcm::new(KeySize::KeySize128, &meta_key, &[0u8; 12], b"");
 
 
     // Guess the mimetype of the file
     // Guess the mimetype of the file
@@ -97,7 +104,15 @@ fn main() {
 
 
     // Open the file and create an encrypted file reader
     // Open the file and create an encrypted file reader
     let file = File::open(path).unwrap();
     let file = File::open(path).unwrap();
-    let reader = EncryptedFileReaderTagged::new(file, file_cipher);
+    let reader = EncryptedFileReaderTagged::new(
+        file,
+        cipher,
+        &encrypt_key,
+        &iv,
+    );
+
+    // Buffer the encrypted reader
+    let reader = BufReader::new(reader);
 
 
     // Build the file part, configure the form to send
     // Build the file part, configure the form to send
     let part = Part::reader(reader)
     let part = Part::reader(reader)
@@ -124,9 +139,12 @@ fn main() {
     println!("Download URL: {}", url);
     println!("Download URL: {}", url);
 
 
     // Open the URL in the browser
     // Open the URL in the browser
-    open::that(url);
+    open::that(url).expect("failed to open URL");
 }
 }
 
 
+// TODO: implement this some other way
+unsafe impl Send for EncryptedFileReaderTagged {}
+
 /// Run HKDF crypto.
 /// Run HKDF crypto.
 ///
 ///
 /// # Arguments
 /// # Arguments
@@ -163,7 +181,7 @@ fn derive_file_key(secret: &[u8]) -> Vec<u8> {
     hkdf(16, secret, Some(b"encryption"))
     hkdf(16, secret, Some(b"encryption"))
 }
 }
 
 
-fn derive_auth_key(secret: &[u8], password: Option<String>, url: Option<String>) -> Vec<u8> {
+fn derive_auth_key(secret: &[u8], password: Option<String>, _url: Option<String>) -> Vec<u8> {
     if password.is_none() {
     if password.is_none() {
         hkdf(64, secret, Some(b"authentication"))
         hkdf(64, secret, Some(b"authentication"))
     } else {
     } else {
@@ -247,21 +265,27 @@ impl Header for XFileMetadata {
     }
     }
 }
 }
 
 
-/// A file reader, that encrypts the file that is read with the given
-/// `cipher`, and appends the cipher tag to the end of it.
+/// A lazy file reader, that encrypts the file with the given `cipher`
+/// and appends the GCM tag to the end of it.
 ///
 ///
-/// This reader is not lazy, and loads the whole file in memory to
-/// encrypt it at once. Also, a buffer is created to copy the encrypted file
-/// into.
+/// This reader is lazy because the file data loaded from the system
+/// and encrypted when it is read from the reader.
+/// This greatly reduces memory usage for large files.
 ///
 ///
-/// This object requires about twice the memory as the size of the file that is
-/// encrypted when constructed.
+/// This reader encrypts the file data with an appended GCM tag.
 struct EncryptedFileReaderTagged {
 struct EncryptedFileReaderTagged {
-    /// A cursor that reads encrypted file data.
-    data: Cursor<Vec<u8>>,
+    /// The file to read.
+    file: File,
 
 
-    /// A tag cursor that reads the tag to append.
-    tag: Cursor<Vec<u8>>,
+    /// The cipher that is used for decryption.
+    cipher: Cipher,
+
+    /// The crypter used to encrypt the file data.
+    crypter: Crypter,
+
+    /// A tag cursor that reads the tag to append,
+    /// when the file is fully read and the tag is known.
+    tag: Option<Cursor<Vec<u8>>>,
 }
 }
 
 
 impl EncryptedFileReaderTagged {
 impl EncryptedFileReaderTagged {
@@ -270,43 +294,78 @@ impl EncryptedFileReaderTagged {
     /// This method consumes twice the size of the file in memory while
     /// This method consumes twice the size of the file in memory while
     /// constructing, and constructs a reader that has a size similar to the
     /// constructing, and constructs a reader that has a size similar to the
     /// file.
     /// file.
-    pub fn new(mut file: File, mut cipher: AesGcm<'static>) -> Self {
-        // Get the length of the file
-        let len = file.metadata().unwrap().len() as usize;
-
-        // Read the whole file in a data buffer
-        let mut data = Vec::with_capacity(len);
-        file.read_to_end(&mut data).unwrap();
-
-        // Create an encrypted and tag buffer
-        let mut encrypted = vec![0u8; data.len()];
-        let mut tag = vec![0u8; TAG_LEN];
-
-        // Encrypt the file, set the tag
-        cipher.encrypt(&data, &mut encrypted, &mut tag);
-
-        // Construct the reader and return
+    pub fn new(file: File, cipher: Cipher, key: &[u8], iv: &[u8]) -> Self {
+        // TODO: return proper errors from crypter
         EncryptedFileReaderTagged {
         EncryptedFileReaderTagged {
-            data: Cursor::new(encrypted),
-            tag: Cursor::new(tag),
+            file,
+            cipher,
+            crypter: Crypter::new(
+                cipher,
+                CrypterMode::Encrypt,
+                key,
+                Some(iv),
+            ).unwrap(),
+            tag: None,
         }
         }
     }
     }
 }
 }
 
 
 impl Read for EncryptedFileReaderTagged {
 impl Read for EncryptedFileReaderTagged {
     fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
     fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
-        // Read the data if we haven't started with the tag yet
-        if self.tag.position() == 0 {
-            // Read and return if something was read
-            let result = self.data.read(buf);
-            match result {
-                Ok(len) if len > 0 => return result,
-                _ => {},
-            }
+        // If the tag reader has been created, read from it
+        if let Some(ref mut tag) = self.tag {
+            return tag.read(buf);
         }
         }
 
 
-        // Read the tag if it's empty
-        self.tag.read(buf)
+        // Get the block size
+        let block_size = self.cipher.block_size();
+
+        // Create a raw file buffer
+        let mut raw = vec![0u8; buf.len() - block_size];
+
+        // TODO: remove after debugging
+        println!("DEBUG: Reading raw: {} (buf size: {})", raw.len(), buf.len());
+
+        // Read from the file, and truncate the buffer
+        let len = self.file.read(&mut raw)?;
+        raw.truncate(len);
+
+        // Encrypt raw data if if something was read
+        if len > 0 {
+            // Encrypt the raw data
+            // TODO: store raw bytes that were not encrypted yet
+            let len_enc = self.crypter.update(&raw, buf).unwrap();
+
+            // TODO: remove after debugging
+            println!("DEBUG: Read: {}; Encrypted: {}", len, len_enc);
+
+            // Return the number of encrypted bytes
+            return Ok(len_enc);
+        }
+
+        // Create a buffer for data that might be returned when finalizing
+        let mut output = vec![0u8; block_size];
+
+        // Finalize the crypter, truncate the output
+        let len = self.crypter.finalize(&mut output).unwrap();
+        //output.truncate(len);
+
+        // TODO: remove after debugging
+        if len > 0 {
+            println!("DEBUG: Read {} more bytes when finalized!", len);
+        }
+
+        // Create a buffer for the tag
+        let mut tag = vec![0u8; TAG_LEN];
+
+        // Get the tag
+        self.crypter.get_tag(&mut tag).unwrap();
+
+        // Set the tag
+        self.tag = Some(Cursor::new(tag));
+
+        // Read again, to start reading the tag
+        self.read(buf)
     }
     }
 }
 }