Extract common crypto interface for all flavors

This commit is contained in:
valere 2023-05-26 09:50:15 +02:00
parent 8f69e411d7
commit 3bf5c0cc1b
18 changed files with 582 additions and 181 deletions

View file

@ -59,6 +59,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionD
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask 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.keysbackup.tasks.UpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
@ -254,6 +255,9 @@ internal abstract class CryptoModule {
@Binds @Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
@Binds
abstract fun bindCommonCryptoStore(store: RealmCryptoStore): IMXCommonCryptoStore
@Binds @Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask

View file

@ -506,10 +506,7 @@ internal class DefaultCryptoService @Inject constructor(
null null
} else { } else {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
cryptoStore.deviceWithIdentityKey(senderKey).takeIf { cryptoStore.deviceWithIdentityKey(userId, senderKey)
// check that the claimed user id matches
it?.userId == userId
}
} }
} }
} }

View file

@ -28,20 +28,16 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap 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.RoomKeyRequestBody
import org.matrix.android.sdk.api.session.crypto.model.TrailType import org.matrix.android.sdk.api.session.crypto.model.TrailType
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent 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.api.session.events.model.content.WithHeldCode
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmOutboundGroupSession import org.matrix.olm.OlmOutboundGroupSession
@ -49,7 +45,7 @@ import org.matrix.olm.OlmOutboundGroupSession
/** /**
* The crypto data store. * The crypto data store.
*/ */
internal interface IMXCryptoStore { internal interface IMXCryptoStore : IMXCommonCryptoStore {
/** /**
* @return the device id * @return the device id
@ -78,21 +74,6 @@ internal interface IMXCryptoStore {
*/ */
fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper> fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper>
/**
* @return true to unilaterally blacklist all unverified devices.
*/
fun getGlobalBlacklistUnverifiedDevices(): Boolean
/**
* Set the global override for whether the client should ever send encrypted
* messages to unverified devices.
* If false, it can still be overridden per-room.
* If true, it overrides the per-room settings.
*
* @param block true to unilaterally blacklist all
*/
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
/** /**
* Enable or disable key gossiping. * Enable or disable key gossiping.
* Default is true. * Default is true.
@ -123,28 +104,6 @@ internal interface IMXCryptoStore {
*/ */
fun getRoomsListBlacklistUnverifiedDevices(): List<String> fun getRoomsListBlacklistUnverifiedDevices(): List<String>
/**
* A live status regarding sharing keys for unverified devices in this room.
*
* @return Live status
*/
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
/**
* Tell if unverified devices should be blacklisted when sending keys.
*
* @return true if should not send keys to unverified devices
*/
fun getBlockUnverifiedDevices(roomId: String): Boolean
/**
* Define if encryption keys should be sent to unverified devices in this room.
*
* @param roomId the roomId
* @param block if true will not send keys to unverified devices
*/
fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean)
/** /**
* Get the current keys backup version. * Get the current keys backup version.
*/ */
@ -186,16 +145,6 @@ internal interface IMXCryptoStore {
*/ */
fun deleteStore() fun deleteStore()
/**
* open any existing crypto store.
*/
fun open()
/**
* Close the store.
*/
fun close()
/** /**
* Store the device id. * Store the device id.
* *
@ -262,14 +211,6 @@ internal interface IMXCryptoStore {
fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
fun getMyDevicesInfo(): List<DeviceInfo>
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>>
fun saveMyDevicesInfo(info: List<DeviceInfo>)
/** /**
* Store the crypto algorithm for a room. * Store the crypto algorithm for a room.
* *
@ -278,47 +219,8 @@ internal interface IMXCryptoStore {
*/ */
fun storeRoomAlgorithm(roomId: String, algorithm: String?) fun storeRoomAlgorithm(roomId: String, algorithm: String?)
/**
* Provides the algorithm used in a dedicated room.
*
* @param roomId the room id
* @return the algorithm, null is the room is not encrypted
*/
fun getRoomAlgorithm(roomId: String): String?
fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo?
fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?)
/**
* This is a bit different than isRoomEncrypted.
* A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not).
* But the crypto layer has additional guaranty to ensure that encryption would never been reverted.
* It's defensive coding out of precaution (if ever state is reset).
*/
fun roomWasOnceEncrypted(roomId: String): Boolean
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
/**
* Sets a boolean flag that will determine whether or not this device should encrypt Events for
* invited members.
*
* @param roomId the room id
* @param shouldEncryptForInvitedMembers The boolean flag
*/
fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)
fun shouldShareHistory(roomId: String): Boolean fun shouldShareHistory(roomId: String): Boolean
/**
* Sets a boolean flag that will determine whether or not room history (existing inbound sessions)
* will be shared to new user invites.
*
* @param roomId the room id
* @param shouldShareHistory The boolean flag
*/
fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean)
/** /**
* Store a session between the logged-in user and another device. * Store a session between the logged-in user and another device.
* *
@ -361,15 +263,6 @@ internal interface IMXCryptoStore {
*/ */
fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>) fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>)
/**
* Retrieve an inbound group session.
*
* @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return an inbound group session.
*/
fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?
/** /**
* Retrieve an inbound group session, filtering shared history. * Retrieve an inbound group session, filtering shared history.
* *
@ -552,7 +445,6 @@ internal interface IMXCryptoStore {
// fun getCrossSigningPrivateKeysFlow(): Flow<Optional<PrivateKeysInfo>> // fun getCrossSigningPrivateKeysFlow(): Flow<Optional<PrivateKeysInfo>>
fun getGlobalCryptoConfig(): GlobalCryptoConfig fun getGlobalCryptoConfig(): GlobalCryptoConfig
fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig>
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
@ -597,14 +489,8 @@ internal interface IMXCryptoStore {
fun setDeviceKeysUploaded(uploaded: Boolean) fun setDeviceKeysUploaded(uploaded: Boolean)
fun areDeviceKeysUploaded(): Boolean fun areDeviceKeysUploaded(): Boolean
fun tidyUpDataBase()
fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest> fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest>
/**
* Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator].
*/
fun storeData(cryptoStoreAggregator: CryptoStoreAggregator)
/** /**
* Store a bunch of data related to the users. @See [UserDataToStore]. * Store a bunch of data related to the users. @See [UserDataToStore].
*/ */

View file

@ -280,6 +280,19 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo? {
return doWithRealm(realmConfiguration) { realm ->
realm.where<DeviceInfoEntity>()
.equalTo(DeviceInfoEntityFields.USER_ID, userId)
.contains(DeviceInfoEntityFields.KEYS_MAP_JSON, identityKey)
.findAll()
.mapNotNull { CryptoMapper.mapToModel(it) }
.firstOrNull {
it.identityKey() == identityKey
}
}
}
override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) { override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> doRealmTransaction("storeUserDevices", realmConfiguration) { realm ->
storeUserDevices(realm, userId, devices) storeUserDevices(realm, userId, devices)

View file

@ -17,11 +17,11 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import javax.inject.Inject import javax.inject.Inject
internal class ShouldEncryptForInvitedMembersUseCase @Inject constructor(private val cryptoConfig: MXCryptoConfig, internal class ShouldEncryptForInvitedMembersUseCase @Inject constructor(private val cryptoConfig: MXCryptoConfig,
private val cryptoStore: IMXCryptoStore) { private val cryptoStore: IMXCommonCryptoStore) {
operator fun invoke(roomId: String): Boolean { operator fun invoke(roomId: String): Boolean {
return cryptoConfig.enableEncryptionForInvitedMembers && cryptoStore.shouldEncryptForInvitedMembers(roomId) return cryptoConfig.enableEncryptionForInvitedMembers && cryptoStore.shouldEncryptForInvitedMembers(roomId)

View file

@ -0,0 +1,156 @@
/*
* Copyright 2023 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.store
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
/**
* As a temporary measure rust and kotlin flavor are still using realm to store some crypto
* related information. In the near future rust flavor will complitly stop using realm, as soon
* as the missing bits are store in rust side (like room encryption settings, ..)
* This interface defines what's now used by both flavors.
* The actual implementation are moved in each flavors
*/
interface IMXCommonCryptoStore {
/**
* Provides the algorithm used in a dedicated room.
*
* @param roomId the room id
* @return the algorithm, null is the room is not encrypted
*/
fun getRoomAlgorithm(roomId: String): String?
fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo?
fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?)
fun roomWasOnceEncrypted(roomId: String): Boolean
fun saveMyDevicesInfo(info: List<DeviceInfo>)
// questionable that it's stored in crypto store
fun getMyDevicesInfo(): List<DeviceInfo>
// questionable that it's stored in crypto store
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
// questionable that it's stored in crypto store
fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>>
/**
* open any existing crypto store.
*/
fun open()
fun tidyUpDataBase()
/**
* Close the store.
*/
fun close()
/*
* Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator].
*/
fun storeData(cryptoStoreAggregator: CryptoStoreAggregator)
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
/**
* Sets a boolean flag that will determine whether or not room history (existing inbound sessions)
* will be shared to new user invites.
*
* @param roomId the room id
* @param shouldShareHistory The boolean flag
*/
fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean)
/**
* Sets a boolean flag that will determine whether or not this device should encrypt Events for
* invited members.
*
* @param roomId the room id
* @param shouldEncryptForInvitedMembers The boolean flag
*/
fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)
/**
* Define if encryption keys should be sent to unverified devices in this room.
*
* @param roomId the roomId
* @param block if true will not send keys to unverified devices
*/
fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean)
/**
* Set the global override for whether the client should ever send encrypted
* messages to unverified devices.
* If false, it can still be overridden per-room.
* If true, it overrides the per-room settings.
*
* @param block true to unilaterally blacklist all
*/
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig>
/**
* @return true to unilaterally blacklist all unverified devices.
*/
fun getGlobalBlacklistUnverifiedDevices(): Boolean
/**
* A live status regarding sharing keys for unverified devices in this room.
*
* @return Live status
*/
fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean>
/**
* Tell if unverified devices should be blacklisted when sending keys.
*
* @return true if should not send keys to unverified devices
*/
fun getBlockUnverifiedDevices(roomId: String): Boolean
/**
* Retrieve a device by its identity key.
*
* @param userId the device owner
* @param identityKey the device identity key (`MXDeviceInfo.identityKey`)
* @return the device or null if not found
*/
fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo?
/**
* Retrieve an inbound group session.
* Used in rust for lazy migration
*
* @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return an inbound group session.
*/
fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?
}

View file

@ -26,11 +26,11 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCryptoStore) { internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCommonCryptoStore) {
sealed class EditValidity { sealed class EditValidity {
object Valid : EditValidity() object Valid : EditValidity()
@ -80,25 +80,21 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
val replaceDecrypted = replaceEvent.toValidDecryptedEvent() val replaceDecrypted = replaceEvent.toValidDecryptedEvent()
?: return EditValidity.Unknown // UTD can't decide ?: return EditValidity.Unknown // UTD can't decide
val originalCryptoSenderId = cryptoStore.deviceWithIdentityKey(originalDecrypted.cryptoSenderKey)?.userId if (originalEvent.senderId != replaceEvent.senderId) {
val editCryptoSenderId = cryptoStore.deviceWithIdentityKey(replaceDecrypted.cryptoSenderKey)?.userId return EditValidity.Invalid("original event and replacement event must have the same sender")
}
val originalSendingDevice = originalEvent.senderId?.let { cryptoStore.deviceWithIdentityKey(it, originalDecrypted.cryptoSenderKey) }
val editSendingDevice = originalEvent.senderId?.let { cryptoStore.deviceWithIdentityKey(it, replaceDecrypted.cryptoSenderKey) }
if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) { if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) {
return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ") return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ")
} }
if (originalCryptoSenderId == null || editCryptoSenderId == null) { if (originalSendingDevice == null || editSendingDevice == null) {
// mm what can we do? we don't know if it's cryptographically from same user? // mm what can we do? we don't know if it's cryptographically from same user?
// let valid and UI should display send by deleted device warning? // maybe it's a deleted device or a not yet downloaded one?
val bestEffortOriginal = originalCryptoSenderId ?: originalEvent.senderId return EditValidity.Unknown
val bestEffortEdit = editCryptoSenderId ?: replaceEvent.senderId
if (bestEffortOriginal != bestEffortEdit) {
return EditValidity.Invalid("original event and replacement event must have the same sender")
}
} else {
if (originalCryptoSenderId != editCryptoSenderId) {
return EditValidity.Invalid("Crypto: original event and replacement event must have the same sender")
}
} }
if (originalDecrypted.type != replaceDecrypted.type) { if (originalDecrypted.type != replaceDecrypted.type) {

View file

@ -49,7 +49,7 @@ import org.matrix.android.sdk.api.util.CancelableBag
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.TextContent import org.matrix.android.sdk.api.util.TextContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.content.UploadContentWorker import org.matrix.android.sdk.internal.session.content.UploadContentWorker
@ -69,7 +69,7 @@ internal class DefaultSendService @AssistedInject constructor(
private val workManagerProvider: WorkManagerProvider, private val workManagerProvider: WorkManagerProvider,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val eventSenderProcessor: EventSenderProcessor, private val eventSenderProcessor: EventSenderProcessor,

View file

@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.failure.isLimitExceededError
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.CoroutineSequencer import org.matrix.android.sdk.internal.task.CoroutineSequencer
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
@ -54,7 +54,7 @@ private const val MAX_RETRY_COUNT = 3
*/ */
@SessionScope @SessionScope
internal class EventSenderProcessorCoroutine @Inject constructor( internal class EventSenderProcessorCoroutine @Inject constructor(
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
private val sessionParams: SessionParams, private val sessionParams: SessionParams,
private val queuedTaskFactory: QueuedTaskFactory, private val queuedTaskFactory: QueuedTaskFactory,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,

View file

@ -58,8 +58,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionD
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask 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.keysbackup.tasks.UpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.RustCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
@ -246,7 +246,7 @@ internal abstract class CryptoModule {
abstract fun bindVerificationService(service: RustVerificationService): VerificationService abstract fun bindVerificationService(service: RustVerificationService): VerificationService
@Binds @Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore abstract fun bindCryptoStore(store: RustCryptoStore): IMXCommonCryptoStore
@Binds @Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask

View file

@ -54,6 +54,7 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.network.RequestSender import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.SasVerification import org.matrix.android.sdk.internal.crypto.verification.SasVerification
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest
@ -312,10 +313,10 @@ internal class OlmMachine @Inject constructor(
/** /**
* Used for lazy migration of inboundGroupSession from EA to ER * Used for lazy migration of inboundGroupSession from EA to ER
*/ */
suspend fun importRoomKey(inbound: InboundGroupSessionHolder): Result<Unit> { suspend fun importRoomKey(inbound: MXInboundMegolmSessionWrapper): Result<Unit> {
Timber.v("Migration:: Tentative lazy migration") Timber.v("Migration:: Tentative lazy migration")
return withContext(coroutineDispatchers.io) { return withContext(coroutineDispatchers.io) {
val export = inbound.wrapper.exportKeys() val export = inbound.exportKeys()
?: return@withContext Result.failure(Exception("Failed to export key")) ?: return@withContext Result.failure(Exception("Failed to export key"))
val result = importDecryptedKeys(listOf(export), null).also { val result = importDecryptedKeys(listOf(export), null).also {
Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}") Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}")

View file

@ -28,7 +28,7 @@ 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.MXCryptoError
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService 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.network.RequestSender
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.rustcomponents.sdk.crypto.EncryptionSettings import org.matrix.rustcomponents.sdk.crypto.EncryptionSettings
@ -47,7 +47,7 @@ private val loggerTag = LoggerTag("PrepareToEncryptUseCase", LoggerTag.CRYPTO)
internal class PrepareToEncryptUseCase @Inject constructor( internal class PrepareToEncryptUseCase @Inject constructor(
private val olmMachine: OlmMachine, private val olmMachine: OlmMachine,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
private val getRoomUserIds: GetRoomUserIdsUseCase, private val getRoomUserIds: GetRoomUserIdsUseCase,
private val requestSender: RequestSender, private val requestSender: RequestSender,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,

View file

@ -17,7 +17,6 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import android.content.Context import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.paging.PagedList import androidx.paging.PagedList
@ -76,7 +75,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.SessionInfo
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository 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.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
@ -111,7 +110,7 @@ internal class RustCryptoService @Inject constructor(
@UserId private val myUserId: String, @UserId private val myUserId: String,
@DeviceId private val deviceId: String, @DeviceId private val deviceId: String,
// the crypto store // the crypto store
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCommonCryptoStore,
// Set of parameters used to configure/customize the end-to-end crypto. // Set of parameters used to configure/customize the end-to-end crypto.
private val mxCryptoConfig: MXCryptoConfig, private val mxCryptoConfig: MXCryptoConfig,
// Actions // Actions
@ -903,13 +902,6 @@ internal class RustCryptoService @Inject constructor(
// TODO("Not yet implemented") // TODO("Not yet implemented")
} }
/* ==========================================================================================
* For test only
* ========================================================================================== */
@VisibleForTesting
val cryptoStoreForTesting = cryptoStore
companion object { companion object {
const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour
} }

View file

@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError 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.KeysBackupLastVersionResult
@ -36,7 +37,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.EventType
import org.matrix.android.sdk.api.session.uia.UiaResult import org.matrix.android.sdk.api.session.uia.UiaResult
import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.auth.registration.handleUIA
import org.matrix.android.sdk.internal.crypto.InboundGroupSessionStore
import org.matrix.android.sdk.internal.crypto.OlmMachine import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter import org.matrix.android.sdk.internal.crypto.PerSessionBackupQueryRateLimiter
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
@ -59,6 +59,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
@ -102,7 +103,7 @@ internal class RequestSender @Inject constructor(
private val moshi: Moshi, private val moshi: Moshi,
cryptoCoroutineScope: CoroutineScope, cryptoCoroutineScope: CoroutineScope,
private val rateLimiter: PerSessionBackupQueryRateLimiter, private val rateLimiter: PerSessionBackupQueryRateLimiter,
private val inboundGroupSessionStore: InboundGroupSessionStore, private val cryptoStore: IMXCommonCryptoStore,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val olmMachine: Lazy<OlmMachine>, private val olmMachine: Lazy<OlmMachine>,
) { ) {
@ -266,7 +267,9 @@ internal class RequestSender @Inject constructor(
val senderKey = requestBody?.get("sender_key") as? String val senderKey = requestBody?.get("sender_key") as? String
if (roomId != null && sessionId != null) { if (roomId != null && sessionId != null) {
// try to perform a lazy migration from legacy store // try to perform a lazy migration from legacy store
val legacy = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey.orEmpty()) val legacy = tryOrNull("Failed to access legacy crypto store") {
cryptoStore.getInboundGroupSession(sessionId, senderKey.orEmpty())
}
if (legacy == null || olmMachine.get().importRoomKey(legacy).isFailure) { if (legacy == null || olmMachine.get().importRoomKey(legacy).isFailure) {
rateLimiter.tryFromBackupIfPossible(sessionId, roomId) rateLimiter.tryFromBackupIfPossible(sessionId, roomId)
} }

View file

@ -0,0 +1,351 @@
/*
* Copyright 2023 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.store
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.where
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransaction
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync
import org.matrix.android.sdk.internal.crypto.store.db.doWithRealm
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CryptoRoomInfoMapper
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject
private val loggerTag = LoggerTag("RealmCryptoStore", LoggerTag.CRYPTO)
/**
* In the transition phase, the rust SDK is still using parts to the realm crypto store,
* this should be removed after full migration
*/
@SessionScope
internal class RustCryptoStore @Inject constructor(
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
private val clock: Clock,
private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper,
private val olmMachine: dagger.Lazy<OlmMachine>,
private val matrixCoroutineDispatchers: MatrixCoroutineDispatchers,
) : IMXCommonCryptoStore {
/**
* Retrieve a device by its identity key.
*
* @param identityKey the device identity key (`MXDeviceInfo.identityKey`)
* @return the device or null if not found
*/
override fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo? {
// XXX make this suspendable?
val knownDevices = runBlocking(matrixCoroutineDispatchers.io) {
olmMachine.get().getUserDevices(userId)
}
return knownDevices
.map { it.toCryptoDeviceInfo() }
.firstOrNull {
it.identityKey() == identityKey
}
}
/**
* Needed for lazy migration of sessions from the legacy store
*/
override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findFirst()
?.toModel()
}
}
// ================================================
// Things that should be migrated to another store than realm
// ================================================
private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor()
private val monarchy = Monarchy.Builder()
.setRealmConfiguration(realmConfiguration)
.setWriteAsyncExecutor(monarchyWriteAsyncExecutor)
.build()
override fun open() {
// nop
}
override fun tidyUpDataBase() {
// These entities are not used in rust actually, but as they are not yet cleaned up, this will do it with time
val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm ->
// Clean the old ones?
realm.where<OutgoingKeyRequestEntity>()
.lessThan(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, prevWeekTs)
.findAll()
.also { Timber.i("## Crypto Clean up ${it.size} OutgoingKeyRequestEntity") }
.deleteAllFromRealm()
// Only keep one month history
val prevMonthTs = clock.epochMillis() - 4 * 7 * 24 * 60 * 60 * 1_000L
realm.where<AuditTrailEntity>()
.lessThan(AuditTrailEntityFields.AGE_LOCAL_TS, prevMonthTs)
.findAll()
.also { Timber.i("## Crypto Clean up ${it.size} AuditTrailEntity") }
.deleteAllFromRealm()
// Can we do something for WithHeldSessionEntity?
}
}
override fun close() {
val tasks = monarchyWriteAsyncExecutor.shutdownNow()
Timber.w("Closing RealmCryptoStore, ${tasks.size} async task(s) cancelled")
tryOrNull("Interrupted") {
// Wait 1 minute max
monarchyWriteAsyncExecutor.awaitTermination(1, TimeUnit.MINUTES)
}
}
override fun getRoomAlgorithm(roomId: String): String? {
return doWithRealm(realmConfiguration) {
CryptoRoomEntity.getById(it, roomId)?.algorithm
}
}
override fun getRoomCryptoInfo(roomId: String): CryptoRoomInfo? {
return doWithRealm(realmConfiguration) { realm ->
CryptoRoomEntity.getById(realm, roomId)?.let {
CryptoRoomInfoMapper.map(it)
}
}
}
/**
* This is a bit different than isRoomEncrypted.
* A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not).
* But the crypto layer has additional guaranty to ensure that encryption would never been reverted.
* It's defensive coding out of precaution (if ever state is reset).
*/
override fun roomWasOnceEncrypted(roomId: String): Boolean {
return doWithRealm(realmConfiguration) {
CryptoRoomEntity.getById(it, roomId)?.wasEncryptedOnce ?: false
}
}
override fun setAlgorithmInfo(roomId: String, encryption: EncryptionEventContent?) {
doRealmTransaction("setAlgorithmInfo", realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).let { entity ->
entity.algorithm = encryption?.algorithm
// store anyway the new algorithm, but mark the room
// as having been encrypted once whatever, this can never
// go back to false
if (encryption?.algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
entity.wasEncryptedOnce = true
entity.rotationPeriodMs = encryption.rotationPeriodMs
entity.rotationPeriodMsgs = encryption.rotationPeriodMsgs
}
}
}
}
override fun saveMyDevicesInfo(info: List<DeviceInfo>) {
val entities = info.map { myDeviceLastSeenInfoEntityMapper.map(it) }
doRealmTransactionAsync(realmConfiguration) { realm ->
realm.where<MyDeviceLastSeenInfoEntity>().findAll().deleteAllFromRealm()
entities.forEach {
realm.insertOrUpdate(it)
}
}
}
override fun getMyDevicesInfo(): List<DeviceInfo> {
return monarchy.fetchAllCopiedSync {
it.where<MyDeviceLastSeenInfoEntity>()
}.map {
DeviceInfo(
deviceId = it.deviceId,
lastSeenIp = it.lastSeenIp,
lastSeenTs = it.lastSeenTs,
displayName = it.displayName
)
}
}
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
return monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<MyDeviceLastSeenInfoEntity>()
},
{ entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
)
}
override fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<MyDeviceLastSeenInfoEntity>()
.equalTo(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, deviceId)
},
{ entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
)
return Transformations.map(liveData) {
it.firstOrNull().toOptional()
}
}
override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) {
if (cryptoStoreAggregator.isEmpty()) {
return
}
doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm ->
// setShouldShareHistory
cryptoStoreAggregator.setShouldShareHistoryData.forEach {
Timber.tag(loggerTag.value)
.v("setShouldShareHistory for room ${it.key} is ${it.value}")
CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value
}
// setShouldEncryptForInvitedMembers
cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach {
CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value
}
}
}
override fun shouldEncryptForInvitedMembers(roomId: String): Boolean {
return doWithRealm(realmConfiguration) {
CryptoRoomEntity.getById(it, roomId)?.shouldEncryptForInvitedMembers
}
?: false
}
override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) {
Timber.tag(loggerTag.value)
.v("setShouldShareHistory for room $roomId is $shouldShareHistory")
doRealmTransaction("setShouldShareHistory", realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory
}
}
override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) {
doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers
}
}
override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) {
doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm ->
CryptoRoomEntity.getById(realm, roomId)
?.blacklistUnverifiedDevices = block
}
}
override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) {
doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices = block
}
}
override fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm
.where<CryptoMetadataEntity>()
},
{
GlobalCryptoConfig(
globalBlockUnverifiedDevices = it.globalBlacklistUnverifiedDevices,
globalEnableKeyGossiping = it.globalEnableKeyGossiping,
enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite
)
}
)
return Transformations.map(liveData) {
it.firstOrNull() ?: GlobalCryptoConfig(false, false, false)
}
}
override fun getGlobalBlacklistUnverifiedDevices(): Boolean {
return doWithRealm(realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices
} ?: false
}
override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<CryptoRoomEntity>()
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
},
{
it.blacklistUnverifiedDevices
}
)
return Transformations.map(liveData) {
it.firstOrNull() ?: false
}
}
override fun getBlockUnverifiedDevices(roomId: String): Boolean {
return doWithRealm(realmConfiguration) { realm ->
realm.where<CryptoRoomEntity>()
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
.findFirst()
?.blacklistUnverifiedDevices ?: false
}
}
}

View file

@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult 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.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCommonCryptoStore
class EventEditValidatorTest { class EventEditValidatorTest {
@ -62,7 +62,7 @@ class EventEditValidatorTest {
@Test @Test
fun `edit should be valid`() { fun `edit should be valid`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -71,7 +71,7 @@ class EventEditValidatorTest {
@Test @Test
fun `original event and replacement event must have the same sender`() { fun `original event and replacement event must have the same sender`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -83,7 +83,7 @@ class EventEditValidatorTest {
@Test @Test
fun `original event and replacement event must have the same room_id`() { fun `original event and replacement event must have the same room_id`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -101,7 +101,7 @@ class EventEditValidatorTest {
@Test @Test
fun `replacement and original events must not have a state_key property`() { fun `replacement and original events must not have a state_key property`() {
val mockCryptoStore = mockk<IMXCryptoStore>() val mockCryptoStore = mockk<IMXCommonCryptoStore>()
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
validator validator
@ -119,8 +119,8 @@ class EventEditValidatorTest {
@Test @Test
fun `replacement event must have an new_content property`() { fun `replacement event must have an new_content property`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -157,8 +157,8 @@ class EventEditValidatorTest {
@Test @Test
fun `The original event must not itself have a rel_type of m_replace`() { fun `The original event must not itself have a rel_type of m_replace`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -207,8 +207,8 @@ class EventEditValidatorTest {
@Test @Test
fun `valid e2ee edit`() { fun `valid e2ee edit`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -224,8 +224,8 @@ class EventEditValidatorTest {
@Test @Test
fun `If the original event was encrypted, the replacement should be too`() { fun `If the original event was encrypted, the replacement should be too`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk<CryptoDeviceInfo> { mockk<CryptoDeviceInfo> {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
@ -241,12 +241,12 @@ class EventEditValidatorTest {
@Test @Test
fun `encrypted, original event and replacement event must have the same sender`() { fun `encrypted, original event and replacement event must have the same sender`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk { mockk {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns every { deviceWithIdentityKey("@bob:example.com", "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns
mockk { mockk {
every { userId } returns "@bob:example.com" every { userId } returns "@bob:example.com"
} }
@ -256,7 +256,9 @@ class EventEditValidatorTest {
validator validator
.validateEdit( .validateEdit(
encryptedEvent, encryptedEvent,
encryptedEditEvent.copy().apply { encryptedEditEvent.copy(
senderId = "@bob:example.com"
).apply {
mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy(
senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI" senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI"
) )
@ -269,12 +271,12 @@ class EventEditValidatorTest {
@Test @Test
fun `encrypted, sent fom a deleted device, original event and replacement event must have the same sender`() { fun `encrypted, sent fom a deleted device, original event and replacement event must have the same sender`() {
val mockCryptoStore = mockk<IMXCryptoStore> { val mockCryptoStore = mockk<IMXCommonCryptoStore> {
every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns every { deviceWithIdentityKey("@alice:example.com", "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns
mockk { mockk {
every { userId } returns "@alice:example.com" every { userId } returns "@alice:example.com"
} }
every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns every { deviceWithIdentityKey(any(), "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns
null null
} }
val validator = EventEditValidator(mockCryptoStore) val validator = EventEditValidator(mockCryptoStore)
@ -288,7 +290,7 @@ class EventEditValidatorTest {
) )
} }
) shouldBeInstanceOf EventEditValidator.EditValidity.Valid::class ) shouldBeInstanceOf EventEditValidator.EditValidity.Unknown::class
validator validator
.validateEdit( .validateEdit(