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

This commit is contained in:
timvisee 2018-03-06 00:52:45 +01:00
parent c8cb0d7e2e
commit b2b2aeb306
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
4 changed files with 121 additions and 46 deletions

1
.gitignore vendored
View file

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

14
Cargo.lock generated
View file

@ -127,6 +127,7 @@ dependencies = [
"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)",
"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)",
"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)",
@ -415,6 +416,18 @@ dependencies = [
"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]]
name = "openssl-sys"
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_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 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-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"

View file

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

View file

@ -4,6 +4,7 @@ extern crate crypto;
extern crate hyper;
extern crate mime_guess;
extern crate open;
extern crate openssl;
extern crate rand;
extern crate reqwest;
#[macro_use]
@ -12,7 +13,7 @@ extern crate serde_json;
use std::fmt;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::io::{self, BufReader, Cursor, Read};
use std::path::Path;
use clap::{App, Arg};
@ -24,6 +25,11 @@ use crypto::hkdf::{hkdf_extract, hkdf_expand};
use crypto::sha2::Sha256;
use hyper::error::Error as HyperError;
use mime_guess::Mime;
use openssl::symm::{
Cipher,
Crypter,
Mode as CrypterMode,
};
use rand::{Rng, thread_rng};
use reqwest::header::{
Authorization,
@ -77,9 +83,10 @@ fn main() {
let auth_key = derive_auth_key(&secret, None, None);
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"");
// Guess the mimetype of the file
@ -97,7 +104,15 @@ fn main() {
// Open the file and create an encrypted file reader
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
let part = Part::reader(reader)
@ -124,9 +139,12 @@ fn main() {
println!("Download URL: {}", url);
// 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.
///
/// # Arguments
@ -163,7 +181,7 @@ fn derive_file_key(secret: &[u8]) -> Vec<u8> {
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() {
hkdf(64, secret, Some(b"authentication"))
} 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 {
/// 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 {
@ -270,43 +294,78 @@ impl EncryptedFileReaderTagged {
/// This method consumes twice the size of the file in memory while
/// constructing, and constructs a reader that has a size similar to the
/// 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 {
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 {
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)
}
}