From 7e49bad411a64f0e73c8a004b6593b3ac689b7da Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 May 2022 17:26:26 +0200 Subject: [PATCH] Try to clean up after merging upstream develop --- .../sdk/api/session/crypto/CryptoService.kt | 1 - .../api/session/crypto/NewSessionListener.kt | 3 +- .../crosssigning/CrossSigningService.kt | 2 +- .../session/crypto/model/MXUsersDevicesMap.kt | 5 + .../sdk/internal/crypto/CryptoModule.kt | 10 - .../crypto/CryptoSessionInfoProvider.kt | 2 +- ...pRequestType.kt => DecryptEventUseCase.kt} | 16 +- .../internal/crypto/DefaultCryptoService.kt | 166 +--- .../android/sdk/internal/crypto/Device.kt | 6 +- .../sdk/internal/crypto/DeviceListManager.kt | 585 ----------- .../crypto/EncryptEventContentUseCase.kt | 44 + .../sdk/internal/crypto/EventDecryptor.kt | 230 ----- ...ionFactory.kt => GetRoomUserIdsUseCase.kt} | 17 +- .../crypto/IncomingKeyRequestManager.kt | 463 --------- .../sdk/internal/crypto/MXOlmDevice.kt | 922 ------------------ .../crypto/MegolmSessionImportManager.kt | 1 + .../sdk/internal/crypto/MyDeviceInfoHolder.kt | 80 -- .../sdk/internal/crypto/ObjectSigner.kt | 52 - .../internal/crypto/OneTimeKeysUploader.kt | 247 ----- .../crypto/OutgoingKeyRequestManager.kt | 518 ---------- .../PerSessionBackupQueryRateLimiter.kt | 7 +- .../crypto/PrepareToEncryptUseCase.kt | 144 +++ .../sdk/internal/crypto/QrCodeVerification.kt | 2 +- .../internal/crypto/RoomDecryptorProvider.kt | 106 -- .../internal/crypto/RoomEncryptorsStore.kt | 60 -- .../crypto/RustCrossSigningService.kt | 10 +- .../sdk/internal/crypto/SecretShareManager.kt | 13 +- .../ShouldEncryptForInvitedMembersUseCase.kt | 29 + .../sdk/internal/crypto/UserIdentities.kt | 4 +- .../internal/crypto/VerificationRequest.kt | 2 +- .../EnsureOlmSessionsForDevicesAction.kt | 170 ---- .../EnsureOlmSessionsForUsersAction.kt | 48 - .../actions/MegolmSessionDataImporter.kt | 122 --- .../crypto/actions/MessageEncrypter.kt | 88 -- .../actions/SetDeviceVerificationAction.kt | 53 - .../crypto/algorithms/IMXDecrypting.kt | 44 - .../crypto/algorithms/IMXEncrypting.kt | 36 - .../crypto/algorithms/IMXGroupEncryption.kt | 53 - .../algorithms/megolm/MXMegolmDecryption.kt | 304 ------ .../megolm/MXMegolmDecryptionFactory.kt | 40 - .../algorithms/megolm/MXMegolmEncryption.kt | 515 ---------- .../megolm/MXMegolmEncryptionFactory.kt | 67 -- .../megolm/MXOutboundSessionInfo.kt | 76 -- .../algorithms/megolm/SharedWithHelper.kt | 42 - .../crypto/algorithms/olm/MXOlmDecryption.kt | 268 ----- .../crypto/algorithms/olm/MXOlmEncryption.kt | 79 -- .../algorithms/olm/MXOlmEncryptionFactory.kt | 44 - .../crypto/crosssigning/ComputeTrustTask.kt | 93 -- .../DefaultCrossSigningService.kt | 806 --------------- .../crypto/crosssigning/UpdateTrustWorker.kt | 350 ------- .../crypto/keysbackup/RustKeyBackupService.kt | 34 +- .../network/OutgoingRequestsProcessor.kt | 2 +- .../internal/crypto/network/RequestSender.kt | 28 +- .../tasks/InitializeCrossSigningTask.kt | 190 ---- .../DefaultQrCodeVerificationTransaction.kt | 283 ------ .../session/DefaultToDeviceService.kt | 11 +- .../sdk/internal/session/SessionComponent.kt | 3 - .../room/summary/RoomSummaryUpdater.kt | 9 +- .../session/room/timeline/GetEventTask.kt | 7 +- .../room/timeline/TimelineEventDecryptor.kt | 2 +- .../VerificationBottomSheetViewModel.kt | 1 + .../app/features/home/HomeDetailViewModel.kt | 2 +- .../VectorSettingsSecurityPrivacyFragment.kt | 1 + .../CrossSigningSettingsViewModel.kt | 1 + .../signout/soft/SoftLogoutController.kt | 1 + 65 files changed, 330 insertions(+), 7290 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/{GossipRequestType.kt => DecryptEventUseCase.kt} (55%) delete mode 100755 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/{algorithms/olm/MXOlmDecryptionFactory.kt => GetRoomUserIdsUseCase.kt} (52%) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt delete mode 100755 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt delete mode 100755 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 45879a3c9c..207937d393 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.NewSessionListener interface CryptoService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt index 73cbf5fb78..8fe2d160f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt @@ -23,8 +23,7 @@ interface NewSessionListener { /** * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions - * @param senderKey the sender key of the device which the Megolm session is shared with * @param sessionId the session id of the Megolm session */ - fun onNewSession(roomId: String?, senderKey: String, sessionId: String) + fun onNewSession(roomId: String?, sessionId: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt index 5e58893def..006d6da310 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.util.Optional interface CrossSigningService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt index dc5567e908..02f05e6b01 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt @@ -104,6 +104,11 @@ class MXUsersDevicesMap { map.clear() } + fun join(other: Map>) { + map.putAll(other) + } + + /** * Add entries from another MXUsersDevicesMap * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index 94565c9144..748c2501b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -26,8 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.crosssigning.ComputeTrustTask -import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultComputeTrustTask import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask @@ -68,7 +66,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDevicesTask -import org.matrix.android.sdk.internal.crypto.tasks.DefaultInitializeCrossSigningTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendEventTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask @@ -81,7 +78,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask -import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask @@ -247,12 +243,6 @@ internal abstract class CryptoModule { @Binds abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore - @Binds - abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask - - @Binds - abstract fun bindInitializeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask - @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index 082d750cb1..be059888b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.crypto import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipRequestType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DecryptEventUseCase.kt similarity index 55% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipRequestType.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DecryptEventUseCase.kt index 19f89b2f1e..6eab9dbdae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipRequestType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DecryptEventUseCase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.matrix.android.sdk.internal.crypto -interface NewSessionListener { - fun onNewSession(roomId: String?, sessionId: String) +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +internal class DecryptEventUseCase @Inject constructor(olmMachineProvider: OlmMachineProvider) { + + private val olmMachine = olmMachineProvider.olmMachine + + suspend operator fun invoke(event: Event): MXEventDecryptionResult { + return olmMachine.decryptRoomEvent(event) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 589e0c0e30..2e180de4e7 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -138,7 +138,11 @@ internal class DefaultCryptoService @Inject constructor( private val keysBackupService: RustKeyBackupService, private val megolmSessionImportManager: MegolmSessionImportManager, private val olmMachineProvider: OlmMachineProvider, - private val liveEventManager: dagger.Lazy + private val liveEventManager: dagger.Lazy, + private val prepareToEncrypt: PrepareToEncryptUseCase, + private val encryptEventContent: EncryptEventContentUseCase, + private val shouldEncryptForInvitedMembers: ShouldEncryptForInvitedMembersUseCase, + private val getRoomUserIds: GetRoomUserIdsUseCase, ) : CryptoService { private val isStarting = AtomicBoolean(false) @@ -152,14 +156,12 @@ internal class DefaultCryptoService @Inject constructor( // private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver() // Locks for some of our operations - private val keyClaimLock: Mutex = Mutex() private val outgoingRequestsProcessor = OutgoingRequestsProcessor( requestSender = requestSender, coroutineScope = cryptoCoroutineScope, cryptoSessionInfoProvider = cryptoSessionInfoProvider, shieldComputer = crossSigningService::shieldForGroup ) - private val roomKeyShareLocks: ConcurrentHashMap = ConcurrentHashMap() fun onStateEvent(roomId: String, event: Event) { when (event.type) { @@ -450,27 +452,7 @@ internal class DefaultCryptoService @Inject constructor( override suspend fun encryptEventContent(eventContent: Content, eventType: String, roomId: String): MXEncryptEventContentResult { - // moved to crypto scope to have up to date values - return withContext(coroutineDispatchers.crypto) { - val algorithm = getEncryptionAlgorithm(roomId) - if (algorithm != null) { - val userIds = getRoomUserIds(roomId) - val t0 = System.currentTimeMillis() - Timber.tag(loggerTag.value).v("encryptEventContent() starts") - measureTimeMillis { - preshareRoomKey(roomId, userIds) - }.also { - Timber.d("Shared room key in room $roomId took $it ms") - } - val content = encrypt(roomId, eventType, eventContent) - Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms") - MXEncryptEventContentResult(content, EventType.ENCRYPTED) - } else { - val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON) - Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason") - throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)) - } - } + return encryptEventContent.invoke(eventContent, eventType, roomId) } override fun discardOutboundSession(roomId: String) { @@ -523,12 +505,6 @@ internal class DefaultCryptoService @Inject constructor( } } - private fun getRoomUserIds(roomId: String): List { - val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() && - shouldEncryptForInvitedMembers(roomId) - return cryptoSessionInfoProvider.getRoomUserIds(roomId, encryptForInvitedMembers) - } - /** * Handle a change in the membership state of a member of a room. * @@ -593,7 +569,7 @@ internal class DefaultCryptoService @Inject constructor( when (event.type) { EventType.ROOM_KEY -> { val content = event.getClearContent().toModel() ?: return@forEach - + content.sessionKey val roomId = content.sessionId ?: return@forEach val sessionId = content.sessionId @@ -622,80 +598,7 @@ internal class DefaultCryptoService @Inject constructor( } } - private suspend fun preshareRoomKey(roomId: String, roomMembers: List) { - claimMissingKeys(roomMembers) - val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() } - var sharedKey = false - keyShareLock.withLock { - coroutineScope { - olmMachine.shareRoomKey(roomId, roomMembers).map { - when (it) { - is Request.ToDevice -> { - sharedKey = true - async { - sendToDevice(it) - } - } - else -> { - // This request can only be a to-device request but - // we need to handle all our cases and put this - // async block for our joinAll to work. - async {} - } - } - }.joinAll() - } - } - // If we sent out a room key over to-device messages it's likely that we created a new one - // Try to back the key up - if (sharedKey) { - keysBackupService.maybeBackupKeys() - } - } - - private suspend fun claimMissingKeys(roomMembers: List) = keyClaimLock.withLock { - val request = this.olmMachine.getMissingSessions(roomMembers) - // This request can only be a keys claim request. - when (request) { - is Request.KeysClaim -> { - claimKeys(request) - } - else -> { - } - } - } - - private suspend fun encrypt(roomId: String, eventType: String, content: Content): Content { - return olmMachine.encrypt(roomId, eventType, content) - } - - private suspend fun uploadKeys(request: Request.KeysUpload) { - try { - val response = requestSender.uploadKeys(request) - olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_UPLOAD, response) - } catch (throwable: Throwable) { - Timber.tag(loggerTag.value).e(throwable, "## CRYPTO uploadKeys(): error") - } - } - - private suspend fun queryKeys(request: Request.KeysQuery) { - try { - val response = requestSender.queryKeys(request) - olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_QUERY, response) - - // Update the shields! - cryptoCoroutineScope.launch { - cryptoSessionInfoProvider.getRoomsWhereUsersAreParticipating(request.users).forEach { roomId -> - val userGroup = cryptoSessionInfoProvider.getUserListForShieldComputation(roomId) - val shield = crossSigningService.shieldForGroup(userGroup) - cryptoSessionInfoProvider.updateShieldForRoom(roomId, shield) - } - } - } catch (throwable: Throwable) { - Timber.tag(loggerTag.value).e(throwable, "## CRYPTO doKeyDownloadForUsers(): error") - } - } private suspend fun sendToDevice(request: Request.ToDevice) { try { @@ -706,34 +609,6 @@ internal class DefaultCryptoService @Inject constructor( } } - private suspend fun claimKeys(request: Request.KeysClaim) { - try { - val response = requestSender.claimKeys(request) - olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response) - } catch (throwable: Throwable) { - Timber.tag(loggerTag.value).e(throwable, "## CRYPTO claimKeys(): error") - } - } - - private suspend fun signatureUpload(request: Request.SignatureUpload) { - try { - val response = requestSender.sendSignatureUpload(request) - olmMachine.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, response) - } catch (throwable: Throwable) { - Timber.tag(loggerTag.value).e(throwable, "## CRYPTO signatureUpload(): error") - } - } - - private suspend fun sendRoomMessage(request: Request.RoomMessage) { - try { - Timber.v("SendRoomMessage: $request") - val response = requestSender.sendRoomMessage(request) - olmMachine.markRequestAsSent(request.requestId, RequestType.ROOM_MESSAGE, response) - } catch (throwable: Throwable) { - Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendRoomMessage(): error") - } - } - /** * Export the crypto keys * @@ -966,32 +841,7 @@ internal class DefaultCryptoService @Inject constructor( cryptoStore.logDbUsageInfo() } - override suspend fun prepareToEncrypt(roomId: String) { - withContext(coroutineDispatchers.crypto) { - Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") - // Ensure to load all room members - try { - loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId)) - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members") - throw failure - } - val userIds = getRoomUserIds(roomId) - - val algorithm = getEncryptionAlgorithm(roomId) - - if (algorithm == null) { - val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON) - Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason") - throw IllegalArgumentException("Missing algorithm") - } - try { - preshareRoomKey(roomId, userIds) - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to PreshareRoomKey") - } - } - } + override suspend fun prepareToEncrypt(roomId: String) = prepareToEncrypt.invoke(roomId, ensureAllMembersAreLoaded = true) /* ========================================================================================== * For test only diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt index 8f3491bf0a..90bf533d58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/Device.kt @@ -18,11 +18,11 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.verification.prepareMethods import uniffi.olm.CryptoStoreException diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt deleted file mode 100755 index db7edc8aa0..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ /dev/null @@ -1,585 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.session.sync.SyncTokenStore -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.logLimit -import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber -import javax.inject.Inject - -// Legacy name: MXDeviceList -@Deprecated("In favor of rust olmMachine") -@SessionScope -internal class DeviceListManager @Inject constructor( - private val cryptoStore: IMXCryptoStore, - private val olmDevice: MXOlmDevice, - private val syncTokenStore: SyncTokenStore, - private val credentials: Credentials, - private val downloadKeysForUsersTask: DownloadKeysForUsersTask, - private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, - coroutineDispatchers: MatrixCoroutineDispatchers, - private val taskExecutor: TaskExecutor, - private val clock: Clock, -) { - - interface UserDevicesUpdateListener { - fun onUsersDeviceUpdate(userIds: List) - } - - private val deviceChangeListeners = mutableListOf() - - fun addListener(listener: UserDevicesUpdateListener) { - synchronized(deviceChangeListeners) { - deviceChangeListeners.add(listener) - } - } - - fun removeListener(listener: UserDevicesUpdateListener) { - synchronized(deviceChangeListeners) { - deviceChangeListeners.remove(listener) - } - } - - private fun dispatchDeviceChange(users: List) { - synchronized(deviceChangeListeners) { - deviceChangeListeners.forEach { - try { - it.onUsersDeviceUpdate(users) - } catch (failure: Throwable) { - Timber.e(failure, "Failed to dispatch device change") - } - } - } - } - - // HS not ready for retry - private val notReadyToRetryHS = mutableSetOf() - - private val cryptoCoroutineContext = coroutineDispatchers.crypto - - init { - taskExecutor.executorScope.launch(cryptoCoroutineContext) { - var isUpdated = false - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - for ((userId, status) in deviceTrackingStatuses) { - if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) { - // if a download was in progress when we got shut down, it isn't any more. - deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD - isUpdated = true - } - } - if (isUpdated) { - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - } - } - } - - /** - * Tells if the key downloads should be tried - * - * @param userId the userId - * @return true if the keys download can be retrieved - */ - private fun canRetryKeysDownload(userId: String): Boolean { - var res = false - - if (':' in userId) { - try { - synchronized(notReadyToRetryHS) { - res = !notReadyToRetryHS.contains(userId.substringAfter(':')) - } - } catch (e: Exception) { - Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed") - } - } - - return res - } - - /** - * Clear the unavailable server lists - */ - private fun clearUnavailableServersList() { - synchronized(notReadyToRetryHS) { - notReadyToRetryHS.clear() - } - } - - fun onRoomMembersLoadedFor(roomId: String) { - taskExecutor.executorScope.launch(cryptoCoroutineContext) { - if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { - // It's OK to track also device for invited users - val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true) - startTrackingDeviceList(userIds) - refreshOutdatedDeviceLists() - } - } - } - - /** - * Mark the cached device list for the given user outdated - * flag the given user for device-list tracking, if they are not already. - * - * @param userIds the user ids list - */ - fun startTrackingDeviceList(userIds: List) { - var isUpdated = false - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - - for (userId in userIds) { - if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { - Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId") - deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD - isUpdated = true - } - } - - if (isUpdated) { - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - } - } - - /** - * Update the devices list statuses - * - * @param changed the user ids list which have new devices - * @param left the user ids list which left a room - */ - fun handleDeviceListsChanges(changed: Collection, left: Collection) { - Timber.v("## CRYPTO: handleDeviceListsChanges changed: ${changed.logLimit()} / left: ${left.logLimit()}") - var isUpdated = false - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - - if (changed.isNotEmpty() || left.isNotEmpty()) { - clearUnavailableServersList() - } - - for (userId in changed) { - if (deviceTrackingStatuses.containsKey(userId)) { - Timber.v("## CRYPTO | handleDeviceListsChanges() : Marking device list outdated for $userId") - deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD - isUpdated = true - } - } - - for (userId in left) { - if (deviceTrackingStatuses.containsKey(userId)) { - Timber.v("## CRYPTO | handleDeviceListsChanges() : No longer tracking device list for $userId") - deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED - isUpdated = true - } - } - - if (isUpdated) { - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - } - } - - /** - * This will flag each user whose devices we are tracking as in need of an - * + update - */ - fun invalidateAllDeviceLists() { - handleDeviceListsChanges(cryptoStore.getDeviceTrackingStatuses().keys, emptyList()) - } - - /** - * The keys download failed - * - * @param userIds the user ids list - */ - private fun onKeysDownloadFailed(userIds: List) { - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - userIds.associateWithTo(deviceTrackingStatuses) { TRACKING_STATUS_PENDING_DOWNLOAD } - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - } - - /** - * The keys download succeeded. - * - * @param userIds the userIds list - * @param failures the failure map. - */ - private fun onKeysDownloadSucceed(userIds: List, failures: Map>?): MXUsersDevicesMap { - if (failures != null) { - for ((k, value) in failures) { - val statusCode = when (val status = value["status"]) { - is Double -> status.toInt() - is Int -> status.toInt() - else -> 0 - } - if (statusCode == 503) { - synchronized(notReadyToRetryHS) { - notReadyToRetryHS.add(k) - } - } - } - } - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - val usersDevicesInfoMap = MXUsersDevicesMap() - for (userId in userIds) { - val devices = cryptoStore.getUserDevices(userId) - if (null == devices) { - if (canRetryKeysDownload(userId)) { - deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD - Timber.e("failed to retry the devices of $userId : retry later") - } else { - if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) { - deviceTrackingStatuses[userId] = TRACKING_STATUS_UNREACHABLE_SERVER - Timber.e("failed to retry the devices of $userId : the HS is not available") - } - } - } else { - if (deviceTrackingStatuses.containsKey(userId) && TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == deviceTrackingStatuses[userId]) { - // we didn't get any new invalidations since this download started: - // this user's device list is now up to date. - deviceTrackingStatuses[userId] = TRACKING_STATUS_UP_TO_DATE - Timber.v("Device list for $userId now up to date") - } - // And the response result - usersDevicesInfoMap.setObjects(userId, devices) - } - } - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - - dispatchDeviceChange(userIds) - return usersDevicesInfoMap - } - - /** - * Download the device keys for a list of users and stores the keys in the MXStore. - * It must be called in getEncryptingThreadHandler() thread. - * - * @param userIds The users to fetch. - * @param forceDownload Always download the keys even if cached. - */ - suspend fun downloadKeys(userIds: List?, forceDownload: Boolean): MXUsersDevicesMap { - Timber.v("## CRYPTO | downloadKeys() : forceDownload $forceDownload : $userIds") - // Map from userId -> deviceId -> MXDeviceInfo - val stored = MXUsersDevicesMap() - - // List of user ids we need to download keys for - val downloadUsers = ArrayList() - if (null != userIds) { - if (forceDownload) { - downloadUsers.addAll(userIds) - } else { - for (userId in userIds) { - val status = cryptoStore.getDeviceTrackingStatus(userId, TRACKING_STATUS_NOT_TRACKED) - // downloading keys ->the keys download won't be triggered twice but the callback requires the dedicated keys - // not yet retrieved - if (TRACKING_STATUS_UP_TO_DATE != status && TRACKING_STATUS_UNREACHABLE_SERVER != status) { - downloadUsers.add(userId) - } else { - val devices = cryptoStore.getUserDevices(userId) - // should always be true - if (devices != null) { - stored.setObjects(userId, devices) - } else { - downloadUsers.add(userId) - } - } - } - } - } - return if (downloadUsers.isEmpty()) { - Timber.v("## CRYPTO | downloadKeys() : no new user device") - stored - } else { - Timber.v("## CRYPTO | downloadKeys() : starts") - val t0 = clock.epochMillis() - try { - val result = doKeyDownloadForUsers(downloadUsers) - Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${clock.epochMillis() - t0} ms") - result.also { - it.addEntriesFromMap(stored) - } - } catch (failure: Throwable) { - Timber.w(failure, "## CRYPTO | downloadKeys() : doKeyDownloadForUsers failed after ${clock.epochMillis() - t0} ms") - if (forceDownload) { - throw failure - } else { - stored - } - } - } - } - - /** - * Download the devices keys for a set of users. - * - * @param downloadUsers the user ids list - */ - private suspend fun doKeyDownloadForUsers(downloadUsers: List): MXUsersDevicesMap { - Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}") - // get the user ids which did not already trigger a keys download - val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) } - if (filteredUsers.isEmpty()) { - // trigger nothing - return MXUsersDevicesMap() - } - val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken()) - val response = try { - downloadKeysForUsersTask.execute(params) - } catch (throwable: Throwable) { - Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") - if (throwable is CancellationException) { - // the crypto module is getting closed, so we cannot access the DB anymore - Timber.w("The crypto module is closed, ignoring this error") - } else { - onKeysDownloadFailed(filteredUsers) - } - throw throwable - } - Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") - for (userId in filteredUsers) { - // al devices = - val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } - - Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for $userId : $models") - if (!models.isNullOrEmpty()) { - val workingCopy = models.toMutableMap() - for ((deviceId, deviceInfo) in models) { - // Get the potential previously store device keys for this device - val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(userId, deviceId) - - // in some race conditions (like unit tests) - // the self device must be seen as verified - if (deviceInfo.deviceId == credentials.deviceId && userId == credentials.userId) { - deviceInfo.trustLevel = DeviceTrustLevel(previouslyStoredDeviceKeys?.trustLevel?.crossSigningVerified ?: false, true) - } - // Validate received keys - if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) { - // New device keys are not valid. Do not store them - workingCopy.remove(deviceId) - if (null != previouslyStoredDeviceKeys) { - // But keep old validated ones if any - workingCopy[deviceId] = previouslyStoredDeviceKeys - } - } else if (null != previouslyStoredDeviceKeys) { - // The verified status is not sync'ed with hs. - // This is a client side information, valid only for this client. - // So, transfer its previous value - workingCopy[deviceId]!!.trustLevel = previouslyStoredDeviceKeys.trustLevel - } - } - // Update the store - // Note that devices which aren't in the response will be removed from the stores - cryptoStore.storeUserDevices(userId, workingCopy) - } - - val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { - Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}") - } - val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also { - Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}") - } - val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { - Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") - } - cryptoStore.storeUserCrossSigningKeys( - userId, - masterKey, - selfSigningKey, - userSigningKey - ) - } - - // Update devices trust for these users - // dispatchDeviceChange(downloadUsers) - - return onKeysDownloadSucceed(filteredUsers, response.failures) - } - - /** - * Validate device keys. - * This method must called on getEncryptingThreadHandler() thread. - * - * @param deviceKeys the device keys to validate. - * @param userId the id of the user of the device. - * @param deviceId the id of the device. - * @param previouslyStoredDeviceKeys the device keys we received before for this device - * @return true if succeeds - */ - private fun validateDeviceKeys(deviceKeys: CryptoDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: CryptoDeviceInfo?): Boolean { - if (null == deviceKeys) { - Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys is null from $userId:$deviceId") - return false - } - - if (null == deviceKeys.keys) { - Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.keys is null from $userId:$deviceId") - return false - } - - if (null == deviceKeys.signatures) { - Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.signatures is null from $userId:$deviceId") - return false - } - - // Check that the user_id and device_id in the received deviceKeys are correct - if (deviceKeys.userId != userId) { - Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched user_id ${deviceKeys.userId} from $userId:$deviceId") - return false - } - - if (deviceKeys.deviceId != deviceId) { - Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched device_id ${deviceKeys.deviceId} from $userId:$deviceId") - return false - } - - val signKeyId = "ed25519:" + deviceKeys.deviceId - val signKey = deviceKeys.keys[signKeyId] - - if (null == signKey) { - Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key") - return false - } - - val signatureMap = deviceKeys.signatures[userId] - - if (null == signatureMap) { - Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId") - return false - } - - val signature = signatureMap[signKeyId] - - if (null == signature) { - Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} is not signed") - return false - } - - var isVerified = false - var errorMessage: String? = null - - try { - olmDevice.verifySignature(signKey, deviceKeys.signalableJSONDictionary(), signature) - isVerified = true - } catch (e: Exception) { - errorMessage = e.message - } - - if (!isVerified) { - Timber.e( - "## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" + - deviceKeys.deviceId + " with error " + errorMessage - ) - return false - } - - if (null != previouslyStoredDeviceKeys) { - if (previouslyStoredDeviceKeys.fingerprint() != signKey) { - // This should only happen if the list has been MITMed; we are - // best off sticking with the original keys. - // - // Should we warn the user about it somehow? - Timber.e( - "## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" + - deviceKeys.deviceId + " has changed : " + - previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey - ) - - Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys") - Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}") - - return false - } - } - - return true - } - - /** - * Start device queries for any users who sent us an m.new_device recently - * This method must be called on getEncryptingThreadHandler() thread. - */ - suspend fun refreshOutdatedDeviceLists() { - Timber.v("## CRYPTO | refreshOutdatedDeviceLists()") - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - - val users = deviceTrackingStatuses.keys.filterTo(mutableListOf()) { userId -> - TRACKING_STATUS_PENDING_DOWNLOAD == deviceTrackingStatuses[userId] - } - - if (users.isEmpty()) { - return - } - - // update the statuses - users.associateWithTo(deviceTrackingStatuses) { TRACKING_STATUS_DOWNLOAD_IN_PROGRESS } - - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - runCatching { - doKeyDownloadForUsers(users) - }.fold( - { - Timber.v("## CRYPTO | refreshOutdatedDeviceLists() : done") - }, - { - Timber.e(it, "## CRYPTO | refreshOutdatedDeviceLists() : ERROR updating device keys for users $users") - } - ) - } - - companion object { - - /** - * State transition diagram for DeviceList.deviceTrackingStatus - *
-         *
-         *                                   |
-         *        stopTrackingDeviceList     V
-         *      +---------------------> NOT_TRACKED
-         *      |                            |
-         *      +<--------------------+      | startTrackingDeviceList
-         *      |                     |      V
-         *      |   +-------------> PENDING_DOWNLOAD <--------------------+-+
-         *      |   |                      ^ |                            | |
-         *      |   | restart     download | |  start download            | | invalidateUserDeviceList
-         *      |   | client        failed | |                            | |
-         *      |   |                      | V                            | |
-         *      |   +------------ DOWNLOAD_IN_PROGRESS -------------------+ |
-         *      |                    |       |                              |
-         *      +<-------------------+       |  download successful         |
-         *      ^                            V                              |
-         *      +----------------------- UP_TO_DATE ------------------------+
-         *
-         * 
- */ - - const val TRACKING_STATUS_NOT_TRACKED = -1 - const val TRACKING_STATUS_PENDING_DOWNLOAD = 1 - const val TRACKING_STATUS_DOWNLOAD_IN_PROGRESS = 2 - const val TRACKING_STATUS_UP_TO_DATE = 3 - const val TRACKING_STATUS_UNREACHABLE_SERVER = 4 - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt new file mode 100644 index 0000000000..db5168d31c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EncryptEventContentUseCase.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.internal.util.time.Clock +import timber.log.Timber +import javax.inject.Inject + +private val loggerTag = LoggerTag("EncryptEventContentUseCase", LoggerTag.CRYPTO) + +internal class EncryptEventContentUseCase @Inject constructor(olmMachineProvider: OlmMachineProvider, + private val prepareToEncrypt: PrepareToEncryptUseCase, + private val clock: Clock) { + + private val olmMachine = olmMachineProvider.olmMachine + + suspend operator fun invoke(eventContent: Content, + eventType: String, + roomId: String): MXEncryptEventContentResult { + val t0 = clock.epochMillis() + prepareToEncrypt(roomId, ensureAllMembersAreLoaded = false) + val content = olmMachine.encrypt(roomId, eventType, eventContent) + Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms") + return MXEncryptEventContentResult(content, EventType.ENCRYPTED) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt deleted file mode 100644 index a094189645..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber -import javax.inject.Inject - -private const val SEND_TO_DEVICE_RETRY_COUNT = 3 - -private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO) - -@SessionScope -internal class EventDecryptor @Inject constructor( - private val cryptoCoroutineScope: CoroutineScope, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val clock: Clock, - private val roomDecryptorProvider: RoomDecryptorProvider, - private val messageEncrypter: MessageEncrypter, - private val sendToDeviceTask: SendToDeviceTask, - private val deviceListManager: DeviceListManager, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - private val cryptoStore: IMXCryptoStore -) { - - /** - * Rate limit unwedge attempt, should we persist that? - */ - private val lastNewSessionForcedDates = mutableMapOf() - - data class WedgedDeviceInfo( - val userId: String, - val senderKey: String? - ) - - private val wedgedDevices = mutableListOf() - - /** - * Decrypt an event - * - * @param event the raw event. - * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @return the MXEventDecryptionResult data, or throw in case of error - */ - @Throws(MXCryptoError::class) - suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { - return internalDecryptEvent(event, timeline) - } - - /** - * Decrypt an event asynchronously - * - * @param event the raw event. - * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @param callback the callback to return data or null - */ - fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) { - // is it needed to do that on the crypto scope?? - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - runCatching { - internalDecryptEvent(event, timeline) - }.foldToCallback(callback) - } - } - - /** - * Decrypt an event - * - * @param event the raw event. - * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @return the MXEventDecryptionResult data, or null in case of error - */ - @Throws(MXCryptoError::class) - private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult { - val eventContent = event.content - if (eventContent == null) { - Timber.tag(loggerTag.value).e("decryptEvent : empty event content") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) - } else { - val algorithm = eventContent["algorithm"]?.toString() - val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) - if (alg == null) { - val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) - Timber.tag(loggerTag.value).e("decryptEvent() : $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) - } else { - try { - return alg.decryptEvent(event, timeline) - } catch (mxCryptoError: MXCryptoError) { - Timber.tag(loggerTag.value).d("internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError") - if (algorithm == MXCRYPTO_ALGORITHM_OLM) { - if (mxCryptoError is MXCryptoError.Base && - mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) { - // need to find sending device - val olmContent = event.content.toModel() - if (event.senderId != null && olmContent?.senderKey != null) { - markOlmSessionForUnwedging(event.senderId, olmContent.senderKey) - } else { - Timber.tag(loggerTag.value).d("Can't mark as wedge malformed") - } - } - } - throw mxCryptoError - } - } - } - } - - private fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { - val info = WedgedDeviceInfo(senderId, senderKey) - if (!wedgedDevices.contains(info)) { - Timber.tag(loggerTag.value).d("Marking device from $senderId key:$senderKey as wedged") - wedgedDevices.add(info) - } - } - - // coroutineDispatchers.crypto scope - suspend fun unwedgeDevicesIfNeeded() { - // handle wedged devices - // Some olm decryption have failed and some device are wedged - // we should force start a new session for those - Timber.tag(loggerTag.value).v("Unwedging: ${wedgedDevices.size} are wedged") - // get the one that should be retried according to rate limit - val now = clock.epochMillis() - val toUnwedge = wedgedDevices.filter { - val lastForcedDate = lastNewSessionForcedDates[it] ?: 0 - if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { - Timber.tag(loggerTag.value).d("Unwedging, New session for $it already forced with device at $lastForcedDate") - return@filter false - } - // let's already mark that we tried now - lastNewSessionForcedDates[it] = now - true - } - - if (toUnwedge.isEmpty()) { - Timber.tag(loggerTag.value).v("Nothing to unwedge") - return - } - Timber.tag(loggerTag.value).d("Unwedging, trying to create new session for ${toUnwedge.size} devices") - - toUnwedge - .chunked(100) // safer to chunk if we ever have lots of wedged devices - .forEach { wedgedList -> - val groupedByUserId = wedgedList.groupBy { it.userId } - // lets download keys if needed - withContext(coroutineDispatchers.io) { - deviceListManager.downloadKeys(groupedByUserId.keys.toList(), false) - } - - // find the matching devices - groupedByUserId - .map { groupedByUser -> - val userId = groupedByUser.key - val wedgeSenderKeysForUser = groupedByUser.value.map { it.senderKey } - val knownDevices = cryptoStore.getUserDevices(userId)?.values.orEmpty() - userId to wedgeSenderKeysForUser.mapNotNull { senderKey -> - knownDevices.firstOrNull { it.identityKey() == senderKey } - } - } - .toMap() - .let { deviceList -> - try { - // force creating new outbound session and mark them as most recent to - // be used for next encryption (dummy) - val sessionToUse = ensureOlmSessionsForDevicesAction.handle(deviceList, true) - Timber.tag(loggerTag.value).d("Unwedging, found ${sessionToUse.map.size} to send dummy to") - - // Now send a dummy message on that session so the other side knows about it. - val payloadJson = mapOf( - "type" to EventType.DUMMY - ) - val sendToDeviceMap = MXUsersDevicesMap() - sessionToUse.map.values - .flatMap { it.values } - .map { it.deviceInfo } - .forEach { deviceInfo -> - Timber.tag(loggerTag.value).v("encrypting dummy to ${deviceInfo.deviceId}") - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - sendToDeviceMap.setObject(deviceInfo.userId, deviceInfo.deviceId, encodedPayload) - } - - // now let's send that - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - withContext(coroutineDispatchers.io) { - sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT) - } - } catch (failure: Throwable) { - deviceList.flatMap { it.value }.joinToString { it.shortDebugString() }.let { - Timber.tag(loggerTag.value).e(failure, "## Failed to unwedge devices: $it}") - } - } - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GetRoomUserIdsUseCase.kt similarity index 52% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GetRoomUserIdsUseCase.kt index d5c5e85e41..75d3e8b0bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GetRoomUserIdsUseCase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,14 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.algorithms.olm +package org.matrix.android.sdk.internal.crypto -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.di.UserId import javax.inject.Inject -internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, - @UserId private val userId: String) { +internal class GetRoomUserIdsUseCase @Inject constructor(private val shouldEncryptForInvitedMembers: ShouldEncryptForInvitedMembersUseCase, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider) { - fun create(): MXOlmDecryption { - return MXOlmDecryption( - olmDevice, - userId - ) + operator fun invoke(roomId: String): List { + return cryptoSessionInfoProvider.getRoomUserIds(roomId, shouldEncryptForInvitedMembers(roomId)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt deleted file mode 100644 index 13f2fb861a..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.api.crypto.MXCryptoConfig -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent -import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer -import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber -import java.util.concurrent.Executors -import javax.inject.Inject -import kotlin.system.measureTimeMillis - -private val loggerTag = LoggerTag("IncomingKeyRequestManager", LoggerTag.CRYPTO) - -@SessionScope -internal class IncomingKeyRequestManager @Inject constructor( - private val credentials: Credentials, - private val cryptoStore: IMXCryptoStore, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - private val olmDevice: MXOlmDevice, - private val cryptoConfig: MXCryptoConfig, - private val messageEncrypter: MessageEncrypter, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val sendToDeviceTask: SendToDeviceTask, - private val clock: Clock, -) { - - private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher) - val sequencer = SemaphoreCoroutineSequencer() - - private val incomingRequestBuffer = mutableListOf() - - // the listeners - private val gossipingRequestListeners: MutableSet = HashSet() - - enum class MegolmRequestAction { - Request, Cancel - } - - data class ValidMegolmRequestBody( - val requestId: String, - val requestingUserId: String, - val requestingDeviceId: String, - val roomId: String, - val senderKey: String, - val sessionId: String, - val action: MegolmRequestAction - ) { - fun shortDbgString() = "Request from $requestingUserId|$requestingDeviceId for session $sessionId in room $roomId" - } - - private fun RoomKeyShareRequest.toValidMegolmRequest(senderId: String): ValidMegolmRequestBody? { - val deviceId = requestingDeviceId ?: return null - val body = body ?: return null - val roomId = body.roomId ?: return null - val sessionId = body.sessionId ?: return null - val senderKey = body.senderKey ?: return null - val requestId = this.requestId ?: return null - if (body.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null - val action = when (this.action) { - "request" -> MegolmRequestAction.Request - "request_cancellation" -> MegolmRequestAction.Cancel - else -> null - } ?: return null - return ValidMegolmRequestBody( - requestId = requestId, - requestingUserId = senderId, - requestingDeviceId = deviceId, - roomId = roomId, - senderKey = senderKey, - sessionId = sessionId, - action = action - ) - } - - fun addNewIncomingRequest(senderId: String, request: RoomKeyShareRequest) { - if (!cryptoStore.isKeyGossipingEnabled()) { - Timber.tag(loggerTag.value) - .i("Ignore incoming key request as per crypto config in room ${request.body?.roomId}") - return - } - outgoingRequestScope.launch { - // It is important to handle requests in order - sequencer.post { - val validMegolmRequest = request.toValidMegolmRequest(senderId) ?: return@post Unit.also { - Timber.tag(loggerTag.value).w("Received key request for unknown algorithm ${request.body?.algorithm}") - } - - // is there already one like that? - val existing = incomingRequestBuffer.firstOrNull { it == validMegolmRequest } - if (existing == null) { - when (validMegolmRequest.action) { - MegolmRequestAction.Request -> { - // just add to the buffer - incomingRequestBuffer.add(validMegolmRequest) - } - MegolmRequestAction.Cancel -> { - // ignore, we can't cancel as it's not known (probably already processed) - // still notify app layer if it was passed up previously - IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq -> - outgoingRequestScope.launch(coroutineDispatchers.computation) { - val listenersCopy = synchronized(gossipingRequestListeners) { - gossipingRequestListeners.toList() - } - listenersCopy.onEach { - tryOrNull { - withContext(coroutineDispatchers.main) { - it.onRequestCancelled(iReq) - } - } - } - } - } - } - } - } else { - when (validMegolmRequest.action) { - MegolmRequestAction.Request -> { - // it's already in buffer, nop keep existing - } - MegolmRequestAction.Cancel -> { - // discard the request in buffer - incomingRequestBuffer.remove(existing) - outgoingRequestScope.launch(coroutineDispatchers.computation) { - val listenersCopy = synchronized(gossipingRequestListeners) { - gossipingRequestListeners.toList() - } - listenersCopy.onEach { - IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq -> - withContext(coroutineDispatchers.main) { - tryOrNull { it.onRequestCancelled(iReq) } - } - } - } - } - } - } - } - } - } - } - - fun processIncomingRequests() { - outgoingRequestScope.launch { - sequencer.post { - measureTimeMillis { - Timber.tag(loggerTag.value).v("processIncomingKeyRequests : ${incomingRequestBuffer.size} request to process") - incomingRequestBuffer.forEach { - // should not happen, we only store requests - if (it.action != MegolmRequestAction.Request) return@forEach - try { - handleIncomingRequest(it) - } catch (failure: Throwable) { - // ignore and continue, should not happen - Timber.tag(loggerTag.value).w(failure, "processIncomingKeyRequests : failed to process request $it") - } - } - incomingRequestBuffer.clear() - }.let { duration -> - Timber.tag(loggerTag.value).v("Finish processing incoming key request in $duration ms") - } - } - } - } - - private suspend fun handleIncomingRequest(request: ValidMegolmRequestBody) { - // We don't want to download keys, if we don't know the device yet we won't share any how? - val requestingDevice = - cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId) - ?: return Unit.also { - Timber.tag(loggerTag.value).d("Ignoring key request: ${request.shortDbgString()}") - } - - cryptoStore.saveIncomingKeyRequestAuditTrail( - request.requestId, - request.roomId, - request.sessionId, - request.senderKey, - MXCRYPTO_ALGORITHM_MEGOLM, - request.requestingUserId, - request.requestingDeviceId - ) - - val roomAlgorithm = // withContext(coroutineDispatchers.crypto) { - cryptoStore.getRoomAlgorithm(request.roomId) -// } - if (roomAlgorithm != MXCRYPTO_ALGORITHM_MEGOLM) { - // strange we received a request for a room that is not encrypted - // maybe a broken state? - Timber.tag(loggerTag.value).w("Received a key request in a room with unsupported alg:$roomAlgorithm , req:${request.shortDbgString()}") - return - } - - // Is it for one of our sessions? - if (request.requestingUserId == credentials.userId) { - Timber.tag(loggerTag.value).v("handling request from own user: megolm session ${request.sessionId}") - - if (request.requestingDeviceId == credentials.deviceId) { - // ignore it's a remote echo - return - } - // If it's verified we share from the early index we know - // if not we check if it was originaly shared or not - if (requestingDevice.isVerified) { - // we share from the earliest known chain index - shareMegolmKey(request, requestingDevice, null) - } else { - shareIfItWasPreviouslyShared(request, requestingDevice) - } - } else { - if (cryptoConfig.limitRoomKeyRequestsToMyDevices) { - Timber.tag(loggerTag.value).v("Ignore request from other user as per crypto config: ${request.shortDbgString()}") - return - } - Timber.tag(loggerTag.value).v("handling request from other user: megolm session ${request.sessionId}") - if (requestingDevice.isBlocked) { - // it's blocked, so send a withheld code - sendWithheldForRequest(request, WithHeldCode.BLACKLISTED) - } else { - shareIfItWasPreviouslyShared(request, requestingDevice) - } - } - } - - private suspend fun shareIfItWasPreviouslyShared(request: ValidMegolmRequestBody, requestingDevice: CryptoDeviceInfo) { - // we don't reshare unless it was previously shared with - val wasSessionSharedWithUser = withContext(coroutineDispatchers.crypto) { - cryptoStore.getSharedSessionInfo(request.roomId, request.sessionId, requestingDevice) - } - if (wasSessionSharedWithUser.found && wasSessionSharedWithUser.chainIndex != null) { - // we share from the index it was previously shared with - shareMegolmKey(request, requestingDevice, wasSessionSharedWithUser.chainIndex.toLong()) - } else { - val isOwnDevice = requestingDevice.userId == credentials.userId - sendWithheldForRequest(request, if (isOwnDevice) WithHeldCode.UNVERIFIED else WithHeldCode.UNAUTHORISED) - // if it's our device we could delegate to the app layer to decide - if (isOwnDevice) { - outgoingRequestScope.launch(coroutineDispatchers.computation) { - val listenersCopy = synchronized(gossipingRequestListeners) { - gossipingRequestListeners.toList() - } - val iReq = IncomingRoomKeyRequest( - userId = requestingDevice.userId, - deviceId = requestingDevice.deviceId, - requestId = request.requestId, - requestBody = RoomKeyRequestBody( - algorithm = MXCRYPTO_ALGORITHM_MEGOLM, - senderKey = request.senderKey, - sessionId = request.sessionId, - roomId = request.roomId - ), - localCreationTimestamp = clock.epochMillis() - ) - listenersCopy.onEach { - withContext(coroutineDispatchers.main) { - tryOrNull { it.onRoomKeyRequest(iReq) } - } - } - } - } - } - } - - private suspend fun sendWithheldForRequest(request: ValidMegolmRequestBody, code: WithHeldCode) { - Timber.tag(loggerTag.value) - .w("Send withheld $code for req: ${request.shortDbgString()}") - val withHeldContent = RoomKeyWithHeldContent( - roomId = request.roomId, - senderKey = request.senderKey, - algorithm = MXCRYPTO_ALGORITHM_MEGOLM, - sessionId = request.sessionId, - codeString = code.value, - fromDevice = credentials.deviceId - ) - - val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD, - MXUsersDevicesMap().apply { - setObject(request.requestingUserId, request.requestingDeviceId, withHeldContent) - } - ) - try { - withContext(coroutineDispatchers.io) { - sendToDeviceTask.execute(params) - Timber.tag(loggerTag.value) - .d("Send withheld $code req: ${request.shortDbgString()}") - } - - cryptoStore.saveWithheldAuditTrail( - roomId = request.roomId, - sessionId = request.sessionId, - senderKey = request.senderKey, - algorithm = MXCRYPTO_ALGORITHM_MEGOLM, - code = code, - userId = request.requestingUserId, - deviceId = request.requestingDeviceId - ) - } catch (failure: Throwable) { - // Ignore it's not that important? - // do we want to fallback to a worker? - Timber.tag(loggerTag.value) - .w("Failed to send withheld $code req: ${request.shortDbgString()} reason:${failure.localizedMessage}") - } - } - - suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) { - request.requestId ?: return - request.deviceId ?: return - request.userId ?: return - request.requestBody?.roomId ?: return - request.requestBody.senderKey ?: return - request.requestBody.sessionId ?: return - val validReq = ValidMegolmRequestBody( - requestId = request.requestId, - requestingDeviceId = request.deviceId, - requestingUserId = request.userId, - roomId = request.requestBody.roomId, - senderKey = request.requestBody.senderKey, - sessionId = request.requestBody.sessionId, - action = MegolmRequestAction.Request - ) - val requestingDevice = - cryptoStore.getUserDevice(request.userId, request.deviceId) - ?: return Unit.also { - Timber.tag(loggerTag.value).d("Ignoring key request: ${validReq.shortDbgString()}") - } - - shareMegolmKey(validReq, requestingDevice, null) - } - - private suspend fun shareMegolmKey(validRequest: ValidMegolmRequestBody, - requestingDevice: CryptoDeviceInfo, - chainIndex: Long?): Boolean { - Timber.tag(loggerTag.value) - .d("try to re-share Megolm Key at index $chainIndex for ${validRequest.shortDbgString()}") - - val devicesByUser = mapOf(validRequest.requestingUserId to listOf(requestingDevice)) - val usersDeviceMap = try { - ensureOlmSessionsForDevicesAction.handle(devicesByUser) - } catch (failure: Throwable) { - Timber.tag(loggerTag.value) - .w("Failed to establish olm session") - sendWithheldForRequest(validRequest, WithHeldCode.NO_OLM) - return false - } - - val olmSessionResult = usersDeviceMap.getObject(requestingDevice.userId, requestingDevice.deviceId) - if (olmSessionResult?.sessionId == null) { - Timber.tag(loggerTag.value) - .w("reshareKey: no session with this device, probably because there were no one-time keys") - sendWithheldForRequest(validRequest, WithHeldCode.NO_OLM) - return false - } - val sessionHolder = try { - olmDevice.getInboundGroupSession(validRequest.sessionId, validRequest.senderKey, validRequest.roomId) - } catch (failure: Throwable) { - Timber.tag(loggerTag.value) - .e(failure, "shareKeysWithDevice: failed to get session ${validRequest.requestingUserId}") - // It's unavailable - sendWithheldForRequest(validRequest, WithHeldCode.UNAVAILABLE) - return false - } - - val export = sessionHolder.mutex.withLock { - sessionHolder.wrapper.exportKeys(chainIndex) - } ?: return false.also { - Timber.tag(loggerTag.value) - .e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}") - } - - val payloadJson = mapOf( - "type" to EventType.FORWARDED_ROOM_KEY, - "content" to export - ) - - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(requestingDevice)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(requestingDevice.userId, requestingDevice.deviceId, encodedPayload) - Timber.tag(loggerTag.value).d("reshareKey() : try sending session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - return try { - sendToDeviceTask.execute(sendToDeviceParams) - Timber.tag(loggerTag.value) - .i("successfully re-shared session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}") - cryptoStore.saveForwardKeyAuditTrail( - validRequest.roomId, - validRequest.sessionId, - validRequest.senderKey, - MXCRYPTO_ALGORITHM_MEGOLM, - requestingDevice.userId, - requestingDevice.deviceId, - chainIndex - ) - true - } catch (failure: Throwable) { - Timber.tag(loggerTag.value) - .e(failure, "fail to re-share session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}") - false - } - } - - fun addRoomKeysRequestListener(listener: GossipingRequestListener) { - synchronized(gossipingRequestListeners) { - gossipingRequestListeners.add(listener) - } - } - - fun removeRoomKeysRequestListener(listener: GossipingRequestListener) { - synchronized(gossipingRequestListeners) { - gossipingRequestListeners.remove(listener) - } - } - - fun close() { - try { - outgoingRequestScope.cancel("User Terminate") - incomingRequestBuffer.clear() - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).w("Failed to shutDown request manager") - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt deleted file mode 100755 index 82a8199bee..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ /dev/null @@ -1,922 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult -import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE -import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo -import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 -import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.convertFromUTF8 -import org.matrix.android.sdk.internal.util.convertToUTF8 -import org.matrix.android.sdk.internal.util.time.Clock -import org.matrix.olm.OlmAccount -import org.matrix.olm.OlmException -import org.matrix.olm.OlmMessage -import org.matrix.olm.OlmOutboundGroupSession -import org.matrix.olm.OlmSession -import org.matrix.olm.OlmUtility -import timber.log.Timber -import javax.inject.Inject - -private val loggerTag = LoggerTag("MXOlmDevice", LoggerTag.CRYPTO) - -// The libolm wrapper. -@Deprecated("rust") -@SessionScope -internal class MXOlmDevice @Inject constructor( - /** - * The store where crypto data is saved. - */ - private val store: IMXCryptoStore, - private val olmSessionStore: OlmSessionStore, - private val inboundGroupSessionStore: InboundGroupSessionStore, - private val clock: Clock, -) { - - val mutex = Mutex() - - /** - * @return the Curve25519 key for the account. - */ - var deviceCurve25519Key: String? = null - private set - - /** - * @return the Ed25519 key for the account. - */ - var deviceEd25519Key: String? = null - private set - - // The OLM lib utility instance. - private var olmUtility: OlmUtility? = null - - private data class GroupSessionCacheItem( - val groupId: String, - val groupSession: OlmOutboundGroupSession - ) - - // The outbound group session. - // Caches active outbound session to avoid to sync with DB before read - // The key is the session id, the value the . - private val outboundGroupSessionCache: MutableMap = HashMap() - - // Store a set of decrypted message indexes for each group session. - // This partially mitigates a replay attack where a MITM resends a group - // message into the room. - // - // The Matrix SDK exposes events through MXEventTimelines. A developer can open several - // timelines from a same room so that a message can be decrypted several times but from - // a different timeline. - // So, store these message indexes per timeline id. - // - // The first level keys are timeline ids. - // The second level keys are strings of form "||" - private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap() - - init { - // Retrieve the account from the store - try { - store.getOrCreateOlmAccount() - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "MXOlmDevice : cannot initialize olmAccount") - } - - try { - olmUtility = OlmUtility() - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : OlmUtility failed with error") - olmUtility = null - } - - try { - deviceCurve25519Key = store.doWithOlmAccount { it.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error") - } - - try { - deviceEd25519Key = store.doWithOlmAccount { it.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error") - } - } - - /** - * @return The current (unused, unpublished) one-time keys for this account. - */ - fun getOneTimeKeys(): Map>? { - try { - return store.doWithOlmAccount { it.oneTimeKeys() } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## getOneTimeKeys() : failed") - } - - return null - } - - /** - * @return The maximum number of one-time keys the olm account can store. - */ - fun getMaxNumberOfOneTimeKeys(): Long { - return store.doWithOlmAccount { it.maxOneTimeKeys() } - } - - /** - * Returns an unpublished fallback key - * A call to markKeysAsPublished will mark it as published and this - * call will return null (until a call to generateFallbackKey is made) - */ - fun getFallbackKey(): MutableMap>? { - try { - return store.doWithOlmAccount { it.fallbackKey() } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e("## getFallbackKey() : failed") - } - return null - } - - /** - * Generates a new fallback key if there is not already - * an unpublished one. - * @return true if a new key was generated - */ - fun generateFallbackKeyIfNeeded(): Boolean { - try { - if (!hasUnpublishedFallbackKey()) { - store.doWithOlmAccount { - it.generateFallbackKey() - store.saveOlmAccount() - } - return true - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e("## generateFallbackKey() : failed") - } - return false - } - - internal fun hasUnpublishedFallbackKey(): Boolean { - return getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty().isNotEmpty() - } - - fun forgetFallbackKey() { - try { - store.doWithOlmAccount { - it.forgetFallbackKey() - store.saveOlmAccount() - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e("## forgetFallbackKey() : failed") - } - } - - /** - * Release the instance - */ - fun release() { - olmUtility?.releaseUtility() - outboundGroupSessionCache.values.forEach { - it.groupSession.releaseSession() - } - outboundGroupSessionCache.clear() - inboundGroupSessionStore.clear() - olmSessionStore.clear() - } - - /** - * Signs a message with the ed25519 key for this account. - * - * @param message the message to be signed. - * @return the base64-encoded signature. - */ - fun signMessage(message: String): String? { - try { - return store.doWithOlmAccount { it.signMessage(message) } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## signMessage() : failed") - } - - return null - } - - /** - * Marks all of the one-time keys as published. - */ - fun markKeysAsPublished() { - try { - store.doWithOlmAccount { - it.markOneTimeKeysAsPublished() - store.saveOlmAccount() - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## markKeysAsPublished() : failed") - } - } - - /** - * Generate some new one-time keys - * - * @param numKeys number of keys to generate - */ - fun generateOneTimeKeys(numKeys: Int) { - try { - store.doWithOlmAccount { - it.generateOneTimeKeys(numKeys) - store.saveOlmAccount() - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## generateOneTimeKeys() : failed") - } - } - - /** - * Generate a new outbound session. - * The new session will be stored in the MXStore. - * - * @param theirIdentityKey the remote user's Curve25519 identity key - * @param theirOneTimeKey the remote user's one-time Curve25519 key - * @return the session id for the outbound session. - */ - fun createOutboundSession(theirIdentityKey: String, theirOneTimeKey: String): String? { - Timber.tag(loggerTag.value).d("## createOutboundSession() ; theirIdentityKey $theirIdentityKey theirOneTimeKey $theirOneTimeKey") - var olmSession: OlmSession? = null - - try { - olmSession = OlmSession() - store.doWithOlmAccount { olmAccount -> - olmSession.initOutboundSession(olmAccount, theirIdentityKey, theirOneTimeKey) - } - - val olmSessionWrapper = OlmSessionWrapper(olmSession, 0) - - // Pretend we've received a message at this point, otherwise - // if we try to send a message to the device, it won't use - // this session - olmSessionWrapper.onMessageReceived(clock.epochMillis()) - - olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey) - - val sessionIdentifier = olmSession.sessionIdentifier() - - Timber.tag(loggerTag.value).v("## createOutboundSession() ; olmSession.sessionIdentifier: $sessionIdentifier") - return sessionIdentifier - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## createOutboundSession() failed") - - olmSession?.releaseSession() - } - - return null - } - - /** - * Generate a new inbound session, given an incoming message. - * - * @param theirDeviceIdentityKey the remote user's Curve25519 identity key. - * @param messageType the message_type field from the received message (must be 0). - * @param ciphertext base64-encoded body from the received message. - * @return {{payload: string, session_id: string}} decrypted payload, and session id of new session. - */ - fun createInboundSession(theirDeviceIdentityKey: String, messageType: Int, ciphertext: String): Map? { - Timber.tag(loggerTag.value).d("## createInboundSession() : theirIdentityKey: $theirDeviceIdentityKey") - - var olmSession: OlmSession? = null - - try { - try { - olmSession = OlmSession() - store.doWithOlmAccount { olmAccount -> - olmSession.initInboundSessionFrom(olmAccount, theirDeviceIdentityKey, ciphertext) - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## createInboundSession() : the session creation failed") - return null - } - - Timber.tag(loggerTag.value).v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}") - - try { - store.doWithOlmAccount { olmAccount -> - olmAccount.removeOneTimeKeys(olmSession) - store.saveOlmAccount() - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## createInboundSession() : removeOneTimeKeys failed") - } - - val olmMessage = OlmMessage() - olmMessage.mCipherText = ciphertext - olmMessage.mType = messageType.toLong() - - var payloadString: String? = null - - try { - payloadString = olmSession.decryptMessage(olmMessage) - - val olmSessionWrapper = OlmSessionWrapper(olmSession, 0) - // This counts as a received message: set last received message time to now - olmSessionWrapper.onMessageReceived(clock.epochMillis()) - - olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## createInboundSession() : decryptMessage failed") - } - - val res = HashMap() - - if (!payloadString.isNullOrEmpty()) { - res["payload"] = payloadString - } - - val sessionIdentifier = olmSession.sessionIdentifier() - - if (!sessionIdentifier.isNullOrEmpty()) { - res["session_id"] = sessionIdentifier - } - - return res - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## createInboundSession() : OlmSession creation failed") - - olmSession?.releaseSession() - } - - return null - } - - /** - * Get a list of known session IDs for the given device. - * - * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @return a list of known session ids for the device. - */ - fun getSessionIds(theirDeviceIdentityKey: String): List { - return olmSessionStore.getDeviceSessionIds(theirDeviceIdentityKey) - } - - /** - * Get the right olm session id for encrypting messages to the given identity key. - * - * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @return the session id, or null if no established session. - */ - fun getSessionId(theirDeviceIdentityKey: String): String? { - return olmSessionStore.getLastUsedSessionId(theirDeviceIdentityKey) - } - - /** - * Encrypt an outgoing message using an existing session. - * - * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @param sessionId the id of the active session - * @param payloadString the payload to be encrypted and sent - * @return the cipher text - */ - suspend fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map? { - val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId) - - if (olmSessionWrapper != null) { - try { - Timber.tag(loggerTag.value).v("## encryptMessage() : olmSession.sessionIdentifier: $sessionId") - - val olmMessage = olmSessionWrapper.mutex.withLock { - olmSessionWrapper.olmSession.encryptMessage(payloadString) - } - return mapOf( - "body" to olmMessage.mCipherText, - "type" to olmMessage.mType, - ).also { - olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey) - } - } catch (e: Throwable) { - Timber.tag(loggerTag.value).e(e, "## encryptMessage() : failed to encrypt olm with device|session:$theirDeviceIdentityKey|$sessionId") - return null - } - } else { - Timber.tag(loggerTag.value).e("## encryptMessage() : Failed to encrypt unknown session $sessionId") - return null - } - } - - /** - * Decrypt an incoming message using an existing session. - * - * @param ciphertext the base64-encoded body from the received message. - * @param messageType message_type field from the received message. - * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @param sessionId the id of the active session. - * @return the decrypted payload. - */ - @kotlin.jvm.Throws - suspend fun decryptMessage(ciphertext: String, messageType: Int, sessionId: String, theirDeviceIdentityKey: String): String? { - var payloadString: String? = null - - val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId) - - if (null != olmSessionWrapper) { - val olmMessage = OlmMessage() - olmMessage.mCipherText = ciphertext - olmMessage.mType = messageType.toLong() - - payloadString = - olmSessionWrapper.mutex.withLock { - olmSessionWrapper.olmSession.decryptMessage(olmMessage).also { - olmSessionWrapper.onMessageReceived(clock.epochMillis()) - } - } - olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey) - } - - return payloadString - } - - /** - * Determine if an incoming messages is a prekey message matching an existing session. - * - * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. - * @param sessionId the id of the active session. - * @param messageType message_type field from the received message. - * @param ciphertext the base64-encoded body from the received message. - * @return YES if the received message is a prekey message which matchesthe given session. - */ - fun matchesSession(theirDeviceIdentityKey: String, sessionId: String, messageType: Int, ciphertext: String): Boolean { - if (messageType != 0) { - return false - } - - val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId) - return null != olmSessionWrapper && olmSessionWrapper.olmSession.matchesInboundSession(ciphertext) - } - - // Outbound group session - - /** - * Generate a new outbound group session. - * - * @return the session id for the outbound session. - */ - fun createOutboundGroupSessionForRoom(roomId: String): String? { - var session: OlmOutboundGroupSession? = null - try { - session = OlmOutboundGroupSession() - outboundGroupSessionCache[session.sessionIdentifier()] = GroupSessionCacheItem(roomId, session) - store.storeCurrentOutboundGroupSessionForRoom(roomId, session) - return session.sessionIdentifier() - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "createOutboundGroupSession") - - session?.releaseSession() - } - - return null - } - - fun storeOutboundGroupSessionForRoom(roomId: String, sessionId: String) { - outboundGroupSessionCache[sessionId]?.let { - store.storeCurrentOutboundGroupSessionForRoom(roomId, it.groupSession) - } - } - - fun restoreOutboundGroupSessionForRoom(roomId: String): MXOutboundSessionInfo? { - val restoredOutboundGroupSession = store.getCurrentOutboundGroupSessionForRoom(roomId) - if (restoredOutboundGroupSession != null) { - val sessionId = restoredOutboundGroupSession.outboundGroupSession.sessionIdentifier() - // cache it - outboundGroupSessionCache[sessionId] = GroupSessionCacheItem(roomId, restoredOutboundGroupSession.outboundGroupSession) - - return MXOutboundSessionInfo( - sessionId = sessionId, - sharedWithHelper = SharedWithHelper(roomId, sessionId, store), - clock, - restoredOutboundGroupSession.creationTime - ) - } - return null - } - - fun discardOutboundGroupSessionForRoom(roomId: String) { - val toDiscard = outboundGroupSessionCache.filter { - it.value.groupId == roomId - } - toDiscard.forEach { (sessionId, cacheItem) -> - cacheItem.groupSession.releaseSession() - outboundGroupSessionCache.remove(sessionId) - } - store.storeCurrentOutboundGroupSessionForRoom(roomId, null) - } - - /** - * Get the current session key of an outbound group session. - * - * @param sessionId the id of the outbound group session. - * @return the base64-encoded secret key. - */ - fun getSessionKey(sessionId: String): String? { - if (sessionId.isNotEmpty()) { - try { - return outboundGroupSessionCache[sessionId]!!.groupSession.sessionKey() - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## getSessionKey() : failed") - } - } - return null - } - - /** - * Get the current message index of an outbound group session. - * - * @param sessionId the id of the outbound group session. - * @return the current chain index. - */ - fun getMessageIndex(sessionId: String): Int { - return if (sessionId.isNotEmpty()) { - outboundGroupSessionCache[sessionId]!!.groupSession.messageIndex() - } else 0 - } - - /** - * Encrypt an outgoing message with an outbound group session. - * - * @param sessionId the id of the outbound group session. - * @param payloadString the payload to be encrypted and sent. - * @return ciphertext - */ - fun encryptGroupMessage(sessionId: String, payloadString: String): String? { - if (sessionId.isNotEmpty() && payloadString.isNotEmpty()) { - try { - return outboundGroupSessionCache[sessionId]!!.groupSession.encryptMessage(payloadString) - } catch (e: Throwable) { - Timber.tag(loggerTag.value).e(e, "## encryptGroupMessage() : failed") - } - } - return null - } - - // Inbound group session - - sealed interface AddSessionResult { - data class Imported(val ratchetIndex: Int) : AddSessionResult - abstract class Failure : AddSessionResult - object NotImported : Failure() - data class NotImportedHigherIndex(val newIndex: Int) : Failure() - } - - /** - * Add an inbound group session to the session store. - * - * @param sessionId the session identifier. - * @param sessionKey base64-encoded secret key. - * @param roomId the id of the room in which this session will be used. - * @param senderKey the base64-encoded curve25519 key of the sender. - * @param forwardingCurve25519KeyChain Devices involved in forwarding this session to us. - * @param keysClaimed Other keys the sender claims. - * @param exportFormat true if the megolm keys are in export format - * @return true if the operation succeeds. - */ - fun addInboundGroupSession(sessionId: String, - sessionKey: String, - roomId: String, - senderKey: String, - forwardingCurve25519KeyChain: List, - keysClaimed: Map, - exportFormat: Boolean): AddSessionResult { - val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat) - val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } - val existingSession = existingSessionHolder?.wrapper - // If we have an existing one we should check if the new one is not better - if (existingSession != null) { - Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session") - try { - val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also { - // This is quite unexpected, could throw if native was released? - Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session") - candidateSession.olmInboundGroupSession?.releaseSession() - // Probably should discard it? - } - val newKnownFirstIndex = candidateSession.firstKnownIndex - // If our existing session is better we keep it - if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { - Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId") - candidateSession.olmInboundGroupSession?.releaseSession() - return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt()) - } - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}") - candidateSession.olmInboundGroupSession?.releaseSession() - return AddSessionResult.NotImported - } - } - - Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId") - - // sanity check on the new session - val candidateOlmInboundSession = candidateSession.olmInboundGroupSession - if (null == candidateOlmInboundSession) { - Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session ") - return AddSessionResult.NotImported - } - - try { - if (candidateOlmInboundSession.sessionIdentifier() != sessionId) { - Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey") - candidateOlmInboundSession.releaseSession() - return AddSessionResult.NotImported - } - } catch (e: Throwable) { - candidateOlmInboundSession.releaseSession() - Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed") - return AddSessionResult.NotImported - } - - candidateSession.senderKey = senderKey - candidateSession.roomId = roomId - candidateSession.keysClaimed = keysClaimed - candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain - - if (existingSession != null) { - inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey) - } else { - inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey) - } - - return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0) - } - - /** - * Import an inbound group sessions to the session store. - * - * @param megolmSessionsData the megolm sessions data - * @return the successfully imported sessions. - */ - fun importInboundGroupSessions(megolmSessionsData: List): List { - val sessions = ArrayList(megolmSessionsData.size) - - for (megolmSessionData in megolmSessionsData) { - val sessionId = megolmSessionData.sessionId ?: continue - val senderKey = megolmSessionData.senderKey ?: continue - val roomId = megolmSessionData.roomId - - var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null - - try { - candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - } - - // sanity check - if (candidateSessionToImport?.olmInboundGroupSession == null) { - Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session") - continue - } - - val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession - try { - if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) { - Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey") - candidateOlmInboundGroupSession?.releaseSession() - continue - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed") - candidateOlmInboundGroupSession?.releaseSession() - continue - } - - val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } - val existingSession = existingSessionHolder?.wrapper - - if (existingSession == null) { - // Session does not already exist, add it - Timber.tag(loggerTag.value).d("## importInboundGroupSession() : importing new megolm session $senderKey/$sessionId") - sessions.add(candidateSessionToImport) - } else { - Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex } - val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex } - - if (existingFirstKnown == null || candidateFirstKnownIndex == null) { - // should not happen? - candidateSessionToImport.olmInboundGroupSession?.releaseSession() - Timber.tag(loggerTag.value) - .w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex") - } else { - if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) { - // Ignore this, keep existing - candidateOlmInboundGroupSession.releaseSession() - } else { - // update cache with better session - inboundGroupSessionStore.replaceGroupSession( - existingSessionHolder, - InboundGroupSessionHolder(candidateSessionToImport), - sessionId, - senderKey - ) - sessions.add(candidateSessionToImport) - } - } - } - } - - store.storeInboundGroupSessions(sessions) - - return sessions - } - - /** - * Decrypt a received message with an inbound group session. - * - * @param body the base64-encoded body of the encrypted message. - * @param roomId the room in which the message was received. - * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @param sessionId the session identifier. - * @param senderKey the base64-encoded curve25519 key of the sender. - * @return the decrypting result. Nil if the sessionId is unknown. - */ - @Throws(MXCryptoError::class) - suspend fun decryptGroupMessage(body: String, - roomId: String, - timeline: String?, - sessionId: String, - senderKey: String): OlmDecryptionResult { - val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId) - val wrapper = sessionHolder.wrapper - val inboundGroupSession = wrapper.olmInboundGroupSession - ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null") - // Check that the room id matches the original one for the session. This stops - // the HS pretending a message was targeting a different room. - if (roomId == wrapper.roomId) { - val decryptResult = try { - sessionHolder.mutex.withLock { - inboundGroupSession.decryptMessage(body) - } - } catch (e: OlmException) { - Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") - throw MXCryptoError.OlmError(e) - } - - if (timeline?.isNotBlank() == true) { - val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() } - - val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex - - if (timelineSet.contains(messageIndexKey)) { - val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) - Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason) - } - - timelineSet.add(messageIndexKey) - } - - inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) - val payload = try { - val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) - val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) - adapter.fromJson(payloadString) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) - } - - return OlmDecryptionResult( - payload, - wrapper.keysClaimed, - senderKey, - wrapper.forwardingCurve25519KeyChain - ) - } else { - val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId) - Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason) - } - } - - /** - * Reset replay attack data for the given timeline. - * - * @param timeline the id of the timeline. - */ - fun resetReplayAttackCheckInTimeline(timeline: String?) { - if (null != timeline) { - inboundGroupSessionMessageIndexes.remove(timeline) - } - } - -// Utilities - - /** - * Verify an ed25519 signature on a JSON object. - * - * @param key the ed25519 key. - * @param jsonDictionary the JSON object which was signed. - * @param signature the base64-encoded signature to be checked. - * @throws Exception the exception - */ - @Throws(Exception::class) - fun verifySignature(key: String, jsonDictionary: Map, signature: String) { - // Check signature on the canonical version of the JSON - olmUtility!!.verifyEd25519Signature(signature, key, JsonCanonicalizer.getCanonicalJson(Map::class.java, jsonDictionary)) - } - - /** - * Calculate the SHA-256 hash of the input and encodes it as base64. - * - * @param message the message to hash. - * @return the base64-encoded hash value. - */ - fun sha256(message: String): String { - return olmUtility!!.sha256(convertToUTF8(message)) - } - - /** - * Search an OlmSession - * - * @param theirDeviceIdentityKey the device key - * @param sessionId the session Id - * @return the olm session - */ - private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? { - // sanity check - return if (theirDeviceIdentityKey.isEmpty() || sessionId.isEmpty()) null else { - olmSessionStore.getDeviceSession(sessionId, theirDeviceIdentityKey) - } - } - - /** - * Extract an InboundGroupSession from the session store and do some check. - * inboundGroupSessionWithIdError describes the failure reason. - * - * @param roomId the room where the session is used. - * @param sessionId the session identifier. - * @param senderKey the base64-encoded curve25519 key of the sender. - * @return the inbound group session. - */ - fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder { - if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) { - throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON) - } - - val holder = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey) - val session = holder?.wrapper - - if (session != null) { - // Check that the room id matches the original one for the session. This stops - // the HS pretending a message was targeting a different room. - if (roomId != session.roomId) { - val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) - Timber.tag(loggerTag.value).e("## getInboundGroupSession() : $errorDescription") - throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription) - } else { - return holder - } - } else { - Timber.tag(loggerTag.value).w("## getInboundGroupSession() : UISI $sessionId") - throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON) - } - } - - /** - * Determine if we have the keys for a given megolm session. - * - * @param roomId room in which the message was received - * @param senderKey base64-encoded curve25519 key of the sender - * @param sessionId session identifier - * @return true if the unbound session keys are known. - */ - fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean { - return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess - } - - @VisibleForTesting - fun clearOlmSessionCache() { - olmSessionStore.clear() - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt index a6b215ae6b..b73bb96a7d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionImportManager.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt deleted file mode 100644 index 9798d21576..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.session.SessionScope -import javax.inject.Inject - -@SessionScope -internal class MyDeviceInfoHolder @Inject constructor( - // The credentials, - credentials: Credentials, - // the crypto store - cryptoStore: IMXCryptoStore, - // Olm device - olmDevice: MXOlmDevice -) { - // Our device keys - /** - * my device info - */ - val myDevice: CryptoDeviceInfo - - init { - - val keys = HashMap() - -// TODO it's a bit strange, why not load from DB? - if (!olmDevice.deviceEd25519Key.isNullOrEmpty()) { - keys["ed25519:" + credentials.deviceId] = olmDevice.deviceEd25519Key!! - } - - if (!olmDevice.deviceCurve25519Key.isNullOrEmpty()) { - keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!! - } - -// myDevice.keys = keys -// -// myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms() - - // TODO hwo to really check cross signed status? - // - val crossSigned = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.trustLevel?.locallyVerified ?: false -// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true) - - myDevice = CryptoDeviceInfo( - credentials.deviceId!!, - credentials.userId, - keys = keys, - algorithms = MXCryptoAlgorithms.supportedAlgorithms(), - trustLevel = DeviceTrustLevel(crossSigned, true) - ) - - // Add our own deviceinfo to the store - val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId) - - val myDevices = endToEndDevicesForUser.orEmpty().toMutableMap() - - myDevices[myDevice.deviceId] = myDevice - - cryptoStore.storeUserDevices(credentials.userId, myDevices) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt deleted file mode 100644 index 68dd17324b..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import org.matrix.android.sdk.api.auth.data.Credentials -import javax.inject.Inject - -internal class ObjectSigner @Inject constructor(private val credentials: Credentials, - private val olmDevice: MXOlmDevice) { - - /** - * Sign Object - * - * Example: - *
-     *     {
-     *         "[MY_USER_ID]": {
-     *             "ed25519:[MY_DEVICE_ID]": "sign(str)"
-     *         }
-     *     }
-     * 
- * - * @param strToSign the String to sign and to include in the Map - * @return a Map (see example) - */ - fun signObject(strToSign: String): Map> { - val result = HashMap>() - - val content = HashMap() - - content["ed25519:" + credentials.deviceId] = olmDevice.signMessage(strToSign) - ?: "" // null reported by rageshake if happens during logout - - result[credentials.userId] = content - - return result - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt deleted file mode 100644 index 8143e36892..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import android.content.Context -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.internal.crypto.model.MXKey -import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse -import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.time.Clock -import org.matrix.olm.OlmAccount -import timber.log.Timber -import javax.inject.Inject -import kotlin.math.floor -import kotlin.math.min - -// The spec recommend a 5mn delay, but due to federation -// or server downtime we give it a bit more time (1 hour) -private const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L - -@SessionScope -internal class OneTimeKeysUploader @Inject constructor( - private val olmDevice: MXOlmDevice, - private val objectSigner: ObjectSigner, - private val uploadKeysTask: UploadKeysTask, - private val clock: Clock, - context: Context -) { - // tell if there is a OTK check in progress - private var oneTimeKeyCheckInProgress = false - - // last OTK check timestamp - private var lastOneTimeKeyCheck: Long = 0 - private var oneTimeKeyCount: Int? = null - - // Simple storage to remember when was uploaded the last fallback key - private val storage = context.getSharedPreferences("OneTimeKeysUploader_${olmDevice.deviceEd25519Key.hashCode()}", Context.MODE_PRIVATE) - - /** - * Stores the current one_time_key count which will be handled later (in a call of - * _onSyncCompleted). The count is e.g. coming from a /sync response. - * - * @param currentCount the new count - */ - fun updateOneTimeKeyCount(currentCount: Int) { - oneTimeKeyCount = currentCount - } - - fun needsNewFallback() { - if (olmDevice.generateFallbackKeyIfNeeded()) { - // As we generated a new one, it's already forgetting one - // so we can clear the last publish time - // (in case the network calls fails after to avoid calling forgetKey) - saveLastFallbackKeyPublishTime(0L) - } - } - - /** - * Check if the OTK must be uploaded. - */ - suspend fun maybeUploadOneTimeKeys() { - if (oneTimeKeyCheckInProgress) { - Timber.v("maybeUploadOneTimeKeys: already in progress") - return - } - if (clock.epochMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { - // we've done a key upload recently. - Timber.v("maybeUploadOneTimeKeys: executed too recently") - return - } - - oneTimeKeyCheckInProgress = true - - val oneTimeKeyCountFromSync = oneTimeKeyCount - ?: fetchOtkCount() // we don't have count from sync so get from server - ?: return Unit.also { - oneTimeKeyCheckInProgress = false - Timber.w("maybeUploadOneTimeKeys: Failed to get otk count from server") - } - - Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}") - - lastOneTimeKeyCheck = clock.epochMillis() - - // We then check how many keys we can store in the Account object. - val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys() - - // Try to keep at most half that number on the server. This leaves the - // rest of the slots free to hold keys that have been claimed from the - // server but we haven't received a message for. - // If we run out of slots when generating new keys then olm will - // discard the oldest private keys first. This will eventually clean - // out stale private keys that won't receive a message. - val keyLimit = floor(maxOneTimeKeys / 2.0).toInt() - - // We need to keep a pool of one time public keys on the server so that - // other devices can start conversations with us. But we can only store - // a finite number of private keys in the olm Account object. - // To complicate things further then can be a delay between a device - // claiming a public one time key from the server and it sending us a - // message. We need to keep the corresponding private key locally until - // we receive the message. - // But that message might never arrive leaving us stuck with duff - // private keys clogging up our local storage. - // So we need some kind of engineering compromise to balance all of - // these factors. - tryOrNull("Unable to upload OTK") { - val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) - Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") - } - oneTimeKeyCheckInProgress = false - - // Check if we need to forget a fallback key - val latestPublishedTime = getLastFallbackKeyPublishTime() - if (latestPublishedTime != 0L && clock.epochMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) { - // This should be called once you are reasonably certain that you will not receive any more messages - // that use the old fallback key - Timber.d("## forgetFallbackKey()") - olmDevice.forgetFallbackKey() - } - } - - private suspend fun fetchOtkCount(): Int? { - return tryOrNull("Unable to get OTK count") { - val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null)) - result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) - } - } - - /** - * Upload some the OTKs. - * - * @param keyCount the key count - * @param keyLimit the limit - * @return the number of uploaded keys - */ - private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int { - if (keyLimit <= keyCount && !olmDevice.hasUnpublishedFallbackKey()) { - // If we don't need to generate any more keys then we are done. - return 0 - } - var keysThisLoop = 0 - if (keyLimit > keyCount) { - // Creating keys can be an expensive operation so we limit the - // number we generate in one go to avoid blocking the application - // for too long. - keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) - olmDevice.generateOneTimeKeys(keysThisLoop) - } - - // We check before sending if there is an unpublished key in order to saveLastFallbackKeyPublishTime if needed - val hadUnpublishedFallbackKey = olmDevice.hasUnpublishedFallbackKey() - val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys()) - olmDevice.markKeysAsPublished() - if (hadUnpublishedFallbackKey) { - // It had an unpublished fallback key that was published just now - saveLastFallbackKeyPublishTime(clock.epochMillis()) - } - - if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { - // Maybe upload other keys - return keysThisLoop + - uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) + - (if (hadUnpublishedFallbackKey) 1 else 0) - } else { - Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") - throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519") - } - } - - private fun saveLastFallbackKeyPublishTime(timeMillis: Long) { - storage.edit().putLong("last_fb_key_publish", timeMillis).apply() - } - - private fun getLastFallbackKeyPublishTime(): Long { - return storage.getLong("last_fb_key_publish", 0) - } - - /** - * Upload curve25519 one time keys. - */ - private suspend fun uploadOneTimeKeys(oneTimeKeys: Map>?): KeysUploadResponse { - val oneTimeJson = mutableMapOf() - - val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty() - - curve25519Map.forEach { (key_id, value) -> - val k = mutableMapOf() - k["key"] = value - - // the key is also signed - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k) - - k["signatures"] = objectSigner.signObject(canonicalJson) - - oneTimeJson["signed_curve25519:$key_id"] = k - } - - val fallbackJson = mutableMapOf() - val fallbackCurve25519Map = olmDevice.getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty() - fallbackCurve25519Map.forEach { (key_id, key) -> - val k = mutableMapOf() - k["key"] = key - k["fallback"] = true - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k) - k["signatures"] = objectSigner.signObject(canonicalJson) - - fallbackJson["signed_curve25519:$key_id"] = k - } - - // For now, we set the device id explicitly, as we may not be using the - // same one as used in login. - val uploadParams = UploadKeysTask.Params( - deviceKeys = null, - oneTimeKeys = oneTimeJson, - fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() } - ) - return uploadKeysTask.executeRetry(uploadParams, 3) - } - - companion object { - // max number of keys to upload at once - // Creating keys can be an expensive operation so we limit the - // number we generate in one go to avoid blocking the application - // for too long. - private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5 - - // frequency with which to check & upload one-time keys - private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60_000).toLong() // one minute - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt deleted file mode 100755 index 09a9868428..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.api.crypto.MXCryptoConfig -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest -import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.util.fromBase64 -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.di.SessionId -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer -import timber.log.Timber -import java.util.Stack -import java.util.concurrent.Executors -import javax.inject.Inject -import kotlin.system.measureTimeMillis - -private val loggerTag = LoggerTag("OutgoingKeyRequestManager", LoggerTag.CRYPTO) - -/** - * This class is responsible for sending key requests to other devices when a message failed to decrypt. - * It's lifecycle is based on the sync pulse: - * - You can post queries for session, or report when you got a session - * - At the end of the sync (onSyncComplete) it will then process all the posted request and send to devices - * If a request failed it will be retried at the end of the next sync - */ -@SessionScope -internal class OutgoingKeyRequestManager @Inject constructor( - @SessionId private val sessionId: String, - @UserId private val myUserId: String, - private val cryptoStore: IMXCryptoStore, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoConfig: MXCryptoConfig, - private val inboundGroupSessionStore: InboundGroupSessionStore, - private val sendToDeviceTask: SendToDeviceTask, - private val deviceListManager: DeviceListManager, - private val perSessionBackupQueryRateLimiter: PerSessionBackupQueryRateLimiter) { - - private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher) - private val sequencer = SemaphoreCoroutineSequencer() - - // We only have one active key request per session, so we don't request if it's already requested - // But it could make sense to check more the backup, as it's evolving. - // We keep a stack as we consider that the key requested last is more likely to be on screen? - private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack>() - - fun requestKeyForEvent(event: Event, force: Boolean) { - val (targets, body) = getRoomKeyRequestTargetForEvent(event) ?: return - val index = ratchetIndexForMessage(event) ?: 0 - postRoomKeyRequest(body, targets, index, force) - } - - private fun getRoomKeyRequestTargetForEvent(event: Event): Pair>, RoomKeyRequestBody>? { - val sender = event.senderId ?: return null - val encryptedEventContent = event.content.toModel() ?: return null.also { - Timber.tag(loggerTag.value).e("getRoomKeyRequestTargetForEvent Failed to re-request key, null content") - } - if (encryptedEventContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null - - val senderDevice = encryptedEventContent.deviceId - val recipients = if (cryptoConfig.limitRoomKeyRequestsToMyDevices) { - mapOf( - myUserId to listOf("*") - ) - } else { - if (event.senderId == myUserId) { - mapOf( - myUserId to listOf("*") - ) - } else { - // for the case where you share the key with a device that has a broken olm session - // The other user might Re-shares a megolm session key with devices if the key has already been - // sent to them. - mapOf( - myUserId to listOf("*"), - - // We might not have deviceId in the future due to https://github.com/matrix-org/matrix-spec-proposals/pull/3700 - // so in this case query to all - sender to listOf(senderDevice ?: "*") - ) - } - } - - val requestBody = RoomKeyRequestBody( - roomId = event.roomId, - algorithm = encryptedEventContent.algorithm, - senderKey = encryptedEventContent.senderKey, - sessionId = encryptedEventContent.sessionId - ) - return recipients to requestBody - } - - private fun ratchetIndexForMessage(event: Event): Int? { - val encryptedContent = event.content.toModel() ?: return null - if (encryptedContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null - return encryptedContent.ciphertext?.fromBase64()?.inputStream()?.reader()?.let { - tryOrNull { - val megolmVersion = it.read() - if (megolmVersion != 3) return@tryOrNull null - /** Int tag */ - if (it.read() != 8) return@tryOrNull null - it.read() - } - } - } - - fun postRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int, force: Boolean = false) { - outgoingRequestScope.launch { - sequencer.post { - internalQueueRequest(requestBody, recipients, fromIndex, force) - } - } - } - - /** - * Typically called when we the session as been imported or received meanwhile - */ - fun postCancelRequestForSessionIfNeeded(sessionId: String, roomId: String, senderKey: String, fromIndex: Int) { - outgoingRequestScope.launch { - sequencer.post { - internalQueueCancelRequest(sessionId, roomId, senderKey, fromIndex) - } - } - } - - fun onSelfCrossSigningTrustChanged(newTrust: Boolean) { - if (newTrust) { - // we were previously not cross signed, but we are now - // so there is now more chances to get better replies for existing request - // Let's forget about sent request so that next time we try to decrypt we will resend requests - // We don't resend all because we don't want to generate a bulk of traffic - outgoingRequestScope.launch { - sequencer.post { - cryptoStore.deleteOutgoingRoomKeyRequestInState(OutgoingRoomKeyRequestState.SENT) - } - - sequencer.post { - delay(1000) - perSessionBackupQueryRateLimiter.refreshBackupInfoIfNeeded(true) - } - } - } - } - - fun onRoomKeyForwarded(sessionId: String, - algorithm: String, - roomId: String, - senderKey: String, - fromDevice: String?, - fromIndex: Int, - event: Event) { - Timber.tag(loggerTag.value).d("Key forwarded for $sessionId from ${event.senderId}|$fromDevice at index $fromIndex") - outgoingRequestScope.launch { - sequencer.post { - cryptoStore.updateOutgoingRoomKeyReply( - roomId = roomId, - sessionId = sessionId, - algorithm = algorithm, - senderKey = senderKey, - fromDevice = fromDevice, - // strip out encrypted stuff as it's just a trail? - event = event.copy( - type = event.getClearType(), - content = mapOf( - "chain_index" to fromIndex - ) - ) - ) - } - } - } - - fun onRoomKeyWithHeld(sessionId: String, - algorithm: String, - roomId: String, - senderKey: String, - fromDevice: String?, - event: Event) { - outgoingRequestScope.launch { - sequencer.post { - Timber.tag(loggerTag.value).d("Withheld received for $sessionId from ${event.senderId}|$fromDevice") - Timber.tag(loggerTag.value).v("Withheld content ${event.getClearContent()}") - - // We want to store withheld code from the sender of the message (owner of the megolm session), not from - // other devices that might gossip the key. If not the initial reason might be overridden - // by a request to one of our session. - event.getClearContent().toModel()?.let { withheld -> - withContext(coroutineDispatchers.crypto) { - tryOrNull { - deviceListManager.downloadKeys(listOf(event.senderId ?: ""), false) - } - cryptoStore.getUserDeviceList(event.senderId ?: "") - .also { devices -> - Timber.tag(loggerTag.value) - .v("Withheld Devices for ${event.senderId} are ${devices.orEmpty().joinToString { it.identityKey() ?: "" }}") - } - ?.firstOrNull { - it.identityKey() == senderKey - } - }.also { - Timber.tag(loggerTag.value).v("Withheld device for sender key $senderKey is from ${it?.shortDebugString()}") - }?.let { - if (it.userId == event.senderId) { - if (fromDevice != null) { - if (it.deviceId == fromDevice) { - Timber.tag(loggerTag.value).v("Storing sender Withheld code ${withheld.code} for ${withheld.sessionId}") - cryptoStore.addWithHeldMegolmSession(withheld) - } - } else { - Timber.tag(loggerTag.value).v("Storing sender Withheld code ${withheld.code} for ${withheld.sessionId}") - cryptoStore.addWithHeldMegolmSession(withheld) - } - } - } - } - - // Here we store the replies from a given request - cryptoStore.updateOutgoingRoomKeyReply( - roomId = roomId, - sessionId = sessionId, - algorithm = algorithm, - senderKey = senderKey, - fromDevice = fromDevice, - event = event - ) - } - } - } - - /** - * Should be called after a sync, ideally if no catchup sync needed (as keys might arrive in those) - */ - fun requireProcessAllPendingKeyRequests() { - outgoingRequestScope.launch { - sequencer.post { - internalProcessPendingKeyRequests() - } - } - } - - private fun internalQueueCancelRequest(sessionId: String, roomId: String, senderKey: String, localKnownChainIndex: Int) { - // do we have known requests for that session?? - Timber.tag(loggerTag.value).v("Cancel Key Request if needed for $sessionId") - val knownRequest = cryptoStore.getOutgoingRoomKeyRequest( - algorithm = MXCRYPTO_ALGORITHM_MEGOLM, - roomId = roomId, - sessionId = sessionId, - senderKey = senderKey - ) - if (knownRequest.isEmpty()) return Unit.also { - Timber.tag(loggerTag.value).v("Handle Cancel Key Request for $sessionId -- Was not currently requested") - } - if (knownRequest.size > 1) { - // It's worth logging, there should be only one - Timber.tag(loggerTag.value).w("Found multiple requests for same sessionId $sessionId") - } - knownRequest.forEach { request -> - when (request.state) { - OutgoingRoomKeyRequestState.UNSENT -> { - if (request.fromIndex >= localKnownChainIndex) { - // we have a good index we can cancel - cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId) - } - } - OutgoingRoomKeyRequestState.SENT -> { - // It was already sent, and index satisfied we can cancel - if (request.fromIndex >= localKnownChainIndex) { - cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING) - } - } - OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> { - // It is already marked to be cancelled - } - OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { - if (request.fromIndex >= localKnownChainIndex) { - // we just want to cancel now - cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING) - } - } - OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> { - // was already canceled - // if we need a better index, should we resend? - } - } - } - } - - fun close() { - try { - outgoingRequestScope.cancel("User Terminate") - requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.clear() - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).w("Failed to shutDown request manager") - } - } - - private fun internalQueueRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int, force: Boolean) { - if (!cryptoStore.isKeyGossipingEnabled()) { - // we might want to try backup? - if (requestBody.roomId != null && requestBody.sessionId != null) { - requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(requestBody.roomId to requestBody.sessionId) - } - Timber.tag(loggerTag.value).d("discarding request for ${requestBody.sessionId} as gossiping is disabled") - return - } - - Timber.tag(loggerTag.value).d("Queueing key request for ${requestBody.sessionId} force:$force") - val existing = cryptoStore.getOutgoingRoomKeyRequest(requestBody) - Timber.tag(loggerTag.value).v("Queueing key request exiting is ${existing?.state}") - when (existing?.state) { - null -> { - // create a new one - cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex) - } - OutgoingRoomKeyRequestState.UNSENT -> { - // nothing it's new or not yet handled - } - OutgoingRoomKeyRequestState.SENT -> { - // it was already requested - Timber.tag(loggerTag.value).d("The session ${requestBody.sessionId} is already requested") - if (force) { - // update to UNSENT - Timber.tag(loggerTag.value).d(".. force to request ${requestBody.sessionId}") - cryptoStore.updateOutgoingRoomKeyRequestState(existing.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND) - } else { - if (existing.roomId != null && existing.sessionId != null) { - requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(existing.roomId to existing.sessionId) - } - } - } - OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> { - // request is canceled only if I got the keys so what to do here... - if (force) { - cryptoStore.updateOutgoingRoomKeyRequestState(existing.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND) - } - } - OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { - // It's already going to resend - } - OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> { - if (force) { - cryptoStore.deleteOutgoingRoomKeyRequest(existing.requestId) - cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex) - } - } - } - - if (existing != null && existing.fromIndex >= fromIndex) { - // update the required index - cryptoStore.updateOutgoingRoomKeyRequiredIndex(existing.requestId, fromIndex) - } - } - - private suspend fun internalProcessPendingKeyRequests() { - val toProcess = cryptoStore.getOutgoingRoomKeyRequests(OutgoingRoomKeyRequestState.pendingStates()) - Timber.tag(loggerTag.value).v("Processing all pending key requests (found ${toProcess.size} pending)") - - measureTimeMillis { - toProcess.forEach { - when (it.state) { - OutgoingRoomKeyRequestState.UNSENT -> handleUnsentRequest(it) - OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> handleRequestToCancel(it) - OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> handleRequestToCancelWillResend(it) - OutgoingRoomKeyRequestState.SENT_THEN_CANCELED, - OutgoingRoomKeyRequestState.SENT -> { - // these are filtered out - } - } - } - }.let { - Timber.tag(loggerTag.value).v("Finish processing pending key request in $it ms") - } - - val maxBackupCallsBySync = 60 - var currentCalls = 0 - measureTimeMillis { - while (requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.isNotEmpty() && currentCalls < maxBackupCallsBySync) { - requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.pop().let { (roomId, sessionId) -> - // we want to rate limit that somehow :/ - perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId) - } - currentCalls++ - } - }.let { - Timber.tag(loggerTag.value).v("Finish querying backup in $it ms") - } - } - - private suspend fun handleUnsentRequest(request: OutgoingKeyRequest) { - // In order to avoid generating to_device traffic, we can first check if the key is backed up - Timber.tag(loggerTag.value).v("Handling unsent request for megolm session ${request.sessionId} in ${request.roomId}") - val sessionId = request.sessionId ?: return - val roomId = request.roomId ?: return - if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) { - // let's see what's the index - val knownIndex = tryOrNull { - inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex - } - if (knownIndex != null && knownIndex <= request.fromIndex) { - // we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request - Timber.tag(loggerTag.value).v("Megolm session $sessionId successfully restored from backup, do not send request") - cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId) - return - } - } - - // we need to send the request - val toDeviceContent = RoomKeyShareRequest( - requestingDeviceId = cryptoStore.getDeviceId(), - requestId = request.requestId, - action = GossipingToDeviceObject.ACTION_SHARE_REQUEST, - body = request.requestBody - ) - val contentMap = MXUsersDevicesMap() - request.recipients.forEach { userToDeviceMap -> - userToDeviceMap.value.forEach { deviceId -> - contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent) - } - } - - val params = SendToDeviceTask.Params( - eventType = EventType.ROOM_KEY_REQUEST, - contentMap = contentMap, - transactionId = request.requestId - ) - try { - withContext(coroutineDispatchers.io) { - sendToDeviceTask.executeRetry(params, 3) - } - Timber.tag(loggerTag.value).d("Key request sent for $sessionId in room $roomId to ${request.recipients}") - // The request was sent, so update state - cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.SENT) - // TODO update the audit trail - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).v("Failed to request $sessionId targets:${request.recipients}") - } - } - - private suspend fun handleRequestToCancel(request: OutgoingKeyRequest): Boolean { - Timber.tag(loggerTag.value).v("handleRequestToCancel for megolm session ${request.sessionId}") - // we have to cancel this - val toDeviceContent = RoomKeyShareRequest( - requestingDeviceId = cryptoStore.getDeviceId(), - requestId = request.requestId, - action = GossipingToDeviceObject.ACTION_SHARE_CANCELLATION - ) - val contentMap = MXUsersDevicesMap() - request.recipients.forEach { userToDeviceMap -> - userToDeviceMap.value.forEach { deviceId -> - contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent) - } - } - - val params = SendToDeviceTask.Params( - eventType = EventType.ROOM_KEY_REQUEST, - contentMap = contentMap, - transactionId = request.requestId - ) - return try { - withContext(coroutineDispatchers.io) { - sendToDeviceTask.executeRetry(params, 3) - } - // The request cancellation was sent, we don't delete yet because we want - // to keep trace of the sent replies - cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.SENT_THEN_CANCELED) - true - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).v("Failed to cancel request ${request.requestId} for session $sessionId targets:${request.recipients}") - false - } - } - - private suspend fun handleRequestToCancelWillResend(request: OutgoingKeyRequest) { - if (handleRequestToCancel(request)) { - // this will create a new unsent request with no replies that will be process in the following call - cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId) - request.requestBody?.let { cryptoStore.getOrAddOutgoingRoomKeyRequest(it, request.recipients, request.fromIndex) } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt index 1872e33823..8fcdb4168b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.util.awaitCallback +import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -39,7 +40,7 @@ private val loggerTag = LoggerTag("OutgoingGossipingRequestManager", LoggerTag.C */ internal class PerSessionBackupQueryRateLimiter @Inject constructor( private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val keysBackupService: Lazy, + private val keysBackupService: Lazy, private val cryptoStore: IMXCryptoStore, private val clock: Clock, ) { @@ -103,16 +104,14 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor( val successfullyImported = withContext(coroutineDispatchers.io) { try { - awaitCallback { keysBackupService.get().restoreKeysWithRecoveryKey( currentVersion, savedKeyBackupKeyInfo?.recoveryKey ?: "", roomId, sessionId, null, - it ) - }.successfullyNumberOfImportedKeys + .successfullyNumberOfImportedKeys } catch (failure: Throwable) { // Fail silently Timber.tag(loggerTag.value).v("getFromBackup failed ${failure.localizedMessage}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt new file mode 100644 index 0000000000..e2245a6e40 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PrepareToEncryptUseCase.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService +import org.matrix.android.sdk.internal.crypto.network.RequestSender +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import timber.log.Timber +import uniffi.olm.Request +import uniffi.olm.RequestType +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +private val loggerTag = LoggerTag("PrepareToEncryptUseCase", LoggerTag.CRYPTO) + +@SessionScope +internal class PrepareToEncryptUseCase @Inject constructor(olmMachineProvider: OlmMachineProvider, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoStore: IMXCryptoStore, + private val getRoomUserIds: GetRoomUserIdsUseCase, + private val requestSender: RequestSender, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val keysBackupService: RustKeyBackupService +) { + + private val olmMachine = olmMachineProvider.olmMachine + + private val keyClaimLock: Mutex = Mutex() + private val roomKeyShareLocks: ConcurrentHashMap = ConcurrentHashMap() + + suspend operator fun invoke(roomId: String, ensureAllMembersAreLoaded: Boolean) { + withContext(coroutineDispatchers.crypto) { + Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") + // Ensure to load all room members + if (ensureAllMembersAreLoaded) { + try { + loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId)) + } catch (failure: Throwable) { + Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members") + throw failure + } + } + val userIds = getRoomUserIds(roomId) + val algorithm = getEncryptionAlgorithm(roomId) + if (algorithm == null) { + val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON) + Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason") + throw IllegalArgumentException("Missing algorithm") + } + preshareRoomKey(roomId, userIds) + } + } + + private fun getEncryptionAlgorithm(roomId: String): String? { + return cryptoStore.getRoomAlgorithm(roomId) + } + + private suspend fun preshareRoomKey(roomId: String, roomMembers: List) { + claimMissingKeys(roomMembers) + val keyShareLock = roomKeyShareLocks.getOrPut(roomId) { Mutex() } + var sharedKey = false + keyShareLock.withLock { + coroutineScope { + olmMachine.shareRoomKey(roomId, roomMembers).map { + when (it) { + is Request.ToDevice -> { + sharedKey = true + async { + sendToDevice(it) + } + } + else -> { + // This request can only be a to-device request but + // we need to handle all our cases and put this + // async block for our joinAll to work. + async {} + } + } + }.joinAll() + } + } + + // If we sent out a room key over to-device messages it's likely that we created a new one + // Try to back the key up + if (sharedKey) { + keysBackupService.maybeBackupKeys() + } + } + + private suspend fun claimMissingKeys(roomMembers: List) = keyClaimLock.withLock { + val request = this.olmMachine.getMissingSessions(roomMembers) + // This request can only be a keys claim request. + when (request) { + is Request.KeysClaim -> { + claimKeys(request) + } + else -> { + } + } + } + + private suspend fun sendToDevice(request: Request.ToDevice) { + try { + requestSender.sendToDevice(request) + olmMachine.markRequestAsSent(request.requestId, RequestType.TO_DEVICE, "{}") + } catch (throwable: Throwable) { + Timber.tag(loggerTag.value).e(throwable, "## CRYPTO sendToDevice(): error") + } + } + + private suspend fun claimKeys(request: Request.KeysClaim) { + try { + val response = requestSender.claimKeys(request) + olmMachine.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response) + } catch (throwable: Throwable) { + Timber.tag(loggerTag.value).e(throwable, "## CRYPTO claimKeys(): error") + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt index b6e5cdd8a2..63a1047451 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/QrCodeVerification.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerification import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher import uniffi.olm.CryptoStoreException diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt deleted file mode 100644 index 7a4f08fe8e..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.api.session.crypto.NewSessionListener -import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting -import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryptionFactory -import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmDecryptionFactory -import org.matrix.android.sdk.internal.session.SessionScope -import timber.log.Timber -import javax.inject.Inject - -@SessionScope -internal class RoomDecryptorProvider @Inject constructor( - private val olmDecryptionFactory: MXOlmDecryptionFactory, - private val megolmDecryptionFactory: MXMegolmDecryptionFactory -) { - - // A map from algorithm to MXDecrypting instance, for each room - private val roomDecryptors: MutableMap> = HashMap() - - private val newSessionListeners = ArrayList() - - fun addNewSessionListener(listener: NewSessionListener) { - if (!newSessionListeners.contains(listener)) newSessionListeners.add(listener) - } - - fun removeSessionListener(listener: NewSessionListener) { - newSessionListeners.remove(listener) - } - - /** - * Get a decryptor for a given room and algorithm. - * If we already have a decryptor for the given room and algorithm, return - * it. Otherwise try to instantiate it. - * - * @param roomId the room id - * @param algorithm the crypto algorithm - * @return the decryptor - * // TODO Create another method for the case of roomId is null - */ - fun getOrCreateRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? { - // sanity check - if (algorithm.isNullOrEmpty()) { - Timber.e("## getRoomDecryptor() : null algorithm") - return null - } - if (roomId != null && roomId.isNotEmpty()) { - synchronized(roomDecryptors) { - val decryptors = roomDecryptors.getOrPut(roomId) { mutableMapOf() } - val alg = decryptors[algorithm] - if (alg != null) { - return alg - } - } - } - val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm) - if (decryptingClass) { - val alg = when (algorithm) { - MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply { - this.newSessionListener = object : NewSessionListener { - override fun onNewSession(roomId: String?, sessionId: String) { - // PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor - newSessionListeners.toList().forEach { - try { - it.onNewSession(roomId, sessionId) - } catch (e: Throwable) { - } - } - } - } - } - else -> olmDecryptionFactory.create() - } - if (!roomId.isNullOrEmpty()) { - synchronized(roomDecryptors) { - roomDecryptors[roomId]?.put(algorithm, alg) - } - } - return alg - } - return null - } - - fun getRoomDecryptor(roomId: String?, algorithm: String?): IMXDecrypting? { - if (roomId == null || algorithm == null) { - return null - } - return roomDecryptors[roomId]?.get(algorithm) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt deleted file mode 100644 index 1a8c160d9c..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM -import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting -import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory -import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.session.SessionScope -import javax.inject.Inject - -@SessionScope -internal class RoomEncryptorsStore @Inject constructor( - private val cryptoStore: IMXCryptoStore, - private val megolmEncryptionFactory: MXMegolmEncryptionFactory, - private val olmEncryptionFactory: MXOlmEncryptionFactory, -) { - - // MXEncrypting instance for each room. - private val roomEncryptors = mutableMapOf() - - fun put(roomId: String, alg: IMXEncrypting) { - synchronized(roomEncryptors) { - roomEncryptors.put(roomId, alg) - } - } - - fun get(roomId: String): IMXEncrypting? { - return synchronized(roomEncryptors) { - val cache = roomEncryptors[roomId] - if (cache != null) { - return@synchronized cache - } else { - val alg: IMXEncrypting? = when (cryptoStore.getRoomAlgorithm(roomId)) { - MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId) - MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId) - else -> null - } - alg?.let { roomEncryptors.put(roomId, it) } - return@synchronized alg - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt index f20034e9c1..68ec92ab98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt @@ -18,15 +18,15 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustResult import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult -import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult -import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.di.UserId import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt index 6fb6914206..3ef5a963f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt @@ -24,11 +24,6 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME -import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME -import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME -import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME -import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest @@ -36,9 +31,6 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.util.toBase64NoPadding -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId @@ -54,8 +46,6 @@ internal class SecretShareManager @Inject constructor( private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, private val cryptoCoroutineScope: CoroutineScope, - private val messageEncrypter: MessageEncrypter, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val sendToDeviceTask: SendToDeviceTask, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val clock: Clock, @@ -107,6 +97,7 @@ internal class SecretShareManager @Inject constructor( } } + /* suspend fun handleSecretRequest(toDevice: Event) { val request = toDevice.getClearContent().toModel() ?: return Unit.also { @@ -214,6 +205,8 @@ internal class SecretShareManager @Inject constructor( } } + */ + private suspend fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean { val verifTimestamp = verifMutex.withLock { recentlyVerifiedDevices[deviceId] diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt new file mode 100644 index 0000000000..51e373b1d9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ShouldEncryptForInvitedMembersUseCase.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import org.matrix.android.sdk.api.crypto.MXCryptoConfig +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import javax.inject.Inject + +internal class ShouldEncryptForInvitedMembersUseCase @Inject constructor(private val cryptoConfig: MXCryptoConfig, + private val cryptoStore: IMXCryptoStore) { + + operator fun invoke(roomId: String): Boolean { + return cryptoConfig.enableEncryptionForInvitedMembers && cryptoStore.shouldEncryptForInvitedMembers(roomId) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt index 3cc94dae86..3d0c42b878 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/UserIdentities.kt @@ -18,11 +18,11 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.verification.prepareMethods import uniffi.olm.CryptoStoreException diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt index 9c1b892bfd..aec2495fc7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/VerificationRequest.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationI import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding +import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.verification.prepareMethods diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt deleted file mode 100644 index df29a494db..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.actions - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.model.MXKey -import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult -import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask -import org.matrix.android.sdk.internal.session.SessionScope -import timber.log.Timber -import javax.inject.Inject - -private const val ONE_TIME_KEYS_RETRY_COUNT = 3 - -private val loggerTag = LoggerTag("EnsureOlmSessionsForDevicesAction", LoggerTag.CRYPTO) - -@SessionScope -internal class EnsureOlmSessionsForDevicesAction @Inject constructor( - private val olmDevice: MXOlmDevice, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) { - - private val ensureMutex = Mutex() - - /** - * We want to synchronize a bit here, because we are iterating to check existing olm session and - * also adding some - */ - suspend fun handle(devicesByUser: Map>, force: Boolean = false): MXUsersDevicesMap { - ensureMutex.withLock { - val results = MXUsersDevicesMap() - val deviceList = devicesByUser.flatMap { it.value } - Timber.tag(loggerTag.value) - .d("ensure olm forced:$force for ${deviceList.joinToString { it.shortDebugString() }}") - val devicesToCreateSessionWith = mutableListOf() - if (force) { - // we take all devices and will query otk for them - devicesToCreateSessionWith.addAll(deviceList) - } else { - // only peek devices without active session - deviceList.forEach { deviceInfo -> - val deviceId = deviceInfo.deviceId - val userId = deviceInfo.userId - val key = deviceInfo.identityKey() ?: return@forEach Unit.also { - Timber.tag(loggerTag.value).w("Ignoring device ${deviceInfo.shortDebugString()} without identity key") - } - - // is there a session that as been already used? - val sessionId = olmDevice.getSessionId(key) - if (sessionId.isNullOrEmpty()) { - Timber.tag(loggerTag.value).d("Found no existing olm session ${deviceInfo.shortDebugString()} add to claim list") - devicesToCreateSessionWith.add(deviceInfo) - } else { - Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)") - val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId) - results.setObject(userId, deviceId, olmSessionResult) - } - } - } - - if (devicesToCreateSessionWith.isEmpty()) { - // no session to create - return results - } - val usersDevicesToClaim = MXUsersDevicesMap().apply { - devicesToCreateSessionWith.forEach { - setObject(it.userId, it.deviceId, MXKey.KEY_SIGNED_CURVE_25519_TYPE) - } - } - - // Let's now claim one time keys - val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) - val oneTimeKeys = withContext(coroutineDispatchers.io) { - oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT) - } - - // let now start olm session using the new otks - devicesToCreateSessionWith.forEach { deviceInfo -> - val userId = deviceInfo.userId - val deviceId = deviceInfo.deviceId - // Did we get an OTK - val oneTimeKey = oneTimeKeys.getObject(userId, deviceId) - if (oneTimeKey == null) { - Timber.tag(loggerTag.value).d("No otk for ${deviceInfo.shortDebugString()}") - } else if (oneTimeKey.type != MXKey.KEY_SIGNED_CURVE_25519_TYPE) { - Timber.tag(loggerTag.value).d("Bad otk type (${oneTimeKey.type}) for ${deviceInfo.shortDebugString()}") - } else { - val olmSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo) - if (olmSessionId != null) { - val olmSessionResult = MXOlmSessionResult(deviceInfo, olmSessionId) - results.setObject(userId, deviceId, olmSessionResult) - } else { - Timber - .tag(loggerTag.value) - .d("## CRYPTO | cant unwedge failed to create outbound ${deviceInfo.shortDebugString()}") - } - } - } - return results - } - } - - private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? { - var sessionId: String? = null - - val deviceId = deviceInfo.deviceId - val signKeyId = "ed25519:$deviceId" - val signature = oneTimeKey.signatureForUserId(userId, signKeyId) - - val fingerprint = deviceInfo.fingerprint() - if (!signature.isNullOrEmpty() && !fingerprint.isNullOrEmpty()) { - var isVerified = false - var errorMessage: String? = null - - try { - olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature) - isVerified = true - } catch (e: Exception) { - Timber.tag(loggerTag.value).d( - e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," + - " signature:$signature fingerprint:$fingerprint" - ) - Timber.tag(loggerTag.value).e( - "verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " + - " - signable json ${oneTimeKey.signalableJSONDictionary()}" - ) - errorMessage = e.message - } - - // Check one-time key signature - if (isVerified) { - sessionId = deviceInfo.identityKey()?.let { identityKey -> - olmDevice.createOutboundSession(identityKey, oneTimeKey.value) - } - - if (sessionId.isNullOrEmpty()) { - // Possibly a bad key - Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Error starting session with device $userId:$deviceId") - } else { - Timber.tag(loggerTag.value).d("verifyKeyAndStartSession() : Started new sessionId $sessionId for device $userId:$deviceId") - } - } else { - Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Unable to verify otk signature for $userId:$deviceId: $errorMessage") - } - } - - return sessionId - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt deleted file mode 100644 index fc211537a6..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.actions - -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import timber.log.Timber -import javax.inject.Inject - -internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice, - private val cryptoStore: IMXCryptoStore, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) { - - /** - * Try to make sure we have established olm sessions for the given users. - * @param users a list of user ids. - */ - suspend fun handle(users: List): MXUsersDevicesMap { - Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") - val devicesByUser = users.associateWith { userId -> - val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty() - - devices.filter { - // Don't bother setting up session to ourself - it.identityKey() != olmDevice.deviceCurve25519Key && - // Don't bother setting up sessions with blocked users - !(it.trustLevel?.isVerified() ?: false) - } - } - return ensureOlmSessionsForDevicesAction.handle(devicesByUser) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt deleted file mode 100644 index 22c4e59b18..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.actions - -import androidx.annotation.WorkerThread -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.listeners.ProgressListener -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.MegolmSessionData -import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager -import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider -import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber -import javax.inject.Inject - -private val loggerTag = LoggerTag("MegolmSessionDataImporter", LoggerTag.CRYPTO) - -internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice, - private val roomDecryptorProvider: RoomDecryptorProvider, - private val outgoingKeyRequestManager: OutgoingKeyRequestManager, - private val cryptoStore: IMXCryptoStore, - private val clock: Clock, -) { - - /** - * Import a list of megolm session keys. - * Must be call on the crypto coroutine thread - * - * @param megolmSessionsData megolm sessions. - * @param fromBackup true if the imported keys are already backed up on the server. - * @param progressListener the progress listener - * @return import room keys result - */ - @WorkerThread - fun handle(megolmSessionsData: List, - fromBackup: Boolean, - progressListener: ProgressListener?): ImportRoomKeysResult { - val t0 = clock.epochMillis() - - val totalNumbersOfKeys = megolmSessionsData.size - var lastProgress = 0 - var totalNumbersOfImportedKeys = 0 - - progressListener?.onProgress(0, 100) - val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData) - - megolmSessionsData.forEachIndexed { cpt, megolmSessionData -> - val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm) - - if (null != decrypting) { - try { - val sessionId = megolmSessionData.sessionId - Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId") - - totalNumbersOfImportedKeys++ - - // cancel any outstanding room key requests for this session - - Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}") - outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded( - megolmSessionData.sessionId ?: "", - megolmSessionData.roomId ?: "", - megolmSessionData.senderKey ?: "", - tryOrNull { - olmInboundGroupSessionWrappers - .firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId } - ?.firstKnownIndex?.toInt() - } ?: 0 - ) - - // Have another go at decrypting events sent with this session - when (decrypting) { - is MXMegolmDecryption -> { - decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!) - } - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importRoomKeys() : onNewSession failed") - } - } - - if (progressListener != null) { - val progress = 100 * (cpt + 1) / totalNumbersOfKeys - - if (lastProgress != progress) { - lastProgress = progress - - progressListener.onProgress(progress, 100) - } - } - } - - // Do not back up the key if it comes from a backup recovery - if (fromBackup) { - cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - } - - val t1 = clock.epochMillis() - - Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)") - - return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt deleted file mode 100644 index 9bbbab4992..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.actions - -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedMessage -import org.matrix.android.sdk.internal.di.DeviceId -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.convertToUTF8 -import timber.log.Timber -import javax.inject.Inject - -private val loggerTag = LoggerTag("MessageEncrypter", LoggerTag.CRYPTO) - -internal class MessageEncrypter @Inject constructor( - @UserId - private val userId: String, - @DeviceId - private val deviceId: String?, - private val olmDevice: MXOlmDevice) { - /** - * Encrypt an event payload for a list of devices. - * This method must be called from the getCryptoHandler() thread. - * - * @param payloadFields fields to include in the encrypted payload. - * @param deviceInfos list of device infos to encrypt for. - * @return the content for an m.room.encrypted event. - */ - suspend fun encryptMessage(payloadFields: Content, deviceInfos: List): EncryptedMessage { - val deviceInfoParticipantKey = deviceInfos.associateBy { it.identityKey()!! } - - val payloadJson = payloadFields.toMutableMap() - - payloadJson["sender"] = userId - payloadJson["sender_device"] = deviceId!! - - // Include the Ed25519 key so that the recipient knows what - // device this message came from. - // We don't need to include the curve25519 key since the - // recipient will already know this from the olm headers. - // When combined with the device keys retrieved from the - // homeserver signed by the ed25519 key this proves that - // the curve25519 key and the ed25519 key are owned by - // the same device. - payloadJson["keys"] = mapOf("ed25519" to olmDevice.deviceEd25519Key!!) - - val ciphertext = mutableMapOf() - - for ((deviceKey, deviceInfo) in deviceInfoParticipantKey) { - val sessionId = olmDevice.getSessionId(deviceKey) - - if (!sessionId.isNullOrEmpty()) { - Timber.tag(loggerTag.value).d("Using sessionid $sessionId for device $deviceKey") - - payloadJson["recipient"] = deviceInfo.userId - payloadJson["recipient_keys"] = mapOf("ed25519" to deviceInfo.fingerprint()!!) - - val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson)) - ciphertext[deviceKey] = olmDevice.encryptMessage(deviceKey, sessionId, payloadString)!! - } - } - - return EncryptedMessage( - algorithm = MXCRYPTO_ALGORITHM_OLM, - senderKey = olmDevice.deviceCurve25519Key, - cipherText = ciphertext - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt deleted file mode 100644 index a1b1766de6..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.actions - -import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.di.UserId -import timber.log.Timber -import javax.inject.Inject - -internal class SetDeviceVerificationAction @Inject constructor( - private val cryptoStore: IMXCryptoStore, - @UserId private val userId: String, - private val defaultKeysBackupService: DefaultKeysBackupService) { - - fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { - val device = cryptoStore.getUserDevice(userId, deviceId) - - // Sanity check - if (null == device) { - Timber.w("## setDeviceVerification() : Unknown device $userId:$deviceId") - return - } - - if (device.isVerified != trustLevel.isVerified()) { - if (userId == this.userId) { - // If one of the user's own devices is being marked as verified / unverified, - // check the key backup status, since whether or not we use this depends on - // whether it has a signature from a verified device - defaultKeysBackupService.checkAndStartKeysBackup() - } - } - - if (device.trustLevel != trustLevel) { - device.trustLevel = trustLevel - cryptoStore.setDeviceTrust(userId, deviceId, trustLevel.crossSigningVerified, trustLevel.locallyVerified) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt deleted file mode 100644 index 72eae066c9..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms - -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult -import org.matrix.android.sdk.api.session.events.model.Event - -/** - * An interface for decrypting data - */ -internal interface IMXDecrypting { - - /** - * Decrypt an event - * - * @param event the raw event. - * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @return the decryption information, or an error - */ - @Throws(MXCryptoError::class) - suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult - - /** - * Handle a key event. - * - * @param event the key event. - */ - fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt deleted file mode 100644 index b0f411cbcc..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms - -import org.matrix.android.sdk.api.session.events.model.Content - -/** - * An interface for encrypting data - */ -@Deprecated("rust") -internal interface IMXEncrypting { - - /** - * Encrypt an event content according to the configuration of the room. - * - * @param eventContent the content of the event. - * @param eventType the type of the event. - * @param userIds the room members the event will be sent to. - * @return the encrypted content - */ - suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt deleted file mode 100644 index 19d2ade788..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms - -@Deprecated("rust") -internal interface IMXGroupEncryption { - - /** - * In Megolm, each recipient maintains a record of the ratchet value which allows - * them to decrypt any messages sent in the session after the corresponding point - * in the conversation. If this value is compromised, an attacker can similarly - * decrypt past messages which were encrypted by a key derived from the - * compromised or subsequent ratchet values. This gives 'partial' forward - * secrecy. - * - * To mitigate this issue, the application should offer the user the option to - * discard historical conversations, by winding forward any stored ratchet values, - * or discarding sessions altogether. - */ - fun discardSessionKey() - - suspend fun preshareKey(userIds: List) - - /** - * Re-shares a session key with devices if the key has already been - * sent to them. - * - * @param sessionId The id of the outbound session to share. - * @param userId The id of the user who owns the target device. - * @param deviceId The id of the target device. - * @param senderKey The key of the originating device for the session. - * - * @return true in case of success - */ - suspend fun reshareKey(groupSessionId: String, - userId: String, - deviceId: String, - senderKey: String): Boolean -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt deleted file mode 100644 index e808e18e76..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.megolm - -import dagger.Lazy -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.NewSessionListener -import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent -import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager -import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.session.StreamEventsManager -import timber.log.Timber - -private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO) - -internal class MXMegolmDecryption( - private val olmDevice: MXOlmDevice, - private val outgoingKeyRequestManager: OutgoingKeyRequestManager, - private val cryptoStore: IMXCryptoStore, - private val liveEventManager: Lazy -) : IMXDecrypting { - - var newSessionListener: NewSessionListener? = null - - /** - * Events which we couldn't decrypt due to unknown sessions / indexes: map from - * senderKey|sessionId to timelines to list of MatrixEvents. - */ -// private var pendingEvents: MutableMap>> = HashMap() - - @Throws(MXCryptoError::class) - override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { - return decryptEvent(event, timeline, true) - } - - @Throws(MXCryptoError::class) - private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult { - Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail") - if (event.roomId.isNullOrBlank()) { - throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) - } - - val encryptedEventContent = event.content.toModel() - ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) - - if (encryptedEventContent.senderKey.isNullOrBlank() || - encryptedEventContent.sessionId.isNullOrBlank() || - encryptedEventContent.ciphertext.isNullOrBlank()) { - throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) - } - - return runCatching { - olmDevice.decryptGroupMessage( - encryptedEventContent.ciphertext, - event.roomId, - timeline, - encryptedEventContent.sessionId, - encryptedEventContent.senderKey - ) - } - .fold( - { olmDecryptionResult -> - // the decryption succeeds - if (olmDecryptionResult.payload != null) { - MXEventDecryptionResult( - clearEvent = olmDecryptionResult.payload, - senderCurve25519Key = olmDecryptionResult.senderKey, - claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"), - forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain - .orEmpty() - ).also { - liveEventManager.get().dispatchLiveEventDecrypted(event, it) - } - } else { - throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) - } - }, - { throwable -> - liveEventManager.get().dispatchLiveEventDecryptionFailed(event, throwable) - if (throwable is MXCryptoError.OlmError) { - // TODO Check the value of .message - if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") { - // So we know that session, but it's ratcheted and we can't decrypt at that index - - if (requestKeysOnFail) { - requestKeysForEvent(event) - } - // Check if partially withheld - val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId) - if (withHeldInfo != null) { - // Encapsulate as withHeld exception - throw MXCryptoError.Base( - MXCryptoError.ErrorType.KEYS_WITHHELD, - withHeldInfo.code?.value ?: "", - withHeldInfo.reason - ) - } - - throw MXCryptoError.Base( - MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, - "UNKNOWN_MESSAGE_INDEX", - null - ) - } - - val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message) - val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason) - - throw MXCryptoError.Base( - MXCryptoError.ErrorType.OLM, - reason, - detailedReason - ) - } - if (throwable is MXCryptoError.Base) { - if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { - // Check if it was withheld by sender to enrich error code - val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId) - if (withHeldInfo != null) { - if (requestKeysOnFail) { - requestKeysForEvent(event) - } - // Encapsulate as withHeld exception - throw MXCryptoError.Base( - MXCryptoError.ErrorType.KEYS_WITHHELD, - withHeldInfo.code?.value ?: "", - withHeldInfo.reason) - } - - if (requestKeysOnFail) { - requestKeysForEvent(event) - } - } - } - throw throwable - } - ) - } - - /** - * Helper for the real decryptEvent and for _retryDecryption. If - * requestKeysOnFail is true, we'll send an m.room_key_request when we fail - * to decrypt the event due to missing megolm keys. - * - * @param event the event - */ - private fun requestKeysForEvent(event: Event) { - outgoingKeyRequestManager.requestKeyForEvent(event, false) - } - - /** - * Handle a key event. - * - * @param event the key event. - */ - override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) { - Timber.tag(loggerTag.value).v("onRoomKeyEvent()") - var exportFormat = false - val roomKeyContent = event.getClearContent().toModel() ?: return - - var senderKey: String? = event.getSenderKey() - var keysClaimed: MutableMap = HashMap() - val forwardingCurve25519KeyChain: MutableList = ArrayList() - - if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.sessionId.isNullOrEmpty() || roomKeyContent.sessionKey.isNullOrEmpty()) { - Timber.tag(loggerTag.value).e("onRoomKeyEvent() : Key event is missing fields") - return - } - if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { - if (!cryptoStore.isKeyGossipingEnabled()) { - Timber.tag(loggerTag.value) - .i("onRoomKeyEvent(), ignore forward adding as per crypto config : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") - return - } - Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") - val forwardedRoomKeyContent = event.getClearContent().toModel() - ?: return - - forwardedRoomKeyContent.forwardingCurve25519KeyChain?.let { - forwardingCurve25519KeyChain.addAll(it) - } - - if (senderKey == null) { - Timber.tag(loggerTag.value).e("onRoomKeyEvent() : event is missing sender_key field") - return - } - - forwardingCurve25519KeyChain.add(senderKey) - - exportFormat = true - senderKey = forwardedRoomKeyContent.senderKey - if (null == senderKey) { - Timber.tag(loggerTag.value).e("onRoomKeyEvent() : forwarded_room_key event is missing sender_key field") - return - } - - if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) { - Timber.tag(loggerTag.value).e("forwarded_room_key_event is missing sender_claimed_ed25519_key field") - return - } - - keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key - } else { - Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") - if (null == senderKey) { - Timber.tag(loggerTag.value).e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)") - return - } - - // inherit the claimed ed25519 key from the setup message - keysClaimed = event.getKeysClaimed().toMutableMap() - } - - Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") - val addSessionResult = olmDevice.addInboundGroupSession( - roomKeyContent.sessionId, - roomKeyContent.sessionKey, - roomKeyContent.roomId, - senderKey, - forwardingCurve25519KeyChain, - keysClaimed, - exportFormat - ) - - when (addSessionResult) { - is MXOlmDevice.AddSessionResult.Imported -> addSessionResult.ratchetIndex - is MXOlmDevice.AddSessionResult.NotImportedHigherIndex -> addSessionResult.newIndex - else -> null - }?.let { index -> - if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { - val fromDevice = (event.content?.get("sender_key") as? String)?.let { senderDeviceIdentityKey -> - cryptoStore.getUserDeviceList(event.senderId ?: "") - ?.firstOrNull { - it.identityKey() == senderDeviceIdentityKey - } - }?.deviceId - - outgoingKeyRequestManager.onRoomKeyForwarded( - sessionId = roomKeyContent.sessionId, - algorithm = roomKeyContent.algorithm ?: "", - roomId = roomKeyContent.roomId, - senderKey = senderKey, - fromIndex = index, - fromDevice = fromDevice, - event = event) - - cryptoStore.saveIncomingForwardKeyAuditTrail( - roomId = roomKeyContent.roomId, - sessionId = roomKeyContent.sessionId, - senderKey = senderKey, - algorithm = roomKeyContent.algorithm ?: "", - userId = event.senderId ?: "", - deviceId = fromDevice ?: "", - chainIndex = index.toLong()) - - // The index is used to decide if we cancel sent request or if we wait for a better key - outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, senderKey, index) - } - } - - if (addSessionResult is MXOlmDevice.AddSessionResult.Imported) { - Timber.tag(loggerTag.value) - .d("onRoomKeyEvent(${event.getClearType()}) : Added megolm session ${roomKeyContent.sessionId} in ${roomKeyContent.roomId}") - defaultKeysBackupService.maybeBackupKeys() - - onNewSession(roomKeyContent.roomId, senderKey, roomKeyContent.sessionId) - } - } - - /** - * Check if the some messages can be decrypted with a new session - * - * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions - * @param senderKey the session sender key - * @param sessionId the session id - */ - fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { - Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") - newSessionListener?.onNewSession(roomId, senderKey, sessionId) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt deleted file mode 100644 index 096773a959..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.megolm - -import dagger.Lazy -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.session.StreamEventsManager -import javax.inject.Inject - -internal class MXMegolmDecryptionFactory @Inject constructor( - private val olmDevice: MXOlmDevice, - private val outgoingKeyRequestManager: OutgoingKeyRequestManager, - private val cryptoStore: IMXCryptoStore, - private val eventsManager: Lazy -) { - - fun create(): MXMegolmDecryption { - return MXMegolmDecryption( - olmDevice, - outgoingKeyRequestManager, - cryptoStore, - eventsManager) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt deleted file mode 100644 index 0131f54ce9..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.megolm - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.crypto.model.forEach -import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent -import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode -import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting -import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption -import org.matrix.android.sdk.internal.crypto.model.toDebugCount -import org.matrix.android.sdk.internal.crypto.model.toDebugString -import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.convertToUTF8 -import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber - -private val loggerTag = LoggerTag("MXMegolmEncryption", LoggerTag.CRYPTO) - -internal class MXMegolmEncryption( - // The id of the room we will be sending to. - private val roomId: String, - private val olmDevice: MXOlmDevice, - private val defaultKeysBackupService: DefaultKeysBackupService, - private val cryptoStore: IMXCryptoStore, - private val deviceListManager: DeviceListManager, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - private val myUserId: String, - private val myDeviceId: String, - private val sendToDeviceTask: SendToDeviceTask, - private val messageEncrypter: MessageEncrypter, - private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope, - private val clock: Clock, -) : IMXEncrypting, IMXGroupEncryption { - - // OutboundSessionInfo. Null if we haven't yet started setting one up. Note - // that even if this is non-null, it may not be ready for use (in which - // case outboundSession.shareOperation will be non-null.) - private var outboundSession: MXOutboundSessionInfo? = null - - init { - // restore existing outbound session if any - outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId) - } - - // Default rotation periods - // TODO: Make it configurable via parameters - // Session rotation periods - private var sessionRotationPeriodMsgs: Int = 100 - private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000 - - override suspend fun encryptEventContent(eventContent: Content, - eventType: String, - userIds: List): Content { - val ts = clock.epochMillis() - Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom") - val devices = getDevicesInRoom(userIds) - Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}") - Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}") - val outboundSession = ensureOutboundSession(devices.allowedDevices) - - return encryptContent(outboundSession, eventType, eventContent) - .also { - notifyWithheldForSession(devices.withHeldDevices, outboundSession) - // annoyingly we have to serialize again the saved outbound session to store message index :/ - // if not we would see duplicate message index errors - olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId) - Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${clock.epochMillis() - ts} millis") - } - } - - private fun notifyWithheldForSession(devices: MXUsersDevicesMap, outboundSession: MXOutboundSessionInfo) { - // offload to computation thread - cryptoCoroutineScope.launch(coroutineDispatchers.computation) { - mutableListOf>().apply { - devices.forEach { userId, deviceId, withheldCode -> - this.add(UserDevice(userId, deviceId) to withheldCode) - } - }.groupBy( - { it.second }, - { it.first } - ).forEach { (code, targets) -> - notifyKeyWithHeld(targets, outboundSession.sessionId, olmDevice.deviceCurve25519Key, code) - } - } - } - - override fun discardSessionKey() { - outboundSession = null - olmDevice.discardOutboundGroupSessionForRoom(roomId) - } - - override suspend fun preshareKey(userIds: List) { - val ts = clock.epochMillis() - Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...") - val devices = getDevicesInRoom(userIds) - val outboundSession = ensureOutboundSession(devices.allowedDevices) - - notifyWithheldForSession(devices.withHeldDevices, outboundSession) - - Timber.tag(loggerTag.value).d("preshareKey in $roomId done in ${clock.epochMillis() - ts} millis") - } - - /** - * Prepare a new session. - * - * @return the session description - */ - private fun prepareNewSessionInRoom(): MXOutboundSessionInfo { - Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() ") - val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId) - - val keysClaimedMap = mapOf( - "ed25519" to olmDevice.deviceEd25519Key!! - ) - - olmDevice.addInboundGroupSession( - sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, - emptyList(), keysClaimedMap, false - ) - - defaultKeysBackupService.maybeBackupKeys() - - return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock) - } - - /** - * Ensure the outbound session - * - * @param devicesInRoom the devices list - */ - private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap): MXOutboundSessionInfo { - Timber.tag(loggerTag.value).v("ensureOutboundSession roomId:$roomId") - var session = outboundSession - if (session == null || - // Need to make a brand new session? - session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) || - // Determine if we have shared with anyone we shouldn't have - session.sharedWithTooManyDevices(devicesInRoom)) { - Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.") - session = prepareNewSessionInRoom() - outboundSession = session - } - val safeSession = session - val shareMap = HashMap>()/* userId */ - val userIds = devicesInRoom.userIds - for (userId in userIds) { - val deviceIds = devicesInRoom.getUserDeviceIds(userId) - for (deviceId in deviceIds!!) { - val deviceInfo = devicesInRoom.getObject(userId, deviceId) - if (deviceInfo != null && !cryptoStore.getSharedSessionInfo(roomId, safeSession.sessionId, deviceInfo).found) { - val devices = shareMap.getOrPut(userId) { ArrayList() } - devices.add(deviceInfo) - } - } - } - val devicesCount = shareMap.entries.fold(0) { acc, new -> acc + new.value.size } - Timber.tag(loggerTag.value).d("roomId:$roomId found $devicesCount devices without megolm session(${session.sessionId})") - shareKey(safeSession, shareMap) - return safeSession - } - - /** - * Share the device key to a list of users - * - * @param session the session info - * @param devicesByUsers the devices map - */ - private suspend fun shareKey(session: MXOutboundSessionInfo, - devicesByUsers: Map>) { - // nothing to send, the task is done - if (devicesByUsers.isEmpty()) { - Timber.tag(loggerTag.value).v("shareKey() : nothing more to do") - return - } - // reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user) - val subMap = HashMap>() - var devicesCount = 0 - for ((userId, devices) in devicesByUsers) { - subMap[userId] = devices - devicesCount += devices.size - if (devicesCount > 100) { - break - } - } - Timber.tag(loggerTag.value).v("shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}") - shareUserDevicesKey(session, subMap) - val remainingDevices = devicesByUsers - subMap.keys - shareKey(session, remainingDevices) - } - - /** - * Share the device keys of a an user - * - * @param session the session info - * @param devicesByUser the devices map - */ - private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo, - devicesByUser: Map>) { - val sessionKey = olmDevice.getSessionKey(session.sessionId) - val chainIndex = olmDevice.getMessageIndex(session.sessionId) - - val submap = HashMap() - submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM - submap["room_id"] = roomId - submap["session_id"] = session.sessionId - submap["session_key"] = sessionKey!! - submap["chain_index"] = chainIndex - - val payload = HashMap() - payload["type"] = EventType.ROOM_KEY - payload["content"] = submap - - var t0 = clock.epochMillis() - Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts") - - val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser) - Timber.tag(loggerTag.value).v( - """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${clock.epochMillis() - t0} ms""" - .trimMargin() - ) - val contentMap = MXUsersDevicesMap() - var haveTargets = false - val userIds = results.userIds - val noOlmToNotify = mutableListOf() - for (userId in userIds) { - val devicesToShareWith = devicesByUser[userId] - for ((deviceID) in devicesToShareWith!!) { - val sessionResult = results.getObject(userId, deviceID) - if (sessionResult?.sessionId == null) { - // no session with this device, probably because there - // were no one-time keys. - - // MSC 2399 - // send withheld m.no_olm: an olm session could not be established. - // This may happen, for example, if the sender was unable to obtain a one-time key from the recipient. - Timber.tag(loggerTag.value).v("shareUserDevicesKey() : No Olm Session for $userId:$deviceID mark for withheld") - noOlmToNotify.add(UserDevice(userId, deviceID)) - continue - } - Timber.tag(loggerTag.value).v("shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID") - contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo))) - haveTargets = true - } - } - - // Add the devices we have shared with to session.sharedWithDevices. - // we deliberately iterate over devicesByUser (ie, the devices we - // attempted to share with) rather than the contentMap (those we did - // share with), because we don't want to try to claim a one-time-key - // for dead devices on every message. - for ((_, devicesToShareWith) in devicesByUser) { - for (deviceInfo in devicesToShareWith) { - session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex) - // XXX is it needed to add it to the audit trail? - // For now decided that no, we are more interested by forward trail - } - } - - if (haveTargets) { - t0 = clock.epochMillis() - Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target") - Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap) - try { - withContext(coroutineDispatchers.io) { - sendToDeviceTask.execute(sendToDeviceParams) - } - Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms") - } catch (failure: Throwable) { - // What to do here... - Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>") - } - } else { - Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key") - } - - if (noOlmToNotify.isNotEmpty()) { - // XXX offload?, as they won't read the message anyhow? - notifyKeyWithHeld( - noOlmToNotify, - session.sessionId, - olmDevice.deviceCurve25519Key, - WithHeldCode.NO_OLM - ) - } - } - - private suspend fun notifyKeyWithHeld(targets: List, - sessionId: String, - senderKey: String?, - code: WithHeldCode) { - Timber.tag(loggerTag.value).d( - "notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" + - " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}" - ) - val withHeldContent = RoomKeyWithHeldContent( - roomId = roomId, - senderKey = senderKey, - algorithm = MXCRYPTO_ALGORITHM_MEGOLM, - sessionId = sessionId, - codeString = code.value, - fromDevice = myDeviceId - ) - val params = SendToDeviceTask.Params( - EventType.ROOM_KEY_WITHHELD, - MXUsersDevicesMap().apply { - targets.forEach { - setObject(it.userId, it.deviceId, withHeldContent) - } - } - ) - try { - withContext(coroutineDispatchers.io) { - sendToDeviceTask.execute(params) - } - } catch (failure: Throwable) { - Timber.tag(loggerTag.value) - .e("notifyKeyWithHeld() :$sessionId Failed to send withheld ${targets.map { "${it.userId}|${it.deviceId}" }}") - } - } - - /** - * process the pending encryptions - */ - private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content { - // Everything is in place, encrypt all pending events - val payloadJson = HashMap() - payloadJson["room_id"] = roomId - payloadJson["type"] = eventType - payloadJson["content"] = eventContent - - // Get canonical Json from - - val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson)) - val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString) - - val map = HashMap() - map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM - map["sender_key"] = olmDevice.deviceCurve25519Key!! - map["ciphertext"] = ciphertext!! - map["session_id"] = session.sessionId - - // Include our device ID so that recipients can send us a - // m.new_device message if they don't have our session key. - map["device_id"] = myDeviceId - session.useCount++ - return map - } - - /** - * Get the list of devices which can encrypt data to. - * This method must be called in getDecryptingThreadHandler() thread. - * - * @param userIds the user ids whose devices must be checked. - */ - private suspend fun getDevicesInRoom(userIds: List): DeviceInRoomInfo { - // We are happy to use a cached version here: we assume that if we already - // have a list of the user's devices, then we already share an e2e room - // with them, which means that they will have announced any new devices via - // an m.new_device. - val keys = deviceListManager.downloadKeys(userIds, false) - val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() || - cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId) - - val devicesInRoom = DeviceInRoomInfo() - val unknownDevices = MXUsersDevicesMap() - - for (userId in keys.userIds) { - val deviceIds = keys.getUserDeviceIds(userId) ?: continue - for (deviceId in deviceIds) { - val deviceInfo = keys.getObject(userId, deviceId) ?: continue - if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) { - // The device is not yet known by the user - unknownDevices.setObject(userId, deviceId, deviceInfo) - continue - } - if (deviceInfo.isBlocked) { - // Remove any blocked devices - devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.BLACKLISTED) - continue - } - - if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) { - devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.UNVERIFIED) - continue - } - - if (deviceInfo.identityKey() == olmDevice.deviceCurve25519Key) { - // Don't bother sending to ourself - continue - } - devicesInRoom.allowedDevices.setObject(userId, deviceId, deviceInfo) - } - } - if (unknownDevices.isEmpty) { - return devicesInRoom - } else { - throw MXCryptoError.UnknownDevice(unknownDevices) - } - } - - override suspend fun reshareKey(groupSessionId: String, - userId: String, - deviceId: String, - senderKey: String): Boolean { - Timber.tag(loggerTag.value).i("process reshareKey for $groupSessionId to $userId:$deviceId") - val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false - .also { Timber.tag(loggerTag.value).w("reshareKey: Device not found") } - - // Get the chain index of the key we previously sent this device - val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, groupSessionId, deviceInfo) - if (!wasSessionSharedWithUser.found) { - // This session was never shared with this user - // Send a room key with held - notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), groupSessionId, senderKey, WithHeldCode.UNAUTHORISED) - Timber.tag(loggerTag.value).w("reshareKey: ERROR : Never shared megolm with this device") - return false - } - // if found chain index should not be null - val chainIndex = wasSessionSharedWithUser.chainIndex ?: return false - .also { - Timber.tag(loggerTag.value).w("reshareKey: Null chain index") - } - - val devicesByUser = mapOf(userId to listOf(deviceInfo)) - val usersDeviceMap = try { - ensureOlmSessionsForDevicesAction.handle(devicesByUser) - } catch (failure: Throwable) { - null - } - val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId) - if (olmSessionResult?.sessionId == null) { - Timber.tag(loggerTag.value).w("reshareKey: no session with this device, probably because there were no one-time keys") - return false - } - Timber.tag(loggerTag.value).i(" reshareKey: $groupSessionId:$chainIndex with device $userId:$deviceId using session ${olmSessionResult.sessionId}") - - val sessionHolder = try { - olmDevice.getInboundGroupSession(groupSessionId, senderKey, roomId) - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice: failed to get session $groupSessionId") - return false - } - - val export = sessionHolder.mutex.withLock { - sessionHolder.wrapper.exportKeys() - } ?: return false.also { - Timber.tag(loggerTag.value).e("shareKeysWithDevice: failed to export group session $groupSessionId") - } - - val payloadJson = mapOf( - "type" to EventType.FORWARDED_ROOM_KEY, - "content" to export - ) - - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(userId, deviceId, encodedPayload) - Timber.tag(loggerTag.value).i("reshareKey() : sending session $groupSessionId to $userId:$deviceId") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - return try { - sendToDeviceTask.execute(sendToDeviceParams) - Timber.tag(loggerTag.value).i("reshareKey() : successfully send <$groupSessionId> to $userId:$deviceId") - true - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).e(failure, "reshareKey() : fail to send <$groupSessionId> to $userId:$deviceId") - false - } - } - - data class DeviceInRoomInfo( - val allowedDevices: MXUsersDevicesMap = MXUsersDevicesMap(), - val withHeldDevices: MXUsersDevicesMap = MXUsersDevicesMap() - ) - - data class UserDevice( - val userId: String, - val deviceId: String - ) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt deleted file mode 100644 index 5c22e5f40c..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.megolm - -import kotlinx.coroutines.CoroutineScope -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.di.DeviceId -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.time.Clock -import javax.inject.Inject - -internal class MXMegolmEncryptionFactory @Inject constructor( - private val olmDevice: MXOlmDevice, -// private val defaultKeysBackupService: DefaultKeysBackupService, - private val cryptoStore: IMXCryptoStore, - private val deviceListManager: DeviceListManager, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - @UserId private val userId: String, - @DeviceId private val deviceId: String?, - private val sendToDeviceTask: SendToDeviceTask, - private val messageEncrypter: MessageEncrypter, - private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope, - private val clock: Clock, -) { - - fun create(roomId: String): MXMegolmEncryption { - return MXMegolmEncryption( - roomId = roomId, - olmDevice = olmDevice, -// defaultKeysBackupService = defaultKeysBackupService, - cryptoStore = cryptoStore, - deviceListManager = deviceListManager, - ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction, - myUserId = userId, - myDeviceId = deviceId!!, - sendToDeviceTask = sendToDeviceTask, - messageEncrypter = messageEncrypter, - warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository, - coroutineDispatchers = coroutineDispatchers, - cryptoCoroutineScope = cryptoCoroutineScope, - clock = clock, - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt deleted file mode 100644 index 28d925d8fd..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.megolm - -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber - -internal class MXOutboundSessionInfo( - // The id of the session - val sessionId: String, - val sharedWithHelper: SharedWithHelper, - private val clock: Clock, - // When the session was created - private val creationTime: Long = clock.epochMillis(), -) { - - // Number of times this session has been used - var useCount: Int = 0 - - fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean { - var needsRotation = false - val sessionLifetime = clock.epochMillis() - creationTime - - if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) { - Timber.v("## needsRotation() : Rotating megolm session after $useCount, ${sessionLifetime}ms") - needsRotation = true - } - - return needsRotation - } - - /** - * Determine if this session has been shared with devices which it shouldn't have been. - * - * @param devicesInRoom the devices map - * @return true if we have shared the session with devices which aren't in devicesInRoom. - */ - fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap): Boolean { - val sharedWithDevices = sharedWithHelper.sharedWithDevices() - val userIds = sharedWithDevices.userIds - - for (userId in userIds) { - if (null == devicesInRoom.getUserDeviceIds(userId)) { - Timber.v("## sharedWithTooManyDevices() : Starting new session because we shared with $userId") - return true - } - - val deviceIds = sharedWithDevices.getUserDeviceIds(userId) - - for (deviceId in deviceIds!!) { - if (null == devicesInRoom.getObject(userId, deviceId)) { - Timber.v("## sharedWithTooManyDevices() : Starting new session because we shared with $userId:$deviceId") - return true - } - } - } - - return false - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt deleted file mode 100644 index 61ad345c62..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.megolm - -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore - -internal class SharedWithHelper( - private val roomId: String, - private val sessionId: String, - private val cryptoStore: IMXCryptoStore) { - - fun sharedWithDevices(): MXUsersDevicesMap { - return cryptoStore.getSharedWithInfo(roomId, sessionId) - } - - fun markedSessionAsShared(deviceInfo: CryptoDeviceInfo, chainIndex: Int) { - cryptoStore.markedSessionAsShared( - roomId = roomId, - sessionId = sessionId, - userId = deviceInfo.userId, - deviceId = deviceInfo.deviceId, - deviceIdentityKey = deviceInfo.identityKey() ?: "", - chainIndex = chainIndex - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt deleted file mode 100644 index 1e66fe84c9..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.olm - -import kotlinx.coroutines.sync.withLock -import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent -import org.matrix.android.sdk.api.session.events.model.content.OlmPayloadContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE -import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting -import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.internal.util.convertFromUTF8 -import timber.log.Timber - -private val loggerTag = LoggerTag("MXOlmDecryption", LoggerTag.CRYPTO) - -internal class MXOlmDecryption( - // The olm device interface - private val olmDevice: MXOlmDevice, - // the matrix userId - private val userId: String) : - IMXDecrypting { - - @Throws(MXCryptoError::class) - override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { - val olmEventContent = event.content.toModel() ?: run { - Timber.tag(loggerTag.value).e("## decryptEvent() : bad event format") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.BAD_EVENT_FORMAT, - MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON - ) - } - - val cipherText = olmEventContent.ciphertext ?: run { - Timber.tag(loggerTag.value).e("## decryptEvent() : missing cipher text") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.MISSING_CIPHER_TEXT, - MXCryptoError.MISSING_CIPHER_TEXT_REASON - ) - } - - val senderKey = olmEventContent.senderKey ?: run { - Timber.tag(loggerTag.value).e("## decryptEvent() : missing sender key") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.MISSING_SENDER_KEY, - MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON - ) - } - - val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run { - Timber.tag(loggerTag.value).e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients") - throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON) - } - - // The message for myUser - @Suppress("UNCHECKED_CAST") - val message = messageAny as JsonDict - - val decryptedPayload = decryptMessage(message, senderKey) - - if (decryptedPayload == null) { - Timber.tag(loggerTag.value).e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) - } - val payloadString = convertFromUTF8(decryptedPayload) - - val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) - val payload = adapter.fromJson(payloadString) - - if (payload == null) { - Timber.tag(loggerTag.value).e("## decryptEvent failed : null payload") - throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON) - } - - val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run { - Timber.tag(loggerTag.value).e("## decryptEvent() : bad olmPayloadContent format") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) - } - - if (olmPayloadContent.recipient.isNullOrBlank()) { - val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient") - Timber.tag(loggerTag.value).e("## decryptEvent() : $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason) - } - - if (olmPayloadContent.recipient != userId) { - Timber.tag(loggerTag.value).e( - "## decryptEvent() : Event ${event.eventId}:" + - " Intended recipient ${olmPayloadContent.recipient} does not match our id $userId" - ) - throw MXCryptoError.Base( - MXCryptoError.ErrorType.BAD_RECIPIENT, - String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient) - ) - } - - val recipientKeys = olmPayloadContent.recipientKeys ?: run { - Timber.tag(loggerTag.value).e( - "## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" + - " property; cannot prevent unknown-key attack" - ) - throw MXCryptoError.Base( - MXCryptoError.ErrorType.MISSING_PROPERTY, - String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys") - ) - } - - val ed25519 = recipientKeys["ed25519"] - - if (ed25519 != olmDevice.deviceEd25519Key) { - Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.BAD_RECIPIENT_KEY, - MXCryptoError.BAD_RECIPIENT_KEY_REASON - ) - } - - if (olmPayloadContent.sender.isNullOrBlank()) { - Timber.tag(loggerTag.value) - .e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.MISSING_PROPERTY, - String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender") - ) - } - - if (olmPayloadContent.sender != event.senderId) { - Timber.tag(loggerTag.value) - .e("Event ${event.eventId}: sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.FORWARDED_MESSAGE, - String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender) - ) - } - - if (olmPayloadContent.roomId != event.roomId) { - Timber.tag(loggerTag.value) - .e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.BAD_ROOM, - String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId) - ) - } - - val keys = olmPayloadContent.keys ?: run { - Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys") - throw MXCryptoError.Base( - MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, - MXCryptoError.MISSING_CIPHER_TEXT_REASON - ) - } - - return MXEventDecryptionResult( - clearEvent = payload, - senderCurve25519Key = senderKey, - claimedEd25519Key = keys["ed25519"] - ) - } - - /** - * Attempt to decrypt an Olm message. - * - * @param theirDeviceIdentityKey the Curve25519 identity key of the sender. - * @param message message object, with 'type' and 'body' fields. - * @return payload, if decrypted successfully. - */ - private suspend fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { - val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) - - val messageBody = message["body"] as? String ?: return null - val messageType = when (val typeAsVoid = message["type"]) { - is Double -> typeAsVoid.toInt() - is Int -> typeAsVoid - is Long -> typeAsVoid.toInt() - else -> return null - } - - // Try each session in turn - // decryptionErrors = {}; - - val isPreKey = messageType == 0 - // we want to synchronize on prekey if not we could end up create two olm sessions - // Not very clear but it looks like the js-sdk for consistency - return if (isPreKey) { - olmDevice.mutex.withLock { - reallyDecryptMessage(sessionIds, messageBody, messageType, theirDeviceIdentityKey) - } - } else { - reallyDecryptMessage(sessionIds, messageBody, messageType, theirDeviceIdentityKey) - } - } - - private suspend fun reallyDecryptMessage(sessionIds: List, messageBody: String, messageType: Int, theirDeviceIdentityKey: String): String? { - Timber.tag(loggerTag.value).d("decryptMessage() try to decrypt olm message type:$messageType from ${sessionIds.size} known sessions") - for (sessionId in sessionIds) { - val payload = try { - olmDevice.decryptMessage(messageBody, messageType, sessionId, theirDeviceIdentityKey) - } catch (throwable: Exception) { - // As we are trying one by one, we don't really care of the error here - Timber.tag(loggerTag.value).d("decryptMessage() failed with session $sessionId") - null - } - - if (null != payload) { - Timber.tag(loggerTag.value).v("## decryptMessage() : Decrypted Olm message from $theirDeviceIdentityKey with session $sessionId") - return payload - } else { - val foundSession = olmDevice.matchesSession(theirDeviceIdentityKey, sessionId, messageType, messageBody) - - if (foundSession) { - // Decryption failed, but it was a prekey message matching this - // session, so it should have worked. - Timber.tag(loggerTag.value).e("## decryptMessage() : Error decrypting prekey message with existing session id $sessionId:TODO") - return null - } - } - } - - if (messageType != 0) { - // not a prekey message, so it should have matched an existing session, but it - // didn't work. - - if (sessionIds.isEmpty()) { - Timber.tag(loggerTag.value).e("## decryptMessage() : No existing sessions") - } else { - Timber.tag(loggerTag.value).e("## decryptMessage() : Error decrypting non-prekey message with existing sessions") - } - - return null - } - - // prekey message which doesn't match any existing sessions: make a new - // session. - // XXXX Possible races here? if concurrent access for same prekey message, we might create 2 sessions? - Timber.tag(loggerTag.value).d("## decryptMessage() : Create inbound group session from prekey sender:$theirDeviceIdentityKey") - - val res = olmDevice.createInboundSession(theirDeviceIdentityKey, messageType, messageBody) - - if (null == res) { - Timber.tag(loggerTag.value).e("## decryptMessage() : Error decrypting non-prekey message with existing sessions") - return null - } - - Timber.tag(loggerTag.value).v("## decryptMessage() : Created new inbound Olm session get id ${res["session_id"]} with $theirDeviceIdentityKey") - - return res["payload"] - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt deleted file mode 100644 index c842c54041..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.olm - -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForUsersAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore - -internal class MXOlmEncryption( - private val roomId: String, - private val olmDevice: MXOlmDevice, - private val cryptoStore: IMXCryptoStore, - private val messageEncrypter: MessageEncrypter, - private val deviceListManager: DeviceListManager, - private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) : - IMXEncrypting { - - override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content { - // pick the list of recipients based on the membership list. - // - // TODO: there is a race condition here! What if a new user turns up - ensureSession(userIds) - val deviceInfos = ArrayList() - for (userId in userIds) { - val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty() - for (device in devices) { - val key = device.identityKey() - if (key == olmDevice.deviceCurve25519Key) { - // Don't bother setting up session to ourself - continue - } - if (device.isBlocked) { - // Don't bother setting up sessions with blocked users - continue - } - deviceInfos.add(device) - } - } - - val messageMap = mapOf( - "room_id" to roomId, - "type" to eventType, - "content" to eventContent - ) - - messageEncrypter.encryptMessage(messageMap, deviceInfos) - return messageMap.toContent() - } - - /** - * Ensure that the session - * - * @param users the user ids list - */ - private suspend fun ensureSession(users: List) { - deviceListManager.downloadKeys(users, false) - ensureOlmSessionsForUsersAction.handle(users) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt deleted file mode 100644 index 44e55900e4..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.algorithms.olm - -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForUsersAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import javax.inject.Inject - -internal class MXOlmEncryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, - private val cryptoStore: IMXCryptoStore, - private val messageEncrypter: MessageEncrypter, - private val deviceListManager: DeviceListManager, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) { - - fun create(roomId: String): MXOlmEncryption { - return MXOlmEncryption( - roomId, - olmDevice, - cryptoStore, - messageEncrypter, - deviceListManager, - ensureOlmSessionsForUsersAction - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt deleted file mode 100644 index 02ea943284..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.matrix.android.sdk.internal.crypto.crosssigning - -import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.task.Task -import javax.inject.Inject - -internal interface ComputeTrustTask : Task { - data class Params( - val activeMemberUserIds: List, - val isDirectRoom: Boolean - ) -} - -internal class DefaultComputeTrustTask @Inject constructor( - private val cryptoStore: IMXCryptoStore, - @UserId private val userId: String, - private val coroutineDispatchers: MatrixCoroutineDispatchers -) : ComputeTrustTask { - - override suspend fun execute(params: ComputeTrustTask.Params): RoomEncryptionTrustLevel = withContext(coroutineDispatchers.crypto) { - // The set of “all users” depends on the type of room: - // For regular / topic rooms, all users including yourself, are considered when decorating a room - // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room - val listToCheck = if (params.isDirectRoom) { - params.activeMemberUserIds.filter { it != userId } - } else { - params.activeMemberUserIds - } - - val allTrustedUserIds = listToCheck - .filter { userId -> getUserCrossSigningKeys(userId)?.isTrusted() == true } - - if (allTrustedUserIds.isEmpty()) { - RoomEncryptionTrustLevel.Default - } else { - // If one of the verified user as an untrusted device -> warning - // If all devices of all verified users are trusted -> green - // else -> black - allTrustedUserIds - .mapNotNull { cryptoStore.getUserDeviceList(it) } - .flatten() - .let { allDevices -> - if (getMyCrossSigningKeys() != null) { - allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } - } else { - // Legacy method - allDevices.any { !it.isVerified } - } - } - .let { hasWarning -> - if (hasWarning) { - RoomEncryptionTrustLevel.Warning - } else { - if (listToCheck.size == allTrustedUserIds.size) { - // all users are trusted and all devices are verified - RoomEncryptionTrustLevel.Trusted - } else { - RoomEncryptionTrustLevel.Default - } - } - } - } - } - - private fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { - return cryptoStore.getCrossSigningInfo(otherUserId) - } - - private fun getMyCrossSigningKeys(): MXCrossSigningInfo? { - return cryptoStore.getMyCrossSigningInfo() - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt deleted file mode 100644 index b8cdc922cc..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ /dev/null @@ -1,806 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.crosssigning - -import androidx.lifecycle.LiveData -import androidx.work.BackoffPolicy -import androidx.work.ExistingWorkPolicy -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService -import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustResult -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo -import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult -import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified -import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified -import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.api.util.fromBase64 -import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager -import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask -import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask -import org.matrix.android.sdk.internal.di.SessionId -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.di.WorkManagerProvider -import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.TaskThread -import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.logLimit -import org.matrix.android.sdk.internal.worker.WorkerParamsFactory -import org.matrix.olm.OlmPkSigning -import org.matrix.olm.OlmUtility -import timber.log.Timber -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -@SessionScope -internal class DefaultCrossSigningService @Inject constructor( - @UserId private val userId: String, - @SessionId private val sessionId: String, - private val cryptoStore: IMXCryptoStore, - private val deviceListManager: DeviceListManager, - private val initializeCrossSigningTask: InitializeCrossSigningTask, - private val uploadSignaturesTask: UploadSignaturesTask, - private val taskExecutor: TaskExecutor, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope, - private val workManagerProvider: WorkManagerProvider, - private val outgoingKeyRequestManager: OutgoingKeyRequestManager, - private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository -) : CrossSigningService, - DeviceListManager.UserDevicesUpdateListener { - - private var olmUtility: OlmUtility? = null - - private var masterPkSigning: OlmPkSigning? = null - private var userPkSigning: OlmPkSigning? = null - private var selfSigningPkSigning: OlmPkSigning? = null - - init { - try { - olmUtility = OlmUtility() - - // Try to get stored keys if they exist - cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo -> - Timber.i("## CrossSigning - Found Existing self signed keys") - Timber.i("## CrossSigning - Checking if private keys are known") - - cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo -> - privateKeysInfo.master - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning = pkSigning - Timber.i("## CrossSigning - Loading master key success") - } else { - Timber.w("## CrossSigning - Public master key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - privateKeysInfo.user - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading User Signing key success") - } else { - Timber.w("## CrossSigning - Public User key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - privateKeysInfo.selfSigned - ?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading Self Signing key success") - } else { - Timber.w("## CrossSigning - Public Self Signing key does not match the private key") - pkSigning.releaseSigning() - // TODO untrust? - } - } - } - - // Recover local trust in case private key are there? - setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified()) - } - } catch (e: Throwable) { - // Mmm this kind of a big issue - Timber.e(e, "Failed to initialize Cross Signing") - } - - deviceListManager.addListener(this) - } - - fun release() { - olmUtility?.releaseUtility() - listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() } - deviceListManager.removeListener(this) - } - - protected fun finalize() { - release() - } - - /** - * - Make 3 key pairs (MSK, USK, SSK) - * - Save the private keys with proper security - * - Sign the keys and upload them - * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures - */ - override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback) { - Timber.d("## CrossSigning initializeCrossSigning") - - val params = InitializeCrossSigningTask.Params( - interactiveAuthInterceptor = uiaInterceptor - ) - initializeCrossSigningTask.configureWith(params) { - this.callbackThread = TaskThread.CRYPTO - this.callback = object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.e(failure, "Error in initializeCrossSigning()") - callback.onFailure(failure) - } - - override fun onSuccess(data: InitializeCrossSigningTask.Result) { - val crossSigningInfo = MXCrossSigningInfo(userId, listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo)) - cryptoStore.setMyCrossSigningInfo(crossSigningInfo) - setUserKeysAsTrusted(userId, true) - cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) - masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } - userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } - selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } - - callback.onSuccess(Unit) - } - } - }.executeBy(taskExecutor) - } - - override fun onSecretMSKGossip(mskPrivateKey: String) { - Timber.i("## CrossSigning - onSecretSSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { - Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known") - } - - mskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning?.releaseSigning() - masterPkSigning = pkSigning - Timber.i("## CrossSigning - Loading MSK success") - cryptoStore.storeMSKPrivateKey(mskPrivateKey) - return - } else { - Timber.e("## CrossSigning - onSecretMSKGossip() private key do not match public key") - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - Timber.e("## CrossSigning - onSecretMSKGossip() ${failure.localizedMessage}") - pkSigning.releaseSigning() - } - } - } - - override fun onSecretSSKGossip(sskPrivateKey: String) { - Timber.i("## CrossSigning - onSecretSSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { - Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known") - } - - sskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading SSK success") - cryptoStore.storeSSKPrivateKey(sskPrivateKey) - return - } else { - Timber.e("## CrossSigning - onSecretSSKGossip() private key do not match public key") - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - Timber.e("## CrossSigning - onSecretSSKGossip() ${failure.localizedMessage}") - pkSigning.releaseSigning() - } - } - } - - override fun onSecretUSKGossip(uskPrivateKey: String) { - Timber.i("## CrossSigning - onSecretUSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { - Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ") - } - - uskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning?.releaseSigning() - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading USK success") - cryptoStore.storeUSKPrivateKey(uskPrivateKey) - return - } else { - Timber.e("## CrossSigning - onSecretUSKGossip() private key do not match public key") - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - } - - override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, - uskKeyPrivateKey: String?, - sskPrivateKey: String? - ): UserTrustResult { - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - var masterKeyIsTrusted = false - var userKeyIsTrusted = false - var selfSignedKeyIsTrusted = false - - masterKeyPrivateKey?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning?.releaseSigning() - masterPkSigning = pkSigning - masterKeyIsTrusted = true - Timber.i("## CrossSigning - Loading master key success") - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - - uskKeyPrivateKey?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning?.releaseSigning() - userPkSigning = pkSigning - userKeyIsTrusted = true - Timber.i("## CrossSigning - Loading master key success") - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - - sskPrivateKey?.fromBase64() - ?.let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning - selfSignedKeyIsTrusted = true - Timber.i("## CrossSigning - Loading master key success") - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { - pkSigning.releaseSigning() - } - } - - if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) { - return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo) - } else { - cryptoStore.markMyMasterKeyAsLocallyTrusted(true) - val checkSelfTrust = checkSelfTrust() - if (checkSelfTrust.isVerified()) { - cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey) - setUserKeysAsTrusted(userId, true) - } - return checkSelfTrust - } - } - - /** - * - * ┏━━━━━━━━┓ ┏━━━━━━━━┓ - * ┃ ALICE ┃ ┃ BOB ┃ - * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶ MSK - * │ - * │ │ - * │ SSK │ - * │ │ - * │ │ - * └──▶ USK ────────────┘ - */ - override fun isUserTrusted(otherUserId: String): Boolean { - return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true - } - - override fun isCrossSigningVerified(): Boolean { - return checkSelfTrust().isVerified() - } - - /** - * Will not force a download of the key, but will verify signatures trust chain - */ - override fun checkUserTrust(otherUserId: String): UserTrustResult { - Timber.v("## CrossSigning checkUserTrust for $otherUserId") - if (otherUserId == userId) { - return checkSelfTrust() - } - // I trust a user if I trust his master key - // I can trust the master key if it is signed by my user key - // TODO what if the master key is signed by a device key that i have verified - - // First let's get my user key - val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) - - checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) - - return UserTrustResult.Success - } - - fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { - val myUserKey = myCrossSigningInfo?.userKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - if (!myCrossSigningInfo.isTrusted()) { - return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) - } - - // Let's get the other user master key - val otherMasterKey = otherInfo?.masterKey() - ?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") - - val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures - ?.get(userId) // Signatures made by me - ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") - - if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey") - return UserTrustResult.KeyNotSigned(otherMasterKey) - } - - // Check that Alice USK signature of Bob MSK is valid - try { - olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable()) - } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey) - } - - return UserTrustResult.Success - } - - private fun checkSelfTrust(): UserTrustResult { - // Special case when it's me, - // I have to check that MSK -> USK -> SSK - // and that MSK is trusted (i know the private key, or is signed by a trusted device) - val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) - - return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(userId)) - } - - fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List?): UserTrustResult { - // Special case when it's me, - // I have to check that MSK -> USK -> SSK - // and that MSK is trusted (i know the private key, or is signed by a trusted device) -// val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId) - - val myMasterKey = myCrossSigningInfo?.masterKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - // Is the master key trusted - // 1) check if I know the private key - val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys() - ?.master - ?.fromBase64() - - var isMaterKeyTrusted = false - if (myMasterKey.trustLevel?.locallyVerified == true) { - isMaterKeyTrusted = true - } else if (masterPrivateKey != null) { - // Check if private match public - var olmPkSigning: OlmPkSigning? = null - try { - olmPkSigning = OlmPkSigning() - val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey) - isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK - } catch (failure: Throwable) { - Timber.e(failure) - } - olmPkSigning?.releaseSigning() - } else { - // Maybe it's signed by a locally trusted device? - myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> - val potentialDeviceId = key.removePrefix("ed25519:") - val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) - if (potentialDevice != null && potentialDevice.isVerified) { - // Check signature validity? - try { - olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable()) - isMaterKeyTrusted = true - return@forEach - } catch (failure: Throwable) { - // log - Timber.w(failure, "Signature not valid?") - } - } - } - } - - if (!isMaterKeyTrusted) { - return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) - } - - val myUserKey = myCrossSigningInfo.userKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures - ?.get(userId) // Signatures made by me - ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") - - if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK") - return UserTrustResult.KeyNotSigned(myUserKey) - } - - // Check that Alice USK signature of Alice MSK is valid - try { - olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable()) - } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey) - } - - val mySSKey = myCrossSigningInfo.selfSigningKey() - ?: return UserTrustResult.CrossSigningNotConfigured(userId) - - val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures - ?.get(userId) // Signatures made by me - ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") - - if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { - Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK") - return UserTrustResult.KeyNotSigned(mySSKey) - } - - // Check that Alice USK signature of Alice MSK is valid - try { - olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable()) - } catch (failure: Throwable) { - return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey) - } - - return UserTrustResult.Success - } - - override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { - return cryptoStore.getCrossSigningInfo(otherUserId) - } - - override fun getLiveCrossSigningKeys(userId: String): LiveData> { - return cryptoStore.getLiveCrossSigningInfo(userId) - } - - override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { - return cryptoStore.getMyCrossSigningInfo() - } - - override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { - return cryptoStore.getCrossSigningPrivateKeys() - } - - override fun getLiveCrossSigningPrivateKeys(): LiveData> { - return cryptoStore.getLiveCrossSigningPrivateKeys() - } - - override fun canCrossSign(): Boolean { - return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null && - cryptoStore.getCrossSigningPrivateKeys()?.user != null - } - - override fun allPrivateKeysKnown(): Boolean { - return checkSelfTrust().isVerified() && - cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse() - } - - override fun trustUser(otherUserId: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - Timber.d("## CrossSigning - Mark user $userId as trusted ") - // We should have this user keys - val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() - if (otherMasterKeys == null) { - callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) - return@launch - } - val myKeys = getUserCrossSigningKeys(userId) - if (myKeys == null) { - callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) - return@launch - } - val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey - if (userPubKey == null || userPkSigning == null) { - callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) - return@launch - } - - // Sign the other MasterKey with our UserSigning key - val newSignature = JsonCanonicalizer.getCanonicalJson( - Map::class.java, - otherMasterKeys.signalableJSONDictionary() - ).let { userPkSigning?.sign(it) } - - if (newSignature == null) { - // race?? - callback.onFailure(Throwable("## CrossSigning - Failed to sign")) - return@launch - } - - cryptoStore.setUserKeysAsTrusted(otherUserId, true) - // TODO update local copy with new signature directly here? kind of local echo of trust? - - Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") - val uploadQuery = UploadSignatureQueryBuilder() - .withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) - .build() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - }.executeBy(taskExecutor) - } - } - - override fun markMyMasterKeyAsTrusted() { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - cryptoStore.markMyMasterKeyAsLocallyTrusted(true) - checkSelfTrust() - // re-verify all trusts - onUsersDeviceUpdate(listOf(userId)) - } - } - - override fun trustDevice(deviceId: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - // This device should be yours - val device = cryptoStore.getUserDevice(userId, deviceId) - if (device == null) { - callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) - return@launch - } - - val myKeys = getUserCrossSigningKeys(userId) - if (myKeys == null) { - callback.onFailure(Throwable("CrossSigning is not setup for this account")) - return@launch - } - - val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey - if (ssPubKey == null || selfSigningPkSigning == null) { - callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) - return@launch - } - - // Sign with self signing - val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable()) - - if (newSignature == null) { - // race?? - callback.onFailure(Throwable("Failed to sign")) - return@launch - } - val toUpload = device.copy( - signatures = mapOf( - userId - to - mapOf( - "ed25519:$ssPubKey" to newSignature - ) - ) - ) - - val uploadQuery = UploadSignatureQueryBuilder() - .withDeviceInfo(toUpload) - .build() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - }.executeBy(taskExecutor) - } - } - - override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { - val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) - ?: return DeviceTrustResult.UnknownDevice(otherDeviceId) - - val myKeys = getUserCrossSigningKeys(userId) - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) - - if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) - - val otherKeys = getUserCrossSigningKeys(otherUserId) - ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId)) - - // TODO should we force verification ? - if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) - - // Check if the trust chain is valid - /* - * ┏━━━━━━━━┓ ┏━━━━━━━━┓ - * ┃ ALICE ┃ ┃ BOB ┃ - * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶MSK - * │ - * │ │ │ - * │ SSK │ └──▶ SSK ──────────────────┐ - * │ │ │ - * │ │ USK │ - * └──▶ USK ────────────┘ (not visible by │ - * Alice) │ - * ▼ - * ┌──────────────┐ - * │ BOB's Device │ - * └──────────────┘ - */ - - val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") - ?: return legacyFallbackTrust( - locallyTrusted, - DeviceTrustResult.MissingDeviceSignature( - otherDeviceId, otherKeys.selfSigningKey() - ?.unpaddedBase64PublicKey - ?: "" - ) - ) - - // Check bob's device is signed by bob's SSK - try { - olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) - } catch (e: Throwable) { - return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e)) - } - - return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) - } - - fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult { - val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified() - myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId)) - - if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys)) - - otherKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherDevice.userId)) - - // TODO should we force verification ? - if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys)) - - // Check if the trust chain is valid - /* - * ┏━━━━━━━━┓ ┏━━━━━━━━┓ - * ┃ ALICE ┃ ┃ BOB ┃ - * ┗━━━━━━━━┛ ┗━━━━━━━━┛ - * MSK ┌────────────▶MSK - * │ - * │ │ │ - * │ SSK │ └──▶ SSK ──────────────────┐ - * │ │ │ - * │ │ USK │ - * └──▶ USK ────────────┘ (not visible by │ - * Alice) │ - * ▼ - * ┌──────────────┐ - * │ BOB's Device │ - * └──────────────┘ - */ - - val otherSSKSignature = otherDevice.signatures?.get(otherKeys.userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") - ?: return legacyFallbackTrust( - locallyTrusted, - DeviceTrustResult.MissingDeviceSignature( - otherDevice.deviceId, otherKeys.selfSigningKey() - ?.unpaddedBase64PublicKey - ?: "" - ) - ) - - // Check bob's device is signed by bob's SSK - try { - olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) - } catch (e: Throwable) { - return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e)) - } - - return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted)) - } - - private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult { - return if (locallyTrusted == true) { - DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true)) - } else { - crossSignTrustFail - } - } - - override fun onUsersDeviceUpdate(userIds: List) { - Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") - val workerParams = UpdateTrustWorker.Params( - sessionId = sessionId, - filename = updateTrustWorkerDataRepository.createParam(userIds) - ) - val workerData = WorkerParamsFactory.toData(workerParams) - - val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() - .setInputData(workerData) - .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) - .build() - - workManagerProvider.workManager - .beginUniqueWork("TRUST_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) - .enqueue() - } - - private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { - val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() - cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) - // If it's me, recheck trust of all users and devices? - val users = ArrayList() - if (otherUserId == userId && currentTrust != trusted) { - // notify key requester - outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted) - cryptoStore.updateUsersTrust { - users.add(it) - checkUserTrust(it).isVerified() - } - - users.forEach { - cryptoStore.getUserDeviceList(it)?.forEach { device -> - val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") - cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) - } - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt deleted file mode 100644 index 74f0f5745d..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.crosssigning - -import android.content.Context -import androidx.work.WorkerParameters -import com.squareup.moshi.JsonClass -import io.realm.Realm -import io.realm.RealmConfiguration -import io.realm.kotlin.where -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult -import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified -import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified -import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel -import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper -import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields -import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper -import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields -import org.matrix.android.sdk.internal.database.awaitTransaction -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.CryptoDatabase -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.SessionComponent -import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import org.matrix.android.sdk.internal.util.logLimit -import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker -import org.matrix.android.sdk.internal.worker.SessionWorkerParams -import timber.log.Timber -import javax.inject.Inject - -internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { - - @JsonClass(generateAdapter = true) - internal data class Params( - override val sessionId: String, - override val lastFailureMessage: String? = null, - // Kept for compatibility, but not used anymore (can be used for pending Worker) - val updatedUserIds: List? = null, - // Passing a long list of userId can break the Work Manager due to data size limitation. - // so now we use a temporary file to store the data - val filename: String? = null - ) : SessionWorkerParams - - @Inject lateinit var crossSigningService: DefaultCrossSigningService - - // It breaks the crypto store contract, but we need to batch things :/ - @CryptoDatabase - @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration - - @SessionDatabase - @Inject lateinit var sessionRealmConfiguration: RealmConfiguration - - @UserId - @Inject lateinit var myUserId: String - @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper - @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository - - // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater - @Inject lateinit var cryptoStore: IMXCryptoStore - - override fun injectWith(injector: SessionComponent) { - injector.inject(this) - } - - override suspend fun doSafeWork(params: Params): Result { - val userList = params.filename - ?.let { updateTrustWorkerDataRepository.getParam(it) } - ?.userIds - ?: params.updatedUserIds.orEmpty() - - // List should not be empty, but let's avoid go further in case of empty list - if (userList.isNotEmpty()) { - // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, - // or a new device?) So we check all again :/ - Timber.v("## CrossSigning - Updating trust for users: ${userList.logLimit()}") - updateTrust(userList) - } - - cleanup(params) - return Result.success() - } - - private suspend fun updateTrust(userListParam: List) { - var userList = userListParam - var myCrossSigningInfo: MXCrossSigningInfo? = null - - // First we check that the users MSK are trusted by mine - // After that we check the trust chain for each devices of each users - awaitTransaction(cryptoRealmConfiguration) { cryptoRealm -> - // By mapping here to model, this object is not live - // I should update it if needed - myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) - - var myTrustResult: UserTrustResult? = null - - if (userList.contains(myUserId)) { - Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") - // i am in the list.. but i don't know exactly the delta of change :/ - // If it's my cross signing keys we should refresh all trust - // do it anyway ? - userList = cryptoRealm.where(CrossSigningInfoEntity::class.java) - .findAll() - .mapNotNull { it.userId } - - // check right now my keys and mark it as trusted as other trust depends on it - val myDevices = cryptoRealm.where() - .equalTo(UserEntityFields.USER_ID, myUserId) - .findFirst() - ?.devices - ?.map { CryptoMapper.mapToModel(it) } - - myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices) - updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified()) - // update model reference - myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) - } - - val otherInfos = userList.associateWith { userId -> - getCrossSigningInfo(cryptoRealm, userId) - } - - val trusts = otherInfos.mapValues { entry -> - when (entry.key) { - myUserId -> myTrustResult - else -> { - crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also { - Timber.v("## CrossSigning - user:${entry.key} result:$it") - } - } - } - } - - // TODO! if it's me and my keys has changed... I have to reset trust for everyone! - // i have all the new trusts, update DB - trusts.forEach { - val verified = it.value?.isVerified() == true - updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) - } - - // Ok so now we have to check device trust for all these users.. - Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}") - trusts.keys.forEach { userId -> - val devicesEntities = cryptoRealm.where() - .equalTo(UserEntityFields.USER_ID, userId) - .findFirst() - ?.devices - - val trustMap = devicesEntities?.associateWith { device -> - // get up to date from DB has could have been updated - val otherInfo = getCrossSigningInfo(cryptoRealm, userId) - crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) - } - - // Update trust if needed - devicesEntities?.forEach { device -> - val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() - Timber.v("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") - if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { - Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") - // need to save - val trustEntity = device.trustLevelEntity - if (trustEntity == null) { - device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { - it.locallyVerified = false - it.crossSignedVerified = crossSignedVerified - } - } else { - trustEntity.crossSignedVerified = crossSignedVerified - } - } - } - } - } - - // So Cross Signing keys trust is updated, device trust is updated - // We can now update room shields? in the session DB? - updateTrustStep2(userList, myCrossSigningInfo) - } - - private suspend fun updateTrustStep2(userList: List, myCrossSigningInfo: MXCrossSigningInfo?) { - Timber.d("## CrossSigning - Updating shields for impacted rooms...") - awaitTransaction(sessionRealmConfiguration) { sessionRealm -> - Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> - sessionRealm.where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) - .distinct(RoomMemberSummaryEntityFields.ROOM_ID) - .findAll() - .map { it.roomId } - .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } - .forEach { roomId -> - RoomSummaryEntity.where(sessionRealm, roomId) - .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) - .findFirst() - ?.let { roomSummary -> - Timber.v("## CrossSigning - Check shield state for room $roomId") - val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() - try { - val updatedTrust = computeRoomShield( - myCrossSigningInfo, - cryptoRealm, - allActiveRoomMembers, - roomSummary - ) - if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { - Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") - roomSummary.roomEncryptionTrustLevel = updatedTrust - } - } catch (failure: Throwable) { - Timber.e(failure) - } - } - } - } - } - } - - private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? { - return cryptoRealm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) - .findFirst() - ?.let { mapCrossSigningInfoEntity(it) } - } - - private fun cleanup(params: Params) { - params.filename - ?.let { updateTrustWorkerDataRepository.delete(it) } - } - - private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) { - cryptoRealm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) - .findFirst() - ?.crossSigningKeys - ?.forEach { info -> - // optimization to avoid trigger updates when there is no change.. - if (info.trustLevelEntity?.isVerified() != verified) { - Timber.d("## CrossSigning - Trust change for $userId : $verified") - val level = info.trustLevelEntity - if (level == null) { - info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { - it.locallyVerified = verified - it.crossSignedVerified = verified - } - } else { - level.locallyVerified = verified - level.crossSignedVerified = verified - } - } - } - } - - private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?, - cryptoRealm: Realm, - activeMemberUserIds: List, - roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { - Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}") - // The set of “all users” depends on the type of room: - // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room - // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room - val listToCheck = if (roomSummaryEntity.isDirect || activeMemberUserIds.size <= 2) { - activeMemberUserIds.filter { it != myUserId } - } else { - activeMemberUserIds - } - - val allTrustedUserIds = listToCheck - .filter { userId -> - getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true - } - - return if (allTrustedUserIds.isEmpty()) { - RoomEncryptionTrustLevel.Default - } else { - // If one of the verified user as an untrusted device -> warning - // If all devices of all verified users are trusted -> green - // else -> black - allTrustedUserIds - .mapNotNull { userId -> - cryptoRealm.where() - .equalTo(UserEntityFields.USER_ID, userId) - .findFirst() - ?.devices - ?.map { CryptoMapper.mapToModel(it) } - } - .flatten() - .let { allDevices -> - Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }.logLimit()}") - if (myCrossSigningInfo != null) { - allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } - } else { - // Legacy method - allDevices.any { !it.isVerified } - } - } - .let { hasWarning -> - if (hasWarning) { - RoomEncryptionTrustLevel.Warning - } else { - if (listToCheck.size == allTrustedUserIds.size) { - // all users are trusted and all devices are verified - RoomEncryptionTrustLevel.Trusted - } else { - RoomEncryptionTrustLevel.Default - } - } - } - } - } - - private fun mapCrossSigningInfoEntity(xsignInfo: CrossSigningInfoEntity): MXCrossSigningInfo { - val userId = xsignInfo.userId ?: "" - return MXCrossSigningInfo( - userId = userId, - crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull { - crossSigningKeysMapper.map(userId, it) - } - ) - } - - override fun buildErrorParams(params: Params, message: String): Params { - return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt index 72aeece896..37ef4ea29b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/RustKeyBackupService.kt @@ -28,31 +28,33 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.MegolmSessionImportManager import org.matrix.android.sdk.internal.crypto.OlmMachineProvider -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.network.RequestSender -import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.JsonCanonicalizer @@ -263,7 +265,7 @@ internal class RustKeyBackupService @Inject constructor( private suspend fun checkBackupTrust(authData: MegolmBackupAuthData?): KeysBackupVersionTrust { return if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") - KeysBackupVersionTrust() + KeysBackupVersionTrust(usable = false) } else { KeysBackupVersionTrust(olmMachine.checkAuthDataSignature(authData)) } @@ -375,7 +377,7 @@ internal class RustKeyBackupService @Inject constructor( Timber.i("## CrossSigning - onSecretKeyGossip") withContext(coroutineDispatchers.crypto) { try { - val version = sender.getKeyBackupVersion() + val version = sender.getKeyBackupLastVersion()?.toKeysVersionResult() if (version != null) { val key = BackupRecoveryKey.fromBase64(secret) @@ -584,13 +586,13 @@ internal class RustKeyBackupService @Inject constructor( } @Throws - override suspend fun getCurrentVersion(): KeysVersionResult? { - return sender.getKeyBackupVersion() + override suspend fun getCurrentVersion(): KeysBackupLastVersionResult? { + return sender.getKeyBackupLastVersion() } override suspend fun forceUsingLastVersion(): Boolean { val response = withContext(coroutineDispatchers.io) { - sender.getKeyBackupVersion() + sender.getKeyBackupLastVersion()?.toKeysVersionResult() } return withContext(coroutineDispatchers.crypto) { @@ -644,7 +646,7 @@ internal class RustKeyBackupService @Inject constructor( keysBackupVersion = null keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver try { - val data = getCurrentVersion() + val data = getCurrentVersion()?.toKeysVersionResult() withContext(coroutineDispatchers.crypto) { checkAndStartWithKeysBackupVersion(data) } @@ -718,6 +720,10 @@ internal class RustKeyBackupService @Inject constructor( } } + override fun computePrivateKey(passphrase: String, privateKeySalt: String, privateKeyIterations: Int, progressListener: ProgressListener): ByteArray { + return deriveKey(passphrase, privateKeySalt, privateKeyIterations, progressListener) + } + override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { val info = olmMachine.getBackupKeys() ?: return null return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt index d1ec8e5589..195a2d5e9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/OutgoingRequestsProcessor.kt @@ -23,8 +23,8 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.OlmMachine import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt index fac164544a..04251ca9c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/network/RequestSender.kt @@ -22,15 +22,18 @@ import dagger.Lazy import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.uia.UiaResult import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask @@ -42,7 +45,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDa import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse @@ -177,7 +179,7 @@ internal class RequestSender @Inject constructor( uploadSigningKeysTask.execute(uploadSigningKeysParams) } catch (failure: Throwable) { if (interactiveAuthInterceptor == null || - !handleUIA( + handleUIA( failure = failure, interceptor = interactiveAuthInterceptor, retryBlock = { authUpdate -> @@ -186,7 +188,7 @@ internal class RequestSender @Inject constructor( REQUEST_RETRY_COUNT ) } - ) + ) != UiaResult.SUCCESS ) { Timber.d("## UIA: propagate failure") throw failure @@ -217,13 +219,17 @@ internal class RequestSender @Inject constructor( sendToDeviceTask.executeRetry(sendToDeviceParams, REQUEST_RETRY_COUNT) } - suspend fun getKeyBackupVersion(version: String? = null): KeysVersionResult? { + suspend fun getKeyBackupVersion(version: String): KeysVersionResult? = getKeyBackupVersion { + getKeysBackupVersionTask.executeRetry(version, 3) + } + + suspend fun getKeyBackupLastVersion(): KeysBackupLastVersionResult? = getKeyBackupVersion { + getKeysBackupLastVersionTask.executeRetry(Unit, 3) + } + + private inline fun getKeyBackupVersion(block: ()-> T?): T?{ return try { - if (version != null) { - getKeysBackupVersionTask.executeRetry(version, 3) - } else { - getKeysBackupLastVersionTask.executeRetry(Unit, 3) - } + block() } catch (failure: Throwable) { if (failure is Failure.ServerError && failure.error.code == MatrixError.M_NOT_FOUND) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt deleted file mode 100644 index 53190c43ff..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.tasks - -import dagger.Lazy -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey -import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage -import org.matrix.android.sdk.api.session.uia.UiaResult -import org.matrix.android.sdk.api.util.toBase64NoPadding -import org.matrix.android.sdk.internal.auth.registration.handleUIA -import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder -import org.matrix.android.sdk.internal.crypto.crosssigning.canonicalSignable -import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.olm.OlmPkSigning -import timber.log.Timber -import javax.inject.Inject - -internal interface InitializeCrossSigningTask : Task { - data class Params( - val interactiveAuthInterceptor: UserInteractiveAuthInterceptor? - ) - - data class Result( - val masterKeyPK: String, - val userKeyPK: String, - val selfSigningKeyPK: String, - val masterKeyInfo: CryptoCrossSigningKey, - val userKeyInfo: CryptoCrossSigningKey, - val selfSignedKeyInfo: CryptoCrossSigningKey - ) -} - -internal class DefaultInitializeCrossSigningTask @Inject constructor( - @UserId private val userId: String, - private val olmDevice: MXOlmDevice, - private val myDeviceInfoHolder: Lazy, - private val uploadSigningKeysTask: UploadSigningKeysTask, - private val uploadSignaturesTask: UploadSignaturesTask -) : InitializeCrossSigningTask { - - override suspend fun execute(params: InitializeCrossSigningTask.Params): InitializeCrossSigningTask.Result { - var masterPkOlm: OlmPkSigning? = null - var userSigningPkOlm: OlmPkSigning? = null - var selfSigningPkOlm: OlmPkSigning? = null - - try { - // ================= - // MASTER KEY - // ================= - - masterPkOlm = OlmPkSigning() - val masterKeyPrivateKey = OlmPkSigning.generateSeed() - val masterPublicKey = masterPkOlm.initWithSeed(masterKeyPrivateKey) - - Timber.v("## CrossSigning - masterPublicKey:$masterPublicKey") - - // ================= - // USER KEY - // ================= - userSigningPkOlm = OlmPkSigning() - val uskPrivateKey = OlmPkSigning.generateSeed() - val uskPublicKey = userSigningPkOlm.initWithSeed(uskPrivateKey) - - Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey") - - // Sign userSigningKey with master - val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) - .key(uskPublicKey) - .build() - .canonicalSignable() - .let { masterPkOlm.sign(it) } - - // ================= - // SELF SIGNING KEY - // ================= - selfSigningPkOlm = OlmPkSigning() - val sskPrivateKey = OlmPkSigning.generateSeed() - val sskPublicKey = selfSigningPkOlm.initWithSeed(sskPrivateKey) - - Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") - - // Sign selfSigningKey with master - val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) - .key(sskPublicKey) - .build() - .canonicalSignable() - .let { masterPkOlm.sign(it) } - - // I need to upload the keys - val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER) - .key(masterPublicKey) - .build() - val uploadSigningKeysParams = UploadSigningKeysTask.Params( - masterKey = mskCrossSigningKeyInfo, - userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) - .key(uskPublicKey) - .signature(userId, masterPublicKey, signedUSK) - .build(), - selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) - .key(sskPublicKey) - .signature(userId, masterPublicKey, signedSSK) - .build(), - userAuthParam = null -// userAuthParam = params.authParams - ) - - try { - uploadSigningKeysTask.execute(uploadSigningKeysParams) - } catch (failure: Throwable) { - if (params.interactiveAuthInterceptor == null || - handleUIA( - failure = failure, - interceptor = params.interactiveAuthInterceptor, - retryBlock = { authUpdate -> - uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate)) - } - ) != UiaResult.SUCCESS - ) { - Timber.d("## UIA: propagate failure") - throw failure - } - } - - // Sign the current device with SSK - val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder() - - val myDevice = myDeviceInfoHolder.get().myDevice - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) - val signedDevice = selfSigningPkOlm.sign(canonicalJson) - val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()) - .also { - it[userId] = (it[userId] - ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) - } - myDevice.copy(signatures = updateSignatures).let { - uploadSignatureQueryBuilder.withDeviceInfo(it) - } - - // sign MSK with device key (migration) and upload signatures - val message = JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()) - olmDevice.signMessage(message)?.let { sign -> - val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() - ?: HashMap()).also { - it[userId] = (it[userId] - ?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign) - } - mskCrossSigningKeyInfo.copy( - signatures = mskUpdatedSignatures - ).let { - uploadSignatureQueryBuilder.withSigningKeyInfo(it) - } - } - - // TODO should we ignore failure of that? - uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) - - return InitializeCrossSigningTask.Result( - masterKeyPK = masterKeyPrivateKey.toBase64NoPadding(), - userKeyPK = uskPrivateKey.toBase64NoPadding(), - selfSigningKeyPK = sskPrivateKey.toBase64NoPadding(), - masterKeyInfo = uploadSigningKeysParams.masterKey, - userKeyInfo = uploadSigningKeysParams.userKey, - selfSignedKeyInfo = uploadSigningKeysParams.selfSignedKey - ) - } finally { - masterPkOlm?.releaseSigning() - userSigningPkOlm?.releaseSigning() - selfSigningPkOlm?.releaseSigning() - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt deleted file mode 100644 index a51fdea9f2..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.verification.qrcode - -import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService -import org.matrix.android.sdk.api.session.crypto.verification.CancelCode -import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction -import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.util.fromBase64 -import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager -import org.matrix.android.sdk.internal.crypto.SecretShareManager -import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe -import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.verification.ValidVerificationInfoStart -import timber.log.Timber - -internal class DefaultQrCodeVerificationTransaction( - setDeviceVerificationAction: SetDeviceVerificationAction, - override val transactionId: String, - override val otherUserId: String, - override var otherDeviceId: String?, - private val crossSigningService: CrossSigningService, - outgoingKeyRequestManager: OutgoingKeyRequestManager, - secretShareManager: SecretShareManager, - private val cryptoStore: IMXCryptoStore, - // Not null only if other user is able to scan QR code - private val qrCodeData: QrCodeData?, - val userId: String, - val deviceId: String, - override val isIncoming: Boolean -) : DefaultVerificationTransaction( - setDeviceVerificationAction, - crossSigningService, - outgoingKeyRequestManager, - secretShareManager, - userId, - transactionId, - otherUserId, - otherDeviceId, - isIncoming -), - QrCodeVerificationTransaction { - - override val qrCodeText: String? - get() = qrCodeData?.toEncodedString() - - override var state: VerificationTxState = VerificationTxState.None - set(newState) { - field = newState - - listeners.forEach { - try { - it.transactionUpdated(this) - } catch (e: Throwable) { - Timber.e(e, "## Error while notifying listeners") - } - } - } - - override fun userHasScannedOtherQrCode(otherQrCodeText: String) { - val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run { - Timber.d("## Verification QR: Invalid QR Code Data") - cancel(CancelCode.QrCodeInvalid) - return - } - - // Perform some checks - if (otherQrCodeData.transactionId != transactionId) { - Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.transactionId} expected:$transactionId") - cancel(CancelCode.QrCodeInvalid) - return - } - - // check master key - val myMasterKey = crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey - var canTrustOtherUserMasterKey = false - - // Check the other device view of my MSK - when (otherQrCodeData) { - is QrCodeData.VerifyingAnotherUser -> { - // key2 (aka otherUserMasterCrossSigningPublicKey) is what the one displaying the QR code (other user) think my MSK is. - // Let's check that it's correct - // If not -> Cancel - if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != myMasterKey) { - Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}") - cancel(CancelCode.MismatchedKeys) - return - } else Unit - } - is QrCodeData.SelfVerifyingMasterKeyTrusted -> { - // key1 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK. - // Let's check that I see the same MSK - // If not -> Cancel - if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) { - Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}") - cancel(CancelCode.MismatchedKeys) - return - } else { - // I can trust the MSK then (i see the same one, and other session tell me it's trusted by him) - canTrustOtherUserMasterKey = true - } - } - is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> { - // key2 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK. - // Let's check that it's the good one - // If not -> Cancel - if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) { - Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}") - cancel(CancelCode.MismatchedKeys) - return - } else { - // Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK - } - } - } - - val toVerifyDeviceIds = mutableListOf() - - // Let's now check the other user/device key material - when (otherQrCodeData) { - is QrCodeData.VerifyingAnotherUser -> { - // key1(aka userMasterCrossSigningPublicKey) is the MSK of the one displaying the QR code (i.e other user) - // Let's check that it matches what I think it should be - if (otherQrCodeData.userMasterCrossSigningPublicKey - != crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) { - Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}") - cancel(CancelCode.MismatchedKeys) - return - } else { - // It does so i should mark it as trusted - canTrustOtherUserMasterKey = true - Unit - } - } - is QrCodeData.SelfVerifyingMasterKeyTrusted -> { - // key2 (aka otherDeviceKey) is my current device key in POV of the one displaying the QR code (i.e other device) - // Let's check that it's correct - if (otherQrCodeData.otherDeviceKey - != cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) { - Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}") - cancel(CancelCode.MismatchedKeys) - return - } else Unit // Nothing special here, we will send a reciprocate start event, and then the other session will trust my device - // and thus allow me to request SSSS secret - } - is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> { - // key1 (aka otherDeviceKey) is the device key of the one displaying the QR code (i.e other device) - // Let's check that it matches what I have locally - if (otherQrCodeData.deviceKey - != cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) { - Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}") - cancel(CancelCode.MismatchedKeys) - return - } else { - // Yes it does -> i should trust it and sign then upload the signature - toVerifyDeviceIds.add(otherDeviceId ?: "") - Unit - } - } - } - - if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) { - // Nothing to verify - cancel(CancelCode.MismatchedKeys) - return - } - - // All checks are correct - // Send the shared secret so that sender can trust me - // qrCodeData.sharedSecret will be used to send the start request - start(otherQrCodeData.sharedSecret) - - trust( - canTrustOtherUserMasterKey = canTrustOtherUserMasterKey, - toVerifyDeviceIds = toVerifyDeviceIds.distinct(), - eventuallyMarkMyMasterKeyAsTrusted = true, - autoDone = false - ) - } - - private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) { - if (state != VerificationTxState.None) { - Timber.e("## Verification QR: start verification from invalid state") - // should I cancel?? - throw IllegalStateException("Interactive Key verification already started") - } - - state = VerificationTxState.Started - val startMessage = transport.createStartForQrCode( - deviceId, - transactionId, - remoteSecret - ) - - transport.sendToOther( - EventType.KEY_VERIFICATION_START, - startMessage, - VerificationTxState.WaitingOtherReciprocateConfirm, - CancelCode.User, - onDone - ) - } - - override fun cancel() { - cancel(CancelCode.User) - } - - override fun cancel(code: CancelCode) { - state = VerificationTxState.Cancelled(code, true) - transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code) - } - - override fun isToDeviceTransport() = false - - // Other user has scanned our QR code. check that the secret matched, so we can trust him - fun onStartReceived(startReq: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) { - if (qrCodeData == null) { - // Should not happen - cancel(CancelCode.UnexpectedMessage) - return - } - - if (startReq.sharedSecret.fromBase64Safe()?.contentEquals(qrCodeData.sharedSecret.fromBase64()) == true) { - // Ok, we can trust the other user - // We can only trust the master key in this case - // But first, ask the user for a confirmation - state = VerificationTxState.QrScannedByOther - } else { - // Display a warning - cancel(CancelCode.MismatchedKeys) - } - } - - fun onDoneReceived() { - if (state != VerificationTxState.WaitingOtherReciprocateConfirm) { - cancel(CancelCode.UnexpectedMessage) - return - } - state = VerificationTxState.Verified - transport.done(transactionId) {} - } - - override fun otherUserScannedMyQrCode() { - when (qrCodeData) { - is QrCodeData.VerifyingAnotherUser -> { - // Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice's key, - trust(true, emptyList(), false) - } - is QrCodeData.SelfVerifyingMasterKeyTrusted -> { - // I now know that I have the correct device key for other session, - // and can sign it with the self-signing key and upload the signature - trust(false, listOf(otherDeviceId ?: ""), false) - } - is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> { - // I now know that i can trust my MSK - trust(true, emptyList(), true) - } - null -> Unit - } - } - - override fun otherUserDidNotScannedMyQrCode() { - // What can I do then? - // At least remove the transaction... - cancel(CancelCode.MismatchedKeys) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt index 609acdd89c..f61d444925 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt @@ -20,15 +20,14 @@ import org.matrix.android.sdk.api.session.ToDeviceService import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter +import org.matrix.android.sdk.internal.crypto.EncryptEventContentUseCase +import org.matrix.android.sdk.internal.crypto.OlmMachineProvider import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import javax.inject.Inject internal class DefaultToDeviceService @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, - private val messageEncrypter: MessageEncrypter, - private val cryptoStore: IMXCryptoStore ) : ToDeviceService { override suspend fun sendToDevice(eventType: String, targets: Map>, content: Content, txnId: String?) { @@ -53,6 +52,8 @@ internal class DefaultToDeviceService @Inject constructor( } override suspend fun sendEncryptedToDevice(eventType: String, targets: Map>, content: Content, txnId: String?) { + //TODO: add to rust-ffi + /* val payloadJson = mapOf( "type" to eventType, "content" to content @@ -63,11 +64,13 @@ internal class DefaultToDeviceService @Inject constructor( targets.forEach { (userId, deviceIdList) -> deviceIdList.forEach { deviceId -> cryptoStore.getUserDevice(userId, deviceId)?.let { deviceInfo -> - sendToDeviceMap.setObject(userId, deviceId, messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))) + sendToDeviceMap.setObject(userId, deviceId, encryptEventContent(payloadJson, listOf(deviceInfo))) } } } sendToDevice(EventType.ENCRYPTED, sendToDeviceMap, txnId) + + */ } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 3ab1e0206c..71bbb8e44f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.CryptoModule import org.matrix.android.sdk.internal.crypto.OlmMachineProvider -import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.federation.FederationModule import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker @@ -132,8 +131,6 @@ internal interface SessionComponent { fun inject(worker: AddPusherWorker) - fun inject(worker: UpdateTrustWorker) - @Component.Factory interface Factory { fun create( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 6c68910627..22f0647a28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications -import org.matrix.android.sdk.internal.crypto.EventDecryptor +import org.matrix.android.sdk.internal.crypto.OlmMachineProvider import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity @@ -72,11 +72,12 @@ internal class RoomSummaryUpdater @Inject constructor( @UserId private val userId: String, private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver, - private val crossSigningService: CrossSigningService, - private val eventDecryptor: EventDecryptor, + private val olmMachineProvider: OlmMachineProvider, private val roomAccountDataDataSource: RoomAccountDataDataSource ) { + private val olmMachine = olmMachineProvider.olmMachine + fun refreshLatestPreviewContent(realm: Realm, roomId: String) { val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId) if (roomSummaryEntity != null) { @@ -194,7 +195,7 @@ internal class RoomSummaryUpdater @Inject constructor( if (root.type == EventType.ENCRYPTED && root.decryptionResultJson == null) { Timber.v("Should decrypt $eventId") tryOrNull { - runBlocking { eventDecryptor.decryptEvent(root.asDomain(), "") } + runBlocking { olmMachine.decryptRoomEvent(root.asDomain()) } }?.let { root.setDecryptionResult(it) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index 75df8aae1f..36d4aa2bd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -19,8 +19,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.DefaultCryptoService -import org.matrix.android.sdk.internal.crypto.EventDecryptor +import org.matrix.android.sdk.internal.crypto.DecryptEventUseCase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI @@ -38,7 +37,7 @@ internal interface GetEventTask : Task { internal class DefaultGetEventTask @Inject constructor( private val roomAPI: RoomAPI, private val globalErrorReceiver: GlobalErrorReceiver, - private val cryptoService: DefaultCryptoService, + private val decryptEvent: DecryptEventUseCase, private val clock: Clock, ) : GetEventTask { @@ -50,7 +49,7 @@ internal class DefaultGetEventTask @Inject constructor( // Try to decrypt the Event if (event.isEncrypted()) { tryOrNull(message = "Unable to decrypt the event") { - cryptoService.decryptEvent(event, "") + decryptEvent(event) } ?.let { result -> event.mxDecryptionResult = OlmDecryptionResult( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt index 8757c20c0a..c5d7598a46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -42,7 +42,7 @@ internal class TimelineEventDecryptor @Inject constructor( ) { private val newSessionListener = object : NewSessionListener { - override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + override fun onNewSession(roomId: String?, sessionId: String) { synchronized(unknownSessionsFailure) { unknownSessionsFailure[sessionId] ?.toList() diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index f9c2a88daf..4910c74e59 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.verification.CancelCode diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index fc39165a7e..855f5c065e 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -90,7 +90,7 @@ class HomeDetailViewModel @AssistedInject constructor( } private val refreshRoomSummariesOnCryptoSessionChange = object : NewSessionListener { - override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + override fun onNewSession(roomId: String?, sessionId: String) { session.roomService().refreshJoinedRoomSummaryPreviews(roomId) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index d661072d92..39e6e39e14 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -72,6 +72,7 @@ import kotlinx.coroutines.launch import me.gujun.android.span.span import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 88e2ed37cf..f335e1d3c8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.flow.flow diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt index 400845284e..02da3ea879 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.signout.soft import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized