mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-06 02:29:57 +02:00
Allow key set derivation from download URL, start on download logic
This commit is contained in:
parent
e7e0d8c1ff
commit
8101bb3d19
4 changed files with 116 additions and 72 deletions
|
@ -22,38 +22,82 @@ use reader::{
|
||||||
ProgressReader,
|
ProgressReader,
|
||||||
ProgressReporter,
|
ProgressReporter,
|
||||||
};
|
};
|
||||||
use file::file::File as SendFile;
|
use file::file::DownloadFile;
|
||||||
use file::metadata::{Metadata, XFileMetadata};
|
use file::metadata::{Metadata, XFileMetadata};
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
||||||
|
|
||||||
|
/// The name of the header that is used for the authentication nonce.
|
||||||
|
const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
|
||||||
|
|
||||||
/// A file upload action to a Send server.
|
/// A file upload action to a Send server.
|
||||||
pub struct Download {
|
pub struct Download<'a> {
|
||||||
/// The Send host to upload the file to.
|
/// The Send file to download.
|
||||||
host: Url,
|
file: &DownloadFile,
|
||||||
|
|
||||||
/// The file to upload.
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Download {
|
impl<'a> Download<'a> {
|
||||||
/// Construct a new upload action.
|
/// Construct a new download action for the given file.
|
||||||
pub fn new(host: Url, path: PathBuf) -> Self {
|
pub fn new(file: &'a DownloadFile) -> Self {
|
||||||
Self {
|
Self {
|
||||||
host,
|
file,
|
||||||
path,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoke the upload action.
|
/// Invoke the download action.
|
||||||
pub fn invoke(
|
pub fn invoke(
|
||||||
self,
|
self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
reporter: Arc<Mutex<ProgressReporter>>,
|
|
||||||
) -> Result<SendFile> {
|
) -> Result<SendFile> {
|
||||||
// Create file data, generate a key
|
// Create a key set for the file
|
||||||
let file = FileData::from(Box::new(&self.path))?;
|
let key = KeySet::from(self.file);
|
||||||
let key = KeySet::generate(true);
|
|
||||||
|
// 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
|
// Crpate metadata and a file reader
|
||||||
let metadata = self.create_metadata(&key, &file)?;
|
let metadata = self.create_metadata(&key, &file)?;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use openssl::symm::Cipher;
|
use openssl::symm::Cipher;
|
||||||
|
|
||||||
|
use file::file::DownloadFile;
|
||||||
use super::{b64, rand_bytes};
|
use super::{b64, rand_bytes};
|
||||||
use super::hdkf::{derive_auth_key, derive_file_key, derive_meta_key};
|
use super::hdkf::{derive_auth_key, derive_file_key, derive_meta_key};
|
||||||
|
|
||||||
|
@ -51,56 +52,6 @@ impl KeySet {
|
||||||
// Derive all keys
|
// Derive all keys
|
||||||
set.derive();
|
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
|
set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +82,7 @@ impl KeySet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive a file, authentication and metadata key.
|
/// Derive a file, authentication and metadata key.
|
||||||
|
// TODO: add support for deriving with a password and URL
|
||||||
pub fn derive(&mut self) {
|
pub fn derive(&mut self) {
|
||||||
self.file_key = Some(derive_file_key(&self.secret));
|
self.file_key = Some(derive_file_key(&self.secret));
|
||||||
self.auth_key = Some(derive_auth_key(&self.secret, None, None));
|
self.auth_key = Some(derive_auth_key(&self.secret, None, None));
|
||||||
|
|
|
@ -85,6 +85,12 @@ impl File {
|
||||||
|
|
||||||
/// Get the raw secret.
|
/// Get the raw secret.
|
||||||
pub fn secret_raw(&self) -> &Vec<u8> {
|
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
|
&self.secret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,11 +99,22 @@ impl File {
|
||||||
b64::encode(self.secret_raw())
|
b64::encode(self.secret_raw())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the download URL of the file, with the secret key included.
|
/// Check whether a file secret is set.
|
||||||
pub fn download_url(&self) -> Url {
|
/// 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
|
// Get the download URL, and add the secret fragment
|
||||||
let mut url = self.url.clone();
|
let mut url = self.url.clone();
|
||||||
|
if secret && self.has_secret() {
|
||||||
url.set_fragment(Some(&self.secret()));
|
url.set_fragment(Some(&self.secret()));
|
||||||
|
} else {
|
||||||
|
url.set_fragment(None);
|
||||||
|
}
|
||||||
|
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
@ -160,6 +177,7 @@ impl DownloadFile {
|
||||||
let re_path = Regex::new(DOWNLOAD_PATH_PATTERN).unwrap();
|
let re_path = Regex::new(DOWNLOAD_PATH_PATTERN).unwrap();
|
||||||
let id = re_path.captures(url.path())
|
let id = re_path.captures(url.path())
|
||||||
.ok_or(FileParseError::InvalidDownloadUrl)?[1]
|
.ok_or(FileParseError::InvalidDownloadUrl)?[1]
|
||||||
|
.trim()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
// Get the file secret
|
// Get the file secret
|
||||||
|
@ -170,7 +188,7 @@ impl DownloadFile {
|
||||||
.ok_or(FileParseError::InvalidSecret)?
|
.ok_or(FileParseError::InvalidSecret)?
|
||||||
.get(1)
|
.get(1)
|
||||||
{
|
{
|
||||||
secret = b64::decode(raw.as_str())
|
secret = b64::decode(raw.as_str().trim())
|
||||||
.map_err(|_| FileParseError::InvalidSecret)?
|
.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.
|
/// Check whether a file secret is set.
|
||||||
/// This secret must be set to decrypt a downloaded Send file.
|
/// This secret must be set to decrypt a downloaded Send file.
|
||||||
pub fn has_secret(&self) -> bool {
|
pub fn has_secret(&self) -> bool {
|
||||||
|
@ -195,6 +229,20 @@ impl DownloadFile {
|
||||||
pub fn set_secret(&mut self, secret: Vec<u8>) {
|
pub fn set_secret(&mut self, secret: Vec<u8>) {
|
||||||
self.secret = secret;
|
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 {
|
pub enum FileParseError {
|
||||||
|
|
|
@ -41,7 +41,7 @@ impl<'a> Upload<'a> {
|
||||||
let file = ApiUpload::new(host, path).invoke(&client, bar).unwrap();
|
let file = ApiUpload::new(host, path).invoke(&client, bar).unwrap();
|
||||||
|
|
||||||
// Get the download URL, and report it in the console
|
// 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);
|
println!("Download URL: {}", url);
|
||||||
|
|
||||||
// Open the URL in the browser
|
// Open the URL in the browser
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue