Skip to content

Commit 1a118fc

Browse files
committed
WIP: TutaCrypt authentication in rust
1 parent b7a1ac2 commit 1a118fc

File tree

4 files changed

+111
-9
lines changed

4 files changed

+111
-9
lines changed

src/common/api/worker/crypto/CryptoFacade.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ export class CryptoFacade {
258258
public async resolveWithBucketKey(bucketKey: BucketKey, instance: Record<string, any>, typeModel: TypeModel): Promise<ResolvedSessionKeys> {
259259
const instanceElementId = this.getElementIdFromInstance(instance)
260260
let decryptedBucketKey: AesKey
261-
let unencryptedSenderAuthStatus: EncryptionAuthStatus | null = null
261+
let encryptionAuthStatus: EncryptionAuthStatus | null = null
262262
let pqMessageSenderKey: EccPublicKey | null = null
263263
if (bucketKey.keyGroup && bucketKey.pubEncBucketKey) {
264264
// bucket key is encrypted with public key for internal recipient
@@ -284,7 +284,7 @@ export class CryptoFacade {
284284
}
285285

286286
decryptedBucketKey = await this.resolveWithGroupReference(keyGroup, groupKeyVersion, bucketKey.groupEncBucketKey)
287-
unencryptedSenderAuthStatus = EncryptionAuthStatus.AES_NO_AUTHENTICATION
287+
encryptionAuthStatus = EncryptionAuthStatus.AES_NO_AUTHENTICATION
288288
} else {
289289
throw new SessionKeyNotFoundError(`encrypted bucket key not set on instance ${typeModel.name}`)
290290
}
@@ -294,7 +294,7 @@ export class CryptoFacade {
294294
instanceElementId,
295295
instance,
296296
typeModel,
297-
unencryptedSenderAuthStatus,
297+
encryptionAuthStatus,
298298
pqMessageSenderKey,
299299
)
300300

@@ -508,9 +508,9 @@ export class CryptoFacade {
508508
pqMessageSenderKey: Uint8Array | null,
509509
pqMessageSenderKeyVersion: KeyVersion | null,
510510
instance: Record<string, any>,
511-
resolvedSessionKeyForInstance: number[],
511+
resolvedSessionKeyForInstance: AesKey,
512512
instanceSessionKeyWithOwnerEncSessionKey: InstanceSessionKey,
513-
decryptedSessionKey: number[],
513+
decryptedSessionKey: AesKey,
514514
keyGroup: Id | null,
515515
) {
516516
// we only authenticate mail instances

tuta-sdk/rust/sdk/src/crypto/crypto_facade.rs

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::crypto::aes::Iv;
33
use crate::crypto::asymmetric_crypto_facade::AsymmetricCryptoFacade;
44
use crate::crypto::asymmetric_crypto_facade::{AsymmetricCryptoError, DecapsulatedAesKey};
55
use crate::crypto::key::{GenericAesKey, KeyLoadError};
6+
use crate::crypto::public_key_provider::PublicKeyIdentifier;
67
use crate::crypto::randomizer_facade::RandomizerFacade;
78
use crate::crypto::rsa::RSAEncryptionError;
89
use crate::crypto::tuta_crypt::PQError;
@@ -12,12 +13,14 @@ use crate::entities::entity_facade::{
1213
BUCKET_KEY_FIELD, ID_FIELD, OWNER_ENC_SESSION_KEY_FIELD, OWNER_GROUP_FIELD,
1314
OWNER_KEY_VERSION_FIELD,
1415
};
15-
use crate::entities::generated::sys::BucketKey;
16+
use crate::entities::generated::sys::{BucketKey, InstanceSessionKey};
1617
use crate::instance_mapper::InstanceMapper;
1718
#[cfg_attr(test, mockall_double::double)]
1819
use crate::key_loader_facade::KeyLoaderFacade;
1920
use crate::metamodel::TypeModel;
20-
use crate::tutanota_constants::{CryptoProtocolVersion, EncryptionAuthStatus};
21+
use crate::tutanota_constants::{
22+
CryptoProtocolVersion, EncryptionAuthStatus, PublicKeyIdentifierType,
23+
};
2124
use crate::util::{convert_version_to_u64, ArrayCastingError};
2225
use crate::GeneratedId;
2326
use crate::IdTupleGenerated;
@@ -145,7 +148,7 @@ impl CryptoFacade {
145148
});
146149
};
147150

148-
let mut _auth_status: Option<EncryptionAuthStatus> = None; // TODO: implement
151+
let mut encryption_auth_status: Option<EncryptionAuthStatus> = None; // TODO: implement
149152
let DecapsulatedAesKey {
150153
decrypted_aes_key: decrypted_bucket_key,
151154
sender_identity_pub_key: _sender_identity_key,
@@ -163,7 +166,7 @@ impl CryptoFacade {
163166
} else if let Some(_group_enc_bucket_key) = &bucket_key.groupEncBucketKey {
164167
// TODO: to be used with secure external
165168
let _key_group = bucket_key.keyGroup.as_ref().unwrap_or(owner_group);
166-
_auth_status = Some(EncryptionAuthStatus::AESNoAuthentication);
169+
encryption_auth_status = Some(EncryptionAuthStatus::AESNoAuthentication);
167170
todo!("secure external resolveWithGroupReference")
168171
} else {
169172
return Err(SessionKeyResolutionError {
@@ -174,6 +177,21 @@ impl CryptoFacade {
174177
});
175178
};
176179

180+
// we can only authenticate once we have the session key
181+
// because we need to check if the confidential flag is set, which is encrypted still
182+
// we need to do it here at the latest because we must write the flag when updating the session key on the instance
183+
// self.authenticateMainInstance(
184+
// typeModel,
185+
// encryptionAuthStatus,
186+
// pqMessageSenderKey,
187+
// bucketKey.protocolVersion == CryptoProtocolVersion.TUTA_CRYPT ? parseKeyVersion(bucketKey.senderKeyVersion ?? "0") : null,
188+
// instance,
189+
// resolvedSessionKeyForInstance,
190+
// instanceSessionKeyWithOwnerEncSessionKey,
191+
// decryptedSessionKey,
192+
// bucketKey.keyGroup,
193+
// ).await?;
194+
177195
let mut session_key_for_this_instance = None;
178196

179197
for instance_session_key in bucket_key.bucketEncSessionKeys {
@@ -208,6 +226,83 @@ impl CryptoFacade {
208226
owner_key_version: versioned_owner_group_key.version,
209227
})
210228
}
229+
230+
async fn authenticate_main_instance(
231+
type_model: TypeModel,
232+
encryption_auth_status: Option<EncryptionAuthStatus>,
233+
pq_message_sender_key: Option<&[u8]>,
234+
pq_message_sender_key_version: Option<u64>,
235+
entity: &ParsedEntity,
236+
resolved_session_key_for_instance: GenericAesKey,
237+
instance_session_key_with_owner_enc_session_key: InstanceSessionKey,
238+
decrypted_session_key: GenericAesKey,
239+
key_group: Option<GeneratedId>,
240+
) -> () {
241+
// we only authenticate mail instances
242+
243+
// TODO type_model.is_same_type_by_attr()
244+
// const isMailInstance = isSameTypeRefByAttr(MailTypeRef, typeModel.app, typeModel.name)
245+
// if (isMailInstance) {
246+
// if (!encryptionAuthStatus) {
247+
// if (!pqMessageSenderKey) {
248+
// // This message was encrypted with RSA. We check if TutaCrypt could have been used instead.
249+
// const recipientGroup = assertNotNull(
250+
// keyGroup,
251+
// "trying to authenticate an asymmetrically encrypted message, but we can't determine the recipient's group ID",
252+
// )
253+
// const currentKeyPair = await this.keyLoaderFacade.loadCurrentKeyPair(recipientGroup)
254+
// encryptionAuthStatus = EncryptionAuthStatus.RSA_NO_AUTHENTICATION
255+
// if (isPqKeyPairs(currentKeyPair.object)) {
256+
// const keyRotationFacade = this.keyRotationFacade()
257+
// const rotatedGroups = await keyRotationFacade.getGroupIdsThatPerformedKeyRotations()
258+
// if (!rotatedGroups.includes(recipientGroup)) {
259+
// encryptionAuthStatus = EncryptionAuthStatus::RsaDespiteTutacrypt
260+
// }
261+
// }
262+
// } else {
263+
// const mail = this.isLiteralInstance(instance)
264+
// ? ((await this.instanceMapper.decryptAndMapToInstance(typeModel, instance, resolvedSessionKeyForInstance)) as Mail)
265+
// : (instance as Mail)
266+
// const senderMailAddress = mail.confidential ? mail.sender.address : SYSTEM_GROUP_MAIL_ADDRESS
267+
// encryptionAuthStatus = await this.tryAuthenticateSenderOfMainInstance(
268+
// senderMailAddress,
269+
// pqMessageSenderKey,
270+
// // must not be null if this is a TutaCrypt message with a pqMessageSenderKey
271+
// assertNotNull(pqMessageSenderKeyVersion),
272+
// )
273+
// }
274+
// }
275+
// instanceSessionKeyWithOwnerEncSessionKey.encryptionAuthStatus = aesEncrypt(decryptedSessionKey, stringToUtf8Uint8Array(encryptionAuthStatus))
276+
// }
277+
}
278+
279+
async fn try_authenticate_sender_of_main_instance(
280+
&self,
281+
sender_mail_address: String,
282+
pq_message_sender_key: &[u8],
283+
pq_message_sender_key_version: u64,
284+
) -> EncryptionAuthStatus {
285+
let result = self
286+
.asymmetric_crypto_facade
287+
.authenticate_sender(
288+
PublicKeyIdentifier {
289+
identifier: sender_mail_address,
290+
identifier_type: PublicKeyIdentifierType::MailAddress,
291+
},
292+
pq_message_sender_key,
293+
pq_message_sender_key_version,
294+
)
295+
.await;
296+
match result {
297+
Err(_e) => {
298+
// TODO log the error
299+
// we do not want to fail mail decryption here, e.g. in case an alias was removed we would get a permanent NotFoundError.
300+
// in those cases we will just show a warning banner but still want to display the mail
301+
EncryptionAuthStatus::TutacryptAuthenticationFailed
302+
},
303+
Ok(encryption_auth_status) => encryption_auth_status,
304+
}
305+
}
211306
}
212307

213308
/// Resolves the id field of an entity into a generated id

tuta-sdk/rust/sdk/src/metamodel.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,8 @@ impl TypeModel {
156156
self.encrypted
157157
}
158158
}
159+
160+
pub fn is_same_type_by_attr(&self, app: &str, name: &str) -> bool {
161+
self.app == app && self.name == name
162+
}
159163
}

tuta-sdk/rust/sdk/src/tutanota_constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ pub enum EncryptionAuthStatus {
7272

7373
/// The entity was sent by the user and doesn't need authenticated.
7474
TutacryptSender = 4,
75+
76+
/// the entity was encrypted with RSA although TutaCrypt keys were available.
77+
RsaDespiteTutacrypt = 5,
7578
}
7679

7780
/// Used for identifying the protocol version used for encrypting a session key.

0 commit comments

Comments
 (0)