Skip to content

Commit

Permalink
WIP: authenticate in crypto_entit_client to avoid decrypting and pars…
Browse files Browse the repository at this point in the history
…ing mails in resolve_session_key
  • Loading branch information
vaf-hub committed Feb 12, 2025
1 parent b83ffca commit cd28527
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 125 deletions.
130 changes: 8 additions & 122 deletions tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,25 @@ use crate::crypto::aes::Iv;
#[cfg_attr(test, mockall_double::double)]
use crate::crypto::asymmetric_crypto_facade::AsymmetricCryptoFacade;
use crate::crypto::asymmetric_crypto_facade::{AsymmetricCryptoError, DecapsulatedAesKey};
use crate::crypto::key::{AsymmetricKeyPair, GenericAesKey, KeyLoadError};
use crate::crypto::public_key_provider::PublicKeyIdentifier;
use crate::crypto::key::{GenericAesKey, KeyLoadError};
use crate::crypto::randomizer_facade::RandomizerFacade;
use crate::crypto::rsa::RSAEncryptionError;
use crate::crypto::tuta_crypt::PQError;
use crate::crypto::Aes256Key;
use crate::element_value::{ElementValue, ParsedEntity};
#[cfg_attr(test, mockall_double::double)]
use crate::entities::entity_facade::EntityFacade;
use crate::entities::entity_facade::{
BUCKET_KEY_FIELD, ID_FIELD, OWNER_ENC_SESSION_KEY_FIELD, OWNER_GROUP_FIELD,
OWNER_KEY_VERSION_FIELD,
};
use crate::entities::generated::sys::{BucketKey, InstanceSessionKey};
use crate::entities::generated::sys::BucketKey;
use crate::instance_mapper::InstanceMapper;
#[cfg_attr(test, mockall_double::double)]
use crate::key_loader_facade::KeyLoaderFacade;
use crate::metamodel::TypeModel;
use crate::tutanota_constants::{
CryptoProtocolVersion, EncryptionAuthStatus, PublicKeyIdentifierType, SYSTEM_GROUP_MAIL_ADDRESS,
};
use crate::util::{convert_version_to_u64, ArrayCastingError, Versioned};
use crate::tutanota_constants::{CryptoProtocolVersion, EncryptionAuthStatus};
use crate::util::{convert_version_to_u64, ArrayCastingError};
use crate::GeneratedId;
use crate::IdTupleGenerated;
use crate::{ApiCallError, GeneratedId};
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use base64::Engine;
use std::sync::Arc;
Expand All @@ -36,7 +31,6 @@ pub struct CryptoFacade {
instance_mapper: Arc<InstanceMapper>,
randomizer_facade: RandomizerFacade,
asymmetric_crypto_facade: Arc<AsymmetricCryptoFacade>,
entity_facade: Arc<dyn EntityFacade>,
}

/// Session key that encrypts an entity and the same key encrypted with the owner group.
Expand All @@ -48,22 +42,20 @@ pub struct ResolvedSessionKey {
pub owner_key_version: u64,
}

#[cfg_attr(test, for<'a> mockall::automock)]
#[cfg_attr(test, mockall::automock)]
impl CryptoFacade {
#[must_use]
pub fn new(
key_loader_facade: Arc<KeyLoaderFacade>,
instance_mapper: Arc<InstanceMapper>,
randomizer_facade: RandomizerFacade,
asymmetric_crypto_facade: Arc<AsymmetricCryptoFacade>,
entity_facade: Arc<dyn EntityFacade>,
) -> Self {
Self {
key_loader_facade,
instance_mapper,
randomizer_facade,
asymmetric_crypto_facade,
entity_facade,
}
}

Expand Down Expand Up @@ -153,7 +145,7 @@ impl CryptoFacade {
});
};

