Remove EventDecryptor and inject the cryptoService when needed

Not used anymore in RoomSummaryUpdater, to avoid a DI dependency loop. let's see if this is a problem
This commit is contained in:
Benoit Marty 2021-07-09 12:50:34 +02:00
parent f609bfaf10
commit b26aba9fc0
5 changed files with 11 additions and 217 deletions

View file

@ -642,7 +642,6 @@ internal class DefaultCryptoService @Inject constructor(
*/
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking {
olmMachine!!.decryptRoomEvent(event)
}

View file

@ -1,175 +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.session.crypto.MXCryptoError
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.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.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
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.MatrixCoroutineDispatchers
import timber.log.Timber
import javax.inject.Inject
import kotlin.jvm.Throws
@SessionScope
internal class EventDecryptor @Inject constructor(
private val cryptoCoroutineScope: CoroutineScope,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val roomDecryptorProvider: RoomDecryptorProvider,
private val messageEncrypter: MessageEncrypter,
private val sendToDeviceTask: SendToDeviceTask,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore
) {
// The date of the last time we forced establishment
// of a new session for each user:device.
private val lastNewSessionForcedDates = MXUsersDevicesMap<Long>()
/**
* 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)
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<MXEventDecryptionResult>) {
// 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 fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val eventContent = event.content
if (eventContent == null) {
Timber.e("## CRYPTO | 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.e("## CRYPTO | decryptEvent() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else {
try {
return alg.decryptEvent(event, timeline)
} catch (mxCryptoError: MXCryptoError) {
Timber.v("## CRYPTO | 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
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val olmContent = event.content.toModel<OlmEventContent>()
cryptoStore.getUserDevices(event.senderId ?: "")
?.values
?.firstOrNull { it.identityKey() == olmContent?.senderKey }
?.let {
markOlmSessionForUnwedging(event.senderId ?: "", it)
}
?: run {
Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging")
}
}
}
}
throw mxCryptoError
}
}
}
}
// coroutineDispatchers.crypto scope
private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
val deviceKey = deviceInfo.identityKey()
val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
val now = System.currentTimeMillis()
if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
return
}
Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
// offload this from crypto thread (?)
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
val ensured = ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}")
// Now send a blank message on that session so the other side knows about it.
// (The keyshare request is sent in the clear so that won't do)
// We send this first such that, as long as the toDevice messages arrive in the
// same order we sent them, the other end will get this first, set up the new session,
// then get the keyshare request and send the key over this new session (because it
// is the session it has most recently received a message on).
val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}")
withContext(coroutineDispatchers.io) {
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
} catch (failure: Throwable) {
Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}")
}
}
}
}
}

View file

@ -17,6 +17,9 @@
package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy
import io.realm.RealmConfiguration
import io.realm.RealmResults
import kotlinx.coroutines.launch
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
@ -24,17 +27,13 @@ import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
import io.realm.RealmConfiguration
import io.realm.RealmResults
import kotlinx.coroutines.launch
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import timber.log.Timber
import javax.inject.Inject
internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>,
private val eventDecryptor: EventDecryptor)
: RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
internal class EventInsertLiveObserver @Inject constructor(
@SessionDatabase realmConfiguration: RealmConfiguration,
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
) : RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
override val query = Monarchy.Query<EventInsertEntity> {
it.where(EventInsertEntity::class.java)
@ -86,23 +85,6 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
}
}
// private fun decryptIfNeeded(event: Event) {
// if (event.isEncrypted() && event.mxDecryptionResult == null) {
// try {
// val result = eventDecryptor.decryptEvent(event, event.roomId ?: "")
// event.mxDecryptionResult = OlmDecryptionResult(
// payload = result.clearEvent,
// senderKey = result.senderCurve25519Key,
// keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
// forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
// )
// } catch (e: MXCryptoError) {
// Timber.v("Failed to decrypt event")
// // TODO -> we should keep track of this and retry, or some processing will never be handled
// }
// }
// }
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
return processors.any {
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room.summary
import io.realm.Realm
import io.realm.kotlin.createObject
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
@ -32,11 +31,9 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
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
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
@ -71,7 +68,6 @@ internal class RoomSummaryUpdater @Inject constructor(
@UserId private val userId: String,
private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor,
private val crossSigningService: DefaultCrossSigningService,
private val roomAccountDataDataSource: RoomAccountDataDataSource) {
@ -156,15 +152,7 @@ internal class RoomSummaryUpdater @Inject constructor(
}
roomSummaryEntity.updateHasFailedSending()
val root = latestPreviewableEvent?.root
if (root?.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
// mmm i want to decrypt now or is it ok to do it async?
tryOrNull {
eventDecryptor.decryptEvent(root.asDomain(), "")
}
?.let { root.setDecryptionResult(it) }
}
// We do not decrypt Event anymore here, let's see if this is OK
if (updateMembers) {
val otherRoomMembers = RoomMemberHelper(realm, roomId)

View file

@ -18,7 +18,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.events.model.Event
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
@ -36,7 +36,7 @@ internal interface GetEventTask : Task<GetEventTask.Params, Event> {
internal class DefaultGetEventTask @Inject constructor(
private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver,
private val eventDecryptor: EventDecryptor
private val cryptoService: DefaultCryptoService
) : GetEventTask {
override suspend fun execute(params: GetEventTask.Params): Event {
@ -47,7 +47,7 @@ internal class DefaultGetEventTask @Inject constructor(
// Try to decrypt the Event
if (event.isEncrypted()) {
tryOrNull(message = "Unable to decrypt the event") {
eventDecryptor.decryptEvent(event, "")
cryptoService.decryptEvent(event, "")
}
?.let { result ->
event.mxDecryptionResult = OlmDecryptionResult(