diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreHelper.kt index 6b0ebbf6a4..7b4020bf04 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreHelper.kt @@ -20,6 +20,8 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule +import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper +import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.RealmConfiguration import kotlin.random.Random @@ -31,6 +33,7 @@ internal class CryptoStoreHelper { .name("test.realm") .modules(RealmCryptoStoreModule()) .build(), + crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()), credentials = createCredential()) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 6171b2633b..8804f98976 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -49,7 +49,6 @@ data class RoomSummary constructor( val inviterId: String? = null, val typingRoomMemberIds: List = emptyList(), val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, - // TODO Plug it val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 50ffb3082a..1efdffdb06 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -112,6 +112,7 @@ internal abstract class CryptoModule { @SessionScope fun providesRealmConfiguration(@SessionFilesDirectory directory: File, @UserMd5 userMd5: String, + realmCryptoStoreMigration: RealmCryptoStoreMigration, realmKeysUtils: RealmKeysUtils): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) @@ -121,7 +122,7 @@ internal abstract class CryptoModule { .name("crypto_store.realm") .modules(RealmCryptoStoreModule()) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) - .migration(RealmCryptoStoreMigration) + .migration(realmCryptoStoreMigration) .build() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt index c4c49a5940..d7597f00c9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt @@ -15,18 +15,20 @@ */ package im.vector.matrix.android.internal.crypto.crosssigning +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater +import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.createBackgroundHandler import io.realm.Realm import io.realm.RealmConfiguration +import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import timber.log.Timber @@ -38,13 +40,13 @@ internal class ShieldTrustUpdater @Inject constructor( private val eventBus: EventBus, private val computeTrustTask: ComputeTrustTask, private val taskExecutor: TaskExecutor, - private val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val sessionRealmConfiguration: RealmConfiguration, private val roomSummaryUpdater: RoomSummaryUpdater ) { companion object { private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD") + private val BACKGROUND_HANDLER_DISPATCHER = BACKGROUND_HANDLER.asCoroutineDispatcher() } private val backgroundSessionRealm = AtomicReference() @@ -76,14 +78,11 @@ internal class ShieldTrustUpdater @Inject constructor( if (!isStarted.get()) { return } - taskExecutor.executorScope.launch(coroutineDispatchers.crypto) { + taskExecutor.executorScope.launch(BACKGROUND_HANDLER_DISPATCHER) { val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds)) // We need to send that back to session base - - BACKGROUND_HANDLER.post { - backgroundSessionRealm.get()?.executeTransaction { realm -> - roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust) - } + backgroundSessionRealm.get()?.executeTransaction { realm -> + roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust) } } } @@ -93,45 +92,30 @@ internal class ShieldTrustUpdater @Inject constructor( if (!isStarted.get()) { return } - onCryptoDevicesChange(update.userIds) } private fun onCryptoDevicesChange(users: List) { - BACKGROUND_HANDLER.post { - val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java) - ?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) - ?.findAll() - ?.map { it.roomId } - ?.distinct() + taskExecutor.executorScope.launch(BACKGROUND_HANDLER_DISPATCHER) { + val realm = backgroundSessionRealm.get() ?: return@launch + val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java) + .`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) + .distinct(RoomMemberSummaryEntityFields.ROOM_ID) + .findAll() + .map { it.roomId } - val map = HashMap>() - impactedRoomsId?.forEach { roomId -> - backgroundSessionRealm.get()?.let { realm -> - RoomMemberSummaryEntity.where(realm, roomId) - .findAll() - .let { results -> - map[roomId] = results.map { it.userId } - } - } - } - - map.forEach { entry -> - val roomId = entry.key - val userList = entry.value - taskExecutor.executorScope.launch { - withContext(coroutineDispatchers.crypto) { - try { - // Can throw if the crypto database has been closed in between, in this case log and ignore? - val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList)) - BACKGROUND_HANDLER.post { - backgroundSessionRealm.get()?.executeTransaction { realm -> - roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust) - } - } - } catch (failure: Throwable) { - Timber.e(failure) + distinctRoomIds.forEach { roomId -> + val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() + if (roomSummary?.isEncrypted.orFalse()) { + val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() + try { + // Can throw if the crypto database has been closed in between, in this case log and ignore? + val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(allActiveRoomMembers)) + realm.executeTransaction { + roomSummaryUpdater.updateShieldTrust(it, roomId, updatedTrust) } + } catch (failure: Throwable) { + Timber.e(failure) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt index 642c466e42..ab4f4df354 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt @@ -62,6 +62,7 @@ fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) - realm.executeTransaction { action.invoke(it) } } } + fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { Realm.getInstance(realmConfiguration).use { realm -> realm.executeTransactionAsync { action.invoke(it) } @@ -79,31 +80,26 @@ fun serializeForRealm(o: Any?): String? { val baos = ByteArrayOutputStream() val gzis = CompatUtil.createGzipOutputStream(baos) val out = ObjectOutputStream(gzis) - - out.writeObject(o) - out.close() - + out.use { + it.writeObject(o) + } return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT) } /** * Do the opposite of serializeForRealm. */ +@Suppress("UNCHECKED_CAST") fun deserializeFromRealm(string: String?): T? { if (string == null) { return null } - val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT) val bais = ByteArrayInputStream(decodedB64) val gzis = GZIPInputStream(bais) val ois = ObjectInputStream(gzis) - - @Suppress("UNCHECKED_CAST") - val result = ois.readObject() as T - - ois.close() - - return result + return ois.use { + it.readObject() as T + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index a6f3f5d593..107d286d43 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -36,7 +36,6 @@ import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult -import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper @@ -46,6 +45,7 @@ import im.vector.matrix.android.internal.crypto.model.toEntity import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo +import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMapper @@ -91,6 +91,7 @@ import kotlin.collections.set @SessionScope internal class RealmCryptoStore @Inject constructor( @CryptoDatabase private val realmConfiguration: RealmConfiguration, + private val crossSigningKeysMapper: CrossSigningKeysMapper, private val credentials: Credentials) : IMXCryptoStore { /* ========================================================================================== @@ -200,9 +201,9 @@ internal class RealmCryptoStore @Inject constructor( } override fun getDeviceId(): String { - return doRealmQueryAndCopy(realmConfiguration) { - it.where().findFirst() - }?.deviceId ?: "" + return doWithRealm(realmConfiguration) { + it.where().findFirst()?.deviceId + } ?: "" } override fun saveOlmAccount() { @@ -256,24 +257,25 @@ internal class RealmCryptoStore @Inject constructor( } override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? { - return doRealmQueryAndCopy(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst() - }?.let { - CryptoMapper.mapToModel(it) + ?.let { deviceInfo -> + CryptoMapper.mapToModel(deviceInfo) + } } } override fun deviceWithIdentityKey(identityKey: String): CryptoDeviceInfo? { - return doRealmQueryAndCopy(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(DeviceInfoEntityFields.IDENTITY_KEY, identityKey) .findFirst() + ?.let { deviceInfo -> + CryptoMapper.mapToModel(deviceInfo) + } } - ?.let { - CryptoMapper.mapToModel(it) - } } override fun storeUserDevices(userId: String, devices: Map?) { @@ -309,36 +311,19 @@ internal class RealmCryptoStore @Inject constructor( } else { CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> // What should we do if we detect a change of the keys? - val existingMaster = signingInfo.getMasterKey() if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { - // update signatures? - existingMaster.putSignatures(masterKey.signatures) - existingMaster.usages = masterKey.usages?.toTypedArray()?.let { RealmList(*it) } - ?: RealmList() + crossSigningKeysMapper.update(existingMaster, masterKey) } else { - val keyEntity = realm.createObject(KeyInfoEntity::class.java).apply { - this.publicKeyBase64 = masterKey.unpaddedBase64PublicKey - this.usages = masterKey.usages?.toTypedArray()?.let { RealmList(*it) } - ?: RealmList() - this.putSignatures(masterKey.signatures) - } + val keyEntity = crossSigningKeysMapper.map(masterKey) signingInfo.setMasterKey(keyEntity) } val existingSelfSigned = signingInfo.getSelfSignedKey() if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { - // update signatures? - existingSelfSigned.putSignatures(selfSigningKey.signatures) - existingSelfSigned.usages = selfSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } - ?: RealmList() + crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) } else { - val keyEntity = realm.createObject(KeyInfoEntity::class.java).apply { - this.publicKeyBase64 = selfSigningKey.unpaddedBase64PublicKey - this.usages = selfSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } - ?: RealmList() - this.putSignatures(selfSigningKey.signatures) - } + val keyEntity = crossSigningKeysMapper.map(selfSigningKey) signingInfo.setSelfSignedKey(keyEntity) } @@ -346,21 +331,12 @@ internal class RealmCryptoStore @Inject constructor( if (userSigningKey != null) { val existingUSK = signingInfo.getUserSigningKey() if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { - // update signatures? - existingUSK.putSignatures(userSigningKey.signatures) - existingUSK.usages = userSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } - ?: RealmList() + crossSigningKeysMapper.update(existingUSK, userSigningKey) } else { - val keyEntity = realm.createObject(KeyInfoEntity::class.java).apply { - this.publicKeyBase64 = userSigningKey.unpaddedBase64PublicKey - this.usages = userSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } - ?: RealmList() - this.putSignatures(userSigningKey.signatures) - } + val keyEntity = crossSigningKeysMapper.map(userSigningKey) signingInfo.setUserSignedKey(keyEntity) } } - userEntity.crossSigningInfoEntity = signingInfo } } @@ -369,14 +345,16 @@ internal class RealmCryptoStore @Inject constructor( } override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { - return doRealmQueryAndCopy(realmConfiguration) { realm -> - realm.where().findFirst() - }?.let { - PrivateKeysInfo( - master = it.xSignMasterPrivateKey, - selfSigned = it.xSignSelfSignedPrivateKey, - user = it.xSignUserPrivateKey - ) + return doWithRealm(realmConfiguration) { realm -> + realm.where() + .findFirst() + ?.let { + PrivateKeysInfo( + master = it.xSignMasterPrivateKey, + selfSigned = it.xSignSelfSignedPrivateKey, + user = it.xSignUserPrivateKey + ) + } } } @@ -400,16 +378,18 @@ internal class RealmCryptoStore @Inject constructor( } override fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { - return doRealmQueryAndCopy(realmConfiguration) { realm -> - realm.where().findFirst() - }?.let { - val key = it.keyBackupRecoveryKey - val version = it.keyBackupRecoveryKeyVersion - if (!key.isNullOrBlank() && !version.isNullOrBlank()) { - SavedKeyBackupKeyInfo(recoveryKey = key, version = version) - } else { - null - } + return doWithRealm(realmConfiguration) { realm -> + realm.where() + .findFirst() + ?.let { + val key = it.keyBackupRecoveryKey + val version = it.keyBackupRecoveryKeyVersion + if (!key.isNullOrBlank() && !version.isNullOrBlank()) { + SavedKeyBackupKeyInfo(recoveryKey = key, version = version) + } else { + null + } + } } } @@ -430,24 +410,30 @@ internal class RealmCryptoStore @Inject constructor( } override fun getUserDevices(userId: String): Map? { - return doRealmQueryAndCopy(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() + ?.devices + ?.map { deviceInfo -> + CryptoMapper.mapToModel(deviceInfo) + } + ?.associateBy { cryptoDevice -> + cryptoDevice.deviceId + } } - ?.devices - ?.map { CryptoMapper.mapToModel(it) } - ?.associateBy { it.deviceId } } override fun getUserDeviceList(userId: String): List? { - return doRealmQueryAndCopy(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() + ?.devices + ?.map { deviceInfo -> + CryptoMapper.mapToModel(deviceInfo) + } } - ?.devices - ?.map { CryptoMapper.mapToModel(it) } } override fun getLiveDeviceList(userId: String): LiveData> { @@ -503,17 +489,16 @@ internal class RealmCryptoStore @Inject constructor( } override fun getRoomAlgorithm(roomId: String): String? { - return doRealmQueryAndCopy(realmConfiguration) { - CryptoRoomEntity.getById(it, roomId) + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.algorithm } - ?.algorithm } override fun shouldEncryptForInvitedMembers(roomId: String): Boolean { - return doRealmQueryAndCopy(realmConfiguration) { - CryptoRoomEntity.getById(it, roomId) + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.shouldEncryptForInvitedMembers } - ?.shouldEncryptForInvitedMembers ?: false + ?: false } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { @@ -577,24 +562,24 @@ internal class RealmCryptoStore @Inject constructor( } override fun getLastUsedSessionId(deviceKey: String): String? { - return doRealmQueryAndCopy(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) .sort(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Sort.DESCENDING) .findFirst() + ?.sessionId } - ?.sessionId } override fun getDeviceSessionIds(deviceKey: String): MutableSet { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) .findAll() + .mapNotNull { sessionEntity -> + sessionEntity.sessionId + } } - .mapNotNull { - it.sessionId - } .toMutableSet() } @@ -641,12 +626,12 @@ internal class RealmCryptoStore @Inject constructor( // If not in cache (or not found), try to read it from realm if (inboundGroupSessionToRelease[key] == null) { - doRealmQueryAndCopy(realmConfiguration) { + doWithRealm(realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findFirst() + ?.getInboundGroupSession() } - ?.getInboundGroupSession() ?.let { inboundGroupSessionToRelease[key] = it } @@ -660,13 +645,13 @@ internal class RealmCryptoStore @Inject constructor( * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management */ override fun getInboundGroupSessions(): MutableList { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .findAll() + .mapNotNull { inboundGroupSessionEntity -> + inboundGroupSessionEntity.getInboundGroupSession() + } } - .mapNotNull { - it.getInboundGroupSession() - } .toMutableList() } @@ -755,13 +740,14 @@ internal class RealmCryptoStore @Inject constructor( } override fun inboundGroupSessionsToBackup(limit: Int): List { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false) .limit(limit.toLong()) .findAll() - }.mapNotNull { inboundGroupSession -> - inboundGroupSession.getInboundGroupSession() + .mapNotNull { inboundGroupSession -> + inboundGroupSession.getInboundGroupSession() + } } } @@ -785,10 +771,9 @@ internal class RealmCryptoStore @Inject constructor( } override fun getGlobalBlacklistUnverifiedDevices(): Boolean { - return doRealmQueryAndCopy(realmConfiguration) { - it.where().findFirst() - }?.globalBlacklistUnverifiedDevices - ?: false + return doWithRealm(realmConfiguration) { + it.where().findFirst()?.globalBlacklistUnverifiedDevices + } ?: false } override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List) { @@ -811,28 +796,28 @@ internal class RealmCryptoStore @Inject constructor( } override fun getRoomsListBlacklistUnverifiedDevices(): MutableList { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true) .findAll() + .mapNotNull { cryptoRoom -> + cryptoRoom.roomId + } } - .mapNotNull { - it.roomId - } .toMutableList() } override fun getDeviceTrackingStatuses(): MutableMap { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .findAll() + .associateBy { user -> + user.userId!! + } + .mapValues { entry -> + entry.value.deviceTrackingStatus + } } - .associateBy { - it.userId!! - } - .mapValues { - it.value.deviceTrackingStatus - } .toMutableMap() } @@ -847,12 +832,12 @@ internal class RealmCryptoStore @Inject constructor( } override fun getDeviceTrackingStatus(userId: String, defaultValue: Int): Int { - return doRealmQueryAndCopy(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() + ?.deviceTrackingStatus } - ?.deviceTrackingStatus ?: defaultValue } @@ -1089,63 +1074,65 @@ internal class RealmCryptoStore @Inject constructor( } override fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? { - return doRealmQueryAndCopyList(realmConfiguration) { realm -> + return doWithRealm(realmConfiguration) { realm -> realm.where() .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, deviceId) .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, userId) .findAll() - }.mapNotNull { entity -> - entity.toIncomingGossipingRequest() as? IncomingRoomKeyRequest - }.firstOrNull() + .mapNotNull { entity -> + entity.toIncomingGossipingRequest() as? IncomingRoomKeyRequest + } + .firstOrNull() + } } override fun getPendingIncomingRoomKeyRequests(): List { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name) .findAll() + .map { entity -> + IncomingRoomKeyRequest( + userId = entity.otherUserId, + deviceId = entity.otherDeviceId, + requestId = entity.requestId, + requestBody = entity.getRequestedKeyInfo(), + localCreationTimestamp = entity.localCreationTimestamp + ) + } } - .map { entity -> - IncomingRoomKeyRequest( - userId = entity.otherUserId, - deviceId = entity.otherDeviceId, - requestId = entity.requestId, - requestBody = entity.getRequestedKeyInfo(), - localCreationTimestamp = entity.localCreationTimestamp - ) - } } override fun getPendingIncomingGossipingRequests(): List { - return doRealmQueryAndCopyList(realmConfiguration) { + return doWithRealm(realmConfiguration) { it.where() .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name) .findAll() - } - .mapNotNull { entity -> - when (entity.type) { - GossipRequestType.KEY -> { - IncomingRoomKeyRequest( - userId = entity.otherUserId, - deviceId = entity.otherDeviceId, - requestId = entity.requestId, - requestBody = entity.getRequestedKeyInfo(), - localCreationTimestamp = entity.localCreationTimestamp - ) - } - GossipRequestType.SECRET -> { - IncomingSecretShareRequest( - userId = entity.otherUserId, - deviceId = entity.otherDeviceId, - requestId = entity.requestId, - secretName = entity.getRequestedSecretName(), - localCreationTimestamp = entity.localCreationTimestamp - ) + .mapNotNull { entity -> + when (entity.type) { + GossipRequestType.KEY -> { + IncomingRoomKeyRequest( + userId = entity.otherUserId, + deviceId = entity.otherDeviceId, + requestId = entity.requestId, + requestBody = entity.getRequestedKeyInfo(), + localCreationTimestamp = entity.localCreationTimestamp + ) + } + GossipRequestType.SECRET -> { + IncomingSecretShareRequest( + userId = entity.otherUserId, + deviceId = entity.otherDeviceId, + requestId = entity.requestId, + secretName = entity.getRequestedSecretName(), + localCreationTimestamp = entity.localCreationTimestamp + ) + } } } - } + } } override fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) { @@ -1183,9 +1170,9 @@ internal class RealmCryptoStore @Inject constructor( * Cross Signing * ========================================================================================== */ override fun getMyCrossSigningInfo(): MXCrossSigningInfo? { - return doRealmQueryAndCopy(realmConfiguration) { - it.where().findFirst() - }?.userId?.let { + return doWithRealm(realmConfiguration) { + it.where().findFirst()?.userId + }?.let { getCrossSigningInfo(it) } } @@ -1304,33 +1291,24 @@ internal class RealmCryptoStore @Inject constructor( } override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { - return doRealmQueryAndCopy(realmConfiguration) { realm -> - realm.where(CrossSigningInfoEntity::class.java) + return doWithRealm(realmConfiguration) { realm -> + val crossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() - }?.let { xsignInfo -> - mapCrossSigningInfoEntity(xsignInfo) + if (crossSigningInfo == null) { + null + } else { + mapCrossSigningInfoEntity(crossSigningInfo) + } } } private fun mapCrossSigningInfoEntity(xsignInfo: CrossSigningInfoEntity): MXCrossSigningInfo { + val userId = xsignInfo.userId ?: "" return MXCrossSigningInfo( - userId = xsignInfo.userId ?: "", + userId = userId, crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull { - val pubKey = it.publicKeyBase64 ?: return@mapNotNull null - CryptoCrossSigningKey( - userId = xsignInfo.userId ?: "", - keys = mapOf("ed25519:$pubKey" to pubKey), - usages = it.usages.map { it }, - signatures = it.getSignatures(), - trustLevel = it.trustLevelEntity?.let { - DeviceTrustLevel( - crossSigningVerified = it.crossSignedVerified ?: false, - locallyVerified = it.locallyVerified ?: false - ) - } - - ) + crossSigningKeysMapper.map(userId, it) } ) } @@ -1341,26 +1319,7 @@ internal class RealmCryptoStore @Inject constructor( realm.where() .equalTo(UserEntityFields.USER_ID, userId) }, - { entity -> - MXCrossSigningInfo( - userId = userId, - crossSigningKeys = entity.crossSigningKeys.mapNotNull { - val pubKey = it.publicKeyBase64 ?: return@mapNotNull null - CryptoCrossSigningKey( - userId = userId, - keys = mapOf("ed25519:$pubKey" to pubKey), - usages = it.usages.map { it }, - signatures = it.getSignatures(), - trustLevel = it.trustLevelEntity?.let { - DeviceTrustLevel( - crossSigningVerified = it.crossSignedVerified ?: false, - locallyVerified = it.locallyVerified ?: false - ) - } - ) - } - ) - } + { mapCrossSigningInfoEntity(it) } ) return Transformations.map(liveData) { it.firstOrNull().toOptional() @@ -1402,17 +1361,8 @@ internal class RealmCryptoStore @Inject constructor( // existing.crossSigningKeys.forEach { it.deleteFromRealm() } val xkeys = RealmList() info.crossSigningKeys.forEach { cryptoCrossSigningKey -> - xkeys.add( - realm.createObject(KeyInfoEntity::class.java).also { keyInfoEntity -> - keyInfoEntity.publicKeyBase64 = cryptoCrossSigningKey.unpaddedBase64PublicKey - keyInfoEntity.usages = cryptoCrossSigningKey.usages?.let { RealmList(*it.toTypedArray()) } - ?: RealmList() - keyInfoEntity.putSignatures(cryptoCrossSigningKey.signatures) - // TODO how to handle better, check if same keys? - // reset trust - keyInfoEntity.trustLevelEntity = null - } - ) + val keyEntity = crossSigningKeysMapper.map(cryptoCrossSigningKey) + xkeys.add(keyEntity) } existing.crossSigningKeys = xkeys } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index d5972b5686..c0949319c1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Moshi import com.squareup.moshi.Types import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields @@ -33,11 +34,14 @@ import im.vector.matrix.android.internal.di.SerializeNulls import io.realm.DynamicRealm import io.realm.RealmMigration import timber.log.Timber +import javax.inject.Inject -internal object RealmCryptoStoreMigration : RealmMigration { +internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration { // Version 1L added Cross Signing info persistence - const val CRYPTO_STORE_SCHEMA_VERSION = 3L + companion object { + const val CRYPTO_STORE_SCHEMA_VERSION = 4L + } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion") @@ -45,6 +49,7 @@ internal object RealmCryptoStoreMigration : RealmMigration { if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 2) migrateTo3(realm) + if (oldVersion <= 3) migrateTo4(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -193,4 +198,18 @@ internal object RealmCryptoStoreMigration : RealmMigration { ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java) ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java) } + + private fun migrateTo4(realm: DynamicRealm) { + Timber.d("Updating KeyInfoEntity table") + val keyInfoEntities = realm.where("KeyInfoEntity").findAll() + try { + keyInfoEntities.forEach { + val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES) + val objectSignatures: Map>? = deserializeFromRealm(stringSignatures) + val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures) + it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures) + } + } catch (failure: Throwable) { + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt new file mode 100644 index 0000000000..0e2c9c7eb7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 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 im.vector.matrix.android.internal.crypto.store.db.mapper + +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel +import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey +import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity +import io.realm.RealmList +import timber.log.Timber +import javax.inject.Inject + +internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) { + + private val signaturesAdapter = moshi.adapter>>(Types.newParameterizedType( + Map::class.java, + String::class.java, + Any::class.java + )) + + fun update(keyInfo: KeyInfoEntity, cryptoCrossSigningKey: CryptoCrossSigningKey) { + // update signatures? + keyInfo.signatures = serializeSignatures(cryptoCrossSigningKey.signatures) + keyInfo.usages = cryptoCrossSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } + ?: RealmList() + } + + fun map(userId: String?, keyInfo: KeyInfoEntity?): CryptoCrossSigningKey? { + val pubKey = keyInfo?.publicKeyBase64 ?: return null + return CryptoCrossSigningKey( + userId = userId ?: "", + keys = mapOf("ed25519:$pubKey" to pubKey), + usages = keyInfo.usages.map { it }, + signatures = deserializeSignatures(keyInfo.signatures), + trustLevel = keyInfo.trustLevelEntity?.let { + DeviceTrustLevel( + crossSigningVerified = it.crossSignedVerified ?: false, + locallyVerified = it.locallyVerified ?: false + ) + } + ) + } + + fun map(keyInfo: CryptoCrossSigningKey): KeyInfoEntity { + return KeyInfoEntity().apply { + publicKeyBase64 = keyInfo.unpaddedBase64PublicKey + usages = keyInfo.usages?.let { RealmList(*it.toTypedArray()) } ?: RealmList() + signatures = serializeSignatures(keyInfo.signatures) + // TODO how to handle better, check if same keys? + // reset trust + trustLevelEntity = null + } + } + + fun serializeSignatures(signatures: Map>?): String { + return signaturesAdapter.toJson(signatures) + } + + fun deserializeSignatures(signatures: String?): Map>? { + if (signatures == null) { + return null + } + return try { + signaturesAdapter.fromJson(signatures) + } catch (failure: Throwable) { + Timber.e(failure) + null + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt index c40c752fbe..3ced818449 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt @@ -16,8 +16,6 @@ package im.vector.matrix.android.internal.crypto.store.db.model -import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm -import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm import io.realm.RealmList import io.realm.RealmObject @@ -31,15 +29,4 @@ internal open class KeyInfoEntity( */ var signatures: String? = null, var trustLevelEntity: TrustLevelEntity? = null -) : RealmObject() { - - // Deserialize data - fun getSignatures(): Map>? { - return deserializeFromRealm(signatures) - } - - // Serialize data - fun putSignatures(deviceInfo: Map>?) { - signatures = serializeForRealm(deviceInfo) - } -} +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index f4886c72da..69c0877a40 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -152,7 +152,7 @@ internal class RoomSummaryUpdater @Inject constructor( if (updateMembers) { val otherRoomMembers = RoomMemberHelper(realm, roomId) - .queryRoomMembersEvent() + .queryActiveRoomMembersEvent() .notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) .findAll() .asSequence()