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 88ba1453de..8bc10e7e7a 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 @@ -45,6 +45,7 @@ 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.getTimelineEvent 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 @@ -83,7 +84,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: } @OptIn(ExperimentalCoroutinesApi::class) - internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) { + internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) { val testHelper = CommonTestHelper(context, cryptoConfig) val cryptoTestHelper = CryptoTestHelper(testHelper) return try { @@ -209,18 +210,28 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: } }) return messageSent.await() - // return withTimeout(TestConstants.timeOutMillis) { messageSent.await() } + // return withTimeout(TestConstants.timeOutMillis) { messageSent.await() } } suspend fun ensureMessage(room: Room, eventId: String, block: ((event: TimelineEvent) -> Boolean)) { Log.v("#E2E TEST", "ensureMessage room:${room.roomId} <$eventId>") - val timeline = room.timelineService().createTimeline(null, TimelineSettings(60)) + val timeline = room.timelineService().createTimeline(null, TimelineSettings(60, buildReadReceipts = false)) + + // check if not already there? + val existing = withContext(Dispatchers.Main) { + room.getTimelineEvent(eventId) + } + if (existing != null && block(existing)) return Unit.also { + Log.v("#E2E TEST", "Already received") + } + val messageSent = CompletableDeferred() timeline.addListener(object : Timeline.Listener { override fun onNewTimelineEvents(eventIds: List) { Log.v("#E2E TEST", "onNewTimelineEvents snapshot is $eventIds") } + override fun onTimelineUpdated(snapshot: List) { val success = timeline.getSnapshot() // .filter { it.root.getClearType() == EventType.MESSAGE } @@ -243,13 +254,13 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: timeline.start() - return messageSent.await() + return messageSent.await() // withTimeout(TestConstants.timeOutMillis) { // messageSent.await() // } } - fun ensureMessagePromise(room: Room, eventId: String, block: ((event: TimelineEvent) -> Boolean)): CompletableDeferred { + fun ensureMessagePromise(room: Room, eventId: String, block: ((event: TimelineEvent) -> Boolean)): CompletableDeferred { val timeline = room.timelineService().createTimeline(null, TimelineSettings(60)) timeline.start() val messageSent = CompletableDeferred() @@ -334,11 +345,11 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: } suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, roomID: String) { - retryPeriodically { + retryWithBackoff { val roomSummary = otherSession.getRoomSummary(roomID) (roomSummary != null && roomSummary.membership == Membership.INVITE).also { if (it) { - Log.v("# TEST", "${otherSession.myUserId} can see the invite") + Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite") } } } @@ -354,7 +365,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: } Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") - retryPeriodically { + retryWithBackoff { val roomSummary = otherSession.getRoomSummary(roomID) roomSummary != null && roomSummary.membership == Membership.JOIN } 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 75881fb628..b47c3cbbd5 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 @@ -136,7 +136,9 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { suspend fun inviteNewUsersAndWaitForThemToJoin(session: Session, roomId: String, usernames: List): List { val newSessions = usernames.map { username -> testHelper.createAccount(username, SessionTestParams(true)).also { - it.cryptoService().enableKeyGossiping(false) + if (it.cryptoService().supportsDisablingKeyGossiping()) { + it.cryptoService().enableKeyGossiping(false) + } } } @@ -560,7 +562,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { } catch (error: MXCryptoError) { // nop } - Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}") + Log.v("#E2E TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}") event.getClearType() == EventType.MESSAGE && messagesText[index] == event.getClearContent()?.toModel()?.body } 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 index 06e546ff43..5d0a7e532d 100644 --- 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 @@ -20,6 +20,7 @@ import android.util.Log import androidx.test.filters.LargeTest import org.amshove.kluent.internal.assertEquals import org.junit.Assert +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -28,6 +29,7 @@ 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.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent 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 @@ -58,15 +60,15 @@ class E2EShareKeysConfigTest : InstrumentedTest { enableEncryption() }) - commonTestHelper.retryPeriodically { + commonTestHelper.retryWithBackoff { aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true } val roomAlice = aliceSession.roomService().getRoom(roomId)!! // send some messages - val withSession1 = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1) + val withSession1 = commonTestHelper.sendMessageInRoom(roomAlice, "Hello") aliceSession.cryptoService().discardOutboundSession(roomId) - val withSession2 = commonTestHelper.sendTextMessage(roomAlice, "World", 1) + val withSession2 = commonTestHelper.sendMessageInRoom(roomAlice, "World") // Create bob account val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(withInitialSync = true)) @@ -78,7 +80,7 @@ class E2EShareKeysConfigTest : InstrumentedTest { // Bob has join but should not be able to decrypt history cryptoTestHelper.ensureCannotDecrypt( - withSession1.map { it.eventId } + withSession2.map { it.eventId }, + listOf(withSession1, withSession2), bobSession, roomId ) @@ -86,44 +88,53 @@ class E2EShareKeysConfigTest : InstrumentedTest { // We don't need bob anymore commonTestHelper.signOutAndClose(bobSession) - // Now let's enable history key sharing on alice side - aliceSession.cryptoService().enableShareKeyOnInvite(true) + if (aliceSession.cryptoService().supportsShareKeysOnInvite()) { + // 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) + // let's add a new message first + val afterFlagOn = commonTestHelper.sendMessageInRoom(roomAlice, "After") - // 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")!! - ) + // Worth nothing to check that the session was rotated + Assert.assertNotEquals( + "Session should have been rotated", + aliceSession.roomService().getRoom(roomId)?.getTimelineEvent(withSession1)?.root?.content?.get("session_id")!!, + aliceSession.roomService().getRoom(roomId)?.getTimelineEvent(afterFlagOn)?.root?.content?.get("session_id")!! + ) - // Invite a new user - val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) + // Invite a new user + val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true)) - // Let alice invite sam - roomAlice.membershipService().invite(samSession.myUserId) + // Let alice invite sam + roomAlice.membershipService().invite(samSession.myUserId) - commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId) + 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 - ) + // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session + cryptoTestHelper.ensureCannotDecrypt( + listOf(withSession1, withSession2), + samSession, + roomId + ) - cryptoTestHelper.ensureCanDecrypt( - afterFlagOn.map { it.eventId }, - samSession, - roomId, - afterFlagOn.map { it.root.getClearContent()?.get("body") as String }) + cryptoTestHelper.ensureCanDecrypt( + listOf(afterFlagOn), + samSession, + roomId, + listOf(aliceSession.roomService().getRoom(roomId)?.getTimelineEvent(afterFlagOn)?.root?.getClearContent()?.get("body") as String) + ) + } } @Test fun ifSharingDisabledOnAliceSideBobShouldNotShareAliceHistory() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED) + + Assume.assumeTrue("Shared key on invite needed to test this", + testData.firstSession.cryptoService().supportsShareKeysOnInvite() + ) + val aliceSession = testData.firstSession.also { it.cryptoService().enableShareKeyOnInvite(false) } @@ -151,6 +162,11 @@ class E2EShareKeysConfigTest : InstrumentedTest { @Test fun ifSharingEnabledOnAliceSideBobShouldShareAliceHistory() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED) + + Assume.assumeTrue("Shared key on invite needed to test this", + testData.firstSession.cryptoService().supportsShareKeysOnInvite() + ) + val aliceSession = testData.firstSession.also { it.cryptoService().enableShareKeyOnInvite(true) } @@ -193,6 +209,11 @@ class E2EShareKeysConfigTest : InstrumentedTest { @Test fun testBackupFlagIsCorrect() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + Assume.assumeTrue("Shared key on invite needed to test this", + aliceSession.cryptoService().supportsShareKeysOnInvite() + ) + aliceSession.cryptoService().enableShareKeyOnInvite(false) val roomId = aliceSession.roomService().createRoom(CreateRoomParams().apply { historyVisibility = RoomHistoryVisibility.SHARED @@ -206,9 +227,10 @@ class E2EShareKeysConfigTest : InstrumentedTest { val roomAlice = aliceSession.roomService().getRoom(roomId)!! // send some messages - val notSharableMessage = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1) + val notSharableMessage = commonTestHelper.sendMessageInRoom(roomAlice, "Hello") + aliceSession.cryptoService().enableShareKeyOnInvite(true) - val sharableMessage = commonTestHelper.sendTextMessage(roomAlice, "World", 1) + val sharableMessage = commonTestHelper.sendMessageInRoom(roomAlice, "World") Log.v("#E2E TEST", "Create and start key backup for bob ...") val keysBackupService = aliceSession.cryptoService().keysBackupService() @@ -224,6 +246,7 @@ class E2EShareKeysConfigTest : InstrumentedTest { commonTestHelper.signOutAndClose(aliceSession) val newAliceSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + newAliceSession.cryptoService().enableShareKeyOnInvite(true) newAliceSession.cryptoService().keysBackupService().let { kbs -> @@ -251,15 +274,16 @@ class E2EShareKeysConfigTest : InstrumentedTest { // 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 }, + listOf(notSharableMessage), samSession, roomId ) cryptoTestHelper.ensureCanDecrypt( - sharableMessage.map { it.eventId }, + listOf(sharableMessage), samSession, roomId, - sharableMessage.map { it.root.getClearContent()?.get("body") as String }) + listOf(roomAlice.getTimelineEvent(sharableMessage)?.root?.getClearContent()?.get("body") as String) + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt index 226277fef2..67ea90fb97 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt @@ -53,15 +53,13 @@ class E2eeConfigTest : InstrumentedTest { val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! - val sentMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first() + val sentMessage = testHelper.sendMessageInRoom(roomAlicePOV, "you are blocked") val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! // ensure other received - testHelper.retryPeriodically { - roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null - } + testHelper.ensureMessage(roomBobPOV, sentMessage) { true } - cryptoTestHelper.ensureCannotDecrypt(listOf(sentMessage.eventId), cryptoTestData.secondSession!!, cryptoTestData.roomId) + cryptoTestHelper.ensureCannotDecrypt(listOf(sentMessage), cryptoTestData.secondSession!!, cryptoTestData.roomId) } @Test @@ -77,19 +75,20 @@ class E2eeConfigTest : InstrumentedTest { val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! - val sentMessage = testHelper.sendTextMessage(roomAlicePOV, "you can read", 1).first() + val sentMessage = testHelper.sendMessageInRoom(roomAlicePOV, "you can read") val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! // ensure other received - testHelper.retryPeriodically { - roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null - } + + testHelper.ensureMessage(roomBobPOV, sentMessage) { true } cryptoTestHelper.ensureCanDecrypt( - listOf(sentMessage.eventId), + listOf(sentMessage), cryptoTestData.secondSession!!, cryptoTestData.roomId, - listOf(sentMessage.getLastMessageContent()!!.body) + listOf( + roomBobPOV.timelineService().getTimelineEvent(sentMessage)?.getLastMessageContent()!!.body + ) ) } 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 8839bf6d23..204a1ec18a 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 @@ -75,8 +75,12 @@ class E2eeSanityTests : InstrumentedTest { val e2eRoomID = cryptoTestData.roomId val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! // we want to disable key gossiping to just check initial sending of keys - aliceSession.cryptoService().enableKeyGossiping(false) - cryptoTestData.secondSession?.cryptoService()?.enableKeyGossiping(false) + if (aliceSession.cryptoService().supportsDisablingKeyGossiping()) { + aliceSession.cryptoService().enableKeyGossiping(false) + } + if (cryptoTestData.secondSession?.cryptoService()?.supportsDisablingKeyGossiping() == true) { + cryptoTestData.secondSession?.cryptoService()?.enableKeyGossiping(false) + } // add some more users and invite them val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu") 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 3e57a56e18..1517e965c5 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 @@ -22,6 +22,7 @@ import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.internal.assertNotEquals import org.junit.Assert +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -79,9 +80,9 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { runCryptoTest(context()) { cryptoTestHelper, testHelper -> val aliceMessageText = "Hello Bob, I am Alice!" val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility) - val e2eRoomID = cryptoTestData.roomId + Assume.assumeTrue(cryptoTestData.firstSession.cryptoService().supportsShareKeysOnInvite()) // Alice val aliceSession = cryptoTestData.firstSession.also { it.cryptoService().enableShareKeyOnInvite(true) @@ -251,6 +252,8 @@ class E2eeShareKeysHistoryTest : InstrumentedTest { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, initRoomHistoryVisibility) val e2eRoomID = cryptoTestData.roomId + Assume.assumeTrue(cryptoTestData.firstSession.cryptoService().supportsShareKeysOnInvite()) + // Alice val aliceSession = cryptoTestData.firstSession.also { it.cryptoService().enableShareKeyOnInvite(true) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index aafecc9a62..12c63edf92 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -24,6 +24,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -195,11 +196,15 @@ class XSigningTest : InstrumentedTest { @Test fun testWarnOnCrossSigningReset() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession + // Remove when https://github.com/matrix-org/matrix-rust-sdk/issues/1129 + Assume.assumeTrue("Not yet supported by rust", aliceSession.cryptoService().name() != "rust-sdk") + val aliceAuthParams = UserPasswordAuth( user = aliceSession.myUserId, password = TestConstants.PASSWORD diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 314dbf811d..d7c3f2ea02 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -25,6 +25,7 @@ import junit.framework.TestCase.assertTrue import org.amshove.kluent.shouldBeEqualTo import org.junit.Assert import org.junit.Assert.assertNull +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -59,6 +60,8 @@ class KeyShareTests : InstrumentedTest { fun test_DoNotSelfShareIfNotTrusted() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + + Assume.assumeTrue("Not supported", aliceSession.cryptoService().supportKeyRequestInspection()) Log.v("#TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}") // Create an encrypted room and add a message @@ -70,8 +73,9 @@ class KeyShareTests : InstrumentedTest { ) val room = aliceSession.getRoom(roomId) assertNotNull(room) - Thread.sleep(4_000) - assertTrue(room?.roomCryptoService()?.isEncrypted() == true) + commonTestHelper.retryWithBackoff { + room?.roomCryptoService()?.isEncrypted() == true + } val sentEvent = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first() val sentEventId = sentEvent.eventId @@ -207,6 +211,9 @@ class KeyShareTests : InstrumentedTest { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = testData.firstSession + + Assume.assumeTrue("Not supported", aliceSession.cryptoService().supportKeyRequestInspection()) + val roomFromAlice = aliceSession.getRoom(testData.roomId)!! val bobSession = testData.secondSession!! @@ -239,6 +246,9 @@ class KeyShareTests : InstrumentedTest { val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true) val aliceSession = testData.firstSession + + Assume.assumeTrue("Not supported", aliceSession.cryptoService().supportKeyRequestInspection()) + val roomFromAlice = aliceSession.getRoom(testData.roomId)!! val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) @@ -274,6 +284,9 @@ class KeyShareTests : InstrumentedTest { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = testData.firstSession + + Assume.assumeTrue("Not supported", aliceSession.cryptoService().supportKeyRequestInspection()) + val bobSession = testData.secondSession!! val roomFromBob = bobSession.getRoom(testData.roomId)!! @@ -386,6 +399,9 @@ class KeyShareTests : InstrumentedTest { ) { cryptoTestHelper, commonTestHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = testData.firstSession + + Assume.assumeTrue("Not supported", aliceSession.cryptoService().supportKeyRequestInspection()) + val bobSession = testData.secondSession!! val roomFromBob = bobSession.getRoom(testData.roomId)!! diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index bd813002b8..31ce8c30ed 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -20,6 +20,7 @@ import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -153,6 +154,7 @@ class WithHeldTests : InstrumentedTest { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession + Assume.assumeTrue("Not supported", aliceSession.cryptoService().supportKeyRequestInspection()) val bobSession = testData.secondSession!! val aliceInterceptor = testHelper.getTestInterceptor(aliceSession) @@ -222,6 +224,7 @@ class WithHeldTests : InstrumentedTest { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession + Assume.assumeTrue("Not supported by rust sdk", aliceSession.cryptoService().supportsForwardedKeyWiththeld()) val bobSession = testData.secondSession!! val roomAlicePov = aliceSession.getRoom(testData.roomId)!! @@ -236,19 +239,9 @@ class WithHeldTests : InstrumentedTest { // initialize to force request keys if missing cryptoTestHelper.initializeCrossSigning(bobSecondSession) -// // wait until alice downloaded the new device -// testHelper.retryWithBackoff { -// aliceSession.cryptoService().getUserDevices(bobSession.myUserId).any { it.deviceId == bobSecondSession.sessionParams.deviceId} -// } -// -// // Trust bob second device from Alice POV -// aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId) -// -// // wait until bob downloaded alice device -// testHelper.retryWithBackoff { -// bobSecondSession.cryptoService().getUserDevices(aliceSession.myUserId).any { it.deviceId == aliceSession.sessionParams.deviceId} -// } -// bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId) + // Trust bob second device from Alice POV + aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId) + bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId) var sessionId: String? = null // Check that the @@ -264,18 +257,10 @@ class WithHeldTests : InstrumentedTest { timeLineEvent != null } - - mustFail( - message = "This session should not be able to decrypt", - failureBlock = { failure -> - val type = (failure as MXCryptoError.Base).errorType - val technicalMessage = failure.technicalMessage - Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type) - Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage) - } - ) { - val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) - bobSecondSession.cryptoService().decryptEvent(timeLineEvent!!.root, "") + // Check that bob second session requested the key + testHelper.retryPeriodically { + val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!) + wc?.code == WithHeldCode.UNAUTHORISED } // // Check that bob second session requested the key // testHelper.retryPeriodically { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt index 0dfecffbde..7babfc1834 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt @@ -21,6 +21,7 @@ import org.amshove.kluent.internal.assertFailsWith import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.fail +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,6 +44,9 @@ class ReplayAttackTest : InstrumentedTest { // Alice val aliceSession = cryptoTestData.firstSession + + // Until https://github.com/matrix-org/matrix-rust-sdk/issues/397 + Assume.assumeTrue("Not yet supported by rust", cryptoTestData.firstSession.cryptoService().name() != "rust-sdk") val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! // Bob diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index d87416884f..4ef648a6a3 100755 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -194,6 +194,15 @@ internal class DefaultCryptoService @Inject constructor( private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) + override fun name() = "kotlin-sdk" + + override fun supportsKeyWithheld() = true + override fun supportKeyRequestInspection() = true + + override fun supportsDisablingKeyGossiping() = true + + override fun supportsForwardedKeyWiththeld() = true + override suspend fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) @@ -1395,7 +1404,7 @@ internal class DefaultCryptoService @Inject constructor( throw IllegalArgumentException("Missing algorithm") } - (alg as? IMXGroupEncryption)?.preshareKey(userIds) + (alg as? IMXGroupEncryption)?.preshareKey(userIds) } } 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 326e4ac2a1..043d0a29f5 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 @@ -48,6 +48,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator interface CryptoService { + fun name(): String fun verificationService(): VerificationService fun crossSigningService(): CrossSigningService @@ -80,6 +81,8 @@ interface CryptoService { fun getLiveGlobalCryptoConfig(): LiveData + fun supportsDisablingKeyGossiping(): Boolean + /** * Enable or disable key gossiping. * Default is true. @@ -186,6 +189,8 @@ interface CryptoService { fun addNewSessionListener(newSessionListener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) + fun supportKeyRequestInspection(): Boolean + fun getOutgoingRoomKeyRequests(): List fun getOutgoingRoomKeyRequestsPaged(): LiveData> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo052.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo052.kt index 1638c2334d..9ec9df1db1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo052.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo052.kt @@ -28,4 +28,3 @@ internal class MigrateSessionTo052(realm: DynamicRealm) : RealmMigrator(realm, 5 ?.setNullable(EventEntityFields.IS_VERIFICATION_STATE_DIRTY, true) } } - diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index 7d0126a32e..a264eabed3 100644 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -43,6 +43,8 @@ import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction 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.EventType +import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse @@ -439,6 +441,19 @@ internal class OlmMachine @Inject constructor( if (event.roomId.isNullOrBlank()) { throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) } + if (event.isRedacted()) { + // we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm + // Workaround until https://github.com/matrix-org/matrix-rust-sdk/issues/1642 + return@withContext MXEventDecryptionResult( + clearEvent = mapOf( + "room_id" to event.roomId, + "type" to EventType.MESSAGE, + "content" to emptyMap(), + "unsigned" to event.unsignedData.toContent() + ) + ) + } + val serializedEvent = adapter.toJson(event) val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId, false, false) @@ -493,7 +508,7 @@ internal class OlmMachine @Inject constructor( ShieldColor.RED -> { when (this.message) { "Encrypted by an unverified device." -> MessageVerificationState.UN_SIGNED_DEVICE - "Encrypted by a device not verified by its owner." -> MessageVerificationState.UN_SIGNED_DEVICE_OF_VERIFIED_USER + "Encrypted by a device not verified by its owner." -> MessageVerificationState.UN_SIGNED_DEVICE "Encrypted by an unknown or deleted device." -> MessageVerificationState.UNKNOWN_DEVICE else -> MessageVerificationState.UN_SIGNED_DEVICE } diff --git a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt index 64ce0fcc14..3f2b2a6ebe 100755 --- a/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt +++ b/matrix-sdk-android/src/rustCrypto/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt @@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.crypto.model.TrailType 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.EventType @@ -141,6 +140,8 @@ internal class RustCryptoService @Inject constructor( private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) + override fun name() = "rust-sdk" + override suspend fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) @@ -510,8 +511,8 @@ internal class RustCryptoService @Inject constructor( // } catch (throwable: Throwable) { // Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") // } finally { - val userIds = getRoomUserIds(roomId) - setEncryptionInRoom(roomId, event.content?.toModel(), userIds) + val userIds = getRoomUserIds(roomId) + setEncryptionInRoom(roomId, event.content?.toModel(), userIds) // } // } } @@ -559,7 +560,7 @@ internal class RustCryptoService @Inject constructor( // know what other servers are in the room at the time they've been invited. // They therefore will not send device updates if a user logs in whilst // their state is invite. - olmMachine.updateTrackedUsers(listOf(userId)) + olmMachine.updateTrackedUsers(listOf(userId)) } else { // nop } @@ -651,8 +652,8 @@ internal class RustCryptoService @Inject constructor( this.verificationService.onEvent(null, event) } } - liveEventManager.get().dispatchOnLiveToDevice(event) - } + liveEventManager.get().dispatchOnLiveToDevice(event) + } } /** @@ -717,12 +718,14 @@ internal class RustCryptoService @Inject constructor( return cryptoStore.getLiveGlobalCryptoConfig() } + // Until https://github.com/matrix-org/matrix-rust-sdk/issues/1364 + override fun supportsDisablingKeyGossiping() = false override fun enableKeyGossiping(enable: Boolean) { - cryptoStore.enableKeyGossiping(enable) + if (!enable) throw UnsupportedOperationException("Not supported by rust") } override fun isKeyGossipingEnabled(): Boolean { - return cryptoStore.isKeyGossipingEnabled() + return true } override fun supportsShareKeysOnInvite() = false @@ -730,10 +733,9 @@ internal class RustCryptoService @Inject constructor( override fun supportsKeyWithheld() = true override fun supportsForwardedKeyWiththeld() = false - override fun enableShareKeyOnInvite(enable: Boolean) { if (enable && !supportsShareKeysOnInvite()) { - throw java.lang.UnsupportedOperationException("Enable share key on invite not implemented in rust"); + throw java.lang.UnsupportedOperationException("Enable share key on invite not implemented in rust") } } @@ -844,19 +846,20 @@ internal class RustCryptoService @Inject constructor( return "DefaultCryptoService of $myUserId ($deviceId)" } + // Until https://github.com/matrix-org/matrix-rust-sdk/issues/701 + // https://github.com/matrix-org/matrix-rust-sdk/issues/702 + override fun supportKeyRequestInspection() = false override fun getOutgoingRoomKeyRequests(): List { - return cryptoStore.getOutgoingRoomKeyRequests() + throw UnsupportedOperationException("Not supported by rust") } override fun getOutgoingRoomKeyRequestsPaged(): LiveData> { - return cryptoStore.getOutgoingRoomKeyRequestsPaged() + throw UnsupportedOperationException("Not supported by rust") +// return cryptoStore.getOutgoingRoomKeyRequestsPaged() } override fun getIncomingRoomKeyRequestsPaged(): LiveData> { - return cryptoStore.getGossipingEventsTrail(TrailType.IncomingKeyRequest) { - IncomingRoomKeyRequest.fromEvent(it) - ?: IncomingRoomKeyRequest(localCreationTimestamp = 0L) - } + throw UnsupportedOperationException("Not supported by rust") } override suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) { @@ -864,25 +867,23 @@ internal class RustCryptoService @Inject constructor( } override fun getIncomingRoomKeyRequests(): List { - return emptyList() + throw UnsupportedOperationException("Not supported by rust") } override fun getGossipingEventsTrail(): LiveData> { - return cryptoStore.getGossipingEventsTrail() + throw UnsupportedOperationException("Not supported by rust") } override fun getGossipingEvents(): List { - return cryptoStore.getGossipingEvents() + throw UnsupportedOperationException("Not supported by rust") } override fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap { - // TODO not exposed in rust? - return cryptoStore.getSharedWithInfo(roomId, sessionId) + throw UnsupportedOperationException("Not supported by rust") } override fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? { - // TODO not exposed in rust. - return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) + throw UnsupportedOperationException("Not supported by rust") } override fun logDbUsageInfo() { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index df0879da24..f1b01b2909 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -242,6 +242,9 @@ class BugReporter @Inject constructor( activeSessionHolder.getSafeActiveSession() ?.takeIf { !mIsCancelled && withKeyRequestHistory } ?.cryptoService() + ?.takeIf { + it.supportKeyRequestInspection() + } ?.getGossipingEvents() ?.let { GossipingEventsSerializer().serialize(it) } ?.toByteArray() 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 02bb79ac51..50a53b6e77 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 @@ -187,6 +187,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_PREF_SPACE_CATEGORY = "SETTINGS_PREF_SPACE_CATEGORY" private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY" + const val SETTINGS_DEVELOPER_MODE_KEY_REQUEST_AUDIT_KEY = "SETTINGS_DEVELOPER_MODE_KEY_REQUEST_AUDIT_KEY" private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY" private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index 514f2529e9..0a9350fdef 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -73,6 +73,10 @@ class VectorSettingsAdvancedSettingsFragment : copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken) true } + + findPreference(VectorPreferences.SETTINGS_DEVELOPER_MODE_KEY_REQUEST_AUDIT_KEY)?.apply { + isVisible = session.cryptoService().supportKeyRequestInspection() + } } private fun setupRageShakeSection() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index 576cb1bf6b..88d2c9141d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -52,11 +52,13 @@ class GossipingEventsPaperTrailViewModel @AssistedInject constructor( setState { copy(events = Loading()) } - session.cryptoService().getGossipingEventsTrail() - .asFlow() - .execute { - copy(events = it) - } + if (session.cryptoService().supportKeyRequestInspection()) { + session.cryptoService().getGossipingEventsTrail() + .asFlow() + .execute { + copy(events = it) + } + } } override fun handle(action: EmptyAction) {} diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 6399d54cbb..56609fe26d 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -90,6 +90,7 @@