Skip to content

Commit 5d966c6

Browse files
committed
Cleaner API for encrypt_file_data / decrypt_file_data
- Struct for encrypted and unencrypted data - Allow decrypting thumbnails as well, not just files - Symmetric APIs - Add unit test
1 parent 55b3e14 commit 5d966c6

File tree

5 files changed

+134
-35
lines changed

5 files changed

+134
-35
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Possible log types:
1515
### Unreleased
1616

1717
- [added] Support downloading of blobs (#65)
18+
- [added] New `decrypt_file_data` helper function (#67)
1819
- [changed] Remove `mime` dependency in favor of plain strings (#64)
20+
- [changed] The API of `encrypt_file_data` has changed (#67)
1921

2022
### v0.15.1 (2021-12-06)
2123

examples/download_blob.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use std::process;
22

33
use data_encoding::HEXLOWER_PERMISSIVE;
44
use docopt::Docopt;
5-
use threema_gateway::{ApiBuilder, BlobId};
5+
use threema_gateway::{decrypt_file_data, ApiBuilder, BlobId, EncryptedFileData, Key};
66

77
const USAGE: &str = "
8-
Usage: download_blob [options] <our-id> <secret> <private-key> <blob-id>
8+
Usage: download_blob [options] <our-id> <secret> <private-key> <blob-id> [<blob-key>]
99
1010
Options:
1111
-h, --help Show this help
@@ -28,6 +28,15 @@ async fn main() {
2828
process::exit(1);
2929
}
3030
};
31+
let blob_key_raw = args.get_str("<blob-key>");
32+
let blob_key = if !blob_key_raw.is_empty() {
33+
let bytes = HEXLOWER_PERMISSIVE
34+
.decode(blob_key_raw.as_bytes())
35+
.expect("Invalid blob key");
36+
Some(Key::from_slice(&bytes).expect("Invalid blob key bytes"))
37+
} else {
38+
None
39+
};
3140

3241
// Create E2eApi instance
3342
let api = ApiBuilder::new(our_id, secret)
@@ -37,14 +46,30 @@ async fn main() {
3746

3847
// Download blob
3948
println!("Downloading blob with ID {}...", blob_id);
40-
match api.blob_download(&blob_id).await {
49+
let bytes = match api.blob_download(&blob_id).await {
4150
Err(e) => {
4251
eprintln!("Could not download blob: {}", e);
4352
process::exit(1);
4453
}
4554
Ok(bytes) => {
4655
println!("Downloaded {} blob bytes:", bytes.len());
47-
println!("{}", HEXLOWER_PERMISSIVE.encode(&bytes));
56+
bytes
4857
}
58+
};
59+
if let Some(key) = blob_key {
60+
let decrypted = decrypt_file_data(
61+
&EncryptedFileData {
62+
file: bytes,
63+
thumbnail: None,
64+
},
65+
&key,
66+
)
67+
.expect("Could not decrypt file data");
68+
println!(
69+
"Decrypted bytes: {}",
70+
HEXLOWER_PERMISSIVE.encode(&decrypted.file)
71+
);
72+
} else {
73+
println!("Encrypted bytes: {}", HEXLOWER_PERMISSIVE.encode(&bytes));
4974
}
5075
}

examples/send_e2e_file.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{ffi::OsStr, fs::File, io::Read, path::Path, process};
22

33
use docopt::Docopt;
4-
use threema_gateway::{encrypt_file_data, ApiBuilder, FileMessage, RenderingType};
4+
use threema_gateway::{encrypt_file_data, ApiBuilder, FileData, FileMessage, RenderingType};
55

66
const USAGE: &str = "
77
Usage: send_e2e_file [options] <from> <to> <secret> <private-key> <path-to-file>
@@ -73,9 +73,9 @@ async fn main() {
7373

7474
// Read files
7575
let mut file = etry!(File::open(filepath), "Could not open file");
76-
let mut file_data: Vec<u8> = vec![];
77-
etry!(file.read_to_end(&mut file_data), "Could not read file");
78-
let thumb_data = match thumbpath {
76+
let mut file_bytes: Vec<u8> = vec![];
77+
etry!(file.read_to_end(&mut file_bytes), "Could not read file");
78+
let thumbnail_bytes = match thumbpath {
7979
Some(p) => {
8080
let mut thumb = etry!(File::open(p), format!("Could not open thumbnail {:?}", p));
8181
let mut thumb_data: Vec<u8> = vec![];
@@ -89,15 +89,18 @@ async fn main() {
8989
};
9090

9191
// Encrypt file data
92-
let (encrypted_file, encrypted_thumb, key) =
93-
encrypt_file_data(&file_data, thumb_data.as_deref());
92+
let file_data = FileData {
93+
file: file_bytes,
94+
thumbnail: thumbnail_bytes,
95+
};
96+
let (encrypted, key) = encrypt_file_data(&file_data);
9497

9598
// Upload files to blob server
9699
let file_blob_id = etry!(
97-
api.blob_upload_raw(&encrypted_file, false).await,
100+
api.blob_upload_raw(&encrypted.file, false).await,
98101
"Could not upload file to blob server"
99102
);
100-
let thumb_blob_id = if let Some(et) = encrypted_thumb {
103+
let thumb_blob_id = if let Some(et) = encrypted.thumbnail {
101104
let blob_id = etry!(
102105
api.blob_upload_raw(&et, false).await,
103106
"Could not upload thumbnail to blob server"
@@ -115,7 +118,8 @@ async fn main() {
115118
.first_or_octet_stream()
116119
.to_string();
117120
let file_name = filepath.file_name().and_then(OsStr::to_str);
118-
let msg = FileMessage::builder(file_blob_id, key, file_media_type, file_data.len() as u32)
121+
let file_size_bytes = file_data.file.len() as u32;
122+
let msg = FileMessage::builder(file_blob_id, key, file_media_type, file_size_bytes)
119123
.thumbnail_opt(thumb_blob_id)
120124
.file_name_opt(file_name)
121125
.description_opt(caption)

src/crypto.rs

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,23 @@ static THUMB_NONCE: secretbox::Nonce = secretbox::Nonce([
161161
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
162162
]);
163163

164+
#[derive(Clone)]
165+
pub struct FileData {
166+
pub file: Vec<u8>,
167+
pub thumbnail: Option<Vec<u8>>,
168+
}
169+
170+
#[derive(Clone)]
171+
pub struct EncryptedFileData {
172+
pub file: Vec<u8>,
173+
pub thumbnail: Option<Vec<u8>>,
174+
}
175+
164176
/// Encrypt file data and an optional thumbnail using a randomly generated
165177
/// symmetric key.
166178
///
167179
/// Return the encrypted bytes and the key.
168-
pub fn encrypt_file_data(
169-
file_data: &[u8],
170-
thumb_data: Option<&[u8]>,
171-
) -> (Vec<u8>, Option<Vec<u8>>, secretbox::Key) {
180+
pub fn encrypt_file_data(data: &FileData) -> (EncryptedFileData, Key) {
172181
// Make sure to init sodiumoxide library
173182
sodiumoxide::init().unwrap();
174183

@@ -177,23 +186,48 @@ pub fn encrypt_file_data(
177186

178187
// Encrypt data
179188
// Note: Since we generate a random key, we can safely re-use constant nonces.
180-
let encrypted_file = secretbox::seal(file_data, &FILE_NONCE, &key);
181-
let encrypted_thumb = thumb_data.map(|t| secretbox::seal(t, &THUMB_NONCE, &key));
189+
let file = secretbox::seal(&data.file, &FILE_NONCE, &key);
190+
let thumbnail = data
191+
.thumbnail
192+
.as_ref()
193+
.map(|t| secretbox::seal(t, &THUMB_NONCE, &key));
182194

183-
(encrypted_file, encrypted_thumb, key)
195+
(EncryptedFileData { file, thumbnail }, key)
184196
}
185197

186-
pub fn decrypt_file_data(encrypted_data: &[u8], blob_key: &Key) -> Result<Vec<u8>, CryptoError> {
187-
let decrypted_blob = secretbox::open(encrypted_data, &FILE_NONCE, blob_key);
188-
decrypted_blob.map_err(|_| CryptoError::DecryptionFailed)
198+
/// Decrypt file data and optional thumbnail data with the provided symmetric
199+
/// key.
200+
///
201+
/// Return the decrypted bytes.
202+
pub fn decrypt_file_data(
203+
data: &EncryptedFileData,
204+
encryption_key: &Key,
205+
) -> Result<FileData, CryptoError> {
206+
// Make sure to init sodiumoxide library
207+
sodiumoxide::init().unwrap();
208+
209+
let file = secretbox::open(&data.file, &FILE_NONCE, encryption_key)
210+
.map_err(|_| CryptoError::DecryptionFailed)?;
211+
let thumbnail = match data.thumbnail.as_ref() {
212+
Some(t) => {
213+
let decrypted = secretbox::open(t, &THUMB_NONCE, encryption_key)
214+
.map_err(|_| CryptoError::DecryptionFailed)?;
215+
Some(decrypted)
216+
}
217+
None => None,
218+
};
219+
220+
Ok(FileData { file, thumbnail })
189221
}
190222

191223
#[cfg(test)]
192224
mod test {
193225
use std::str::FromStr;
194226

195-
use crate::api::ApiBuilder;
196-
use crate::types::{BlobId, MessageType};
227+
use crate::{
228+
api::ApiBuilder,
229+
types::{BlobId, MessageType},
230+
};
197231
use sodiumoxide::crypto::box_::{self, Nonce, PublicKey, SecretKey};
198232

199233
use super::*;
@@ -336,23 +370,26 @@ mod test {
336370
fn test_encrypt_file_data() {
337371
let file_data = [1, 2, 3, 4];
338372
let thumb_data = [5, 6, 7];
373+
let data = FileData {
374+
file: file_data.to_vec(),
375+
thumbnail: Some(thumb_data.to_vec()),
376+
};
339377

340378
// Encrypt
341-
let (encrypted_file, encrypted_thumb, key) =
342-
encrypt_file_data(&file_data, Some(&thumb_data));
343-
let encrypted_thumb = encrypted_thumb.expect("Thumbnail missing");
379+
let (encrypted, key) = encrypt_file_data(&data);
380+
let encrypted_thumb = encrypted.thumbnail.expect("Thumbnail missing");
344381

345382
// Ensure that encrypted data is different from plaintext data
346-
assert_ne!(encrypted_file, file_data);
383+
assert_ne!(encrypted.file, file_data);
347384
assert_ne!(encrypted_thumb, thumb_data);
348-
assert_eq!(encrypted_file.len(), file_data.len() + secretbox::MACBYTES);
385+
assert_eq!(encrypted.file.len(), file_data.len() + secretbox::MACBYTES);
349386
assert_eq!(
350387
encrypted_thumb.len(),
351388
thumb_data.len() + secretbox::MACBYTES
352389
);
353390

354391
// Test that data can be decrypted
355-
let decrypted_file = secretbox::open(&encrypted_file, &FILE_NONCE, &key).unwrap();
392+
let decrypted_file = secretbox::open(&encrypted.file, &FILE_NONCE, &key).unwrap();
356393
let decrypted_thumb = secretbox::open(&encrypted_thumb, &THUMB_NONCE, &key).unwrap();
357394
assert_eq!(decrypted_file, &file_data);
358395
assert_eq!(decrypted_thumb, &thumb_data);
@@ -361,11 +398,41 @@ mod test {
361398
#[test]
362399
fn test_encrypt_file_data_random_key() {
363400
// Ensure that a different key is generated each time
364-
let (_, _, key1) = encrypt_file_data(&[1, 2, 3], None);
365-
let (_, _, key2) = encrypt_file_data(&[1, 2, 3], None);
366-
let (_, _, key3) = encrypt_file_data(&[1, 2, 3], None);
401+
let (_, key1) = encrypt_file_data(&FileData {
402+
file: [1, 2, 3].to_vec(),
403+
thumbnail: None,
404+
});
405+
let (_, key2) = encrypt_file_data(&FileData {
406+
file: [1, 2, 3].to_vec(),
407+
thumbnail: None,
408+
});
409+
let (_, key3) = encrypt_file_data(&FileData {
410+
file: [1, 2, 3].to_vec(),
411+
thumbnail: None,
412+
});
367413
assert_ne!(key1, key2);
368414
assert_ne!(key2, key3);
369415
assert_ne!(key1, key3);
370416
}
417+
418+
#[test]
419+
fn test_decrypt_file_data() {
420+
let file_data = [1, 2, 3, 4];
421+
let thumb_data = [5, 6, 7];
422+
let data = FileData {
423+
file: file_data.to_vec(),
424+
thumbnail: Some(thumb_data.to_vec()),
425+
};
426+
427+
// Encrypt
428+
let (encrypted, key) = encrypt_file_data(&data);
429+
assert_ne!(encrypted.file, data.file);
430+
assert!(encrypted.thumbnail.is_some());
431+
assert_ne!(encrypted.thumbnail, data.thumbnail);
432+
433+
// Decrypt
434+
let decrypted = decrypt_file_data(&encrypted, &key).unwrap();
435+
assert_eq!(decrypted.file, &file_data);
436+
assert_eq!(decrypted.thumbnail.unwrap(), &thumb_data);
437+
}
371438
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ pub use crate::{
9191
api::{ApiBuilder, E2eApi, SimpleApi},
9292
connection::Recipient,
9393
crypto::{
94-
decrypt_file_data, encrypt, encrypt_file_data, encrypt_raw, EncryptedMessage, RecipientKey,
94+
decrypt_file_data, encrypt, encrypt_file_data, encrypt_raw, EncryptedFileData,
95+
EncryptedMessage, FileData, RecipientKey,
9596
},
9697
lookup::{Capabilities, LookupCriterion},
9798
types::{BlobId, FileMessage, FileMessageBuilder, MessageType, RenderingType},

0 commit comments

Comments
 (0)