let mut encryption_auth_status: Option<EncryptionAuthStatus> = None; // TODO: implement
let mut _auth_status: Option<EncryptionAuthStatus> = None; // TODO: implement
let DecapsulatedAesKey {
decrypted_aes_key: decrypted_bucket_key,
sender_identity_pub_key: _sender_identity_key,
Expand All @@ -171,7 +163,7 @@ impl CryptoFacade {
} else if let Some(_group_enc_bucket_key) = &bucket_key.groupEncBucketKey {
// TODO: to be used with secure external
let _key_group = bucket_key.keyGroup.as_ref().unwrap_or(owner_group);
encryption_auth_status = Some(EncryptionAuthStatus::AESNoAuthentication);
_auth_status = Some(EncryptionAuthStatus::AESNoAuthentication);
todo!("secure external resolveWithGroupReference")
} else {
return Err(SessionKeyResolutionError {
Expand All @@ -182,21 +174,6 @@ impl CryptoFacade {
});
};

// we can only authenticate once we have the session key
// because we need to check if the confidential flag is set, which is encrypted still
// we need to do it here at the latest because we must write the flag when updating the session key on the instance
// self.authenticateMainInstance(
// typeModel,
// encryptionAuthStatus,
// pqMessageSenderKey,
// bucketKey.protocolVersion == CryptoProtocolVersion.TUTA_CRYPT ? parseKeyVersion(bucketKey.senderKeyVersion ?? "0") : null,
// instance,
// resolvedSessionKeyForInstance,
// instanceSessionKeyWithOwnerEncSessionKey,
// decryptedSessionKey,
// bucketKey.keyGroup,
// ).await?;

let mut session_key_for_this_instance = None;

for instance_session_key in bucket_key.bucketEncSessionKeys {
Expand Down Expand Up @@ -231,96 +208,6 @@ impl CryptoFacade {
owner_key_version: versioned_owner_group_key.version,
})
}

/// @return None if not a main instance, the EncryptionAuthStatus from the asymmetric decryption otherwise
async fn authenticate_main_instance(
&self,
type_model: TypeModel,
encryption_auth_status: Option<EncryptionAuthStatus>,
pq_message_sender_key: Option<Versioned<&[u8]>>,
entity: &ParsedEntity,
resolved_session_key_for_instance: GenericAesKey,
instance_session_key_with_owner_enc_session_key: InstanceSessionKey,
decrypted_session_key: GenericAesKey,
recipient_group: GeneratedId,
) -> Option<EncryptionAuthStatus> {
// TODO the (Mail)TypeRef must be generated
if !type_model.is_same_type_by_attr("tutanota", "Mail") {
// we only authenticate mail instances currently
return None;
}
match encryption_auth_status {
Some(_) => encryption_auth_status,
None => {
match pq_message_sender_key {
None => {
// This message was encrypted with RSA. We check if TutaCrypt could have been used instead.
let current_key_pair: Versioned<AsymmetricKeyPair> = self
.key_loader_facade
.load_current_key_pair(recipient_group)
.await?;
match current_key_pair.object {
AsymmetricKeyPair::RSAKeyPair(_)
| AsymmetricKeyPair::RSAEccKeyPair(_) => EncryptionAuthStatus::RSANoAuthentication,
AsymmetricKeyPair::PQKeyPairs(_) => {
// theoretically we could check that we did not rotate during this session.
// However, we currently cannot rotate in the sdk. So it is pointless.
EncryptionAuthStatus::RsaDespiteTutacrypt
},
}
},
Some(sender_key) => {
// TODO do we need to check if this is a literal?
let mail = self.entity_facade.decrypt_and_map(type_model, entity);
fn decrypt_and_map(
&self,
type_model: &TypeModel,
entity: ParsedEntity,
resolved_session_key: ResolvedSessionKey,
) -> Result<ParsedEntity, ApiCallError>;

//TODO sender addr
let sender_mail_address = if is_confidential_mail {
"SENDER_ADDR"
} else {
SYSTEM_GROUP_MAIL_ADDRESS
};
self.try_authenticate_sender_of_main_instance(
sender_mail_address,
sender_key.object,
)
},
}
},
}
}

async fn try_authenticate_sender_of_main_instance(
&self,
sender_mail_address: String,
pq_message_sender_key: Versioned<&[u8]>,
) -> EncryptionAuthStatus {
let result = self
.asymmetric_crypto_facade
.authenticate_sender(
PublicKeyIdentifier {
identifier: sender_mail_address,
identifier_type: PublicKeyIdentifierType::MailAddress,
},
pq_message_sender_key.object,
pq_message_sender_key.version,
)
.await;
match result {
Err(_e) => {
// TODO log the error
// we do not want to fail mail decryption here, e.g. in case an alias was removed we would get a permanent NotFoundError.
// in those cases we will just show a warning banner but still want to display the mail
EncryptionAuthStatus::TutacryptAuthenticationFailed
},
Ok(encryption_auth_status) => encryption_auth_status,
}
}
}

/// Resolves the id field of an entity into a generated id
Expand Down Expand Up @@ -690,7 +577,6 @@ mod test {
instance_mapper: Arc::new(InstanceMapper::new()),
randomizer_facade,
asymmetric_crypto_facade: Arc::new(asymmetric_crypto_facade),
entity_facade: Arc::new(EntityFacade::new()),
}
}

Expand Down
97 changes: 96 additions & 1 deletion tuta-sdk/rust/sdk/src/crypto_entity_client.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#[cfg_attr(test, mockall_double::double)]
use crate::crypto::crypto_facade::CryptoFacade;
use crate::crypto::key::GenericAesKey;
use crate::crypto::public_key_provider::PublicKeyIdentifier;
use crate::element_value::ParsedEntity;
use crate::entities::entity_facade::{EntityFacade, ID_FIELD};
use crate::entities::entity_facade::{EntityFacade, BUCKET_KEY_FIELD, ID_FIELD};
use crate::entities::generated::base::PersistenceResourcePostReturn;
use crate::entities::Entity;
#[cfg_attr(test, mockall_double::double)]
use crate::entity_client::EntityClient;
use crate::id::id_tuple::IdType;
use crate::instance_mapper::InstanceMapper;
use crate::metamodel::TypeModel;
use crate::tutanota_constants::{EncryptionAuthStatus, PublicKeyIdentifierType};
use crate::util::Versioned;
use crate::GeneratedId;
use crate::{ApiCallError, ListLoadDirection};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -162,6 +165,8 @@ impl CryptoEntityClient {
),
}
})?;
let bucket_key = parsed_entity.get(BUCKET_KEY_FIELD);

