import o from "@tutao/otest" import { arrayEquals, assertNotNull, hexToUint8Array, KeyVersion, neverNull, utf8Uint8ArrayToString, Versioned } from "@tutao/tutanota-utils" import { CryptoFacade } from "../../../../../src/common/api/worker/crypto/CryptoFacade.js" import { asCryptoProtoocolVersion, BucketPermissionType, CryptoProtocolVersion, EncryptionAuthStatus, EncryptionKeyVerificationState, GroupType, PermissionType, PresentableKeyVerificationState, ProcessingState, PublicKeyIdentifierType, } from "../../../../../src/common/api/common/TutanotaConstants.js" import { createMail, createMailAddress, FileTypeRef, InternalRecipientKeyData, Mail, MailAddressTypeRef, MailDetailsBlobTypeRef, MailTypeRef, } from "../../../../../src/common/api/entities/tutanota/TypeRefs.js" import { BucketKey, BucketKeyTypeRef, BucketPermissionTypeRef, BucketTypeRef, createBucket, createBucketKey, createBucketPermission, createGroup, createInstanceSessionKey, createKeyPair, createPermission, createTypeInfo, CustomerAccountTerminationRequestTypeRef, Group, GroupKeysRefTypeRef, GroupMembershipTypeRef, GroupTypeRef, InstanceSessionKey, InstanceSessionKeyTypeRef, KeyPair, KeyPairTypeRef, PermissionTypeRef, TypeInfoTypeRef, UpdatePermissionKeyData, User, UserTypeRef, } from "../../../../../src/common/api/entities/sys/TypeRefs.js" import { spy } from "@tutao/tutanota-test-utils" import { RestClient } from "../../../../../src/common/api/worker/rest/RestClient.js" import { EntityClient } from "../../../../../src/common/api/common/EntityClient.js" import { Aes256Key, aes256RandomKey, aesDecrypt, aesEncrypt, AesKey, bitArrayToUint8Array, decryptKey, encryptKey, encryptRsaKey, generateX25519KeyPair, KeyPairType, kyberPrivateKeyToBytes, kyberPublicKeyToBytes, pqKeyPairsToPublicKeys, PQPublicKeys, RsaPublicKey, rsaPublicKeyToHex, X25519KeyPair, X25519PublicKey, } from "@tutao/tutanota-crypto" import { ServerModelUntypedInstance, TypeModel, UntypedInstance } from "../../../../../src/common/api/common/EntityTypes.js" import { IServiceExecutor } from "../../../../../src/common/api/common/ServiceRequest.js" import { matchers, object, verify, when } from "testdouble" import { UpdatePermissionKeyService } from "../../../../../src/common/api/entities/sys/Services.js" import { elementIdPart, getListId, isSameId, listIdPart } from "../../../../../src/common/api/common/utils/EntityUtils.js" import { HttpMethod, TypeModelResolver } from "../../../../../src/common/api/common/EntityFunctions.js" import { UserFacade } from "../../../../../src/common/api/worker/facades/UserFacade.js" import { SessionKeyNotFoundError } from "../../../../../src/common/api/common/error/SessionKeyNotFoundError.js" import { OwnerEncSessionKeysUpdateQueue } from "../../../../../src/common/api/worker/crypto/OwnerEncSessionKeysUpdateQueue.js" import { WASMKyberFacade } from "../../../../../src/common/api/worker/facades/KyberFacade.js" import { PQFacade } from "../../../../../src/common/api/worker/facades/PQFacade.js" import { encodePQMessage, PQBucketKeyEncapsulation } from "../../../../../src/common/api/worker/facades/PQMessage.js" import { clientInitializedTypeModelResolver, createTestEntity, instancePipelineFromTypeModelResolver } from "../../../TestUtils.js" import { RSA_TEST_KEYPAIR } from "../facades/RsaPqPerformanceTest.js" import { DefaultEntityRestCache } from "../../../../../src/common/api/worker/rest/DefaultEntityRestCache.js" import { loadLibOQSWASM } from "../WASMTestUtils.js" import { AsymmetricCryptoFacade } from "../../../../../src/common/api/worker/crypto/AsymmetricCryptoFacade.js" import { VerifiedPublicEncryptionKey } from "../../../../../src/common/api/worker/facades/lazy/KeyVerificationFacade" import { KeyLoaderFacade, parseKeyVersion } from "../../../../../src/common/api/worker/facades/KeyLoaderFacade.js" import { PublicEncryptionKeyProvider } from "../../../../../src/common/api/worker/facades/PublicEncryptionKeyProvider.js" import { KeyRotationFacade } from "../../../../../src/common/api/worker/facades/KeyRotationFacade.js" import { NotFoundError } from "../../../../../src/common/api/common/error/RestError" import { AttributeModel } from "../../../../../src/common/api/common/AttributeModel" import { EntityAdapter } from "../../../../../src/common/api/worker/crypto/EntityAdapter" import { KeyVerificationMismatchError } from "../../../../../src/common/api/common/error/KeyVerificationMismatchError" const { captor, anything, argThat } = matchers const kyberFacade = new WASMKyberFacade(await loadLibOQSWASM()) const pqFacade: PQFacade = new PQFacade(kyberFacade) let publicEncryptionKeyProvider: PublicEncryptionKeyProvider /** * Helper to have all the mocked items available in the test case. */ type TestUser = { user: User name: string userGroup: Group mailGroup: Group userGroupKey: AesKey mailGroupKey: AesKey } const senderAddress = "hello@tutao.de" o.spec("CryptoFacadeTest", function () { let restClient: RestClient let instancePipeline let serviceExecutor: IServiceExecutor let entityClient: EntityClient let ownerEncSessionKeysUpdateQueue: OwnerEncSessionKeysUpdateQueue let crypto: CryptoFacade let userFacade: UserFacade let keyLoaderFacade: KeyLoaderFacade let cache: DefaultEntityRestCache let asymmetricCryptoFacade: AsymmetricCryptoFacade let keyRotationFacade: KeyRotationFacade let typeModelResolver: TypeModelResolver async function prepareBucketKeyInstance( bucketEncMailSessionKey: Uint8Array, fileSessionKeys: Array, bk: AesKey, pubEncBucketKey: Uint8Array, recipientUser: TestUser, mail: Mail, senderPubEccKey: Versioned | undefined, recipientKeyVersion: NumberString, protocolVersion: CryptoProtocolVersion, asymmetricCryptoFacade: AsymmetricCryptoFacade, ) { const MailTypeModel = await typeModelResolver.resolveClientTypeReference(MailTypeRef) const mailInstanceSessionKey = createTestEntity(InstanceSessionKeyTypeRef, { typeInfo: createTestEntity(TypeInfoTypeRef, { application: MailTypeModel.app, typeId: String(MailTypeModel.id), }), symEncSessionKey: bucketEncMailSessionKey, instanceList: "mailListId", instanceId: "mailId", }) const FileTypeModel = await typeModelResolver.resolveClientTypeReference(FileTypeRef) const bucketEncSessionKeys = fileSessionKeys.map((fileSessionKey, index) => { return createTestEntity(InstanceSessionKeyTypeRef, { typeInfo: createTestEntity(TypeInfoTypeRef, { application: FileTypeModel.app, typeId: String(FileTypeModel.id), }), symEncSessionKey: encryptKey(bk, fileSessionKey), instanceList: "fileListId", instanceId: "fileId" + (index + 1), }) }) bucketEncSessionKeys.push(mailInstanceSessionKey) const bucketKey = createTestEntity(BucketKeyTypeRef, { pubEncBucketKey, keyGroup: recipientUser.userGroup._id, bucketEncSessionKeys: bucketEncSessionKeys, recipientKeyVersion, senderKeyVersion: senderPubEccKey != null ? senderPubEccKey.version.toString() : "0", protocolVersion, }) when( asymmetricCryptoFacade.loadKeyPairAndDecryptSymKey( assertNotNull(bucketKey.keyGroup), parseKeyVersion(bucketKey.recipientKeyVersion), asCryptoProtoocolVersion(bucketKey.protocolVersion), pubEncBucketKey, anything(), ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: senderPubEccKey?.object ?? null }) mail.bucketKey = bucketKey } o.before(function () { restClient = object() when(restClient.request(anything(), anything(), anything())).thenResolve(undefined) userFacade = object() cache = object() }) o.beforeEach(function () { serviceExecutor = object() entityClient = object() asymmetricCryptoFacade = object() ownerEncSessionKeysUpdateQueue = object() publicEncryptionKeyProvider = object() keyLoaderFacade = object() keyRotationFacade = object() typeModelResolver = clientInitializedTypeModelResolver() instancePipeline = instancePipelineFromTypeModelResolver(typeModelResolver) crypto = new CryptoFacade( userFacade, entityClient, restClient, serviceExecutor, instancePipeline, ownerEncSessionKeysUpdateQueue, cache, keyLoaderFacade, asymmetricCryptoFacade, publicEncryptionKeyProvider, () => keyRotationFacade, typeModelResolver, ) }) o("resolve session key: unencrypted instance", async function () { const customerAccountTerminationRequest = createTestEntity(CustomerAccountTerminationRequestTypeRef) o(await crypto.resolveSessionKey(customerAccountTerminationRequest)).equals(null) }) o("resolve session key: _ownerEncSessionKey instance.", async function () { const recipientUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientUser, userFacade, keyLoaderFacade) const sk = aes256RandomKey() const mail = createTestEntity(MailTypeRef, { _ownerEncSessionKey: recipientUser.mailGroupKey ? encryptKey(recipientUser.mailGroupKey, sk) : null, _ownerGroup: recipientUser.mailGroup._id, _ownerKeyVersion: recipientUser.mailGroup.groupKeyVersion, }) const sessionKey: AesKey = neverNull(await crypto.resolveSessionKey(mail)) o(sessionKey).deepEquals(sk) }) o("resolve session key: _ownerEncSessionKey instance, fetches correct version.", async function () { const recipientUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientUser, userFacade, keyLoaderFacade) const sk = aes256RandomKey() const groupKey_v1 = aes256RandomKey() when(keyLoaderFacade.loadSymGroupKey(recipientUser.mailGroup._id, 1)).thenResolve(groupKey_v1) const mail = createTestEntity(MailTypeRef, { _ownerGroup: recipientUser.mailGroup._id, _ownerEncSessionKey: encryptKey(groupKey_v1, sk), _ownerKeyVersion: "1", }) const sessionKey: AesKey = neverNull(await crypto.resolveSessionKey(mail)) o(sessionKey).deepEquals(sk) }) const protocolVersion = CryptoProtocolVersion.TUTA_CRYPT o("resolve session key: rsa public key decryption of session key.", async function () { o.timeout(500) // in CI or with debugging it can take a while const recipientUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientUser, userFacade, keyLoaderFacade) let confidential = true let sk = aes256RandomKey() let bk = aes256RandomKey() let privateKey = RSA_TEST_KEYPAIR.privateKey let publicKey = RSA_TEST_KEYPAIR.publicKey const keyPair = createTestEntity(KeyPairTypeRef, { _id: "keyPairId", symEncPrivRsaKey: encryptRsaKey(recipientUser.userGroupKey, privateKey), pubRsaKey: hexToUint8Array(rsaPublicKeyToHex(RSA_TEST_KEYPAIR.publicKey)), }) recipientUser.userGroup.currentKeys = keyPair const mail = createTestEntity(MailTypeRef, { confidential, _ownerGroup: recipientUser.mailGroup._id, _permissions: "permissionListId", }) const bucket = createTestEntity(BucketTypeRef, { bucketPermissions: "bucketPermissionListId", }) const permission = createTestEntity(PermissionTypeRef, { _id: ["permissionListId", "permissionId"], _ownerGroup: recipientUser.userGroup._id, bucketEncSessionKey: encryptKey(bk, sk), bucket, type: PermissionType.Public, }) const pubEncBucketKey = object() const bucketPermission = createTestEntity(BucketPermissionTypeRef, { _id: ["bucketPermissionListId", "bucketPermissionId"], _ownerGroup: recipientUser.userGroup._id, type: BucketPermissionType.Public, group: recipientUser.userGroup._id, pubEncBucketKey, protocolVersion: protocolVersion, pubKeyVersion: "0", }) when( asymmetricCryptoFacade.loadKeyPairAndDecryptSymKey( assertNotNull(bucketPermission.group), parseKeyVersion(bucketPermission.pubKeyVersion!), protocolVersion, pubEncBucketKey, anything(), ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: null }) when(entityClient.loadAll(BucketPermissionTypeRef, getListId(bucketPermission))).thenResolve([bucketPermission]) when(entityClient.loadAll(PermissionTypeRef, getListId(permission))).thenResolve([permission]) when( serviceExecutor.post( UpdatePermissionKeyService, argThat((p: UpdatePermissionKeyData) => { return isSameId(p.permission, permission._id) && isSameId(p.bucketPermission, bucketPermission._id) }), ), ).thenResolve(undefined) const sessionKey = neverNull(await crypto.resolveSessionKey(mail)) o(sessionKey).deepEquals(sk) }) o("resolve session key: pq public key decryption of session key.", async function () { o.timeout(500) // in CI or with debugging it can take a while const recipientTestUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientTestUser, userFacade, keyLoaderFacade) let pqKeyPairs = await pqFacade.generateKeyPairs() const senderIdentityKeyPair = generateX25519KeyPair() // configure test mail let sk = aes256RandomKey() let bk = aes256RandomKey() const mail = createTestEntity(MailTypeRef, { _permissions: "permissionListId", _ownerGroup: recipientTestUser.mailGroup._id, confidential: true, }) const bucket = createBucket({ bucketPermissions: "bucketPermissionListId", }) const permission = createPermission({ _format: "", listElementApplication: null, listElementTypeId: null, ops: null, symEncSessionKey: null, symKeyVersion: null, _id: ["permissionListId", "permissionId"], _ownerGroup: recipientTestUser.mailGroup._id, bucketEncSessionKey: encryptKey(bk, sk), bucket, type: PermissionType.Public, _ownerEncSessionKey: null, _ownerKeyVersion: null, _permissions: "p_id", group: null, }) const pubEncBucketKey = await pqFacade.encapsulateAndEncode( senderIdentityKeyPair, generateX25519KeyPair(), pqKeyPairsToPublicKeys(pqKeyPairs), bitArrayToUint8Array(bk), ) const protocolVersion = CryptoProtocolVersion.RSA const bucketPermission = createBucketPermission({ _id: ["bucketPermissionListId", "bucketPermissionId"], _format: "", _permissions: "", _ownerGroup: recipientTestUser.mailGroup._id, type: BucketPermissionType.Public, group: recipientTestUser.userGroup._id, pubEncBucketKey, senderKeyVersion: "0", ownerEncBucketKey: null, ownerKeyVersion: null, protocolVersion, pubKeyVersion: "0", symEncBucketKey: null, symKeyVersion: null, }) when( asymmetricCryptoFacade.loadKeyPairAndDecryptSymKey( assertNotNull(bucketPermission.group), parseKeyVersion(bucketPermission.pubKeyVersion!), protocolVersion, pubEncBucketKey, anything(), ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: senderIdentityKeyPair.publicKey }) when(userFacade.createAuthHeaders()).thenReturn({}) when(restClient.request(anything(), HttpMethod.PATCH, anything())).thenResolve(undefined) when(entityClient.loadAll(BucketPermissionTypeRef, getListId(bucketPermission))).thenResolve([bucketPermission]) when(entityClient.loadAll(PermissionTypeRef, getListId(permission))).thenResolve([permission]) const sessionKey = neverNull(await crypto.resolveSessionKey(mail)) o(sessionKey).deepEquals(sk) }) o("resolve session key: pq public key decryption of session key, fetches correct recipient key version", async function () { o.timeout(500) // in CI or with debugging it can take a while const recipientTestUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientTestUser, userFacade, keyLoaderFacade) const pqKeyPairs_v1 = await pqFacade.generateKeyPairs() const senderIdentityKeyPair = generateX25519KeyPair() // configure test mail const sk = aes256RandomKey() const bk = aes256RandomKey() const mail = createTestEntity(MailTypeRef, { _ownerGroup: recipientTestUser.mailGroup._id, confidential: true, _permissions: "permissionListId", }) const bucket = createBucket({ bucketPermissions: "bucketPermissionListId", }) const permission = createPermission({ _format: "", listElementApplication: null, listElementTypeId: null, ops: null, symEncSessionKey: null, symKeyVersion: null, _id: ["permissionListId", "permissionId"], _ownerGroup: recipientTestUser.mailGroup._id, bucketEncSessionKey: encryptKey(bk, sk), bucket, type: PermissionType.Public, _ownerEncSessionKey: null, _ownerKeyVersion: null, _permissions: "p_id", group: null, }) const pubEncBucketKey = await pqFacade.encapsulateAndEncode( senderIdentityKeyPair, generateX25519KeyPair(), pqKeyPairsToPublicKeys(pqKeyPairs_v1), bitArrayToUint8Array(bk), ) const protocolVersion = CryptoProtocolVersion.RSA const bucketPermission = createBucketPermission({ _id: ["bucketPermissionListId", "bucketPermissionId"], _format: "", _permissions: "", _ownerGroup: recipientTestUser.mailGroup._id, type: BucketPermissionType.Public, group: recipientTestUser.userGroup._id, pubEncBucketKey, senderKeyVersion: "0", ownerEncBucketKey: null, ownerKeyVersion: null, protocolVersion: "0", pubKeyVersion: "1", symEncBucketKey: null, symKeyVersion: null, }) when( asymmetricCryptoFacade.loadKeyPairAndDecryptSymKey( assertNotNull(bucketPermission.group), parseKeyVersion(bucketPermission.pubKeyVersion!), protocolVersion, pubEncBucketKey, anything(), ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: senderIdentityKeyPair.publicKey }) when(userFacade.createAuthHeaders()).thenReturn({}) when(restClient.request(anything(), HttpMethod.PATCH, anything())).thenResolve(undefined) when(entityClient.loadAll(BucketPermissionTypeRef, getListId(bucketPermission))).thenResolve([bucketPermission]) when(entityClient.loadAll(PermissionTypeRef, getListId(permission))).thenResolve([permission]) const sessionKey = neverNull(await crypto.resolveSessionKey(mail)) o(sessionKey).deepEquals(sk) }) o("resolve session key: pq public key decryption of session key using bucketKey", async function () { o.timeout(500) // in CI or with debugging it can take a while let confidential = true const recipientTestUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientTestUser, userFacade, keyLoaderFacade) const pqKeyPairs_v1 = await pqFacade.generateKeyPairs() const senderIdentityKeyPair = generateX25519KeyPair() // configure test mail const sk = aes256RandomKey() const bk = aes256RandomKey() let mail = createTestEntity(MailTypeRef, { _id: ["mailListId", "mailId"], _ownerGroup: recipientTestUser.mailGroup._id, confidential, mailDetails: ["mailDetailsArchiveId", "mailDetailsId"], sender: createTestEntity(MailAddressTypeRef, { address: senderAddress, name: "sender name", }), }) const bucketEncMailSessionKey = encryptKey(bk, sk) const pubEncBucketKey = await pqFacade.encapsulateAndEncode( senderIdentityKeyPair, generateX25519KeyPair(), pqKeyPairsToPublicKeys(pqKeyPairs_v1), bitArrayToUint8Array(bk), ) const senderKeyVersion = 1 await prepareBucketKeyInstance( bucketEncMailSessionKey, [], bk, pubEncBucketKey, recipientTestUser, mail, { object: senderIdentityKeyPair.publicKey, version: senderKeyVersion, }, "1", protocolVersion, asymmetricCryptoFacade, ) when( asymmetricCryptoFacade.decryptSymKeyWithKeyPair( { keyPairType: pqKeyPairs_v1.keyPairType, x25519KeyPair: pqKeyPairs_v1.x25519KeyPair, kyberKeyPair: pqKeyPairs_v1.kyberKeyPair, }, protocolVersion, pubEncBucketKey, ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: senderIdentityKeyPair.publicKey }) when(userFacade.createAuthHeaders()).thenReturn({}) when(restClient.request(anything(), HttpMethod.PATCH, anything())).thenResolve(undefined) when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, senderIdentityKeyPair.publicKey, senderKeyVersion, ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) const sessionKey = neverNull(await crypto.resolveSessionKey(mail)) o(sessionKey).deepEquals(sk) }) o("enforceSessionKeyUpdateIfNeeded: _ownerEncSessionKey already defined", async function () { const files = [createTestEntity(FileTypeRef, { _ownerEncSessionKey: new Uint8Array() })] const mail = createTestEntity(MailTypeRef, { bucketKey: null }) await crypto.enforceSessionKeyUpdateIfNeeded(mail, files) verify(ownerEncSessionKeysUpdateQueue.postUpdateSessionKeysService(anything()), { times: 0 }) verify(cache.deleteFromCacheIfExists(anything(), anything(), anything()), { times: 0 }) }) o("enforceSessionKeyUpdateIfNeeded: _ownerEncSessionKey missing", async function () { o.timeout(500) // in CI or with debugging it can take a while const files = [ createTestEntity(FileTypeRef, { _id: ["listId", "1"], _ownerEncSessionKey: new Uint8Array() }), createTestEntity(FileTypeRef, { _id: ["listId", "2"], _ownerEncSessionKey: null }), ] const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest() const bucketKey = assertNotNull(testData.mail.bucketKey) when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, testData.senderIdentityKeyPair.publicKey, anything(), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) await crypto.enforceSessionKeyUpdateIfNeeded(testData.mail, files) verify(ownerEncSessionKeysUpdateQueue.postUpdateSessionKeysService(anything()), { times: 1 }) verify(cache.deleteFromCacheIfExists(FileTypeRef, "listId", "2")) }) o("encryptBucketKeyForInternalRecipient with existing PQKeys for sender and recipient", async function () { let recipientMailAddress = "bob@tutanota.com" let senderGroupKey = aes256RandomKey() let bk = aes256RandomKey() const recipientKeyPairs = await pqFacade.generateKeyPairs() const recipientKeyPair = createKeyPair({ _id: "recipientKeyPairId", pubEccKey: recipientKeyPairs.x25519KeyPair.publicKey, symEncPrivEccKey: null, pubKyberKey: kyberPublicKeyToBytes(recipientKeyPairs.kyberKeyPair.publicKey), symEncPrivKyberKey: null, pubRsaKey: null, symEncPrivRsaKey: null, signature: null, }) const senderKeyPairs = await pqFacade.generateKeyPairs() const senderKeyPair = createKeyPair({ _id: "senderKeyPairId", pubRsaKey: null, symEncPrivRsaKey: null, pubEccKey: senderKeyPairs.x25519KeyPair.publicKey, symEncPrivEccKey: aesEncrypt(senderGroupKey, senderKeyPairs.x25519KeyPair.privateKey), pubKyberKey: kyberPublicKeyToBytes(senderKeyPairs.kyberKeyPair.publicKey), symEncPrivKyberKey: aesEncrypt(senderGroupKey, kyberPrivateKeyToBytes(senderKeyPairs.kyberKeyPair.privateKey)), signature: null, }) const senderUserGroup = createGroup({ _format: "", _ownerGroup: "", _permissions: "", admin: "admin1", adminGroupEncGKey: null, adminGroupKeyVersion: null, archives: [], customer: "customer1", enabled: false, external: false, groupInfo: ["", ""], invitations: "", members: "", storageCounter: "counter1", type: "", user: "user1", _id: "userGroupId", currentKeys: senderKeyPair, groupKeyVersion: "0", formerGroupKeys: createTestEntity(GroupKeysRefTypeRef), pubAdminGroupEncGKey: null, identityKeyPair: null, }) const notFoundRecipients = [] const keyVerificationMismatchRecipients = [] const pqEncapsulation: PQBucketKeyEncapsulation = { kyberCipherText: new Uint8Array([1]), kekEncBucketKey: new Uint8Array([2]), } const encodedPqMessage: Uint8Array = encodePQMessage({ senderIdentityPubKey: senderKeyPair.pubEccKey!, ephemeralPubKey: senderKeyPair.pubEccKey!, encapsulation: pqEncapsulation, }) const recipientPublicKeys: Versioned = { version: 0, object: { keyPairType: KeyPairType.TUTA_CRYPT, x25519PublicKey: recipientKeyPair.pubEccKey!, kyberPublicKey: { raw: recipientKeyPair.pubKyberKey!, }, }, } const loadedPublicKey: VerifiedPublicEncryptionKey = { publicEncryptionKey: recipientPublicKeys, verificationState: EncryptionKeyVerificationState.NO_ENTRY, } when(publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey(anything())).thenResolve(loadedPublicKey) when(asymmetricCryptoFacade.asymEncryptSymKey(bk, recipientPublicKeys, senderUserGroup._id)).thenResolve({ recipientKeyVersion: recipientPublicKeys.version, senderKeyVersion: parseKeyVersion(senderUserGroup.groupKeyVersion), pubEncSymKeyBytes: encodedPqMessage, cryptoProtocolVersion: CryptoProtocolVersion.TUTA_CRYPT, }) const internalRecipientKeyData = (await crypto.encryptBucketKeyForInternalRecipient( senderUserGroup._id, bk, recipientMailAddress, notFoundRecipients, keyVerificationMismatchRecipients, )) as InternalRecipientKeyData o(internalRecipientKeyData!.recipientKeyVersion).equals("0") o(internalRecipientKeyData.protocolVersion).equals(CryptoProtocolVersion.TUTA_CRYPT) o(internalRecipientKeyData!.mailAddress).equals(recipientMailAddress) o(internalRecipientKeyData!.pubEncBucketKey).deepEquals(encodedPqMessage) verify( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: recipientMailAddress, }), { times: 1 }, ) }) o("encryptBucketKeyForInternalRecipient with existing PQKeys for sender", async () => { let recipientMailAddress = "bob@tutanota.com" let bk = aes256RandomKey() let senderMailAddress = "alice@tutanota.com" const senderKeyPair: KeyPair = object() const senderUserGroup = createGroup({ _id: "userGroupId", currentKeys: senderKeyPair, groupKeyVersion: "0", _permissions: "", admin: null, adminGroupEncGKey: null, adminGroupKeyVersion: null, archives: [], customer: null, enabled: false, external: false, groupInfo: ["", ""], invitations: "", members: "", storageCounter: null, type: "", user: null, formerGroupKeys: createTestEntity(GroupKeysRefTypeRef), pubAdminGroupEncGKey: null, identityKeyPair: null, }) const notFoundRecipients = [] const keyVerificationMismatchRecipients = [] const recipientPublicKeys: Versioned = { version: 0, object: object(), } const loadedRecipientPublicKey: VerifiedPublicEncryptionKey = { publicEncryptionKey: recipientPublicKeys, verificationState: EncryptionKeyVerificationState.NO_ENTRY, } when(publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey(anything())).thenResolve(loadedRecipientPublicKey) const senderPublicKeys: Versioned = { version: 0, object: object(), } const loadedSenderPublicKey: VerifiedPublicEncryptionKey = { publicEncryptionKey: senderPublicKeys, verificationState: EncryptionKeyVerificationState.NO_ENTRY, } when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: senderMailAddress, }), ).thenResolve(loadedSenderPublicKey) const pubEncBucketKey = object() when(asymmetricCryptoFacade.asymEncryptSymKey(bk, recipientPublicKeys, senderUserGroup._id)).thenResolve({ recipientKeyVersion: recipientPublicKeys.version, senderKeyVersion: parseKeyVersion(senderUserGroup.groupKeyVersion), pubEncSymKeyBytes: pubEncBucketKey, cryptoProtocolVersion: CryptoProtocolVersion.RSA, }) const internalRecipientKeyData = (await crypto.encryptBucketKeyForInternalRecipient( senderUserGroup._id, bk, recipientMailAddress, notFoundRecipients, keyVerificationMismatchRecipients, )) as InternalRecipientKeyData o(internalRecipientKeyData!.recipientKeyVersion).equals("0") o(internalRecipientKeyData!.mailAddress).equals(recipientMailAddress) o(internalRecipientKeyData.protocolVersion).equals(CryptoProtocolVersion.RSA) o(internalRecipientKeyData.pubEncBucketKey).deepEquals(pubEncBucketKey) verify( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: recipientMailAddress, }), { times: 1 }, ) }) o("encryptBucketKeyForInternalRecipient for non-existing recipients", async function () { let notFoundRecipientMailAddress = "notfound@tutanota.com" let bk = aes256RandomKey() const notFoundRecipients: string[] = [] const keyVerificationMismatchRecipients: string[] = [] when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: notFoundRecipientMailAddress, }), ).thenReject(new NotFoundError("")) await crypto.encryptBucketKeyForInternalRecipient( "senderGroupId", bk, notFoundRecipientMailAddress, notFoundRecipients, keyVerificationMismatchRecipients, ) o(notFoundRecipients).deepEquals(["notfound@tutanota.com"]) o(keyVerificationMismatchRecipients).deepEquals([]) verify(userFacade.getUser(), { times: 0 }) }) o("encryptBucketKeyForInternalRecipient with non-existing recipients", async function () { let notFoundRecipient1MailAddress = "notfound1@tutanota.com" let notFoundRecipient2MailAddress = "notfound2@tutanota.com" const validRecipientMailAddress = "alice@tuta.com" let bk = aes256RandomKey() const notFoundRecipients: string[] = [] const mismatchRecipients: string[] = [] const recipientPublicKey: Versioned = { version: 0, object: object(), } recipientPublicKey.object.keyPairType = KeyPairType.TUTA_CRYPT const loadedRecipientPublicKey: VerifiedPublicEncryptionKey = { publicEncryptionKey: recipientPublicKey, verificationState: EncryptionKeyVerificationState.NO_ENTRY, } when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: validRecipientMailAddress, }), ).thenResolve(loadedRecipientPublicKey) when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: notFoundRecipient1MailAddress, }), ).thenReject(new NotFoundError("")) when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: notFoundRecipient2MailAddress, }), ).thenReject(new NotFoundError("")) await crypto.encryptBucketKeyForInternalRecipient("senderGroupId", bk, notFoundRecipient1MailAddress, notFoundRecipients, mismatchRecipients) await crypto.encryptBucketKeyForInternalRecipient("senderGroupId", bk, validRecipientMailAddress, notFoundRecipients, mismatchRecipients) await crypto.encryptBucketKeyForInternalRecipient("senderGroupId", bk, notFoundRecipient2MailAddress, notFoundRecipients, mismatchRecipients) o(notFoundRecipients).deepEquals([notFoundRecipient1MailAddress, notFoundRecipient2MailAddress]) o(mismatchRecipients).deepEquals([]) verify(userFacade.getUser(), { times: 0 }) }) o("encryptBucketKeyForInternalRecipient for verification-failing recipients", async function () { let mismatchRecipient1MailAddress = "mismatch1@tutanota.com" let mismatchRecipient2MailAddress = "mismatch2@tutanota.com" const validRecipientMailAddress = "alice@tuta.com" let bk = aes256RandomKey() const notFoundRecipients: string[] = [] const mismatchRecipients: string[] = [] const recipientPublicKey: Versioned = { version: 0, object: object(), } recipientPublicKey.object.keyPairType = KeyPairType.TUTA_CRYPT const loadedRecipientPublicKey: VerifiedPublicEncryptionKey = { publicEncryptionKey: recipientPublicKey, verificationState: EncryptionKeyVerificationState.NO_ENTRY, } when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: validRecipientMailAddress, }), ).thenResolve(loadedRecipientPublicKey) when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: mismatchRecipient1MailAddress, }), ).thenReject(new KeyVerificationMismatchError("")) when( publicEncryptionKeyProvider.loadCurrentPublicEncryptionKey({ identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, identifier: mismatchRecipient2MailAddress, }), ).thenReject(new KeyVerificationMismatchError("")) await crypto.encryptBucketKeyForInternalRecipient("senderGroupId", bk, mismatchRecipient1MailAddress, notFoundRecipients, mismatchRecipients) await crypto.encryptBucketKeyForInternalRecipient("senderGroupId", bk, validRecipientMailAddress, notFoundRecipients, mismatchRecipients) await crypto.encryptBucketKeyForInternalRecipient("senderGroupId", bk, mismatchRecipient2MailAddress, notFoundRecipients, mismatchRecipients) o(notFoundRecipients).deepEquals([]) o(mismatchRecipients).deepEquals([mismatchRecipient1MailAddress, mismatchRecipient2MailAddress]) verify(userFacade.getUser(), { times: 0 }) }) o("authenticateSender | sender is authenticated for correct SenderIdentityKey", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest() const senderKeyVersion = "0" when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, testData.senderIdentityKeyPair.publicKey, parseKeyVersion(senderKeyVersion), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) const sessionKey: AesKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything())) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.mail.bucketKey!.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.mail._id), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, neverNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED) }) o("authenticateSender | sender is authenticated for correct SenderIdentityKey from system@tutanota.de", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest([], false) const senderKeyVersion = "0" const senderIdentifier = "system@tutanota.de" when( asymmetricCryptoFacade.authenticateSender( { identifier: senderIdentifier, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, testData.senderIdentityKeyPair.publicKey, parseKeyVersion(senderKeyVersion), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) const sessionKey: AesKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything())) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.mail.bucketKey!.bucketEncSessionKeys.length) const mailInstanceSessionKey = assertNotNull( updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.mail._id), ), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, mailInstanceSessionKey.encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED) }) o("authenticateSender | sender is not authenticated for incorrect SenderIdentityKey", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest() const senderKeyVersion = "0" when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, testData.senderIdentityKeyPair.publicKey, parseKeyVersion(senderKeyVersion), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_FAILED, verificationState: PresentableKeyVerificationState.ALERT }) const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything())) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.mail.bucketKey!.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.mail._id), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, neverNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_FAILED) }) o("authenticateSender | no authentication needed for sender with RSAKeypair", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest() const sessionKey = assertNotNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything()), { times: 1 }) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.mail.bucketKey!.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.mail._id), ) const actualAuthStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, assertNotNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAuthStatus).deepEquals(EncryptionAuthStatus.RSA_NO_AUTHENTICATION) }) o("authenticateSender | RSA was used despite recipient having tutacrypt", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest() when(keyLoaderFacade.loadCurrentKeyPair(anything())).thenResolve({ version: 1, object: { keyPairType: KeyPairType.TUTA_CRYPT, kyberKeyPair: object(), x25519KeyPair: object(), }, }) when(keyRotationFacade.getGroupIdsThatPerformedKeyRotations()).thenResolve([]) const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything()), { times: 1 }) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.mail.bucketKey!.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.mail._id), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, neverNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.RSA_DESPITE_TUTACRYPT) }) o("authenticateSender | RSA was used right after a key rotation", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest() when(keyLoaderFacade.loadCurrentKeyPair(anything())).thenResolve({ version: 1, object: { keyPairType: KeyPairType.TUTA_CRYPT, kyberKeyPair: object(), x25519KeyPair: object(), }, }) when(keyRotationFacade.getGroupIdsThatPerformedKeyRotations()).thenResolve([testData.userGroupId]) const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) const bucketKey = assertNotNull(testData.mail.bucketKey) o(sessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything()), { times: 1 }) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(bucketKey.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.mail._id), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, neverNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.RSA_NO_AUTHENTICATION) }) o("authenticateSender | no authentication needed for secure external recipient", async function () { o.timeout(500) // in CI or with debugging it can take a while const file1SessionKey = aes256RandomKey() const file2SessionKey = aes256RandomKey() const testData = await prepareConfidentialMailToExternalRecipient([file1SessionKey, file2SessionKey]) const mailSessionKey = neverNull(await crypto.resolveSessionKey(testData.entityAdapter)) o(mailSessionKey).deepEquals(testData.sk) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything()), { times: 1 }) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.bucketKey.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.entityAdapter._id), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, neverNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.AES_NO_AUTHENTICATION) }) o("authenticateSender | no authentication needed for secure external sender", async function () { //o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareConfidentialReplyFromExternalUser() const externalUser = testData.externalUser const mailSessionKey = neverNull(await crypto.resolveSessionKey(testData.entityAdapter)) o(mailSessionKey).deepEquals(testData.sk) const mailCaptor = matchers.captor() const userCaptor = matchers.captor() verify(keyLoaderFacade.loadSymGroupKey(externalUser.userGroup._id, parseKeyVersion(externalUser.mailGroup.adminGroupKeyVersion!), userCaptor.capture())) verify(keyLoaderFacade.loadSymGroupKey(externalUser.mailGroup._id, testData.recipientKeyVersion, mailCaptor.capture())) o(userCaptor.value.version).equals(parseKeyVersion(externalUser.userGroup.groupKeyVersion)) o(mailCaptor.value.version).equals(parseKeyVersion(externalUser.mailGroup.groupKeyVersion)) const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything()), { times: 1 }) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value as Array o(updatedInstanceSessionKeys.length).equals(testData.bucketKey.bucketEncSessionKeys.length) const mailInstanceSessionKey = updatedInstanceSessionKeys.find((instanceSessionKey) => isSameId([instanceSessionKey.instanceList, instanceSessionKey.instanceId], testData.entityAdapter._id), ) const actualAutStatus = utf8Uint8ArrayToString(aesDecrypt(testData.sk, neverNull(mailInstanceSessionKey).encryptionAuthStatus!)) o(actualAutStatus).deepEquals(EncryptionAuthStatus.AES_NO_AUTHENTICATION) }) o("resolve session key: rsa public key decryption of session key using BucketKey aggregated type", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest() const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) }) o( "resolve session key: rsa public key decryption of mail session key using BucketKey aggregated type - already decoded/decrypted Mail referencing MailDetailsDraft", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest() // do not use testdouble here because it's hard to not break the function itself and then verify invocations const decryptAndMapToInstance = (instancePipeline.cryptoMapper.decryptParsedInstance = spy(instancePipeline.cryptoMapper.decryptParsedInstance)) const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(decryptAndMapToInstance.invocations.length).equals(0) o(sessionKey).deepEquals(testData.sk) }, ) o("resolve session key: rsa public key decryption of session key using BucketKey aggregated type - Mail referencing MailDetailsBlob", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest() const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) }) o( "resolve session key: rsa public key decryption of session key using BucketKey aggregated type - Mail referencing MailDetailsBlob with attachments", async function () { o.timeout(500) // in CI or with debugging it can take a while const file1SessionKey = aes256RandomKey() const file2SessionKey = aes256RandomKey() const testData = await prepareRsaPubEncBucketKeyResolveSessionKeyTest([file1SessionKey, file2SessionKey]) const mailSessionKey = assertNotNull(await crypto.resolveSessionKey(testData.mail)) o(mailSessionKey).deepEquals(testData.sk) const bucketKey = assertNotNull(testData.mail.bucketKey) o(bucketKey.bucketEncSessionKeys.length).equals(3) //mail, file1, file2 const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything())) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value o(updatedInstanceSessionKeys.length).equals(bucketKey.bucketEncSessionKeys.length) for (const isk of bucketKey.bucketEncSessionKeys) { const expectedSessionKey = decryptKey(testData.bk, isk.symEncSessionKey) o( updatedInstanceSessionKeys.some((updatedKey) => { let updatedSessionKey = decryptKey(testData.mailGroupKey, updatedKey.symEncSessionKey) return ( updatedKey.instanceId === isk.instanceId && updatedKey.instanceList === isk.instanceList && updatedKey.typeInfo.application === isk.typeInfo.application && updatedKey.typeInfo.typeId === isk.typeInfo.typeId && arrayEquals(updatedSessionKey, expectedSessionKey) ) }), ).equals(true) } }, ) // ------------ o("resolve session key: pq public key decryption of mail session key using BucketKey aggregated type - Mail", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest() when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, anything(), anything(), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(sessionKey).deepEquals(testData.sk) }) o( "resolve session key: pq public key decryption of mail session key using BucketKey aggregated type - already decoded/decrypted Mail referencing MailDetailsDraft", async function () { o.timeout(500) // in CI or with debugging it can take a while const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest() when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, anything(), anything(), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) // do not use testdouble here because it's hard to not break the function itself and then verify invocations const decryptAndMapToInstance = (instancePipeline.cryptoMapper.decryptParsedInstance = spy(instancePipeline.cryptoMapper.decryptParsedInstance)) const sessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) o(decryptAndMapToInstance.invocations.length).equals(0) o(sessionKey).deepEquals(testData.sk) }, ) o( "resolve session key: pq public key decryption of session key using BucketKey aggregated type - Mail referencing MailDetailsBlob with attachments", async function () { o.timeout(500) // in CI or with debugging it can take a while const file1SessionKey = aes256RandomKey() const file2SessionKey = aes256RandomKey() const testData = await preparePqPubEncBucketKeyResolveSessionKeyTest([file1SessionKey, file2SessionKey]) when( asymmetricCryptoFacade.authenticateSender( { identifier: senderAddress, identifierType: PublicKeyIdentifierType.MAIL_ADDRESS, }, anything(), anything(), ), ).thenResolve({ authStatus: EncryptionAuthStatus.TUTACRYPT_AUTHENTICATION_SUCCEEDED, verificationState: PresentableKeyVerificationState.SECURE }) const mailSessionKey = neverNull(await crypto.resolveSessionKey(testData.mail)) const bucketKey = assertNotNull(testData.mail.bucketKey) o(mailSessionKey).deepEquals(testData.sk) o(bucketKey.bucketEncSessionKeys.length).equals(3) //mail, file1, file2 const updatedInstanceSessionKeysCaptor = captor() verify(ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(updatedInstanceSessionKeysCaptor.capture(), anything())) const updatedInstanceSessionKeys = updatedInstanceSessionKeysCaptor.value o(updatedInstanceSessionKeys.length).equals(bucketKey.bucketEncSessionKeys.length) for (const isk of bucketKey.bucketEncSessionKeys) { const expectedSessionKey = decryptKey(testData.bk, isk.symEncSessionKey) if ( !updatedInstanceSessionKeys.some((updatedKey) => { const updatedSessionKey = decryptKey(testData.mailGroupKey, updatedKey.symEncSessionKey) return ( updatedKey.instanceId === isk.instanceId && updatedKey.instanceList === isk.instanceList && updatedKey.typeInfo.application === isk.typeInfo.application && updatedKey.typeInfo.typeId === isk.typeInfo.typeId && arrayEquals(updatedSessionKey, expectedSessionKey) ) }) ) { console.log("===============================") updatedInstanceSessionKeys.some((updatedKey) => { const updatedSessionKey = decryptKey(testData.mailGroupKey, updatedKey.symEncSessionKey) console.log(">>>>>>>>>>>>>>>>>>>>>>>") console.log("1 ", updatedKey.instanceId, isk.instanceId) console.log("2 ", updatedKey.instanceList, isk.instanceList) console.log("3 ", updatedKey.typeInfo.application, isk.typeInfo.application) console.log("4 ", updatedKey.typeInfo.typeId, isk.typeInfo.typeId) console.log("5 ", updatedSessionKey, expectedSessionKey) }) } o( updatedInstanceSessionKeys.some((updatedKey) => { const updatedSessionKey = decryptKey(testData.mailGroupKey, updatedKey.symEncSessionKey) return ( updatedKey.instanceId === isk.instanceId && updatedKey.instanceList === isk.instanceList && updatedKey.typeInfo.application === isk.typeInfo.application && updatedKey.typeInfo.typeId === isk.typeInfo.typeId && arrayEquals(updatedSessionKey, expectedSessionKey) ) }), ).equals(true) } }, ) o( "resolve session key: external user key decryption of session key using BucketKey aggregated type encrypted with MailGroupKey - Mail referencing MailDetailsBlob with attachments", async function () { o.timeout(500) // in CI or with debugging it can take a while const file1SessionKey = aes256RandomKey() const file2SessionKey = aes256RandomKey() const testData = await prepareConfidentialMailToExternalRecipient([file1SessionKey, file2SessionKey]) const mailSessionKey = neverNull(await crypto.resolveSessionKey(testData.entityAdapter)) o(mailSessionKey).deepEquals(testData.sk) }, ) o( "resolve session key: external user key decryption of session key using BucketKey aggregated type encrypted with UserGroupKey - Mail referencing MailDetailsBlob with attachments", async function () { o.timeout(500) // in CI or with debugging it can take a while const file1SessionKey = aes256RandomKey() const file2SessionKey = aes256RandomKey() const testData = await prepareConfidentialMailToExternalRecipient([file1SessionKey, file2SessionKey], true) const mailSessionKey = neverNull(await crypto.resolveSessionKey(testData.entityAdapter)) o(mailSessionKey).deepEquals(testData.sk) }, ) o("resolve session key: MailDetailsBlob", async function () { const gk = aes256RandomKey() const sk = aes256RandomKey() const ownerGroup = "mailGroupId" when(keyLoaderFacade.getCurrentSymGroupKey(ownerGroup)).thenResolve({ object: gk, version: 0 }) when(userFacade.hasGroup(ownerGroup)).thenReturn(true) when(userFacade.isFullyLoggedIn()).thenReturn(true) const mailDetailsBlob = createTestEntity(MailDetailsBlobTypeRef, { _id: ["mailDetailsArchiveId", "mailDetailsId"], _ownerGroup: ownerGroup, _ownerEncSessionKey: encryptKey(gk, sk), }) when(keyLoaderFacade.loadSymGroupKey(ownerGroup, 0)).thenResolve(gk) const mailDetailsBlobSessionKey = neverNull(await crypto.resolveSessionKey(mailDetailsBlob)) o(mailDetailsBlobSessionKey).deepEquals(sk) }) o("resolve session key: MailDetailsBlob - session key not found", async function () { const mailDetailsBlob = createTestEntity(MailDetailsBlobTypeRef, { _id: ["mailDetailsArchiveId", "mailDetailsId"], _permissions: "permissionListId", }) when(entityClient.loadAll(PermissionTypeRef, "permissionListId")).thenResolve([]) try { await crypto.resolveSessionKey(mailDetailsBlob) o(true).equals(false) // let the test fails if there is no exception } catch (error) { o(error.constructor).equals(SessionKeyNotFoundError) } }) /** * Prepares the environment to test receiving rsa asymmetric encrypted emails that have been sent with the simplified permission system. * - Creates key pair for the recipient user * - Creates group, bucket and session keys * - Creates mail literal and encrypts all encrypted attributes of the mail * - Create BucketKey object on the mail * * @param fileSessionKeys List of session keys for the attachments. When the list is empty there are no attachments */ async function prepareRsaPubEncBucketKeyResolveSessionKeyTest(fileSessionKeys: Array = []): Promise<{ mail: Mail sk: Aes256Key bk: Aes256Key mailGroupKey: Aes256Key userGroupId: Id }> { // configure test user const recipientUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientUser, userFacade, keyLoaderFacade) let privateKey = RSA_TEST_KEYPAIR.privateKey let publicKey = RSA_TEST_KEYPAIR.publicKey const keyPair = createTestEntity(KeyPairTypeRef, { _id: "keyPairId", symEncPrivRsaKey: encryptRsaKey(recipientUser.userGroupKey, privateKey), pubRsaKey: hexToUint8Array(rsaPublicKeyToHex(publicKey)), }) recipientUser.userGroup.currentKeys = keyPair let sk = aes256RandomKey() let bk = aes256RandomKey() const mail = createTestEntity(MailTypeRef, { _id: ["mailListId", "mailId"], _permissions: "permissionListId", _ownerGroup: recipientUser.mailGroup._id, confidential: true, subject: "oh no is this a subject", }) const pubEncBucketKey = new Uint8Array([1, 2, 3, 4]) const bucketEncMailSessionKey = encryptKey(bk, sk) const mailInstanceSessionKey = createInstanceSessionKey({ typeInfo: createTypeInfo({ application: MailTypeRef.app, typeId: MailTypeRef.typeId.toString(), }), symEncSessionKey: bucketEncMailSessionKey, instanceList: listIdPart(mail._id), instanceId: elementIdPart(mail._id), encryptionAuthStatus: null, symKeyVersion: "0", keyVerificationState: null, }) const FileTypeModel = await typeModelResolver.resolveClientTypeReference(FileTypeRef) const bucketEncSessionKeys = fileSessionKeys.map((fileSessionKey, index) => { return createInstanceSessionKey({ typeInfo: createTypeInfo({ application: FileTypeModel.app, typeId: String(FileTypeModel.id), }), symEncSessionKey: encryptKey(bk, fileSessionKey), symKeyVersion: "0", instanceList: "fileListId", instanceId: "fileId" + (index + 1), encryptionAuthStatus: null, keyVerificationState: null, }) }) bucketEncSessionKeys.push(mailInstanceSessionKey) const protocolVersion = CryptoProtocolVersion.RSA const bucketKey = createBucketKey({ pubEncBucketKey, keyGroup: recipientUser.userGroup._id, bucketEncSessionKeys: bucketEncSessionKeys, groupEncBucketKey: null, protocolVersion, senderKeyVersion: null, recipientKeyVersion: "0", }) when(keyLoaderFacade.loadCurrentKeyPair(recipientUser.userGroup._id)).thenResolve({ object: { keyPairType: KeyPairType.RSA, publicKey: RSA_TEST_KEYPAIR.publicKey, privateKey: RSA_TEST_KEYPAIR.privateKey, }, version: 0, }) when( asymmetricCryptoFacade.loadKeyPairAndDecryptSymKey( assertNotNull(bucketKey.keyGroup), parseKeyVersion(bucketKey.recipientKeyVersion), asCryptoProtoocolVersion(bucketKey.protocolVersion), pubEncBucketKey, anything(), ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: null }) mail.bucketKey = bucketKey return { mail, sk, bk, mailGroupKey: recipientUser.mailGroupKey, userGroupId: recipientUser.userGroup._id, } } /** * Prepares the environment to test receiving pq asymmetric encrypted emails that have been sent with the simplified permission system. * - Creates key pair for the recipient user * - Creates group, bucket and session keys * - Creates mail literal and encrypts all encrypted attributes of the mail * - Create BucketKey object on the mail * * @param fileSessionKeys List of session keys for the attachments. When the list is empty there are no attachments */ async function preparePqPubEncBucketKeyResolveSessionKeyTest( fileSessionKeys: Array = [], confidential: boolean = true, ): Promise<{ mail: Mail sk: AesKey bk: AesKey mailGroupKey: AesKey senderIdentityKeyPair: X25519KeyPair }> { // create test user const recipientUser = createTestUser("Bob", entityClient) configureLoggedInUser(recipientUser, userFacade, keyLoaderFacade) let pqKeyPairs = await pqFacade.generateKeyPairs() const recipientKeyPair = createKeyPair({ _id: "keyPairId", pubEccKey: pqKeyPairs.x25519KeyPair.publicKey, symEncPrivEccKey: aesEncrypt(recipientUser.userGroupKey, pqKeyPairs.x25519KeyPair.privateKey), pubKyberKey: kyberPublicKeyToBytes(pqKeyPairs.kyberKeyPair.publicKey), symEncPrivKyberKey: aesEncrypt(recipientUser.userGroupKey, kyberPrivateKeyToBytes(pqKeyPairs.kyberKeyPair.privateKey)), pubRsaKey: null, symEncPrivRsaKey: null, signature: null, }) recipientUser.userGroup.currentKeys = recipientKeyPair const senderIdentityKeyPair = generateX25519KeyPair() let sk = aes256RandomKey() let bk = aes256RandomKey() const mail = createTestEntity(MailTypeRef, { confidential, _ownerGroup: recipientUser.mailGroup._id, _ownerEncSessionKey: null, // enforce asymmetric crypto to resolve session key _id: ["mailListId", "mailId"], _permissions: "permissionListId", sender: createTestEntity(MailAddressTypeRef, { address: senderAddress, name: "sender name", }), }) const pubEncBucketKey = await pqFacade.encapsulateAndEncode( senderIdentityKeyPair, generateX25519KeyPair(), pqKeyPairsToPublicKeys(pqKeyPairs), bitArrayToUint8Array(bk), ) const bucketEncMailSessionKey = encryptKey(bk, sk) await prepareBucketKeyInstance( bucketEncMailSessionKey, fileSessionKeys, bk, pubEncBucketKey, recipientUser, mail, undefined, "0", CryptoProtocolVersion.TUTA_CRYPT, asymmetricCryptoFacade, ) when( asymmetricCryptoFacade.decryptSymKeyWithKeyPair( { keyPairType: pqKeyPairs.keyPairType, x25519KeyPair: pqKeyPairs.x25519KeyPair, kyberKeyPair: pqKeyPairs.kyberKeyPair, }, CryptoProtocolVersion.TUTA_CRYPT, pubEncBucketKey, ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: senderIdentityKeyPair.publicKey }) when( asymmetricCryptoFacade.loadKeyPairAndDecryptSymKey( assertNotNull(mail.bucketKey?.keyGroup), parseKeyVersion(assertNotNull(mail.bucketKey?.recipientKeyVersion)), asCryptoProtoocolVersion(assertNotNull(mail.bucketKey?.protocolVersion)), pubEncBucketKey, anything(), ), ).thenResolve({ decryptedAesKey: bk, senderIdentityPubKey: senderIdentityKeyPair.publicKey }) return { mail, sk, bk, mailGroupKey: recipientUser.mailGroupKey, senderIdentityKeyPair, } } /** * Prepares the environment to test receiving symmetric encrypted emails (mails sent from internal to external user) that have been sent with the simplified permission system. * - Creates group, bucket and session keys * - Creates mail literal and encrypts all encrypted attributes of the mail * - Create BucketKey object on the mail * * @param fileSessionKeys List of session keys for the attachments. When the list is empty there are no attachments * @param externalUserGroupEncBucketKey for legacy external user group to encrypt bucket key */ async function prepareConfidentialMailToExternalRecipient( fileSessionKeys: Array = [], externalUserGroupEncBucketKey = false, ): Promise<{ entityAdapter: EntityAdapter bucketKey: BucketKey sk: AesKey bk: AesKey MailTypeModel: TypeModel }> { // create user const externalUser = createTestUser("Bob", entityClient) configureLoggedInUser(externalUser, userFacade, keyLoaderFacade) // create test mail let confidential = true let sk = aes256RandomKey() let bk = aes256RandomKey() const mailUntypedInstance = await createUntypedMailInstance(null, sk, confidential, externalUser.mailGroup._id) const groupKeyToEncryptBucketKey = externalUserGroupEncBucketKey ? externalUser.userGroupKey : externalUser.mailGroupKey const groupEncBucketKey = encryptKey(groupKeyToEncryptBucketKey, bk) const bucketEncMailSessionKey = encryptKey(bk, sk) const MailTypeModel = await typeModelResolver.resolveServerTypeReference(MailTypeRef) const mailInstanceSessionKey = createTestEntity(InstanceSessionKeyTypeRef, { typeInfo: createTestEntity(TypeInfoTypeRef, { application: MailTypeModel.app, typeId: String(MailTypeModel.id), }), symEncSessionKey: bucketEncMailSessionKey, instanceList: "mailListId", instanceId: "mailId", }) const FileTypeModel = await typeModelResolver.resolveServerTypeReference(FileTypeRef) const bucketEncSessionKeys = fileSessionKeys.map((fileSessionKey, index) => { return createTestEntity(InstanceSessionKeyTypeRef, { typeInfo: createTestEntity(TypeInfoTypeRef, { application: FileTypeModel.app, typeId: String(FileTypeModel.id), }), symEncSessionKey: encryptKey(bk, fileSessionKey), instanceList: "fileListId", instanceId: "fileId" + (index + 1), }) }) bucketEncSessionKeys.push(mailInstanceSessionKey) const bucketKey = createTestEntity(BucketKeyTypeRef, { pubEncBucketKey: null, keyGroup: externalUserGroupEncBucketKey ? externalUser.userGroup._id : null, groupEncBucketKey: groupEncBucketKey, bucketEncSessionKeys: bucketEncSessionKeys, }) const bucketKeyUntypedInstance: UntypedInstance = await instancePipeline.mapAndEncrypt(BucketKeyTypeRef, bucketKey, null) mailUntypedInstance[assertNotNull(AttributeModel.getAttributeId(MailTypeModel, "bucketKey"))] = [bucketKeyUntypedInstance] const mailEncryptedParsedInstance = await instancePipeline.typeMapper.applyJsTypes(MailTypeModel, mailUntypedInstance) return { entityAdapter: await EntityAdapter.from(MailTypeModel, mailEncryptedParsedInstance, instancePipeline), bucketKey, sk, bk, MailTypeModel, } } /** * Prepares the environment to test receiving symmetric encrypted emails from an external sender(mails sent from external to internal user) that have been sent with the simplified permission system. * - Creates group, bucket and session keys * - Creates mail literal and encrypts all encrypted attributes of the mail * - Create BucketKey object on the mail * * @param fileSessionKeys List of session keys for the attachments. When the list is empty there are no attachments */ async function prepareConfidentialReplyFromExternalUser(): Promise<{ entityAdapter: EntityAdapter bucketKey: BucketKey sk: AesKey bk: AesKey MailTypeModel: TypeModel internalUser: TestUser externalUser: TestUser recipientKeyVersion: KeyVersion }> { // Setup test users and groups const internalUser = createTestUser("Alice", entityClient) const externalUser = createTestUser("Bob", entityClient) // Setup relationship between internal and external user externalUser.userGroup.admin = internalUser.userGroup._id externalUser.userGroup.adminGroupEncGKey = encryptKey(internalUser.userGroupKey, externalUser.userGroupKey) externalUser.userGroup.adminGroupKeyVersion = "0" externalUser.mailGroup.admin = externalUser.userGroup._id externalUser.mailGroup.adminGroupEncGKey = encryptKey(externalUser.userGroupKey, externalUser.mailGroupKey) externalUser.mailGroup.adminGroupKeyVersion = "4" const recipientKeyVersion = "5" externalUser.userGroup.groupKeyVersion = "7" externalUser.mailGroup.groupKeyVersion = "8" configureLoggedInUser(internalUser, userFacade, keyLoaderFacade) when(keyLoaderFacade.loadSymGroupKey(externalUser.mailGroup._id, parseKeyVersion(recipientKeyVersion), anything())).thenResolve( externalUser.mailGroupKey, ) when(keyLoaderFacade.loadSymGroupKey(externalUser.userGroup._id, parseKeyVersion(externalUser.mailGroup.adminGroupKeyVersion), anything())).thenResolve( externalUser.userGroupKey, ) // setup test mail (confidential reply from external) let confidential = true let sk = aes256RandomKey() let bk = aes256RandomKey() const untypedMailInstance = await createUntypedMailInstance(null, sk, confidential, internalUser.mailGroup._id) const keyGroup = externalUser.mailGroup._id const groupEncBucketKey = encryptKey(externalUser.mailGroupKey, bk) const bucketEncMailSessionKey = encryptKey(bk, sk) const MailTypeModel = await typeModelResolver.resolveServerTypeReference(MailTypeRef) const mailInstanceSessionKey = createTestEntity(InstanceSessionKeyTypeRef, { typeInfo: createTestEntity(TypeInfoTypeRef, { application: MailTypeModel.app, typeId: String(MailTypeModel.id), }), symEncSessionKey: bucketEncMailSessionKey, instanceList: "mailListId", instanceId: "mailId", }) const bucketEncSessionKeys = new Array() bucketEncSessionKeys.push(mailInstanceSessionKey) const bucketKey = createTestEntity(BucketKeyTypeRef, { pubEncBucketKey: null, keyGroup: keyGroup, groupEncBucketKey: groupEncBucketKey, recipientKeyVersion, bucketEncSessionKeys: bucketEncSessionKeys, protocolVersion: CryptoProtocolVersion.SYMMETRIC_ENCRYPTION, senderKeyVersion: null, }) const bucketKeyUntypedInstance: UntypedInstance = await instancePipeline.mapAndEncrypt(BucketKeyTypeRef, bucketKey, null) untypedMailInstance[assertNotNull(AttributeModel.getAttributeId(MailTypeModel, "bucketKey"))] = [bucketKeyUntypedInstance] const encryptedMailParsedInstance = await instancePipeline.typeMapper.applyJsTypes(MailTypeModel, untypedMailInstance) const entityAdapter = await EntityAdapter.from(MailTypeModel, encryptedMailParsedInstance, instancePipeline) return { entityAdapter: entityAdapter, bucketKey, sk, bk, MailTypeModel, internalUser, externalUser, recipientKeyVersion: parseKeyVersion(recipientKeyVersion), } } async function createUntypedMailInstance( ownerGroupKey: AesKey | null, sessionKey: AesKey, confidential: boolean, ownerGroupId: string, ): Promise { const mail = createMail({ _format: "0", _ownerGroup: ownerGroupId, _ownerEncSessionKey: ownerGroupKey ? encryptKey(ownerGroupKey, sessionKey) : null, _permissions: "permissionListId", _id: ["mailListId", "mailId"], receivedDate: new Date(1470039025474), state: "", unread: true, subject: "any subject", replyType: "", confidential: confidential, sender: createMailAddress({ address: senderAddress, name: "any sender", contact: null, }), bucketKey: null, authStatus: "0", listUnsubscribe: false, method: "", phishingStatus: "0", recipientCount: "0", differentEnvelopeSender: null, movedTime: null, encryptionAuthStatus: null, _ownerKeyVersion: null, attachments: [], conversationEntry: ["entryListId", "entryId"], firstRecipient: null, mailDetails: null, mailDetailsDraft: null, sets: [], keyVerificationState: null, processingState: ProcessingState.INBOX_RULE_APPLIED, clientSpamClassifierResult: null, }) // casting here is fine, since we just want to mimic server response data return (await instancePipeline.mapAndEncrypt(MailTypeRef, mail, sessionKey)) as unknown as ServerModelUntypedInstance } }) export function createTestUser(name: string, entityClient: EntityClient): TestUser { const userGroupKey = aes256RandomKey() const mailGroupKey = aes256RandomKey() const userGroup = createTestEntity(GroupTypeRef, { _id: "userGroup" + name, type: GroupType.User, currentKeys: null, groupKeyVersion: "0", }) const mailGroup = createTestEntity(GroupTypeRef, { _id: "mailGroup" + name, type: GroupType.Mail, currentKeys: null, groupKeyVersion: "0", }) const userGroupMembership = createTestEntity(GroupMembershipTypeRef, { group: userGroup._id, }) const mailGroupMembership = createTestEntity(GroupMembershipTypeRef, { group: mailGroup._id, }) const user = createTestEntity(UserTypeRef, { userGroup: userGroupMembership, memberships: [mailGroupMembership], }) when(entityClient.load(GroupTypeRef, userGroup._id)).thenResolve(userGroup) when(entityClient.load(GroupTypeRef, mailGroup._id)).thenResolve(mailGroup) return { user, userGroup, mailGroup, userGroupKey, mailGroupKey, name, } } /** * Helper function to mock the user facade so that the given test user is considered as logged in user. */ export function configureLoggedInUser(testUser: TestUser, userFacade: UserFacade, keyLoaderFacade: KeyLoaderFacade) { when(userFacade.getLoggedInUser()).thenReturn(testUser.user) when(keyLoaderFacade.getCurrentSymGroupKey(testUser.mailGroup._id)).thenResolve({ object: testUser.mailGroupKey, version: 0, }) when(keyLoaderFacade.getCurrentSymGroupKey(testUser.userGroup._id)).thenResolve({ object: testUser.userGroupKey, version: 0, }) when(userFacade.hasGroup(testUser.userGroup._id)).thenReturn(true) when(userFacade.hasGroup(testUser.mailGroup._id)).thenReturn(true) when(userFacade.getCurrentUserGroupKey()).thenReturn({ object: testUser.userGroupKey, version: 0 }) when(userFacade.isLeader()).thenReturn(true) when(userFacade.isFullyLoggedIn()).thenReturn(true) when(keyLoaderFacade.loadSymGroupKey(testUser.mailGroup._id, 0)).thenResolve(testUser.mailGroupKey) when(keyLoaderFacade.loadSymGroupKey(testUser.userGroup._id, 0)).thenResolve(testUser.userGroupKey) }