Skip to content

Commit

Permalink
Add wrapper for Key that implements zeroize
Browse files Browse the repository at this point in the history
  • Loading branch information
threema-donat committed Feb 5, 2024
1 parent 2f03cd5 commit ee198a0
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ serde_json = "1.0"
serde_urlencoded = { version = "0.7", optional = true }
sha2 = "0.10.8"
thiserror = "1"
zeroize = { version = "1", features = ["zeroize_derive"], default-features = false }

[dev-dependencies]
docopt = "1.1.0"
Expand Down
3 changes: 2 additions & 1 deletion examples/download_blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ async fn main() {
let bytes = HEXLOWER_PERMISSIVE
.decode(blob_key_raw.as_bytes())
.expect("Invalid blob key");
Some(Key::clone_from_slice(bytes.as_ref()))
let key = Key::try_from(bytes).expect("Invalid size of blob key");
Some(key)
} else {
None
};
Expand Down
61 changes: 52 additions & 9 deletions src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,67 @@
//! Encrypt and decrypt messages.

use std::{convert::Into, io::Write, iter::repeat, str::FromStr, sync::OnceLock};
use std::{convert::Into, fmt::Debug, io::Write, iter::repeat, str::FromStr, sync::OnceLock};

use byteorder::{LittleEndian, WriteBytesExt};
use crypto_box::{aead::Aead, SalsaBox};
use crypto_secretbox::{
aead::{OsRng, Payload},
AeadCore, KeyInit, Nonce, XSalsa20Poly1305,
cipher::generic_array::GenericArray,
AeadCore, Key as SecretboxKey, KeyInit, Nonce, XSalsa20Poly1305,
};
use data_encoding::{HEXLOWER, HEXLOWER_PERMISSIVE};
use rand::Rng;
use serde_json as json;
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::{
errors::CryptoError,
errors::{self, CryptoError},
types::{BlobId, FileMessage, MessageType},
Key, PublicKey, SecretKey,
PublicKey, SecretKey,
};

pub const NONCE_SIZE: usize = 24;
const KEY_SIZE: usize = 32;

#[derive(PartialEq, Zeroize, ZeroizeOnDrop, Clone)]
pub struct Key(SecretboxKey);

impl AsRef<SecretboxKey> for Key {
fn as_ref(&self) -> &SecretboxKey {
&self.0
}
}

impl Debug for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write! {f, "Key([…])"}
}
}

impl From<SecretboxKey> for Key {
fn from(value: SecretboxKey) -> Self {
Self(value)
}
}

impl From<[u8; KEY_SIZE]> for Key {
fn from(value: [u8; KEY_SIZE]) -> Self {
Self(GenericArray::from(value))
}
}

impl TryFrom<Vec<u8>> for Key {
type Error = errors::CryptoError;

fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
<[u8; KEY_SIZE]>::try_from(value)
.map_err(|original| {
CryptoError::BadKey(format!("Key has wrong size: {}", original.len()))
})
.map(SecretboxKey::from)
.map(Self::from)
}
}

fn get_file_nonce() -> &'static Nonce {
static FILE_NONCE: OnceLock<Nonce> = OnceLock::new();
Expand Down Expand Up @@ -195,12 +238,12 @@ pub struct EncryptedFileData {
/// Return the encrypted bytes and the key.
pub fn encrypt_file_data(data: &FileData) -> Result<(EncryptedFileData, Key), CryptoError> {
// Generate a random encryption key
let key = XSalsa20Poly1305::generate_key(&mut OsRng);
let secretbox = XSalsa20Poly1305::new(&key);
let key: Key = XSalsa20Poly1305::generate_key(&mut OsRng).into();
let secretbox = XSalsa20Poly1305::new(key.as_ref());

// Encrypt data
// Note: Since we generate a random key, we can safely re-use constant nonces.
let file = XSalsa20Poly1305::new(&key)
let file = secretbox
.encrypt(get_file_nonce(), Payload::from(data.file.as_ref()))
.map_err(|_| CryptoError::EncryptionFailed)?;
let thumbnail = data
Expand All @@ -221,7 +264,7 @@ pub fn decrypt_file_data(
data: &EncryptedFileData,
encryption_key: &Key,
) -> Result<FileData, CryptoError> {
let secretbox: XSalsa20Poly1305 = XSalsa20Poly1305::new(encryption_key);
let secretbox: XSalsa20Poly1305 = XSalsa20Poly1305::new(encryption_key.as_ref());

let file = secretbox
.decrypt(get_file_nonce(), Payload::from(data.file.as_ref()))
Expand Down Expand Up @@ -399,7 +442,7 @@ mod test {
let (encrypted, key) = encrypt_file_data(&data).unwrap();
let encrypted_thumb = encrypted.thumbnail.expect("Thumbnail missing");

let secretbox: XSalsa20Poly1305 = XSalsa20Poly1305::new(&key);
let secretbox: XSalsa20Poly1305 = XSalsa20Poly1305::new(key.as_ref());

// Ensure that encrypted data is different from plaintext data
assert_ne!(encrypted.file, file_data);
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ mod lookup;
mod receive;
mod types;

pub use crypto::Key;
pub use crypto_box::{PublicKey, SecretKey};
pub use crypto_secretbox::{Key, Nonce};
pub use crypto_secretbox::Nonce;

pub use crate::{
api::{ApiBuilder, E2eApi, SimpleApi},
Expand Down
20 changes: 11 additions & 9 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,13 @@ impl Serialize for BlobId {
}

fn key_to_hex<S: Serializer>(val: &Key, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&HEXLOWER.encode(val))
serializer.serialize_str(&HEXLOWER.encode(&val.as_ref()[..]))
}

#[cfg(test)]
mod test {
use std::collections::HashMap;

use crypto_secretbox::cipher::generic_array::GenericArray;
use serde_json as json;

use super::*;
Expand All @@ -421,10 +420,11 @@ mod test {

#[test]
fn test_serialize_to_string_minimal() {
let pk: Key = GenericArray::from([
let pk: Key = [
1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1,
2, 3, 4,
]);
]
.into();
let msg = FileMessage {
file_blob_id: BlobId::from_str("0123456789abcdef0123456789abcdef").unwrap(),
file_media_type: "application/pdf".parse().unwrap(),
Expand Down Expand Up @@ -461,10 +461,11 @@ mod test {

#[test]
fn test_serialize_to_string_full() {
let pk: Key = GenericArray::from([
let pk: Key = [
1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1,
2, 3, 4,
]);
]
.into();
let msg = FileMessage {
file_blob_id: BlobId::from_str("0123456789abcdef0123456789abcdef").unwrap(),
file_media_type: "application/pdf".parse().unwrap(),
Expand Down Expand Up @@ -514,13 +515,14 @@ mod test {

#[test]
fn test_builder() {
let key: Key = GenericArray::from([
let key: Key = [
1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1,
2, 3, 4,
]);
]
.into();
let file_blob_id = BlobId::from_str("0123456789abcdef0123456789abcdef").unwrap();
let thumb_blob_id = BlobId::from_str("abcdef0123456789abcdef0123456789").unwrap();
let msg = FileMessage::builder(file_blob_id.clone(), key, "image/jpeg", 2048)
let msg = FileMessage::builder(file_blob_id.clone(), key.clone(), "image/jpeg", 2048)
.thumbnail(thumb_blob_id.clone(), "image/png")
.file_name("hello.jpg")
.description(String::from("An image file"))
Expand Down

0 comments on commit ee198a0

Please sign in to comment.