match possible_session_key {
Some(session_key) => {
let decrypted_entity =
Expand All @@ -186,6 +191,96 @@ impl CryptoEntityClient {
}
}

/// @return None if not a main instance, the EncryptionAuthStatus from the asymmetric decryption otherwise
// async fn authenticate_main_instance(
// &self,
// type_model: TypeModel,
// encryption_auth_status: Option<EncryptionAuthStatus>,
// pq_message_sender_key: Option<Versioned<&[u8]>>,
// entity: &ParsedEntity,
// resolved_session_key_for_instance: GenericAesKey,
// instance_session_key_with_owner_enc_session_key: InstanceSessionKey,
// decrypted_session_key: GenericAesKey,
// recipient_group: GeneratedId,
// ) -> Option<EncryptionAuthStatus> {
// // TODO the (Mail)TypeRef must be generated
// if !type_model.is_same_type_by_attr("tutanota", "Mail") {
// // we only authenticate mail instances currently
// return None;
// }
// match encryption_auth_status {
// Some(_) => encryption_auth_status,
// None => {
// match pq_message_sender_key {
// None => {
// // This message was encrypted with RSA. We check if TutaCrypt could have been used instead.
// let current_key_pair: Versioned<AsymmetricKeyPair> = self
// .key_loader_facade
// .load_current_key_pair(recipient_group)
// .await?;
// match current_key_pair.object {
// AsymmetricKeyPair::RSAKeyPair(_)
// | AsymmetricKeyPair::RSAEccKeyPair(_) => EncryptionAuthStatus::RSANoAuthentication,
// AsymmetricKeyPair::PQKeyPairs(_) => {
// // theoretically we could check that we did not rotate during this session.
// // However, we currently cannot rotate in the sdk. So it is pointless.
// EncryptionAuthStatus::RsaDespiteTutacrypt
// },
// }
// },
// Some(sender_key) => {
// // TODO do we need to check if this is a literal?
// let mail = self.entity_facade.decrypt_and_map(type_model, entity);
// fn decrypt_and_map(
// &self,
// type_model: &TypeModel,
// entity: ParsedEntity,
// resolved_session_key: ResolvedSessionKey,
// ) -> Result<ParsedEntity, ApiCallError>;
//
// //TODO sender addr
// let sender_mail_address = if is_confidential_mail {
// "SENDER_ADDR"
// } else {
// SYSTEM_GROUP_MAIL_ADDRESS
// };
// self.try_authenticate_sender_of_main_instance(
// sender_mail_address,
// sender_key.object,
// )
// },
// }
// },
// }
// }

async fn try_authenticate_sender_of_main_instance(
&self,
sender_mail_address: String,
pq_message_sender_key: Versioned<&[u8]>,
) -> EncryptionAuthStatus {
let result = self
.asymmetric_crypto_facade
.authenticate_sender(
PublicKeyIdentifier {
identifier: sender_mail_address,
identifier_type: PublicKeyIdentifierType::MailAddress,
},
pq_message_sender_key.object,
pq_message_sender_key.version,
)
.await;
match result {
Err(_e) => {
// TODO log the error
// we do not want to fail mail decryption here, e.g. in case an alias was removed we would get a permanent NotFoundError.
// in those cases we will just show a warning banner but still want to display the mail
EncryptionAuthStatus::TutacryptAuthenticationFailed
},
Ok(encryption_auth_status) => encryption_auth_status,
}
}

#[allow(dead_code)] // will be used but rustc can't see it in some configurations right now
pub async fn load_range<T: Entity + Deserialize<'static>>(
&self,
Expand Down
2 changes: 1 addition & 1 deletion tuta-sdk/rust/sdk/src/entities/entity_facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ impl EntityFacadeImpl {
}
}
}
pub fn decrypt_and_parse_value(
fn decrypt_and_parse_value(
value: ElementValue,
session_key: &GenericAesKey,
key: &str,
Expand Down
1 change: 0 additions & 1 deletion tuta-sdk/rust/sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ impl Sdk {
self.instance_mapper.clone(),
RandomizerFacade::from_core(rand_core::OsRng),
asymmetric_crypto_facade.clone(),
entity_facade.clone(),
));
let crypto_entity_client: Arc<CryptoEntityClient> = Arc::new(CryptoEntityClient::new(
entity_client.clone(),
Expand Down

0 comments on commit cd28527

Please sign in to comment.