From 34713d50232eae0453fb04380f12d1792ba110d9 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 20 Apr 2022 12:02:08 +0300 Subject: [PATCH 01/40] Add sharing existing inbound sessions functionality on new room invites --- .../crypto/algorithms/IMXDecrypting.kt | 3 ++ .../algorithms/megolm/MXMegolmDecryption.kt | 44 ++++++++++++++++++- .../internal/crypto/store/IMXCryptoStore.kt | 8 ++++ .../crypto/store/db/RealmCryptoStore.kt | 12 +++++ .../membership/DefaultMembershipService.kt | 3 ++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index 6847a46369..ce445d3ce6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService /** @@ -43,4 +44,6 @@ internal interface IMXDecrypting { * @param defaultKeysBackupService the keys backup service */ fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} + + fun shareKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) {} } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 141d6f74cd..a9ffe5f008 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -28,7 +28,9 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager +import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction +import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -307,4 +309,44 @@ internal class MXMegolmDecryption( Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") newSessionListener?.onNewSession(roomId, senderKey, sessionId) } + override fun shareKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) { + exportedKeys ?: return + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + runCatching { deviceListManager.downloadKeys(listOf(userId), false) } + .mapCatching { + val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) + if (deviceInfo == null) { + throw RuntimeException() + } else { + val devicesByUser = mapOf(userId to listOf(deviceInfo)) + val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser) + val olmSessionResult = usersDeviceMap.getObject(userId, deviceId) + if (olmSessionResult?.sessionId == null) { + // no session with this device, probably because there + // were no one-time keys. + Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.") + return@mapCatching + } + Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${exportedKeys.sessionId} with device $userId:$deviceId") + + val payloadJson = mapOf( + "type" to EventType.FORWARDED_ROOM_KEY, + "content" to exportedKeys + ) + + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap() + sendToDeviceMap.setObject(userId, deviceId, encodedPayload) + Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${exportedKeys.sessionId} to $userId:$deviceId") + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + try { + sendToDeviceTask.execute(sendToDeviceParams) + } catch (failure: Throwable) { + Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${exportedKeys.sessionId} to $userId:$deviceId") + } + } + } + } + } + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index b5b8d8e974..b961e83b9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -66,6 +66,14 @@ internal interface IMXCryptoStore { */ fun getInboundGroupSessions(): List + /** + * Retrieve the known inbound group sessions for the specified room + * + * @param roomId The roomId that the sessions will be returned + * @return the list of all known group sessions, for the provided roomId + */ + fun getInboundGroupSessions(roomId: String): List + /** * @return true to unilaterally blacklist all unverified devices. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 028d8f73f9..a3c7d6ccc1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -828,6 +828,18 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getInboundGroupSessions(roomId: String): List { + return doWithRealm(realmConfiguration) { + it.where() + .findAll() + .mapNotNull { inboundGroupSessionEntity -> + inboundGroupSessionEntity.getInboundGroupSession() + }.filter { inboundSession -> + inboundSession.roomId == roomId + } + } + } + override fun removeInboundGroupSession(sessionId: String, senderKey: String) { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index ec140e7b17..2b3fa10c89 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -23,6 +23,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.Realm import io.realm.RealmQuery +import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams @@ -50,6 +51,7 @@ internal class DefaultMembershipService @AssistedInject constructor( private val inviteThreePidTask: InviteThreePidTask, private val membershipAdminTask: MembershipAdminTask, private val roomDataSource: RoomDataSource, + private val cryptoService: CryptoService, @UserId private val userId: String, private val queryStringValueProcessor: QueryStringValueProcessor @@ -139,6 +141,7 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun invite(userId: String, reason: String?) { + cryptoService.sendSharedHistoryKeys(roomId, userId) val params = InviteTask.Params(roomId, userId, reason) inviteTask.execute(params) } From 98b55457b5e036b1a2ea8e094aefae35847a00f7 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 21 Apr 2022 13:43:41 +0300 Subject: [PATCH 02/40] Add sendSharedHistoryKeys in crypto service --- .../sdk/api/session/crypto/CryptoService.kt | 5 +++++ .../internal/crypto/DefaultCryptoService.kt | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 638da11804..99fb41b10d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -176,4 +176,9 @@ interface CryptoService { * send, in order to speed up sending of the message. */ fun prepareToEncrypt(roomId: String, callback: MatrixCallback) + + /** + * Share existing inbound sessions with the provided userId devices + */ + fun sendSharedHistoryKeys(roomId: String, userId: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index e0bcde2296..689c49f33b 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -1334,7 +1334,23 @@ internal class DefaultCryptoService @Inject constructor( ) } } - + override fun sendSharedHistoryKeys(roomId: String, userId: String) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + val userDevices = cryptoStore.getUserDevices(userId) + userDevices?.forEach { + // Lets share our existing inbound sessions for every user device + val deviceId = it.key + val inboundSessions = cryptoStore.getInboundGroupSessions(roomId) + inboundSessions.forEach { inboundGroupSession -> + // Share the session with the to userId with deviceId + val exportedKeys = inboundGroupSession.exportKeys() + val algorithm = exportedKeys?.algorithm + val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) + decryptor?.shareKeysWithDevice(exportedKeys, deviceId, userId) + } + } + } + } /* ========================================================================================== * For test only * ========================================================================================== */ From 6e57aeb9e5f981746d298a89728fa34834ae9367 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 21 Apr 2022 17:10:02 +0300 Subject: [PATCH 03/40] Add roomId in InboundSessionEntity for better performance Add shared history flag to InboundSessionEntity --- .../internal/crypto/DefaultCryptoService.kt | 1 + .../crypto/store/db/RealmCryptoStore.kt | 13 +++++--- .../store/db/RealmCryptoStoreMigration.kt | 4 ++- .../store/db/migration/MigrateCryptoTo017.kt | 32 +++++++++++++++++++ .../db/model/OlmInboundGroupSessionEntity.kt | 4 +++ 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 689c49f33b..ee95dfed9e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -964,6 +964,7 @@ internal class DefaultCryptoService @Inject constructor( if (!event.isStateEvent()) return val eventContent = event.content.toModel() eventContent?.historyVisibility?.let { + Timber.i("-----> onRoomHistoryVisibilityEvent $it") cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index a3c7d6ccc1..c39ae6813e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -745,9 +745,13 @@ internal class RealmCryptoStore @Inject constructor( if (sessionIdentifier != null) { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey) - val existing = realm.where() - .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) - .findFirst() + val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { + primaryKey = key + sessionId = sessionIdentifier + senderKey = session.senderKey + roomId = session.roomId + putInboundGroupSession(session) + } if (existing != null) { // we want to keep the existing backup status @@ -831,11 +835,10 @@ internal class RealmCryptoStore @Inject constructor( override fun getInboundGroupSessions(roomId: String): List { return doWithRealm(realmConfiguration) { it.where() + .equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId) .findAll() .mapNotNull { inboundGroupSessionEntity -> inboundGroupSessionEntity.getInboundGroupSession() - }.filter { inboundSession -> - inboundSession.roomId == roomId } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 02c2a27dec..4ca9d44f98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016 +import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017 import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -51,7 +52,7 @@ internal class RealmCryptoStoreMigration @Inject constructor( // 0, 1, 2: legacy Riot-Android // 3: migrate to RiotX schema // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - val schemaVersion = 16L + val schemaVersion = 17L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion") @@ -72,5 +73,6 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 14) MigrateCryptoTo014(realm).perform() if (oldVersion < 15) MigrateCryptoTo015(realm).perform() if (oldVersion < 16) MigrateCryptoTo016(realm).perform() + if (oldVersion < 17) MigrateCryptoTo017(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt new file mode 100644 index 0000000000..36f17f4674 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.store.db.migration + +import io.realm.DynamicRealm + +import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +// Version 16L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061 +internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 16) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("OlmInboundGroupSessionEntity") + ?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java) + ?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt index a4f6c279ac..12e9b8baee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt @@ -30,8 +30,12 @@ internal open class OlmInboundGroupSessionEntity( @PrimaryKey var primaryKey: String? = null, var sessionId: String? = null, var senderKey: String? = null, + var roomId: String? = null, // olmInboundGroupSessionData contains Json var olmInboundGroupSessionData: String? = null, + // Flag that indicates whether or not the current inboundSession will be shared to + // invited users to decrypt past messages + var sharedHistory: Boolean = false, // Indicate if the key has been backed up to the homeserver var backedUp: Boolean = false ) : From e861edd544b49a5702f66f8d1b00e98c5df094be Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 26 Apr 2022 17:51:03 +0300 Subject: [PATCH 04/40] Implement history key sharing functionality with respect to room visibility settings --- .../room/model/RoomHistoryVisibility.kt | 6 ++++ .../internal/crypto/DefaultCryptoService.kt | 9 +++-- .../model/OlmInboundGroupSessionWrapper2.kt | 4 +++ .../internal/crypto/store/IMXCryptoStore.kt | 11 ++++++ .../crypto/store/db/RealmCryptoStore.kt | 34 +++++++++++-------- .../store/db/RealmCryptoStoreMigration.kt | 3 ++ .../store/db/migration/MigrateCryptoTo017.kt | 4 +++ .../crypto/store/db/model/CryptoRoomEntity.kt | 2 ++ 8 files changed, 57 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt index 06069f2646..3648d6b883 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt @@ -48,3 +48,9 @@ enum class RoomHistoryVisibility { */ @Json(name = "joined") JOINED } + +/** + * Room history should be shared only if room visibility is world_readable or shared + */ +internal fun RoomHistoryVisibility.shouldShareHistory() = + this == RoomHistoryVisibility.WORLD_READABLE || this == RoomHistoryVisibility.SHARED diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index ee95dfed9e..4a44e9e2d0 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -71,6 +71,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction @@ -964,8 +965,8 @@ internal class DefaultCryptoService @Inject constructor( if (!event.isStateEvent()) return val eventContent = event.content.toModel() eventContent?.historyVisibility?.let { - Timber.i("-----> onRoomHistoryVisibilityEvent $it") cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, it.shouldShareHistory()) } } @@ -1335,6 +1336,7 @@ internal class DefaultCryptoService @Inject constructor( ) } } + override fun sendSharedHistoryKeys(roomId: String, userId: String) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { val userDevices = cryptoStore.getUserDevices(userId) @@ -1342,12 +1344,15 @@ internal class DefaultCryptoService @Inject constructor( // Lets share our existing inbound sessions for every user device val deviceId = it.key val inboundSessions = cryptoStore.getInboundGroupSessions(roomId) - inboundSessions.forEach { inboundGroupSession -> + inboundSessions.filter { inboundGroupSession -> + inboundGroupSession.sharedHistory + }.forEach { inboundGroupSession -> // Share the session with the to userId with deviceId val exportedKeys = inboundGroupSession.exportKeys() val algorithm = exportedKeys?.algorithm val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) decryptor?.shareKeysWithDevice(exportedKeys, deviceId, userId) + Timber.i("## CRYPTO | Sharing inbound session") } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index 289c169d6d..570532ae8b 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -43,6 +43,10 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { // Devices which forwarded this session to us (normally empty). var forwardingCurve25519KeyChain: List? = ArrayList() + // Flag that indicates whether or not the current inboundSession will be shared to + // invited users to decrypt past messages + var sharedHistory: Boolean = false + /** * @return the first known message index */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index b961e83b9a..ceb91aa8ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -258,6 +258,17 @@ internal interface IMXCryptoStore { fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: 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. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index c39ae6813e..7cc2f75285 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -657,12 +657,25 @@ internal class RealmCryptoStore @Inject constructor( ?: false } + override fun shouldShareHistory(roomId: String): Boolean { + return doWithRealm(realmConfiguration) { + CryptoRoomEntity.getById(it, roomId)?.shouldShareHistory + } + ?: false + } + override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers } } + override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { + doRealmTransaction(realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory + } + } + override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) { var sessionIdentifier: String? = null @@ -743,6 +756,10 @@ internal class RealmCryptoStore @Inject constructor( } if (sessionIdentifier != null) { + val shouldShareHistory = session.roomId?.let { roomId -> + CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory + } ?: false + session.sharedHistory = shouldShareHistory val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey) val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { @@ -750,22 +767,11 @@ internal class RealmCryptoStore @Inject constructor( sessionId = sessionIdentifier senderKey = session.senderKey roomId = session.roomId + sharedHistory = shouldShareHistory putInboundGroupSession(session) } - - if (existing != null) { - // we want to keep the existing backup status - existing.putInboundGroupSession(session) - } else { - val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { - primaryKey = key - sessionId = sessionIdentifier - senderKey = session.senderKey - putInboundGroupSession(session) - } - - realm.insertOrUpdate(realmOlmInboundGroupSession) - } + Timber.i("## CRYPTO | shouldShareHistory: $shouldShareHistory for $key") + realm.insertOrUpdate(realmOlmInboundGroupSession) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 4ca9d44f98..35229d205d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -73,6 +73,9 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 14) MigrateCryptoTo014(realm).perform() if (oldVersion < 15) MigrateCryptoTo015(realm).perform() if (oldVersion < 16) MigrateCryptoTo016(realm).perform() +<<<<<<< develop if (oldVersion < 17) MigrateCryptoTo017(realm).perform() +======= +>>>>>>> Implement history key sharing functionality with respect to room visibility settings } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt index 36f17f4674..6ce94c3eb2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.migration import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator @@ -28,5 +29,8 @@ internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 16 realm.schema.get("OlmInboundGroupSessionEntity") ?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java) ?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java) + + realm.schema.get("CryptoRoomEntity") + ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt index 114a596964..be57586163 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt @@ -24,6 +24,8 @@ internal open class CryptoRoomEntity( var algorithm: String? = null, var shouldEncryptForInvitedMembers: Boolean? = null, var blacklistUnverifiedDevices: Boolean = false, + // Determines whether or not room history should be shared on new member invites + var shouldShareHistory: Boolean = false, // Store the current outbound session for this room, // to avoid re-create and re-share at each startup (if rotation not needed..) // This is specific to megolm but not sure how to model it better From b3bfd05ecbc723244ad1165fbc849a7e514bcdaf Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 27 Apr 2022 12:10:21 +0300 Subject: [PATCH 05/40] - Share only the first chunk of inbound sessions instead of the whole key history - Download keys if the user is unknown (first invite) --- .../sdk/api/session/crypto/CryptoService.kt | 8 ++++- .../internal/crypto/DefaultCryptoService.kt | 31 ++++++++++++++++++- .../database/helper/ChunkEntityHelper.kt | 15 +++++++++ .../membership/DefaultMembershipService.kt | 7 ++++- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 99fb41b10d..febf26baf0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent +import org.matrix.android.sdk.internal.database.helper.SessionInfoPair interface CryptoService { @@ -178,7 +179,12 @@ interface CryptoService { fun prepareToEncrypt(roomId: String, callback: MatrixCallback) /** - * Share existing inbound sessions with the provided userId devices + * Share all existing inbound sessions to the provided userId devices */ fun sendSharedHistoryKeys(roomId: String, userId: String) + + /** + * Share all inbound sessions of the last chunk messages to the provided userId devices + */ + fun sendSharedHistoryKeysToLastChunk(roomId: String, userId: String, sessionInfoSet: Set?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 4a44e9e2d0..46457ee494 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -92,6 +92,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor +import org.matrix.android.sdk.internal.database.helper.SessionInfoPair import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId @@ -1337,6 +1338,34 @@ internal class DefaultCryptoService @Inject constructor( } } + override fun sendSharedHistoryKeysToLastChunk(roomId: String, userId: String, sessionInfoSet: Set?) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + runCatching { + deviceListManager.downloadKeys(listOf(userId), false) + }.mapCatching { + val userDevices = cryptoStore.getUserDevices(userId) + userDevices?.forEach { + // Lets share the provided inbound sessions for every user device + val deviceId = it.key + sessionInfoSet?.mapNotNull { sessionInfoPair -> + // Get inbound session from sessionId and sessionKey + cryptoStore.getInboundGroupSession(sessionInfoPair.first, sessionInfoPair.second) + }?.filter { inboundGroupSession -> + // Filter only sessions with sharedHistory enabled + inboundGroupSession.sharedHistory + }?.forEach { inboundGroupSession -> + // Share the session to userId with deviceId + val exportedKeys = inboundGroupSession.exportKeys() + val algorithm = exportedKeys?.algorithm + val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) + decryptor?.shareKeysWithDevice(exportedKeys, deviceId, userId) + Timber.i("## CRYPTO | Sharing inbound session") + } + } + } + } + } + override fun sendSharedHistoryKeys(roomId: String, userId: String) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { val userDevices = cryptoStore.getUserDevices(userId) @@ -1347,7 +1376,7 @@ internal class DefaultCryptoService @Inject constructor( inboundSessions.filter { inboundGroupSession -> inboundGroupSession.sharedHistory }.forEach { inboundGroupSession -> - // Share the session with the to userId with deviceId + // Share the session to userId with deviceId val exportedKeys = inboundGroupSession.exportKeys() val algorithm = exportedKeys?.algorithm val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 234caec970..73af812271 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -18,7 +18,10 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm import io.realm.kotlin.createObject +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity @@ -31,6 +34,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection @@ -180,3 +184,14 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { // We don't know, so we assume it's false return false } + +internal fun ChunkEntity.Companion.findLatestSessionInfo(realm: Realm, roomId: String): Set? = + ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.mapNotNull { timelineEvent -> + timelineEvent?.root?.asDomain()?.content?.toModel()?.let { content -> + content.sessionId ?: return@mapNotNull null + content.senderKey ?: return@mapNotNull null + Pair(content.sessionId, content.senderKey) + } + }?.toSet() + +internal typealias SessionInfoPair = Pair diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 2b3fa10c89..b6dbaa7be8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -29,7 +29,9 @@ import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import org.matrix.android.sdk.internal.database.helper.findLatestSessionInfo import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType @@ -141,7 +143,10 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun invite(userId: String, reason: String?) { - cryptoService.sendSharedHistoryKeys(roomId, userId) + val sessionInfoSet = Realm.getInstance(monarchy.realmConfiguration).use { + ChunkEntity.findLatestSessionInfo(it, roomId) + } + cryptoService.sendSharedHistoryKeysToLastChunk(roomId, userId, sessionInfoSet) val params = InviteTask.Params(roomId, userId, reason) inviteTask.execute(params) } From dd3928f075c144304cf4c761ad7823e069e580d6 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 28 Apr 2022 14:16:48 +0300 Subject: [PATCH 06/40] Remove sendSharedHistoryKeys while we will only share latest messages --- .../matrix/android/sdk/api/session/crypto/CryptoService.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index febf26baf0..95db92c9b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -178,11 +178,6 @@ interface CryptoService { */ fun prepareToEncrypt(roomId: String, callback: MatrixCallback) - /** - * Share all existing inbound sessions to the provided userId devices - */ - fun sendSharedHistoryKeys(roomId: String, userId: String) - /** * Share all inbound sessions of the last chunk messages to the provided userId devices */ From 28a3ae264cc31d676257e7215dc858f477318b9b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 28 Apr 2022 14:19:27 +0300 Subject: [PATCH 07/40] Remove sharedHistory from OlmInboundGroupSessionWrapper2 while there are migration issues, and use only the equivalent DB entity value --- .../internal/crypto/DefaultCryptoService.kt | 33 ++++--------------- .../sdk/internal/crypto/MegolmSessionData.kt | 10 +++++- .../model/OlmInboundGroupSessionWrapper2.kt | 11 +++---- .../internal/crypto/store/IMXCryptoStore.kt | 12 ++++++- .../crypto/store/db/RealmCryptoStore.kt | 12 ++++++- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 46457ee494..59acf1b8db 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -1349,13 +1349,14 @@ internal class DefaultCryptoService @Inject constructor( val deviceId = it.key sessionInfoSet?.mapNotNull { sessionInfoPair -> // Get inbound session from sessionId and sessionKey - cryptoStore.getInboundGroupSession(sessionInfoPair.first, sessionInfoPair.second) - }?.filter { inboundGroupSession -> - // Filter only sessions with sharedHistory enabled - inboundGroupSession.sharedHistory + cryptoStore.getInboundGroupSession( + sessionId = sessionInfoPair.first, + senderKey = sessionInfoPair.second, + sharedHistory = true + ) }?.forEach { inboundGroupSession -> - // Share the session to userId with deviceId - val exportedKeys = inboundGroupSession.exportKeys() + // Share the sharable session to userId with deviceId + val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true) val algorithm = exportedKeys?.algorithm val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) decryptor?.shareKeysWithDevice(exportedKeys, deviceId, userId) @@ -1366,26 +1367,6 @@ internal class DefaultCryptoService @Inject constructor( } } - override fun sendSharedHistoryKeys(roomId: String, userId: String) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - val userDevices = cryptoStore.getUserDevices(userId) - userDevices?.forEach { - // Lets share our existing inbound sessions for every user device - val deviceId = it.key - val inboundSessions = cryptoStore.getInboundGroupSessions(roomId) - inboundSessions.filter { inboundGroupSession -> - inboundGroupSession.sharedHistory - }.forEach { inboundGroupSession -> - // Share the session to userId with deviceId - val exportedKeys = inboundGroupSession.exportKeys() - val algorithm = exportedKeys?.algorithm - val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) - decryptor?.shareKeysWithDevice(exportedKeys, deviceId, userId) - Timber.i("## CRYPTO | Sharing inbound session") - } - } - } - } /* ========================================================================================== * For test only * ========================================================================================== */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt index f6bc9a9148..43b88c0b42 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt @@ -69,5 +69,13 @@ internal data class MegolmSessionData( * Devices which forwarded this session to us (normally empty). */ @Json(name = "forwarding_curve25519_key_chain") - val forwardingCurve25519KeyChain: List? = null + val forwardingCurve25519KeyChain: List? = null, + + /** + * Flag that indicates whether or not the current inboundSession will be shared to + * invited users to decrypt past messages + */ + // When this feature lands in spec name = shared_history should be used + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean = false, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index 570532ae8b..b3898e001b 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -43,10 +43,6 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { // Devices which forwarded this session to us (normally empty). var forwardingCurve25519KeyChain: List? = ArrayList() - // Flag that indicates whether or not the current inboundSession will be shared to - // invited users to decrypt past messages - var sharedHistory: Boolean = false - /** * @return the first known message index */ @@ -110,10 +106,10 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { /** * Export the inbound group session keys. * @param index the index to export. If null, the first known index will be used - * + * @param sharedHistory the flag that indicates whether or not the session can be shared * @return the inbound group session as MegolmSessionData if the operation succeeds */ - fun exportKeys(index: Long? = null): MegolmSessionData? { + fun exportKeys(sharedHistory: Boolean = false, index: Long? = null): MegolmSessionData? { return try { if (null == forwardingCurve25519KeyChain) { forwardingCurve25519KeyChain = ArrayList() @@ -135,7 +131,8 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { roomId = roomId, sessionId = safeOlmInboundGroupSession.sessionIdentifier(), sessionKey = safeOlmInboundGroupSession.export(wantedIndex), - algorithm = MXCRYPTO_ALGORITHM_MEGOLM + algorithm = MXCRYPTO_ALGORITHM_MEGOLM, + sharedHistory = sharedHistory ) } catch (e: Exception) { Timber.e(e, "## export() : senderKey $senderKey failed") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index ceb91aa8ce..65e360a4f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -321,7 +321,17 @@ internal interface IMXCryptoStore { fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? /** - * Get the current outbound group session for this encrypted room. + * Retrieve an inbound group session, filtering shared history. + * + * @param sessionId the session identifier. + * @param senderKey the base64-encoded curve25519 key of the sender. + * @param sharedHistory filter inbound session with respect to shared history field + * @return an inbound group session. + */ + fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): OlmInboundGroupSessionWrapper2? + + /** + * Get the current outbound group session for this encrypted room */ fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 7cc2f75285..3034d432c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -759,7 +759,6 @@ internal class RealmCryptoStore @Inject constructor( val shouldShareHistory = session.roomId?.let { roomId -> CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory } ?: false - session.sharedHistory = shouldShareHistory val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey) val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { @@ -788,6 +787,17 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): OlmInboundGroupSessionWrapper2? { + val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) + return doWithRealm(realmConfiguration) { + it.where() + .equalTo(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, sharedHistory) + .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) + .findFirst() + ?.getInboundGroupSession() + } + } + override fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? { return doWithRealm(realmConfiguration) { realm -> realm.where() From d6358dcb169469a68ed253ee3847f735fc6ca05b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 28 Apr 2022 17:15:46 +0300 Subject: [PATCH 08/40] Prevent injecting a forged encrypted message and using session_id/sender_key of another room. --- .../matrix/android/sdk/internal/crypto/DefaultCryptoService.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 59acf1b8db..202cc2273c 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -1354,6 +1354,9 @@ internal class DefaultCryptoService @Inject constructor( senderKey = sessionInfoPair.second, sharedHistory = true ) + }?.filter { inboundGroupSession -> + // Prevent injecting a forged encrypted message and using session_id/sender_key of another room. + inboundGroupSession.roomId == roomId }?.forEach { inboundGroupSession -> // Share the sharable session to userId with deviceId val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true) From 497f7cf04425b8bb71145fa0699a8511e3ed32be Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 29 Apr 2022 13:44:26 +0300 Subject: [PATCH 09/40] Rotate our session when there is a room history visibility change since the last outboundSession --- .../crypto/algorithms/megolm/MXMegolmEncryption.kt | 2 ++ .../sdk/internal/crypto/store/IMXCryptoStore.kt | 9 ++++++++- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 10 ++++++++++ .../store/db/model/OutboundGroupSessionInfoEntity.kt | 3 ++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 7bfbae6edf..975530f5c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -172,6 +172,8 @@ internal class MXMegolmEncryption( if (session == null || // Need to make a brand new session? session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) || + // Is there a room history visibility change since the last outboundSession + cryptoStore.needsRotationDueToVisibilityChange(roomId) || // Determine if we have shared with anyone we shouldn't have session.sharedWithTooManyDevices(devicesInRoom)) { Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 65e360a4f3..42ff4e11ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -341,7 +341,14 @@ internal interface IMXCryptoStore { fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?) /** - * Remove an inbound group session. + * Returns true if there is a room history visibility change since the latest outbound + * session. Specifically when the room's history visibility setting changes to + * world_readable or shared from invited or joined, or changes to invited or joined from world_readable or shared + */ + fun needsRotationDueToVisibilityChange(roomId: String): Boolean + + /** + * Remove an inbound group session * * @param sessionId the session identifier. * @param senderKey the base64-encoded curve25519 key of the sender. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 3034d432c2..e226aa2e8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -826,6 +826,8 @@ internal class RealmCryptoStore @Inject constructor( if (outboundGroupSession != null) { val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply { creationTime = clock.epochMillis() + // Store the room history visibility on the outbound session creation + shouldShareHistory = entity.shouldShareHistory putOutboundGroupSession(outboundGroupSession) } entity.outboundSessionInfo = info @@ -834,6 +836,14 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun needsRotationDueToVisibilityChange(roomId: String): Boolean { + return doWithRealm(realmConfiguration) { realm -> + CryptoRoomEntity.getById(realm, roomId)?.let { entity -> + entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory + } + } ?: false + } + /** * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2, * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt index d50db78415..2ebd550201 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt @@ -24,7 +24,8 @@ import timber.log.Timber internal open class OutboundGroupSessionInfoEntity( var serializedOutboundSessionData: String? = null, - var creationTime: Long? = null + var creationTime: Long? = null, + var shouldShareHistory: Boolean = false ) : RealmObject() { fun getOutboundGroupSession(): OlmOutboundGroupSession? { From 395d48f946246b7297c39782fe91f5a6904e2fcb Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 29 Apr 2022 14:02:26 +0300 Subject: [PATCH 10/40] Refactor code structure and improve naming --- .../sdk/api/session/crypto/CryptoService.kt | 4 ++-- .../internal/crypto/DefaultCryptoService.kt | 13 ++++++----- .../crypto/algorithms/IMXDecrypting.kt | 2 +- .../sdk/internal/crypto/model/SessionInfo.kt | 22 +++++++++++++++++++ .../database/helper/ChunkEntityHelper.kt | 11 +++++----- .../membership/DefaultMembershipService.kt | 6 +++-- 6 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 95db92c9b4..b84c5e2a80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.database.helper.SessionInfoPair +import org.matrix.android.sdk.internal.crypto.model.SessionInfo interface CryptoService { @@ -181,5 +181,5 @@ interface CryptoService { /** * Share all inbound sessions of the last chunk messages to the provided userId devices */ - fun sendSharedHistoryKeysToLastChunk(roomId: String, userId: String, sessionInfoSet: Set?) + fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 202cc2273c..044f3e86f3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -82,6 +82,7 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFact import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE +import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -968,7 +969,7 @@ internal class DefaultCryptoService @Inject constructor( eventContent?.historyVisibility?.let { cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED) cryptoStore.setShouldShareHistory(roomId, it.shouldShareHistory()) - } + } ?: cryptoStore.setShouldShareHistory(roomId, false) } /** @@ -1338,7 +1339,7 @@ internal class DefaultCryptoService @Inject constructor( } } - override fun sendSharedHistoryKeysToLastChunk(roomId: String, userId: String, sessionInfoSet: Set?) { + override fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { deviceListManager.downloadKeys(listOf(userId), false) @@ -1347,11 +1348,11 @@ internal class DefaultCryptoService @Inject constructor( userDevices?.forEach { // Lets share the provided inbound sessions for every user device val deviceId = it.key - sessionInfoSet?.mapNotNull { sessionInfoPair -> + sessionInfoSet?.mapNotNull { sessionInfo -> // Get inbound session from sessionId and sessionKey cryptoStore.getInboundGroupSession( - sessionId = sessionInfoPair.first, - senderKey = sessionInfoPair.second, + sessionId = sessionInfo.sessionId, + senderKey = sessionInfo.senderKey, sharedHistory = true ) }?.filter { inboundGroupSession -> @@ -1362,7 +1363,7 @@ internal class DefaultCryptoService @Inject constructor( val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true) val algorithm = exportedKeys?.algorithm val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) - decryptor?.shareKeysWithDevice(exportedKeys, deviceId, userId) + decryptor?.shareForwardKeysWithDevice(exportedKeys, deviceId, userId) Timber.i("## CRYPTO | Sharing inbound session") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index ce445d3ce6..7edae70fd8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -45,5 +45,5 @@ internal interface IMXDecrypting { */ fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} - fun shareKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) {} + fun shareForwardKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) {} } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt new file mode 100644 index 0000000000..45f0c519d1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.model + +data class SessionInfo( + val sessionId: String, + val senderKey: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 73af812271..221abe0df5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -21,6 +21,7 @@ import io.realm.kotlin.createObject import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields @@ -185,13 +186,11 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { return false } -internal fun ChunkEntity.Companion.findLatestSessionInfo(realm: Realm, roomId: String): Set? = +internal fun ChunkEntity.Companion.findLatestSessionInfo(realm: Realm, roomId: String): Set? = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.mapNotNull { timelineEvent -> timelineEvent?.root?.asDomain()?.content?.toModel()?.let { content -> - content.sessionId ?: return@mapNotNull null - content.senderKey ?: return@mapNotNull null - Pair(content.sessionId, content.senderKey) + content.sessionId ?: return@mapNotNull null + content.senderKey ?: return@mapNotNull null + SessionInfo(content.sessionId, content.senderKey) } }?.toSet() - -internal typealias SessionInfoPair = Pair diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index b6dbaa7be8..0f82872f23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.membership import androidx.lifecycle.LiveData +import com.otaliastudios.opengl.core.use import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -44,6 +45,7 @@ import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipA import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask import org.matrix.android.sdk.internal.util.fetchCopied +import timber.log.Timber internal class DefaultMembershipService @AssistedInject constructor( @Assisted private val roomId: String, @@ -143,10 +145,10 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun invite(userId: String, reason: String?) { - val sessionInfoSet = Realm.getInstance(monarchy.realmConfiguration).use { + val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use { ChunkEntity.findLatestSessionInfo(it, roomId) } - cryptoService.sendSharedHistoryKeysToLastChunk(roomId, userId, sessionInfoSet) + cryptoService.sendSharedHistoryKeys(roomId, userId, sessionInfo) val params = InviteTask.Params(roomId, userId, reason) inviteTask.execute(params) } From 243463adbc0504cd0093949fb87347274dac892b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 29 Apr 2022 14:19:22 +0300 Subject: [PATCH 11/40] Add logs --- .../android/sdk/internal/crypto/DefaultCryptoService.kt | 4 +++- .../internal/crypto/algorithms/megolm/MXMegolmEncryption.kt | 6 +++++- .../session/room/membership/DefaultMembershipService.kt | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 044f3e86f3..51e21a181e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -1357,7 +1357,9 @@ internal class DefaultCryptoService @Inject constructor( ) }?.filter { inboundGroupSession -> // Prevent injecting a forged encrypted message and using session_id/sender_key of another room. - inboundGroupSession.roomId == roomId + (inboundGroupSession.roomId == roomId).also { + Timber.tag(loggerTag.value).d("Forged encrypted message detected for roomId:$roomId") + } }?.forEach { inboundGroupSession -> // Share the sharable session to userId with deviceId val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 975530f5c5..740676cd59 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -173,7 +173,11 @@ internal class MXMegolmEncryption( // Need to make a brand new session? session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) || // Is there a room history visibility change since the last outboundSession - cryptoStore.needsRotationDueToVisibilityChange(roomId) || + cryptoStore.needsRotationDueToVisibilityChange(roomId).also { + if (it) { + Timber.tag(loggerTag.value).d("roomId:$roomId Room history visibility change detected since the last outbound session") + } + } || // Determine if we have shared with anyone we shouldn't have session.sharedWithTooManyDevices(devicesInRoom)) { Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 0f82872f23..fdda38777a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -45,7 +45,6 @@ import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipA import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask import org.matrix.android.sdk.internal.util.fetchCopied -import timber.log.Timber internal class DefaultMembershipService @AssistedInject constructor( @Assisted private val roomId: String, From 45c80de333ffed7d861eb38a1952f9653fa54d27 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 29 Apr 2022 15:15:12 +0300 Subject: [PATCH 12/40] Add changelog --- changelog.d/5853.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5853.feature diff --git a/changelog.d/5853.feature b/changelog.d/5853.feature new file mode 100644 index 0000000000..2a399e76aa --- /dev/null +++ b/changelog.d/5853.feature @@ -0,0 +1 @@ +Improve user experience when he is first invited to a room. Users will be able to decrypt and view previous messages From 96f0d527536bcd14c79d2b872554c8fefeb9438d Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 3 May 2022 12:01:30 +0300 Subject: [PATCH 13/40] Update copyright --- .../org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt index 45f0c519d1..b3a2ba4dfe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 28dd507a7477050ab5d7989e0e959fbaaad93884 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 3 May 2022 14:09:19 +0300 Subject: [PATCH 14/40] Add crypto shared history sanity test --- .../android/sdk/common/CryptoTestHelper.kt | 12 +- .../crypto/E2eeShareKeysSanityTest.kt | 229 ++++++++++++++++++ 2 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index 5fd86d4fdb..3b0ea70f92 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -76,11 +77,14 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { /** * @return alice session */ - fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData { + fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = testHelper.runBlockingTest { - aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" }) + aliceSession.createRoom(CreateRoomParams().apply { + historyVisibility = roomHistoryVisibility + name = "MyRoom" + }) } if (encryptedRoom) { testHelper.waitWithLatch { latch -> @@ -104,8 +108,8 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { /** * @return alice and bob sessions */ - fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData { - val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom) + fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData { + val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom, roomHistoryVisibility) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt new file mode 100644 index 0000000000..83dd258bbb --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt @@ -0,0 +1,229 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import android.util.Log +import androidx.test.filters.LargeTest +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class E2eeShareKeysSanityTest : InstrumentedTest { + + private val testHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(testHelper) + + @Test + fun testShareMessagesHistoryWithRoomWorldReadable() { + testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.WORLD_READABLE) + } + + @Test + fun testShareMessagesHistoryWithRoomShared() { + testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.SHARED) + } + + @Test + fun testShareMessagesHistoryWithRoomJoined() { + testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.JOINED) + } + + @Test + fun testShareMessagesHistoryWithRoomInvited() { + testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.INVITED) + } + + /** + * Simple test that creates an e2ee room. + * In this test we create a room and test that new members + * can decrypt history when the room visibility is + * RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE + */ + private fun testSharedHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) { + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) + + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession + val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! + + // Bob + val bobSession = cryptoTestData.secondSession + val bobRoomPOV = bobSession!!.getRoom(e2eRoomID)!! + + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") + + val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!") + Assert.assertTrue("Message should be sent", aliceMessageId != null) + Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") + + // Bob should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + + // Create a new user + val arisSession = testHelper.createAccount("aris", SessionTestParams(true)) + Log.v("#E2E TEST", "Aris user created") + + // Alice invites new user to the room + testHelper.runBlockingTest { + Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") + aliceRoomPOV.invite(arisSession.myUserId) + } + + waitForAndAcceptInviteInRoom(arisSession, e2eRoomID) + + ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID) + Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID") + + when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE, + RoomHistoryVisibility.SHARED, + null + -> { + // Aris should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE + ).also { + if (it) { + Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + } + RoomHistoryVisibility.INVITED, + RoomHistoryVisibility.JOINED -> { + // Aris should not even be able to get the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + timelineEvent == null + } + } + } + } + + testHelper.signOutAndClose(arisSession) + cryptoTestData.cleanUp(testHelper) + } + + private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? { + aliceRoomPOV.sendTextMessage(text) + var sentEventId: String? = null + testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> + val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60)) + timeline.start() + + testHelper.retryPeriodicallyWithLatch(latch) { + val decryptedMsg = timeline.getSnapshot() + .filter { it.root.getClearType() == EventType.MESSAGE } + .also { list -> + val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } + Log.v("#E2E TEST", "Timeline snapshot is $message") + } + .filter { it.root.sendState == SendState.SYNCED } + .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true } + sentEventId = decryptedMsg?.eventId + decryptedMsg != null + } + + timeline.dispose() + } + return sentEventId + } + + private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List, e2eRoomID: String) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + otherAccounts.map { + aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership + }.all { + it == Membership.JOIN + } + } + } + } + + private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = otherSession.getRoomSummary(e2eRoomID) + (roomSummary != null && roomSummary.membership == Membership.INVITE).also { + if (it) { + Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice") + } + } + } + } + + testHelper.runBlockingTest(60_000) { + Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") + try { + otherSession.joinRoom(e2eRoomID) + } catch (ex: JoinRoomFailure.JoinedWithTimeout) { + // it's ok we will wait after + } + } + + Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { + val roomSummary = otherSession.getRoomSummary(e2eRoomID) + roomSummary != null && roomSummary.membership == Membership.JOIN + } + } + } +} From 3a5b737639cc93be02fc705a9c3b05153c809dfa Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 3 May 2022 14:10:14 +0300 Subject: [PATCH 15/40] Fix existing E2eeSanityTests to support changes for key history sharing --- .../matrix/android/sdk/internal/crypto/E2eeSanityTests.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 5a61eee7fe..d8c3c021cf 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -166,7 +166,7 @@ class E2eeSanityTests : InstrumentedTest { delay(3_000) } - // check that messages are encrypted (uisi) + // Due to the new shared keys implementation, invited user should be able to decrypt messages newAccount.forEach { otherSession -> testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { @@ -174,8 +174,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") } timelineEvent != null && - timelineEvent.root.getClearType() == EventType.ENCRYPTED && - timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID + timelineEvent.root.getClearType() == EventType.MESSAGE } } } From 2e88998b058a0f4468903c6e79c7f12e0c429303 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 3 May 2022 18:08:03 +0300 Subject: [PATCH 16/40] Add integration tests for shared keys rotation on room history visibility change --- .../crypto/E2eeShareKeysHistoryTest.kt | 375 ++++++++++++++++++ .../crypto/E2eeShareKeysSanityTest.kt | 229 ----------- 2 files changed, 375 insertions(+), 229 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt delete mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt new file mode 100644 index 0000000000..fa5c9c8b23 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -0,0 +1,375 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import android.util.Log +import androidx.test.filters.LargeTest +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.shouldShareHistory +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class E2eeShareKeysHistoryTest : InstrumentedTest { + + @Test + fun testShareMessagesHistoryWithRoomWorldReadable() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.WORLD_READABLE) + } + + @Test + fun testShareMessagesHistoryWithRoomShared() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.SHARED) + } + + @Test + fun testShareMessagesHistoryWithRoomJoined() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.JOINED) + } + + @Test + fun testShareMessagesHistoryWithRoomInvited() { + testShareHistoryWithRoomVisibility(RoomHistoryVisibility.INVITED) + } + + /** + * In this test we create a room and test that new members + * can decrypt history when the room visibility is + * RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE. + * We should not be able to view messages/decrypt otherwise + */ + private fun testShareHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) + + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! + + // Bob + val bobSession = cryptoTestData.secondSession + val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") + + val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper) + Assert.assertTrue("Message should be sent", aliceMessageId != null) + Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") + + // Bob should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + + // Create a new user + val arisSession = testHelper.createAccount("aris", SessionTestParams(true)) + Log.v("#E2E TEST", "Aris user created") + + // Alice invites new user to the room + testHelper.runBlockingTest { + Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") + aliceRoomPOV.invite(arisSession.myUserId) + } + + waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper) + + ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID, testHelper) + Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID") + + when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE, + RoomHistoryVisibility.SHARED, + null + -> { + // Aris should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE + ).also { + if (it) { + Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + } + RoomHistoryVisibility.INVITED, + RoomHistoryVisibility.JOINED -> { + // Aris should not even be able to get the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + timelineEvent == null + } + } + } + } + + testHelper.signOutAndClose(arisSession) + cryptoTestData.cleanUp(testHelper) + } + + @Test + fun testNeedsRotationFromWorldReadableToShared() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared")) + } + + @Test + fun testNeedsRotationFromWorldReadableToInvited() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("invited")) + } + + @Test + fun testNeedsRotationFromWorldReadableToJoined() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("joined")) + } + + @Test + fun testNeedsRotationFromSharedToWorldReadable() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("world_readable")) + } + + @Test + fun testNeedsRotationFromSharedToInvited() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("invited")) + } + + @Test + fun testNeedsRotationFromSharedToJoined() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("joined")) + } + + @Test + fun testNeedsRotationFromInvitedToShared() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared")) + } + + @Test + fun testNeedsRotationFromInvitedToWorldReadable() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("world_readable")) + } + + @Test + fun testNeedsRotationFromInvitedToJoined() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("joined")) + } + + @Test + fun testNeedsRotationFromJoinedToShared() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared")) + } + + @Test + fun testNeedsRotationFromJoinedToInvited() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("invited")) + } + + @Test + fun testNeedsRotationFromJoinedToWorldReadable() { + testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("world_readable")) + } + + /** + * In this test we will test that a rotation is needed when + * When the room's history visibility setting changes to world_readable or shared + * from invited or joined, or changes to invited or joined from world_readable or shared, + * senders that support this flag must rotate their megolm sessions. + */ + private fun testRotationDueToVisibilityChange( + initRoomHistoryVisibility: RoomHistoryVisibility, + nextRoomHistoryVisibility: RoomHistoryVisibilityContent + ) { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, initRoomHistoryVisibility) + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! + val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting + + // Bob + val bobSession = cryptoTestData.secondSession + val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + Log.v("#E2E TEST ROTATION", "Alice and Bob are in roomId: $e2eRoomID") + + val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper) + Assert.assertTrue("Message should be sent", aliceMessageId != null) + Log.v("#E2E TEST ROTATION", "Alice sent message to roomId: $e2eRoomID") + + // Bob should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + + // Rotation has already been done so we do not need to rotate again + assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false) + Log.v("#E2E TEST ROTATION", "No rotation needed yet") + + // Let's change the room history visibility + testHelper.waitWithLatch { + aliceRoomPOV.sendStateEvent( + eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + body = RoomHistoryVisibilityContent(_historyVisibility = nextRoomHistoryVisibility._historyVisibility).toContent() + ) + it.countDown() + } + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val roomVisibility = aliceSession.getRoom(e2eRoomID)!! + .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY) + ?.content + ?.toModel() + Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}") + roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility + } + } + + when { + initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> { + assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false) + Log.v("#E2E TEST ROTATION", "Rotation is not needed") + } + initRoomHistoryVisibility.shouldShareHistory() != nextRoomHistoryVisibility.historyVisibility!!.shouldShareHistory() -> { + assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), true) + Log.v("#E2E TEST ROTATION", "Rotation is needed!") + } + } + + cryptoTestData.cleanUp(testHelper) + } + + private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? { + aliceRoomPOV.sendTextMessage(text) + var sentEventId: String? = null + testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> + val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60)) + timeline.start() + testHelper.retryPeriodicallyWithLatch(latch) { + val decryptedMsg = timeline.getSnapshot() + .filter { it.root.getClearType() == EventType.MESSAGE } + .also { list -> + val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } + Log.v("#E2E TEST", "Timeline snapshot is $message") + } + .filter { it.root.sendState == SendState.SYNCED } + .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true } + sentEventId = decryptedMsg?.eventId + decryptedMsg != null + } + + timeline.dispose() + } + return sentEventId + } + + private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List, e2eRoomID: String, testHelper: CommonTestHelper) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + otherAccounts.map { + aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership + }.all { + it == Membership.JOIN + } + } + } + } + + private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) + (roomSummary != null && roomSummary.membership == Membership.INVITE).also { + if (it) { + Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice") + } + } + } + } + + testHelper.runBlockingTest(60_000) { + Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") + try { + otherSession.roomService().joinRoom(e2eRoomID) + } catch (ex: JoinRoomFailure.JoinedWithTimeout) { + // it's ok we will wait after + } + } + + Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { + val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) + roomSummary != null && roomSummary.membership == Membership.JOIN + } + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt deleted file mode 100644 index 83dd258bbb..0000000000 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysSanityTest.kt +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto - -import android.util.Log -import androidx.test.filters.LargeTest -import org.amshove.kluent.internal.assertEquals -import org.junit.Assert -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.runners.MethodSorters -import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.Room -import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper -import org.matrix.android.sdk.common.SessionTestParams -import org.matrix.android.sdk.common.TestConstants - -@RunWith(JUnit4::class) -@FixMethodOrder(MethodSorters.JVM) -@LargeTest -class E2eeShareKeysSanityTest : InstrumentedTest { - - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) - - @Test - fun testShareMessagesHistoryWithRoomWorldReadable() { - testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.WORLD_READABLE) - } - - @Test - fun testShareMessagesHistoryWithRoomShared() { - testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.SHARED) - } - - @Test - fun testShareMessagesHistoryWithRoomJoined() { - testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.JOINED) - } - - @Test - fun testShareMessagesHistoryWithRoomInvited() { - testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.INVITED) - } - - /** - * Simple test that creates an e2ee room. - * In this test we create a room and test that new members - * can decrypt history when the room visibility is - * RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE - */ - private fun testSharedHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) - - val e2eRoomID = cryptoTestData.roomId - - // Alice - val aliceSession = cryptoTestData.firstSession - val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! - - // Bob - val bobSession = cryptoTestData.secondSession - val bobRoomPOV = bobSession!!.getRoom(e2eRoomID)!! - - assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) - Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") - - val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!") - Assert.assertTrue("Message should be sent", aliceMessageId != null) - Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") - - // Bob should be able to decrypt the message - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) - (timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE).also { - if (it) { - Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") - } - } - } - } - - // Create a new user - val arisSession = testHelper.createAccount("aris", SessionTestParams(true)) - Log.v("#E2E TEST", "Aris user created") - - // Alice invites new user to the room - testHelper.runBlockingTest { - Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") - aliceRoomPOV.invite(arisSession.myUserId) - } - - waitForAndAcceptInviteInRoom(arisSession, e2eRoomID) - - ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID) - Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID") - - when (roomHistoryVisibility) { - RoomHistoryVisibility.WORLD_READABLE, - RoomHistoryVisibility.SHARED, - null - -> { - // Aris should be able to decrypt the message - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = arisSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) - (timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE - ).also { - if (it) { - Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") - } - } - } - } - } - RoomHistoryVisibility.INVITED, - RoomHistoryVisibility.JOINED -> { - // Aris should not even be able to get the message - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = arisSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) - timelineEvent == null - } - } - } - } - - testHelper.signOutAndClose(arisSession) - cryptoTestData.cleanUp(testHelper) - } - - private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? { - aliceRoomPOV.sendTextMessage(text) - var sentEventId: String? = null - testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> - val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60)) - timeline.start() - - testHelper.retryPeriodicallyWithLatch(latch) { - val decryptedMsg = timeline.getSnapshot() - .filter { it.root.getClearType() == EventType.MESSAGE } - .also { list -> - val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } - Log.v("#E2E TEST", "Timeline snapshot is $message") - } - .filter { it.root.sendState == SendState.SYNCED } - .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true } - sentEventId = decryptedMsg?.eventId - decryptedMsg != null - } - - timeline.dispose() - } - return sentEventId - } - - private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List, e2eRoomID: String) { - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - otherAccounts.map { - aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership - }.all { - it == Membership.JOIN - } - } - } - } - - private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) { - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val roomSummary = otherSession.getRoomSummary(e2eRoomID) - (roomSummary != null && roomSummary.membership == Membership.INVITE).also { - if (it) { - Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice") - } - } - } - } - - testHelper.runBlockingTest(60_000) { - Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") - try { - otherSession.joinRoom(e2eRoomID) - } catch (ex: JoinRoomFailure.JoinedWithTimeout) { - // it's ok we will wait after - } - } - - Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") - testHelper.waitWithLatch { - testHelper.retryPeriodicallyWithLatch(it) { - val roomSummary = otherSession.getRoomSummary(e2eRoomID) - roomSummary != null && roomSummary.membership == Membership.JOIN - } - } - } -} From 93aac8faeaf1a17d15c906753def64a6a65aa900 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 11 May 2022 15:11:34 +0200 Subject: [PATCH 17/40] post rebase fix --- .../internal/crypto/DefaultCryptoService.kt | 1 - .../crypto/IncomingKeyRequestManager.kt | 2 +- .../algorithms/megolm/MXMegolmDecryption.kt | 79 +++++++++---------- .../store/db/RealmCryptoStoreMigration.kt | 3 - 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 51e21a181e..a42719146e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -93,7 +93,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor -import org.matrix.android.sdk.internal.database.helper.SessionInfoPair import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt index 7f36224dae..6960d94918 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt @@ -405,7 +405,7 @@ internal class IncomingKeyRequestManager @Inject constructor( } val export = sessionHolder.mutex.withLock { - sessionHolder.wrapper.exportKeys(chainIndex) + sessionHolder.wrapper.exportKeys(/**TODO*/ false ,chainIndex) } ?: return false.also { Timber.tag(loggerTag.value) .e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index a9ffe5f008..7b6f80f5bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -29,8 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MegolmSessionData -import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction -import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter +import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -309,44 +308,44 @@ internal class MXMegolmDecryption( Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") newSessionListener?.onNewSession(roomId, senderKey, sessionId) } - override fun shareKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) { - exportedKeys ?: return - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - runCatching { deviceListManager.downloadKeys(listOf(userId), false) } - .mapCatching { - val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) - if (deviceInfo == null) { - throw RuntimeException() - } else { - val devicesByUser = mapOf(userId to listOf(deviceInfo)) - val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser) - val olmSessionResult = usersDeviceMap.getObject(userId, deviceId) - if (olmSessionResult?.sessionId == null) { - // no session with this device, probably because there - // were no one-time keys. - Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.") - return@mapCatching - } - Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${exportedKeys.sessionId} with device $userId:$deviceId") - val payloadJson = mapOf( - "type" to EventType.FORWARDED_ROOM_KEY, - "content" to exportedKeys - ) - - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(userId, deviceId, encodedPayload) - Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${exportedKeys.sessionId} to $userId:$deviceId") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - try { - sendToDeviceTask.execute(sendToDeviceParams) - } catch (failure: Throwable) { - Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${exportedKeys.sessionId} to $userId:$deviceId") - } - } - } - } + override fun shareForwardKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) { +// exportedKeys ?: return +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// runCatching { deviceListManager.downloadKeys(listOf(userId), false) } +// .mapCatching { +// val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) +// if (deviceInfo == null) { +// throw RuntimeException() +// } else { +// val devicesByUser = mapOf(userId to listOf(deviceInfo)) +// val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser) +// val olmSessionResult = usersDeviceMap.getObject(userId, deviceId) +// if (olmSessionResult?.sessionId == null) { +// // no session with this device, probably because there +// // were no one-time keys. +// Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.") +// return@mapCatching +// } +// Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${exportedKeys.sessionId} with device $userId:$deviceId") +// +// val payloadJson = mapOf( +// "type" to EventType.FORWARDED_ROOM_KEY, +// "content" to exportedKeys +// ) +// +// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) +// val sendToDeviceMap = MXUsersDevicesMap() +// sendToDeviceMap.setObject(userId, deviceId, encodedPayload) +// Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${exportedKeys.sessionId} to $userId:$deviceId") +// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) +// try { +// sendToDeviceTask.execute(sendToDeviceParams) +// } catch (failure: Throwable) { +// Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${exportedKeys.sessionId} to $userId:$deviceId") +// } +// } +// } +// } } - } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 35229d205d..4ca9d44f98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -73,9 +73,6 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 14) MigrateCryptoTo014(realm).perform() if (oldVersion < 15) MigrateCryptoTo015(realm).perform() if (oldVersion < 16) MigrateCryptoTo016(realm).perform() -<<<<<<< develop if (oldVersion < 17) MigrateCryptoTo017(realm).perform() -======= ->>>>>>> Implement history key sharing functionality with respect to room visibility settings } } From 9b8e45ebfe612dad35d71faf5586233e373fc123 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 May 2022 12:28:00 +0200 Subject: [PATCH 18/40] share keys for history take2 --- .../android/sdk/common/CryptoTestHelper.kt | 2 +- .../crypto/E2eeShareKeysHistoryTest.kt | 82 +++++++-- .../sdk/internal/crypto/PreShareKeysTest.kt | 2 +- .../keysbackup/KeysBackupScenarioData.kt | 4 +- .../crypto/keysbackup/KeysBackupTest.kt | 4 +- .../crypto/keysbackup/KeysBackupTestHelper.kt | 2 +- .../sdk/api/session/crypto/CryptoService.kt | 2 +- .../crypto/model/ForwardedRoomKeyContent.kt | 9 +- .../events/model/content/RoomKeyContent.kt | 10 +- .../internal/crypto/DefaultCryptoService.kt | 62 +++---- .../crypto/InboundGroupSessionStore.kt | 18 +- .../crypto/IncomingKeyRequestManager.kt | 2 +- .../sdk/internal/crypto/MXOlmDevice.kt | 166 ++++++++++-------- .../crypto/OutgoingKeyRequestManager.kt | 5 +- .../actions/MegolmSessionDataImporter.kt | 5 +- .../crypto/algorithms/IMXDecrypting.kt | 3 - .../crypto/algorithms/IMXEncrypting.kt | 4 + .../algorithms/megolm/MXMegolmDecryption.kt | 55 +----- .../algorithms/megolm/MXMegolmEncryption.kt | 114 +++++++++--- .../megolm/MXOutboundSessionInfo.kt | 1 + .../keysbackup/DefaultKeysBackupService.kt | 28 ++- .../crypto/model/InboundGroupSessionData.kt | 51 ++++++ .../model/MXInboundMegolmSessionWrapper.kt | 97 ++++++++++ .../model/OlmInboundGroupSessionWrapper2.kt | 2 + .../model/OutboundGroupSessionWrapper.kt | 6 +- .../internal/crypto/store/IMXCryptoStore.kt | 23 +-- .../crypto/store/db/RealmCryptoStore.kt | 106 ++++++----- .../store/db/migration/MigrateCryptoTo017.kt | 56 +++++- .../db/model/OlmInboundGroupSessionEntity.kt | 69 +++++++- .../internal/crypto/tasks/SendEventTask.kt | 7 +- .../membership/DefaultMembershipService.kt | 1 + 31 files changed, 673 insertions(+), 325 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index 3b0ea70f92..f36bfb6210 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -81,7 +81,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = testHelper.runBlockingTest { - aliceSession.createRoom(CreateRoomParams().apply { + aliceSession.roomService().createRoom(CreateRoomParams().apply { historyVisibility = roomHistoryVisibility name = "MyRoom" }) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index fa5c9c8b23..0df7e0eb7d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto import android.util.Log import androidx.test.filters.LargeTest import org.amshove.kluent.internal.assertEquals +import org.amshove.kluent.internal.assertNotEquals import org.junit.Assert import org.junit.FixMethodOrder import org.junit.Test @@ -101,7 +102,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { // Bob should be able to decrypt the message testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) (timelineEvent != null && timelineEvent.isEncrypted() && timelineEvent.root.getClearType() == EventType.MESSAGE).also { @@ -119,7 +120,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { // Alice invites new user to the room testHelper.runBlockingTest { Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") - aliceRoomPOV.invite(arisSession.myUserId) + aliceRoomPOV.membershipService().invite(arisSession.myUserId) } waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper) @@ -135,7 +136,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { // Aris should be able to decrypt the message testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) (timelineEvent != null && timelineEvent.isEncrypted() && timelineEvent.root.getClearType() == EventType.MESSAGE @@ -152,7 +153,9 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { // Aris should not even be able to get the message testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID) + ?.timelineService() + ?.getTimelineEvent(aliceMessageId!!) timelineEvent == null } } @@ -242,7 +245,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { // Alice val aliceSession = cryptoTestData.firstSession val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! - val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting +// val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting // Bob val bobSession = cryptoTestData.secondSession @@ -256,35 +259,62 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { Log.v("#E2E TEST ROTATION", "Alice sent message to roomId: $e2eRoomID") // Bob should be able to decrypt the message + var firstAliceMessageMegolmSessionId: String? = null testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!) + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID) + ?.timelineService() + ?.getTimelineEvent(aliceMessageId!!) (timelineEvent != null && timelineEvent.isEncrypted() && timelineEvent.root.getClearType() == EventType.MESSAGE).also { if (it) { + firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") } } } } - // Rotation has already been done so we do not need to rotate again - assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false) + Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId) + + var secondAliceMessageSessionId: String? = null + sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage -> + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID) + ?.timelineService() + ?.getTimelineEvent(secondMessage) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String + } + } + } + } + } + assertEquals("No rotation needed session should be the same", firstAliceMessageMegolmSessionId, secondAliceMessageSessionId) Log.v("#E2E TEST ROTATION", "No rotation needed yet") // Let's change the room history visibility testHelper.waitWithLatch { - aliceRoomPOV.sendStateEvent( - eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, - stateKey = "", - body = RoomHistoryVisibilityContent(_historyVisibility = nextRoomHistoryVisibility._historyVisibility).toContent() - ) + aliceRoomPOV.stateService() + .sendStateEvent( + eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + body = RoomHistoryVisibilityContent( + _historyVisibility = nextRoomHistoryVisibility._historyVisibility + ).toContent() + ) it.countDown() } + testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { val roomVisibility = aliceSession.getRoom(e2eRoomID)!! + .stateService() .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY) ?.content ?.toModel() @@ -293,13 +323,31 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { } } + var aliceThirdMessageSessionId: String? = null + sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage -> + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID) + ?.timelineService() + ?.getTimelineEvent(thirdMessage) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String + } + } + } + } + } + when { initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> { - assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), false) + assertEquals("Session shouldn't have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId) Log.v("#E2E TEST ROTATION", "Rotation is not needed") } initRoomHistoryVisibility.shouldShareHistory() != nextRoomHistoryVisibility.historyVisibility!!.shouldShareHistory() -> { - assertEquals(aliceCryptoStore.needsRotationDueToVisibilityChange(e2eRoomID), true) + assertNotEquals("Session should have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId) Log.v("#E2E TEST ROTATION", "Rotation is needed!") } } @@ -308,10 +356,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { } private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? { - aliceRoomPOV.sendTextMessage(text) + aliceRoomPOV.sendService().sendTextMessage(text) var sentEventId: String? = null testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> - val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60)) + val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) timeline.start() testHelper.retryPeriodicallyWithLatch(latch) { val decryptedMsg = timeline.getSnapshot() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt index e37ae5be86..e8e7b1d708 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt @@ -72,7 +72,7 @@ class PreShareKeysTest : InstrumentedTest { assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice) assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId) - val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier() + val megolmSessionId = bobInboundForAlice.session.sessionIdentifier() assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt index 45fdb9e1e3..cf201611a0 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt @@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper /** * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword] */ internal data class KeysBackupScenarioData( val cryptoTestData: CryptoTestData, - val aliceKeys: List, + val aliceKeys: List, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, val aliceSession2: Session ) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index fb498e0de5..e160938721 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -301,7 +301,7 @@ class KeysBackupTest : InstrumentedTest { val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo // - Check encryptGroupSession() returns stg - val keyBackupData = keysBackup.encryptGroupSession(session) + val keyBackupData = testHelper.runBlockingTest { keysBackup.encryptGroupSession(session) } assertNotNull(keyBackupData) assertNotNull(keyBackupData!!.sessionData) @@ -312,7 +312,7 @@ class KeysBackupTest : InstrumentedTest { val sessionData = keysBackup .decryptKeyBackupData( keyBackupData, - session.olmInboundGroupSession!!.sessionIdentifier(), + session.safeSessionId!!, cryptoTestData.roomId, decryption!! ) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt index 38f94c5103..2cc2b506b9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt @@ -187,7 +187,7 @@ internal class KeysBackupTestHelper( // - Alice must have the same keys on both devices for (aliceKey1 in testData.aliceKeys) { val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store - .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!) + .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!) Assert.assertNotNull(aliceKey2) assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index b84c5e2a80..6b52cff512 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -181,5 +181,5 @@ interface CryptoService { /** * Share all inbound sessions of the last chunk messages to the provided userId devices */ - fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) + suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt index 3df4ef7c9a..dbee04de88 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt @@ -69,5 +69,12 @@ data class ForwardedRoomKeyContent( * private part of this key unless they have done device verification. */ @Json(name = "sender_claimed_ed25519_key") - val senderClaimedEd25519Key: String? = null + val senderClaimedEd25519Key: String? = null, + + /** + * MSC3061 + * Identifies keys that were sent when the room's visibility setting was set to world_readable or shared + */ + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean? = false, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt index 0830a566ab..75162f8ace 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt @@ -38,5 +38,13 @@ data class RoomKeyContent( // should be a Long but it is sometimes a double @Json(name = "chain_index") - val chainIndex: Any? = null + val chainIndex: Any? = null, + + /** + * MSC3061 + * Identifies keys that were sent when the room's visibility setting was set to world_readable or shared + */ + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean? = false + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index a42719146e..b00b4f6173 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -110,6 +110,7 @@ import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject +import kotlin.coroutines.coroutineContext import kotlin.math.max /** @@ -965,10 +966,13 @@ internal class DefaultCryptoService @Inject constructor( private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { if (!event.isStateEvent()) return val eventContent = event.content.toModel() - eventContent?.historyVisibility?.let { - cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED) - cryptoStore.setShouldShareHistory(roomId, it.shouldShareHistory()) - } ?: cryptoStore.setShouldShareHistory(roomId, false) + val historyVisibility = eventContent?.historyVisibility + if (historyVisibility == null) { + cryptoStore.setShouldShareHistory(roomId, false) + } else { + cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + } } /** @@ -1338,36 +1342,26 @@ internal class DefaultCryptoService @Inject constructor( } } - override fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - runCatching { - deviceListManager.downloadKeys(listOf(userId), false) - }.mapCatching { - val userDevices = cryptoStore.getUserDevices(userId) - userDevices?.forEach { - // Lets share the provided inbound sessions for every user device - val deviceId = it.key - sessionInfoSet?.mapNotNull { sessionInfo -> - // Get inbound session from sessionId and sessionKey - cryptoStore.getInboundGroupSession( - sessionId = sessionInfo.sessionId, - senderKey = sessionInfo.senderKey, - sharedHistory = true - ) - }?.filter { inboundGroupSession -> - // Prevent injecting a forged encrypted message and using session_id/sender_key of another room. - (inboundGroupSession.roomId == roomId).also { - Timber.tag(loggerTag.value).d("Forged encrypted message detected for roomId:$roomId") - } - }?.forEach { inboundGroupSession -> - // Share the sharable session to userId with deviceId - val exportedKeys = inboundGroupSession.exportKeys(sharedHistory = true) - val algorithm = exportedKeys?.algorithm - val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, algorithm) - decryptor?.shareForwardKeysWithDevice(exportedKeys, deviceId, userId) - Timber.i("## CRYPTO | Sharing inbound session") - } - } + override suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) { + deviceListManager.downloadKeys(listOf(userId), false) + val userDevices = cryptoStore.getUserDeviceList(userId) + val sessionToShare = sessionInfoSet.orEmpty().mapNotNull { sessionInfo -> + // Get inbound session from sessionId and sessionKey + withContext(coroutineDispatchers.crypto) { + olmDevice.getInboundGroupSession( + sessionId = sessionInfo.sessionId, + senderKey = sessionInfo.senderKey, + roomId = roomId + ).takeIf { it.wrapper.sessionData.sharedHistory } + } + } + + userDevices?.forEach { deviceInfo -> + // Lets share the provided inbound sessions for every user device + sessionToShare.forEach { inboundGroupSession -> + val encryptor = roomEncryptorsStore.get(roomId) + encryptor?.shareHistoryKeysWithDevice(inboundGroupSession, deviceInfo) + Timber.i("## CRYPTO | Sharing inbound session") } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt index e4d322cadd..ab7cbb74b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.sync.Mutex import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber import java.util.Timer @@ -31,7 +31,7 @@ import java.util.TimerTask import javax.inject.Inject internal data class InboundGroupSessionHolder( - val wrapper: OlmInboundGroupSessionWrapper2, + val wrapper: MXInboundMegolmSessionWrapper, val mutex: Mutex = Mutex() ) @@ -58,7 +58,7 @@ internal class InboundGroupSessionStore @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}") store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper }) - oldValue.wrapper.olmInboundGroupSession?.releaseSession() + oldValue.wrapper.session.releaseSession() } } } @@ -67,7 +67,7 @@ internal class InboundGroupSessionStore @Inject constructor( private val timer = Timer() private var timerTask: TimerTask? = null - private val dirtySession = mutableListOf() + private val dirtySession = mutableListOf() @Synchronized fun clear() { @@ -90,12 +90,12 @@ internal class InboundGroupSessionStore @Inject constructor( @Synchronized fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) { Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}") - dirtySession.remove(old.wrapper) + dirtySession.remove(old) store.removeInboundGroupSession(sessionId, senderKey) sessionCache.remove(CacheKey(sessionId, senderKey)) // release removed session - old.wrapper.olmInboundGroupSession?.releaseSession() + old.wrapper.session.releaseSession() internalStoreGroupSession(new, sessionId, senderKey) } @@ -108,7 +108,7 @@ internal class InboundGroupSessionStore @Inject constructor( private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) { Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}") // We want to batch this a bit for performances - dirtySession.add(holder.wrapper) + dirtySession.add(holder) if (sessionCache[CacheKey(sessionId, senderKey)] == null) { // first time seen, put it in memory cache while waiting for batch insert @@ -127,12 +127,12 @@ internal class InboundGroupSessionStore @Inject constructor( @Synchronized private fun batchSave() { - val toSave = mutableListOf().apply { addAll(dirtySession) } + val toSave = mutableListOf().apply { addAll(dirtySession) } dirtySession.clear() cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}") tryOrNull { - store.storeInboundGroupSessions(toSave) + store.storeInboundGroupSessions(toSave.map { it.wrapper }) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt index 6960d94918..7f36224dae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt @@ -405,7 +405,7 @@ internal class IncomingKeyRequestManager @Inject constructor( } val export = sessionHolder.mutex.withLock { - sessionHolder.wrapper.exportKeys(/**TODO*/ false ,chainIndex) + sessionHolder.wrapper.exportKeys(chainIndex) } ?: return false.also { Timber.tag(loggerTag.value) .e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 24b6fd166f..409945e468 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData +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.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.MoshiProvider @@ -38,6 +39,7 @@ import org.matrix.android.sdk.internal.util.convertToUTF8 import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.olm.OlmAccount import org.matrix.olm.OlmException +import org.matrix.olm.OlmInboundGroupSession import org.matrix.olm.OlmMessage import org.matrix.olm.OlmOutboundGroupSession import org.matrix.olm.OlmSession @@ -514,8 +516,9 @@ internal class MXOlmDevice @Inject constructor( return MXOutboundSessionInfo( sessionId = sessionId, sharedWithHelper = SharedWithHelper(roomId, sessionId, store), - clock, - restoredOutboundGroupSession.creationTime + clock = clock, + creationTime = restoredOutboundGroupSession.creationTime, + sharedHistory = restoredOutboundGroupSession.sharedHistory ) } return null @@ -600,38 +603,44 @@ internal class MXOlmDevice @Inject constructor( * @param exportFormat true if the megolm keys are in export format * @return true if the operation succeeds. */ - fun addInboundGroupSession( - sessionId: String, - sessionKey: String, - roomId: String, - senderKey: String, - forwardingCurve25519KeyChain: List, - keysClaimed: Map, - exportFormat: Boolean - ): AddSessionResult { - val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat) + fun addInboundGroupSession(sessionId: String, + sessionKey: String, + roomId: String, + senderKey: String, + forwardingCurve25519KeyChain: List, + keysClaimed: Map, + exportFormat: Boolean, + sharedHistory: Boolean): AddSessionResult { + val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") { + if (exportFormat) { + OlmInboundGroupSession.importSession(sessionKey) + } else { + OlmInboundGroupSession(sessionKey) + } + } + val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } val existingSession = existingSessionHolder?.wrapper // If we have an existing one we should check if the new one is not better if (existingSession != null) { Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session") try { - val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also { + val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex } ?: return AddSessionResult.NotImported.also { // This is quite unexpected, could throw if native was released? Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session") - candidateSession.olmInboundGroupSession?.releaseSession() + candidateSession?.releaseSession() // Probably should discard it? } - val newKnownFirstIndex = candidateSession.firstKnownIndex + val newKnownFirstIndex = tryOrNull("Failed to get candidate first known index") { candidateSession?.firstKnownIndex } // If our existing session is better we keep it if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId") - candidateSession.olmInboundGroupSession?.releaseSession() + candidateSession?.releaseSession() return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt()) } } catch (failure: Throwable) { Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}") - candidateSession.olmInboundGroupSession?.releaseSession() + candidateSession?.releaseSession() return AddSessionResult.NotImported } } @@ -639,36 +648,42 @@ internal class MXOlmDevice @Inject constructor( Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId") // sanity check on the new session - val candidateOlmInboundSession = candidateSession.olmInboundGroupSession - if (null == candidateOlmInboundSession) { + if (null == candidateSession) { Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session ") return AddSessionResult.NotImported } try { - if (candidateOlmInboundSession.sessionIdentifier() != sessionId) { + if (candidateSession.sessionIdentifier() != sessionId) { Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey") - candidateOlmInboundSession.releaseSession() + candidateSession.releaseSession() return AddSessionResult.NotImported } } catch (e: Throwable) { - candidateOlmInboundSession.releaseSession() + candidateSession.releaseSession() Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed") return AddSessionResult.NotImported } - candidateSession.senderKey = senderKey - candidateSession.roomId = roomId - candidateSession.keysClaimed = keysClaimed - candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain + val candidateSessionData = InboundGroupSessionData( + senderKey = senderKey, + roomId = roomId, + keysClaimed = keysClaimed, + forwardingCurve25519KeyChain = forwardingCurve25519KeyChain, + sharedHistory = sharedHistory, + ) + val wrapper = MXInboundMegolmSessionWrapper( + candidateSession, + candidateSessionData + ) if (existingSession != null) { - inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey) + inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(wrapper), sessionId, senderKey) } else { - inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey) + inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(wrapper), sessionId, senderKey) } - return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0) + return AddSessionResult.Imported(candidateSession.firstKnownIndex.toInt()) } /** @@ -677,41 +692,22 @@ internal class MXOlmDevice @Inject constructor( * @param megolmSessionsData the megolm sessions data * @return the successfully imported sessions. */ - fun importInboundGroupSessions(megolmSessionsData: List): List { - val sessions = ArrayList(megolmSessionsData.size) + fun importInboundGroupSessions(megolmSessionsData: List): List { + val sessions = ArrayList(megolmSessionsData.size) for (megolmSessionData in megolmSessionsData) { val sessionId = megolmSessionData.sessionId ?: continue val senderKey = megolmSessionData.senderKey ?: continue val roomId = megolmSessionData.roomId - var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null - - try { - candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - } - - // sanity check - if (candidateSessionToImport?.olmInboundGroupSession == null) { - Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session") - continue - } - - val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession - try { - if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) { - Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey") - candidateOlmInboundGroupSession?.releaseSession() - continue - } - } catch (e: Exception) { - Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed") - candidateOlmInboundGroupSession?.releaseSession() + val candidateSessionToImport = try { + MXInboundMegolmSessionWrapper.newFromMegolmData(megolmSessionData, true) + } catch (e: Throwable) { + Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Failed to import session $senderKey/$sessionId") continue } + val candidateOlmInboundGroupSession = candidateSessionToImport.session val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } val existingSession = existingSessionHolder?.wrapper @@ -721,16 +717,16 @@ internal class MXOlmDevice @Inject constructor( sessions.add(candidateSessionToImport) } else { Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex } - val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex } + val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex } + val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.session.firstKnownIndex } if (existingFirstKnown == null || candidateFirstKnownIndex == null) { // should not happen? - candidateSessionToImport.olmInboundGroupSession?.releaseSession() + candidateSessionToImport.session.releaseSession() Timber.tag(loggerTag.value) .w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex") } else { - if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) { + if (existingFirstKnown <= candidateFirstKnownIndex) { // Ignore this, keep existing candidateOlmInboundGroupSession.releaseSession() } else { @@ -774,18 +770,17 @@ internal class MXOlmDevice @Inject constructor( ): OlmDecryptionResult { val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId) val wrapper = sessionHolder.wrapper - val inboundGroupSession = wrapper.olmInboundGroupSession - ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null") - if (roomId != wrapper.roomId) { - // Check that the room id matches the original one for the session. This stops - // the HS pretending a message was targeting a different room. - val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId) - Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason) - } - val decryptResult = try { - sessionHolder.mutex.withLock { - inboundGroupSession.decryptMessage(body) + val inboundGroupSession = wrapper.session + // Check that the room id matches the original one for the session. This stops + // the HS pretending a message was targeting a different room. + if (roomId == wrapper.roomId) { + val decryptResult = try { + sessionHolder.mutex.withLock { + inboundGroupSession.decryptMessage(body) + } + } catch (e: OlmException) { + Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") + throw MXCryptoError.OlmError(e) } } catch (e: OlmException) { Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") @@ -820,12 +815,27 @@ internal class MXOlmDevice @Inject constructor( throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) } - return OlmDecryptionResult( - payload, - wrapper.keysClaimed, - senderKey, - wrapper.forwardingCurve25519KeyChain - ) + inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) + val payload = try { + val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) + val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) + adapter.fromJson(payloadString) + } catch (e: Exception) { + Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload") + throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) + } + + return OlmDecryptionResult( + payload, + wrapper.sessionData.keysClaimed, + senderKey, + wrapper.sessionData.forwardingCurve25519KeyChain + ) + } else { + val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId) + Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason") + throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason) + } } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt index 6b22cc09d6..810699d933 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt @@ -437,7 +437,10 @@ internal class OutgoingKeyRequestManager @Inject constructor( if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) { // let's see what's the index val knownIndex = tryOrNull { - inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex + inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "") + ?.wrapper + ?.session + ?.firstKnownIndex } if (knownIndex != null && knownIndex <= request.fromIndex) { // we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt index f6ab96aee6..a624b92a19 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -84,8 +84,9 @@ internal class MegolmSessionDataImporter @Inject constructor( megolmSessionData.senderKey ?: "", tryOrNull { olmInboundGroupSessionWrappers - .firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId } - ?.firstKnownIndex?.toInt() + .firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId } + ?.session?.firstKnownIndex + ?.toInt() } ?: 0 ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index 7edae70fd8..6847a46369 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.algorithms import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService /** @@ -44,6 +43,4 @@ internal interface IMXDecrypting { * @param defaultKeysBackupService the keys backup service */ fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} - - fun shareForwardKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) {} } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt index 73ce5a5004..1454f5b486 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt @@ -16,7 +16,9 @@ package org.matrix.android.sdk.internal.crypto.algorithms +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder /** * An interface for encrypting data. @@ -32,4 +34,6 @@ internal interface IMXEncrypting { * @return the encrypted content */ suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content + + suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {} } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 7b6f80f5bb..7302a481ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService @@ -241,13 +240,14 @@ internal class MXMegolmDecryption( Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") val addSessionResult = olmDevice.addInboundGroupSession( - roomKeyContent.sessionId, - roomKeyContent.sessionKey, - roomKeyContent.roomId, - senderKey, - forwardingCurve25519KeyChain, - keysClaimed, - exportFormat + sessionId = roomKeyContent.sessionId, + sessionKey = roomKeyContent.sessionKey, + roomId = roomKeyContent.roomId, + senderKey = senderKey, + forwardingCurve25519KeyChain = forwardingCurve25519KeyChain, + keysClaimed = keysClaimed, + exportFormat = exportFormat, + sharedHistory = roomKeyContent.sharedHistory ?: false ) when (addSessionResult) { @@ -309,43 +309,4 @@ internal class MXMegolmDecryption( newSessionListener?.onNewSession(roomId, senderKey, sessionId) } - override fun shareForwardKeysWithDevice(exportedKeys: MegolmSessionData?, deviceId: String, userId: String) { -// exportedKeys ?: return -// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { -// runCatching { deviceListManager.downloadKeys(listOf(userId), false) } -// .mapCatching { -// val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) -// if (deviceInfo == null) { -// throw RuntimeException() -// } else { -// val devicesByUser = mapOf(userId to listOf(deviceInfo)) -// val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser) -// val olmSessionResult = usersDeviceMap.getObject(userId, deviceId) -// if (olmSessionResult?.sessionId == null) { -// // no session with this device, probably because there -// // were no one-time keys. -// Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.") -// return@mapCatching -// } -// Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${exportedKeys.sessionId} with device $userId:$deviceId") -// -// val payloadJson = mapOf( -// "type" to EventType.FORWARDED_ROOM_KEY, -// "content" to exportedKeys -// ) -// -// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) -// val sendToDeviceMap = MXUsersDevicesMap() -// sendToDeviceMap.setObject(userId, deviceId, encodedPayload) -// Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${exportedKeys.sessionId} to $userId:$deviceId") -// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) -// try { -// sendToDeviceTask.execute(sendToDeviceParams) -// } catch (failure: Throwable) { -// Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${exportedKeys.sessionId} to $userId:$deviceId") -// } -// } -// } -// } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 740676cd59..4d213dfd9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.internal.crypto.DeviceListManager +import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter @@ -151,14 +152,27 @@ internal class MXMegolmEncryption( "ed25519" to olmDevice.deviceEd25519Key!! ) + val sharedHistory = cryptoStore.shouldShareHistory(roomId) + Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() as sharedHistory $sharedHistory") olmDevice.addInboundGroupSession( - sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, - emptyList(), keysClaimedMap, false + sessionId = sessionId!!, + sessionKey = olmDevice.getSessionKey(sessionId)!!, + roomId = roomId, + senderKey = olmDevice.deviceCurve25519Key!!, + forwardingCurve25519KeyChain = emptyList(), + keysClaimed = keysClaimedMap, + exportFormat = false, + sharedHistory = sharedHistory ) defaultKeysBackupService.maybeBackupKeys() - return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock) + return MXOutboundSessionInfo( + sessionId = sessionId, + sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore), + clock = clock, + sharedHistory = sharedHistory + ) } /** @@ -173,11 +187,7 @@ internal class MXMegolmEncryption( // Need to make a brand new session? session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) || // Is there a room history visibility change since the last outboundSession - cryptoStore.needsRotationDueToVisibilityChange(roomId).also { - if (it) { - Timber.tag(loggerTag.value).d("roomId:$roomId Room history visibility change detected since the last outbound session") - } - } || + cryptoStore.shouldShareHistory(roomId) != session.sharedHistory || // Determine if we have shared with anyone we shouldn't have session.sharedWithTooManyDevices(devicesInRoom)) { Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.") @@ -240,23 +250,24 @@ internal class MXMegolmEncryption( * @param session the session info * @param devicesByUser the devices map */ - private suspend fun shareUserDevicesKey( - session: MXOutboundSessionInfo, - devicesByUser: Map> - ) { - val sessionKey = olmDevice.getSessionKey(session.sessionId) - val chainIndex = olmDevice.getMessageIndex(session.sessionId) + private suspend fun shareUserDevicesKey(sessionInfo: MXOutboundSessionInfo, + devicesByUser: Map>) { + val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also { + Timber.tag(loggerTag.value).v("shareUserDevicesKey() Failed to share session, failed to export") + } + val chainIndex = olmDevice.getMessageIndex(sessionInfo.sessionId) - val submap = HashMap() - submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM - submap["room_id"] = roomId - submap["session_id"] = session.sessionId - submap["session_key"] = sessionKey!! - submap["chain_index"] = chainIndex - - val payload = HashMap() - payload["type"] = EventType.ROOM_KEY - payload["content"] = submap + val payload = mapOf( + "type" to EventType.ROOM_KEY, + "content" to mapOf( + "algorithm" to MXCRYPTO_ALGORITHM_MEGOLM, + "room_id" to roomId, + "session_id" to sessionInfo.sessionId, + "session_key" to sessionKey, + "chain_index" to chainIndex, + "org.matrix.msc3061.shared_history" to sessionInfo.sharedHistory + ) + ) var t0 = clock.epochMillis() Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts") @@ -298,7 +309,7 @@ internal class MXMegolmEncryption( // for dead devices on every message. for ((_, devicesToShareWith) in devicesByUser) { for (deviceInfo in devicesToShareWith) { - session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex) + sessionInfo.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex) // XXX is it needed to add it to the audit trail? // For now decided that no, we are more interested by forward trail } @@ -306,8 +317,8 @@ internal class MXMegolmEncryption( if (haveTargets) { t0 = clock.epochMillis() - Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target") - Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}") + Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${sessionInfo.sessionId} : has target") + Timber.tag(loggerTag.value).d("sending to device room key for ${sessionInfo.sessionId} to ${contentMap.toDebugString()}") val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap) try { withContext(coroutineDispatchers.io) { @@ -316,7 +327,7 @@ internal class MXMegolmEncryption( Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms") } catch (failure: Throwable) { // What to do here... - Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>") + Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${sessionInfo.sessionId}>") } } else { Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key") @@ -326,7 +337,7 @@ internal class MXMegolmEncryption( // XXX offload?, as they won't read the message anyhow? notifyKeyWithHeld( noOlmToNotify, - session.sessionId, + sessionInfo.sessionId, olmDevice.deviceCurve25519Key, WithHeldCode.NO_OLM ) @@ -520,6 +531,51 @@ internal class MXMegolmEncryption( } } + @Throws + override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) { + if (!inboundSessionWrapper.wrapper.sessionData.sharedHistory) throw IllegalArgumentException("This key can't be shared") + Timber.tag(loggerTag.value).i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}") + val userId = deviceInfo.userId + val deviceId = deviceInfo.deviceId + val devicesByUser = mapOf(userId to listOf(deviceInfo)) + val usersDeviceMap = try { + ensureOlmSessionsForDevicesAction.handle(devicesByUser) + } catch (failure: Throwable) { + Timber.tag(loggerTag.value).i(failure, "process shareHistoryKeys failed to ensure olm") + // process anyway? + null + } + val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId) + if (olmSessionResult?.sessionId == null) { + Timber.tag(loggerTag.value).w("shareHistoryKeys: no session with this device, probably because there were no one-time keys") + return + } + + val export = inboundSessionWrapper.mutex.withLock { + inboundSessionWrapper.wrapper.exportKeys() + } ?: return Unit.also { + Timber.tag(loggerTag.value).e("shareHistoryKeys: failed to export group session ${inboundSessionWrapper.wrapper.safeSessionId}") + } + + val payloadJson = mapOf( + "type" to EventType.FORWARDED_ROOM_KEY, + "content" to export + ) + + val encodedPayload = + withContext(coroutineDispatchers.computation) { + messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + } + val sendToDeviceMap = MXUsersDevicesMap() + sendToDeviceMap.setObject(userId, deviceId, encodedPayload) + Timber.tag(loggerTag.value) + .d("shareHistoryKeys() : sending session ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}") + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + withContext(coroutineDispatchers.io) { + sendToDeviceTask.execute(sendToDeviceParams) + } + } + data class DeviceInRoomInfo( val allowedDevices: MXUsersDevicesMap = MXUsersDevicesMap(), val withHeldDevices: MXUsersDevicesMap = MXUsersDevicesMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt index 28d925d8fd..e0caa0d9a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt @@ -28,6 +28,7 @@ internal class MXOutboundSessionInfo( private val clock: Clock, // When the session was created private val creationTime: Long = clock.epochMillis(), + val sharedHistory: Boolean = false ) { // Number of times this session has been used diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 5eaa106af3..04f33d1778 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCoroutineDispatchers @@ -50,6 +51,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.api.util.fromBase64 +import org.matrix.android.sdk.internal.crypto.InboundGroupSessionStore import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.ObjectSigner @@ -71,7 +73,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDa import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.di.MoshiProvider @@ -118,6 +120,7 @@ internal class DefaultKeysBackupService @Inject constructor( private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, // Task executor private val taskExecutor: TaskExecutor, + private val inboundGroupSessionStore: InboundGroupSessionStore, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope ) : KeysBackupService { @@ -1316,7 +1319,7 @@ internal class DefaultKeysBackupService @Inject constructor( olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach - val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach + val olmInboundGroupSession = olmInboundGroupSessionWrapper.session try { encryptGroupSession(olmInboundGroupSessionWrapper) @@ -1405,13 +1408,22 @@ internal class DefaultKeysBackupService @Inject constructor( @VisibleForTesting @WorkerThread - fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? { + suspend fun encryptGroupSession(olmInboundGroupSessionWrapper: MXInboundMegolmSessionWrapper): KeyBackupData? { + olmInboundGroupSessionWrapper.safeSessionId ?: return null + olmInboundGroupSessionWrapper.senderKey ?: return null // Gather information for each key val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format - val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null + val sessionData = inboundGroupSessionStore + .getInboundGroupSession(olmInboundGroupSessionWrapper.safeSessionId, olmInboundGroupSessionWrapper.senderKey) + ?.let { + withContext(coroutineDispatchers.computation) { + it.mutex.withLock { it.wrapper.exportKeys() } + } + } + ?: return null val sessionBackupData = mapOf( "algorithm" to sessionData.algorithm, "sender_key" to sessionData.senderKey, @@ -1425,7 +1437,9 @@ internal class DefaultKeysBackupService @Inject constructor( .toJson(sessionBackupData) val encryptedSessionBackupData = try { - backupOlmPkEncryption?.encrypt(json) + withContext(coroutineDispatchers.computation) { + backupOlmPkEncryption?.encrypt(json) + } } catch (e: OlmException) { Timber.e(e, "OlmException") null @@ -1435,12 +1449,12 @@ internal class DefaultKeysBackupService @Inject constructor( // Build backup data for that key return KeyBackupData( firstMessageIndex = try { - olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0 + olmInboundGroupSessionWrapper.session.firstKnownIndex } catch (e: OlmException) { Timber.e(e, "OlmException") 0L }, - forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size, + forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size, isVerified = device?.isVerified == true, sessionData = mapOf( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt new file mode 100644 index 0000000000..0e60791282 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class InboundGroupSessionData( + + /** The room in which this session is used. */ + @Json(name = "room_id") + var roomId: String? = null, + + /** The base64-encoded curve25519 key of the sender. */ + @Json(name = "sender_key") + var senderKey: String? = null, + + /** Other keys the sender claims. */ + @Json(name = "keys_claimed") + var keysClaimed: Map? = null, + + /** Devices which forwarded this session to us (normally emty). */ + @Json(name = "forwarding_curve25519_key_chain") + var forwardingCurve25519KeyChain: List? = emptyList(), + + /** Not yet used, will be in backup v2 + val untrusted?: Boolean = false */ + + /** + * Flag that indicates whether or not the current inboundSession will be shared to + *invited users to decrypt past messages + */ + @Json(name = "shared_history") + val sharedHistory: Boolean = false, + +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt new file mode 100644 index 0000000000..9cf66814f3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.model + +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.crypto.MegolmSessionData +import org.matrix.olm.OlmInboundGroupSession +import timber.log.Timber + +data class MXInboundMegolmSessionWrapper( + // olm object + val session: OlmInboundGroupSession, + // data about the session + val sessionData: InboundGroupSessionData +) { + // shortcut + val roomId = sessionData.roomId + val senderKey = sessionData.senderKey + val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() } + + /** + * Export the inbound group session keys + * @param index the index to export. If null, the first known index will be used + * @return the inbound group session as MegolmSessionData if the operation succeeds + */ + internal fun exportKeys(index: Long? = null): MegolmSessionData? { + return try { + val keysClaimed = sessionData.keysClaimed ?: return null + val wantedIndex = index ?: session.firstKnownIndex + + MegolmSessionData( + senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"), + forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(), + senderKey = session.export(index ?: session.firstKnownIndex), + senderClaimedKeys = keysClaimed, + roomId = sessionData.roomId, + sessionId = session.sessionIdentifier(), + sessionKey = session.export(wantedIndex), + algorithm = MXCRYPTO_ALGORITHM_MEGOLM, + sharedHistory = sessionData.sharedHistory + ) + } catch (e: Exception) { + Timber.e(e, "## Failed to export megolm : sessionID ${tryOrNull { session.sessionIdentifier() }} failed") + null + } + } + + companion object { + + /** + * @exportFormat true if the megolm keys are in export format + * (ie, they lack an ed25519 signature) + */ + @Throws + internal fun newFromMegolmData(megolmSessionData: MegolmSessionData, exportFormat: Boolean): MXInboundMegolmSessionWrapper { + val exportedKey = megolmSessionData.sessionKey ?: throw IllegalArgumentException("key data not found") + val inboundSession = if (exportFormat) { + OlmInboundGroupSession.importSession(exportedKey) + } else { + OlmInboundGroupSession(exportedKey) + } + .also { + if (it.sessionIdentifier() != megolmSessionData.sessionId) { + it.releaseSession() + throw IllegalStateException("Mismatched group session Id") + } + } + val data = InboundGroupSessionData( + roomId = megolmSessionData.roomId, + senderKey = megolmSessionData.senderKey, + keysClaimed = megolmSessionData.senderClaimedKeys, + forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain, + sharedHistory = megolmSessionData.sharedHistory, + ) + + return MXInboundMegolmSessionWrapper( + inboundSession, + data + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index b3898e001b..e5b540fb2f 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -26,6 +26,8 @@ import java.io.Serializable * This class adds more context to a OlmInboundGroupSession object. * This allows additional checks. The class implements Serializable so that the context can be stored. */ +// Note used anymore, just for database migration +@Deprecated("Use MXInboundMegolmSessionWrapper") internal class OlmInboundGroupSessionWrapper2 : Serializable { // The associated olm inbound group session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt index 4ac87f44ce..e02953c9e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt @@ -20,5 +20,9 @@ import org.matrix.olm.OlmOutboundGroupSession internal data class OutboundGroupSessionWrapper( val outboundGroupSession: OlmOutboundGroupSession, - val creationTime: Long + val creationTime: Long, + /** + * As per MSC 3061, declares if this key could be shared when inviting a new user to the room + */ + val sharedHistory: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 42ff4e11ff..d315e0310a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -35,7 +35,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +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.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity @@ -64,7 +64,7 @@ internal interface IMXCryptoStore { * * @return the list of all known group sessions, to export them. */ - fun getInboundGroupSessions(): List + fun getInboundGroupSessions(): List /** * Retrieve the known inbound group sessions for the specified room @@ -72,7 +72,7 @@ internal interface IMXCryptoStore { * @param roomId The roomId that the sessions will be returned * @return the list of all known group sessions, for the provided roomId */ - fun getInboundGroupSessions(roomId: String): List + fun getInboundGroupSessions(roomId: String): List /** * @return true to unilaterally blacklist all unverified devices. @@ -309,7 +309,7 @@ internal interface IMXCryptoStore { * * @param sessions the inbound group sessions to store. */ - fun storeInboundGroupSessions(sessions: List) + fun storeInboundGroupSessions(sessions: List) /** * Retrieve an inbound group session. @@ -318,7 +318,7 @@ internal interface IMXCryptoStore { * @param senderKey the base64-encoded curve25519 key of the sender. * @return an inbound group session. */ - fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? + fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? /** * Retrieve an inbound group session, filtering shared history. @@ -328,7 +328,7 @@ internal interface IMXCryptoStore { * @param sharedHistory filter inbound session with respect to shared history field * @return an inbound group session. */ - fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): OlmInboundGroupSessionWrapper2? + fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? /** * Get the current outbound group session for this encrypted room @@ -340,13 +340,6 @@ internal interface IMXCryptoStore { */ fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?) - /** - * Returns true if there is a room history visibility change since the latest outbound - * session. Specifically when the room's history visibility setting changes to - * world_readable or shared from invited or joined, or changes to invited or joined from world_readable or shared - */ - fun needsRotationDueToVisibilityChange(roomId: String): Boolean - /** * Remove an inbound group session * @@ -369,7 +362,7 @@ internal interface IMXCryptoStore { * * @param olmInboundGroupSessionWrappers the sessions */ - fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List) + fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List) /** * Retrieve inbound group sessions that are not yet backed up. @@ -377,7 +370,7 @@ internal interface IMXCryptoStore { * @param limit the maximum number of sessions to return. * @return an array of non backed up inbound group sessions. */ - fun inboundGroupSessionsToBackup(limit: Int): List + fun inboundGroupSessionsToBackup(limit: Int): List /** * Number of stored inbound group sessions. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index e226aa2e8e..74d547b9fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -50,7 +50,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldCo 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.toOptional -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +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.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -671,6 +671,8 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { + Timber.tag(loggerTag.value) + .v("setShouldShareHistory for room $roomId is $shouldShareHistory") doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory } @@ -740,61 +742,55 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeInboundGroupSessions(sessions: List) { + override fun storeInboundGroupSessions(sessions: List) { if (sessions.isEmpty()) { return } doRealmTransaction(realmConfiguration) { realm -> - sessions.forEach { session -> - var sessionIdentifier: String? = null + sessions.forEach { wrapper -> - try { - sessionIdentifier = session.olmInboundGroupSession?.sessionIdentifier() + val sessionIdentifier = try { + wrapper.session.sessionIdentifier() } catch (e: OlmException) { Timber.e(e, "## storeInboundGroupSession() : sessionIdentifier failed") + return@forEach } - if (sessionIdentifier != null) { - val shouldShareHistory = session.roomId?.let { roomId -> - CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory - } ?: false - val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey) +// val shouldShareHistory = session.roomId?.let { roomId -> +// CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory +// } ?: false + val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, wrapper.sessionData.senderKey) - val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { - primaryKey = key - sessionId = sessionIdentifier - senderKey = session.senderKey - roomId = session.roomId - sharedHistory = shouldShareHistory - putInboundGroupSession(session) - } - Timber.i("## CRYPTO | shouldShareHistory: $shouldShareHistory for $key") - realm.insertOrUpdate(realmOlmInboundGroupSession) + val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { + primaryKey = key + store(wrapper) } + Timber.i("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key") + realm.insertOrUpdate(realmOlmInboundGroupSession) } } } - override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? { + override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) - return doWithRealm(realmConfiguration) { - it.where() + return doWithRealm(realmConfiguration) { realm -> + realm.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findFirst() - ?.getInboundGroupSession() + ?.toModel() } } - override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): OlmInboundGroupSessionWrapper2? { + override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) return doWithRealm(realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, sharedHistory) .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findFirst() - ?.getInboundGroupSession() + ?.toModel() } } @@ -806,7 +802,8 @@ internal class RealmCryptoStore @Inject constructor( entity.getOutboundGroupSession()?.let { OutboundGroupSessionWrapper( it, - entity.creationTime ?: 0 + entity.creationTime ?: 0, + entity.shouldShareHistory ) } } @@ -836,36 +833,32 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun needsRotationDueToVisibilityChange(roomId: String): Boolean { - return doWithRealm(realmConfiguration) { realm -> - CryptoRoomEntity.getById(realm, roomId)?.let { entity -> - entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory - } - } ?: false - } +// override fun needsRotationDueToVisibilityChange(roomId: String): Boolean { +// return doWithRealm(realmConfiguration) { realm -> +// CryptoRoomEntity.getById(realm, roomId)?.let { entity -> +// entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory +// } +// } ?: false +// } /** * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2, * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management. */ - override fun getInboundGroupSessions(): List { - return doWithRealm(realmConfiguration) { - it.where() + override fun getInboundGroupSessions(): List { + return doWithRealm(realmConfiguration) { realm -> + realm.where() .findAll() - .mapNotNull { inboundGroupSessionEntity -> - inboundGroupSessionEntity.getInboundGroupSession() - } + .mapNotNull { it.toModel() } } } - override fun getInboundGroupSessions(roomId: String): List { - return doWithRealm(realmConfiguration) { - it.where() + override fun getInboundGroupSessions(roomId: String): List { + return doWithRealm(realmConfiguration) { realm -> + realm.where() .equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId) .findAll() - .mapNotNull { inboundGroupSessionEntity -> - inboundGroupSessionEntity.getInboundGroupSession() - } + .mapNotNull { it.toModel() } } } @@ -926,7 +919,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List) { + override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List) { if (olmInboundGroupSessionWrappers.isEmpty()) { return } @@ -934,10 +927,13 @@ internal class RealmCryptoStore @Inject constructor( doRealmTransaction(realmConfiguration) { realm -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { - val sessionIdentifier = olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier() + val sessionIdentifier = + tryOrNull("Failed to get session identifier") { + olmInboundGroupSessionWrapper.session.sessionIdentifier() + } ?: return@forEach val key = OlmInboundGroupSessionEntity.createPrimaryKey( sessionIdentifier, - olmInboundGroupSessionWrapper.senderKey + olmInboundGroupSessionWrapper.sessionData.senderKey ) val existing = realm.where() @@ -950,9 +946,7 @@ internal class RealmCryptoStore @Inject constructor( // ... might be in cache but not yet persisted, create a record to persist backedup state val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply { primaryKey = key - sessionId = sessionIdentifier - senderKey = olmInboundGroupSessionWrapper.senderKey - putInboundGroupSession(olmInboundGroupSessionWrapper) + store(olmInboundGroupSessionWrapper) backedUp = true } @@ -965,15 +959,13 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun inboundGroupSessionsToBackup(limit: Int): List { + override fun inboundGroupSessionsToBackup(limit: Int): List { return doWithRealm(realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false) .limit(limit.toLong()) .findAll() - .mapNotNull { inboundGroupSession -> - inboundGroupSession.getInboundGroupSession() - } + .mapNotNull { it.toModel() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt index 6ce94c3eb2..b2a2129853 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -17,20 +17,66 @@ package org.matrix.android.sdk.internal.crypto.store.db.migration import io.realm.DynamicRealm +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData +import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields - import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator +import timber.log.Timber -// Version 16L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061 -internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 16) { +/** + * Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061 + * Also migrates how megolm session are stored to avoid additional serialized frozen class +*/ +internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) { override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("CryptoRoomEntity") + ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java) + + val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java) + realm.schema.get("OlmInboundGroupSessionEntity") ?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java) ?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java) + ?.addField(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java) + ?.addField(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java) + ?.transform { dynamicObject -> + try { + // we want to convert the old wrapper frozen class into a + // map of sessionData & the pickled session herself + dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData -> + val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") { + @Suppress("DEPRECATION") + deserializeFromRealm(oldData) + } + val groupSession = oldWrapper?.olmInboundGroupSession + ?: return@transform Unit.also { + Timber.w("Failed to migrate megolm session, no olmInboundGroupSession") + } + // now convert to new data + val data = InboundGroupSessionData( + senderKey = oldWrapper.senderKey, + roomId = oldWrapper.roomId, + keysClaimed = oldWrapper.keysClaimed, + forwardingCurve25519KeyChain = oldWrapper.forwardingCurve25519KeyChain, + sharedHistory = false, + ) - realm.schema.get("CryptoRoomEntity") - ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java) + dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(data)) + dynamicObject.setString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, serializeForRealm(groupSession)) + + // denormalized fields + dynamicObject.setString(OlmInboundGroupSessionEntityFields.ROOM_ID, oldWrapper.roomId) + dynamicObject.setBoolean(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, false) + } + } catch (failure: Throwable) { + Timber.e(failure, "Failed to migrate megolm session") + } + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt index 12e9b8baee..62ab73e379 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt @@ -18,9 +18,12 @@ package org.matrix.android.sdk.internal.crypto.store.db.model import io.realm.RealmObject import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 +import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData +import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.olm.OlmInboundGroupSession import timber.log.Timber internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey" @@ -28,11 +31,23 @@ internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: internal open class OlmInboundGroupSessionEntity( // Combined value to build a primary key @PrimaryKey var primaryKey: String? = null, + + // denormalization for faster querying (these fields are in the inboundGroupSessionDataJson) var sessionId: String? = null, var senderKey: String? = null, var roomId: String? = null, - // olmInboundGroupSessionData contains Json + + // Deprecated, used for migration / olmInboundGroupSessionData contains Json + // keep it in case of problem to have a chance to recover var olmInboundGroupSessionData: String? = null, + + // Stores the session data in an extensible format + // to allow to store data not yet supported for later use + var inboundGroupSessionDataJson: String? = null, + + // The pickled session + var serializedOlmInboundGroupSession: String? = null, + // Flag that indicates whether or not the current inboundSession will be shared to // invited users to decrypt past messages var sharedHistory: Boolean = false, @@ -41,18 +56,58 @@ internal open class OlmInboundGroupSessionEntity( ) : RealmObject() { - fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { + fun store(wrapper: MXInboundMegolmSessionWrapper) { + this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session) + this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData) + this.roomId = wrapper.sessionData.roomId + this.senderKey = wrapper.sessionData.senderKey + this.sessionId = wrapper.session.sessionIdentifier() + this.sharedHistory = wrapper.sessionData.sharedHistory + } +// fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { +// return try { +// deserializeFromRealm(olmInboundGroupSessionData) +// } catch (failure: Throwable) { +// Timber.e(failure, "## Deserialization failure") +// return null +// } +// } +// +// fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) { +// olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper) +// } + + fun getOlmGroupSession(): OlmInboundGroupSession? { return try { - deserializeFromRealm(olmInboundGroupSessionData) + deserializeFromRealm(serializedOlmInboundGroupSession) } catch (failure: Throwable) { Timber.e(failure, "## Deserialization failure") return null } } - fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) { - olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper) + fun getData(): InboundGroupSessionData? { + return try { + inboundGroupSessionDataJson?.let { + adapter.fromJson(it) + } + } catch (failure: Throwable) { + Timber.e(failure, "## Deserialization failure") + return null + } } - companion object + fun toModel(): MXInboundMegolmSessionWrapper? { + val data = getData() ?: return null + val session = getOlmGroupSession() ?: return null + return MXInboundMegolmSessionWrapper( + session = session, + sessionData = data + ) + } + + companion object { + private val adapter = MoshiProvider.providesMoshi() + .adapter(InboundGroupSessionData::class.java) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index fbd9d245d9..bb14b417dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -15,7 +15,6 @@ */ package org.matrix.android.sdk.internal.crypto.tasks -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.network.GlobalErrorReceiver @@ -48,8 +47,12 @@ internal class DefaultSendEventTask @Inject constructor( params.event.roomId ?.takeIf { params.encrypt } ?.let { roomId -> - tryOrNull { + try { loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId)) + } catch (failure: Throwable) { + // send any way? + // the result is that some users won't probably be able to decrypt :/ + Timber.w(failure, "SendEvent: failed to load members in room ${params.event.roomId}") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index fdda38777a..54c1a6b57e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -144,6 +144,7 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun invite(userId: String, reason: String?) { + // TODO not sure it's the right way to get the latest messages in a room val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use { ChunkEntity.findLatestSessionInfo(it, roomId) } From 8c26592d462f44f2169da649fab6e78e3bff0f46 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 May 2022 12:46:33 +0200 Subject: [PATCH 19/40] cleaning --- .../internal/crypto/keysbackup/DefaultKeysBackupService.kt | 2 +- .../internal/crypto/store/db/migration/MigrateCryptoTo017.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 04f33d1778..08765f0c39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -1412,7 +1412,7 @@ internal class DefaultKeysBackupService @Inject constructor( olmInboundGroupSessionWrapper.safeSessionId ?: return null olmInboundGroupSessionWrapper.senderKey ?: return null // Gather information for each key - val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) } + val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey) // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt index b2a2129853..2e847ab28e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.migration import io.realm.DynamicRealm import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData -import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields @@ -31,7 +30,7 @@ import timber.log.Timber /** * Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061 * Also migrates how megolm session are stored to avoid additional serialized frozen class -*/ + */ internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) { override fun doMigrate(realm: DynamicRealm) { @@ -52,7 +51,7 @@ internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17 dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData -> val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") { @Suppress("DEPRECATION") - deserializeFromRealm(oldData) + deserializeFromRealm(oldData) } val groupSession = oldWrapper?.olmInboundGroupSession ?: return@transform Unit.also { From d8d808d0b414d34de79d7a85c0982e34cd13965b Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 May 2022 12:58:04 +0200 Subject: [PATCH 20/40] removed deprecated annotation, CI don't like --- .../sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index e5b540fb2f..f19275a8a1 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -27,7 +27,7 @@ import java.io.Serializable * This allows additional checks. The class implements Serializable so that the context can be stored. */ // Note used anymore, just for database migration -@Deprecated("Use MXInboundMegolmSessionWrapper") +// Deprecated("Use MXInboundMegolmSessionWrapper") internal class OlmInboundGroupSessionWrapper2 : Serializable { // The associated olm inbound group session. From fb352ffa38a9beb1c7bb6ec2c7d81e6f3f3a29a5 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 May 2022 13:57:53 +0200 Subject: [PATCH 21/40] quick format --- .../sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 7302a481ba..53ff4b83ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -308,5 +308,4 @@ internal class MXMegolmDecryption( Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") newSessionListener?.onNewSession(roomId, senderKey, sessionId) } - } From a9a7400fefcbfcf905b8c7a09e644812bd264cee Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 20 May 2022 17:46:31 +0300 Subject: [PATCH 22/40] Add MXCryptoConfig flag for key history sharing Add shared_history flag to sessionBackupData --- .../matrix/android/sdk/api/crypto/MXCryptoConfig.kt | 6 ++++++ .../crypto/keysbackup/DefaultKeysBackupService.kt | 5 +++-- .../crypto/keysbackup/model/rest/KeyBackupData.kt | 9 ++++++++- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 3 +++ .../room/membership/DefaultMembershipService.kt | 11 +++++++++-- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt index 9507ddda65..ea0c413546 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt @@ -38,4 +38,10 @@ data class MXCryptoConfig constructor( * You can limit request only to your sessions by turning this setting to `true` */ val limitRoomKeyRequestsToMyDevices: Boolean = false, + + /** + * Flag that indicates whether or not key history will be shared to invited + * users with respect to room visibility + */ + val shouldShareKeyHistory: Boolean = true, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 08765f0c39..3514dae3f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -1429,7 +1429,8 @@ internal class DefaultKeysBackupService @Inject constructor( "sender_key" to sessionData.senderKey, "sender_claimed_keys" to sessionData.senderClaimedKeys, "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()), - "session_key" to sessionData.sessionKey + "session_key" to sessionData.sessionKey, + "org.matrix.msc3061.shared_history" to sessionData.sharedHistory ) val json = MoshiProvider.providesMoshi() @@ -1456,7 +1457,7 @@ internal class DefaultKeysBackupService @Inject constructor( }, forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size, isVerified = device?.isVerified == true, - + sharedHistory = olmInboundGroupSessionWrapper.sessionData.sharedHistory, sessionData = mapOf( "ciphertext" to encryptedSessionBackupData.mCipherText, "mac" to encryptedSessionBackupData.mMac, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt index 5c3d0c12b0..675bf12248 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt @@ -50,5 +50,12 @@ internal data class KeyBackupData( * Algorithm-dependent data. */ @Json(name = "session_data") - val sessionData: JsonDict + val sessionData: JsonDict, + + /** + * Flag that indicates whether or not the current inboundSession will be shared to + * invited users to decrypt past messages + */ + @Json(name = "org.matrix.msc3061.shared_history") + val sharedHistory: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 74d547b9fb..15f95f01cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -26,6 +26,7 @@ import io.realm.RealmConfiguration import io.realm.Sort import io.realm.kotlin.createObject import io.realm.kotlin.where +import org.matrix.android.sdk.api.MatrixConfiguration 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 @@ -111,6 +112,7 @@ internal class RealmCryptoStore @Inject constructor( private val crossSigningKeysMapper: CrossSigningKeysMapper, @UserId private val userId: String, @DeviceId private val deviceId: String?, + private val matrixConfiguration: MatrixConfiguration, private val clock: Clock, ) : IMXCryptoStore { @@ -658,6 +660,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun shouldShareHistory(roomId: String): Boolean { + if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return false return doWithRealm(realmConfiguration) { CryptoRoomEntity.getById(it, roomId)?.shouldShareHistory } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 54c1a6b57e..331119530e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -24,6 +24,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.Realm import io.realm.RealmQuery +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.members.MembershipService @@ -57,6 +58,7 @@ internal class DefaultMembershipService @AssistedInject constructor( private val cryptoService: CryptoService, @UserId private val userId: String, + private val matrixConfiguration: MatrixConfiguration, private val queryStringValueProcessor: QueryStringValueProcessor ) : MembershipService { @@ -144,13 +146,18 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun invite(userId: String, reason: String?) { + sendShareHistoryKeysIfNeeded(userId) + val params = InviteTask.Params(roomId, userId, reason) + inviteTask.execute(params) + } + + private suspend fun sendShareHistoryKeysIfNeeded(userId: String) { + if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return // TODO not sure it's the right way to get the latest messages in a room val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use { ChunkEntity.findLatestSessionInfo(it, roomId) } cryptoService.sendSharedHistoryKeys(roomId, userId, sessionInfo) - val params = InviteTask.Params(roomId, userId, reason) - inviteTask.execute(params) } override suspend fun invite3pid(threePid: ThreePid) { From d3a516b05db38107883668b68317d474b3ad824b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 31 May 2022 11:26:26 +0300 Subject: [PATCH 23/40] Enhance key sharing to respect matrix configuration --- .../crypto/algorithms/megolm/MXMegolmDecryption.kt | 14 ++++++++++++-- .../crypto/keysbackup/DefaultKeysBackupService.kt | 12 +++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 53ff4b83ce..479c2654ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import dagger.Lazy +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.NewSessionListener @@ -41,6 +42,7 @@ internal class MXMegolmDecryption( private val olmDevice: MXOlmDevice, private val outgoingKeyRequestManager: OutgoingKeyRequestManager, private val cryptoStore: IMXCryptoStore, + private val matrixConfiguration: MatrixConfiguration, private val liveEventManager: Lazy ) : IMXDecrypting { @@ -247,7 +249,7 @@ internal class MXMegolmDecryption( forwardingCurve25519KeyChain = forwardingCurve25519KeyChain, keysClaimed = keysClaimed, exportFormat = exportFormat, - sharedHistory = roomKeyContent.sharedHistory ?: false + sharedHistory = roomKeyContent.getSharedKey() ) when (addSessionResult) { @@ -298,7 +300,15 @@ internal class MXMegolmDecryption( } /** - * Check if the some messages can be decrypted with a new session. + * Returns boolean shared key flag, if enabled with respect to matrix configuration + */ + private fun RoomKeyContent.getSharedKey(): Boolean { + if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return false + return sharedHistory ?: false + } + + /** + * Check if the some messages can be decrypted with a new session * * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions * @param senderKey the session sender key diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 3514dae3f2..f8b00007bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP @@ -120,6 +121,7 @@ internal class DefaultKeysBackupService @Inject constructor( private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, // Task executor private val taskExecutor: TaskExecutor, + private val matrixConfiguration: MatrixConfiguration, private val inboundGroupSessionStore: InboundGroupSessionStore, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope @@ -1457,7 +1459,7 @@ internal class DefaultKeysBackupService @Inject constructor( }, forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size, isVerified = device?.isVerified == true, - sharedHistory = olmInboundGroupSessionWrapper.sessionData.sharedHistory, + sharedHistory = olmInboundGroupSessionWrapper.getSharedKey(), sessionData = mapOf( "ciphertext" to encryptedSessionBackupData.mCipherText, "mac" to encryptedSessionBackupData.mMac, @@ -1466,6 +1468,14 @@ internal class DefaultKeysBackupService @Inject constructor( ) } + /** + * Returns boolean shared key flag, if enabled with respect to matrix configuration + */ + private fun MXInboundMegolmSessionWrapper.getSharedKey(): Boolean { + if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return false + return sessionData.sharedHistory + } + @VisibleForTesting @WorkerThread fun decryptKeyBackupData(keyBackupData: KeyBackupData, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? { From 55fdff4242fc9682c6cfa2394e8c1168d4714123 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 31 May 2022 11:43:33 +0300 Subject: [PATCH 24/40] Resolve merge conflicts --- .../sdk/internal/crypto/MXOlmDevice.kt | 20 +++++++++---------- .../megolm/MXMegolmDecryptionFactory.kt | 6 ++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 409945e468..7ad398ff1f 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -771,16 +771,16 @@ internal class MXOlmDevice @Inject constructor( val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId) val wrapper = sessionHolder.wrapper val inboundGroupSession = wrapper.session - // Check that the room id matches the original one for the session. This stops - // the HS pretending a message was targeting a different room. - if (roomId == wrapper.roomId) { - val decryptResult = try { - sessionHolder.mutex.withLock { - inboundGroupSession.decryptMessage(body) - } - } catch (e: OlmException) { - Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") - throw MXCryptoError.OlmError(e) + if (roomId != wrapper.roomId) { + // Check that the room id matches the original one for the session. This stops + // the HS pretending a message was targeting a different room. + val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId) + Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason") + throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason) + } + val decryptResult = try { + sessionHolder.mutex.withLock { + inboundGroupSession.decryptMessage(body) } } catch (e: OlmException) { Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 81a6fb28c0..414416a0f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import dagger.Lazy +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -27,6 +28,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( private val olmDevice: MXOlmDevice, private val outgoingKeyRequestManager: OutgoingKeyRequestManager, private val cryptoStore: IMXCryptoStore, + private val matrixConfiguration: MatrixConfiguration, private val eventsManager: Lazy ) { @@ -35,7 +37,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( olmDevice, outgoingKeyRequestManager, cryptoStore, - eventsManager - ) + matrixConfiguration, + eventsManager) } } From 010cf540b6bb4983b0a969b51c47ef7f7511f771 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 31 May 2022 13:13:24 +0300 Subject: [PATCH 25/40] Fix broken unit test --- .../matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt index ba1afd4758..d8890e2b38 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.crypto import io.realm.RealmConfiguration +import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.common.TestRoomDisplayNameFallbackProvider 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.RealmCryptoStoreModule @@ -36,6 +38,10 @@ internal class CryptoStoreHelper { crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()), userId = "userId_" + Random.nextInt(), deviceId = "deviceId_sample", + matrixConfiguration = MatrixConfiguration( + applicationFlavor = "TestFlavor", + roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider() + ), clock = DefaultClock(), ) } From df241dbdb8cd55df9eb7d2f8c57974a82ad3ac99 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 31 May 2022 15:07:11 +0300 Subject: [PATCH 26/40] Fix broken unit test --- .../java/org/matrix/android/sdk/common/CommonTestHelper.kt | 2 +- .../android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 7dafe33935..3ad8b281e9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -54,7 +54,7 @@ import java.util.concurrent.TimeUnit * This class exposes methods to be used in common cases * Registration, login, Sync, Sending messages... */ -class CommonTestHelper private constructor(context: Context) { +class CommonTestHelper internal constructor(context: Context) { companion object { internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index 0df7e0eb7d..4a7b49a280 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -305,7 +305,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, stateKey = "", body = RoomHistoryVisibilityContent( - _historyVisibility = nextRoomHistoryVisibility._historyVisibility + historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr ).toContent() ) it.countDown() From 34145f0374ae3f07c6cedbe3b5813c2130b70377 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Jun 2022 10:37:15 +0200 Subject: [PATCH 27/40] post rebase fix --- .../sdk/internal/crypto/MXOlmDevice.kt | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 7ad398ff1f..192df3ff0c 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -815,27 +815,12 @@ internal class MXOlmDevice @Inject constructor( throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) } - inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) - val payload = try { - val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) - val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) - adapter.fromJson(payloadString) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) - } - - return OlmDecryptionResult( - payload, - wrapper.sessionData.keysClaimed, - senderKey, - wrapper.sessionData.forwardingCurve25519KeyChain - ) - } else { - val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId) - Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason) - } + return OlmDecryptionResult( + payload, + wrapper.sessionData.keysClaimed, + senderKey, + wrapper.sessionData.forwardingCurve25519KeyChain + ) } /** From f64adeba7ffad1c08dabf912489034b02b7260a1 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 13 Jun 2022 13:56:44 +0200 Subject: [PATCH 28/40] fix bad sender key export --- .../internal/crypto/model/MXInboundMegolmSessionWrapper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt index 9cf66814f3..0820b1c207 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt @@ -46,11 +46,11 @@ data class MXInboundMegolmSessionWrapper( MegolmSessionData( senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"), forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(), - senderKey = session.export(index ?: session.firstKnownIndex), + sessionKey = session.export(wantedIndex), senderClaimedKeys = keysClaimed, roomId = sessionData.roomId, sessionId = session.sessionIdentifier(), - sessionKey = session.export(wantedIndex), + senderKey = senderKey, algorithm = MXCRYPTO_ALGORITHM_MEGOLM, sharedHistory = sessionData.sharedHistory ) From d9fb58fbcb66b717286d2fc0bfec3acde5c08eb0 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 14 Jun 2022 09:26:29 +0200 Subject: [PATCH 29/40] Fix tests --- .../sdk/internal/crypto/E2eeSanityTests.kt | 2 +- .../crypto/E2eeShareKeysHistoryTest.kt | 152 +++++++++--------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index d8c3c021cf..de4817f2b3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -70,7 +70,7 @@ import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") +//@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") class E2eeSanityTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index 4a7b49a280..c855c14a54 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants @@ -77,94 +78,93 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { * RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE. * We should not be able to view messages/decrypt otherwise */ - private fun testShareHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) + private fun testShareHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) = + runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) - val e2eRoomID = cryptoTestData.roomId + val e2eRoomID = cryptoTestData.roomId - // Alice - val aliceSession = cryptoTestData.firstSession - val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! + // Alice + val aliceSession = cryptoTestData.firstSession + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! - // Bob - val bobSession = cryptoTestData.secondSession - val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + // Bob + val bobSession = cryptoTestData.secondSession + val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! - assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) - Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") - val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper) - Assert.assertTrue("Message should be sent", aliceMessageId != null) - Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") + val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper) + Assert.assertTrue("Message should be sent", aliceMessageId != null) + Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") - // Bob should be able to decrypt the message - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) - (timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE).also { - if (it) { - Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") - } - } - } - } - - // Create a new user - val arisSession = testHelper.createAccount("aris", SessionTestParams(true)) - Log.v("#E2E TEST", "Aris user created") - - // Alice invites new user to the room - testHelper.runBlockingTest { - Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") - aliceRoomPOV.membershipService().invite(arisSession.myUserId) - } - - waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper) - - ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID, testHelper) - Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID") - - when (roomHistoryVisibility) { - RoomHistoryVisibility.WORLD_READABLE, - RoomHistoryVisibility.SHARED, - null - -> { - // Aris should be able to decrypt the message + // Bob should be able to decrypt the message testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) + val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) (timelineEvent != null && timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE - ).also { - if (it) { - Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") - } - } + timelineEvent.root.getClearType() == EventType.MESSAGE).also { + if (it) { + Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } } } - } - RoomHistoryVisibility.INVITED, - RoomHistoryVisibility.JOINED -> { - // Aris should not even be able to get the message - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = arisSession.roomService().getRoom(e2eRoomID) - ?.timelineService() - ?.getTimelineEvent(aliceMessageId!!) - timelineEvent == null - } - } - } - } - testHelper.signOutAndClose(arisSession) - cryptoTestData.cleanUp(testHelper) - } + // Create a new user + val arisSession = testHelper.createAccount("aris", SessionTestParams(true)) + Log.v("#E2E TEST", "Aris user created") + + // Alice invites new user to the room + testHelper.runBlockingTest { + Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}") + aliceRoomPOV.membershipService().invite(arisSession.myUserId) + } + + waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper) + + ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID, testHelper) + Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID") + + when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE, + RoomHistoryVisibility.SHARED, + null + -> { + // Aris should be able to decrypt the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) + (timelineEvent != null && + timelineEvent.isEncrypted() && + timelineEvent.root.getClearType() == EventType.MESSAGE + ).also { + if (it) { + Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + } + } + } + } + } + RoomHistoryVisibility.INVITED, + RoomHistoryVisibility.JOINED -> { + // Aris should not even be able to get the message + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timelineEvent = arisSession.roomService().getRoom(e2eRoomID) + ?.timelineService() + ?.getTimelineEvent(aliceMessageId!!) + timelineEvent == null + } + } + } + } + + testHelper.signOutAndClose(arisSession) + cryptoTestData.cleanUp(testHelper) + } @Test fun testNeedsRotationFromWorldReadableToShared() { @@ -342,7 +342,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { } when { - initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> { + initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> { assertEquals("Session shouldn't have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId) Log.v("#E2E TEST ROTATION", "Rotation is not needed") } From 8e829c6aad97b344dbe37135eb34d1b60ff62579 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 15 Jun 2022 12:20:57 +0200 Subject: [PATCH 30/40] Add lab flag and more tests --- .../android/sdk/common/CommonTestHelper.kt | 60 +++- .../internal/crypto/E2EShareKeysConfigTest.kt | 298 ++++++++++++++++++ .../sdk/internal/crypto/E2eeSanityTests.kt | 45 +-- .../crypto/E2eeShareKeysHistoryTest.kt | 14 +- .../android/sdk/api/crypto/MXCryptoConfig.kt | 7 +- .../sdk/api/session/crypto/CryptoService.kt | 14 + .../internal/crypto/DefaultCryptoService.kt | 5 +- .../algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../keysbackup/DefaultKeysBackupService.kt | 2 +- .../internal/crypto/store/IMXCryptoStore.kt | 14 + .../crypto/store/db/RealmCryptoStore.kt | 14 +- .../store/db/RealmCryptoStoreMigration.kt | 4 +- .../store/db/migration/MigrateCryptoTo018.kt | 36 +++ .../store/db/model/CryptoMetadataEntity.kt | 5 + .../android/sdk/internal/di/NetworkModule.kt | 6 +- .../membership/DefaultMembershipService.kt | 2 +- .../features/settings/VectorPreferences.kt | 2 + .../settings/VectorSettingsLabsFragment.kt | 11 + vector/src/main/res/values/strings.xml | 3 + .../src/main/res/xml/vector_settings_labs.xml | 7 + 20 files changed, 490 insertions(+), 61 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 3ad8b281e9..a78953caac 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -18,12 +18,15 @@ package org.matrix.android.sdk.common import android.content.Context import android.net.Uri +import android.util.Log import androidx.lifecycle.Observer import androidx.test.internal.runner.junit4.statement.UiThreadStatement import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -38,7 +41,10 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -47,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.sync.SyncState import timber.log.Timber import java.util.UUID +import java.util.concurrent.CancellationException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -241,6 +248,37 @@ class CommonTestHelper internal constructor(context: Context) { return sentEvents } + fun waitForAndAcceptInviteInRoom(otherSession: Session, roomID: String) { + waitWithLatch { latch -> + retryPeriodicallyWithLatch(latch) { + val roomSummary = otherSession.getRoomSummary(roomID) + (roomSummary != null && roomSummary.membership == Membership.INVITE).also { + if (it) { + Log.v("# TEST", "${otherSession.myUserId} can see the invite") + } + } + } + } + + // not sure why it's taking so long :/ + runBlockingTest(90_000) { + Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID") + try { + otherSession.roomService().joinRoom(roomID) + } catch (ex: JoinRoomFailure.JoinedWithTimeout) { + // it's ok we will wait after + } + } + + Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") + waitWithLatch { + retryPeriodicallyWithLatch(it) { + val roomSummary = otherSession.getRoomSummary(roomID) + roomSummary != null && roomSummary.membership == Membership.JOIN + } + } + } + /** * Reply in a thread * @param room the room where to send the messages @@ -285,6 +323,8 @@ class CommonTestHelper internal constructor(context: Context) { ) assertNotNull(session) return session.also { + // most of the test was created pre-MSC3061 so ensure compatibility + it.cryptoService().enableShareKeyOnInvite(false) trackedSessions.add(session) } } @@ -428,16 +468,26 @@ class CommonTestHelper internal constructor(context: Context) { * @param latch * @throws InterruptedException */ - fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) { + fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis, job: Job? = null) { assertTrue( "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.", - latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS).also { + if (!it) { + // cancel job on timeout + job?.cancel("Await timeout") + } + } ) } suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { while (true) { - delay(1000) + try { + delay(1000) + } catch (ex: CancellationException) { + // the job was canceled, just stop + return + } if (condition()) { latch.countDown() return @@ -447,10 +497,10 @@ class CommonTestHelper internal constructor(context: Context) { fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) { val latch = CountDownLatch(1) - coroutineScope.launch(dispatcher) { + val job = coroutineScope.launch(dispatcher) { block(latch) } - await(latch, timeout) + await(latch, timeout, job) } fun runBlockingTest(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt new file mode 100644 index 0000000000..32d63a1934 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt @@ -0,0 +1,298 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import android.util.Log +import androidx.test.filters.LargeTest +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CryptoTestData +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants +import org.matrix.android.sdk.common.TestMatrixCallback + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class E2EShareKeysConfigTest : InstrumentedTest { + + @Test + fun msc3061ShouldBeDisabledByDefault() = runCryptoTest(context()) { _, commonTestHelper -> + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) + Assert.assertFalse("MSC3061 is lab and should be disabled by default", aliceSession.cryptoService().isShareKeysOnInviteEnabled()) + } + + @Test + fun ensureKeysAreNotSharedIfOptionDisabled() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + aliceSession.cryptoService().enableShareKeyOnInvite(false) + val roomId = commonTestHelper.runBlockingTest { + aliceSession.roomService().createRoom(CreateRoomParams().apply { + historyVisibility = RoomHistoryVisibility.SHARED + name = "MyRoom" + enableEncryption() + }) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true + } + } + val roomAlice = aliceSession.roomService().getRoom(roomId)!! + + // send some messages + val withSession1 = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1) + aliceSession.cryptoService().discardOutboundSession(roomId) + val withSession2 = commonTestHelper.sendTextMessage(roomAlice, "World", 1) + + // Create bob account + val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(withInitialSync = true)) + + // Let alice invite bob + commonTestHelper.runBlockingTest { + roomAlice.membershipService().invite(bobSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(bobSession, roomId) + + // Bob has join but should not be able to decrypt history + cryptoTestHelper.ensureCannotDecrypt( + withSession1.map { it.eventId } + withSession2.map { it.eventId }, + bobSession, + roomId + ) + + // We don't need bob anymore + commonTestHelper.signOutAndClose(bobSession) + + // Now let's enable history key sharing on alice side + aliceSession.cryptoService().enableShareKeyOnInvite(true) + + // let's add a new message first + val afterFlagOn = commonTestHelper.sendTextMessage(roomAlice, "After", 1) + + // Worth nothing to check that the session was rotated + Assert.assertNotEquals( + "Session should have been rotated", + withSession2.first().root.content?.get("session_id")!!, + afterFlagOn.first().root.content?.get("session_id")!! + ) + + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let alice invite sam + commonTestHelper.runBlockingTest { + roomAlice.membershipService().invite(samSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId) + + // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session + cryptoTestHelper.ensureCannotDecrypt( + withSession1.map { it.eventId } + withSession2.map { it.eventId }, + samSession, + roomId + ) + + cryptoTestHelper.ensureCanDecrypt( + afterFlagOn.map { it.eventId }, + samSession, + roomId, + afterFlagOn.map { it.root.getClearContent()?.get("body") as String }) + } + + @Test + fun ifSharingDisabledOnAliceSideBobShouldNotShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED) + val aliceSession = testData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(false) + } + val bobSession = testData.secondSession!!.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + + val (fromAliceNotSharable, fromBobSharable, samSession) = commonAliceAndBobSendMessages(commonTestHelper, aliceSession, testData, bobSession) + + // Bob should have shared history keys to sam. + // But has alice hasn't enabled sharing, bob shouldn't send her sessions + cryptoTestHelper.ensureCannotDecrypt( + fromAliceNotSharable.map { it.eventId }, + samSession, + testData.roomId + ) + + cryptoTestHelper.ensureCanDecrypt( + fromBobSharable.map { it.eventId }, + samSession, + testData.roomId, + fromBobSharable.map { it.root.getClearContent()?.get("body") as String }) + } + + @Test + fun ifSharingEnabledOnAliceSideBobShouldShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED) + val aliceSession = testData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + val bobSession = testData.secondSession!!.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + + val (fromAliceNotSharable, fromBobSharable, samSession) = commonAliceAndBobSendMessages(commonTestHelper, aliceSession, testData, bobSession) + + cryptoTestHelper.ensureCanDecrypt( + fromAliceNotSharable.map { it.eventId }, + samSession, + testData.roomId, + fromAliceNotSharable.map { it.root.getClearContent()?.get("body") as String }) + + cryptoTestHelper.ensureCanDecrypt( + fromBobSharable.map { it.eventId }, + samSession, + testData.roomId, + fromBobSharable.map { it.root.getClearContent()?.get("body") as String }) + } + + private fun commonAliceAndBobSendMessages(commonTestHelper: CommonTestHelper, aliceSession: Session, testData: CryptoTestData, bobSession: Session): Triple, List, Session> { + val fromAliceNotSharable = commonTestHelper.sendTextMessage(aliceSession.getRoom(testData.roomId)!!, "Hello from alice", 1) + val fromBobSharable = commonTestHelper.sendTextMessage(bobSession.getRoom(testData.roomId)!!, "Hello from bob", 1) + + // Now let bob invite Sam + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let bob invite sam + commonTestHelper.runBlockingTest { + bobSession.getRoom(testData.roomId)!!.membershipService().invite(samSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(samSession, testData.roomId) + return Triple(fromAliceNotSharable, fromBobSharable, samSession) + } + + // test flag on backup is correct + + @Test + fun testBackupFlagIsCorrect() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + aliceSession.cryptoService().enableShareKeyOnInvite(false) + val roomId = commonTestHelper.runBlockingTest { + aliceSession.roomService().createRoom(CreateRoomParams().apply { + historyVisibility = RoomHistoryVisibility.SHARED + name = "MyRoom" + enableEncryption() + }) + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true + } + } + val roomAlice = aliceSession.roomService().getRoom(roomId)!! + + // send some messages + val notSharableMessage = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1) + aliceSession.cryptoService().enableShareKeyOnInvite(true) + val sharableMessage = commonTestHelper.sendTextMessage(roomAlice, "World", 1) + + Log.v("#E2E TEST", "Create and start key backup for bob ...") + val keysBackupService = aliceSession.cryptoService().keysBackupService() + val keyBackupPassword = "FooBarBaz" + val megolmBackupCreationInfo = commonTestHelper.doSync { + keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) + } + val version = commonTestHelper.doSync { + keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it) + } + + commonTestHelper.waitWithLatch { latch -> + keysBackupService.backupAllGroupSessions( + null, + TestMatrixCallback(latch, true) + ) + } + + // signout + commonTestHelper.signOutAndClose(aliceSession) + + val newAliceSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + newAliceSession.cryptoService().enableShareKeyOnInvite(true) + + newAliceSession.cryptoService().keysBackupService().let { kbs -> + val keyVersionResult = commonTestHelper.doSync { + kbs.getVersion(version.version, it) + } + + val importedResult = commonTestHelper.doSync { + kbs.restoreKeyBackupWithPassword( + keyVersionResult!!, + keyBackupPassword, + null, + null, + null, + it + ) + } + + assertEquals(2, importedResult.totalNumberOfKeys) + } + + // Now let's invite sam + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + + // Let alice invite sam + commonTestHelper.runBlockingTest { + newAliceSession.getRoom(roomId)!!.membershipService().invite(samSession.myUserId) + } + + commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId) + + // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session + cryptoTestHelper.ensureCannotDecrypt( + notSharableMessage.map { it.eventId }, + samSession, + roomId + ) + + cryptoTestHelper.ensureCanDecrypt( + sharableMessage.map { it.eventId }, + samSession, + roomId, + sharableMessage.map { it.root.getClearContent()?.get("body") as String }) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index de4817f2b3..251c13ccbf 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -23,7 +23,6 @@ import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -49,9 +48,7 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.Room -import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -67,10 +64,10 @@ import org.matrix.android.sdk.common.TestMatrixCallback import org.matrix.android.sdk.mustFail import java.util.concurrent.CountDownLatch +// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -//@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") class E2eeSanityTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) @@ -115,7 +112,7 @@ class E2eeSanityTests : InstrumentedTest { // All user should accept invite otherAccounts.forEach { otherSession -> - waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID) + testHelper.waitForAndAcceptInviteInRoom(otherSession, e2eRoomID) Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID") } @@ -156,7 +153,7 @@ class E2eeSanityTests : InstrumentedTest { } newAccount.forEach { - waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID) + testHelper.waitForAndAcceptInviteInRoom(it, e2eRoomID) } ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID) @@ -166,7 +163,7 @@ class E2eeSanityTests : InstrumentedTest { delay(3_000) } - // Due to the new shared keys implementation, invited user should be able to decrypt messages + // check that messages are encrypted (uisi) newAccount.forEach { otherSession -> testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { @@ -174,7 +171,8 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") } timelineEvent != null && - timelineEvent.root.getClearType() == EventType.MESSAGE + timelineEvent.root.getClearType() == EventType.ENCRYPTED && + timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID } } } @@ -739,37 +737,6 @@ class E2eeSanityTests : InstrumentedTest { } } - private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) { - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val roomSummary = otherSession.getRoomSummary(e2eRoomID) - (roomSummary != null && roomSummary.membership == Membership.INVITE).also { - if (it) { - Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice") - } - } - } - } - - // not sure why it's taking so long :/ - testHelper.runBlockingTest(90_000) { - Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") - try { - otherSession.roomService().joinRoom(e2eRoomID) - } catch (ex: JoinRoomFailure.JoinedWithTimeout) { - // it's ok we will wait after - } - } - - Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") - testHelper.waitWithLatch { - testHelper.retryPeriodicallyWithLatch(it) { - val roomSummary = otherSession.getRoomSummary(e2eRoomID) - roomSummary != null && roomSummary.membership == Membership.JOIN - } - } - } - private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List, session: Session, e2eRoomID: String) { testHelper.waitWithLatch { latch -> sentEventIds.forEach { sentEventId -> diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index c855c14a54..ee1ad73ed8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -85,12 +85,16 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { val e2eRoomID = cryptoTestData.roomId // Alice - val aliceSession = cryptoTestData.firstSession + val aliceSession = cryptoTestData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(true) + } val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! // Bob - val bobSession = cryptoTestData.secondSession - val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + val bobSession = cryptoTestData.secondSession!!.also { + it.cryptoService().enableShareKeyOnInvite(true) + } + val bobRoomPOV = bobSession.roomService().getRoom(e2eRoomID)!! assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID") @@ -114,7 +118,9 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { } // Create a new user - val arisSession = testHelper.createAccount("aris", SessionTestParams(true)) + val arisSession = testHelper.createAccount("aris", SessionTestParams(true)).also { + it.cryptoService().enableShareKeyOnInvite(true) + } Log.v("#E2E TEST", "Aris user created") // Alice invites new user to the room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt index ea0c413546..015cb6a1a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt @@ -39,9 +39,4 @@ data class MXCryptoConfig constructor( */ val limitRoomKeyRequestsToMyDevices: Boolean = false, - /** - * Flag that indicates whether or not key history will be shared to invited - * users with respect to room visibility - */ - val shouldShareKeyHistory: Boolean = true, -) + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 6b52cff512..112382c4e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -85,6 +85,20 @@ interface CryptoService { fun isKeyGossipingEnabled(): Boolean + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun enableShareKeyOnInvite(enable: Boolean) + + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun isShareKeysOnInviteEnabled(): Boolean + fun setRoomUnBlacklistUnverifiedDevices(roomId: String) fun getDeviceTrackingStatus(userId: String): Int diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index b00b4f6173..850a4379ca 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -110,7 +110,6 @@ import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -import kotlin.coroutines.coroutineContext import kotlin.math.max /** @@ -1118,6 +1117,10 @@ internal class DefaultCryptoService @Inject constructor( override fun isKeyGossipingEnabled() = cryptoStore.isKeyGossipingEnabled() + override fun isShareKeysOnInviteEnabled() = cryptoStore.isShareKeysOnInviteEnabled() + + override fun enableShareKeyOnInvite(enable: Boolean) = cryptoStore.enableShareKeyOnInvite(enable) + /** * Tells whether the client should ever send encrypted messages to unverified devices. * The default value is false. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 479c2654ed..d884293ae1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -303,7 +303,7 @@ internal class MXMegolmDecryption( * Returns boolean shared key flag, if enabled with respect to matrix configuration */ private fun RoomKeyContent.getSharedKey(): Boolean { - if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return false + if (!cryptoStore.isShareKeysOnInviteEnabled()) return false return sharedHistory ?: false } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index f8b00007bb..2e52325e73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -1472,7 +1472,7 @@ internal class DefaultKeysBackupService @Inject constructor( * Returns boolean shared key flag, if enabled with respect to matrix configuration */ private fun MXInboundMegolmSessionWrapper.getSharedKey(): Boolean { - if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return false + if (!cryptoStore.isShareKeysOnInviteEnabled()) return false return sessionData.sharedHistory } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index d315e0310a..3a07149b58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -98,6 +98,20 @@ internal interface IMXCryptoStore { fun isKeyGossipingEnabled(): Boolean + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun enableShareKeyOnInvite(enable: Boolean) + + /** + * As per MSC3061. + * If true will make it possible to share part of e2ee room history + * on invite depending on the room visibility setting. + */ + fun isShareKeysOnInviteEnabled(): Boolean + /** * Provides the rooms ids list in which the messages are not encrypted for the unverified devices. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 15f95f01cb..0a59820261 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -660,7 +660,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun shouldShareHistory(roomId: String): Boolean { - if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return false + if (!isShareKeysOnInviteEnabled()) return false return doWithRealm(realmConfiguration) { CryptoRoomEntity.getById(it, roomId)?.shouldShareHistory } @@ -1009,6 +1009,18 @@ internal class RealmCryptoStore @Inject constructor( } ?: false } + override fun isShareKeysOnInviteEnabled(): Boolean { + return doWithRealm(realmConfiguration) { + it.where().findFirst()?.enableKeyForwardingOnInvite + } ?: false + } + + override fun enableShareKeyOnInvite(enable: Boolean) { + doRealmTransaction(realmConfiguration) { + it.where().findFirst()?.enableKeyForwardingOnInvite = enable + } + } + override fun setDeviceKeysUploaded(uploaded: Boolean) { doRealmTransaction(realmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer = uploaded diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 4ca9d44f98..56222ab88e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017 +import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018 import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -52,7 +53,7 @@ internal class RealmCryptoStoreMigration @Inject constructor( // 0, 1, 2: legacy Riot-Android // 3: migrate to RiotX schema // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - val schemaVersion = 17L + val schemaVersion = 18L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion") @@ -74,5 +75,6 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 15) MigrateCryptoTo015(realm).perform() if (oldVersion < 16) MigrateCryptoTo016(realm).perform() if (oldVersion < 17) MigrateCryptoTo017(realm).perform() + if (oldVersion < 18) MigrateCryptoTo018(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt new file mode 100644 index 0000000000..d0989bc7d5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.store.db.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Support for MSC3061 (share room keys for past messages as a flag) + */ +internal class MigrateCryptoTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("CryptoMetadataEntity") + ?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java) + ?.transform { obj -> + // default to false + obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt index 63ed0e537e..88708f824e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt @@ -35,6 +35,11 @@ internal open class CryptoMetadataEntity( var globalBlacklistUnverifiedDevices: Boolean = false, // setting to enable or disable key gossiping var globalEnableKeyGossiping: Boolean = true, + + // MSC3061: Sharing room keys for past messages + // If set to true key history will be shared to invited users with respect to room setting + var enableKeyForwardingOnInvite: Boolean = false, + // The keys backup version currently used. Null means no backup. var backupVersion: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index b5b46a3f5a..113e780e5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi import dagger.Module import dagger.Provides import okhttp3.ConnectionSpec +import okhttp3.Dispatcher import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.logging.HttpLoggingInterceptor @@ -73,7 +74,9 @@ internal object NetworkModule { apiInterceptor: ApiInterceptor ): OkHttpClient { val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build() - + val dispatcher = Dispatcher().apply { + maxRequestsPerHost = 20 + } return OkHttpClient.Builder() // workaround for #4669 .protocols(listOf(Protocol.HTTP_1_1)) @@ -94,6 +97,7 @@ internal object NetworkModule { addInterceptor(curlLoggingInterceptor) } } + .dispatcher(dispatcher) .connectionSpecs(Collections.singletonList(spec)) .applyMatrixConfiguration(matrixConfiguration) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 331119530e..20708b3814 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -152,7 +152,7 @@ internal class DefaultMembershipService @AssistedInject constructor( } private suspend fun sendShareHistoryKeysIfNeeded(userId: String) { - if (!matrixConfiguration.cryptoConfig.shouldShareKeyHistory) return + if (!cryptoService.isShareKeysOnInviteEnabled()) return // TODO not sure it's the right way to get the latest messages in a room val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use { ChunkEntity.findLatestSessionInfo(it, roomId) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 6d91dc3348..ea039f0ed8 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -168,6 +168,8 @@ class VectorPreferences @Inject constructor( private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" private const val SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY = "SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY" + const val SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY = "SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY" + // SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS private const val SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM = "SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM" const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 3b9fdc5e55..1ed045757b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.text.method.LinkMovementMethod import android.widget.TextView import androidx.preference.Preference +import androidx.preference.SwitchPreference import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.preference.VectorSwitchPreference @@ -57,6 +58,16 @@ class VectorSettingsLabsFragment @Inject constructor( false } } + + findPreference(VectorPreferences.SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY)?.let { pref -> + // ensure correct default + pref.isChecked = session.cryptoService().isShareKeysOnInviteEnabled() + + pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + session.cryptoService().enableShareKeyOnInvite(pref.isChecked) + true + } + } } /** diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 933f3f0602..bc1b59ebd4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2958,6 +2958,9 @@ Enable LaTeX mathematics Restart the application for the change to take effect. + MSC3061: Sharing room keys for past messages + When inviting in an encrypted room that is sharing history, encrypted history will be visible. + Create Poll Poll question or topic diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 9b9293e2e2..555470e643 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -45,6 +45,13 @@ android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" android:title="@string/labs_show_unread_notifications_as_tab" /> + + Date: Wed, 15 Jun 2022 15:35:42 +0200 Subject: [PATCH 31/40] kdoc --- .../sdk/api/session/crypto/CryptoService.kt | 2 +- .../crypto/model/ForwardedRoomKeyContent.kt | 3 +- .../events/model/content/RoomKeyContent.kt | 3 +- .../room/model/RoomHistoryVisibility.kt | 2 +- .../sdk/internal/crypto/MXOlmDevice.kt | 1 + .../sdk/internal/crypto/MegolmSessionData.kt | 2 +- .../algorithms/megolm/MXMegolmDecryption.kt | 4 +-- .../keysbackup/DefaultKeysBackupService.kt | 2 +- .../keysbackup/model/rest/KeyBackupData.kt | 2 +- .../crypto/model/InboundGroupSessionData.kt | 4 +-- .../model/MXInboundMegolmSessionWrapper.kt | 2 +- .../model/OlmInboundGroupSessionWrapper2.kt | 2 +- .../model/OutboundGroupSessionWrapper.kt | 2 +- .../internal/crypto/store/IMXCryptoStore.kt | 8 ++--- .../store/db/migration/MigrateCryptoTo018.kt | 36 ------------------- 15 files changed, 19 insertions(+), 56 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 112382c4e9..a5e05f69e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -193,7 +193,7 @@ interface CryptoService { fun prepareToEncrypt(roomId: String, callback: MatrixCallback) /** - * Share all inbound sessions of the last chunk messages to the provided userId devices + * Share all inbound sessions of the last chunk messages to the provided userId devices. */ suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt index dbee04de88..664cd00e94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt @@ -72,8 +72,7 @@ data class ForwardedRoomKeyContent( val senderClaimedEd25519Key: String? = null, /** - * MSC3061 - * Identifies keys that were sent when the room's visibility setting was set to world_readable or shared + * MSC3061 Identifies keys that were sent when the room's visibility setting was set to world_readable or shared. */ @Json(name = "org.matrix.msc3061.shared_history") val sharedHistory: Boolean? = false, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt index 75162f8ace..5b18d29ea0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt @@ -41,8 +41,7 @@ data class RoomKeyContent( val chainIndex: Any? = null, /** - * MSC3061 - * Identifies keys that were sent when the room's visibility setting was set to world_readable or shared + * MSC3061 Identifies keys that were sent when the room's visibility setting was set to world_readable or shared. */ @Json(name = "org.matrix.msc3061.shared_history") val sharedHistory: Boolean? = false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt index 3648d6b883..2b0ea1d8fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt @@ -50,7 +50,7 @@ enum class RoomHistoryVisibility { } /** - * Room history should be shared only if room visibility is world_readable or shared + * Room history should be shared only if room visibility is world_readable or shared. */ internal fun RoomHistoryVisibility.shouldShareHistory() = this == RoomHistoryVisibility.WORLD_READABLE || this == RoomHistoryVisibility.SHARED diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 192df3ff0c..c4a6488258 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -601,6 +601,7 @@ internal class MXOlmDevice @Inject constructor( * @param forwardingCurve25519KeyChain Devices involved in forwarding this session to us. * @param keysClaimed Other keys the sender claims. * @param exportFormat true if the megolm keys are in export format + * @param sharedHistory MSC3061, this key is sharable on invite * @return true if the operation succeeds. */ fun addInboundGroupSession(sessionId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt index 43b88c0b42..ca0bdc8a0e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt @@ -73,7 +73,7 @@ internal data class MegolmSessionData( /** * Flag that indicates whether or not the current inboundSession will be shared to - * invited users to decrypt past messages + * invited users to decrypt past messages. */ // When this feature lands in spec name = shared_history should be used @Json(name = "org.matrix.msc3061.shared_history") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index d884293ae1..410b74e19f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -300,7 +300,7 @@ internal class MXMegolmDecryption( } /** - * Returns boolean shared key flag, if enabled with respect to matrix configuration + * Returns boolean shared key flag, if enabled with respect to matrix configuration. */ private fun RoomKeyContent.getSharedKey(): Boolean { if (!cryptoStore.isShareKeysOnInviteEnabled()) return false @@ -308,7 +308,7 @@ internal class MXMegolmDecryption( } /** - * Check if the some messages can be decrypted with a new session + * Check if the some messages can be decrypted with a new session. * * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions * @param senderKey the session sender key diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 2e52325e73..49cf60d051 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -1469,7 +1469,7 @@ internal class DefaultKeysBackupService @Inject constructor( } /** - * Returns boolean shared key flag, if enabled with respect to matrix configuration + * Returns boolean shared key flag, if enabled with respect to matrix configuration. */ private fun MXInboundMegolmSessionWrapper.getSharedKey(): Boolean { if (!cryptoStore.isShareKeysOnInviteEnabled()) return false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt index 675bf12248..1817b18e2a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt @@ -54,7 +54,7 @@ internal data class KeyBackupData( /** * Flag that indicates whether or not the current inboundSession will be shared to - * invited users to decrypt past messages + * invited users to decrypt past messages. */ @Json(name = "org.matrix.msc3061.shared_history") val sharedHistory: Boolean = false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt index 0e60791282..2ce36aa209 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt @@ -43,9 +43,9 @@ data class InboundGroupSessionData( /** * Flag that indicates whether or not the current inboundSession will be shared to - *invited users to decrypt past messages + * invited users to decrypt past messages. */ @Json(name = "shared_history") val sharedHistory: Boolean = false, -) + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt index 0820b1c207..2772b34835 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt @@ -34,7 +34,7 @@ data class MXInboundMegolmSessionWrapper( val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() } /** - * Export the inbound group session keys + * Export the inbound group session keys. * @param index the index to export. If null, the first known index will be used * @return the inbound group session as MegolmSessionData if the operation succeeds */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index f19275a8a1..4b37e97e0d 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -107,8 +107,8 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { /** * Export the inbound group session keys. - * @param index the index to export. If null, the first known index will be used * @param sharedHistory the flag that indicates whether or not the session can be shared + * @param index the index to export. If null, the first known index will be used * @return the inbound group session as MegolmSessionData if the operation succeeds */ fun exportKeys(sharedHistory: Boolean = false, index: Long? = null): MegolmSessionData? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt index e02953c9e0..5a6d1f4bc1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt @@ -22,7 +22,7 @@ internal data class OutboundGroupSessionWrapper( val outboundGroupSession: OlmOutboundGroupSession, val creationTime: Long, /** - * As per MSC 3061, declares if this key could be shared when inviting a new user to the room + * As per MSC 3061, declares if this key could be shared when inviting a new user to the room. */ val sharedHistory: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 3a07149b58..0413fc730c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -67,7 +67,7 @@ internal interface IMXCryptoStore { fun getInboundGroupSessions(): List /** - * Retrieve the known inbound group sessions for the specified room + * Retrieve the known inbound group sessions for the specified room. * * @param roomId The roomId that the sessions will be returned * @return the list of all known group sessions, for the provided roomId @@ -276,7 +276,7 @@ internal interface IMXCryptoStore { /** * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) - * will be shared to new user invites + * will be shared to new user invites. * * @param roomId the room id * @param shouldShareHistory The boolean flag @@ -345,7 +345,7 @@ internal interface IMXCryptoStore { fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? /** - * Get the current outbound group session for this encrypted room + * Get the current outbound group session for this encrypted room. */ fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? @@ -355,7 +355,7 @@ internal interface IMXCryptoStore { fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?) /** - * Remove an inbound group session + * Remove an inbound group session. * * @param sessionId the session identifier. * @param senderKey the base64-encoded curve25519 key of the sender. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt deleted file mode 100644 index d0989bc7d5..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.store.db.migration - -import io.realm.DynamicRealm -import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields -import org.matrix.android.sdk.internal.util.database.RealmMigrator - -/** - * Support for MSC3061 (share room keys for past messages as a flag) - */ -internal class MigrateCryptoTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) { - - override fun doMigrate(realm: DynamicRealm) { - realm.schema.get("CryptoMetadataEntity") - ?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java) - ?.transform { obj -> - // default to false - obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false) - } - } -} From b0907de582b893299c9eeaeaf746e0b0358d09ec Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 16 Jun 2022 09:14:38 +0200 Subject: [PATCH 32/40] Fix migration --- .../crypto/store/db/RealmCryptoStore.kt | 2 -- .../store/db/RealmCryptoStoreMigration.kt | 4 +--- .../store/db/migration/MigrateCryptoTo017.kt | 18 +++++++++++++++--- .../settings/VectorSettingsLabsFragment.kt | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 0a59820261..20ca357d1a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -26,7 +26,6 @@ import io.realm.RealmConfiguration import io.realm.Sort import io.realm.kotlin.createObject import io.realm.kotlin.where -import org.matrix.android.sdk.api.MatrixConfiguration 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 @@ -112,7 +111,6 @@ internal class RealmCryptoStore @Inject constructor( private val crossSigningKeysMapper: CrossSigningKeysMapper, @UserId private val userId: String, @DeviceId private val deviceId: String?, - private val matrixConfiguration: MatrixConfiguration, private val clock: Clock, ) : IMXCryptoStore { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 56222ab88e..4ca9d44f98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -35,7 +35,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017 -import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018 import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -53,7 +52,7 @@ internal class RealmCryptoStoreMigration @Inject constructor( // 0, 1, 2: legacy Riot-Android // 3: migrate to RiotX schema // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - val schemaVersion = 18L + val schemaVersion = 17L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion") @@ -75,6 +74,5 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 15) MigrateCryptoTo015(realm).perform() if (oldVersion < 16) MigrateCryptoTo016(realm).perform() if (oldVersion < 17) MigrateCryptoTo017(realm).perform() - if (oldVersion < 18) MigrateCryptoTo018(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt index 2e847ab28e..afb0e0186d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -20,6 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm +import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm @@ -28,14 +29,25 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber /** - * Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061 - * Also migrates how megolm session are stored to avoid additional serialized frozen class + * Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061. + * Also migrates how megolm session are stored to avoid additional serialized frozen class. */ internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("CryptoRoomEntity") - ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java) + ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform { + // We don't have access to the session database to check for the state here and set the good value. + // But for now as it's behind a lab flag, will set to false and force initial sync when enabled + it.setBoolean(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, false) + } + + realm.schema.get("CryptoMetadataEntity") + ?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java) + ?.transform { obj -> + // default to false + obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false) + } val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 1ed045757b..70908d7560 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -65,6 +65,7 @@ class VectorSettingsLabsFragment @Inject constructor( pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { session.cryptoService().enableShareKeyOnInvite(pref.isChecked) + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) true } } From a885ff5e479315afd239ffe847d981fc54f90d6d Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 16 Jun 2022 11:09:31 +0200 Subject: [PATCH 33/40] Fix test --- .../sdk/internal/crypto/CryptoStoreHelper.kt | 6 --- .../crypto/E2eeShareKeysHistoryTest.kt | 49 +++++++++++++------ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt index d8890e2b38..ba1afd4758 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt @@ -17,8 +17,6 @@ package org.matrix.android.sdk.internal.crypto import io.realm.RealmConfiguration -import org.matrix.android.sdk.api.MatrixConfiguration -import org.matrix.android.sdk.common.TestRoomDisplayNameFallbackProvider 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.RealmCryptoStoreModule @@ -38,10 +36,6 @@ internal class CryptoStoreHelper { crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()), userId = "userId_" + Random.nextInt(), deviceId = "deviceId_sample", - matrixConfiguration = MatrixConfiguration( - applicationFlavor = "TestFlavor", - roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider() - ), clock = DefaultClock(), ) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index ee1ad73ed8..4dcbe9f2b4 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -27,6 +27,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent @@ -45,7 +46,6 @@ import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.SessionTestParams -import org.matrix.android.sdk.common.TestConstants @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -249,13 +249,16 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { val e2eRoomID = cryptoTestData.roomId // Alice - val aliceSession = cryptoTestData.firstSession + val aliceSession = cryptoTestData.firstSession.also { + it.cryptoService().enableShareKeyOnInvite(true) + } val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! // val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting // Bob - val bobSession = cryptoTestData.secondSession - val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + val bobSession = cryptoTestData.secondSession!! + + val bobRoomPOV = bobSession.roomService().getRoom(e2eRoomID)!! assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) Log.v("#E2E TEST ROTATION", "Alice and Bob are in roomId: $e2eRoomID") @@ -266,9 +269,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { // Bob should be able to decrypt the message var firstAliceMessageMegolmSessionId: String? = null + val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID) testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.roomService().getRoom(e2eRoomID) + val timelineEvent = bobRoomPov ?.timelineService() ?.getTimelineEvent(aliceMessageId!!) (timelineEvent != null && @@ -276,7 +280,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { timelineEvent.root.getClearType() == EventType.MESSAGE).also { if (it) { firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String - Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}") + Log.v( + "#E2E TEST", + "Bob can decrypt the message (sid:$firstAliceMessageMegolmSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}" + ) } } } @@ -288,7 +295,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage -> testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.roomService().getRoom(e2eRoomID) + val timelineEvent = bobRoomPov ?.timelineService() ?.getTimelineEvent(secondMessage) (timelineEvent != null && @@ -296,6 +303,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { timelineEvent.root.getClearType() == EventType.MESSAGE).also { if (it) { secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String + Log.v( + "#E2E TEST", + "Bob can decrypt the message (sid:$secondAliceMessageSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}" + ) } } } @@ -305,7 +316,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { Log.v("#E2E TEST ROTATION", "No rotation needed yet") // Let's change the room history visibility - testHelper.waitWithLatch { + testHelper.runBlockingTest { aliceRoomPOV.stateService() .sendStateEvent( eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, @@ -314,7 +325,14 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr ).toContent() ) - it.countDown() + } + + // ensure that the state did synced down + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content + ?.toModel()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility + } } testHelper.waitWithLatch { latch -> @@ -333,7 +351,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage -> testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.roomService().getRoom(e2eRoomID) + val timelineEvent = bobRoomPov ?.timelineService() ?.getTimelineEvent(thirdMessage) (timelineEvent != null && @@ -362,14 +380,14 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { } private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? { + val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) + timeline.start() aliceRoomPOV.sendService().sendTextMessage(text) var sentEventId: String? = null - testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> - val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) - timeline.start() + testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { val decryptedMsg = timeline.getSnapshot() - .filter { it.root.getClearType() == EventType.MESSAGE } + .filter { it.root.isEncrypted() || it.root.getClearType() == EventType.MESSAGE } .also { list -> val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } Log.v("#E2E TEST", "Timeline snapshot is $message") @@ -379,9 +397,8 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { sentEventId = decryptedMsg?.eventId decryptedMsg != null } - - timeline.dispose() } + timeline.dispose() return sentEventId } From 5a67c39c7f85270731352fedd50993127acf1d43 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 16 Jun 2022 11:54:39 +0200 Subject: [PATCH 34/40] reuse code for test --- .../crypto/E2eeShareKeysHistoryTest.kt | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index 4dcbe9f2b4..933de69c93 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -38,10 +38,7 @@ import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent -import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.shouldShareHistory -import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CryptoTestHelper @@ -380,26 +377,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { } private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? { - val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) - timeline.start() - aliceRoomPOV.sendService().sendTextMessage(text) - var sentEventId: String? = null - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val decryptedMsg = timeline.getSnapshot() - .filter { it.root.isEncrypted() || it.root.getClearType() == EventType.MESSAGE } - .also { list -> - val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" } - Log.v("#E2E TEST", "Timeline snapshot is $message") - } - .filter { it.root.sendState == SendState.SYNCED } - .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true } - sentEventId = decryptedMsg?.eventId - decryptedMsg != null - } - } - timeline.dispose() - return sentEventId + return testHelper.sendTextMessage(aliceRoomPOV, text, 1).firstOrNull()?.eventId } private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List, e2eRoomID: String, testHelper: CommonTestHelper) { From e7322e852448b9aa18da90d9504e5f4c91348278 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 16 Jun 2022 12:22:16 +0200 Subject: [PATCH 35/40] outdated configuration --- .../sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 4d213dfd9a..48a25f2a8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -247,7 +247,7 @@ internal class MXMegolmEncryption( /** * Share the device keys of a an user. * - * @param session the session info + * @param sessionInfo the session info * @param devicesByUser the devices map */ private suspend fun shareUserDevicesKey(sessionInfo: MXOutboundSessionInfo, From fb5f0cbd004d56791896a5bde00ae09fc6e0b62d Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 28 Jun 2022 16:09:52 +0200 Subject: [PATCH 36/40] Fix test compilation --- .../android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt index 933de69c93..32a95008b1 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt @@ -336,7 +336,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { testHelper.retryPeriodicallyWithLatch(latch) { val roomVisibility = aliceSession.getRoom(e2eRoomID)!! .stateService() - .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY) + .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty) ?.content ?.toModel() Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}") From 08cb6de83d917566fd6db059ad1ac2e39e180a23 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 1 Jul 2022 11:08:35 +0200 Subject: [PATCH 37/40] Fix migration --- .../crypto/model/OlmInboundGroupSessionWrapper2.kt | 7 +++---- .../crypto/store/db/migration/MigrateCryptoTo017.kt | 8 ++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index 4b37e97e0d..600fcb1003 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -107,11 +107,11 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { /** * Export the inbound group session keys. - * @param sharedHistory the flag that indicates whether or not the session can be shared * @param index the index to export. If null, the first known index will be used + * * @return the inbound group session as MegolmSessionData if the operation succeeds */ - fun exportKeys(sharedHistory: Boolean = false, index: Long? = null): MegolmSessionData? { + fun exportKeys(index: Long? = null): MegolmSessionData? { return try { if (null == forwardingCurve25519KeyChain) { forwardingCurve25519KeyChain = ArrayList() @@ -133,8 +133,7 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable { roomId = roomId, sessionId = safeOlmInboundGroupSession.sessionIdentifier(), sessionKey = safeOlmInboundGroupSession.export(wantedIndex), - algorithm = MXCRYPTO_ALGORITHM_MEGOLM, - sharedHistory = sharedHistory + algorithm = MXCRYPTO_ALGORITHM_MEGOLM ) } catch (e: Exception) { Timber.e(e, "## export() : senderKey $senderKey failed") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt index afb0e0186d..8904c412cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator @@ -42,6 +43,13 @@ internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17 it.setBoolean(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, false) } + realm.schema.get("OutboundGroupSessionInfoEntity") + ?.addField(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform { + // We don't have access to the session database to check for the state here and set the good value. + // But for now as it's behind a lab flag, will set to false and force initial sync when enabled + it.setBoolean(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, false) + } + realm.schema.get("CryptoMetadataEntity") ?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java) ?.transform { obj -> From 90a4e71b067428c4ffa746df2889fc8c12daa285 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 1 Jul 2022 14:30:21 +0200 Subject: [PATCH 38/40] update flacky test --- .../sdk/session/space/SpaceHierarchyTest.kt | 297 +++++++++--------- 1 file changed, 152 insertions(+), 145 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 63ca963479..80020665f8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -20,7 +20,6 @@ import android.util.Log import androidx.lifecycle.Observer import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder import org.junit.Ignore @@ -62,47 +61,40 @@ class SpaceHierarchyTest : InstrumentedTest { val spaceName = "My Space" val topic = "A public space for test" var spaceId = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { spaceId = session.spaceService().createSpace(spaceName, topic, null, true) - it.countDown() } val syncedSpace = session.spaceService().getSpace(spaceId) var roomId = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" }) - it.countDown() } val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { syncedSpace!!.addChildren(roomId, viaServers, null, true) - it.countDown() } - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers) - it.countDown() } - Thread.sleep(9000) - - val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents - val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true } - - parents?.forEach { - Log.d("## TEST", "parent : $it") + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents + val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true } + parents?.forEach { + Log.d("## TEST", "parent : $it") + } + parents?.size == 1 && + parents.first().roomSummary?.name == spaceName && + canonicalParents?.size == 1 && + canonicalParents.first().roomSummary?.name == spaceName + } } - - assertNotNull(parents) - assertEquals(1, parents!!.size) - assertEquals(spaceName, parents.first().roomSummary?.name) - - assertNotNull(canonicalParents) - assertEquals(1, canonicalParents!!.size) - assertEquals(spaceName, canonicalParents.first().roomSummary?.name) } // @Test @@ -173,52 +165,55 @@ class SpaceHierarchyTest : InstrumentedTest { // } @Test - fun testFilteringBySpace() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> + fun testFilteringBySpace() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) /* val spaceBInfo = */ createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) + commonTestHelper, + session, "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) ) val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) // add C as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) - it.countDown() } // Create orphan rooms var orphan1 = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" }) - it.countDown() } var orphan2 = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" }) - it.countDown() } val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) @@ -240,10 +235,9 @@ class SpaceHierarchyTest : InstrumentedTest { assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" }) // Add a non canonical child and check that it does not appear as orphan - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" }) spaceA!!.addChildren(a3, viaServers, null, false) - it.countDown() } Thread.sleep(6_000) @@ -255,37 +249,39 @@ class SpaceHierarchyTest : InstrumentedTest { @Test @Ignore("This test will be ignored until it is fixed") - fun testBreakCycle() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> + fun testBreakCycle() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) // add C as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) - it.countDown() } // add back A as subspace of C - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId) spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true) - it.countDown() } // A -> C -> A @@ -300,37 +296,46 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test - fun testLiveFlatChildren() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> + fun testLiveFlatChildren() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, + "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) val spaceBInfo = createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) + commonTestHelper, + session, + "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) ) // add B as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - runBlocking { + commonTestHelper.runBlockingTest { spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true) session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers) } val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, + "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) commonTestHelper.waitWithLatch { latch -> @@ -348,13 +353,13 @@ class SpaceHierarchyTest : InstrumentedTest { } } + flatAChildren.observeForever(childObserver) + // add C as subspace of B val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) // C1 and C2 should be in flatten child of A now - - flatAChildren.observeForever(childObserver) } // Test part one of the rooms @@ -374,10 +379,10 @@ class SpaceHierarchyTest : InstrumentedTest { } } - // part from b room - session.roomService().leaveRoom(bRoomId) // The room should have disapear from flat children flatAChildren.observeForever(childObserver) + // part from b room + session.roomService().leaveRoom(bRoomId) } commonTestHelper.signOutAndClose(session) } @@ -388,6 +393,7 @@ class SpaceHierarchyTest : InstrumentedTest { ) private fun createPublicSpace( + commonTestHelper: CommonTestHelper, session: Session, spaceName: String, childInfo: List> @@ -395,29 +401,27 @@ class SpaceHierarchyTest : InstrumentedTest { ): TestSpaceCreationResult { var spaceId = "" var roomIds: List = emptyList() - runSessionTest(context()) { commonTestHelper -> - commonTestHelper.waitWithLatch { latch -> - spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) - val syncedSpace = session.spaceService().getSpace(spaceId) - val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + commonTestHelper.runBlockingTest { + spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - roomIds = childInfo.map { entry -> - session.roomService().createRoom(CreateRoomParams().apply { name = entry.first }) + roomIds = childInfo.map { entry -> + session.roomService().createRoom(CreateRoomParams().apply { name = entry.first }) + } + roomIds.forEachIndexed { index, roomId -> + syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) + val canonical = childInfo[index].third + if (canonical != null) { + session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) } - roomIds.forEachIndexed { index, roomId -> - syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) - val canonical = childInfo[index].third - if (canonical != null) { - session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) - } - } - latch.countDown() } } return TestSpaceCreationResult(spaceId, roomIds) } private fun createPrivateSpace( + commonTestHelper: CommonTestHelper, session: Session, spaceName: String, childInfo: List> @@ -425,34 +429,31 @@ class SpaceHierarchyTest : InstrumentedTest { ): TestSpaceCreationResult { var spaceId = "" var roomIds: List = emptyList() - runSessionTest(context()) { commonTestHelper -> - commonTestHelper.waitWithLatch { latch -> - spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) - val syncedSpace = session.spaceService().getSpace(spaceId) - val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - roomIds = - childInfo.map { entry -> - val homeServerCapabilities = session - .homeServerCapabilitiesService() - .getHomeServerCapabilities() - session.roomService().createRoom(CreateRoomParams().apply { - name = entry.first - this.featurePreset = RestrictedRoomPreset( - homeServerCapabilities, - listOf( - RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) - ) - ) - }) - } - roomIds.forEachIndexed { index, roomId -> - syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) - val canonical = childInfo[index].third - if (canonical != null) { - session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) + commonTestHelper.runBlockingTest { + spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + roomIds = + childInfo.map { entry -> + val homeServerCapabilities = session + .homeServerCapabilitiesService() + .getHomeServerCapabilities() + session.roomService().createRoom(CreateRoomParams().apply { + name = entry.first + this.featurePreset = RestrictedRoomPreset( + homeServerCapabilities, + listOf( + RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) + ) + ) + }) } + roomIds.forEachIndexed { index, roomId -> + syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) + val canonical = childInfo[index].third + if (canonical != null) { + session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) } - latch.countDown() } } return TestSpaceCreationResult(spaceId, roomIds) @@ -463,25 +464,31 @@ class SpaceHierarchyTest : InstrumentedTest { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) /* val spaceAInfo = */ createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) + commonTestHelper, + session, "SpaceA", + listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) ) val spaceBInfo = createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) + commonTestHelper, + session, "SpaceB", + listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) ) val spaceCInfo = createPublicSpace( - session, "SpaceC", listOf( - Triple("C1", true /*auto-join*/, true/*canonical*/), - Triple("C2", true, true) - ) + commonTestHelper, + session, "SpaceC", + listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + ) ) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") @@ -490,7 +497,6 @@ class SpaceHierarchyTest : InstrumentedTest { runBlocking { val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) - Thread.sleep(6_000) } // Thread.sleep(4_000) @@ -501,11 +507,12 @@ class SpaceHierarchyTest : InstrumentedTest { // + C // + c1, c2 - val rootSpaces = commonTestHelper.runBlockingTest { - session.spaceService().getRootSpaceSummaries() + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val rootSpaces = commonTestHelper.runBlockingTest { session.spaceService().getRootSpaceSummaries() } + rootSpaces.size == 2 + } } - - assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) } @Test @@ -514,10 +521,12 @@ class SpaceHierarchyTest : InstrumentedTest { val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true)) val spaceAInfo = createPrivateSpace( - aliceSession, "Private Space A", listOf( - Triple("General", true /*suggested*/, true/*canonical*/), - Triple("Random", true, true) - ) + commonTestHelper, + aliceSession, "Private Space A", + listOf( + Triple("General", true /*suggested*/, true/*canonical*/), + Triple("Random", true, true) + ) ) commonTestHelper.runBlockingTest { @@ -529,10 +538,9 @@ class SpaceHierarchyTest : InstrumentedTest { } var bobRoomId = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" }) bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId) - it.countDown() } commonTestHelper.runBlockingTest { @@ -545,9 +553,8 @@ class SpaceHierarchyTest : InstrumentedTest { } } - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) - it.countDown() } commonTestHelper.waitWithLatch { latch -> From 6fd99dc302d0f6d2201a21d796bdf23bdfa02cbd Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 1 Jul 2022 15:56:03 +0200 Subject: [PATCH 39/40] resist ConnectivityManager$TooManyRequestsException --- .../android/sdk/session/space/SpaceCreationTest.kt | 10 ++++------ .../sdk/internal/network/NetworkCallbackStrategy.kt | 10 +++++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 38136ff5ce..2cd579df24 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -56,19 +56,17 @@ class SpaceCreationTest : InstrumentedTest { val roomName = "My Space" val topic = "A public space for test" var spaceId: String = "" - commonTestHelper.waitWithLatch { + commonTestHelper.runBlockingTest { spaceId = session.spaceService().createSpace(roomName, topic, null, true) - // wait a bit to let the summary update it self :/ - it.countDown() } - Thread.sleep(4_000) - val syncedSpace = session.spaceService().getSpace(spaceId) commonTestHelper.waitWithLatch { commonTestHelper.retryPeriodicallyWithLatch(it) { - syncedSpace?.asRoom()?.roomSummary()?.name != null + session.spaceService().getSpace(spaceId)?.asRoom()?.roomSummary()?.name != null } } + + val syncedSpace = session.spaceService().getSpace(spaceId) assertEquals("Room name should be set", roomName, syncedSpace?.asRoom()?.roomSummary()?.name) assertEquals("Room topic should be set", topic, syncedSpace?.asRoom()?.roomSummary()?.topic) // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt index f75fb01746..6ac6ff9797 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt @@ -70,7 +70,15 @@ internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Con override fun register(hasChanged: () -> Unit) { hasChangedCallback = hasChanged - conn.registerDefaultNetworkCallback(networkCallback) + // Add a try catch for safety + // TODO: It happens when running all tests in CI, at some points we reach a limit here causing TooManyRequestsException + // and crashing the sync thread. We might have problem here, would need some investigation + // for now adding a catch to allow CI to continue running + try { + conn.registerDefaultNetworkCallback(networkCallback) + } catch (t: Throwable) { + Timber.e(t, "Unable to register default network callback") + } } override fun unregister() { From d281f9dde5d36e4708c7d713152bfd89e92f7954 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 1 Jul 2022 16:07:03 +0200 Subject: [PATCH 40/40] use XXX not TODO --- .../android/sdk/internal/network/NetworkCallbackStrategy.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt index 6ac6ff9797..90d2719e25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt @@ -71,7 +71,7 @@ internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Con override fun register(hasChanged: () -> Unit) { hasChangedCallback = hasChanged // Add a try catch for safety - // TODO: It happens when running all tests in CI, at some points we reach a limit here causing TooManyRequestsException + // XXX: It happens when running all tests in CI, at some points we reach a limit here causing TooManyRequestsException // and crashing the sync thread. We might have problem here, would need some investigation // for now adding a catch to allow CI to continue running try {