use crate::proto::metadata as pmeta;
use crate::{errors, rand};
use protobuf::Message;
pub const RING_NONCE_SIZE: usize = 12;
pub const PBKDF2_SALT_SIZE: usize = 32;
pub const PBKDF2_DEFAULT_ITERATIONS: usize = 100000;
pub const PBKDF2_DEFAULT_HASH_FN: HashFunction = HashFunction::SHA256;
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum HashFunction {
SHA256,
SHA384,
SHA512,
}
impl HashFunction {
pub fn from_proto(
proto_hash_fn: pmeta::HashFunction,
) -> Result<Self, errors::Error> {
match proto_hash_fn {
pmeta::HashFunction::HASH_FUNCTION_INVALID => {
Err(errors::Error::MetadataInvalid)
}
pmeta::HashFunction::HASH_FUNCTION_SHA256 => {
Ok(HashFunction::SHA256)
}
pmeta::HashFunction::HASH_FUNCTION_SHA384 => {
Ok(HashFunction::SHA384)
}
pmeta::HashFunction::HASH_FUNCTION_SHA512 => {
Ok(HashFunction::SHA512)
}
}
}
pub fn to_proto(&self) -> pmeta::HashFunction {
match self {
HashFunction::SHA256 => pmeta::HashFunction::HASH_FUNCTION_SHA256,
HashFunction::SHA384 => pmeta::HashFunction::HASH_FUNCTION_SHA384,
HashFunction::SHA512 => pmeta::HashFunction::HASH_FUNCTION_SHA512,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct KeyDerivationMetadata {
pub hash_fn: HashFunction,
pub iterations: usize,
pub salt: [u8; PBKDF2_SALT_SIZE],
}
impl KeyDerivationMetadata {
pub fn new(
hash_fn: HashFunction,
iterations: usize,
salt: [u8; PBKDF2_SALT_SIZE],
) -> Self {
Self {
hash_fn,
iterations,
salt,
}
}
pub fn generate() -> Self {
let mut salt = [0u8; PBKDF2_SALT_SIZE];
rand::fill_buf(&mut salt);
Self::new(PBKDF2_DEFAULT_HASH_FN, PBKDF2_DEFAULT_ITERATIONS, salt)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum KeyDerivationAlgorithm {
None,
PBKDF2(KeyDerivationMetadata),
}
impl KeyDerivationAlgorithm {
pub fn from_proto(
proto_meta: &pmeta::KeyDerivationMetadata,
) -> Result<Self, errors::Error> {
let err = Err(errors::Error::MetadataInvalid);
match proto_meta.algo.enum_value_or_default() {
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_INVALID => {
return err;
},
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_NONE => {
return Ok(KeyDerivationAlgorithm::None);
},
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_PBKDF2 => {
()
},
}
if proto_meta.iterations == 0 {
return err;
}
let iterations = proto_meta.iterations as usize;
let hash_fn = HashFunction::from_proto(
proto_meta.hash_fn.enum_value_or_default(),
)?;
if proto_meta.salt.len() != PBKDF2_SALT_SIZE {
return err;
}
let mut salt = [0u8; PBKDF2_SALT_SIZE];
salt.copy_from_slice(&proto_meta.salt);
let meta = KeyDerivationMetadata::new(hash_fn, iterations, salt);
Ok(KeyDerivationAlgorithm::PBKDF2(meta))
}
pub fn to_proto(&self) -> pmeta::KeyDerivationMetadata {
let mut proto_meta = pmeta::KeyDerivationMetadata::new();
let proto_none_algo =
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_NONE;
let proto_pbkdf2_algo =
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_PBKDF2;
let meta = match self {
KeyDerivationAlgorithm::None => {
proto_meta.algo = proto_none_algo.into();
return proto_meta;
}
KeyDerivationAlgorithm::PBKDF2(meta) => meta,
};
proto_meta.algo = proto_pbkdf2_algo.into();
proto_meta.iterations = meta.iterations as u64;
proto_meta.hash_fn = meta.hash_fn.to_proto().into();
proto_meta.salt = meta.salt.to_vec();
proto_meta
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EncryptionMetadata {
pub nonce: [u8; RING_NONCE_SIZE],
}
impl EncryptionMetadata {
pub fn new(nonce: [u8; RING_NONCE_SIZE]) -> Self {
Self { nonce }
}
pub fn generate() -> Self {
let mut nonce = [0u8; RING_NONCE_SIZE];
rand::fill_buf(&mut nonce);
Self::new(nonce)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EncryptionAlgorithm {
AES256GCM(EncryptionMetadata),
ChaCha20Poly1305(EncryptionMetadata),
}
impl EncryptionAlgorithm {
pub fn from_proto(
proto_meta: &pmeta::EncryptionMetadata,
) -> Result<Self, errors::Error> {
let err = Err(errors::Error::MetadataInvalid);
match proto_meta.algo.enum_value_or_default() {
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID => {
return err
}
_ => (),
};
if proto_meta.nonce.len() != RING_NONCE_SIZE {
return err;
}
let mut nonce = [0u8; RING_NONCE_SIZE];
nonce.copy_from_slice(&proto_meta.nonce);
let meta = EncryptionMetadata::new(nonce);
match proto_meta.algo.enum_value_or_default() {
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_INVALID => err,
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_AES256GCM => {
Ok(EncryptionAlgorithm::AES256GCM(meta))
},
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_CHACHA20_POLY1305 => {
Ok(EncryptionAlgorithm::ChaCha20Poly1305(meta))
}
}
}
pub fn to_proto(&self) -> pmeta::EncryptionMetadata {
let mut proto_meta = pmeta::EncryptionMetadata::new();
let proto_aes_algo =
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_AES256GCM;
let proto_chacha_algo =
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_CHACHA20_POLY1305;
match self {
EncryptionAlgorithm::AES256GCM(meta) => {
proto_meta.algo = proto_aes_algo.into();
proto_meta.nonce = meta.nonce.to_vec();
}
EncryptionAlgorithm::ChaCha20Poly1305(meta) => {
proto_meta.algo = proto_chacha_algo.into();
proto_meta.nonce = meta.nonce.to_vec();
}
};
proto_meta
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Metadata {
pub key_deriv_algo: KeyDerivationAlgorithm,
pub enc_algo: EncryptionAlgorithm,
pub ciphertext_size: usize,
}
impl<'a> Metadata {
pub fn new(
key_deriv_algo: KeyDerivationAlgorithm,
enc_algo: EncryptionAlgorithm,
plaintext_size: usize,
) -> Self {
let ciphertext_size =
Self::calculate_ciphertext_size(plaintext_size, &enc_algo);
Self {
key_deriv_algo,
enc_algo,
ciphertext_size,
}
}
pub fn calculate_ciphertext_size(
plaintext_size: usize,
enc_algo: &EncryptionAlgorithm,
) -> usize {
match enc_algo {
EncryptionAlgorithm::AES256GCM(_) => {
plaintext_size + ring::aead::AES_256_GCM.tag_len()
}
EncryptionAlgorithm::ChaCha20Poly1305(_) => {
plaintext_size + ring::aead::CHACHA20_POLY1305.tag_len()
}
}
}
pub fn generate_for_key(plaintext_size: usize) -> Self {
let key_deriv_algo = KeyDerivationAlgorithm::None;
let enc_meta = EncryptionMetadata::generate();
let enc_algo = EncryptionAlgorithm::AES256GCM(enc_meta);
Self::new(key_deriv_algo, enc_algo, plaintext_size)
}
pub fn generate_for_passphrase(plaintext_size: usize) -> Self {
let key_deriv_meta = KeyDerivationMetadata::generate();
let key_deriv_algo = KeyDerivationAlgorithm::PBKDF2(key_deriv_meta);
let enc_meta = EncryptionMetadata::generate();
let enc_algo = EncryptionAlgorithm::AES256GCM(enc_meta);
Self::new(key_deriv_algo, enc_algo, plaintext_size)
}
pub fn from_proto(
proto_meta: &pmeta::Metadata,
) -> Result<Self, errors::Error> {
let err = Err(errors::Error::MetadataInvalid);
let proto_key_meta = proto_meta.key_deriv_meta.get_or_default();
let key_deriv_algo =
KeyDerivationAlgorithm::from_proto(proto_key_meta)?;
let proto_enc_meta = proto_meta.enc_meta.get_or_default();
let enc_algo = EncryptionAlgorithm::from_proto(proto_enc_meta)?;
let ciphertext_size = proto_meta.ciphertext_size as usize;
let min_ciphertext_size =
Self::calculate_ciphertext_size(0, &enc_algo);
if min_ciphertext_size > ciphertext_size {
return err;
}
Ok(Self {
key_deriv_algo,
enc_algo,
ciphertext_size,
})
}
pub fn to_proto(&self) -> pmeta::Metadata {
let mut proto_meta = pmeta::Metadata::new();
let key_meta = self.key_deriv_algo.to_proto();
proto_meta.key_deriv_meta = Some(key_meta).into();
let enc_meta = self.enc_algo.to_proto();
proto_meta.enc_meta = Some(enc_meta).into();
proto_meta.ciphertext_size = self.ciphertext_size as u64;
proto_meta
}
pub fn from_buf(buf: &'a [u8]) -> Result<(Self, usize), errors::Error> {
let mut is = protobuf::CodedInputStream::from_bytes(&buf);
let proto_meta = match is.read_message() {
Ok(meta) => meta,
Err(_) => return Err(errors::Error::MetadataMissing),
};
let proto_meta_size = is.pos() as usize;
let meta = Metadata::from_proto(&proto_meta)?;
Ok((meta, proto_meta_size))
}
pub fn to_buf(&self) -> (Vec<u8>, usize) {
let proto_meta = self.to_proto();
let mut buf = proto_meta.write_length_delimited_to_bytes().unwrap();
let proto_meta_size = buf.len();
buf.resize(proto_meta_size + self.ciphertext_size, 0u8);
(buf, proto_meta_size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_function() {
let err = Err(errors::Error::MetadataInvalid);
let hash_fn = HashFunction::SHA256;
let proto_hash_fn = pmeta::HashFunction::HASH_FUNCTION_SHA256;
let inv_proto_hash_fn = pmeta::HashFunction::HASH_FUNCTION_INVALID;
assert_eq!(HashFunction::from_proto(inv_proto_hash_fn), err);
assert_eq!(HashFunction::from_proto(proto_hash_fn), Ok(hash_fn));
assert_eq!(hash_fn.to_proto(), proto_hash_fn);
}
#[test]
fn test_key_derivation_algorithm() {
let err = Err(errors::Error::MetadataInvalid);
let proto_none_algo =
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_NONE;
let proto_pbkdf2_algo =
pmeta::KeyDerivationAlgorithm::KEY_DERIVATION_ALGORITHM_PBKDF2;
let inv_proto_meta = pmeta::KeyDerivationMetadata::new();
assert_eq!(KeyDerivationAlgorithm::from_proto(&inv_proto_meta), err);
let proto_meta = KeyDerivationAlgorithm::None.to_proto();
assert_eq!(proto_meta.algo.enum_value_or_default(), proto_none_algo);
assert_eq!(
KeyDerivationAlgorithm::from_proto(&proto_meta),
Ok(KeyDerivationAlgorithm::None)
);
let meta = KeyDerivationMetadata::generate();
let algo = KeyDerivationAlgorithm::PBKDF2(meta);
let proto_meta = algo.to_proto();
assert_eq!(proto_meta.algo.enum_value_or_default(), proto_pbkdf2_algo);
assert_eq!(KeyDerivationAlgorithm::from_proto(&proto_meta), Ok(algo));
let mut proto_meta = algo.to_proto();
proto_meta.iterations = 0;
assert_eq!(KeyDerivationAlgorithm::from_proto(&proto_meta), err);
let mut proto_meta = algo.to_proto();
proto_meta.hash_fn = pmeta::HashFunction::HASH_FUNCTION_INVALID.into();
assert_eq!(KeyDerivationAlgorithm::from_proto(&proto_meta), err);
let mut proto_meta = algo.to_proto();
proto_meta.salt = vec![];
assert_eq!(KeyDerivationAlgorithm::from_proto(&proto_meta), err);
}
#[test]
fn test_encryption_algorithm() {
let err = Err(errors::Error::MetadataInvalid);
let proto_aes_algo =
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_AES256GCM;
let proto_chacha_algo =
pmeta::EncryptionAlgorithm::ENCRYPTION_ALGORITHM_CHACHA20_POLY1305;
let inv_proto_meta = pmeta::EncryptionMetadata::new();
assert_eq!(EncryptionAlgorithm::from_proto(&inv_proto_meta), err);
let meta = EncryptionMetadata::generate();
let aes_algo = EncryptionAlgorithm::AES256GCM(meta);
let meta = EncryptionMetadata::generate();
let chacha_algo = EncryptionAlgorithm::ChaCha20Poly1305(meta);
for (algo, proto_algo) in
&[(aes_algo, proto_aes_algo), (chacha_algo, proto_chacha_algo)]
{
let mut proto_meta = algo.to_proto();
assert_eq!(proto_meta.algo, (*proto_algo).into());
assert_eq!(
EncryptionAlgorithm::from_proto(&proto_meta),
Ok(algo.clone())
);
proto_meta.nonce = vec![];
assert_eq!(EncryptionAlgorithm::from_proto(&proto_meta), err);
}
}
#[test]
fn test_metadata_proto() {
let err = Err(errors::Error::MetadataInvalid);
let inv_proto_meta = pmeta::Metadata::new();
assert_eq!(Metadata::from_proto(&inv_proto_meta), err);
let meta1 = Metadata::generate_for_passphrase(0);
let enc_meta = EncryptionMetadata::generate();
let enc_algo = EncryptionAlgorithm::ChaCha20Poly1305(enc_meta);
let meta2 = Metadata::new(KeyDerivationAlgorithm::None, enc_algo, 0);
for meta in &[meta1, meta2] {
assert!(meta.ciphertext_size > 0);
let proto_meta = meta.to_proto();
assert_eq!(Metadata::from_proto(&proto_meta), Ok(meta.clone()));
let mut proto_meta = meta.to_proto();
proto_meta.key_deriv_meta.clear();
assert_eq!(Metadata::from_proto(&proto_meta), err);
let mut proto_meta = meta.to_proto();
proto_meta.enc_meta.clear();
assert_eq!(Metadata::from_proto(&proto_meta), err);
let mut proto_meta = meta.to_proto();
proto_meta.ciphertext_size = 0;
assert_eq!(Metadata::from_proto(&proto_meta), err);
}
}
#[test]
fn test_metadata_buf() {
let missing_err = Err(errors::Error::MetadataMissing);
let invalid_err = Err(errors::Error::MetadataInvalid);
let meta = Metadata::generate_for_passphrase(9);
let (buf, meta_size) = meta.to_buf();
assert_eq!(Metadata::from_buf(&buf), Ok((meta, meta_size)));
assert_eq!(Metadata::from_buf(&[]), missing_err);
let mut proto_meta = Metadata::generate_for_key(9).to_proto();
proto_meta.key_deriv_meta.clear();
let buf = proto_meta.write_length_delimited_to_bytes().unwrap();
assert_eq!(Metadata::from_buf(&buf), invalid_err);
}
}