|
@@ -31,6 +31,8 @@ use reqwest::header::{
|
|
|
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
|
|
use reqwest::multipart::Part;
|
|
|
|
|
|
+const TAG_LEN: usize = 16;
|
|
|
+
|
|
|
fn main() {
|
|
|
// TODO: a fixed path for now, as upload test
|
|
|
let path = Path::new("/home/timvisee/Pictures/Avatar/1024x1024/Avatar.png");
|
|
@@ -88,65 +90,85 @@ fn main() {
|
|
|
.send()
|
|
|
.unwrap();
|
|
|
|
|
|
- let text = res.text().unwrap();
|
|
|
+ // Parse the response
|
|
|
+ let upload_res: UploadResponse = res.json().unwrap();
|
|
|
|
|
|
- // TODO: remove after debugging
|
|
|
- println!("TEXT: {}", text);
|
|
|
+ // Print the response
|
|
|
+ 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"");
|
|
|
|
|
|
-/// A file reader, that encrypts the file with the given cipher, and appends
|
|
|
-/// the raw cipher tag.
|
|
|
-///
|
|
|
-/// This reader is lazy, and reads/encrypts the file on the fly.
|
|
|
-///
|
|
|
-/// TODO: the current implementation is not lazy
|
|
|
-struct EncryptedFileReaderTagged {
|
|
|
- /// A cursor that reads encrypted file data.
|
|
|
- data: Cursor<Vec<u8>>,
|
|
|
+ // Define the digest to use
|
|
|
+ let digest = Sha256::new();
|
|
|
|
|
|
- /// A tag cursor that reads the tag to append.
|
|
|
- tag: Cursor<Vec<u8>>,
|
|
|
-}
|
|
|
+ let mut pkr: Vec<u8> = vec![0u8; digest.output_bytes()];
|
|
|
+ hkdf_extract(digest, salt, ikm, &mut pkr);
|
|
|
|
|
|
-impl EncryptedFileReaderTagged {
|
|
|
- /// Construct a new reader.
|
|
|
- pub fn new(mut file: File, mut cipher: AesGcm<'static>) -> Self {
|
|
|
- // Get the file length
|
|
|
- let len = file.metadata().unwrap().len() as usize;
|
|
|
+ let mut okm: Vec<u8> = vec![0u8; length];
|
|
|
+ hkdf_expand(digest, &pkr, info, &mut okm);
|
|
|
|
|
|
- // Create a file data buffer and an encrypted buffer
|
|
|
- let mut data = Vec::with_capacity(len);
|
|
|
- file.read_to_end(&mut data).unwrap();
|
|
|
- let mut encrypted = vec![0u8; data.len()];
|
|
|
+ okm
|
|
|
+}
|
|
|
|
|
|
- // Encrypt the data, get the tag
|
|
|
- let mut tag = vec![0u8; TAG_LEN];
|
|
|
- cipher.encrypt(&data, &mut encrypted, &mut tag);
|
|
|
+fn derive_file_key(secret: &[u8]) -> Vec<u8> {
|
|
|
+ hkdf(16, secret, None, Some(b"encryption"))
|
|
|
+}
|
|
|
|
|
|
- // Construct the reader and return
|
|
|
- EncryptedFileReaderTagged {
|
|
|
- data: Cursor::new(encrypted),
|
|
|
- tag: Cursor::new(tag),
|
|
|
- }
|
|
|
+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!();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-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,
|
|
|
- _ => {},
|
|
|
- }
|
|
|
+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(),
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Read the tag if it's empty
|
|
|
- self.tag.read(buf)
|
|
|
+ /// Convert this structure to a JSON string.
|
|
|
+ pub fn to_json(&self) -> String {
|
|
|
+ serde_json::to_string(&self).unwrap()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -184,75 +206,86 @@ impl Header for XFileMetadata {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[derive(Serialize)]
|
|
|
-struct Metadata {
|
|
|
- /// The input vector
|
|
|
- iv: String,
|
|
|
-
|
|
|
- /// The file name
|
|
|
- name: String,
|
|
|
+/// A file reader, that encrypts the file with the given cipher, and appends
|
|
|
+/// the raw cipher tag.
|
|
|
+///
|
|
|
+/// This reader is lazy, and reads/encrypts the file on the fly.
|
|
|
+///
|
|
|
+/// TODO: the current implementation is not lazy
|
|
|
+struct EncryptedFileReaderTagged {
|
|
|
+ /// A cursor that reads encrypted file data.
|
|
|
+ data: Cursor<Vec<u8>>,
|
|
|
|
|
|
- /// The file mimetype
|
|
|
- #[serde(rename="type")]
|
|
|
- mime: String,
|
|
|
+ /// A tag cursor that reads the tag to append.
|
|
|
+ tag: Cursor<Vec<u8>>,
|
|
|
}
|
|
|
|
|
|
-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(),
|
|
|
- }
|
|
|
- }
|
|
|
+impl EncryptedFileReaderTagged {
|
|
|
+ /// Construct a new reader.
|
|
|
+ pub fn new(mut file: File, mut cipher: AesGcm<'static>) -> Self {
|
|
|
+ // Get the file length
|
|
|
+ let len = file.metadata().unwrap().len() as usize;
|
|
|
|
|
|
- /// Convert this structure to a JSON string.
|
|
|
- pub fn to_json(&self) -> String {
|
|
|
- serde_json::to_string(&self).unwrap()
|
|
|
+ // Create a file data buffer and an encrypted buffer
|
|
|
+ let mut data = Vec::with_capacity(len);
|
|
|
+ file.read_to_end(&mut data).unwrap();
|
|
|
+ let mut encrypted = vec![0u8; data.len()];
|
|
|
+
|
|
|
+ // Encrypt the data, get the tag
|
|
|
+ let mut tag = vec![0u8; TAG_LEN];
|
|
|
+ cipher.encrypt(&data, &mut encrypted, &mut tag);
|
|
|
+
|
|
|
+ // Construct the reader and return
|
|
|
+ EncryptedFileReaderTagged {
|
|
|
+ data: Cursor::new(encrypted),
|
|
|
+ tag: Cursor::new(tag),
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-fn derive_file_key(secret: &[u8]) -> Vec<u8> {
|
|
|
- hkdf(16, secret, None, Some(b"encryption"))
|
|
|
-}
|
|
|
+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,
|
|
|
+ _ => {},
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-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!();
|
|
|
+ // Read the tag if it's empty
|
|
|
+ self.tag.read(buf)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-fn derive_meta_key(secret: &[u8]) -> Vec<u8> {
|
|
|
- hkdf(16, secret, None, Some(b"metadata"))
|
|
|
+/// The response from the server after a file has been uploaded.
|
|
|
+/// This response contains the file ID and owner key, to manage the file.
|
|
|
+///
|
|
|
+/// It also contains the download URL, although an additional secret is
|
|
|
+/// required.
|
|
|
+///
|
|
|
+/// The download URL can be generated using `download_url()` which will
|
|
|
+/// include the required secret in the URL.
|
|
|
+#[derive(Debug, Deserialize)]
|
|
|
+struct UploadResponse {
|
|
|
+ /// The URL the file is reachable at.
|
|
|
+ /// 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,
|
|
|
}
|
|
|
|
|
|
-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
|
|
|
+impl UploadResponse {
|
|
|
+ /// Get the download URL, including the secret.
|
|
|
+ ///
|
|
|
+ /// The secret bytes must be passed to `secret`.
|
|
|
+ pub fn download_url(&self, secret: &[u8]) -> String {
|
|
|
+ format!("{}#{}", self.url, base64::encode(secret))
|
|
|
+ }
|
|
|
}
|