diff --git a/CHANGES.md b/CHANGES.md index 100aea1bda..717139261f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: - Migrate to binary QR code verification (#994) - Share action is added to room profile and room member profile (#858) - Display avatar in fullscreen (#861) + - Fix some performance issues with crypto Bugfix 🐛: - Account creation: wrongly hints that an email can be used to create an account (#941) @@ -22,6 +23,7 @@ Bugfix 🐛: - Leaving a room creates a stuck "leaving room" loading screen. (#1041) - Fix some invitation handling issues (#1013) - New direct chat: selecting a participant sometimes results in two breadcrumbs (#1022) + - New direct chat: selecting several participants was not adding the room to the direct chats list Translations 🗣: - @@ -29,6 +31,7 @@ Translations 🗣: SDK API changes ⚠: - Get crypto methods through Session.cryptoService() - ProgressListener.onProgress() function will be invoked on the background thread instead of UI thread + - Improve CreateRoomParams API (#1070) Build đŸ§±: - diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt index c44ac9c47b..679ea8f3fe 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt @@ -38,9 +38,7 @@ class AccountCreationTest : InstrumentedTest { fun createAccountTest() { val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) - commonTestHelper.signout(session) - - session.close() + commonTestHelper.signOutAndClose(session) } @Test @@ -50,14 +48,14 @@ class AccountCreationTest : InstrumentedTest { // Log again to the same account val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) - session.close() - session2.close() + commonTestHelper.signOutAndClose(session) + commonTestHelper.signOutAndClose(session2) } @Test fun simpleE2eTest() { val res = cryptoTestHelper.doE2ETestWithAliceInARoom() - res.close() + res.cleanUp(commonTestHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 2e18133071..386787b882 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -28,7 +28,10 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings @@ -113,7 +116,7 @@ class CommonTestHelper(context: Context) { fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List { val sentEvents = ArrayList(nbOfMessages) val latch = CountDownLatch(nbOfMessages) - val onEventSentListener = object : Timeline.Listener { + val timelineListener = object : Timeline.Listener { override fun onTimelineFailure(throwable: Throwable) { } @@ -122,20 +125,26 @@ class CommonTestHelper(context: Context) { } override fun onTimelineUpdated(snapshot: List) { - // TODO Count only new messages? - if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) { - sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE }) + val newMessages = snapshot + .filter { LocalEcho.isLocalEchoId(it.eventId).not() } + .filter { it.root.getClearType() == EventType.MESSAGE } + .filter { it.root.getClearContent().toModel()?.body?.startsWith(message) == true } + + if (newMessages.size == nbOfMessages) { + sentEvents.addAll(newMessages) latch.countDown() } } } val timeline = room.createTimeline(null, TimelineSettings(10)) - timeline.addListener(onEventSentListener) + timeline.start() + timeline.addListener(timelineListener) for (i in 0 until nbOfMessages) { room.sendTextMessage(message + " #" + (i + 1)) } await(latch) - timeline.removeListener(onEventSentListener) + timeline.removeListener(timelineListener) + timeline.dispose() // Check that all events has been created assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong()) @@ -283,11 +292,10 @@ class CommonTestHelper(context: Context) { /** * Clear all provided sessions */ - fun Iterable.close() = forEach { it.close() } + fun Iterable.signOutAndClose() = forEach { signOutAndClose(it) } - fun signout(session: Session) { - val lock = CountDownLatch(1) - session.signOut(true, TestMatrixCallback(lock)) - await(lock) + fun signOutAndClose(session: Session) { + doSync { session.signOut(true, it) } + session.close() } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt index 8ad9f1ec6f..7eea832160 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt @@ -23,9 +23,9 @@ data class CryptoTestData(val firstSession: Session, val secondSession: Session? = null, val thirdSession: Session? = null) { - fun close() { - firstSession.close() - secondSession?.close() - secondSession?.close() + fun cleanUp(testHelper: CommonTestHelper) { + testHelper.signOutAndClose(firstSession) + secondSession?.let { testHelper.signOutAndClose(it) } + thirdSession?.let { testHelper.signOutAndClose(it) } } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index 7368c87252..826c70a63f 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -41,16 +41,15 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue -import java.util.Arrays import java.util.HashMap import java.util.concurrent.CountDownLatch -class CryptoTestHelper(val mTestHelper: CommonTestHelper) { +class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { - val messagesFromAlice: List = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!") - val messagesFromBob: List = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") + private val messagesFromAlice: List = listOf("0 - Hello I'm Alice!", "4 - Go!") + private val messagesFromBob: List = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") - val defaultSessionParams = SessionTestParams(true) + private val defaultSessionParams = SessionTestParams(true) /** * @return alice session @@ -58,34 +57,23 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { fun doE2ETestWithAliceInARoom(): CryptoTestData { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) - var roomId: String? = null - val lock1 = CountDownLatch(1) + val roomId = mTestHelper.doSync { + aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it) + } - aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback(lock1) { - override fun onSuccess(data: String) { - roomId = data - super.onSuccess(data) - } - }) + val room = aliceSession.getRoom(roomId)!! - mTestHelper.await(lock1) - assertNotNull(roomId) + mTestHelper.doSync { + room.enableEncryption(callback = it) + } - val room = aliceSession.getRoom(roomId!!)!! - - val lock2 = CountDownLatch(1) - room.enableEncryption(callback = TestMatrixCallback(lock2)) - mTestHelper.await(lock2) - - return CryptoTestData(aliceSession, roomId!!) + return CryptoTestData(aliceSession, roomId) } /** * @return alice and bob sessions */ fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData { - val statuses = HashMap() - val cryptoTestData = doE2ETestWithAliceInARoom() val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId @@ -94,7 +82,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) - val lock1 = CountDownLatch(2) + val lock1 = CountDownLatch(1) val bobRoomSummariesLive = runBlocking(Dispatchers.Main) { bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) @@ -103,7 +91,6 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (t?.isNotEmpty() == true) { - statuses["onNewRoom"] = "onNewRoom" lock1.countDown() bobRoomSummariesLive.removeObserver(this) } @@ -114,26 +101,20 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { bobRoomSummariesLive.observeForever(newRoomObserver) } - aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback(lock1) { - override fun onSuccess(data: Unit) { - statuses["invite"] = "invite" - super.onSuccess(data) - } - }) + mTestHelper.doSync { + aliceRoom.invite(bobSession.myUserId, callback = it) + } mTestHelper.await(lock1) - assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) - - val lock2 = CountDownLatch(2) + val lock = CountDownLatch(1) val roomJoinedObserver = object : Observer> { override fun onChanged(t: List?) { if (bobSession.getRoom(aliceRoomId) ?.getRoomMember(aliceSession.myUserId) ?.membership == Membership.JOIN) { - statuses["AliceJoin"] = "AliceJoin" - lock2.countDown() + lock.countDown() bobRoomSummariesLive.removeObserver(this) } } @@ -143,19 +124,15 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { bobRoomSummariesLive.observeForever(roomJoinedObserver) } - bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2)) + mTestHelper.doSync { bobSession.joinRoom(aliceRoomId, callback = it) } - mTestHelper.await(lock2) + mTestHelper.await(lock) // Ensure bob can send messages to the room // val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! // assertNotNull(roomFromBobPOV.powerLevels) // assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) - assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin")) - -// bobSession.dataHandler.removeListener(bobEventListener) - return CryptoTestData(aliceSession, aliceRoomId, bobSession) } @@ -237,7 +214,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - var lock = CountDownLatch(1) + val lock = CountDownLatch(1) val bobEventsListener = object : Timeline.Listener { override fun onTimelineFailure(throwable: Throwable) { @@ -249,63 +226,35 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { } override fun onTimelineUpdated(snapshot: List) { - val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE } - .size + val messages = snapshot.filter { it.root.getClearType() == EventType.MESSAGE } + .groupBy { it.root.senderId!! } - if (size == 3) { + // Alice has sent 2 messages and Bob has sent 3 messages + if (messages[aliceSession.myUserId]?.size == 2 && messages[bobSession.myUserId]?.size == 3) { lock.countDown() } } } - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10)) + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20)) + bobTimeline.start() bobTimeline.addListener(bobEventsListener) - val results = HashMap() - - // bobSession.dataHandler.addListener(object : MXEventListener() { - // override fun onToDeviceEvent(event: Event) { - // results["onToDeviceEvent"] = event - // lock.countDown() - // } - // }) - // Alice sends a message roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) - assertTrue(results.containsKey("onToDeviceEvent")) -// assertEquals(1, messagesReceivedByBobCount) - // Bob send a message - lock = CountDownLatch(1) + // Bob send 3 messages roomFromBobPOV.sendTextMessage(messagesFromBob[0]) - // android does not echo the messages sent from itself -// messagesReceivedByBobCount++ - mTestHelper.await(lock) -// assertEquals(2, messagesReceivedByBobCount) - - // Bob send a message - lock = CountDownLatch(1) roomFromBobPOV.sendTextMessage(messagesFromBob[1]) - // android does not echo the messages sent from itself -// messagesReceivedByBobCount++ - mTestHelper.await(lock) -// assertEquals(3, messagesReceivedByBobCount) - - // Bob send a message - lock = CountDownLatch(1) roomFromBobPOV.sendTextMessage(messagesFromBob[2]) - // android does not echo the messages sent from itself -// messagesReceivedByBobCount++ - mTestHelper.await(lock) -// assertEquals(4, messagesReceivedByBobCount) // Alice sends a message - lock = CountDownLatch(2) roomFromAlicePOV.sendTextMessage(messagesFromAlice[1]) + mTestHelper.await(lock) -// assertEquals(5, messagesReceivedByBobCount) bobTimeline.removeListener(bobEventsListener) + bobTimeline.dispose() return cryptoTestData } @@ -340,18 +289,14 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData { return MegolmBackupAuthData( publicKey = "abcdefg", - signatures = HashMap>().apply { - this["something"] = HashMap().apply { - this["ed25519:something"] = "hijklmnop" - } - } + signatures = mapOf("something" to mapOf("ed25519:something" to "hijklmnop")) ) } fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo { - return MegolmBackupCreationInfo().apply { - algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP - authData = createFakeMegolmBackupAuthData() - } + return MegolmBackupCreationInfo( + algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, + authData = createFakeMegolmBackupAuthData() + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt index 60cc87d330..2346898ca7 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt @@ -22,11 +22,11 @@ object TestConstants { const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" - // Time out to use when waiting for server response. 60s - private const val AWAIT_TIME_OUT_MILLIS = 60000 + // Time out to use when waiting for server response. 10s + private const val AWAIT_TIME_OUT_MILLIS = 10_000 // Time out to use when waiting for server response, when the debugger is connected. 10 minutes - private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60000 + private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000 const val USER_ALICE = "Alice" const val USER_BOB = "Bob" diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt index 069d32acaf..f8d30a2679 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt @@ -2,12 +2,10 @@ package im.vector.matrix.android.internal.crypto.crosssigning import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CryptoTestHelper import im.vector.matrix.android.common.SessionTestParams import im.vector.matrix.android.common.TestConstants -import im.vector.matrix.android.common.TestMatrixCallback import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth @@ -21,7 +19,6 @@ import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters -import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -34,14 +31,13 @@ class XSigningTest : InstrumentedTest { fun test_InitializeAndStoreKeys() { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - val aliceLatch = CountDownLatch(1) - aliceSession.cryptoService().crossSigningService() - .initializeCrossSigning(UserPasswordAuth( - user = aliceSession.myUserId, - password = TestConstants.PASSWORD - ), TestMatrixCallback(aliceLatch)) - - mTestHelper.await(aliceLatch) + mTestHelper.doSync { + aliceSession.cryptoService().crossSigningService() + .initializeCrossSigning(UserPasswordAuth( + user = aliceSession.myUserId, + password = TestConstants.PASSWORD + ), it) + } val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys() val masterPubKey = myCrossSigningKeys?.masterKey() @@ -55,7 +51,7 @@ class XSigningTest : InstrumentedTest { assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified()) - mTestHelper.signout(aliceSession) + mTestHelper.signOutAndClose(aliceSession) } @Test @@ -74,17 +70,11 @@ class XSigningTest : InstrumentedTest { password = TestConstants.PASSWORD ) - val latch = CountDownLatch(2) - - aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch)) - bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch)) - - mTestHelper.await(latch) + mTestHelper.doSync { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) } + mTestHelper.doSync { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) } // Check that alice can see bob keys - val downloadLatch = CountDownLatch(1) - aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch)) - mTestHelper.await(downloadLatch) + mTestHelper.doSync> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey()) @@ -96,8 +86,8 @@ class XSigningTest : InstrumentedTest { assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) - mTestHelper.signout(aliceSession) - mTestHelper.signout(bobSession) + mTestHelper.signOutAndClose(aliceSession) + mTestHelper.signOutAndClose(bobSession) } @Test @@ -116,94 +106,56 @@ class XSigningTest : InstrumentedTest { password = TestConstants.PASSWORD ) - val latch = CountDownLatch(2) - - aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch)) - bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch)) - - mTestHelper.await(latch) + mTestHelper.doSync { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) } + mTestHelper.doSync { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) } // Check that alice can see bob keys - val downloadLatch = CountDownLatch(1) val bobUserId = bobSession.myUserId - aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch)) - mTestHelper.await(downloadLatch) + mTestHelper.doSync> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId) assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false) - val trustLatch = CountDownLatch(1) - aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, object : MatrixCallback { - override fun onSuccess(data: Unit) { - trustLatch.countDown() - } - - override fun onFailure(failure: Throwable) { - fail("Failed to trust bob") - } - }) - mTestHelper.await(trustLatch) + mTestHelper.doSync { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) } // Now bobs logs in on a new device and verifies it // We will want to test that in alice POV, this new device would be trusted by cross signing val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true)) - val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId + val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!! // Check that bob first session sees the new login - val bobKeysLatch = CountDownLatch(1) - bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, object : MatrixCallback> { - override fun onFailure(failure: Throwable) { - fail("Failed to get device") - } + val data = mTestHelper.doSync> { + bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) + } - override fun onSuccess(data: MXUsersDevicesMap) { - if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) { - fail("Bob should see the new device") - } - bobKeysLatch.countDown() - } - }) - mTestHelper.await(bobKeysLatch) + if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { + fail("Bob should see the new device") + } val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) // Manually mark it as trusted from first session - val bobSignLatch = CountDownLatch(1) - bobSession.cryptoService().crossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback { - override fun onSuccess(data: Unit) { - bobSignLatch.countDown() - } - - override fun onFailure(failure: Throwable) { - fail("Failed to trust bob ${failure.localizedMessage}") - } - }) - mTestHelper.await(bobSignLatch) + mTestHelper.doSync { + bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it) + } // Now alice should cross trust bob's second device - val aliceKeysLatch = CountDownLatch(1) - aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, object : MatrixCallback> { - override fun onFailure(failure: Throwable) { - fail("Failed to get device") - } + val data2 = mTestHelper.doSync> { + aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) + } - override fun onSuccess(data: MXUsersDevicesMap) { - // check that the device is seen - if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { - fail("Alice should see the new device") - } - aliceKeysLatch.countDown() - } - }) - mTestHelper.await(aliceKeysLatch) + // check that the device is seen + if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { + fail("Alice should see the new device") + } val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified()) - mTestHelper.signout(aliceSession) - mTestHelper.signout(bobSession) - mTestHelper.signout(bobSession2) + mTestHelper.signOutAndClose(aliceSession) + mTestHelper.signOutAndClose(bobSession) + mTestHelper.signOutAndClose(bobSession2) } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt index 2edc0c3976..77ba66d341 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.keysbackup import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.session.Session @@ -58,7 +57,7 @@ import java.util.Collections import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FixMethodOrder(MethodSorters.JVM) class KeysBackupTest : InstrumentedTest { private val mTestHelper = CommonTestHelper(context()) @@ -103,6 +102,8 @@ class KeysBackupTest : InstrumentedTest { assertEquals(sessionsCount, sessions3.size) assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) + + cryptoTestData.cleanUp(mTestHelper) } /** @@ -120,31 +121,18 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - val latch = CountDownLatch(1) + val megolmBackupCreationInfo = mTestHelper.doSync { + keysBackup.prepareKeysBackupVersion(null, null, it) + } - keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback { - override fun onSuccess(data: MegolmBackupCreationInfo) { - assertNotNull(data) - - assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, data.algorithm) - assertNotNull(data.authData) - assertNotNull(data.authData!!.publicKey) - assertNotNull(data.authData!!.signatures) - assertNotNull(data.recoveryKey) - - latch.countDown() - } - - override fun onFailure(failure: Throwable) { - fail(failure.localizedMessage) - - latch.countDown() - } - }) - mTestHelper.await(latch) + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm) + assertNotNull(megolmBackupCreationInfo.authData) + assertNotNull(megolmBackupCreationInfo.authData!!.publicKey) + assertNotNull(megolmBackupCreationInfo.authData!!.signatures) + assertNotNull(megolmBackupCreationInfo.recoveryKey) stateObserver.stopAndCheckStates(null) - bobSession.close() + mTestHelper.signOutAndClose(bobSession) } /** @@ -160,45 +148,22 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) - var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null - val latch = CountDownLatch(1) - keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback { - override fun onSuccess(data: MegolmBackupCreationInfo) { - megolmBackupCreationInfo = data - - latch.countDown() - } - - override fun onFailure(failure: Throwable) { - fail(failure.localizedMessage) - - latch.countDown() - } - }) - mTestHelper.await(latch) - - assertNotNull(megolmBackupCreationInfo) + val megolmBackupCreationInfo = mTestHelper.doSync { + keysBackup.prepareKeysBackupVersion(null, null, it) + } assertFalse(keysBackup.isEnabled) - val latch2 = CountDownLatch(1) - // Create the version - keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) { - override fun onSuccess(data: KeysVersion) { - assertNotNull(data) - assertNotNull(data.version) - - super.onSuccess(data) - } - }) - mTestHelper.await(latch2) + mTestHelper.doSync { + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) + } // Backup must be enable now assertTrue(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - bobSession.close() + mTestHelper.signOutAndClose(bobSession) } /** @@ -238,7 +203,7 @@ class KeysBackupTest : InstrumentedTest { KeysBackupState.ReadyToBackUp ) ) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /** @@ -259,18 +224,17 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, nbOfKeys) - val latch = CountDownLatch(1) - var lastBackedUpKeysProgress = 0 - keysBackup.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - assertEquals(nbOfKeys, total) - lastBackedUpKeysProgress = progress - } - }, TestMatrixCallback(latch)) + mTestHelper.doSync { + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + assertEquals(nbOfKeys, total) + lastBackedUpKeysProgress = progress + } + }, it) + } - mTestHelper.await(latch) assertEquals(nbOfKeys, lastBackedUpKeysProgress) val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) @@ -278,7 +242,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) stateObserver.stopAndCheckStates(null) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /** @@ -321,7 +285,7 @@ class KeysBackupTest : InstrumentedTest { assertKeysEquals(session.exportKeys(), sessionData) stateObserver.stopAndCheckStates(null) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /** @@ -335,25 +299,19 @@ class KeysBackupTest : InstrumentedTest { val testData = createKeysBackupScenarioWithPassword(null) // - Restore the e2e backup from the homeserver - val latch2 = CountDownLatch(1) - var importRoomKeysResult: ImportRoomKeysResult? = null - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, - null, - null, - null, - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val importRoomKeysResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + it + ) + } - checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -370,6 +328,8 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun restoreKeysBackupAndKeyShareRequestTest() { + fail("Check with Valere for this test. I think we do not send key share request") + val testData = createKeysBackupScenarioWithPassword(null) // - Check the SDK sent key share requests @@ -383,23 +343,17 @@ class KeysBackupTest : InstrumentedTest { assertTrue(unsentRequest != null || sentRequest != null) // - Restore the e2e backup from the homeserver - val latch2 = CountDownLatch(1) - var importRoomKeysResult: ImportRoomKeysResult? = null - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, - null, - null, - null, - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val importRoomKeysResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + it + ) + } - checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) // - There must be no more pending key share requests val unsentRequestAfterRestoration = cryptoStore2 @@ -410,7 +364,7 @@ class KeysBackupTest : InstrumentedTest { // Request is either sent or unsent assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -437,13 +391,13 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device - val latch = CountDownLatch(1) - testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( - testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - true, - TestMatrixCallback(latch) - ) - mTestHelper.await(latch) + mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + true, + it + ) + } // Wait for backup state to be ReadyToBackUp waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) @@ -453,38 +407,23 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val latch2 = CountDownLatch(1) - var keysVersionResult: KeysVersionResult? = null - testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion( - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: KeysVersionResult?) { - keysVersionResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val keysVersionResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) + } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val latch3 = CountDownLatch(1) - var keysBackupVersionTrust: KeysBackupVersionTrust? = null - testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!, - object : TestMatrixCallback(latch3) { - override fun onSuccess(data: KeysBackupVersionTrust) { - keysBackupVersionTrust = data - super.onSuccess(data) - } - }) - mTestHelper.await(latch3) + val keysBackupVersionTrust = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) + } // - It must be trusted and must have 2 signatures now - assertTrue(keysBackupVersionTrust!!.usable) - assertEquals(2, keysBackupVersionTrust!!.signatures.size) + assertTrue(keysBackupVersionTrust.usable) + assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -511,13 +450,13 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device with the recovery key - val latch = CountDownLatch(1) - testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( - testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, - TestMatrixCallback(latch) - ) - mTestHelper.await(latch) + mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + it + ) + } // Wait for backup state to be ReadyToBackUp waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) @@ -527,38 +466,23 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val latch2 = CountDownLatch(1) - var keysVersionResult: KeysVersionResult? = null - testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion( - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: KeysVersionResult?) { - keysVersionResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val keysVersionResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) + } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val latch3 = CountDownLatch(1) - var keysBackupVersionTrust: KeysBackupVersionTrust? = null - testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!, - object : TestMatrixCallback(latch3) { - override fun onSuccess(data: KeysBackupVersionTrust) { - keysBackupVersionTrust = data - super.onSuccess(data) - } - }) - mTestHelper.await(latch3) + val keysBackupVersionTrust = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) + } // - It must be trusted and must have 2 signatures now - assertTrue(keysBackupVersionTrust!!.usable) - assertEquals(2, keysBackupVersionTrust!!.signatures.size) + assertTrue(keysBackupVersionTrust.usable) + assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -597,7 +521,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) stateObserver.stopAndCheckStates(null) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -626,13 +550,13 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) // - Trust the backup from the new device with the password - val latch = CountDownLatch(1) - testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( - testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - password, - TestMatrixCallback(latch) - ) - mTestHelper.await(latch) + mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + password, + it + ) + } // Wait for backup state to be ReadyToBackUp waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) @@ -642,38 +566,23 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val latch2 = CountDownLatch(1) - var keysVersionResult: KeysVersionResult? = null - testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion( - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: KeysVersionResult?) { - keysVersionResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val keysVersionResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) + } // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) - val latch3 = CountDownLatch(1) - var keysBackupVersionTrust: KeysBackupVersionTrust? = null - testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult!!, - object : TestMatrixCallback(latch3) { - override fun onSuccess(data: KeysBackupVersionTrust) { - keysBackupVersionTrust = data - super.onSuccess(data) - } - }) - mTestHelper.await(latch3) + val keysBackupVersionTrust = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) + } // - It must be trusted and must have 2 signatures now - assertTrue(keysBackupVersionTrust!!.usable) - assertEquals(2, keysBackupVersionTrust!!.signatures.size) + assertTrue(keysBackupVersionTrust.usable) + assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -715,7 +624,7 @@ class KeysBackupTest : InstrumentedTest { assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) stateObserver.stopAndCheckStates(null) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -748,7 +657,7 @@ class KeysBackupTest : InstrumentedTest { // onSuccess may not have been called assertNull(importRoomKeysResult) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -764,27 +673,21 @@ class KeysBackupTest : InstrumentedTest { val testData = createKeysBackupScenarioWithPassword(password) // - Restore the e2e backup with the password - val latch2 = CountDownLatch(1) - var importRoomKeysResult: ImportRoomKeysResult? = null val steps = ArrayList() - testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - password, - null, - null, - object : StepProgressListener { - override fun onStepProgress(step: StepProgressListener.Step) { - steps.add(step) - } - }, - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val importRoomKeysResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + password, + null, + null, + object : StepProgressListener { + override fun onStepProgress(step: StepProgressListener.Step) { + steps.add(step) + } + }, + it + ) + } // Check steps assertEquals(105, steps.size) @@ -807,9 +710,9 @@ class KeysBackupTest : InstrumentedTest { assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress) assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) - checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -845,7 +748,7 @@ class KeysBackupTest : InstrumentedTest { // onSuccess may not have been called assertNull(importRoomKeysResult) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -861,25 +764,19 @@ class KeysBackupTest : InstrumentedTest { val testData = createKeysBackupScenarioWithPassword(password) // - Restore the e2e backup with the recovery key. - val latch2 = CountDownLatch(1) - var importRoomKeysResult: ImportRoomKeysResult? = null - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, - null, - null, - null, - object : TestMatrixCallback(latch2) { - override fun onSuccess(data: ImportRoomKeysResult) { - importRoomKeysResult = data - super.onSuccess(data) - } - } - ) - mTestHelper.await(latch2) + val importRoomKeysResult = mTestHelper.doSync { + testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + it + ) + } - checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -912,7 +809,7 @@ class KeysBackupTest : InstrumentedTest { // onSuccess may not have been called assertNull(importRoomKeysResult) - testData.cryptoTestData.close() + testData.cleanUp(mTestHelper) } /** @@ -932,46 +829,27 @@ class KeysBackupTest : InstrumentedTest { prepareAndCreateKeysBackupData(keysBackup) // Get key backup version from the home server - var keysVersionResult: KeysVersionResult? = null - val lock = CountDownLatch(1) - keysBackup.getCurrentVersion(object : TestMatrixCallback(lock) { - override fun onSuccess(data: KeysVersionResult?) { - keysVersionResult = data - super.onSuccess(data) - } - }) - mTestHelper.await(lock) - - assertNotNull(keysVersionResult) + val keysVersionResult = mTestHelper.doSync { + keysBackup.getCurrentVersion(it) + } // - Check the returned KeyBackupVersion is trusted - val latch = CountDownLatch(1) - var keysBackupVersionTrust: KeysBackupVersionTrust? = null - keysBackup.getKeysBackupTrust(keysVersionResult!!, object : MatrixCallback { - override fun onSuccess(data: KeysBackupVersionTrust) { - keysBackupVersionTrust = data - latch.countDown() - } - - override fun onFailure(failure: Throwable) { - super.onFailure(failure) - latch.countDown() - } - }) - mTestHelper.await(latch) + val keysBackupVersionTrust = mTestHelper.doSync { + keysBackup.getKeysBackupTrust(keysVersionResult!!, it) + } assertNotNull(keysBackupVersionTrust) - assertTrue(keysBackupVersionTrust!!.usable) - assertEquals(1, keysBackupVersionTrust!!.signatures.size) + assertTrue(keysBackupVersionTrust.usable) + assertEquals(1, keysBackupVersionTrust.signatures.size) - val signature = keysBackupVersionTrust!!.signatures[0] + val signature = keysBackupVersionTrust.signatures[0] assertTrue(signature.valid) assertNotNull(signature.device) assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId) stateObserver.stopAndCheckStates(null) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /** @@ -983,6 +861,7 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { + fail("This test still fail. To investigate") // - Create a backup version val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -1000,7 +879,7 @@ class KeysBackupTest : InstrumentedTest { // - Log Alice on a new device val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) val keysBackup2 = aliceSession2.cryptoService().keysBackupService() @@ -1012,12 +891,12 @@ class KeysBackupTest : InstrumentedTest { keysBackup2.addListener(object : KeysBackupStateListener { override fun onStateChange(newState: KeysBackupState) { // Check the backup completes - if (keysBackup.state == KeysBackupState.ReadyToBackUp) { + if (newState == KeysBackupState.ReadyToBackUp) { count++ if (count == 2) { // Remove itself from the list of listeners - keysBackup.removeListener(this) + keysBackup2.removeListener(this) latch.countDown() } @@ -1030,7 +909,7 @@ class KeysBackupTest : InstrumentedTest { stateObserver.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null) - aliceSession2.close() + mTestHelper.signOutAndClose(aliceSession2) } /** @@ -1079,21 +958,17 @@ class KeysBackupTest : InstrumentedTest { mTestHelper.await(latch0) // - Create a new backup with fake data on the homeserver, directly using the rest client - val latch = CountDownLatch(1) - val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() - (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, TestMatrixCallback(latch)) - mTestHelper.await(latch) + mTestHelper.doSync { + (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it) + } // Reset the store backup status for keys (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers() // - Make alice back up all her keys again val latch2 = CountDownLatch(1) - keysBackup.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - } - }, TestMatrixCallback(latch2, false)) + keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false)) mTestHelper.await(latch2) // -> That must fail and her backup state must be WrongBackUpVersion @@ -1101,7 +976,7 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /** @@ -1129,20 +1004,14 @@ class KeysBackupTest : InstrumentedTest { prepareAndCreateKeysBackupData(keysBackup) // Wait for keys backup to finish by asking again to backup keys. - val latch = CountDownLatch(1) - keysBackup.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - } - }, TestMatrixCallback(latch)) - mTestHelper.await(latch) + mTestHelper.doSync { + keysBackup.backupAllGroupSessions(null, it) + } val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!! val oldKeyBackupVersion = keysBackup.currentBackupVersion val aliceUserId = cryptoTestData.firstSession.myUserId - // Close first Alice session, else they will share the same Crypto store and the test fails. - cryptoTestData.firstSession.close() - // - Log Alice on a new device val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) @@ -1160,15 +1029,14 @@ class KeysBackupTest : InstrumentedTest { var isSuccessful = false val latch2 = CountDownLatch(1) - keysBackup2.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - } - }, object : TestMatrixCallback(latch2, false) { - override fun onSuccess(data: Unit) { - isSuccessful = true - super.onSuccess(data) - } - }) + keysBackup2.backupAllGroupSessions( + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: Unit) { + isSuccessful = true + super.onSuccess(data) + } + }) mTestHelper.await(latch2) assertFalse(isSuccessful) @@ -1178,7 +1046,7 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup2.isEnabled) // - Validate the old device from the new one - aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(false, true), aliceSession2.myUserId, oldDeviceId) + aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId) // -> Backup should automatically enable on the new device val latch4 = CountDownLatch(1) @@ -1198,17 +1066,17 @@ class KeysBackupTest : InstrumentedTest { // -> It must use the same backup version assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) - val latch5 = CountDownLatch(1) - aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, TestMatrixCallback(latch5)) - mTestHelper.await(latch5) + mTestHelper.doSync { + aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) + } // -> It must success assertTrue(aliceSession2.cryptoService().keysBackupService().isEnabled) stateObserver.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null) - aliceSession2.close() - cryptoTestData.close() + mTestHelper.signOutAndClose(aliceSession2) + cryptoTestData.cleanUp(mTestHelper) } /** @@ -1230,18 +1098,14 @@ class KeysBackupTest : InstrumentedTest { assertTrue(keysBackup.isEnabled) - val latch = CountDownLatch(1) - // Delete the backup - keysBackup.deleteBackup(keyBackupCreationInfo.version, TestMatrixCallback(latch)) - - mTestHelper.await(latch) + mTestHelper.doSync { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } // Backup is now disabled assertFalse(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /* ========================================================================================== @@ -1280,49 +1144,26 @@ class KeysBackupTest : InstrumentedTest { password: String? = null): PrepareKeysBackupDataResult { val stateObserver = StateObserver(keysBackup) - var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null - val latch = CountDownLatch(1) - keysBackup.prepareKeysBackupVersion(password, null, object : MatrixCallback { - override fun onSuccess(data: MegolmBackupCreationInfo) { - megolmBackupCreationInfo = data - - latch.countDown() - } - - override fun onFailure(failure: Throwable) { - fail(failure.localizedMessage) - - latch.countDown() - } - }) - mTestHelper.await(latch) + val megolmBackupCreationInfo = mTestHelper.doSync { + keysBackup.prepareKeysBackupVersion(password, null, it) + } assertNotNull(megolmBackupCreationInfo) assertFalse(keysBackup.isEnabled) - val latch2 = CountDownLatch(1) - // Create the version - var version: String? = null - keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) { - override fun onSuccess(data: KeysVersion) { - assertNotNull(data) - assertNotNull(data.version) + val keysVersion = mTestHelper.doSync { + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) + } - version = data.version - - super.onSuccess(data) - } - }) - mTestHelper.await(latch2) + assertNotNull(keysVersion.version) // Backup must be enable now assertTrue(keysBackup.isEnabled) - assertNotNull(version) stateObserver.stopAndCheckStates(null) - return PrepareKeysBackupDataResult(megolmBackupCreationInfo!!, version!!) + return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!) } private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { @@ -1347,7 +1188,12 @@ class KeysBackupTest : InstrumentedTest { private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData, val aliceKeys: List, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, - val aliceSession2: Session) + val aliceSession2: Session) { + fun cleanUp(testHelper: CommonTestHelper) { + cryptoTestData.cleanUp(testHelper) + testHelper.signOutAndClose(aliceSession2) + } + } /** * Common initial condition @@ -1369,27 +1215,22 @@ class KeysBackupTest : InstrumentedTest { // - Do an e2e backup to the homeserver val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) - val latch = CountDownLatch(1) var lastProgress = 0 var lastTotal = 0 - keysBackup.backupAllGroupSessions(object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - lastProgress = progress - lastTotal = total - } - }, TestMatrixCallback(latch)) - mTestHelper.await(latch) + mTestHelper.doSync { + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + lastProgress = progress + lastTotal = total + } + }, it) + } assertEquals(2, lastProgress) assertEquals(2, lastTotal) val aliceUserId = cryptoTestData.firstSession.myUserId - // Logout first Alice session, else they will share the same Crypto store and some tests may fail. - val latch2 = CountDownLatch(1) - cryptoTestData.firstSession.signOut(true, TestMatrixCallback(latch2)) - mTestHelper.await(latch2) - // - Log Alice on a new device val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt index 7802096338..1a0723c725 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt @@ -16,26 +16,25 @@ package im.vector.matrix.android.internal.crypto.ssss -import android.util.Base64 import androidx.lifecycle.Observer import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent import im.vector.matrix.android.api.session.securestorage.KeySigner +import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.SessionTestParams import im.vector.matrix.android.common.TestConstants import im.vector.matrix.android.common.TestMatrixCallback -import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 +import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService -import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -71,7 +70,7 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - val ssssKeyCreationInfo = mTestHelper.doSync { + mTestHelper.doSync { quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it) } @@ -95,16 +94,9 @@ class QuadSTests : InstrumentedTest { assertNotNull("Key should be stored in account data", accountData) val parsed = SecretStorageKeyContent.fromJson(accountData!!.content) assertNotNull("Key Content cannot be parsed", parsed) - assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm) + assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_AES_HMAC_SHA2, parsed!!.algorithm) assertEquals("Unexpected key name", "Test Key", parsed.name) assertNull("Key was not generated from passphrase", parsed.passphrase) - assertNotNull("Pubkey should be defined", parsed.publicKey) - - val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(ssssKeyCreationInfo.recoveryKey) - val pubKey = withOlmDecryption { olmPkDecryption -> - olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey) - } - assertEquals("Unexpected Public Key", pubKey, parsed.publicKey) // Set as default key quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback {}) @@ -128,7 +120,7 @@ class QuadSTests : InstrumentedTest { assertNotNull(defaultKeyAccountData?.content) assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key")) - mTestHelper.signout(aliceSession) + mTestHelper.signOutAndClose(aliceSession) } @Test @@ -137,13 +129,15 @@ class QuadSTests : InstrumentedTest { val keyId = "My.Key" val info = generatedSecret(aliceSession, keyId, true) + val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey) + // Store a secret - val clearSecret = Base64.encodeToString("42".toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP) + val clearSecret = "42".toByteArray().toBase64NoPadding() mTestHelper.doSync { aliceSession.sharedSecretStorageService.storeSecret( "secret.of.life", clearSecret, - null, // default key + listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key it ) } @@ -157,14 +151,13 @@ class QuadSTests : InstrumentedTest { val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId)) assertNotNull(secret?.ciphertext) assertNotNull(secret?.mac) - assertNotNull(secret?.ephemeral) + assertNotNull(secret?.initializationVector) // Try to decrypt?? - val keySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(info.recoveryKey) - val decryptedSecret = mTestHelper.doSync { - aliceSession.sharedSecretStorageService.getSecret("secret.of.life", + aliceSession.sharedSecretStorageService.getSecret( + "secret.of.life", null, // default key keySpec!!, it @@ -172,7 +165,7 @@ class QuadSTests : InstrumentedTest { } assertEquals("Secret mismatch", clearSecret, decryptedSecret) - mTestHelper.signout(aliceSession) + mTestHelper.signOutAndClose(aliceSession) } @Test @@ -192,7 +185,7 @@ class QuadSTests : InstrumentedTest { quadS.setDefaultKey(TEST_KEY_ID, it) } - mTestHelper.signout(aliceSession) + mTestHelper.signOutAndClose(aliceSession) } @Test @@ -209,7 +202,10 @@ class QuadSTests : InstrumentedTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), - listOf(keyId1, keyId2), + listOf( + SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)), + SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)) + ), it ) } @@ -226,7 +222,7 @@ class QuadSTests : InstrumentedTest { mTestHelper.doSync { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, - Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!, + RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!, it ) } @@ -234,12 +230,12 @@ class QuadSTests : InstrumentedTest { mTestHelper.doSync { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId2, - Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!, + RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!, it ) } - mTestHelper.signout(aliceSession) + mTestHelper.signOutAndClose(aliceSession) } @Test @@ -255,7 +251,7 @@ class QuadSTests : InstrumentedTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), - listOf(keyId1), + listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))), it ) } @@ -264,7 +260,7 @@ class QuadSTests : InstrumentedTest { var error = false aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, - Curve25519AesSha2KeySpec.fromPassphrase( + RawBytesKeySpec.fromPassphrase( "A bad passphrase", key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, @@ -289,7 +285,7 @@ class QuadSTests : InstrumentedTest { mTestHelper.doSync { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, - Curve25519AesSha2KeySpec.fromPassphrase( + RawBytesKeySpec.fromPassphrase( passphrase, key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, @@ -298,7 +294,7 @@ class QuadSTests : InstrumentedTest { ) } - mTestHelper.signout(aliceSession) + mTestHelper.signOutAndClose(aliceSession) } private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt index 7fad87b82d..85fc7e1b36 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -132,7 +132,7 @@ class SASTest : InstrumentedTest { assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } @Test @@ -189,7 +189,7 @@ class SASTest : InstrumentedTest { assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } @Test @@ -227,7 +227,7 @@ class SASTest : InstrumentedTest { val cancelReq = canceledToDeviceEvent!!.content.toModel()!! assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } @Test @@ -265,7 +265,7 @@ class SASTest : InstrumentedTest { val cancelReq = canceledToDeviceEvent!!.content.toModel()!! assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } private fun fakeBobStart(bobSession: Session, @@ -334,7 +334,7 @@ class SASTest : InstrumentedTest { mTestHelper.await(aliceCreatedLatch) mTestHelper.await(aliceCancelledLatch) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } /** @@ -393,7 +393,7 @@ class SASTest : InstrumentedTest { assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it)) } - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } @Test @@ -449,7 +449,7 @@ class SASTest : InstrumentedTest { assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } @Test @@ -514,6 +514,6 @@ class SASTest : InstrumentedTest { assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/VerificationTest.kt index d62aef077e..ae8a1ad718 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/VerificationTest.kt @@ -227,6 +227,6 @@ class VerificationTest : InstrumentedTest { pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode } - cryptoTestData.close() + cryptoTestData.cleanUp(mTestHelper) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt index 6285e866cc..bdad4702b7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt @@ -46,13 +46,13 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WellKnown( @Json(name = "m.homeserver") - var homeServer: WellKnownBaseConfig? = null, + val homeServer: WellKnownBaseConfig? = null, @Json(name = "m.identity_server") - var identityServer: WellKnownBaseConfig? = null, + val identityServer: WellKnownBaseConfig? = null, @Json(name = "m.integrations") - var integrations: Map? = null + val integrations: Map? = null ) { /** * Returns the list of integration managers proposed diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/MatrixSdkExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/MatrixSdkExtensions.kt index 23e8c70386..6ce04f8b30 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/MatrixSdkExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/MatrixSdkExtensions.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.api.extensions -import im.vector.matrix.android.api.comparators.DatedObjectComparators import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo @@ -33,7 +32,5 @@ fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint() * ========================================================================================== */ fun List.sortByLastSeen(): List { - val list = toMutableList() - list.sortWith(DatedObjectComparators.descComparator) - return list + return this.sortedByDescending { it.lastSeenTs ?: 0 } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index b7bc20a1dc..ff4745ef46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -42,6 +42,10 @@ interface CrossSigningService { fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback? = null) + fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, + uskKeyPrivateKey: String?, + sskPrivateKey: String?) : UserTrustResult + fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? fun getLiveCrossSigningKeys(userId: String): LiveData> @@ -53,11 +57,13 @@ interface CrossSigningService { fun trustUser(otherUserId: String, callback: MatrixCallback) + fun markMyMasterKeyAsTrusted() + /** * Sign one of your devices and upload the signature */ - fun signDevice(deviceId: String, - callback: MatrixCallback) + fun trustDevice(deviceId: String, + callback: MatrixCallback) fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningSsssSecretConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningSsssSecretConstants.kt new file mode 100644 index 0000000000..d46a724463 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningSsssSecretConstants.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.crypto.crosssigning + +const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master" + +const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing" + +const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index b69c189f89..1abbe9ef3a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.room.model.create import android.util.Patterns +import androidx.annotation.CheckResult import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.MatrixPatterns.isUserId @@ -120,37 +121,53 @@ data class CreateRoomParams( @Json(name = "power_level_content_override") val powerLevelContentOverride: PowerLevelsContent? = null ) { - /** - * Set to true means that if cross-signing is enabled and we can get keys for every invited users, - * the encryption will be enabled on the created room - */ @Transient internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false private set - fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams { - enableEncryptionIfInvitedUsersSupportIt = true + /** + * After calling this method, when the room will be created, if cross-signing is enabled and we can get keys for every invited users, + * the encryption will be enabled on the created room + * @param value true to activate this behavior. + * @return this, to allow chaining methods + */ + fun enableEncryptionIfInvitedUsersSupportIt(value: Boolean = true): CreateRoomParams { + enableEncryptionIfInvitedUsersSupportIt = value return this } /** * Add the crypto algorithm to the room creation parameters. * - * @param algorithm the algorithm + * @param enable true to enable encryption. + * @param algorithm the algorithm, default to [MXCRYPTO_ALGORITHM_MEGOLM], which is actually the only supported algorithm for the moment + * @return a modified copy of the CreateRoomParams object, or this if there is no modification */ - fun enableEncryptionWithAlgorithm(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams { + @CheckResult + fun enableEncryptionWithAlgorithm(enable: Boolean = true, + algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams { + // Remove the existing value if any. + val newInitialStates = initialStates + ?.filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { - val contentMap = mapOf("algorithm" to algorithm) + if (enable) { + val contentMap = mapOf("algorithm" to algorithm) - val algoEvent = Event( - type = EventType.STATE_ROOM_ENCRYPTION, - stateKey = "", - content = contentMap.toContent() - ) + val algoEvent = Event( + type = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + content = contentMap.toContent() + ) - copy( - initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent - ) + copy( + initialStates = newInitialStates.orEmpty() + algoEvent + ) + } else { + return copy( + initialStates = newInitialStates + ) + } } else { Timber.e("Unsupported algorithm: $algorithm") this @@ -161,7 +178,9 @@ data class CreateRoomParams( * Force the history visibility in the room creation parameters. * * @param historyVisibility the expected history visibility, set null to remove any existing value. + * @return a modified copy of the CreateRoomParams object */ + @CheckResult fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams { // Remove the existing value if any. val newInitialStates = initialStates @@ -187,7 +206,9 @@ data class CreateRoomParams( /** * Mark as a direct message room. + * @return a modified copy of the CreateRoomParams object */ + @CheckResult fun setDirectMessage(): CreateRoomParams { return copy( preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT, @@ -195,20 +216,6 @@ data class CreateRoomParams( ) } - /** - * @return the invite count - */ - private fun getInviteCount(): Int { - return invitedUserIds?.size ?: 0 - } - - /** - * @return the pid invite count - */ - private fun getInvite3PidCount(): Int { - return invite3pids?.size ?: 0 - } - /** * Tells if the created room can be a direct chat one. * @@ -217,7 +224,6 @@ data class CreateRoomParams( fun isDirect(): Boolean { return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT && isDirect == true - && (1 == getInviteCount() || 1 == getInvite3PidCount()) } /** @@ -232,7 +238,9 @@ data class CreateRoomParams( * ids might be a matrix id or an email address. * * @param ids the participant ids to add. + * @return a modified copy of the CreateRoomParams object */ + @CheckResult fun addParticipantIds(hsConfig: HomeServerConnectionConfig, userId: String, ids: List): CreateRoomParams { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt index 29c8cb830d..da54b344a2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt @@ -24,7 +24,7 @@ internal data class CreateRoomResponse( /** * Required. The created room's ID. */ - @Json(name = "room_id") var roomId: String + @Json(name = "room_id") val roomId: String ) internal typealias JoinRoomResponse = CreateRoomResponse diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsFilter.kt index b4de72e41a..c519d054f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsFilter.kt @@ -27,5 +27,5 @@ data class PublicRoomsFilter( * A string to search for in the room metadata, e.g. name, topic, canonical alias etc. (Optional). */ @Json(name = "generic_search_term") - var searchTerm: String? = null + val searchTerm: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsParams.kt index e2af1c3ccb..467968cd2a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/roomdirectory/PublicRoomsParams.kt @@ -28,30 +28,30 @@ data class PublicRoomsParams( * Limit the number of results returned. */ @Json(name = "limit") - var limit: Int? = null, + val limit: Int? = null, /** * A pagination token from a previous request, allowing clients to get the next (or previous) batch of rooms. * The direction of pagination is specified solely by which token is supplied, rather than via an explicit flag. */ @Json(name = "since") - var since: String? = null, + val since: String? = null, /** * Filter to apply to the results. */ @Json(name = "filter") - var filter: PublicRoomsFilter? = null, + val filter: PublicRoomsFilter? = null, /** * Whether or not to include all known networks/protocols from application services on the homeserver. Defaults to false. */ @Json(name = "include_all_networks") - var includeAllNetworks: Boolean = false, + val includeAllNetworks: Boolean = false, /** * The specific third party network/protocol to request from the homeserver. Can only be used if include_all_networks is false. */ @Json(name = "third_party_instance_id") - var thirdPartyInstanceId: String? = null + val thirdPartyInstanceId: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocol.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocol.kt index b066cff164..b4ed1f1a8e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocol.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocol.kt @@ -26,7 +26,7 @@ data class ThirdPartyProtocol( * where higher groupings are ordered first. For example, the name of a network should be searched before the nickname of a user. */ @Json(name = "user_fields") - var userFields: List? = null, + val userFields: List? = null, /** * Required. Fields which may be used to identify a third party location. These should be ordered to suggest the way that @@ -34,15 +34,15 @@ data class ThirdPartyProtocol( * searched before the name of a channel. */ @Json(name = "location_fields") - var locationFields: List? = null, + val locationFields: List? = null, /** * Required. A content URI representing an icon for the third party protocol. * - * FIXDOC: This field was not present in legacy Riot, and it is sometimes sent by the server (no not Required?) + * FIXDOC: This field was not present in legacy Riot, and it is sometimes sent by the server (so not Required?) */ @Json(name = "icon") - var icon: String? = null, + val icon: String? = null, /** * Required. The type definitions for the fields defined in the user_fields and location_fields. Each entry in those arrays MUST have an entry here. @@ -51,12 +51,12 @@ data class ThirdPartyProtocol( * May be an empty object if no fields are defined. */ @Json(name = "field_types") - var fieldTypes: Map? = null, + val fieldTypes: Map? = null, /** * Required. A list of objects representing independent instances of configuration. For example, multiple networks on IRC * if multiple are provided by the same application service. */ @Json(name = "instances") - var instances: List? = null + val instances: List? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt index 50f92356fb..f5d59f9282 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt @@ -25,35 +25,35 @@ data class ThirdPartyProtocolInstance( * Required. A human-readable description for the protocol, such as the name. */ @Json(name = "desc") - var desc: String? = null, + val desc: String? = null, /** * An optional content URI representing the protocol. Overrides the one provided at the higher level Protocol object. */ @Json(name = "icon") - var icon: String? = null, + val icon: String? = null, /** * Required. Preset values for fields the client may use to search by. */ @Json(name = "fields") - var fields: Map? = null, + val fields: Map? = null, /** * Required. A unique identifier across all instances. */ @Json(name = "network_id") - var networkId: String? = null, + val networkId: String? = null, /** * FIXDOC Not documented on matrix.org doc */ @Json(name = "instance_id") - var instanceId: String? = null, + val instanceId: String? = null, /** * FIXDOC Not documented on matrix.org doc */ @Json(name = "bot_user_id") - var botUserId: String? = null + val botUserId: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt index 164afb3a60..eb4a9b59e4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt @@ -41,12 +41,12 @@ interface Timeline { fun removeAllListeners() /** - * This should be called before any other method after creating the timeline. It ensures the underlying database is open + * This must be called before any other method after creating the timeline. It ensures the underlying database is open */ fun start() /** - * This should be called when you don't need the timeline. It ensures the underlying database get closed. + * This must be called when you don't need the timeline. It ensures the underlying database get closed. */ fun dispose() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt index 2b23ee40ca..a69127532e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt @@ -27,6 +27,9 @@ interface TimelineService { /** * Instantiate a [Timeline] with an optional initial eventId, to be used with permalink. * You can also configure some settings with the [settings] param. + * + * Important: the returned Timeline has to be started + * * @param eventId the optional initial eventId. * @param settings settings to configure the timeline. * @return the instantiated timeline diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt index f88b39fd13..57fd652735 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt @@ -32,7 +32,8 @@ data class EncryptedSecretContent( /** unpadded base64-encoded ciphertext */ @Json(name = "ciphertext") val ciphertext: String? = null, @Json(name = "mac") val mac: String? = null, - @Json(name = "ephemeral") val ephemeral: String? = null + @Json(name = "ephemeral") val ephemeral: String? = null, + @Json(name = "iv") val initializationVector: String? = null ) : AccountDataContent { companion object { /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/IntegrityResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/IntegrityResult.kt new file mode 100644 index 0000000000..70efa56c48 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/IntegrityResult.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.securestorage + +sealed class IntegrityResult { + data class Success(val passphraseBased: Boolean) : IntegrityResult() + data class Error(val cause: SharedSecretStorageError) : IntegrityResult() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt index e1a216ab37..abd12789a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt @@ -27,5 +27,6 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) { object BadKeyFormat : SharedSecretStorageError("Bad Key Format") object ParsingError : SharedSecretStorageError("parsing Error") + object BadMac : SharedSecretStorageError("Bad mac") data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt index 35579f756c..596d8d3e5d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt @@ -42,7 +42,7 @@ interface SharedSecretStorageService { */ fun generateKey(keyId: String, keyName: String, - keySigner: KeySigner, + keySigner: KeySigner?, callback: MatrixCallback) /** @@ -92,7 +92,7 @@ interface SharedSecretStorageService { * @param secret The secret contents. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. */ - fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) + fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) /** * Use this call to determine which SSSSKeySpec to use for requesting secret @@ -104,9 +104,15 @@ interface SharedSecretStorageService { * * @param name The name of the secret * @param keyId The id of the key that should be used to decrypt (null for default key) - * @param secretKey the secret key to use (@see #Curve25519AesSha2KeySpec) + * @param secretKey the secret key to use (@see #RawBytesKeySpec) * */ - @Throws fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) + + fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?) : IntegrityResult + + data class KeyRef( + val keyId: String?, + val keySpec: SsssKeySpec? + ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeySpec.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeySpec.kt index 90dcb92449..1fe8fbb90d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeySpec.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeySpec.kt @@ -23,14 +23,14 @@ import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyF /** Tag class */ interface SsssKeySpec -data class Curve25519AesSha2KeySpec( +data class RawBytesKeySpec( val privateKey: ByteArray ) : SsssKeySpec { companion object { - fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): Curve25519AesSha2KeySpec { - return Curve25519AesSha2KeySpec( + fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): RawBytesKeySpec { + return RawBytesKeySpec( privateKey = deriveKey( passphrase, salt, @@ -40,9 +40,9 @@ data class Curve25519AesSha2KeySpec( ) } - fun fromRecoveryKey(recoveryKey: String): Curve25519AesSha2KeySpec? { + fun fromRecoveryKey(recoveryKey: String): RawBytesKeySpec? { return extractCurveKeyFromRecoveryKey(recoveryKey)?.let { - Curve25519AesSha2KeySpec( + RawBytesKeySpec( privateKey = it ) } @@ -53,7 +53,7 @@ data class Curve25519AesSha2KeySpec( if (this === other) return true if (javaClass != other?.javaClass) return false - other as Curve25519AesSha2KeySpec + other as RawBytesKeySpec if (!privateKey.contentEquals(other.privateKey)) return false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt index 2d3d25e538..7512454052 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt @@ -32,20 +32,20 @@ data class RegistrationFlowResponse( * The list of flows. */ @Json(name = "flows") - var flows: List? = null, + val flows: List? = null, /** * The list of stages the client has completed successfully. */ @Json(name = "completed") - var completedStages: List? = null, + val completedStages: List? = null, /** * The session identifier that the client must pass back to the home server, if one is provided, * in subsequent attempts to authenticate in the same API call. */ @Json(name = "session") - var session: String? = null, + val session: String? = null, /** * The information that the client will need to know in order to use a given type of authentication. @@ -53,7 +53,7 @@ data class RegistrationFlowResponse( * For example, the public key of reCAPTCHA stage could be given here. */ @Json(name = "params") - var params: JsonDict? = null + val params: JsonDict? = null /** * WARNING, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt index fee81a853d..f82300548a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt @@ -35,6 +35,8 @@ const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes- * Secured Shared Storage algorithm constant */ const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2" +/* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. **/ +const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2" // TODO Refacto: use this constants everywhere const val ed25519 = "ed25519" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index c4d115b10c..1db774fd2d 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -1021,12 +1021,12 @@ internal class DefaultCryptoService @Inject constructor( return } - val requestBody = RoomKeyRequestBody() - - requestBody.roomId = event.roomId - requestBody.algorithm = wireContent["algorithm"]?.toString() - requestBody.senderKey = wireContent["sender_key"]?.toString() - requestBody.sessionId = wireContent["session_id"]?.toString() + val requestBody = RoomKeyRequestBody( + algorithm = wireContent["algorithm"]?.toString(), + roomId = event.roomId, + senderKey = wireContent["sender_key"]?.toString(), + sessionId = wireContent["session_id"]?.toString() + ) outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt index fe1f69f904..39b4678a27 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt @@ -25,54 +25,56 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest /** * IncomingRoomKeyRequest class defines the incoming room keys request. */ -open class IncomingRoomKeyRequest { - /** - * The user id - */ - var userId: String? = null +data class IncomingRoomKeyRequest( + /** + * The user id + */ + override val userId: String? = null, - /** - * The device id - */ - var deviceId: String? = null + /** + * The device id + */ + override val deviceId: String? = null, - /** - * The request id - */ - var requestId: String? = null + /** + * The request id + */ + override val requestId: String? = null, - /** - * The request body - */ - var requestBody: RoomKeyRequestBody? = null + /** + * The request body + */ + val requestBody: RoomKeyRequestBody? = null, - /** - * The runnable to call to accept to share the keys - */ - @Transient - var share: Runnable? = null + /** + * The runnable to call to accept to share the keys + */ + @Transient + var share: Runnable? = null, - /** - * The runnable to call to ignore the key share request. - */ - @Transient - var ignore: Runnable? = null - - /** - * Constructor - * - * @param event the event - */ - constructor(event: Event) { - userId = event.senderId - val roomKeyShareRequest = event.getClearContent().toModel()!! - deviceId = roomKeyShareRequest.requestingDeviceId - requestId = roomKeyShareRequest.requestId - requestBody = if (null != roomKeyShareRequest.body) roomKeyShareRequest.body else RoomKeyRequestBody() + /** + * The runnable to call to ignore the key share request. + */ + @Transient + var ignore: Runnable? = null +) : IncomingRoomKeyRequestCommon { + companion object { + /** + * Factory + * + * @param event the event + */ + fun fromEvent(event: Event): IncomingRoomKeyRequest? { + return event.getClearContent() + .toModel() + ?.let { + IncomingRoomKeyRequest( + userId = event.senderId, + deviceId = it.requestingDeviceId, + requestId = it.requestId, + requestBody = it.body ?: RoomKeyRequestBody() + ) + } + } } - - /** - * Constructor for object creation from crypto store - */ - constructor() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt index 0b22a0e28b..6779936f3a 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt @@ -17,13 +17,44 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation /** * IncomingRoomKeyRequestCancellation describes the incoming room key cancellation. */ -class IncomingRoomKeyRequestCancellation(event: Event) : IncomingRoomKeyRequest(event) { +data class IncomingRoomKeyRequestCancellation( + /** + * The user id + */ + override val userId: String? = null, - init { - requestBody = null + /** + * The device id + */ + override val deviceId: String? = null, + + /** + * The request id + */ + override val requestId: String? = null +) : IncomingRoomKeyRequestCommon { + companion object { + /** + * Factory + * + * @param event the event + */ + fun fromEvent(event: Event): IncomingRoomKeyRequestCancellation? { + return event.getClearContent() + .toModel() + ?.let { + IncomingRoomKeyRequestCancellation( + userId = event.senderId, + deviceId = it.requestingDeviceId, + requestId = it.requestId + ) + } + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCommon.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCommon.kt new file mode 100644 index 0000000000..a7b1c6b117 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCommon.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto + +interface IncomingRoomKeyRequestCommon { + /** + * The user id + */ + val userId: String? + + /** + * The device id + */ + val deviceId: String? + + /** + * The request id + */ + val requestId: String? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index 814d9d5a7c..8bad39bd25 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -53,8 +53,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( fun onRoomKeyRequestEvent(event: Event) { val roomKeyShare = event.getClearContent().toModel() when (roomKeyShare?.action) { - RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event)) - RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event)) + RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) } + RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) } else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action ${roomKeyShare?.action}") } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MegolmSessionData.kt index 821ed0a553..d6dae1a865 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MegolmSessionData.kt @@ -28,46 +28,46 @@ data class MegolmSessionData( * The algorithm used. */ @Json(name = "algorithm") - var algorithm: String? = null, + val algorithm: String? = null, /** * Unique id for the session. */ @Json(name = "session_id") - var sessionId: String? = null, + val sessionId: String? = null, /** * Sender's Curve25519 device key. */ @Json(name = "sender_key") - var senderKey: String? = null, + val senderKey: String? = null, /** * Room this session is used in. */ @Json(name = "room_id") - var roomId: String? = null, + val roomId: String? = null, /** * Base64'ed key data. */ @Json(name = "session_key") - var sessionKey: String? = null, + val sessionKey: String? = null, /** * Other keys the sender claims. */ @Json(name = "sender_claimed_keys") - var senderClaimedKeys: Map? = null, + val senderClaimedKeys: Map? = null, // This is a shortcut for sender_claimed_keys.get("ed25519") // Keep it for compatibility reason. @Json(name = "sender_claimed_ed25519_key") - var senderClaimedEd25519Key: String? = null, + val senderClaimedEd25519Key: String? = null, /** * Devices which forwarded this session to us (normally empty). */ @Json(name = "forwarding_curve25519_key_chain") - var forwardingCurve25519KeyChain: List? = null + val forwardingCurve25519KeyChain: List? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt index 5320b84b0e..b59c93ba83 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt @@ -213,10 +213,11 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody + " from " + request.recipients + " id " + request.requestId) - val requestMessage = RoomKeyShareRequest() - requestMessage.requestingDeviceId = cryptoStore.getDeviceId() - requestMessage.requestId = request.requestId - requestMessage.body = request.requestBody + val requestMessage = RoomKeyShareRequest( + requestingDeviceId = cryptoStore.getDeviceId(), + requestId = request.requestId, + body = request.requestBody + ) sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback { private fun onDone(state: OutgoingRoomKeyRequest.RequestState) { @@ -253,9 +254,10 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( + " to " + request.recipients + " cancellation id " + request.cancellationTxnId) - val roomKeyShareCancellation = RoomKeyShareCancellation() - roomKeyShareCancellation.requestingDeviceId = cryptoStore.getDeviceId() - roomKeyShareCancellation.requestId = request.cancellationTxnId + val roomKeyShareCancellation = RoomKeyShareCancellation( + requestingDeviceId = cryptoStore.getDeviceId(), + requestId = request.cancellationTxnId + ) sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback { private fun onDone() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt index ca6cfad0f9..6f41116b90 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -66,12 +66,12 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi totalNumbersOfImportedKeys++ // cancel any outstanding room key requests for this session - val roomKeyRequestBody = RoomKeyRequestBody() - - roomKeyRequestBody.algorithm = megolmSessionData.algorithm - roomKeyRequestBody.roomId = megolmSessionData.roomId - roomKeyRequestBody.senderKey = megolmSessionData.senderKey - roomKeyRequestBody.sessionId = megolmSessionData.sessionId + val roomKeyRequestBody = RoomKeyRequestBody( + algorithm = megolmSessionData.algorithm, + roomId = megolmSessionData.roomId, + senderKey = megolmSessionData.senderKey, + sessionId = megolmSessionData.sessionId + ) outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody) @@ -83,7 +83,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi } if (progressListener != null) { - val progress = 100 * cpt / totalNumbersOfKeys + val progress = 100 * (cpt + 1) / totalNumbersOfKeys if (lastProgress != progress) { lastProgress = progress diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 054b38ad06..90100fcc48 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -163,12 +163,12 @@ internal class MXMegolmDecryption(private val userId: String, recipients.add(senderMap) } - val requestBody = RoomKeyRequestBody() - - requestBody.roomId = event.roomId - requestBody.algorithm = encryptedEventContent.algorithm - requestBody.senderKey = encryptedEventContent.senderKey - requestBody.sessionId = encryptedEventContent.sessionId + val requestBody = RoomKeyRequestBody( + roomId = event.roomId, + algorithm = encryptedEventContent.algorithm, + senderKey = encryptedEventContent.senderKey, + sessionId = encryptedEventContent.sessionId + ) outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients) } @@ -264,12 +264,12 @@ internal class MXMegolmDecryption(private val userId: String, if (added) { defaultKeysBackupService.maybeBackupKeys() - val content = RoomKeyRequestBody() - - content.algorithm = roomKeyContent.algorithm - content.roomId = roomKeyContent.roomId - content.sessionId = roomKeyContent.sessionId - content.senderKey = senderKey + val content = RoomKeyRequestBody( + algorithm = roomKeyContent.algorithm, + roomId = roomKeyContent.roomId, + sessionId = roomKeyContent.sessionId, + senderKey = senderKey + ) outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content) @@ -290,8 +290,8 @@ internal class MXMegolmDecryption(private val userId: String, override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { val roomId = request.requestBody?.roomId ?: return false - val senderKey = request.requestBody?.senderKey ?: return false - val sessionId = request.requestBody?.sessionId ?: return false + val senderKey = request.requestBody.senderKey ?: return false + val sessionId = request.requestBody.sessionId ?: return false return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId) } @@ -319,15 +319,14 @@ internal class MXMegolmDecryption(private val userId: String, return@mapCatching } Timber.v("## shareKeysWithDevice() : sharing keys for session" + - " ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId") + " ${body.senderKey}|${body.sessionId} with device $userId:$deviceId") val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY) - runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) } + runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) } .fold( { // TODO - payloadJson["content"] = it.exportKeys() - ?: "" + payloadJson["content"] = it.exportKeys() ?: "" }, { // TODO diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt index 207dc0b928..841de92130 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ComputeTrustTask.kt @@ -20,6 +20,8 @@ import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.withContext import javax.inject.Inject internal interface ComputeTrustTask : Task { @@ -29,14 +31,15 @@ internal interface ComputeTrustTask : Task getUserCrossSigningKeys(userId)?.isTrusted() == true } - return if (allTrustedUserIds.isEmpty()) { + if (allTrustedUserIds.isEmpty()) { RoomEncryptionTrustLevel.Default } else { // If one of the verified user as an untrusted device -> warning diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 7fc3c0a549..a29f27ddd6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -88,7 +88,8 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.i("## CrossSigning - Loading master key success") } else { Timber.w("## CrossSigning - Public master key does not match the private key") - // TODO untrust + pkSigning.releaseSigning() + // TODO untrust? } } privateKeysInfo.user @@ -100,7 +101,8 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.i("## CrossSigning - Loading User Signing key success") } else { Timber.w("## CrossSigning - Public User key does not match the private key") - // TODO untrust + pkSigning.releaseSigning() + // TODO untrust? } } privateKeysInfo.selfSigned @@ -112,7 +114,8 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.i("## CrossSigning - Loading Self Signing key success") } else { Timber.w("## CrossSigning - Public Self Signing key does not match the private key") - // TODO untrust + pkSigning.releaseSigning() + // TODO untrust? } } } @@ -224,16 +227,18 @@ internal class DefaultCrossSigningService @Inject constructor( val myDevice = myDeviceInfoHolder.get().myDevice val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) val signedDevice = selfSigningPkOlm.sign(canonicalJson) - val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also { - it[userId] = (it[userId] - ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) - } + val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()) + .also { + it[userId] = (it[userId] + ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) + } myDevice.copy(signatures = updateSignatures).let { uploadSignatureQueryBuilder.withDeviceInfo(it) } // sign MSK with device key (migration) and upload signatures - olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign -> + val message = JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()) + olmDevice.signMessage(message)?.let { sign -> val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() ?: HashMap()).also { it[userId] = (it[userId] @@ -292,6 +297,80 @@ internal class DefaultCrossSigningService @Inject constructor( cryptoStore.clearOtherUserTrust() } + override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, + uskKeyPrivateKey: String?, + sskPrivateKey: String? + ): UserTrustResult { + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId) + + var masterKeyIsTrusted = false + var userKeyIsTrusted = false + var selfSignedKeyIsTrusted = false + + masterKeyPrivateKey?.fromBase64NoPadding() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { + masterPkSigning?.releaseSigning() + masterPkSigning = pkSigning + masterKeyIsTrusted = true + Timber.i("## CrossSigning - Loading master key success") + } else { + pkSigning.releaseSigning() + } + } catch (failure: Throwable) { + pkSigning.releaseSigning() + } + } + + uskKeyPrivateKey?.fromBase64NoPadding() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { + userPkSigning?.releaseSigning() + userPkSigning = pkSigning + userKeyIsTrusted = true + Timber.i("## CrossSigning - Loading master key success") + } else { + pkSigning.releaseSigning() + } + } catch (failure: Throwable) { + pkSigning.releaseSigning() + } + } + + sskPrivateKey?.fromBase64NoPadding() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + selfSigningPkSigning?.releaseSigning() + selfSigningPkSigning = pkSigning + selfSignedKeyIsTrusted = true + Timber.i("## CrossSigning - Loading master key success") + } else { + pkSigning.releaseSigning() + } + } catch (failure: Throwable) { + pkSigning.releaseSigning() + } + } + + if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) { + return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo) + } else { + cryptoStore.markMyMasterKeyAsLocallyTrusted(true) + val checkSelfTrust = checkSelfTrust() + if (checkSelfTrust.isVerified()) { + cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey, uskKeyPrivateKey, sskPrivateKey) + setUserKeysAsTrusted(userId, true) + } + return checkSelfTrust + } + } + /** * * ┏━━━━━━━━┓ ┏━━━━━━━━┓ @@ -374,7 +453,9 @@ internal class DefaultCrossSigningService @Inject constructor( ?.fromBase64NoPadding() var isMaterKeyTrusted = false - if (masterPrivateKey != null) { + if (myMasterKey.trustLevel?.locallyVerified == true) { + isMaterKeyTrusted = true + } else if (masterPrivateKey != null) { // Check if private match public var olmPkSigning: OlmPkSigning? = null try { @@ -507,7 +588,12 @@ internal class DefaultCrossSigningService @Inject constructor( }.executeBy(taskExecutor) } - override fun signDevice(deviceId: String, callback: MatrixCallback) { + override fun markMyMasterKeyAsTrusted() { + cryptoStore.markMyMasterKeyAsLocallyTrusted(true) + checkSelfTrust() + } + + override fun trustDevice(deviceId: String, callback: MatrixCallback) { // This device should be yours val device = cryptoStore.getUserDevice(userId, deviceId) if (device == null) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt index 5bc6e2df0f..cc5213fcc5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/ShieldTrustUpdater.kt @@ -18,16 +18,19 @@ package im.vector.matrix.android.internal.crypto.crosssigning import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.createBackgroundHandler import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe +import java.util.concurrent.atomic.AtomicBoolean +import timber.log.Timber import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject @@ -35,7 +38,7 @@ internal class ShieldTrustUpdater @Inject constructor( private val eventBus: EventBus, private val computeTrustTask: ComputeTrustTask, private val taskExecutor: TaskExecutor, - @CryptoDatabase private val cryptoRealmConfiguration: RealmConfiguration, + private val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val sessionRealmConfiguration: RealmConfiguration, private val roomSummaryUpdater: RoomSummaryUpdater ) { @@ -44,51 +47,41 @@ internal class ShieldTrustUpdater @Inject constructor( private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD") } - private val backgroundCryptoRealm = AtomicReference() private val backgroundSessionRealm = AtomicReference() -// private var cryptoDevicesResult: RealmResults? = null - -// private val cryptoDeviceChangeListener = object : OrderedRealmCollectionChangeListener> { -// override fun onChange(t: RealmResults, changeSet: OrderedCollectionChangeSet) { -// val grouped = t.groupBy { it.userId } -// onCryptoDevicesChange(grouped.keys.mapNotNull { it }) -// } -// } + private val isStarted = AtomicBoolean() fun start() { - eventBus.register(this) - BACKGROUND_HANDLER.post { - val cryptoRealm = Realm.getInstance(cryptoRealmConfiguration) - backgroundCryptoRealm.set(cryptoRealm) -// cryptoDevicesResult = cryptoRealm.where().findAll() -// cryptoDevicesResult?.addChangeListener(cryptoDeviceChangeListener) - - backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration)) + if (isStarted.compareAndSet(false, true)) { + eventBus.register(this) + BACKGROUND_HANDLER.post { + backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration)) + } } } fun stop() { - eventBus.unregister(this) - BACKGROUND_HANDLER.post { - // cryptoDevicesResult?.removeAllChangeListeners() - backgroundCryptoRealm.getAndSet(null).also { - it?.close() - } - backgroundSessionRealm.getAndSet(null).also { - it?.close() + if (isStarted.compareAndSet(true, false)) { + eventBus.unregister(this) + BACKGROUND_HANDLER.post { + backgroundSessionRealm.getAndSet(null).also { + it?.close() + } } } } @Subscribe fun onRoomMemberChange(update: SessionToCryptoRoomMembersUpdate) { - taskExecutor.executorScope.launch { + if (!isStarted.get()) { + return + } + taskExecutor.executorScope.launch(coroutineDispatchers.crypto) { val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds)) // We need to send that back to session base BACKGROUND_HANDLER.post { - backgroundSessionRealm.get().executeTransaction { realm -> + backgroundSessionRealm.get()?.executeTransaction { realm -> roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust) } } @@ -97,34 +90,47 @@ internal class ShieldTrustUpdater @Inject constructor( @Subscribe fun onTrustUpdate(update: CryptoToSessionUserTrustChange) { + if (!isStarted.get()) { + return + } + onCryptoDevicesChange(update.userIds) } private fun onCryptoDevicesChange(users: List) { BACKGROUND_HANDLER.post { - val impactedRoomsId = backgroundSessionRealm.get().where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) - .findAll() - .map { it.roomId } - .distinct() + val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java) + ?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) + ?.findAll() + ?.map { it.roomId } + ?.distinct() val map = HashMap>() - impactedRoomsId.forEach { roomId -> - RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId) - .findAll() - .let { results -> - map[roomId] = results.map { it.userId } - } + impactedRoomsId?.forEach { roomId -> + backgroundSessionRealm.get()?.let { realm -> + RoomMemberSummaryEntity.where(realm, roomId) + .findAll() + .let { results -> + map[roomId] = results.map { it.userId } + } + } } map.forEach { entry -> val roomId = entry.key val userList = entry.value taskExecutor.executorScope.launch { - val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList)) - BACKGROUND_HANDLER.post { - backgroundSessionRealm.get().executeTransaction { realm -> - roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust) + withContext(coroutineDispatchers.crypto) { + try { + // Can throw if the crypto database has been closed in between, in this case log and ignore? + val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList)) + BACKGROUND_HANDLER.post { + backgroundSessionRealm.get()?.executeTransaction { realm -> + roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust) + } + } + } catch (failure: Throwable) { + Timber.e(failure) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt index aa291e8206..3ec5a2f979 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -80,6 +80,7 @@ import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.awaitCallback import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.olm.OlmException @@ -167,9 +168,7 @@ internal class DefaultKeysBackupService @Inject constructor( runCatching { withContext(coroutineDispatchers.crypto) { val olmPkDecryption = OlmPkDecryption() - val megolmBackupAuthData = MegolmBackupAuthData() - - if (password != null) { + val megolmBackupAuthData = if (password != null) { // Generate a private key from the password val backgroundProgressListener = if (progressListener == null) { null @@ -188,25 +187,30 @@ internal class DefaultKeysBackupService @Inject constructor( } val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey) - megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt - megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations + MegolmBackupAuthData( + publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey), + privateKeySalt = generatePrivateKeyResult.salt, + privateKeyIterations = generatePrivateKeyResult.iterations + ) } else { val publicKey = olmPkDecryption.generateKey() - megolmBackupAuthData.publicKey = publicKey + MegolmBackupAuthData( + publicKey = publicKey + ) } val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) - megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) + val signedMegolmBackupAuthData = megolmBackupAuthData.copy( + signatures = objectSigner.signObject(canonicalJson) + ) - val megolmBackupCreationInfo = MegolmBackupCreationInfo() - megolmBackupCreationInfo.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP - megolmBackupCreationInfo.authData = megolmBackupAuthData - megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) - - megolmBackupCreationInfo + MegolmBackupCreationInfo( + algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, + authData = signedMegolmBackupAuthData, + recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) + ) } }.foldToCallback(callback) } @@ -214,11 +218,12 @@ internal class DefaultKeysBackupService @Inject constructor( override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, callback: MatrixCallback) { - val createKeysBackupVersionBody = CreateKeysBackupVersionBody() - createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm @Suppress("UNCHECKED_CAST") - createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java) - .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + val createKeysBackupVersionBody = CreateKeysBackupVersionBody( + algorithm = keysBackupCreationInfo.algorithm, + authData = MoshiProvider.providesMoshi().adapter(Map::class.java) + .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + ) keysBackupStateManager.state = KeysBackupState.Enabling @@ -229,14 +234,14 @@ internal class DefaultKeysBackupService @Inject constructor( // Reset backup markers. cryptoStore.resetBackupMarkers() - val keyBackupVersion = KeysVersionResult() - keyBackupVersion.algorithm = createKeysBackupVersionBody.algorithm - keyBackupVersion.authData = createKeysBackupVersionBody.authData - keyBackupVersion.version = data.version - - // We can consider that the server does not have keys yet - keyBackupVersion.count = 0 - keyBackupVersion.hash = null + val keyBackupVersion = KeysVersionResult( + algorithm = createKeysBackupVersionBody.algorithm, + authData = createKeysBackupVersionBody.authData, + version = data.version, + // We can consider that the server does not have keys yet + count = 0, + hash = null + ) enableKeysBackup(keyBackupVersion) @@ -406,7 +411,7 @@ internal class DefaultKeysBackupService @Inject constructor( return keysBackupVersionTrust } - val mySigs = authData.signatures?.get(userId) + val mySigs = authData.signatures[userId] if (mySigs.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") return keysBackupVersionTrust @@ -469,8 +474,7 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.main) { val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() - ?: HashMap() + val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() ?: HashMap() if (trust) { // Add current device signature @@ -487,24 +491,23 @@ internal class DefaultKeysBackupService @Inject constructor( } // Create an updated version of KeysVersionResult - val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!) - - updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm - val newMegolmBackupAuthData = authData.copy() val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() newSignatures[userId] = myUserSignatures - newMegolmBackupAuthData.signatures = newSignatures + val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( + signatures = newSignatures + ) val moshi = MoshiProvider.providesMoshi() val adapter = moshi.adapter(Map::class.java) @Suppress("UNCHECKED_CAST") - updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map? - - updateKeysBackupVersionBody + UpdateKeysBackupVersionBody( + algorithm = keysBackupVersion.algorithm, + authData = adapter.fromJson(newMegolmBackupAuthDataWithNewSignature.toJsonString()) as Map?, + version = keysBackupVersion.version!!) } // And send it to the homeserver @@ -513,13 +516,13 @@ internal class DefaultKeysBackupService @Inject constructor( this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // Relaunch the state machine on this updated backup version - val newKeysBackupVersion = KeysVersionResult() - - newKeysBackupVersion.version = keysBackupVersion.version - newKeysBackupVersion.algorithm = keysBackupVersion.algorithm - newKeysBackupVersion.count = keysBackupVersion.count - newKeysBackupVersion.hash = keysBackupVersion.hash - newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData + val newKeysBackupVersion = KeysVersionResult( + algorithm = keysBackupVersion.algorithm, + authData = updateKeysBackupVersionBody.authData, + version = keysBackupVersion.version, + hash = keysBackupVersion.hash, + count = keysBackupVersion.count + ) checkAndStartWithKeysBackupVersion(newKeysBackupVersion) @@ -807,7 +810,10 @@ internal class DefaultKeysBackupService @Inject constructor( // new key is sent val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS) - uiHandler.postDelayed({ backupKeys() }, delayInMs) + cryptoCoroutineScope.launch { + delay(delayInMs) + uiHandler.post { backupKeys() } + } } else -> { Timber.v("maybeBackupKeys: Skip it because state: $state") @@ -1024,7 +1030,7 @@ internal class DefaultKeysBackupService @Inject constructor( } // Extract the recovery key from the passphrase - val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt!!, authData.privateKeyIterations!!, progressListener) + val data = retrievePrivateKeyWithPassword(password, authData.privateKeySalt, authData.privateKeyIterations, progressListener) return computeRecoveryKey(data) } @@ -1178,14 +1184,16 @@ internal class DefaultKeysBackupService @Inject constructor( // Gather data to send to the homeserver // roomId -> sessionId -> MXKeyBackupData - val keysBackupData = KeysBackupData() - keysBackupData.roomIdToRoomKeysBackupData = HashMap() + val keysBackupData = KeysBackupData( + roomIdToRoomKeysBackupData = HashMap() + ) for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { - val roomKeysBackupData = RoomKeysBackupData() - roomKeysBackupData.sessionIdToKeyBackupData = HashMap() + val roomKeysBackupData = RoomKeysBackupData( + sessionIdToKeyBackupData = HashMap() + ) keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData } @@ -1301,24 +1309,21 @@ internal class DefaultKeysBackupService @Inject constructor( } // Build backup data for that key - val keyBackupData = KeyBackupData() - try { - keyBackupData.firstMessageIndex = olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } + return KeyBackupData( + firstMessageIndex = try { + olmInboundGroupSessionWrapper.olmInboundGroupSession!!.firstKnownIndex + } catch (e: OlmException) { + Timber.e(e, "OlmException") + 0L + }, + forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size, + isVerified = device?.isVerified == true, - keyBackupData.forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain!!.size - keyBackupData.isVerified = device?.isVerified == true - - val data = mapOf( - "ciphertext" to encryptedSessionBackupData!!.mCipherText, - "mac" to encryptedSessionBackupData.mMac, - "ephemeral" to encryptedSessionBackupData.mEphemeralKey) - - keyBackupData.sessionData = data - - return keyBackupData + sessionData = mapOf( + "ciphertext" to encryptedSessionBackupData!!.mCipherText, + "mac" to encryptedSessionBackupData.mMac, + "ephemeral" to encryptedSessionBackupData.mEphemeralKey) + ) } @VisibleForTesting @@ -1350,8 +1355,10 @@ internal class DefaultKeysBackupService @Inject constructor( } if (sessionBackupData != null) { - sessionBackupData.sessionId = sessionId - sessionBackupData.roomId = roomId + sessionBackupData = sessionBackupData.copy( + sessionId = sessionId, + roomId = roomId + ) } } @@ -1370,11 +1377,12 @@ internal class DefaultKeysBackupService @Inject constructor( @VisibleForTesting fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, callback: MatrixCallback) { - val createKeysBackupVersionBody = CreateKeysBackupVersionBody() - createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm @Suppress("UNCHECKED_CAST") - createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java) - .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + val createKeysBackupVersionBody = CreateKeysBackupVersionBody( + algorithm = keysBackupCreationInfo.algorithm, + authData = MoshiProvider.providesMoshi().adapter(Map::class.java) + .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + ) createKeysBackupVersionTask .configureWith(createKeysBackupVersionBody) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt index 442b1f081c..48015a98dd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt @@ -30,26 +30,27 @@ data class MegolmBackupAuthData( * The curve25519 public key used to encrypt the backups. */ @Json(name = "public_key") - var publicKey: String = "", + val publicKey: String = "", /** * In case of a backup created from a password, the salt associated with the backup * private key. */ @Json(name = "private_key_salt") - var privateKeySalt: String? = null, + val privateKeySalt: String? = null, /** * In case of a backup created from a password, the number of key derivations. */ @Json(name = "private_key_iterations") - var privateKeyIterations: Int? = null, + val privateKeyIterations: Int? = null, /** * Signatures of the public key. * userId -> (deviceSignKeyId -> signature) */ - var signatures: Map>? = null + @Json(name = "signatures") + val signatures: Map>? = null ) { fun toJsonString(): String { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt index a08ba9ba96..b329fa44c9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt @@ -19,20 +19,19 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model /** * Data retrieved from Olm library. algorithm and authData will be send to the homeserver, and recoveryKey will be displayed to the user */ -class MegolmBackupCreationInfo { +data class MegolmBackupCreationInfo( + /** + * The algorithm used for storing backups [org.matrix.androidsdk.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP]. + */ + val algorithm: String = "", - /** - * The algorithm used for storing backups [org.matrix.androidsdk.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP]. - */ - var algorithm: String = "" + /** + * Authentication data. + */ + val authData: MegolmBackupAuthData? = null, - /** - * Authentication data. - */ - var authData: MegolmBackupAuthData? = null - - /** - * The Base58 recovery key. - */ - var recoveryKey: String = "" -} + /** + * The Base58 recovery key. + */ + val recoveryKey: String = "" +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt index 5efbc6d017..3b267280e5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt @@ -16,7 +16,21 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.util.JsonDict @JsonClass(generateAdapter = true) -class CreateKeysBackupVersionBody : KeysAlgorithmAndData() +data class CreateKeysBackupVersionBody( + /** + * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined + */ + @Json(name = "algorithm") + override val algorithm: String? = null, + + /** + * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData] + */ + @Json(name = "auth_data") + override val authData: JsonDict? = null +) : KeysAlgorithmAndData diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeyBackupData.kt index f172d45ffd..b2d10687aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeyBackupData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeyBackupData.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.network.parsing.ForceToBoolean /** * Backup data for one key. @@ -29,25 +30,27 @@ data class KeyBackupData( * Required. The index of the first message in the session that the key can decrypt. */ @Json(name = "first_message_index") - var firstMessageIndex: Long = 0, + val firstMessageIndex: Long = 0, /** * Required. The number of times this key has been forwarded. */ @Json(name = "forwarded_count") - var forwardedCount: Int = 0, + val forwardedCount: Int = 0, /** * Whether the device backing up the key has verified the device that the key is from. + * Force to boolean because of https://github.com/matrix-org/synapse/issues/6977 */ + @ForceToBoolean @Json(name = "is_verified") - var isVerified: Boolean = false, + val isVerified: Boolean = false, /** * Algorithm-dependent data. */ @Json(name = "session_data") - var sessionData: Map? = null + val sessionData: Map? = null ) { fun toJsonString(): String { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt index 6fba833589..81ca6586a3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest -import com.squareup.moshi.Json import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData import im.vector.matrix.android.internal.di.MoshiProvider @@ -38,19 +37,17 @@ import im.vector.matrix.android.internal.di.MoshiProvider * } * */ -open class KeysAlgorithmAndData { +interface KeysAlgorithmAndData { /** * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined */ - @Json(name = "algorithm") - var algorithm: String? = null + val algorithm: String? /** * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData] */ - @Json(name = "auth_data") - var authData: JsonDict? = null + val authData: JsonDict? /** * Facility method to convert authData to a MegolmBackupAuthData object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysBackupData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysBackupData.kt index 2f4165d8ab..240c79fd1e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysBackupData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysBackupData.kt @@ -24,9 +24,7 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class KeysBackupData( - // the keys are the room IDs, and the values are RoomKeysBackupData @Json(name = "rooms") - var roomIdToRoomKeysBackupData: MutableMap = HashMap() - + val roomIdToRoomKeysBackupData: MutableMap = HashMap() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt index 4510cdd773..0addd1491e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt @@ -16,16 +16,33 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.util.JsonDict @JsonClass(generateAdapter = true) data class KeysVersionResult( + /** + * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined + */ + @Json(name = "algorithm") + override val algorithm: String? = null, + + /** + * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData] + */ + @Json(name = "auth_data") + override val authData: JsonDict? = null, + // the backup version - var version: String? = null, + @Json(name = "version") + val version: String? = null, // The hash value which is an opaque string representing stored keys in the backup - var hash: String? = null, + @Json(name = "hash") + val hash: String? = null, // The number of keys stored in the backup. - var count: Int? = null -) : KeysAlgorithmAndData() + @Json(name = "count") + val count: Int? = null +) : KeysAlgorithmAndData diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt index 5d69f63538..f3c218baca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt @@ -24,8 +24,7 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class RoomKeysBackupData( - // the keys are the session IDs, and the values are KeyBackupData @Json(name = "sessions") - var sessionIdToKeyBackupData: MutableMap = HashMap() + val sessionIdToKeyBackupData: MutableMap = HashMap() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt index cb8ba5e26c..9d88af20ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt @@ -16,10 +16,25 @@ package im.vector.matrix.android.internal.crypto.keysbackup.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.util.JsonDict @JsonClass(generateAdapter = true) data class UpdateKeysBackupVersionBody( + /** + * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined + */ + @Json(name = "algorithm") + override val algorithm: String? = null, + + /** + * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [im.vector.matrix.android.internal.crypto.keysbackup.MegolmBackupAuthData] + */ + @Json(name = "auth_data") + override val authData: JsonDict? = null, + // the backup version, mandatory + @Json(name = "version") val version: String -) : KeysAlgorithmAndData() +) : KeysAlgorithmAndData diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt index 8f6d64221c..e3e8f3de27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt @@ -28,10 +28,6 @@ data class CryptoDeviceInfo( override val keys: Map? = null, override val signatures: Map>? = null, val unsigned: JsonDict? = null, - - // TODO how to store if this device is verified by a user SSK, or is legacy trusted? - // I need to know if it is trusted via cross signing (Trusted because bob verified it) - var trustLevel: DeviceTrustLevel? = null, var isBlocked: Boolean = false ) : CryptoInfo { @@ -75,19 +71,6 @@ data class CryptoDeviceInfo( keys?.let { map["keys"] = it } return map } -// -// /** -// * @return a dictionary of the parameters -// */ -// fun toDeviceKeys(): DeviceKeys { -// return DeviceKeys( -// userId = userId, -// deviceId = deviceId, -// algorithms = algorithms!!, -// keys = keys!!, -// signatures = signatures!! -// ) -// } } internal fun CryptoDeviceInfo.toRest(): RestDeviceInfo { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt index cc9b3bff74..ae53694a0f 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt @@ -26,48 +26,47 @@ import java.io.Serializable @JsonClass(generateAdapter = true) data class MXDeviceInfo( - /** * The id of this device. */ @Json(name = "device_id") - var deviceId: String, + val deviceId: String, /** * the user id */ @Json(name = "user_id") - var userId: String, + val userId: String, /** * The list of algorithms supported by this device. */ @Json(name = "algorithms") - var algorithms: List? = null, + val algorithms: List? = null, /** * A map from ":" to "". */ @Json(name = "keys") - var keys: Map? = null, + val keys: Map? = null, /** * The signature of this MXDeviceInfo. * A map from "" to a map from ":" to "" */ @Json(name = "signatures") - var signatures: Map>? = null, + val signatures: Map>? = null, /* * Additional data from the home server. */ @Json(name = "unsigned") - var unsigned: JsonDict? = null, + val unsigned: JsonDict? = null, /** * Verification state of this device. */ - var verified: Int = DEVICE_VERIFICATION_UNKNOWN + val verified: Int = DEVICE_VERIFICATION_UNKNOWN ) : Serializable { /** * Tells if the device is unknown @@ -137,11 +136,11 @@ data class MXDeviceInfo( map["user_id"] = userId if (null != algorithms) { - map["algorithms"] = algorithms!! + map["algorithms"] = algorithms } if (null != keys) { - map["keys"] = keys!! + map["keys"] = keys } return map diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/OlmInboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/OlmInboundGroupSessionWrapper.kt index 361b8bc205..cf1a3b237a 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/OlmInboundGroupSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/OlmInboundGroupSessionWrapper.kt @@ -116,16 +116,16 @@ class OlmInboundGroupSessionWrapper : Serializable { return null } - MegolmSessionData().also { - it.senderClaimedEd25519Key = keysClaimed?.get("ed25519") - it.forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!) - it.senderKey = senderKey - it.senderClaimedKeys = keysClaimed - it.roomId = roomId - it.sessionId = olmInboundGroupSession!!.sessionIdentifier() - it.sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex) - it.algorithm = MXCRYPTO_ALGORITHM_MEGOLM - } + MegolmSessionData( + senderClaimedEd25519Key = keysClaimed?.get("ed25519"), + forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!), + senderKey = senderKey, + senderClaimedKeys = keysClaimed, + roomId = roomId, + sessionId = olmInboundGroupSession!!.sessionIdentifier(), + sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex), + algorithm = MXCRYPTO_ALGORITHM_MEGOLM + ) } catch (e: Exception) { Timber.e(e, "## export() : senderKey $senderKey failed") null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptionEventContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptionEventContent.kt index 6de50f84c2..05e97da68d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptionEventContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptionEventContent.kt @@ -23,22 +23,21 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class EncryptionEventContent( - /** * Required. The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'. */ @Json(name = "algorithm") - var algorithm: String, + val algorithm: String, /** * How long the session should be used before changing it. 604800000 (a week) is the recommended default. */ @Json(name = "rotation_period_ms") - var rotationPeriodMs: Long? = null, + val rotationPeriodMs: Long? = null, /** * How many messages should be sent before changing the session. 100 is the recommended default. */ @Json(name = "rotation_period_msgs") - var rotationPeriodMsgs: Long? = null + val rotationPeriodMsgs: Long? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/NewDeviceContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/NewDeviceContent.kt index a6777a4f12..62fe4293e7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/NewDeviceContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/NewDeviceContent.kt @@ -20,12 +20,11 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class NewDeviceContent( - // the device id @Json(name = "device_id") - var deviceId: String? = null, + val deviceId: String? = null, // the room ids list @Json(name = "rooms") - var rooms: List? = null + val rooms: List? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/OlmEventContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/OlmEventContent.kt index 7ac0b075be..2b2b49120a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/OlmEventContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/OlmEventContent.kt @@ -27,11 +27,11 @@ data class OlmEventContent( * */ @Json(name = "ciphertext") - var ciphertext: Map? = null, + val ciphertext: Map? = null, /** * the sender key */ @Json(name = "sender_key") - var senderKey: String? = null + val senderKey: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceInfo.kt index 1289ef3d92..b058fac082 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceInfo.kt @@ -30,31 +30,31 @@ data class DeviceInfo( * The owner user id (not documented and useless but the homeserver sent it. You should not need it) */ @Json(name = "user_id") - var user_id: String? = null, + val user_id: String? = null, /** * The device id */ @Json(name = "device_id") - var deviceId: String? = null, + val deviceId: String? = null, /** * The device display name */ @Json(name = "display_name") - var displayName: String? = null, + val displayName: String? = null, /** * The last time this device has been seen. */ @Json(name = "last_seen_ts") - var lastSeenTs: Long? = null, + val lastSeenTs: Long? = null, /** * The last ip address */ @Json(name = "last_seen_ip") - var lastSeenIp: String? = null + val lastSeenIp: String? = null ) : DatedObject { override val date: Long diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DevicesListResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DevicesListResponse.kt index 9b50b486dc..2bf3d06299 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DevicesListResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DevicesListResponse.kt @@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class DevicesListResponse( @Json(name = "devices") - var devices: List? = null + val devices: List? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt index 5e09b20c91..93e8b4d211 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt @@ -27,38 +27,38 @@ data class EncryptedFileInfo( * Required. The URL to the file. */ @Json(name = "url") - var url: String? = null, + val url: String? = null, /** * Not documented */ @Json(name = "mimetype") - var mimetype: String? = null, + val mimetype: String? = null, /** * Required. A JSON Web Key object. */ @Json(name = "key") - var key: EncryptedFileKey? = null, + val key: EncryptedFileKey? = null, /** * Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64. */ @Json(name = "iv") - var iv: String? = null, + val iv: String? = null, /** * Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. * Clients should support the SHA-256 hash, which uses the key "sha256". */ @Json(name = "hashes") - var hashes: Map? = null, + val hashes: Map? = null, /** * Required. Version of the encrypted attachments protocol. Must be "v2". */ @Json(name = "v") - var v: String? = null + val v: String? = null ) { /** * Check what the spec tells us diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt index 799819ceee..fa5885de49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt @@ -24,31 +24,31 @@ data class EncryptedFileKey( * Required. Algorithm. Must be "A256CTR". */ @Json(name = "alg") - var alg: String? = null, + val alg: String? = null, /** * Required. Extractable. Must be true. This is a W3C extension. */ @Json(name = "ext") - var ext: Boolean? = null, + val ext: Boolean? = null, /** * Required. Key operations. Must at least contain "encrypt" and "decrypt". */ @Json(name = "key_ops") - var key_ops: List? = null, + val key_ops: List? = null, /** * Required. Key type. Must be "oct". */ @Json(name = "kty") - var kty: String? = null, + val kty: String? = null, /** * Required. The key, encoded as urlsafe unpadded base64. */ @Json(name = "k") - var k: String? = null + val k: String? = null ) { /** * Check what the spec tells us @@ -62,7 +62,7 @@ data class EncryptedFileKey( return false } - if (key_ops?.contains("encrypt") != true || key_ops?.contains("decrypt") != true) { + if (key_ops?.contains("encrypt") != true || !key_ops.contains("decrypt")) { return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedMessage.kt index c546cd04c4..e3ada0c0ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedMessage.kt @@ -21,11 +21,12 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class EncryptedMessage( - var algorithm: String? = null, + @Json(name = "algorithm") + val algorithm: String? = null, @Json(name = "sender_key") - var senderKey: String? = null, + val senderKey: String? = null, @Json(name = "ciphertext") - var cipherText: Map? = null + val cipherText: Map? = null ) : SendToDeviceObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyChangesResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyChangesResponse.kt index 12d27a023f..3af7d7c8c5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyChangesResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyChangesResponse.kt @@ -25,9 +25,9 @@ import com.squareup.moshi.JsonClass internal data class KeyChangesResponse( // list of user ids which have new devices @Json(name = "changed") - var changed: List? = null, + val changed: List? = null, // List of user ids who are no more tracked. @Json(name = "left") - var left: List? = null + val left: List? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationDone.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationDone.kt index c0a72d29db..bdce77b31d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationDone.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationDone.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDon */ @JsonClass(generateAdapter = true) internal data class KeyVerificationDone( - @Json(name = "transaction_id") override var transactionID: String? = null + @Json(name = "transaction_id") override val transactionID: String? = null ) : SendToDeviceObject, VerificationInfoDone { override fun toSendToDeviceObject() = this diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt index 5bd09658b5..fcddb5c3d4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -27,7 +27,7 @@ internal data class KeyVerificationRequest( @Json(name = "from_device") override val fromDevice: String?, @Json(name = "methods") override val methods: List, @Json(name = "timestamp") override val timestamp: Long?, - @Json(name = "transaction_id") override var transactionID: String? = null + @Json(name = "transaction_id") override val transactionID: String? = null ) : SendToDeviceObject, VerificationInfoRequest { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimBody.kt index 38f6615dad..26ee1ebe38 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimBody.kt @@ -24,16 +24,15 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) internal data class KeysClaimBody( - /** * The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default. */ @Json(name = "timeout") - var timeout: Int? = null, + val timeout: Int? = null, /** * Required. The keys to be claimed. A map from user ID, to a map from device ID to algorithm name. */ @Json(name = "one_time_keys") - var oneTimeKeys: Map> + val oneTimeKeys: Map> ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimResponse.kt index 59567ba77a..3483873fbb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysClaimResponse.kt @@ -24,11 +24,10 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) internal data class KeysClaimResponse( - /** * The requested keys ordered by device by user. * TODO Type does not match spec, should be Map */ @Json(name = "one_time_keys") - var oneTimeKeys: Map>>>? = null + val oneTimeKeys: Map>>>? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryBody.kt index 3dca696fcd..da2dd781dd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryBody.kt @@ -25,12 +25,11 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) internal data class KeysQueryBody( - /** * The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default. */ @Json(name = "timeout") - var timeout: Int? = null, + val timeout: Int? = null, /** * Required. The keys to be downloaded. @@ -45,6 +44,5 @@ internal data class KeysQueryBody( * by the notification in that sync. */ @Json(name = "token") - var token: String? = null - + val token: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysUploadResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysUploadResponse.kt index 38360fa1cd..cd71749acf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysUploadResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysUploadResponse.kt @@ -23,13 +23,11 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) internal data class KeysUploadResponse( - /** * The count per algorithm as returned by the home server: a map (algorithm to count). */ @Json(name = "one_time_key_counts") - var oneTimeKeyCounts: Map? = null - + val oneTimeKeyCounts: Map? = null ) { /** * Helper methods to extract information from 'oneTimeKeyCounts' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt index 06f70ee25b..3eb6600e5e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt @@ -25,14 +25,14 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class RoomKeyRequestBody( @Json(name = "algorithm") - var algorithm: String? = null, + val algorithm: String? = null, @Json(name = "room_id") - var roomId: String? = null, + val roomId: String? = null, @Json(name = "sender_key") - var senderKey: String? = null, + val senderKey: String? = null, @Json(name = "session_id") - var sessionId: String? = null + val sessionId: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt index de2345e002..4ea95d84ae 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt @@ -15,21 +15,17 @@ */ package im.vector.matrix.android.internal.crypto.model.rest -import com.squareup.moshi.Json - /** - * Parent class representing an room key action request + * Interface representing an room key action request * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] */ -internal open class RoomKeyShare : SendToDeviceObject { +internal interface RoomKeyShare : SendToDeviceObject { - var action: String? = null + val action: String? - @Json(name = "requesting_device_id") - var requestingDeviceId: String? = null + val requestingDeviceId: String? - @Json(name = "request_id") - var requestId: String? = null + val requestId: String? companion object { const val ACTION_SHARE_REQUEST = "request" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt index fcfbfccbac..110eed04c1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt @@ -15,14 +15,21 @@ */ package im.vector.matrix.android.internal.crypto.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare.Companion.ACTION_SHARE_CANCELLATION /** - * Class representing an room key request cancellation content + * Class representing a room key request cancellation content */ @JsonClass(generateAdapter = true) -internal class RoomKeyShareCancellation : RoomKeyShare() { - init { - action = ACTION_SHARE_CANCELLATION - } -} +internal data class RoomKeyShareCancellation( + @Json(name = "action") + override val action: String? = ACTION_SHARE_CANCELLATION, + + @Json(name = "requesting_device_id") + override val requestingDeviceId: String? = null, + + @Json(name = "request_id") + override val requestId: String? = null +) : RoomKeyShare diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt index 3b9d210812..d92bc03aab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt @@ -16,16 +16,23 @@ */ package im.vector.matrix.android.internal.crypto.model.rest +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing an room key request content + * Class representing a room key request content */ @JsonClass(generateAdapter = true) -internal class RoomKeyShareRequest : RoomKeyShare() { - var body: RoomKeyRequestBody? = null +internal data class RoomKeyShareRequest( + @Json(name = "action") + override val action: String? = RoomKeyShare.ACTION_SHARE_REQUEST, - init { - action = ACTION_SHARE_REQUEST - } -} + @Json(name = "requesting_device_id") + override val requestingDeviceId: String? = null, + + @Json(name = "request_id") + override val requestId: String? = null, + + @Json(name = "body") + val body: RoomKeyRequestBody? = null +) : RoomKeyShare diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UpdateDeviceInfoBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UpdateDeviceInfoBody.kt index f2ea24a960..8ae373ba8c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UpdateDeviceInfoBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UpdateDeviceInfoBody.kt @@ -25,5 +25,5 @@ internal data class UpdateDeviceInfoBody( * The new display name for this device. If not given, the display name is unchanged. */ @Json(name = "display_name") - var displayName: String? = null + val displayName: String? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 4bc68f86c2..9627492dc7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -17,37 +17,42 @@ package im.vector.matrix.android.internal.crypto.secrets import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.events.model.toContent -import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent +import im.vector.matrix.android.api.session.securestorage.IntegrityResult import im.vector.matrix.android.api.session.securestorage.KeyInfo import im.vector.matrix.android.api.session.securestorage.KeyInfoResult import im.vector.matrix.android.api.session.securestorage.KeySigner -import im.vector.matrix.android.api.session.securestorage.SsssKeySpec -import im.vector.matrix.android.api.session.securestorage.SsssPassphrase +import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo +import im.vector.matrix.android.api.session.securestorage.SsssKeySpec +import im.vector.matrix.android.api.session.securestorage.SsssPassphrase +import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 +import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding +import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey +import im.vector.matrix.android.internal.crypto.tools.HkdfSha256 import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption -import im.vector.matrix.android.internal.crypto.tools.withOlmEncryption import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.olm.OlmPkMessage +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.Mac +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec import javax.inject.Inject - -private data class Key( - val publicKey: String, - @Suppress("ArrayInDataClass") - val privateKey: ByteArray -) +import kotlin.experimental.and internal class DefaultSharedSecretStorageService @Inject constructor( private val accountDataService: AccountDataService, @@ -57,14 +62,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor( override fun generateKey(keyId: String, keyName: String, - keySigner: KeySigner, + keySigner: KeySigner?, callback: MatrixCallback) { cryptoCoroutineScope.launch(coroutineDispatchers.main) { val key = try { - withOlmDecryption { olmPkDecryption -> - val pubKey = olmPkDecryption.generateKey() - val privateKey = olmPkDecryption.privateKey() - Key(pubKey, privateKey) + ByteArray(32).also { + SecureRandom().nextBytes(it) } } catch (failure: Throwable) { callback.onFailure(failure) @@ -73,12 +76,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor( val storageKeyContent = SecretStorageKeyContent( name = keyName, - algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2, - passphrase = null, - publicKey = key.publicKey + algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2, + passphrase = null ) - val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let { + val signedContent = keySigner?.sign(storageKeyContent.canonicalSignable())?.let { storageKeyContent.copy( signatures = it ) @@ -96,7 +98,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( callback.onSuccess(SsssKeyCreationInfo( keyId = keyId, content = storageKeyContent, - recoveryKey = computeRecoveryKey(key.privateKey) + recoveryKey = computeRecoveryKey(key) )) } } @@ -113,19 +115,9 @@ internal class DefaultSharedSecretStorageService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.main) { val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) - val pubKey = try { - withOlmDecryption { olmPkDecryption -> - olmPkDecryption.setPrivateKey(privatePart.privateKey) - } - } catch (failure: Throwable) { - callback.onFailure(failure) - return@launch - } - val storageKeyContent = SecretStorageKeyContent( - algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2, - passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt), - publicKey = pubKey + algorithm = SSSS_ALGORITHM_AES_HMAC_SHA2, + passphrase = SsssPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt) ) val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let { @@ -188,24 +180,19 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return getKey(keyId) } - override fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) { + override fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) { cryptoCoroutineScope.launch(coroutineDispatchers.main) { val encryptedContents = HashMap() try { - if (keys.isNullOrEmpty()) { - // use default key - when (val key = getDefaultKey()) { + keys.forEach { + val keyId = it.keyId + // encrypt the content + when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { is KeyInfoResult.Success -> { - if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) { - val encryptedResult = withOlmEncryption { olmEncrypt -> - olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey) - olmEncrypt.encrypt(secretBase64) + if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { + encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { + encryptedContents[key.keyInfo.id] = it } - encryptedContents[key.keyInfo.id] = EncryptedSecretContent( - ciphertext = encryptedResult.mCipherText, - ephemeral = encryptedResult.mEphemeralKey, - mac = encryptedResult.mMac - ) } else { // Unknown algorithm callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) @@ -217,34 +204,6 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return@launch } } - } else { - keys.forEach { - val keyId = it - // encrypt the content - when (val key = getKey(keyId)) { - is KeyInfoResult.Success -> { - if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) { - val encryptedResult = withOlmEncryption { olmEncrypt -> - olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey) - olmEncrypt.encrypt(secretBase64) - } - encryptedContents[keyId] = EncryptedSecretContent( - ciphertext = encryptedResult.mCipherText, - ephemeral = encryptedResult.mEphemeralKey, - mac = encryptedResult.mMac - ) - } else { - // Unknown algorithm - callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) - return@launch - } - } - is KeyInfoResult.Error -> { - callback.onFailure(key.error) - return@launch - } - } - } } accountDataService.updateAccountData( @@ -258,8 +217,109 @@ internal class DefaultSharedSecretStorageService @Inject constructor( callback.onFailure(failure) } } + } - // Add default key + /** + * Encryption algorithm m.secret_storage.v1.aes-hmac-sha2 + * Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. The data is encrypted and MACed as follows: + * + * Given the secret storage key, generate 64 bytes by performing an HKDF with SHA-256 as the hash, a salt of 32 bytes + * of 0, and with the secret name as the info. + * + * The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key + * + * Generate 16 random bytes, set bit 63 to 0 (in order to work around differences in AES-CTR implementations), and use + * this as the AES initialization vector. + * This becomes the iv property, encoded using base64. + * + * Encrypt the data using AES-CTR-256 using the AES key generated above. + * + * This encrypted data, encoded using base64, becomes the ciphertext property. + * + * Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256 using the MAC key generated above. + * The resulting MAC is base64-encoded and becomes the mac property. + * (We use AES-CTR to match file encryption and key exports.) + */ + @Throws + private fun encryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, clearDataBase64: String): EncryptedSecretContent { + secretKey as RawBytesKeySpec + val pseudoRandomKey = HkdfSha256.deriveSecret( + secretKey.privateKey, + ByteArray(32) { 0.toByte() }, + secretName.toByteArray(), + 64) + + // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key + val aesKey = pseudoRandomKey.copyOfRange(0, 32) + val macKey = pseudoRandomKey.copyOfRange(32, 64) + + val secureRandom = SecureRandom() + val iv = ByteArray(16) + secureRandom.nextBytes(iv) + + // clear bit 63 of the salt to stop us hitting the 64-bit counter boundary + // (which would mean we wouldn't be able to decrypt on Android). The loss + // of a single bit of salt is a price we have to pay. + iv[9] = iv[9] and 0x7f + + val cipher = Cipher.getInstance("AES/CTR/NoPadding") + + val secretKeySpec = SecretKeySpec(aesKey, "AES") + val ivParameterSpec = IvParameterSpec(iv) + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec) + // secret are not that big, just do Final + val cipherBytes = cipher.doFinal(clearDataBase64.fromBase64NoPadding()) + require(cipherBytes.isNotEmpty()) + + val macKeySpec = SecretKeySpec(macKey, "HmacSHA256") + val mac = Mac.getInstance("HmacSHA256") + mac.init(macKeySpec) + val digest = mac.doFinal(cipherBytes) + + return EncryptedSecretContent( + ciphertext = cipherBytes.toBase64NoPadding(), + initializationVector = iv.toBase64NoPadding(), + mac = digest.toBase64NoPadding() + ) + } + + private fun decryptAesHmacSha2(secretKey: SsssKeySpec, secretName: String, cipherContent: EncryptedSecretContent): String { + secretKey as RawBytesKeySpec + val pseudoRandomKey = HkdfSha256.deriveSecret( + secretKey.privateKey, + ByteArray(32) { 0.toByte() }, + secretName.toByteArray(), + 64) + + // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key + val aesKey = pseudoRandomKey.copyOfRange(0, 32) + val macKey = pseudoRandomKey.copyOfRange(32, 64) + + val iv = cipherContent.initializationVector?.fromBase64NoPadding() ?: ByteArray(16) + + val cipherRawBytes = cipherContent.ciphertext!!.fromBase64NoPadding() + + val cipher = Cipher.getInstance("AES/CTR/NoPadding") + + val secretKeySpec = SecretKeySpec(aesKey, "AES") + val ivParameterSpec = IvParameterSpec(iv) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + // secret are not that big, just do Final + val decryptedSecret = cipher.doFinal(cipherRawBytes) + + require(decryptedSecret.isNotEmpty()) + + // Check Signature + val macKeySpec = SecretKeySpec(macKey, "HmacSHA256") + val mac = Mac.getInstance("HmacSHA256").apply { init(macKeySpec) } + val digest = mac.doFinal(cipherRawBytes) + + if (!cipherContent.mac?.fromBase64NoPadding()?.contentEquals(digest).orFalse()) { + throw SharedSecretStorageError.BadMac + } else { + // we are good + return decryptedSecret.toBase64NoPadding() + } } override fun getAlgorithmsForSecret(name: String): List { @@ -299,7 +359,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( val algorithm = key.keyInfo.content if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? Curve25519AesSha2KeySpec ?: return Unit.also { + val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { callback.onFailure(SharedSecretStorageError.BadKeyFormat) } cryptoCoroutineScope.launch(coroutineDispatchers.main) { @@ -317,6 +377,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } }.foldToCallback(callback) } + } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) { + val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { + callback.onFailure(SharedSecretStorageError.BadKeyFormat) + } + cryptoCoroutineScope.launch(coroutineDispatchers.main) { + kotlin.runCatching { + decryptAesHmacSha2(keySpec, name, secretContent) + }.foldToCallback(callback) + } } else { callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")) } @@ -327,4 +396,37 @@ internal class DefaultSharedSecretStorageService @Inject constructor( const val ENCRYPTED = "encrypted" const val DEFAULT_KEY_ID = "m.secret_storage.default_key" } + + override fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?): IntegrityResult { + if (secretNames.isEmpty()) { + return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret("none")) + } + + val keyInfoResult = if (keyId == null) { + getDefaultKey() + } else { + getKey(keyId) + } + + val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo + ?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: "")) + + if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2 + || keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) { + // Unsupported algorithm + return IntegrityResult.Error( + SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "") + ) + } + + secretNames.forEach { secretName -> + val secretEvent = accountDataService.getAccountDataEvent(secretName) + ?: return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret(secretName)) + if ((secretEvent.content["encrypted"] as? Map<*, *>)?.get(keyInfo.id) == null) { + return IntegrityResult.Error(SharedSecretStorageError.SecretNotEncryptedWithKey(secretName, keyInfo.id)) + } + } + + return IntegrityResult.Success(keyInfo.content.passphrase != null) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 3a12df2cd7..53950e0fcc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -20,6 +20,7 @@ package im.vector.matrix.android.internal.crypto.store import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest @@ -382,7 +383,7 @@ internal interface IMXCryptoStore { * * @param incomingRoomKeyRequest the incoming key request */ - fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest) + fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon) /** * Search an IncomingRoomKeyRequest @@ -412,6 +413,8 @@ internal interface IMXCryptoStore { fun getLiveCrossSigningInfo(userId: String) : LiveData> fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) + fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) + fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) fun getCrossSigningPrivateKeys() : PrivateKeysInfo? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 00a496cae4..cf95553102 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest @@ -888,7 +889,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest) { + override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon) { doRealmTransaction(realmConfiguration) { it.where() .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) @@ -1093,6 +1094,23 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where().findFirst()?.userId?.let { myUserId -> + CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> + val level = xInfoEntity.trustLevelEntity + if (level == null) { + val newLevel = realm.createObject(TrustLevelEntity::class.java) + newLevel.locallyVerified = trusted + xInfoEntity.trustLevelEntity = newLevel + } else { + level.locallyVerified = trusted + } + } + } + } + } + private fun addOrUpdateCrossSigningInfo(realm: Realm, userId: String, info: MXCrossSigningInfo?): CrossSigningInfoEntity? { var existing = CrossSigningInfoEntity.get(realm, userId) if (info == null) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt index 9b1d116aa9..38cece99ac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt @@ -32,17 +32,17 @@ internal open class IncomingRoomKeyRequestEntity( ) : RealmObject() { fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest { - return IncomingRoomKeyRequest().also { - it.requestId = requestId - it.userId = userId - it.deviceId = deviceId - it.requestBody = RoomKeyRequestBody().apply { - algorithm = requestBodyAlgorithm - roomId = requestBodyRoomId - senderKey = requestBodySenderKey - sessionId = requestBodySessionId - } - } + return IncomingRoomKeyRequest( + requestId = requestId, + userId = userId, + deviceId = deviceId, + requestBody = RoomKeyRequestBody( + algorithm = requestBodyAlgorithm, + roomId = requestBodyRoomId, + senderKey = requestBodySenderKey, + sessionId = requestBodySessionId + ) + ) } fun putRequestBody(requestBody: RoomKeyRequestBody?) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt index 3130bd2f89..86fc177f2b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt @@ -43,12 +43,12 @@ internal open class OutgoingRoomKeyRequestEntity( fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest { val cancellationTxnId = this.cancellationTxnId return OutgoingRoomKeyRequest( - RoomKeyRequestBody().apply { - algorithm = requestBodyAlgorithm - roomId = requestBodyRoomId - senderKey = requestBodySenderKey - sessionId = requestBodySessionId - }, + RoomKeyRequestBody( + algorithm = requestBodyAlgorithm, + roomId = requestBodyRoomId, + senderKey = requestBodySenderKey, + sessionId = requestBodySessionId + ), getRecipients()!!, requestId!!, OutgoingRoomKeyRequest.RequestState.from(state) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DownloadKeysForUsersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DownloadKeysForUsersTask.kt index 2e11bb1b3e..94fe3c1e8d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DownloadKeysForUsersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DownloadKeysForUsersTask.kt @@ -29,7 +29,8 @@ internal interface DownloadKeysForUsersTask : Task?, // the up-to token - val token: String?) + val token: String? + ) } internal class DefaultDownloadKeysForUsers @Inject constructor( @@ -41,13 +42,10 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( val downloadQuery = params.userIds?.associateWith { emptyMap() }.orEmpty() val body = KeysQueryBody( - deviceKeys = downloadQuery + deviceKeys = downloadQuery, + token = params.token?.takeIf { it.isNotEmpty() } ) - if (!params.token.isNullOrEmpty()) { - body.token = params.token - } - return executeRequest(eventBus) { apiCall = cryptoApi.downloadKeysForUsers(body) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index d6118867ea..477389a081 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -83,11 +83,14 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( } Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") + // Relates to is not encrypted + val relatesToEventId = event.content.toModel()?.relatesTo?.eventId + if (event.senderId == userId) { // If it's send from me, we need to keep track of Requests or Start // done from another device of mine - if (EventType.MESSAGE == event.type) { + if (EventType.MESSAGE == event.getClearType()) { val msgType = event.getClearContent().toModel()?.msgType if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { event.getClearContent().toModel()?.let { @@ -98,26 +101,26 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( } } } - } else if (EventType.KEY_VERIFICATION_START == event.type) { + } else if (EventType.KEY_VERIFICATION_START == event.getClearType()) { event.getClearContent().toModel()?.let { if (it.fromDevice != deviceId) { // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") - it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") + relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } params.verificationService.onRoomRequestHandledByOtherDevice(event) } } - } else if (EventType.KEY_VERIFICATION_READY == event.type) { + } else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) { event.getClearContent().toModel()?.let { if (it.fromDevice != deviceId) { // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") - it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") + relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } params.verificationService.onRoomRequestHandledByOtherDevice(event) } } - } else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) { - event.getClearContent().toModel()?.relatesTo?.eventId?.let { + } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) { + relatesToEventId?.let { transactionsHandledByOtherDevice.remove(it) params.verificationService.onRoomRequestHandledByOtherDevice(event) } @@ -127,10 +130,9 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( return@forEach } - val relatesTo = event.getClearContent().toModel()?.relatesTo?.eventId - if (relatesTo != null && transactionsHandledByOtherDevice.contains(relatesTo)) { + if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) { // Ignore this event, it is directed to another of my devices - Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesTo ") + Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesToEventId ") return@forEach } when (event.getClearType()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt new file mode 100644 index 0000000000..4a24e054ac --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * Copyright (C) 2015 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.internal.crypto.tools + +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import kotlin.math.ceil + +/** + * HMAC-based Extract-and-Expand Key Derivation Function (HkdfSha256) + * [RFC-5869] https://tools.ietf.org/html/rfc5869 + */ +object HkdfSha256 { + + public fun deriveSecret(inputKeyMaterial: ByteArray, salt: ByteArray?, info: ByteArray, outputLength: Int): ByteArray { + return expand(extract(salt, inputKeyMaterial), info, outputLength) + } + + /** + * HkdfSha256-Extract(salt, IKM) -> PRK + * + * @param salt optional salt value (a non-secret random value); + * if not provided, it is set to a string of HashLen (size in octets) zeros. + * @param ikm input keying material + */ + private fun extract(salt: ByteArray?, ikm: ByteArray): ByteArray { + val mac = initMac(salt ?: ByteArray(HASH_LEN) { 0.toByte() }) + return mac.doFinal(ikm) + } + + /** + * HkdfSha256-Expand(PRK, info, L) -> OKM + * + * @param prk a pseudorandom key of at least HashLen bytes (usually, the output from the extract step) + * @param info optional context and application specific information (can be empty) + * @param outputLength length of output keying material in bytes (<= 255*HashLen) + * @return OKM output keying material + */ + private fun expand(prk: ByteArray, info: ByteArray = ByteArray(0), outputLength: Int): ByteArray { + require(outputLength <= 255 * HASH_LEN) { "outputLength must be less than or equal to 255*HashLen" } + + /* + The output OKM is calculated as follows: + Notation | -> When the message is composed of several elements we use concatenation (denoted |) in the second argument; + + + N = ceil(L/HashLen) + T = T(1) | T(2) | T(3) | ... | T(N) + OKM = first L octets of T + + where: + T(0) = empty string (zero length) + T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) + T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) + T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) + ... + */ + val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt() + + var stepHash = ByteArray(0) // T(0) empty string (zero length) + + val generatedBytes = ByteArrayOutputStream() // ByteBuffer.allocate(Math.multiplyExact(n, HASH_LEN)) + val mac = initMac(prk) + for (roundNum in 1..n) { + mac.reset() + val t = ByteBuffer.allocate(stepHash.size + info.size + 1).apply { + put(stepHash) + put(info) + put(roundNum.toByte()) + } + stepHash = mac.doFinal(t.array()) + generatedBytes.write(stepHash) + } + + return generatedBytes.toByteArray().sliceArray(0 until outputLength) + } + + private fun initMac(secret: ByteArray): Mac { + val mac = Mac.getInstance(HASH_ALG) + mac.init(SecretKeySpec(secret, HASH_ALG)) + return mac + } + + private const val HASH_LEN = 32 + private const val HASH_ALG = "HmacSHA256" +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index fa1bfb3b98..77ab218164 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -255,7 +255,7 @@ internal class DefaultVerificationService @Inject constructor( } fun onRoomRequestHandledByOtherDevice(event: Event) { - val requestInfo = event.getClearContent().toModel() + val requestInfo = event.content.toModel() ?: return val requestId = requestInfo.relatesTo?.eventId ?: return getExistingVerificationRequestInRoom(event.roomId ?: "", requestId)?.let { @@ -465,7 +465,11 @@ internal class DefaultVerificationService @Inject constructor( Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") // If there is a corresponding request, we can auto accept // as we are the one requesting in first place (or we accepted the request) - val autoAccept = getExistingVerificationRequest(otherUserId)?.any { it.transactionId == startReq.transactionID } + // I need to check if the pending request was related to this device also + val autoAccept = getExistingVerificationRequest(otherUserId)?.any { + it.transactionId == startReq.transactionID + && (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId) + } ?: false val tx = DefaultIncomingSASDefaultVerificationTransaction( // this, @@ -1083,8 +1087,12 @@ internal class DefaultVerificationService @Inject constructor( } .distinct() - transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, _ -> + transport.sendVerificationRequest(methodValues, localID, otherUserId, null, targetDevices) { _, info -> // Nothing special to do in to device mode + updatePendingRequest(verificationRequest.copy( + // localId stays different + requestInfo = info + )) } requestsForUser.add(verificationRequest) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 22f543f267..17d0db0450 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -312,7 +312,7 @@ internal abstract class SASDefaultVerificationTransaction( if (otherUserId == userId) { // If me it's reasonable to sign and upload the device signature // Notice that i might not have the private keys, so may not be able to do it - crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { + crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback { override fun onFailure(failure: Throwable) { Timber.w(failure, "## SAS Verification: Failed to sign new device $otherDeviceId") } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 743100ee45..b423bd0ea8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -222,20 +222,25 @@ internal class DefaultQrCodeVerificationTransaction( private fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List) { // If not me sign his MSK and upload the signature - if (otherUserId != userId && canTrustOtherUserMasterKey) { - // we should trust this master key - // And check verification MSK -> SSK? - crossSigningService.trustUser(otherUserId, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") - } - }) + if (canTrustOtherUserMasterKey) { + if (otherUserId != userId) { + // we should trust this master key + // And check verification MSK -> SSK? + crossSigningService.trustUser(otherUserId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## QR Verification: Failed to trust User $otherUserId") + } + }) + } else { + // Mark my keys as trusted locally + crossSigningService.markMyMasterKeyAsTrusted() + } } if (otherUserId == userId) { // If me it's reasonable to sign and upload the device signature // Notice that i might not have the private keys, so may not be able to do it - crossSigningService.signDevice(otherDeviceId!!, object : MatrixCallback { + crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback { override fun onFailure(failure: Throwable) { Timber.w(failure, "## QR Verification: Failed to sign new device $otherDeviceId") } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index de01aab4e8..2f3cdb9545 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -16,19 +16,12 @@ package im.vector.matrix.android.internal.database.mapper -import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag -import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.database.model.RoomSummaryEntity -import timber.log.Timber -import java.util.UUID import javax.inject.Inject -internal class RoomSummaryMapper @Inject constructor( - private val cryptoService: CryptoService, - private val timelineEventMapper: TimelineEventMapper -) { +internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { val tags = roomSummaryEntity.tags.map { @@ -38,21 +31,6 @@ internal class RoomSummaryMapper @Inject constructor( val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let { timelineEventMapper.map(it, buildReadReceipts = false) } - if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) { - // TODO use a global event decryptor? attache to session and that listen to new sessionId? - // for now decrypt sync - try { - val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString()) - latestEvent.root.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: Throwable) { - Timber.d(e) - } - } return RoomSummary( roomId = roomSummaryEntity.roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index cd4e9abbc1..3164bf75c0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -17,7 +17,21 @@ package im.vector.matrix.android.internal.di import com.squareup.moshi.Moshi -import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent +import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent +import im.vector.matrix.android.api.session.room.model.message.MessageFileContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageLocationContent +import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent +import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent +import im.vector.matrix.android.api.session.room.model.message.MessagePollResponseContent +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent +import im.vector.matrix.android.internal.network.parsing.ForceToBooleanJsonAdapter import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData @@ -31,6 +45,7 @@ object MoshiProvider { private val moshi: Moshi = Moshi.Builder() .add(UriMoshiAdapter()) + .add(ForceToBooleanJsonAdapter()) .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/ForceToBoolean.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/ForceToBoolean.kt new file mode 100644 index 0000000000..b823104b4d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/ForceToBoolean.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.network.parsing + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonQualifier +import com.squareup.moshi.JsonReader +import com.squareup.moshi.ToJson +import timber.log.Timber + +@JsonQualifier +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION) +annotation class ForceToBoolean + +internal class ForceToBooleanJsonAdapter { + @ToJson + fun toJson(@ForceToBoolean b: Boolean): Boolean { + return b + } + + @FromJson + @ForceToBoolean + fun fromJson(reader: JsonReader): Boolean { + return when (val token = reader.peek()) { + JsonReader.Token.NUMBER -> reader.nextInt() != 0 + JsonReader.Token.BOOLEAN -> reader.nextBoolean() + else -> { + Timber.e("Expecting a boolean or a int but get: $token") + reader.skipValue() + false + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index e7cb963c5f..84b76345c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -49,6 +49,7 @@ import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.WorkManagerProvider +import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncWorker @@ -93,6 +94,7 @@ internal class DefaultSession @Inject constructor( private val homeServerCapabilitiesService: Lazy, private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, + private val timelineEventDecryptor: TimelineEventDecryptor, private val shieldTrustUpdater: ShieldTrustUpdater) : Session, RoomService by roomService.get(), @@ -126,6 +128,7 @@ internal class DefaultSession @Inject constructor( isOpen = true liveEntityObservers.forEach { it.start() } eventBus.register(this) + timelineEventDecryptor.start() shieldTrustUpdater.start() } @@ -163,6 +166,7 @@ internal class DefaultSession @Inject constructor( override fun close() { assert(isOpen) stopSync() + timelineEventDecryptor.destroy() liveEntityObservers.forEach { it.dispose() } cryptoService.get().close() isOpen = false @@ -217,4 +221,9 @@ internal class DefaultSession @Inject constructor( override fun removeListener(listener: Session.Listener) { sessionListeners.removeListener(listener) } + + // For easy debugging + override fun toString(): String { + return "$myUserId - ${sessionParams.credentials.deviceId}" + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt index 9acdacd897..fc0472e32f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt @@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class Filter( - @Json(name = "limit") var limit: Int? = null, - @Json(name = "senders") var senders: MutableList? = null, - @Json(name = "not_senders") var notSenders: MutableList? = null, - @Json(name = "types") var types: MutableList? = null, - @Json(name = "not_types") var notTypes: MutableList? = null, - @Json(name = "rooms") var rooms: MutableList? = null, - @Json(name = "not_rooms") var notRooms: MutableList? = null + @Json(name = "limit") val limit: Int? = null, + @Json(name = "senders") val senders: List? = null, + @Json(name = "not_senders") val notSenders: List? = null, + @Json(name = "types") val types: List? = null, + @Json(name = "not_types") val notTypes: List? = null, + @Json(name = "rooms") val rooms: List? = null, + @Json(name = "not_rooms") val notRooms: List? = null ) { fun hasData(): Boolean { return (limit != null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt index fa66470c9b..535c66f637 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt @@ -26,11 +26,11 @@ import im.vector.matrix.android.internal.di.MoshiProvider */ @JsonClass(generateAdapter = true) internal data class FilterBody( - @Json(name = "event_fields") var eventFields: List? = null, - @Json(name = "event_format") var eventFormat: String? = null, - @Json(name = "presence") var presence: Filter? = null, - @Json(name = "account_data") var accountData: Filter? = null, - @Json(name = "room") var room: RoomFilter? = null + @Json(name = "event_fields") val eventFields: List? = null, + @Json(name = "event_format") val eventFormat: String? = null, + @Json(name = "presence") val presence: Filter? = null, + @Json(name = "account_data") val accountData: Filter? = null, + @Json(name = "room") val room: RoomFilter? = null ) { fun toJSONString(): String { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt index 86c94d3dfa..a070759de9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt @@ -21,32 +21,30 @@ import im.vector.matrix.android.api.session.events.model.EventType internal object FilterFactory { fun createDefaultFilterBody(): FilterBody { - val filterBody = FilterBody() - FilterUtil.enableLazyLoading(filterBody, true) - return filterBody + return FilterUtil.enableLazyLoading(FilterBody(), true) } fun createRiotFilterBody(): FilterBody { - val filterBody = FilterBody() - filterBody.room = RoomFilter().apply { - timeline = createRiotTimelineFilter() - state = createRiotStateFilter() - } - return filterBody + return FilterBody( + room = RoomFilter( + timeline = createRiotTimelineFilter(), + state = createRiotStateFilter() + ) + ) } fun createDefaultRoomFilter(): RoomEventFilter { - return RoomEventFilter().apply { - lazyLoadMembers = true - } + return RoomEventFilter( + lazyLoadMembers = true + ) } fun createRiotRoomFilter(): RoomEventFilter { - return RoomEventFilter().apply { - lazyLoadMembers = true - // TODO Enable this for optimization - // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() - } + return RoomEventFilter( + lazyLoadMembers = true + // TODO Enable this for optimization + // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() + ) } private fun createRiotTimelineFilter(): RoomEventFilter { @@ -57,9 +55,9 @@ internal object FilterFactory { } private fun createRiotStateFilter(): RoomEventFilter { - return RoomEventFilter().apply { - lazyLoadMembers = true - } + return RoomEventFilter( + lazyLoadMembers = true + ) } // Get only managed types by Riot diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt index b27ddae9d3..75e2c23da9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt @@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class FilterResponse( - @Json(name = "filter_id") var filterId: String + @Json(name = "filter_id") val filterId: String ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt index d3020b3fa6..3f4e61e6b5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt @@ -21,7 +21,6 @@ internal object FilterUtil { /** * Patch the filterBody to enable or disable the data save mode * - * * If data save mode is on, FilterBody will contains * FIXME New expected filter: * "{\"room\": {\"ephemeral\": {\"notTypes\": [\"m.typing\"]}}, \"presence\":{\"notTypes\": [\"*\"]}}" @@ -29,6 +28,7 @@ internal object FilterUtil { * @param filterBody filterBody to patch * @param useDataSaveMode true to enable data save mode */ + /* fun enableDataSaveMode(filterBody: FilterBody, useDataSaveMode: Boolean) { if (useDataSaveMode) { // Enable data save mode @@ -78,10 +78,10 @@ internal object FilterUtil { filterBody.presence = null } } - } + } */ /** - * Patch the filterBody to enable or disable the lazy loading + * Compute a new filterBody to enable or disable the lazy loading * * * If lazy loading is on, the filterBody will looks like @@ -90,29 +90,23 @@ internal object FilterUtil { * @param filterBody filterBody to patch * @param useLazyLoading true to enable lazy loading */ - fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean) { + fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean): FilterBody { if (useLazyLoading) { // Enable lazy loading - if (filterBody.room == null) { - filterBody.room = RoomFilter() - } - if (filterBody.room!!.state == null) { - filterBody.room!!.state = RoomEventFilter() - } - - filterBody.room!!.state!!.lazyLoadMembers = true + return filterBody.copy( + room = filterBody.room?.copy( + state = filterBody.room.state?.copy(lazyLoadMembers = true) + ?: RoomEventFilter(lazyLoadMembers = true) + ) + ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true)) + ) } else { - if (filterBody.room != null && filterBody.room!!.state != null) { - filterBody.room!!.state!!.lazyLoadMembers = null + val newRoomEventFilter = filterBody.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() } + val newRoomFilter = filterBody.room?.copy(state = newRoomEventFilter)?.takeIf { it.hasData() } - if (!filterBody.room!!.state!!.hasData()) { - filterBody.room!!.state = null - } - - if (!filterBody.room!!.hasData()) { - filterBody.room = null - } - } + return filterBody.copy( + room = newRoomFilter + ) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt index ee81e399ee..9cdccc5c8b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt @@ -26,14 +26,14 @@ import im.vector.matrix.android.internal.di.MoshiProvider @JsonClass(generateAdapter = true) data class RoomEventFilter( @Json(name = "limit") var limit: Int? = null, - @Json(name = "not_senders") var notSenders: MutableList? = null, - @Json(name = "not_types") var notTypes: MutableList? = null, - @Json(name = "senders") var senders: MutableList? = null, - @Json(name = "types") var types: MutableList? = null, - @Json(name = "rooms") var rooms: MutableList? = null, - @Json(name = "not_rooms") var notRooms: List? = null, - @Json(name = "contains_url") var containsUrl: Boolean? = null, - @Json(name = "lazy_load_members") var lazyLoadMembers: Boolean? = null + @Json(name = "not_senders") val notSenders: List? = null, + @Json(name = "not_types") val notTypes: List? = null, + @Json(name = "senders") val senders: List? = null, + @Json(name = "types") val types: List? = null, + @Json(name = "rooms") val rooms: List? = null, + @Json(name = "not_rooms") val notRooms: List? = null, + @Json(name = "contains_url") val containsUrl: Boolean? = null, + @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null ) { fun toJSONString(): String { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt index 4742bdb988..3109763570 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt @@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class RoomFilter( - @Json(name = "not_rooms") var notRooms: List? = null, - @Json(name = "rooms") var rooms: List? = null, - @Json(name = "ephemeral") var ephemeral: RoomEventFilter? = null, - @Json(name = "include_leave") var includeLeave: Boolean? = null, - @Json(name = "state") var state: RoomEventFilter? = null, - @Json(name = "timeline") var timeline: RoomEventFilter? = null, - @Json(name = "account_data") var accountData: RoomEventFilter? = null + @Json(name = "not_rooms") val notRooms: List? = null, + @Json(name = "rooms") val rooms: List? = null, + @Json(name = "ephemeral") val ephemeral: RoomEventFilter? = null, + @Json(name = "include_leave") val includeLeave: Boolean? = null, + @Json(name = "state") val state: RoomEventFilter? = null, + @Json(name = "timeline") val timeline: RoomEventFilter? = null, + @Json(name = "account_data") val accountData: RoomEventFilter? = null ) { fun hasData(): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index e6e2b16477..17541de9aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room import com.zhuinden.monarchy.Monarchy +import dagger.Lazy import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel @@ -41,17 +42,20 @@ import im.vector.matrix.android.internal.database.query.whereType import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper +import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor import im.vector.matrix.android.internal.session.sync.RoomSyncHandler import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications import io.realm.Realm import org.greenrobot.eventbus.EventBus +import timber.log.Timber import javax.inject.Inject internal class RoomSummaryUpdater @Inject constructor( @UserId private val userId: String, private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver, + private val timelineEventDecryptor: Lazy, private val eventBus: EventBus, private val monarchy: Monarchy) { @@ -141,6 +145,11 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.inviterId = null } + if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) { + Timber.v("Should decrypt ${latestPreviewableEvent.eventId}") + timelineEventDecryptor.get().requestDecryption(TimelineEventDecryptor.DecryptionRequest(latestPreviewableEvent.eventId, "")) + } + if (updateMembers) { val otherRoomMembers = RoomMemberHelper(realm, roomId) .queryRoomMembersEvent() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 19b87122e8..53bd620e51 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.session.room.timeline import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel @@ -73,11 +72,11 @@ internal class DefaultTimeline( private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, private val paginationTask: PaginationTask, - private val cryptoService: CryptoService, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, private val hiddenReadReceipts: TimelineHiddenReadReceipts, - private val eventBus: EventBus + private val eventBus: EventBus, + private val eventDecryptor: TimelineEventDecryptor ) : Timeline, TimelineHiddenReadReceipts.Delegate { data class OnNewTimelineEvents(val roomId: String, val eventIds: List) @@ -114,8 +113,6 @@ internal class DefaultTimeline( override val isLive get() = !hasMoreToLoad(Timeline.Direction.FORWARDS) - private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService) - private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> if (!results.isLoaded || !results.isValid) { return@OrderedRealmCollectionChangeListener @@ -607,7 +604,7 @@ internal class DefaultTimeline( if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult == null) { - timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } + timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(it, timelineID)) } } val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 3e783f98a4..c02bb915ef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.Transformations import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineService @@ -41,7 +40,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv private val eventBus: EventBus, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, - private val cryptoService: CryptoService, + private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper @@ -60,11 +59,11 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv taskExecutor = taskExecutor, contextOfEventTask = contextOfEventTask, paginationTask = paginationTask, - cryptoService = cryptoService, timelineEventMapper = timelineEventMapper, settings = settings, hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), - eventBus = eventBus + eventBus = eventBus, + eventDecryptor = eventDecryptor ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt index 800e6c8d11..2d6656c2e3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -23,15 +23,19 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.session.SessionScope import io.realm.Realm import io.realm.RealmConfiguration import timber.log.Timber import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +import javax.inject.Inject -internal class TimelineEventDecryptor( +@SessionScope +internal class TimelineEventDecryptor @Inject constructor( + @SessionDatabase private val realmConfiguration: RealmConfiguration, - private val timelineId: String, private val cryptoService: CryptoService ) { @@ -53,9 +57,9 @@ internal class TimelineEventDecryptor( private var executor: ExecutorService? = null // Set of eventIds which are currently decrypting - private val existingRequests = mutableSetOf() + private val existingRequests = mutableSetOf() // sessionId -> list of eventIds - private val unknownSessionsFailure = mutableMapOf>() + private val unknownSessionsFailure = mutableMapOf>() fun start() { executor = Executors.newSingleThreadExecutor() @@ -74,53 +78,51 @@ internal class TimelineEventDecryptor( } } - fun requestDecryption(eventId: String) { + fun requestDecryption(request: DecryptionRequest) { synchronized(unknownSessionsFailure) { - for (eventIds in unknownSessionsFailure.values) { - if (eventId in eventIds) { - Timber.d("Skip Decryption request for event $eventId, unknown session") + for (requests in unknownSessionsFailure.values) { + if (request in requests) { + Timber.d("Skip Decryption request for event ${request.eventId}, unknown session") return } } } synchronized(existingRequests) { - if (!existingRequests.add(eventId)) { - Timber.d("Skip Decryption request for event $eventId, already requested") + if (!existingRequests.add(request)) { + Timber.d("Skip Decryption request for event ${request.eventId}, already requested") return } } executor?.execute { Realm.getInstance(realmConfiguration).use { realm -> - processDecryptRequest(eventId, realm) + processDecryptRequest(request, realm) } } } - private fun processDecryptRequest(eventId: String, realm: Realm) { + private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) = realm.executeTransaction { + val eventId = request.eventId + val timelineId = request.timelineId Timber.v("Decryption request for event $eventId") val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() - ?: return Unit.also { + ?: return@executeTransaction Unit.also { Timber.d("Decryption request for unknown message") } val event = eventEntity.asDomain() try { val result = cryptoService.decryptEvent(event, timelineId) Timber.v("Successfully decrypted event $eventId") - realm.executeTransaction { - eventEntity.setDecryptionResult(result) - } + eventEntity.setDecryptionResult(result) } catch (e: MXCryptoError) { Timber.w(e, "Failed to decrypt event $eventId") if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { // Keep track of unknown sessions to automatically try to decrypt on new session - realm.executeTransaction { - eventEntity.decryptionErrorCode = e.errorType.name - } + eventEntity.decryptionErrorCode = e.errorType.name event.content?.toModel()?.let { content -> content.sessionId?.let { sessionId -> synchronized(unknownSessionsFailure) { val list = unknownSessionsFailure.getOrPut(sessionId) { mutableSetOf() } - list.add(eventId) + list.add(request) } } } @@ -129,8 +131,13 @@ internal class TimelineEventDecryptor( Timber.e(t, "Failed to decrypt event $eventId") } finally { synchronized(existingRequests) { - existingRequests.remove(eventId) + existingRequests.remove(request) } } } + + data class DecryptionRequest( + val eventId: String, + val timelineId: String + ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/GroupSyncProfile.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/GroupSyncProfile.kt index 6d31e84d61..00e3377aa9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/GroupSyncProfile.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/GroupSyncProfile.kt @@ -24,10 +24,10 @@ internal data class GroupSyncProfile( /** * The name of the group, if any. May be nil. */ - @Json(name = "name") var name: String? = null, + @Json(name = "name") val name: String? = null, /** * The URL for the group's avatar. May be nil. */ - @Json(name = "avatar_url") var avatarUrl: String? = null + @Json(name = "avatar_url") val avatarUrl: String? = null ) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 5c5b600828..a0bd725118 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -67,9 +67,10 @@ DO NOT COMMIT ### invalid formatting \s{8}/\*\n \* -[^\w]if\( -while\( -for\( +# Now checked by ktlint +# [^\w]if\( +# while\( +# for\( # Add space after // # DISABLED To re-enable when code will be formatted globally diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index defb1386d1..488f357c4b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -141,6 +141,7 @@ + diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index fd15dc3387..04d4bd0f4c 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -26,6 +26,7 @@ import im.vector.riotx.core.preference.UserAvatarPreference import im.vector.riotx.features.MainActivity import im.vector.riotx.features.createdirect.CreateDirectRoomActivity import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity +import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.debug.DebugMenuActivity import im.vector.riotx.features.home.HomeActivity @@ -152,6 +153,8 @@ interface ScreenComponent { fun inject(deviceListBottomSheet: DeviceListBottomSheet) + fun inject(activity: SharedSecureStorageActivity) + fun inject(bigImageViewerActivity: BigImageViewerActivity) @Component.Factory diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt new file mode 100644 index 0000000000..c2c2b9c42a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageAction.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.crypto.quads + +import im.vector.riotx.core.platform.VectorViewEvents +import im.vector.riotx.core.platform.VectorViewModelAction +import im.vector.riotx.core.platform.WaitingViewData + +sealed class SharedSecureStorageAction : VectorViewModelAction { + + object TogglePasswordVisibility : SharedSecureStorageAction() + object Cancel : SharedSecureStorageAction() + data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction() +} + +sealed class SharedSecureStorageViewEvent : VectorViewEvents { + + object Dismiss : SharedSecureStorageViewEvent() + data class FinishSuccess(val cypherResult: String) : SharedSecureStorageViewEvent() + data class Error(val message: String, val dismiss: Boolean = false) : SharedSecureStorageViewEvent() + data class InlineError(val message: String) : SharedSecureStorageViewEvent() + object ShowModalLoading : SharedSecureStorageViewEvent() + object HideModalLoading : SharedSecureStorageViewEvent() + data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent() +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt new file mode 100644 index 0000000000..1347b6ca19 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageActivity.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.crypto.quads + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import androidx.appcompat.app.AlertDialog +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.viewModel +import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.extensions.addFragment +import im.vector.riotx.core.platform.SimpleFragmentActivity +import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.activity.* +import javax.inject.Inject + +class SharedSecureStorageActivity : SimpleFragmentActivity() { + + @Parcelize + data class Args( + val keyId: String?, + val requestedSecrets: List, + val resultKeyStoreAlias: String + ) : Parcelable + + private val viewModel: SharedSecureStorageViewModel by viewModel() + @Inject lateinit var viewModelFactory: SharedSecureStorageViewModel.Factory + @Inject lateinit var errorFormatter: ErrorFormatter + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + toolbar.visibility = View.GONE + if (isFirstCreation()) { + addFragment(R.id.container, SharedSecuredStoragePassphraseFragment::class.java) + } + + viewModel.viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + observeViewEvents(it) + } + .disposeOnDestroy() + + viewModel.subscribe(this) { + // renderState(it) + } + } + + private fun observeViewEvents(it: SharedSecureStorageViewEvent?) { + when (it) { + is SharedSecureStorageViewEvent.Dismiss -> { + finish() + } + is SharedSecureStorageViewEvent.Error -> { + AlertDialog.Builder(this) + .setTitle(getString(R.string.dialog_title_error)) + .setMessage(it.message) + .setCancelable(false) + .setPositiveButton(R.string.ok) { _, _ -> + if (it.dismiss) { + finish() + } + } + .show() + } + is SharedSecureStorageViewEvent.ShowModalLoading -> { + showWaitingView() + } + is SharedSecureStorageViewEvent.HideModalLoading -> { + hideWaitingView() + } + is SharedSecureStorageViewEvent.UpdateLoadingState -> { + updateWaitingView(it.waitingData) + } + is SharedSecureStorageViewEvent.FinishSuccess -> { + val dataResult = Intent() + dataResult.putExtra(EXTRA_DATA_RESULT, it.cypherResult) + setResult(Activity.RESULT_OK, dataResult) + finish() + } + } + } + + companion object { + const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT" + const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity" + + fun newIntent(context: Context, + keyId: String? = null, + requestedSecrets: List, + resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent { + require(requestedSecrets.isNotEmpty()) + return Intent(context, SharedSecureStorageActivity::class.java).also { + it.putExtra(MvRx.KEY_ARG, Args( + keyId, + requestedSecrets, + resultKeyStoreAlias + )) + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt new file mode 100644 index 0000000000..a9f5d33888 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.crypto.quads + +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.securestorage.IntegrityResult +import im.vector.matrix.android.api.session.securestorage.KeyInfoResult +import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec +import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding +import im.vector.matrix.android.internal.util.awaitCallback +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.platform.WaitingViewData +import im.vector.riotx.core.resources.StringProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream + +data class SharedSecureStorageViewState( + val passphraseVisible: Boolean = false +) : MvRxState + +class SharedSecureStorageViewModel @AssistedInject constructor( + @Assisted initialState: SharedSecureStorageViewState, + @Assisted val args: SharedSecureStorageActivity.Args, + private val stringProvider: StringProvider, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: SharedSecureStorageViewState, args: SharedSecureStorageActivity.Args): SharedSecureStorageViewModel + } + + init { + val isValid = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(args.requestedSecrets, args.keyId) is IntegrityResult.Success + if (!isValid) { + _viewEvents.post( + SharedSecureStorageViewEvent.Error( + stringProvider.getString(R.string.enter_secret_storage_invalid), + true + ) + ) + } + } + + override fun handle(action: SharedSecureStorageAction) = withState { + when (action) { + is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility() + is SharedSecureStorageAction.Cancel -> handleCancel() + is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) + } + } + + private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { + val decryptedSecretMap = HashMap() + GlobalScope.launch(Dispatchers.IO) { + runCatching { + _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) + val passphrase = action.passphrase + val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() + if (!keyInfoResult.isSuccess()) { + _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + _viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key")) + return@launch + } + val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo + + _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = true + ) + )) + val keySpec = RawBytesKeySpec.fromPassphrase( + passphrase, + keyInfo.content.passphrase?.salt ?: "", + keyInfo.content.passphrase?.iterations ?: 0, + // TODO + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = false, + progress = progress, + progressTotal = total + ) + )) + } + } + ) + + withContext(Dispatchers.IO) { + args.requestedSecrets.forEach { + val res = awaitCallback { callback -> + session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec, + callback = callback) + } + decryptedSecretMap[it] = res + } + } + }.fold({ + _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + val safeForIntentCypher = ByteArrayOutputStream().also { + it.use { + session.securelyStoreObject(decryptedSecretMap as Map, args.resultKeyStoreAlias, it) + } + }.toByteArray().toBase64NoPadding() + _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) + }, { + _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + _viewEvents.post(SharedSecureStorageViewEvent.InlineError(stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt))) + }) + } + } + + private fun handleCancel() { + _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) + } + + private fun handleTogglePasswordVisibility() { + setState { + copy( + passphraseVisible = !passphraseVisible + ) + } + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? { + val activity: SharedSecureStorageActivity = viewModelContext.activity() + val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(MvRx.KEY_ARG) + return activity.viewModelFactory.create(state, args) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt new file mode 100644 index 0000000000..f7a5e7c1bc --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.crypto.quads + +import android.os.Bundle +import android.view.View +import android.view.inputmethod.EditorInfo +import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.withState +import com.jakewharton.rxbinding3.view.clicks +import com.jakewharton.rxbinding3.widget.editorActionEvents +import com.jakewharton.rxbinding3.widget.textChanges +import im.vector.riotx.R +import im.vector.riotx.core.extensions.showPassword +import im.vector.riotx.core.platform.VectorBaseFragment +import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.* +import me.gujun.android.span.span +import java.util.concurrent.TimeUnit + +class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_ssss_access_from_passphrase + + val sharedViewModel: SharedSecureStorageViewModel by activityViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ssss_restore_with_passphrase_warning_text.text = span { + span(getString(R.string.enter_secret_storage_passphrase_warning)) { + textStyle = "bold" + } + +" " + +getString(R.string.enter_secret_storage_passphrase_warning_text) + } + + ssss_restore_with_passphrase_warning_reason.text = getString(R.string.enter_secret_storage_passphrase_reason_verify) + + ssss_passphrase_enter_edittext.editorActionEvents() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + if (it.actionId == EditorInfo.IME_ACTION_DONE) { + submit() + } + } + .disposeOnDestroyView() + + ssss_passphrase_enter_edittext.textChanges() + .subscribe { + ssss_passphrase_enter_til.error = null + ssss_passphrase_submit.isEnabled = it.isNotBlank() + } + .disposeOnDestroyView() + + sharedViewModel.observeViewEvents { + when (it) { + is SharedSecureStorageViewEvent.InlineError -> { + ssss_passphrase_enter_til.error = it.message + } + } + } + + ssss_passphrase_submit.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + submit() + } + .disposeOnDestroyView() + + ssss_passphrase_cancel.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + sharedViewModel.handle(SharedSecureStorageAction.Cancel) + } + .disposeOnDestroyView() + + ssss_view_show_password.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) + } + .disposeOnDestroyView() + } + + fun submit() { + val text = ssss_passphrase_enter_edittext.text.toString() + if (text.isBlank()) return // Should not reach this point as button disabled + ssss_passphrase_submit.isEnabled = false + sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text)) + } + + override fun invalidate() = withState(sharedViewModel) { state -> + val shouldBeVisible = state.passphraseVisible + ssss_passphrase_enter_edittext.showPassword(shouldBeVisible) + ssss_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt index 74c85a75c6..79facb71d0 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationAction.kt @@ -28,4 +28,7 @@ sealed class VerificationAction : VectorViewModelAction { data class SASMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction() data class SASDoNotMatchAction(val otherUserId: String, val sasTransactionId: String) : VerificationAction() object GotItConclusion : VerificationAction() + object SkipVerification : VerificationAction() + object VerifyFromPassphrase : VerificationAction() + data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 75983a1969..d73fc0955d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -15,26 +15,32 @@ */ package im.vector.riotx.features.crypto.verification +import android.app.Activity +import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.app.AlertDialog import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.transition.AutoTransition -import androidx.transition.TransitionManager import butterknife.BindView import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent -import im.vector.riotx.core.extensions.commitTransactionNow +import im.vector.riotx.core.extensions.commitTransaction import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment @@ -42,7 +48,6 @@ import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQ import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.home.AvatarRenderer import kotlinx.android.parcel.Parcelize -import kotlinx.android.synthetic.main.bottom_sheet_verification.* import timber.log.Timber import javax.inject.Inject import kotlin.reflect.KClass @@ -54,10 +59,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { val otherUserId: String, val verificationId: String? = null, val roomId: String? = null, - // Special mode where UX should show loading wheel until other user sends a request/tx - val waitForIncomingRequest: Boolean = false + // Special mode where UX should show loading wheel until other session sends a request/tx + val selfVerificationMode: Boolean = false ) : Parcelable + override val showExpanded = true + @Inject lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory @Inject @@ -85,15 +92,44 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { viewModel.observeViewEvents { when (it) { - is VerificationBottomSheetViewEvents.Dismiss -> dismiss() + is VerificationBottomSheetViewEvents.Dismiss -> dismiss() + is VerificationBottomSheetViewEvents.AccessSecretStore -> { + startActivityForResult(SharedSecureStorageActivity.newIntent( + requireContext(), + null, // use default key + listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME), + SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS + ), SECRET_REQUEST_CODE) + } + is VerificationBottomSheetViewEvents.ModalError -> { + AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.dialog_title_error)) + .setMessage(it.errorMessage) + .setCancelable(false) + .setPositiveButton(R.string.ok, null) + .show() + Unit + } }.exhaustive } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) { + data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)?.let { + viewModel.handle(VerificationAction.GotResultFromSsss(it, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)) + } + } + super.onActivityResult(requestCode, resultCode, data) + } + override fun invalidate() = withState(viewModel) { state -> + state.otherUserMxItem?.let { matrixItem -> if (state.isMe) { - if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified) { + if (state.sasTransactionState == VerificationTxState.Verified + || state.qrTransactionState == VerificationTxState.Verified + || state.verifiedFromPrivateKeys) { otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) } else { otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_warning) @@ -113,6 +149,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } + if (state.selfVerificationMode && state.verifiedFromPrivateKeys) { + showFragment(VerificationConclusionFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) + }) + return@withState + } + // Did the request result in a SAS transaction? if (state.sasTransactionState != null) { when (state.sasTransactionState) { @@ -183,7 +226,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } // If it's an outgoing - if (state.pendingRequest.invoke() == null || state.pendingRequest.invoke()?.isIncoming == false || state.waitForOtherUserMode) { + if (state.pendingRequest.invoke() == null || state.pendingRequest.invoke()?.isIncoming == false || state.selfVerificationMode) { Timber.v("## SAS show bottom sheet for outgoing request") if (state.pendingRequest.invoke()?.isReady == true) { Timber.v("## SAS show bottom sheet for outgoing and ready request") @@ -214,12 +257,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { private fun showFragment(fragmentClass: KClass, bundle: Bundle) { if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { - // We want to animate the bottomsheet bound changes - bottomSheetFragmentContainer.getParentCoordinatorLayout()?.let { coordinatorLayout -> - TransitionManager.beginDelayedTransition(coordinatorLayout, AutoTransition().apply { duration = 150 }) - } - // Commit now, to ensure changes occurs before next rendering frame (or bottomsheet want animate) - childFragmentManager.commitTransactionNow { + childFragmentManager.commitTransaction { replace(R.id.bottomSheetFragmentContainer, fragmentClass.java, bundle, @@ -230,14 +268,28 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } companion object { - fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null, waitForIncomingRequest: Boolean = false): VerificationBottomSheet { + + const val SECRET_REQUEST_CODE = 101 + + fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet { return VerificationBottomSheet().apply { arguments = Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationArgs( otherUserId = otherUserId, roomId = roomId, verificationId = transactionId, - waitForIncomingRequest = waitForIncomingRequest + selfVerificationMode = false + )) + } + } + } + + fun forSelfVerification(session: Session): VerificationBottomSheet { + return VerificationBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationArgs( + otherUserId = session.myUserId, + selfVerificationMode = true )) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt index 5509ecbe16..d7c02a8d3b 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt @@ -23,4 +23,6 @@ import im.vector.riotx.core.platform.VectorViewEvents */ sealed class VerificationBottomSheetViewEvents : VectorViewEvents { object Dismiss : VerificationBottomSheetViewEvents() + object AccessSecretStore : VerificationBottomSheetViewEvents() + data class ModalError(val errorMessage: CharSequence) : VerificationBottomSheetViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 343f58b20c..b7da38526a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -28,6 +28,9 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction @@ -39,9 +42,12 @@ import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding +import im.vector.matrix.android.internal.crypto.crosssigning.isVerified import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel +import timber.log.Timber data class VerificationBottomSheetViewState( val otherUserMxItem: MatrixItem? = null, @@ -52,7 +58,8 @@ data class VerificationBottomSheetViewState( val qrTransactionState: VerificationTxState? = null, val transactionId: String? = null, // true when we display the loading and we wait for the other (incoming request) - val waitForOtherUserMode: Boolean = false, + val selfVerificationMode: Boolean = false, + val verifiedFromPrivateKeys: Boolean = false, val isMe: Boolean = false ) : MvRxState @@ -67,10 +74,10 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini val userItem = session.getUser(args.otherUserId) - val isWaitingForOtherMode = args.waitForIncomingRequest + val selfVerificationMode = args.selfVerificationMode var autoReady = false - val pr = if (isWaitingForOtherMode) { + val pr = if (selfVerificationMode) { // See if active tx for this user and take it session.cryptoService().verificationService().getExistingVerificationRequest(args.otherUserId) @@ -100,7 +107,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini qrTransactionState = qrTx?.state, transactionId = pr?.transactionId ?: args.verificationId, pendingRequest = if (pr != null) Success(pr) else Uninitialized, - waitForOtherUserMode = isWaitingForOtherMode, + selfVerificationMode = selfVerificationMode, roomId = args.roomId, isMe = args.otherUserId == session.myUserId ) @@ -250,6 +257,46 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini is VerificationAction.GotItConclusion -> { _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) } + is VerificationAction.SkipVerification -> { + _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) + } + is VerificationAction.VerifyFromPassphrase -> { + _viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore) + } + is VerificationAction.GotResultFromSsss -> { + try { + action.cypherData.fromBase64NoPadding().inputStream().use { ins -> + val res = session.loadSecureSecret>(ins, action.alias) + val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( + res?.get(MASTER_KEY_SSSS_NAME), + res?.get(USER_SIGNING_KEY_SSSS_NAME), + res?.get(SELF_SIGNING_KEY_SSSS_NAME) + ) + if (trustResult.isVerified()) { + // Sign this device and upload the signature + session.sessionParams.credentials.deviceId?.let { deviceId -> + session.cryptoService() + .crossSigningService().trustDevice(deviceId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.w("Failed to sign my device after recovery", failure) + } + }) + } + + setState { + copy(verifiedFromPrivateKeys = true) + } + } else { + // POP UP something + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError("Failed to import keys")) + } + } + } catch (failure: Throwable) { + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage)) + } + + Unit + } }.exhaustive } @@ -258,7 +305,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini } override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> - if (state.waitForOtherUserMode && state.transactionId == null) { + if (state.selfVerificationMode && state.transactionId == null) { // is this an incoming with that user if (tx.isIncoming && tx.otherUserId == state.otherUserMxItem?.id) { // Also auto accept incoming if needed! @@ -308,7 +355,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> - if (state.waitForOtherUserMode && state.pendingRequest.invoke() == null && state.transactionId == null) { + if (state.selfVerificationMode && state.pendingRequest.invoke() == null && state.transactionId == null) { // is this an incoming with that user if (pr.isIncoming && pr.otherUserId == state.otherUserMxItem?.id) { if (!pr.isReady) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt index 9c4a5a870f..05ed2f1799 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt @@ -50,7 +50,7 @@ class VerificationRequestController @Inject constructor( val state = viewState ?: return val matrixItem = viewState?.otherUserMxItem ?: return - if (state.waitForOtherUserMode) { + if (state.selfVerificationMode) { bottomSheetVerificationNoticeItem { id("notice") notice(stringProvider.getString(R.string.verification_open_other_to_verify)) @@ -62,7 +62,26 @@ class VerificationRequestController @Inject constructor( bottomSheetVerificationWaitingItem { id("waiting") - title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName())) + title(stringProvider.getString(R.string.verification_request_waiting, matrixItem.getBestName())) + } + + bottomSheetVerificationActionItem { + id("passphrase") + title(stringProvider.getString(R.string.verification_cannot_access_other_session)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + subTitle(stringProvider.getString(R.string.verification_use_passphrase)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.onClickRecoverFromPassphrase() } + } + bottomSheetVerificationActionItem { + id("skip") + title(stringProvider.getString(R.string.skip)) + titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) +// subTitle(stringProvider.getString(R.string.verification_use_passphrase)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + listener { listener?.onClickDismiss() } } } else { val styledText = matrixItem.let { @@ -112,5 +131,7 @@ class VerificationRequestController @Inject constructor( interface Listener { fun onClickOnVerificationStart() + fun onClickRecoverFromPassphrase() + fun onClickDismiss() } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt index 8231242d08..64000d07a1 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt @@ -61,4 +61,12 @@ class VerificationRequestFragment @Inject constructor( viewModel.handle(VerificationAction.RequestVerificationByDM(otherUserId, state.roomId)) } } + + override fun onClickRecoverFromPassphrase() { + viewModel.handle(VerificationAction.VerifyFromPassphrase) + } + + override fun onClickDismiss() { + viewModel.handle(VerificationAction.SkipVerification) + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 2eac4e946e..2ebe1aebe1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -150,34 +150,17 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { PopupAlertManager.postVectorAlert( PopupAlertManager.VectorAlert( uid = "completeSecurity", - title = getString(R.string.crosssigning_verify_this_session), - description = getString(R.string.crosssigning_other_user_not_trust), + title = getString(R.string.new_signin), + description = getString(R.string.complete_security), iconId = R.drawable.ic_shield_warning ).apply { - colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent) + colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_destructive_accent) contentAction = Runnable { - Runnable { - (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { - it.navigator.waitSessionVerification(it) - } + (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { + it.navigator.waitSessionVerification(it) } } - dismissedAction = Runnable { - // tx.cancel() - } - addButton( - getString(R.string.later), - Runnable { - } - ) - addButton( - getString(R.string.verification_profile_verify), - Runnable { - (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { - it.navigator.waitSessionVerification(it) - } - } - ) + dismissedAction = Runnable {} } ) } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index fe9dbcaeb5..b30932869e 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -99,12 +99,8 @@ class DefaultNavigator @Inject constructor( override fun waitSessionVerification(context: Context) { val session = sessionHolder.getSafeActiveSession() ?: return if (context is VectorBaseActivity) { - VerificationBottomSheet.withArgs( - roomId = null, - otherUserId = session.myUserId, - waitForIncomingRequest = true - - ).show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) + VerificationBottomSheet.forSelfVerification(session) + .show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index 6c750af5ac..cfe50bb2f7 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -30,7 +30,6 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset -import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity @@ -91,10 +90,9 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE, // Public room preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT - ).let { - // Encryption - if (state.isEncrypted) it.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM) else it - } + ) + // Encryption + .enableEncryptionWithAlgorithm(state.isEncrypted) session.createRoom(createRoomParams, object : MatrixCallback { override fun onSuccess(data: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt index 681b6030bf..cf74e83b1f 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt @@ -36,6 +36,7 @@ class CrossSigningEpoxyController @Inject constructor( interface InteractionListener { fun onInitializeCrossSigningKeys() fun onResetCrossSigningKeys() + fun verifySession() } var interactionListener: InteractionListener? = null @@ -77,21 +78,31 @@ class CrossSigningEpoxyController @Inject constructor( interactionListener?.onResetCrossSigningKeys() } } - } else if (data.xSigningIsEnableInAccount) { - genericItem { - id("enable") - titleIconResourceId(R.drawable.ic_shield_black) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) + } + } else if (data.xSigningIsEnableInAccount) { + genericItem { + id("enable") + titleIconResourceId(R.drawable.ic_shield_black) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) + } + bottomSheetVerificationActionItem { + id("verify") + title(stringProvider.getString(R.string.complete_security)) + titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) + listener { + interactionListener?.verifySession() } - bottomSheetVerificationActionItem { - id("resetkeys") - title("Reset keys") - titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) - iconRes(R.drawable.ic_arrow_right) - iconColor(colorProvider.getColor(R.color.riotx_destructive_accent)) - listener { - interactionListener?.onResetCrossSigningKeys() - } + } + bottomSheetVerificationActionItem { + id("resetkeys") + title("Reset keys") + titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + listener { + interactionListener?.onResetCrossSigningKeys() } } } else { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt index ec56929002..76835211cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -54,6 +54,11 @@ class CrossSigningSettingsFragment @Inject constructor( is CrossSigningSettingsViewEvents.RequestPassword -> { requestPassword() } + CrossSigningSettingsViewEvents.VerifySession -> { + (requireActivity() as? VectorBaseActivity)?.let { activity -> + activity.navigator.waitSessionVerification(activity) + } + } }.exhaustive } } @@ -93,6 +98,10 @@ class CrossSigningSettingsFragment @Inject constructor( viewModel.handle(CrossSigningAction.InitializeCrossSigning) } + override fun verifySession() { + viewModel.handle(CrossSigningAction.VerifySession) + } + override fun onResetCrossSigningKeys() { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_confirmation) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt index b03707b363..65a4a18485 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt @@ -25,4 +25,5 @@ sealed class CrossSigningSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents() object RequestPassword : CrossSigningSettingsViewEvents() + object VerifySession : CrossSigningSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 8ddb1802f3..f18e0b3cc7 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -45,6 +45,7 @@ data class CrossSigningSettingsViewState( sealed class CrossSigningAction : VectorViewModelAction { object InitializeCrossSigning : CrossSigningAction() + object VerifySession : CrossSigningAction() data class PasswordEntered(val password: String) : CrossSigningAction() } @@ -88,6 +89,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat password = action.password )) } + CrossSigningAction.VerifySession -> { + _viewEvents.post(CrossSigningSettingsViewEvents.VerifySession) + } }.exhaustive } diff --git a/vector/src/main/res/layout/bottom_sheet_verification.xml b/vector/src/main/res/layout/bottom_sheet_verification.xml index 7ed760b45a..6e4a952e69 100644 --- a/vector/src/main/res/layout/bottom_sheet_verification.xml +++ b/vector/src/main/res/layout/bottom_sheet_verification.xml @@ -60,7 +60,7 @@ android:id="@+id/bottomSheetFragmentContainer" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/verificationRequestAvatar" /> diff --git a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml new file mode 100644 index 0000000000..db50cfd34d --- /dev/null +++ b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 86fb1e0e12..78c262c82a 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2140,7 +2140,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. %d saio aktibo - Egiaztatu saio hau Beste erabiltzaile batzuk ez fidagarritzat jo lezakete Bete segurtasuna diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 959266b9b7..71178e2531 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2148,7 +2148,6 @@ Si vous n’avez pas configurĂ© de nouvelle mĂ©thode de rĂ©cupĂ©ration, un attaq %d sessions actives - VĂ©rifier cette session Les autres utilisateurs ne lui font peut-ĂȘtre pas confiance ComplĂ©ter la sĂ©curitĂ© diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 64eee79075..13566f4ce9 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -2143,7 +2143,6 @@ Ha nem te ĂĄllĂ­tottad be a visszaĂĄllĂ­tĂĄsi metĂłdust, akkor egy tĂĄmadĂł prĂł %d munkamenet hasznĂĄlatban - Munkamenet ellenƑrzĂ©se MĂĄs felhasznĂĄlĂłk lehet, hogy nem bĂ­znak benne BiztonsĂĄg beĂĄllĂ­tĂĄsa diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 4d1ac8edaf..a29ff4ae98 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2193,7 +2193,6 @@ %d sessioni attive - Verifica questa sessione Gli altri utenti potrebbero non fidarsi Completa la sicurezza diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index a8af77d59b..b21a9ad800 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2062,7 +2062,6 @@ QĂ« tĂ« garantoni se s’ju shpĂ«ton gjĂ«, thjesht mbajeni tĂ« aktivizuar mekani %d sesione aktive - Verifikoni kĂ«tĂ« sesion PĂ«rdorues tĂ« tjerĂ« mund tĂ« mos e besojnĂ« Siguri e PlotĂ« diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index d285c364b2..fbe47a5053 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2093,7 +2093,6 @@ Matrix äž­çš„æ¶ˆæŻćŻèŠ‹ćșŠéĄžäŒŒäșŽé›»ć­éƒ”ä»¶ă€‚æˆ‘ć€‘ćż˜èš˜æ‚šçš„éƒ”ä»¶æ„ %d 掻èșçš„ć·„äœœéšŽæź” - é©—è­‰æ­€ć·„äœœéšŽæź” ć…¶ä»–äœżç”šè€…ćŻèƒœäžæœƒäżĄä»»ćźƒ ć…šéąçš„ćź‰ć…šæ€§ diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 29fff11230..6c2caedd01 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2121,11 +2121,10 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming %d active sessions - Verify this session Other users may not trust it Complete Security - Open an existing session & use it to verify this one, granting it access to encrypted messages. If you can’t access one, use your recovery key or passphrase. + Open an existing session & use it to verify this one, granting it access to encrypted messages. Verify diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 402c65e95f..4328355937 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -18,6 +18,19 @@ Selected Option Creates a simple poll + Can‘t access an existing session? + Use your recovery key or passphrase + + + New Sign In + + + Cannot find secrets in storage + Enter secret storage passphrase + Warning: + You should only access secret storage from a trusted device + Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase +