mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-03 09:39:15 +02:00
First working implementation, nicly handle upload response, cleanup
This commit is contained in:
parent
9753f1e3cc
commit
0786ed5917
1 changed files with 138 additions and 105 deletions
243
src/main.rs
243
src/main.rs
|
@ -31,6 +31,8 @@ use reqwest::header::{
|
||||||
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
||||||
use reqwest::multipart::Part;
|
use reqwest::multipart::Part;
|
||||||
|
|
||||||
|
const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// TODO: a fixed path for now, as upload test
|
// TODO: a fixed path for now, as upload test
|
||||||
let path = Path::new("/home/timvisee/Pictures/Avatar/1024x1024/Avatar.png");
|
let path = Path::new("/home/timvisee/Pictures/Avatar/1024x1024/Avatar.png");
|
||||||
|
@ -88,13 +90,121 @@ fn main() {
|
||||||
.send()
|
.send()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let text = res.text().unwrap();
|
// Parse the response
|
||||||
|
let upload_res: UploadResponse = res.json().unwrap();
|
||||||
|
|
||||||
// TODO: remove after debugging
|
// Print the response
|
||||||
println!("TEXT: {}", text);
|
println!("Response: {:#?}", upload_res);
|
||||||
|
println!("Secret key: {}", base64::encode(&secret));
|
||||||
|
println!("Download URL: {}", upload_res.download_url(&secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAG_LEN: usize = 16;
|
fn hkdf<'a>(
|
||||||
|
length: usize,
|
||||||
|
ikm: &[u8],
|
||||||
|
salt: Option<&[u8]>,
|
||||||
|
info: Option<&[u8]>
|
||||||
|
) -> Vec<u8> {
|
||||||
|
// Get the salt and info parameters, use defaults if undefined
|
||||||
|
let salt = salt.unwrap_or(b"");
|
||||||
|
let info = info.unwrap_or(b"");
|
||||||
|
|
||||||
|
// Define the digest to use
|
||||||
|
let digest = Sha256::new();
|
||||||
|
|
||||||
|
let mut pkr: Vec<u8> = vec![0u8; digest.output_bytes()];
|
||||||
|
hkdf_extract(digest, salt, ikm, &mut pkr);
|
||||||
|
|
||||||
|
let mut okm: Vec<u8> = vec![0u8; length];
|
||||||
|
hkdf_expand(digest, &pkr, info, &mut okm);
|
||||||
|
|
||||||
|
okm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_file_key(secret: &[u8]) -> Vec<u8> {
|
||||||
|
hkdf(16, secret, None, Some(b"encryption"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_auth_key(secret: &[u8], password: Option<String>, url: Option<String>) -> Vec<u8> {
|
||||||
|
if password.is_none() {
|
||||||
|
hkdf(64, secret, None, Some(b"authentication"))
|
||||||
|
} else {
|
||||||
|
// TODO: implement this
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_meta_key(secret: &[u8]) -> Vec<u8> {
|
||||||
|
hkdf(16, secret, None, Some(b"metadata"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Metadata {
|
||||||
|
/// The input vector
|
||||||
|
iv: String,
|
||||||
|
|
||||||
|
/// The file name
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// The file mimetype
|
||||||
|
#[serde(rename="type")]
|
||||||
|
mime: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
/// Construct metadata from the given properties.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * iv: initialisation vector
|
||||||
|
/// * name: file name
|
||||||
|
/// * mime: file mimetype
|
||||||
|
pub fn from(iv: &[u8], name: String, mime: Mime) -> Self {
|
||||||
|
Metadata {
|
||||||
|
iv: base64::encode(iv),
|
||||||
|
name,
|
||||||
|
mime: mime.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this structure to a JSON string.
|
||||||
|
pub fn to_json(&self) -> String {
|
||||||
|
serde_json::to_string(&self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct XFileMetadata {
|
||||||
|
/// The metadata, as a base64 encoded string.
|
||||||
|
metadata: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XFileMetadata {
|
||||||
|
pub fn new(metadata: String) -> Self {
|
||||||
|
XFileMetadata {
|
||||||
|
metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(bytes: &[u8]) -> Self {
|
||||||
|
XFileMetadata::new(base64::encode(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for XFileMetadata {
|
||||||
|
fn header_name() -> &'static str {
|
||||||
|
"X-File-Metadata"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_header(_raw: &Raw) -> Result<Self, HyperError> {
|
||||||
|
// TODO: implement this some time
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_header(&self, f: &mut HeaderFormatter) -> fmt::Result {
|
||||||
|
// TODO: is this encoding base64 for us?
|
||||||
|
f.fmt_line(&self.metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A file reader, that encrypts the file with the given cipher, and appends
|
/// A file reader, that encrypts the file with the given cipher, and appends
|
||||||
/// the raw cipher tag.
|
/// the raw cipher tag.
|
||||||
|
@ -150,109 +260,32 @@ impl Read for EncryptedFileReaderTagged {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
/// The response from the server after a file has been uploaded.
|
||||||
struct XFileMetadata {
|
/// This response contains the file ID and owner key, to manage the file.
|
||||||
/// The metadata, as a base64 encoded string.
|
|
||||||
metadata: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XFileMetadata {
|
|
||||||
pub fn new(metadata: String) -> Self {
|
|
||||||
XFileMetadata {
|
|
||||||
metadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from(bytes: &[u8]) -> Self {
|
|
||||||
XFileMetadata::new(base64::encode(bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header for XFileMetadata {
|
|
||||||
fn header_name() -> &'static str {
|
|
||||||
"X-File-Metadata"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_header(_raw: &Raw) -> Result<Self, HyperError> {
|
|
||||||
// TODO: implement this some time
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_header(&self, f: &mut HeaderFormatter) -> fmt::Result {
|
|
||||||
// TODO: is this encoding base64 for us?
|
|
||||||
f.fmt_line(&self.metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Metadata {
|
|
||||||
/// The input vector
|
|
||||||
iv: String,
|
|
||||||
|
|
||||||
/// The file name
|
|
||||||
name: String,
|
|
||||||
|
|
||||||
/// The file mimetype
|
|
||||||
#[serde(rename="type")]
|
|
||||||
mime: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Metadata {
|
|
||||||
/// Construct metadata from the given properties.
|
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// It also contains the download URL, although an additional secret is
|
||||||
/// * iv: initialisation vector
|
/// required.
|
||||||
/// * name: file name
|
///
|
||||||
/// * mime: file mimetype
|
/// The download URL can be generated using `download_url()` which will
|
||||||
pub fn from(iv: &[u8], name: String, mime: Mime) -> Self {
|
/// include the required secret in the URL.
|
||||||
Metadata {
|
#[derive(Debug, Deserialize)]
|
||||||
iv: base64::encode(iv),
|
struct UploadResponse {
|
||||||
name,
|
/// The URL the file is reachable at.
|
||||||
mime: mime.to_string(),
|
/// This includes the file ID, but does not include the secret.
|
||||||
}
|
url: String,
|
||||||
|
|
||||||
|
/// The owner key, used to do further file modifications.
|
||||||
|
owner: String,
|
||||||
|
|
||||||
|
/// The file ID.
|
||||||
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this structure to a JSON string.
|
impl UploadResponse {
|
||||||
pub fn to_json(&self) -> String {
|
/// Get the download URL, including the secret.
|
||||||
serde_json::to_string(&self).unwrap()
|
///
|
||||||
|
/// The secret bytes must be passed to `secret`.
|
||||||
|
pub fn download_url(&self, secret: &[u8]) -> String {
|
||||||
|
format!("{}#{}", self.url, base64::encode(secret))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_file_key(secret: &[u8]) -> Vec<u8> {
|
|
||||||
hkdf(16, secret, None, Some(b"encryption"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derive_auth_key(secret: &[u8], password: Option<String>, url: Option<String>) -> Vec<u8> {
|
|
||||||
if password.is_none() {
|
|
||||||
hkdf(64, secret, None, Some(b"authentication"))
|
|
||||||
} else {
|
|
||||||
// TODO: implement this
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derive_meta_key(secret: &[u8]) -> Vec<u8> {
|
|
||||||
hkdf(16, secret, None, Some(b"metadata"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hkdf<'a>(
|
|
||||||
length: usize,
|
|
||||||
ikm: &[u8],
|
|
||||||
salt: Option<&[u8]>,
|
|
||||||
info: Option<&[u8]>
|
|
||||||
) -> Vec<u8> {
|
|
||||||
// Get the salt and info parameters, use defaults if undefined
|
|
||||||
let salt = salt.unwrap_or(b"");
|
|
||||||
let info = info.unwrap_or(b"");
|
|
||||||
|
|
||||||
// Define the digest to use
|
|
||||||
let digest = Sha256::new();
|
|
||||||
|
|
||||||
let mut pkr: Vec<u8> = vec![0u8; digest.output_bytes()];
|
|
||||||
hkdf_extract(digest, salt, ikm, &mut pkr);
|
|
||||||
|
|
||||||
let mut okm: Vec<u8> = vec![0u8; length];
|
|
||||||
hkdf_expand(digest, &pkr, info, &mut okm);
|
|
||||||
|
|
||||||
okm
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue