mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-03 09:39:15 +02:00
Extract encrypted file reader to module
This commit is contained in:
parent
cce9a28dc3
commit
396cf97963
2 changed files with 222 additions and 214 deletions
219
src/main.rs
219
src/main.rs
|
@ -12,22 +12,18 @@ extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate sha2;
|
extern crate sha2;
|
||||||
|
|
||||||
use std::cmp::min;
|
mod reader;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufReader, Cursor, Read};
|
use std::io::BufReader;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use hyper::error::Error as HyperError;
|
use hyper::error::Error as HyperError;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use openssl::symm::{
|
use openssl::symm::{Cipher, encrypt_aead};
|
||||||
Cipher,
|
|
||||||
Crypter,
|
|
||||||
encrypt_aead,
|
|
||||||
Mode as CrypterMode,
|
|
||||||
};
|
|
||||||
use rand::{Rng, thread_rng};
|
use rand::{Rng, thread_rng};
|
||||||
use reqwest::header::{
|
use reqwest::header::{
|
||||||
Authorization,
|
Authorization,
|
||||||
|
@ -39,8 +35,7 @@ use reqwest::mime::APPLICATION_OCTET_STREAM;
|
||||||
use reqwest::multipart::Part;
|
use reqwest::multipart::Part;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
/// The length in bytes of crytographic tags that are used.
|
use reader::EncryptedFileReaderTagged;
|
||||||
const TAG_LEN: usize = 16;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Handle CLI arguments
|
// Handle CLI arguments
|
||||||
|
@ -264,210 +259,6 @@ impl Header for XFileMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A lazy file reader, that encrypts the file with the given `cipher`
|
|
||||||
/// and appends the cryptographic tag to the end of it.
|
|
||||||
///
|
|
||||||
/// 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 reader encrypts the file data with an appended cryptographic tag.
|
|
||||||
///
|
|
||||||
/// The reader uses a small internal buffer as data is encrypted in blocks,
|
|
||||||
/// which may output more data than fits in the given buffer while reading.
|
|
||||||
/// The excess data is then returned on the next read.
|
|
||||||
struct EncryptedFileReaderTagged {
|
|
||||||
/// The raw file that is read from.
|
|
||||||
file: File,
|
|
||||||
|
|
||||||
/// The cipher type used for encrypting.
|
|
||||||
cipher: Cipher,
|
|
||||||
|
|
||||||
/// The crypter used for encrypting the read file.
|
|
||||||
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>>>,
|
|
||||||
|
|
||||||
/// The internal buffer, containing encrypted data that has yet to be
|
|
||||||
/// outputted to the reader. This data is always outputted before any new
|
|
||||||
/// data is produced.
|
|
||||||
internal_buf: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EncryptedFileReaderTagged {
|
|
||||||
/// Construct a new reader for the given `file` with the given `cipher`.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// It is recommended to wrap this reader in some sort of buffer, such as:
|
|
||||||
/// `std::io::BufReader`
|
|
||||||
pub fn new(file: File, cipher: Cipher, key: &[u8], iv: &[u8])
|
|
||||||
-> Result<Self, io::Error>
|
|
||||||
{
|
|
||||||
// Build the crypter
|
|
||||||
let crypter = Crypter::new(
|
|
||||||
cipher,
|
|
||||||
CrypterMode::Encrypt,
|
|
||||||
key,
|
|
||||||
Some(iv),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Construct the encrypted reader
|
|
||||||
Ok(
|
|
||||||
EncryptedFileReaderTagged {
|
|
||||||
file,
|
|
||||||
cipher,
|
|
||||||
crypter,
|
|
||||||
tag: None,
|
|
||||||
internal_buf: Vec::new(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the total length of the encrypted file with the appended
|
|
||||||
/// tag.
|
|
||||||
/// Useful in combination with some progress monitor, to determine how much
|
|
||||||
/// of the file is read or for example; sent over the network.
|
|
||||||
pub fn len(&self) -> Result<u64, io::Error> {
|
|
||||||
Ok(self.file.metadata()?.len() + TAG_LEN as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read data from the internal buffer if there is any data in it, into
|
|
||||||
/// the given `buf`.
|
|
||||||
///
|
|
||||||
/// The number of bytes that were read into `buf` is returned.
|
|
||||||
///
|
|
||||||
/// If there is no data to be read, or `buf` has a zero size, `0` is always
|
|
||||||
/// returned.
|
|
||||||
fn read_internal(&mut self, buf: &mut [u8]) -> usize {
|
|
||||||
// Return if there is no data to read
|
|
||||||
if self.internal_buf.is_empty() || buf.len() == 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine how much data will be read
|
|
||||||
let len = min(buf.len(), self.internal_buf.len());
|
|
||||||
|
|
||||||
// Slice the section we will read from, copy to the reader
|
|
||||||
{
|
|
||||||
let (out, _) = self.internal_buf.split_at(len);
|
|
||||||
let (buf, _) = buf.split_at_mut(len);
|
|
||||||
buf.copy_from_slice(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drain the read data from the internal buffer
|
|
||||||
self.internal_buf.drain(..len);
|
|
||||||
|
|
||||||
len
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read data directly from the file, and encrypt it.
|
|
||||||
///
|
|
||||||
/// Because data may be encrypted in blocks, it is possible more data
|
|
||||||
/// is produced than fits in the given `buf`. In that case the excess data
|
|
||||||
/// is stored in an internal buffer, and is ouputted the next time being
|
|
||||||
/// read from the reader.
|
|
||||||
///
|
|
||||||
/// The number of bytes that is read into `buf` is returned.
|
|
||||||
fn read_file_encrypted(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
|
||||||
// Get the block size, determine the buffer size, create a data buffer
|
|
||||||
let block_size = self.cipher.block_size();
|
|
||||||
let mut data = vec![0u8; buf.len()];
|
|
||||||
|
|
||||||
// Read the file, return if nothing was read
|
|
||||||
let len = self.file.read(&mut data)?;
|
|
||||||
if len == 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an encrypted buffer, truncate the data buffer
|
|
||||||
let mut encrypted = vec![0u8; len + block_size];
|
|
||||||
data.truncate(len);
|
|
||||||
|
|
||||||
// Encrypt the data that was read
|
|
||||||
let len = self.crypter.update(&data, &mut encrypted)?;
|
|
||||||
|
|
||||||
// Calculate how many bytes will be copied to the reader
|
|
||||||
let out_len = min(buf.len(), len);
|
|
||||||
|
|
||||||
// Fill the reader buffer
|
|
||||||
let (out, remaining) = encrypted.split_at(out_len);
|
|
||||||
let (buf, _) = buf.split_at_mut(out_len);
|
|
||||||
buf.copy_from_slice(out);
|
|
||||||
|
|
||||||
// Splice to the actual remaining bytes, store it for later
|
|
||||||
let (store, _) = remaining.split_at(len - out_len);
|
|
||||||
self.internal_buf.extend(store.iter());
|
|
||||||
|
|
||||||
// Return the number of bytes read to the reader
|
|
||||||
Ok(out_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finalize the crypter once it is done encrypthing the whole file.
|
|
||||||
/// This finalization step produces a tag that is placed after the
|
|
||||||
/// encrypted file data.
|
|
||||||
///
|
|
||||||
/// This step must be invoked to start reading the tag,
|
|
||||||
/// and after it has been invoked no data must be encrypted anymore.
|
|
||||||
///
|
|
||||||
/// This method must only be invoked once.
|
|
||||||
fn finalize_file(&mut self) -> Result<(), io::Error> {
|
|
||||||
// Finalize the crypter, catch any remaining output
|
|
||||||
let mut output = vec![0u8; self.cipher.block_size()];
|
|
||||||
let len = self.crypter.finalize(&mut output)?;
|
|
||||||
|
|
||||||
// Move additional output in the internal buffer
|
|
||||||
if len > 0 {
|
|
||||||
self.internal_buf.extend(output.iter().take(len));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the encryption tag, and create an internal reader for it
|
|
||||||
let mut tag = vec![0u8; TAG_LEN];
|
|
||||||
self.crypter.get_tag(&mut tag)?;
|
|
||||||
self.tag = Some(Cursor::new(tag));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for EncryptedFileReaderTagged {
|
|
||||||
/// Read from the encrypted file, and then the encryption tag.
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
|
||||||
// Read from the internal buffer, return full or splice to empty
|
|
||||||
let len = self.read_internal(buf);
|
|
||||||
if len >= buf.len() {
|
|
||||||
return Ok(len);
|
|
||||||
}
|
|
||||||
let (_, buf) = buf.split_at_mut(len);
|
|
||||||
|
|
||||||
// Keep track of the total number of read bytes, to return
|
|
||||||
let mut total = len;
|
|
||||||
|
|
||||||
// If the tag reader has been created, only read from that one
|
|
||||||
if let Some(ref mut tag) = self.tag {
|
|
||||||
return Ok(tag.read(buf)? + total);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the encrypted file, return full or splice to empty
|
|
||||||
let len = self.read_file_encrypted(buf)?;
|
|
||||||
total += len;
|
|
||||||
if len >= buf.len() {
|
|
||||||
return Ok(total);
|
|
||||||
}
|
|
||||||
let (_, buf) = buf.split_at_mut(len);
|
|
||||||
|
|
||||||
// Finalize the file crypter, and build the tag
|
|
||||||
self.finalize_file()?;
|
|
||||||
|
|
||||||
// Try to fill the remaining part of the buffer
|
|
||||||
Ok(self.read(buf)? + total)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The response from the server after a file has been uploaded.
|
/// The response from the server after a file has been uploaded.
|
||||||
/// This response contains the file ID and owner key, to manage the file.
|
/// This response contains the file ID and owner key, to manage the file.
|
||||||
///
|
///
|
||||||
|
|
217
src/reader.rs
Normal file
217
src/reader.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Cursor, Read};
|
||||||
|
|
||||||
|
use openssl::symm::{
|
||||||
|
Cipher,
|
||||||
|
Crypter,
|
||||||
|
Mode as CrypterMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The length in bytes of crytographic tags that are used.
|
||||||
|
const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
|
/// A lazy file reader, that encrypts the file with the given `cipher`
|
||||||
|
/// and appends the cryptographic tag to the end of it.
|
||||||
|
///
|
||||||
|
/// 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 reader encrypts the file data with an appended cryptographic tag.
|
||||||
|
///
|
||||||
|
/// The reader uses a small internal buffer as data is encrypted in blocks,
|
||||||
|
/// which may output more data than fits in the given buffer while reading.
|
||||||
|
/// The excess data is then returned on the next read.
|
||||||
|
pub struct EncryptedFileReaderTagged {
|
||||||
|
/// The raw file that is read from.
|
||||||
|
file: File,
|
||||||
|
|
||||||
|
/// The cipher type used for encrypting.
|
||||||
|
cipher: Cipher,
|
||||||
|
|
||||||
|
/// The crypter used for encrypting the read file.
|
||||||
|
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>>>,
|
||||||
|
|
||||||
|
/// The internal buffer, containing encrypted data that has yet to be
|
||||||
|
/// outputted to the reader. This data is always outputted before any new
|
||||||
|
/// data is produced.
|
||||||
|
internal_buf: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncryptedFileReaderTagged {
|
||||||
|
/// Construct a new reader for the given `file` with the given `cipher`.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// It is recommended to wrap this reader in some sort of buffer, such as:
|
||||||
|
/// `std::io::BufReader`
|
||||||
|
pub fn new(file: File, cipher: Cipher, key: &[u8], iv: &[u8])
|
||||||
|
-> Result<Self, io::Error>
|
||||||
|
{
|
||||||
|
// Build the crypter
|
||||||
|
let crypter = Crypter::new(
|
||||||
|
cipher,
|
||||||
|
CrypterMode::Encrypt,
|
||||||
|
key,
|
||||||
|
Some(iv),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Construct the encrypted reader
|
||||||
|
Ok(
|
||||||
|
EncryptedFileReaderTagged {
|
||||||
|
file,
|
||||||
|
cipher,
|
||||||
|
crypter,
|
||||||
|
tag: None,
|
||||||
|
internal_buf: Vec::new(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the total length of the encrypted file with the appended
|
||||||
|
/// tag.
|
||||||
|
/// Useful in combination with some progress monitor, to determine how much
|
||||||
|
/// of the file is read or for example; sent over the network.
|
||||||
|
pub fn len(&self) -> Result<u64, io::Error> {
|
||||||
|
Ok(self.file.metadata()?.len() + TAG_LEN as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data from the internal buffer if there is any data in it, into
|
||||||
|
/// the given `buf`.
|
||||||
|
///
|
||||||
|
/// The number of bytes that were read into `buf` is returned.
|
||||||
|
///
|
||||||
|
/// If there is no data to be read, or `buf` has a zero size, `0` is always
|
||||||
|
/// returned.
|
||||||
|
fn read_internal(&mut self, buf: &mut [u8]) -> usize {
|
||||||
|
// Return if there is no data to read
|
||||||
|
if self.internal_buf.is_empty() || buf.len() == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine how much data will be read
|
||||||
|
let len = min(buf.len(), self.internal_buf.len());
|
||||||
|
|
||||||
|
// Slice the section we will read from, copy to the reader
|
||||||
|
{
|
||||||
|
let (out, _) = self.internal_buf.split_at(len);
|
||||||
|
let (buf, _) = buf.split_at_mut(len);
|
||||||
|
buf.copy_from_slice(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drain the read data from the internal buffer
|
||||||
|
self.internal_buf.drain(..len);
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data directly from the file, and encrypt it.
|
||||||
|
///
|
||||||
|
/// Because data may be encrypted in blocks, it is possible more data
|
||||||
|
/// is produced than fits in the given `buf`. In that case the excess data
|
||||||
|
/// is stored in an internal buffer, and is ouputted the next time being
|
||||||
|
/// read from the reader.
|
||||||
|
///
|
||||||
|
/// The number of bytes that is read into `buf` is returned.
|
||||||
|
fn read_file_encrypted(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||||
|
// Get the block size, determine the buffer size, create a data buffer
|
||||||
|
let block_size = self.cipher.block_size();
|
||||||
|
let mut data = vec![0u8; buf.len()];
|
||||||
|
|
||||||
|
// Read the file, return if nothing was read
|
||||||
|
let len = self.file.read(&mut data)?;
|
||||||
|
if len == 0 {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an encrypted buffer, truncate the data buffer
|
||||||
|
let mut encrypted = vec![0u8; len + block_size];
|
||||||
|
data.truncate(len);
|
||||||
|
|
||||||
|
// Encrypt the data that was read
|
||||||
|
let len = self.crypter.update(&data, &mut encrypted)?;
|
||||||
|
|
||||||
|
// Calculate how many bytes will be copied to the reader
|
||||||
|
let out_len = min(buf.len(), len);
|
||||||
|
|
||||||
|
// Fill the reader buffer
|
||||||
|
let (out, remaining) = encrypted.split_at(out_len);
|
||||||
|
let (buf, _) = buf.split_at_mut(out_len);
|
||||||
|
buf.copy_from_slice(out);
|
||||||
|
|
||||||
|
// Splice to the actual remaining bytes, store it for later
|
||||||
|
let (store, _) = remaining.split_at(len - out_len);
|
||||||
|
self.internal_buf.extend(store.iter());
|
||||||
|
|
||||||
|
// Return the number of bytes read to the reader
|
||||||
|
Ok(out_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finalize the crypter once it is done encrypthing the whole file.
|
||||||
|
/// This finalization step produces a tag that is placed after the
|
||||||
|
/// encrypted file data.
|
||||||
|
///
|
||||||
|
/// This step must be invoked to start reading the tag,
|
||||||
|
/// and after it has been invoked no data must be encrypted anymore.
|
||||||
|
///
|
||||||
|
/// This method must only be invoked once.
|
||||||
|
fn finalize_file(&mut self) -> Result<(), io::Error> {
|
||||||
|
// Finalize the crypter, catch any remaining output
|
||||||
|
let mut output = vec![0u8; self.cipher.block_size()];
|
||||||
|
let len = self.crypter.finalize(&mut output)?;
|
||||||
|
|
||||||
|
// Move additional output in the internal buffer
|
||||||
|
if len > 0 {
|
||||||
|
self.internal_buf.extend(output.iter().take(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the encryption tag, and create an internal reader for it
|
||||||
|
let mut tag = vec![0u8; TAG_LEN];
|
||||||
|
self.crypter.get_tag(&mut tag)?;
|
||||||
|
self.tag = Some(Cursor::new(tag));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reader trait implementation.
|
||||||
|
impl Read for EncryptedFileReaderTagged {
|
||||||
|
/// Read from the encrypted file, and then the encryption tag.
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||||
|
// Read from the internal buffer, return full or splice to empty
|
||||||
|
let len = self.read_internal(buf);
|
||||||
|
if len >= buf.len() {
|
||||||
|
return Ok(len);
|
||||||
|
}
|
||||||
|
let (_, buf) = buf.split_at_mut(len);
|
||||||
|
|
||||||
|
// Keep track of the total number of read bytes, to return
|
||||||
|
let mut total = len;
|
||||||
|
|
||||||
|
// If the tag reader has been created, only read from that one
|
||||||
|
if let Some(ref mut tag) = self.tag {
|
||||||
|
return Ok(tag.read(buf)? + total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the encrypted file, return full or splice to empty
|
||||||
|
let len = self.read_file_encrypted(buf)?;
|
||||||
|
total += len;
|
||||||
|
if len >= buf.len() {
|
||||||
|
return Ok(total);
|
||||||
|
}
|
||||||
|
let (_, buf) = buf.split_at_mut(len);
|
||||||
|
|
||||||
|
// Finalize the file crypter, and build the tag
|
||||||
|
self.finalize_file()?;
|
||||||
|
|
||||||
|
// Try to fill the remaining part of the buffer
|
||||||
|
Ok(self.read(buf)? + total)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue