use std::convert::TryInto;
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use aes::Aes256;
use aes_gcm::aead::{Aead, NewAead};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use easy_hasher::easy_hasher::{md2, md4, md5, sha1, sha256, sha384, sha512};
use evpkdf::evpkdf;
use hmac::digest::{FixedOutput, KeyInit};
use hmac::{Hmac, Mac};
use md5::Md5;
use pbkdf2::pbkdf2;
use rand::{thread_rng, Rng};
use rsa::pkcs8::{DecodePrivateKey, DecodePublicKey};
use rsa::PublicKey;
use secstr::{SecUtf8, SecVec};
use snafu::{ensure, Backtrace, ResultExt, Snafu};
use crate::utils;
type Result<T, E = Error> = std::result::Result<T, E>;
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type Aes256CbcDec = cbc::Decryptor<Aes256>;
type HmacSha512 = Hmac<sha2::Sha512>;
pub const OPENSSL_SALT_PREFIX: &[u8] = b"Salted__";
pub const OPENSSL_SALT_PREFIX_BASE64: &[u8] = b"U2FsdGVk";
pub const OPENSSL_SALT_LENGTH: usize = 8;
pub const AES_CBC_IV_LENGTH: usize = 16;
pub const AES_CBC_KEY_LENGTH: usize = 32;
pub const AES_GCM_IV_LENGTH: usize = 12;
pub const FILEN_VERSION_LENGTH: usize = 3;
#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("AES CBC failed to decipher raw bytes"))]
AesCbcCannotDecipherData { message: String },
#[snafu(display("Prefixed AES GCM failed to decipher ciphered message: {}", source))]
AesGcmCannotDecipherData { source: aes_gcm::Error },
#[snafu(display("Prefixed AES GCM failed to cipher data with length {}: {}", data_length, source))]
AesGcmCannotCipherData { data_length: usize, source: aes_gcm::Error },
#[snafu(display("Caller provided invalid argument: {}", message))]
BadArgument { message: String, backtrace: Backtrace },
#[snafu(display(r#"Expected data to be base64-encoded, but cannot decode it as such"#))]
CannotDecodeBase64 { source: base64::DecodeError },
#[snafu(display("Cannot parse Filen metadata from: {:?}", erroneous_part))]
CannotParseFilenMetadataVersion {
erroneous_part: String,
source: std::num::ParseIntError,
},
#[snafu(display(
"Caller expected decrypted metadata to be a valid UTF-8 string, but it was not. \
Perhaps decrypt_metadata() should be used instead of decrypt_metadata_str()?"
))]
DecryptedMetadataIsNotUtf8 { source: std::string::FromUtf8Error },
#[snafu(display(
"Somehow encrypted metadata was not a valid UTF-8 string. It is probably a bug in encrypt_metadata()"
))]
EncryptedMetadataIsNotUtf8 { source: std::string::FromUtf8Error },
#[snafu(display(
"Cannot encrypt data with given public key, assuming RSA-OAEP with SHA512 hash and PKCS8 format: {}",
source
))]
RsaPkcs8CannotEncryptData { source: rsa::errors::Error },
#[snafu(display(
"Cannot decrypt data with given private key, assuming RSA-OAEP with SHA512 hash and PKCS8 format: {}",
source
))]
RsaPkcs8CannotDecryptData { source: rsa::errors::Error },
#[snafu(display("Cannot deserialize PKCS#8 private key from ASN.1 DER-encoded data: {}", source))]
RsaCannotDeserializePrivateKey { source: rsa::pkcs8::Error },
#[snafu(display("Cannot deserialize public key from ASN.1 DER-encoded data: {}", source))]
RsaCannotDeserializePublicKey { source: rsa::pkcs8::spki::Error },
#[snafu(display("Unsupported Filen file version {}", file_version))]
UnsupportedFilenFileVersion { file_version: i64, backtrace: Backtrace },
#[snafu(display("Unsupported Filen metadata version {}", metadata_version))]
UnsupportedFilenMetadataVersion {
metadata_version: i64,
backtrace: Backtrace,
},
}
#[must_use]
pub fn hash_fn<S: Into<String>>(value: S) -> String {
sha1(&sha512(&value.into()).to_hex_string()).to_hex_string()
}
#[must_use]
pub fn hash_password<S: Into<String>>(password: S) -> String {
let password_string: String = password.into();
let mut sha512_part_1 =
sha512(&sha384(&sha256(&sha1(&password_string).to_hex_string()).to_hex_string()).to_hex_string())
.to_hex_string();
let sha512_part_2 =
sha512(&md5(&md4(&md2(&password_string).to_hex_string()).to_hex_string()).to_hex_string()).to_hex_string();
sha512_part_1.push_str(&sha512_part_2);
sha512_part_1
}
#[must_use]
pub fn derive_key_from_password_512(password: &[u8], salt: &[u8], iterations: u32) -> [u8; 64] {
let mut pbkdf2_hash = [0_u8; 64];
derive_key_from_password_generic::<HmacSha512>(password, salt, iterations, &mut pbkdf2_hash);
pbkdf2_hash
}
#[must_use]
pub fn derive_key_from_password_256(password: &[u8], salt: &[u8], iterations: u32) -> [u8; 32] {
let mut pbkdf2_hash = [0_u8; 32];
derive_key_from_password_generic::<HmacSha512>(password, salt, iterations, &mut pbkdf2_hash);
pbkdf2_hash
}
pub fn encrypt_metadata(data: &[u8], key: &[u8], metadata_version: u32) -> Result<Vec<u8>> {
if data.is_empty() {
return Ok(vec![0_u8; 0]);
}
match metadata_version {
1 => encrypt_aes_openssl(data, key, None).map(|encrypted| base64::encode(encrypted).as_bytes().to_vec()),
2 => {
let mut version_mark = format!("{:0>3}", metadata_version).into_bytes();
version_mark.extend(encrypt_aes_gcm_base64(data, key)?);
Ok(version_mark)
}
version => UnsupportedFilenMetadataVersionSnafu {
metadata_version: version,
}
.fail(),
}
}
pub fn decrypt_metadata_any_key(data: &[u8], keys: &[&[u8]]) -> Result<Vec<u8>> {
if data.is_empty() {
return Ok(vec![0_u8; 0]);
}
ensure!(
!keys.is_empty(),
BadArgumentSnafu {
message: "keys for decrypting metadata cannot be empty",
}
);
let mut result = Ok(vec![0_u8; 0]);
for key in keys {
result = decrypt_metadata(data, key);
if result.is_ok() {
break;
}
}
if result.is_ok() {
result
} else {
BadArgumentSnafu {
message: "all given keys failed to decrypt metadata",
}
.fail()
}
}
pub fn decrypt_metadata(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
fn read_metadata_version(data: &[u8]) -> Result<i32> {
let possible_salted_mark = data.get(..OPENSSL_SALT_PREFIX.len()).unwrap_or_default();
let possible_version_mark = data.get(..FILEN_VERSION_LENGTH).unwrap_or_default();
if possible_salted_mark == OPENSSL_SALT_PREFIX_BASE64 {
Ok(1)
} else if possible_salted_mark == OPENSSL_SALT_PREFIX {
Ok(-1) } else {
let possible_version_string = String::from_utf8_lossy(possible_version_mark);
possible_version_string
.parse::<i32>()
.context(CannotParseFilenMetadataVersionSnafu {
erroneous_part: possible_version_string.to_string(),
})
}
}
if data.is_empty() {
return Ok(vec![0_u8; 0]);
}
let metadata_version = read_metadata_version(data)?;
match metadata_version {
-1 => decrypt_aes_openssl(data, key), 1 => base64::decode(data)
.context(CannotDecodeBase64Snafu {})
.and_then(|decoded| decrypt_aes_openssl(&decoded, key)), 2 => decrypt_aes_gcm_base64(data.get(FILEN_VERSION_LENGTH..).unwrap_or_default(), key),
version => UnsupportedFilenMetadataVersionSnafu {
metadata_version: version,
}
.fail(),
}
}
pub fn encrypt_metadata_str(data: &str, key: &SecUtf8, metadata_version: u32) -> Result<String> {
encrypt_metadata(data.as_bytes(), key.unsecure().as_bytes(), metadata_version)
.and_then(|bytes| String::from_utf8(bytes).context(EncryptedMetadataIsNotUtf8Snafu {}))
}
pub fn decrypt_metadata_str(data: &str, key: &SecUtf8) -> Result<String> {
decrypt_metadata(data.as_bytes(), key.unsecure().as_bytes())
.and_then(|bytes| String::from_utf8(bytes).context(DecryptedMetadataIsNotUtf8Snafu {}))
}
pub fn decrypt_metadata_str_any_key(data: &str, keys: &[SecUtf8]) -> Result<String> {
if data.is_empty() {
return Ok(String::new());
}
let keys = keys.iter().map(|key| key.unsecure().as_bytes()).collect::<Vec<&[u8]>>();
decrypt_metadata_any_key(data.as_bytes(), &keys)
.and_then(|bytes| String::from_utf8(bytes).context(DecryptedMetadataIsNotUtf8Snafu {}))
}
pub fn encrypt_file_chunk(chunk_data: &[u8], file_key: &[u8; AES_CBC_KEY_LENGTH], version: u32) -> Result<String> {
if chunk_data.is_empty() {
Ok(String::new())
} else {
match version {
1 => {
let iv: &[u8; 16] = aes_cbc_iv_from_key(file_key)?;
encrypt_aes_cbc_with_key_and_iv(chunk_data, file_key, iv)
.map(|encrypted| utils::bytes_to_binary_string(&encrypted))
}
2 => encrypt_aes_gcm_bstr(chunk_data, file_key),
_ => UnsupportedFilenFileVersionSnafu { file_version: version }.fail(),
}
}
}
pub fn decrypt_file_chunk(
filen_encrypted_chunk_data: &[u8],
file_key: &[u8; AES_CBC_KEY_LENGTH],
version: u32,
) -> Result<Vec<u8>> {
match version {
1 => {
if filen_encrypted_chunk_data.len() < OPENSSL_SALT_PREFIX.len() {
BadArgumentSnafu {
message: "encrypted data is too short, < 8 bytes",
}
.fail()
} else {
let possible_prefix = filen_encrypted_chunk_data
.get(0..OPENSSL_SALT_PREFIX.len())
.unwrap_or_default();
if possible_prefix == OPENSSL_SALT_PREFIX {
decrypt_aes_openssl(base64::encode(filen_encrypted_chunk_data).as_bytes(), file_key)
} else if possible_prefix == OPENSSL_SALT_PREFIX_BASE64 {
decrypt_aes_openssl(
utils::bytes_to_binary_string(filen_encrypted_chunk_data).as_bytes(),
file_key,
)
} else {
let iv: &[u8; 16] = aes_cbc_iv_from_key(file_key)?;
decrypt_aes_cbc_with_key_and_iv(filen_encrypted_chunk_data, file_key, iv)
}
}
}
2 => decrypt_aes_gcm(filen_encrypted_chunk_data, file_key),
_ => UnsupportedFilenFileVersionSnafu { file_version: version }.fail(),
}
}
pub fn encrypt_master_keys_metadata(
master_keys: &[SecUtf8],
last_master_key: &SecUtf8,
metadata_version: u32,
) -> Result<String> {
let master_keys_unsecure = master_keys
.iter()
.map(SecUtf8::unsecure)
.collect::<Vec<&str>>()
.join("|");
encrypt_metadata_str(&master_keys_unsecure, last_master_key, metadata_version)
}
pub fn decrypt_master_keys_metadata(master_keys_metadata: &str, last_master_key: &SecUtf8) -> Result<Vec<SecUtf8>> {
ensure!(
!master_keys_metadata.is_empty(),
BadArgumentSnafu {
message: "cannot decrypt master keys metadata, it is empty",
}
);
decrypt_metadata_str(master_keys_metadata, last_master_key).map(|keys| keys.split('|').map(SecUtf8::from).collect())
}
pub fn decrypt_private_key_metadata(private_key_metadata: &str, master_keys: &[SecUtf8]) -> Result<SecVec<u8>> {
fn decode_base64_to_secvec(string: &str) -> Result<SecVec<u8>> {
base64::decode(string)
.context(CannotDecodeBase64Snafu {})
.map(SecVec::from)
}
ensure!(
!private_key_metadata.is_empty(),
BadArgumentSnafu {
message: "cannot decrypt private key metadata, it is empty",
}
);
decrypt_metadata_str_any_key(private_key_metadata, master_keys).and_then(|str| decode_base64_to_secvec(&str))
}
pub fn encrypt_rsa(data: &[u8], public_key: &[u8]) -> Result<Vec<u8>> {
let mut rng = thread_rng();
let padding = rsa::PaddingScheme::new_oaep::<sha2::Sha512>();
let key = rsa::RsaPublicKey::from_public_key_der(public_key).context(RsaCannotDeserializePublicKeySnafu {})?;
key.encrypt(&mut rng, padding, data)
.context(RsaPkcs8CannotEncryptDataSnafu {})
}
pub fn decrypt_rsa(data: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
let padding = rsa::PaddingScheme::new_oaep::<sha2::Sha512>();
let private_key =
rsa::RsaPrivateKey::from_pkcs8_der(private_key).context(RsaCannotDeserializePrivateKeySnafu {})?;
private_key
.decrypt(padding, data)
.context(RsaPkcs8CannotDecryptDataSnafu {})
}
#[must_use]
pub fn encrypt_to_link_password_and_salt(plain_text_password: &SecUtf8) -> (String, String) {
let salt = utils::random_alphanumeric_string(32);
let password_hashed = utils::bytes_to_hex_string(&derive_key_from_password_512(
plain_text_password.unsecure().as_bytes(),
salt.as_bytes(),
200_000,
));
(password_hashed, salt)
}
pub fn encrypt_aes_openssl(data: &[u8], key: &[u8], maybe_salt: Option<&[u8]>) -> Result<Vec<u8>> {
let mut salt = [0_u8; OPENSSL_SALT_LENGTH];
match maybe_salt {
Some(user_salt) if user_salt.len() == OPENSSL_SALT_LENGTH => salt.copy_from_slice(user_salt),
_ => rand::thread_rng().fill(&mut salt),
};
let (key, iv) = generate_aes_key_and_iv(AES_CBC_KEY_LENGTH, AES_CBC_IV_LENGTH, 1, Some(&salt), key);
let mut encrypted = encrypt_aes_cbc_with_key_and_iv(
data,
&key.try_into().unwrap_or([0_u8; AES_CBC_KEY_LENGTH]),
&iv.try_into().unwrap_or([0_u8; AES_CBC_IV_LENGTH]),
)?;
let mut result = OPENSSL_SALT_PREFIX.to_vec();
result.extend_from_slice(&salt);
result.append(&mut encrypted);
Ok(result)
}
pub fn decrypt_aes_openssl(aes_encrypted_data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
let (salt, message) = salt_and_message_from_aes_openssl_encrypted_data(aes_encrypted_data, OPENSSL_SALT_LENGTH)?;
let (key, iv) = generate_aes_key_and_iv(AES_CBC_KEY_LENGTH, AES_CBC_IV_LENGTH, 1, Some(salt), key);
let key_sized: &[u8; AES_CBC_KEY_LENGTH] = &key.try_into().unwrap_or([0_u8; AES_CBC_KEY_LENGTH]);
let iv_sized: &[u8; AES_CBC_IV_LENGTH] = &iv.try_into().unwrap_or([0_u8; AES_CBC_IV_LENGTH]);
decrypt_aes_cbc_with_key_and_iv(message, key_sized, iv_sized)
}
fn encrypt_aes_cbc_with_key_and_iv(
data: &[u8],
key: &[u8; AES_CBC_KEY_LENGTH],
iv: &[u8; AES_CBC_IV_LENGTH],
) -> Result<Vec<u8>> {
let cipher = Aes256CbcEnc::new(key.into(), iv.into());
Ok(cipher.encrypt_padded_vec_mut::<Pkcs7>(data))
}
fn decrypt_aes_cbc_with_key_and_iv(
aes_encrypted_data: &[u8],
key: &[u8; AES_CBC_KEY_LENGTH],
iv: &[u8; AES_CBC_IV_LENGTH],
) -> Result<Vec<u8>> {
let cipher = Aes256CbcDec::new(key.into(), iv.into());
cipher
.decrypt_padded_vec_mut::<Pkcs7>(aes_encrypted_data)
.map_err(|err| Error::AesCbcCannotDecipherData {
message: err.to_string(),
})
}
pub fn encrypt_aes_gcm_base64(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
let (mut iv, encrypted) = encrypt_aes_gcm(data, key)?;
iv.push_str(&base64::encode(encrypted));
Ok(iv.into_bytes())
}
pub fn encrypt_aes_gcm_bstr(data: &[u8], key: &[u8]) -> Result<String> {
let (mut iv, encrypted) = encrypt_aes_gcm(data, key)?;
iv.push_str(&utils::bytes_to_binary_string(&encrypted));
Ok(iv)
}
pub fn encrypt_aes_gcm(data: &[u8], key: &[u8]) -> Result<(String, Vec<u8>)> {
let derived_key = derive_key_from_password_256(key, key, 1);
let iv = utils::random_alphanumeric_string(AES_GCM_IV_LENGTH);
let cipher = Aes256Gcm::new(Key::from_slice(&derived_key));
let nonce = Nonce::from_slice(iv.as_bytes());
let encrypted = cipher.encrypt(nonce, data).context(AesGcmCannotCipherDataSnafu {
data_length: data.len(),
})?;
Ok((iv, encrypted))
}
pub fn decrypt_aes_gcm_base64(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
let (iv, encrypted_base64) = extract_aes_gcm_iv_and_message(data)?;
base64::decode(encrypted_base64)
.context(CannotDecodeBase64Snafu {})
.and_then(|encrypted| decrypt_aes_gcm_from_iv_and_bytes(key, iv, &encrypted))
}
pub fn decrypt_aes_gcm(data: &[u8], key: &[u8]) -> Result<Vec<u8>> {
let (iv, encrypted) = extract_aes_gcm_iv_and_message(data)?;
decrypt_aes_gcm_from_iv_and_bytes(key, iv, encrypted)
}
fn decrypt_aes_gcm_from_iv_and_bytes(key: &[u8], iv: &[u8], encrypted: &[u8]) -> Result<Vec<u8>> {
let derived_key = derive_key_from_password_256(key, key, 1);
let cipher = Aes256Gcm::new(Key::from_slice(&derived_key));
let nonce = Nonce::from_slice(iv);
cipher
.decrypt(nonce, encrypted)
.context(AesGcmCannotDecipherDataSnafu {})
}
fn extract_aes_gcm_iv_and_message(data: &[u8]) -> Result<(&[u8], &[u8])> {
ensure!(
data.len() > AES_GCM_IV_LENGTH,
BadArgumentSnafu {
message: "encrypted data is too small to contain AES GCM IV"
}
);
let (iv, message) = data.split_at(AES_GCM_IV_LENGTH);
Ok((iv, message))
}
fn aes_cbc_iv_from_key(key: &[u8; AES_CBC_KEY_LENGTH]) -> Result<&[u8; AES_CBC_IV_LENGTH]> {
let iv: &[u8; AES_CBC_IV_LENGTH] = match key[..AES_CBC_IV_LENGTH].try_into() {
Ok(value) => Ok(value),
Err(_) => BadArgumentSnafu {
message: format!("AES CBC key should have {} bytes to extract IV", AES_CBC_KEY_LENGTH),
}
.fail(),
}?;
Ok(iv)
}
fn salt_and_message_from_aes_openssl_encrypted_data(
aes_encrypted_data: &[u8],
salt_length: usize,
) -> Result<(&[u8], &[u8])> {
let message_index = OPENSSL_SALT_PREFIX.len() + salt_length;
ensure!(
aes_encrypted_data.len() >= message_index,
BadArgumentSnafu {
message: "encrypted data is too small to contain OpenSSL-compatible salt",
}
);
let (salt_with_prefix, message) = aes_encrypted_data.split_at(message_index);
let (prefix, salt) = salt_with_prefix.split_at(OPENSSL_SALT_PREFIX.len());
ensure!(
prefix == OPENSSL_SALT_PREFIX,
BadArgumentSnafu {
message: "encrypted data does not contain OpenSSL salt prefix",
}
);
Ok((salt, message))
}
fn derive_key_from_password_generic<M>(password: &[u8], salt: &[u8], iterations: u32, pbkdf2_hash: &mut [u8])
where
M: Clone + FixedOutput + KeyInit + Mac + Sync,
{
let iterations_or_default = if iterations == 0 { 200_000 } else { iterations };
pbkdf2::<M>(password, salt, iterations_or_default, pbkdf2_hash);
}
fn generate_aes_key_and_iv(
key_length: usize,
iv_length: usize,
iterations: usize,
maybe_salt: Option<&[u8]>,
password: &[u8],
) -> (Vec<u8>, Vec<u8>) {
let mut output = vec![0; key_length + iv_length];
let salt = maybe_salt.unwrap_or(&[0; 0]);
evpkdf::<Md5>(password, salt, iterations, &mut output);
let (key, iv) = output.split_at(key_length);
(Vec::from(key), Vec::from(iv))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::read_project_file;
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn encrypt_metadata_v1_should_use_simple_aes_with_base64() {
let m_key = hash_fn("test");
let metadata = "{\"name\":\"perform.js\",\"size\":156,\"mime\":\"application/javascript\",\
\"key\":\"tqNrczqVdTCgFzB1b1gyiQBIYmwDBwa9\",\"lastModified\":499162500}";
let encrypted_metadata = encrypt_metadata(metadata.as_bytes(), m_key.as_bytes(), 1).unwrap();
assert_eq!(encrypted_metadata.len(), 216);
assert_eq!(&encrypted_metadata[..8], OPENSSL_SALT_PREFIX_BASE64);
}
#[test]
fn decrypt_metadata_v1_should_use_simple_aes() {
let m_key = hash_fn("test");
let metadata_base64 = "U2FsdGVkX1//gOpv81xPNI3PuT1CryNCVXpcfmISGNR+1g2OPT8SBP2/My7G6o5lSvVtkn2smbYrAo1\
Mgaq9RIJlCEjcYpMsr+A9RSpkX7zLyXtMPV6q+PRbQj1WkP8ymuh0lmmnFRa+oRy0EvJnw97m3aLTHN4DD5XmJ36tecA2cwSrFskYn9E8+0\
y+Wj/LcXh1l5n4Q1l5j8TSjS5mIQ==";
let expected_metadata = "{\"name\":\"perform.js\",\"size\":156,\"mime\":\"application/javascript\",\
\"key\":\"tqNrczqVdTCgFzB1b1gyiQBIYmwDBwa9\",\"lastModified\":499162500}";
let decrypted_metadata = decrypt_metadata(metadata_base64.as_bytes(), m_key.as_bytes()).unwrap();
assert_eq!(String::from_utf8_lossy(&decrypted_metadata), expected_metadata);
}
#[test]
fn encrypt_metadata_v2_should_use_aes_gcm_with_version_mark() {
let m_key = hash_fn("test");
let metadata = "{\"name\":\"perform.js\",\"size\":156,\"mime\":\"application/javascript\",\
\"key\":\"tqNrczqVdTCgFzB1b1gyiQBIYmwDBwa9\",\"lastModified\":499162500}";
let encrypted_metadata = encrypt_metadata(metadata.as_bytes(), m_key.as_bytes(), 2).unwrap();
assert_eq!(encrypted_metadata.len(), 211);
assert_eq!(&encrypted_metadata[..3], b"002");
}
#[test]
fn decrypt_metadata_v2_should_use_aes_gcm_with_version_mark() {
let m_key = hash_fn("test");
let encrypted_metadata = "002CWAZWUt8h5n0Il13bkeirz7uY05vmrO58ZXemzaIGnmy+iLe95hXtwiAWHF4s\
9+g7gcj3LmwykWnZzUEZIAu8zIEyqe2J//iKaZOJMSIqGIg05GvVBl9INeqf2ACU7wRE9P7tCI5tKqgEWG/sMqRwPGwbNN\
rn3yI8McEqCBdPWNfi6gl8OwzcqUVnMKZI/DPVSkUZQpaN83zCtA=";
let expected_metadata = "{\"name\":\"perform.js\",\"size\":156,\"mime\":\"application/javascript\",\
\"key\":\"tqNrczqVdTCgFzB1b1gyiQBIYmwDBwa9\",\"lastModified\":499162500}";
let decrypted_metadata = decrypt_metadata(encrypted_metadata.as_bytes(), m_key.as_bytes()).unwrap();
let decrypted_metadata_str = String::from_utf8_lossy(&decrypted_metadata);
assert_eq!(decrypted_metadata_str, expected_metadata);
}
#[test]
fn decrypt_metadata_v2_should_work_with_several_keys() {
let m_key_1 = hash_fn("invalid key");
let m_key_2 = hash_fn("test");
let m_keys = [m_key_1.as_bytes(), m_key_2.as_bytes()];
let encrypted_metadata = "002CWAZWUt8h5n0Il13bkeirz7uY05vmrO58ZXemzaIGnmy+iLe95hXtwiAWHF4s\
9+g7gcj3LmwykWnZzUEZIAu8zIEyqe2J//iKaZOJMSIqGIg05GvVBl9INeqf2ACU7wRE9P7tCI5tKqgEWG/sMqRwPGwbNN\
rn3yI8McEqCBdPWNfi6gl8OwzcqUVnMKZI/DPVSkUZQpaN83zCtA=";
let expected_metadata = "{\"name\":\"perform.js\",\"size\":156,\"mime\":\"application/javascript\",\
\"key\":\"tqNrczqVdTCgFzB1b1gyiQBIYmwDBwa9\",\"lastModified\":499162500}";
let decrypted_metadata = decrypt_metadata_any_key(encrypted_metadata.as_bytes(), &m_keys).unwrap();
let decrypted_metadata_str = String::from_utf8_lossy(&decrypted_metadata);
assert_eq!(decrypted_metadata_str, expected_metadata);
}
#[test]
fn decrypt_metadata_str_v2_should_work_with_several_keys() {
let m_key_1 = SecUtf8::from(hash_fn("invalid key"));
let m_key_2 = SecUtf8::from(hash_fn("test"));
let m_keys = [m_key_1, m_key_2];
let encrypted_metadata = "002CWAZWUt8h5n0Il13bkeirz7uY05vmrO58ZXemzaIGnmy+iLe95hXtwiAWHF4s\
9+g7gcj3LmwykWnZzUEZIAu8zIEyqe2J//iKaZOJMSIqGIg05GvVBl9INeqf2ACU7wRE9P7tCI5tKqgEWG/sMqRwPGwbNN\
rn3yI8McEqCBdPWNfi6gl8OwzcqUVnMKZI/DPVSkUZQpaN83zCtA=";
let expected_metadata = "{\"name\":\"perform.js\",\"size\":156,\"mime\":\"application/javascript\",\
\"key\":\"tqNrczqVdTCgFzB1b1gyiQBIYmwDBwa9\",\"lastModified\":499162500}";
let decrypted_metadata = decrypt_metadata_str_any_key(encrypted_metadata, &m_keys).unwrap();
assert_eq!(decrypted_metadata, expected_metadata);
}
#[test]
fn encrypt_aes_gcm_should_should_work_and_have_same_algorithm() {
let key = b"test";
let expected_data = "This is Jimmy.";
let encrypted_data = encrypt_aes_gcm_base64(expected_data.as_bytes(), key).unwrap();
assert_eq!(encrypted_data.len(), 52);
assert_ne!(&encrypted_data[..3], b"002");
let decrypted_data = decrypt_aes_gcm_base64(&encrypted_data, key).unwrap();
assert_eq!(String::from_utf8_lossy(&decrypted_data), expected_data);
}
#[test]
fn encrypt_aes_openssl_should_return_valid_aes_hash_without_explicit_salt() {
let key = b"test";
let expected_prefix = b"Salted__".to_vec();
let actual_aes_hash_bytes = encrypt_aes_openssl(b"This is Jimmy.", key, None).unwrap();
assert_eq!(actual_aes_hash_bytes.len(), 32);
assert_eq!(actual_aes_hash_bytes[..expected_prefix.len()], expected_prefix);
}
#[test]
fn encrypt_aes_openssl_should_return_valid_aes_hash_with_explicit_salt() {
let key = b"test";
let actual_aes_hash_bytes =
encrypt_aes_openssl(b"This is Jimmy.", key, Some(&[0_u8, 1, 2, 3, 4, 5, 6, 7])).unwrap();
let actual_aes_hash = base64::encode(&actual_aes_hash_bytes);
assert_eq!(
actual_aes_hash,
"U2FsdGVkX18AAQIDBAUGBzdjQTWH/ITXhkA7NCAPFOw=".to_owned()
);
}
#[test]
fn decrypt_aes_openssl_should_decrypt_previously_encrypted() {
let key = b"test";
let expected_data = b"This is Jimmy.";
let encrypted_data = base64::decode(b"U2FsdGVkX1/Yn4fcMeb/VlvaU8447BMpZgao7xwEM9I=").unwrap();
let actual_data_result = decrypt_aes_openssl(&encrypted_data, key);
let actual_data = actual_data_result.unwrap();
assert_eq!(actual_data, expected_data);
}
#[test]
fn decrypt_aes_openssl_should_decrypt_currently_encrypted() {
let key = b"test";
let expected_data = b"This is Jimmy.";
let encrypted_data = encrypt_aes_openssl(expected_data, key, Some(&[0_u8, 1, 2, 3, 4, 5, 6, 7])).unwrap();
let actual_data_result = decrypt_aes_openssl(&encrypted_data, key);
let actual_data = actual_data_result.unwrap();
assert_eq!(actual_data, expected_data);
}
#[test]
fn encrypt_rsa_and_decrypt_rsa_should_work_and_have_same_algorithm() {
let expected_data = "This is Jimmy.";
let m_key = SecUtf8::from("ed8d39b6c2d00ece398199a3e83988f1c4942b24");
let private_key_file_contents = read_project_file("tests/resources/filen_private_key.txt");
let private_key_metadata_encrypted = String::from_utf8_lossy(&private_key_file_contents);
let private_key_decrypted = decrypt_metadata_str(&private_key_metadata_encrypted, &m_key)
.map(|str| SecVec::from(base64::decode(str).unwrap()))
.unwrap();
let public_key_file_contents = read_project_file("tests/resources/filen_public_key.txt");
let public_key_file = base64::decode(public_key_file_contents).unwrap();
let encrypted_data = encrypt_rsa(expected_data.as_bytes(), &public_key_file).unwrap();
assert_eq!(encrypted_data.len(), 512);
let decrypted_data = decrypt_rsa(&encrypted_data, private_key_decrypted.unsecure()).unwrap();
assert_eq!(String::from_utf8_lossy(&decrypted_data), expected_data);
}
#[test]
fn derive_key_from_password_256_should_return_valid_pbkdf2_hash() {
let password = b"test_pwd";
let salt = b"test_salt";
let expected_pbkdf2_hash: [u8; 32] = [
248, 42, 24, 18, 8, 10, 202, 183, 237, 87, 81, 231, 25, 57, 132, 86, 92, 139, 21, 155, 224, 11, 182, 198,
110, 172, 112, 255, 12, 138, 216, 221,
];
let actual_pbkdf2_hash = derive_key_from_password_256(password, salt, 200_000);
assert_eq!(actual_pbkdf2_hash, expected_pbkdf2_hash);
}
#[test]
fn derive_key_from_password_512_should_return_valid_pbkdf2_hash() {
let password = b"test_pwd";
let salt = b"test_salt";
let expected_pbkdf2_hash: [u8; 64] = [
248, 42, 24, 18, 8, 10, 202, 183, 237, 87, 81, 231, 25, 57, 132, 86, 92, 139, 21, 155, 224, 11, 182, 198,
110, 172, 112, 255, 12, 138, 216, 221, 58, 253, 102, 41, 117, 40, 216, 13, 51, 181, 109, 144, 46, 10, 63,
172, 173, 165, 89, 54, 223, 115, 173, 131, 123, 157, 117, 100, 113, 185, 63, 49,
];
let actual_pbkdf2_hash = derive_key_from_password_512(password, salt, 200_000);
assert_eq!(actual_pbkdf2_hash, expected_pbkdf2_hash);
}
#[test]
fn hash_password_should_return_valid_hash() {
let password = "test_pwd".to_owned();
let expected_hash = "21160f51da2cbbe04a195db31d7da72639d2eb99f9da3b05461123ab39b856cbb981fc9b97e64b36ab897\
7c6190117b18fa6d3055ac0b3411ea086fdc71bae0d806ec431c8628905f437276c3f64349683680974a7e\
00ef216b94dbbc711bd4645df3ab46de3ed787828b73fc5c8a5abd959cb0d64591042519ef1b14ad08db7";
let actual_hash = hash_password(&password);
assert_eq!(actual_hash, expected_hash);
}
#[test]
fn decrypt_file_data_should_decrypt_raw_aes_cbc() {
let file_key: &[u8; 32] = b"sh1YRHfx22Ij40tQBbt6BgpBlqkzch8Y";
let file_encrypted_bytes = read_project_file("tests/resources/responses/download_file_aes_cbc_as_is.bin");
let file_decrypted_bytes_result = decrypt_file_chunk(&file_encrypted_bytes, file_key, 1);
assert!(file_decrypted_bytes_result.is_ok());
let file_decrypted_bytes = file_decrypted_bytes_result.unwrap();
let image_load_result = image::load_from_memory_with_format(&file_decrypted_bytes, image::ImageFormat::Png);
assert!(image_load_result.is_ok());
}
}