Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDK] Basic keyloader facade #7137

Merged
merged 8 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions tuta-sdk/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tuta-sdk/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pqcrypto-traits = "0.3.4"
rsa = "0.9.6"
rand_core = "0.6.4"
serde_bytes = "0.11.14"
futures = "0.3.30"
mockall_double = "0.3.1"
log = "0.4.22"
simple_logger = "5.0.0"
Expand Down
25 changes: 21 additions & 4 deletions tuta-sdk/rust/src/crypto/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ pub enum EnforceMac {
/// - $subkey_digest: The type of SHA hasher from the `sha2` dependency to use.
macro_rules! aes_key {
($name:tt, $type_name:literal, $size:expr, $cbc:ty, $subkey_digest:ty) => {
#[derive(Clone, ZeroizeOnDrop)]
#[derive(Clone, ZeroizeOnDrop, PartialEq)]
#[cfg_attr(test, derive(Debug))] // only allow Debug in tests because this prints the key!
pub struct $name([u8; $size]);

impl $name {
Expand Down Expand Up @@ -115,7 +116,7 @@ aes_key!(

aes_key!(
Aes256Key,
"Aes128Key",
"Aes256Key",
AES_256_KEY_SIZE,
aes::Aes256,
sha2::Sha512
Expand All @@ -133,6 +134,17 @@ trait AesKey: Clone {
/// An initialisation vector for AES encryption
pub struct Iv([u8; IV_BYTE_SIZE]);

#[cfg(test)]
impl Clone for Iv {
/// Clone the initialization vector
///
/// This is implemented so that entity_facade_test_utils will work. You should never, ever, ever
/// re-use an IV, as this can lead to information leakage.
fn clone(&self) -> Self {
Iv(self.0.clone())
}
}

impl Iv {
/// Generate an initialisation vector.
pub fn generate(randomizer_facade: &RandomizerFacade) -> Self {
Expand Down Expand Up @@ -312,8 +324,13 @@ struct CiphertextWithAuthentication<'a> {

impl<'a> CiphertextWithAuthentication<'a> {
fn parse(bytes: &'a [u8]) -> Result<Option<CiphertextWithAuthentication<'a>>, AesDecryptError> {
// Error if the bytes does not feature a MAC
if !has_mac(bytes) || bytes.len() <= IV_BYTE_SIZE + MAC_SIZE {
// No MAC
if !has_mac(bytes) {
return Ok(None);
}

// Incorrect size for Hmac
if bytes.len() <= IV_BYTE_SIZE + MAC_SIZE {
return Err(AesDecryptError::HmacError);
}

Expand Down
42 changes: 23 additions & 19 deletions tuta-sdk/rust/src/crypto/crypto_facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::crypto::aes::Iv;
use crate::crypto::ecc::EccPublicKey;
use crate::crypto::key::{AsymmetricKeyPair, GenericAesKey, KeyLoadError};
#[mockall_double::double]
use crate::crypto::key_loader_facade::KeyLoaderFacade;
use crate::crypto::key_loader_facade::VersionedAesKey;
use crate::key_loader_facade::KeyLoaderFacade;
use crate::key_loader_facade::VersionedAesKey;
use crate::crypto::randomizer_facade::RandomizerFacade;
use crate::crypto::rsa::RSAEncryptionError;
use crate::crypto::tuta_crypt::{PQError, PQMessage};
Expand Down Expand Up @@ -42,14 +42,14 @@ pub struct CryptoFacade {
impl CryptoFacade {
/// Returns the session key from `entity` and resolves the bucket key fields contained inside
/// if present
pub fn resolve_session_key(&self, entity: &mut ParsedEntity, model: &TypeModel) -> Result<Option<GenericAesKey>, SessionKeyResolutionError> {
pub async fn resolve_session_key(&self, entity: &mut ParsedEntity, model: &TypeModel) -> Result<Option<GenericAesKey>, SessionKeyResolutionError> {
if !model.encrypted {
return Ok(None);
}

// Derive the session key from the bucket key
if entity.contains_key(BUCKET_KEY_FIELD) {
let resolved_key = self.resolve_bucket_key(entity, model)?;
let resolved_key = self.resolve_bucket_key(entity, model).await?;
return Ok(Some(resolved_key));
}

Expand All @@ -62,12 +62,13 @@ impl CryptoFacade {
return Err(SessionKeyResolutionError { reason: "instance missing owner key/group data".to_string() });
};

let group_key = self.key_loader_facade.get_group_key(owner_group, owner_key_version)?;
Ok(group_key.decrypt_key(owner_enc_session_key).map(|k| Some(k))?)
let group_key: GenericAesKey = self.key_loader_facade.load_sym_group_key(owner_group, owner_key_version, None).await?;

Ok(group_key.decrypt_aes_key(owner_enc_session_key).map(|k| Some(k))?)
}

/// Resolves the bucket key fields inside `entity` and returns the session key
fn resolve_bucket_key(&self, entity: &mut ParsedEntity, model: &TypeModel) -> Result<GenericAesKey, SessionKeyResolutionError> {
async fn resolve_bucket_key(&self, entity: &mut ParsedEntity, model: &TypeModel) -> Result<GenericAesKey, SessionKeyResolutionError> {
let Some(ElementValue::Dict(bucket_key_map)) = entity.get(BUCKET_KEY_FIELD) else {
return Err(SessionKeyResolutionError { reason: format!("{BUCKET_KEY_FIELD} is not a dictionary type") });
};
Expand All @@ -82,18 +83,18 @@ impl CryptoFacade {
return Err(SessionKeyResolutionError { reason: "entity has no ownerGroup".to_owned() });
};

let VersionedAesKey { key: _key, version } = self.key_loader_facade.get_current_group_key(owner_group)?;
let VersionedAesKey { version, .. } = self.key_loader_facade.get_current_sym_group_key(owner_group).await?;

let ResolvedBucketKey {
decrypted_bucket_key,
sender_identity_key: _sender_identity_key // TODO: Use when implementing authentication
} = self.decrypt_bucket_key(&bucket_key, owner_group, model)?;
} = self.decrypt_bucket_key(&bucket_key, owner_group, model).await?;

let mut session_key_for_this_instance = None;
let mut re_encrypted_session_keys = Vec::with_capacity(bucket_key.bucketEncSessionKeys.len());

for instance_session_key in bucket_key.bucketEncSessionKeys {
let decrypted_session_key = decrypted_bucket_key.decrypt_key(instance_session_key.symEncSessionKey.as_slice())?;
let decrypted_session_key = decrypted_bucket_key.decrypt_aes_key(instance_session_key.symEncSessionKey.as_slice())?;
let iv = Iv::generate(self.randomizer_facade.as_ref());
let re_encrypted_session_key = decrypted_bucket_key.encrypt_key(&decrypted_session_key, iv);

Expand All @@ -111,6 +112,7 @@ impl CryptoFacade {

// TODO: authenticate

// TODO: Update owner and session keys
let mut queue = self.update_queue.lock().unwrap();
for (instance_data, sym_enc_key) in re_encrypted_session_keys {
queue.queue_update_instance_session_key(
Expand All @@ -128,11 +130,11 @@ impl CryptoFacade {
/// Decrypts a bucket key, using `owner_group` in the case of secure external.
///
/// `model` should be the type model of the instance being decrypted (e.g. `Mail`).
fn decrypt_bucket_key(&self, bucket_key: &BucketKey, owner_group: &GeneratedId, model: &TypeModel) -> Result<ResolvedBucketKey, SessionKeyResolutionError> {
async fn decrypt_bucket_key(&self, bucket_key: &BucketKey, owner_group: &GeneratedId, model: &TypeModel) -> Result<ResolvedBucketKey, SessionKeyResolutionError> {
let mut auth_status = None;

let resolved_key = if let (Some(key_group), Some(pub_enc_bucket_key)) = (&bucket_key.keyGroup, &bucket_key.pubEncBucketKey) {
let keypair = self.key_loader_facade.get_asymmetric_key_pair(key_group, bucket_key.recipientKeyVersion)?;
let keypair = self.key_loader_facade.load_key_pair(key_group, bucket_key.recipientKeyVersion).await?;
match keypair {
AsymmetricKeyPair::PQKeyPairs(k) => {
let decrypted_bucket_key = PQMessage::deserialize(pub_enc_bucket_key)?.decapsulate(&k)?.into();
Expand All @@ -149,6 +151,7 @@ impl CryptoFacade {
sender_identity_key: None,
}
}
AsymmetricKeyPair::RsaEccKeyPair(_) => { todo!() }
}
} else if let Some(_group_enc_bucket_key) = &bucket_key.groupEncBucketKey {
// TODO: to be used with secure external
Expand Down Expand Up @@ -262,7 +265,7 @@ mod test {
use crate::crypto::crypto_facade::CryptoFacade;
use crate::crypto::ecc::EccKeyPair;
use crate::crypto::key::GenericAesKey;
use crate::crypto::key_loader_facade::{MockKeyLoaderFacade, VersionedAesKey};
use crate::key_loader_facade::{MockKeyLoaderFacade, VersionedAesKey};
use crate::crypto::randomizer_facade::test_util::make_thread_rng_facade;
use crate::crypto::tuta_crypt::{PQKeyPairs, PQMessage};
use crate::entities::Entity;
Expand All @@ -275,8 +278,8 @@ mod test {
use crate::type_model_provider::init_type_model_provider;
use crate::util::test_utils::create_test_entity;

#[test]
fn test_bucket_key_resolves() {
#[tokio::test]
async fn test_bucket_key_resolves() {
let randomizer_facade = Arc::new(make_thread_rng_facade());
let mut update_queue = Box::new(MockOwnerEncSessionKeysUpdateQueue::new());
update_queue.expect_queue_update_instance_session_key()
Expand All @@ -298,11 +301,11 @@ mod test {
let group_key = group_key.clone();
let asymmetric_keypair_versioned = asymmetric_keypair.clone();

let mut key_loader = MockKeyLoaderFacade::new();
key_loader.expect_get_current_group_key()
.returning(move |_| Ok(VersionedAesKey { version: sender_key_version, key: group_key.clone().into() }))
let mut key_loader = MockKeyLoaderFacade::default();
key_loader.expect_get_current_sym_group_key()
.returning(move |_| Ok(VersionedAesKey { version: sender_key_version, object: group_key.clone().into() }))
.once();
key_loader.expect_get_asymmetric_key_pair()
key_loader.expect_load_key_pair()
.returning(move |_, _| Ok(asymmetric_keypair_versioned.clone().into()))
.once();
key_loader
Expand Down Expand Up @@ -367,6 +370,7 @@ mod test {
let mail_type_model = provider.get_type_model(&mail_type_ref.app, &mail_type_ref.type_).unwrap();

let key = crypto_facade.resolve_session_key(&mut raw_mail, &mail_type_model)
.await
.expect("should not have errored")
.expect("where is the key");

Expand Down
9 changes: 6 additions & 3 deletions tuta-sdk/rust/src/crypto/ecc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use crate::util::{ArrayCastingError, array_cast_slice};

const ECC_KEY_SIZE: usize = 32;

#[derive(ZeroizeOnDrop, Clone)]
#[derive(ZeroizeOnDrop, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))] // only allow Debug in tests because this prints the key!
pub struct EccPrivateKey([u8; ECC_KEY_SIZE]);

impl EccPrivateKey {
Expand Down Expand Up @@ -40,10 +41,12 @@ impl EccPrivateKey {
}
}

#[derive(ZeroizeOnDrop, Clone)]
#[derive(ZeroizeOnDrop, Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))] // only allow Debug in tests because this prints the key!
pub struct EccPublicKey([u8; ECC_KEY_SIZE]);

#[derive(Clone)]
#[derive(Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))] // only allow Debug in tests because this prints the key!
pub struct EccKeyPair {
pub public_key: EccPublicKey,
pub private_key: EccPrivateKey,
Expand Down
Loading