Merge branch 'rust' into feature/fga/device_verification
This commit is contained in:
		
						commit
						14ed4692dc
					
				@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
 | 
			
		||||
@ -50,7 +51,9 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 | 
			
		||||
import org.matrix.android.sdk.api.session.events.model.toContent
 | 
			
		||||
import org.matrix.android.sdk.api.session.events.model.toModel
 | 
			
		||||
import org.matrix.android.sdk.api.session.getRoom
 | 
			
		||||
import org.matrix.android.sdk.api.session.getRoomSummary
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.Room
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.model.Membership
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 | 
			
		||||
@ -149,6 +152,76 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
 | 
			
		||||
        return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun inviteNewUsersAndWaitForThemToJoin(session: Session, roomId: String, usernames: List<String>): List<Session> {
 | 
			
		||||
        val newSessions = usernames.map { username ->
 | 
			
		||||
            testHelper.createAccount(username, SessionTestParams(true)).also {
 | 
			
		||||
                it.cryptoService().enableKeyGossiping(false)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val room = session.getRoom(roomId)!!
 | 
			
		||||
 | 
			
		||||
        Log.v("#E2E TEST", "All accounts created")
 | 
			
		||||
        // we want to invite them in the room
 | 
			
		||||
        newSessions.forEach { newSession ->
 | 
			
		||||
            testHelper.runBlockingTest {
 | 
			
		||||
                Log.v("#E2E TEST", "${session.myUserId} invites ${newSession.myUserId}")
 | 
			
		||||
                room.membershipService().invite(newSession.myUserId)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // All user should accept invite
 | 
			
		||||
        newSessions.forEach { newSession ->
 | 
			
		||||
            waitForAndAcceptInviteInRoom(newSession, roomId)
 | 
			
		||||
            Log.v("#E2E TEST", "${newSession.myUserId} joined room $roomId")
 | 
			
		||||
        }
 | 
			
		||||
        ensureMembersHaveJoined(session, newSessions, roomId)
 | 
			
		||||
        return newSessions
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun ensureMembersHaveJoined(session: Session, invitedUserSessions: List<Session>, roomId: String) {
 | 
			
		||||
        testHelper.waitWithLatch { latch ->
 | 
			
		||||
            testHelper.retryPeriodicallyWithLatch(latch) {
 | 
			
		||||
                invitedUserSessions.map { invitedUserSession ->
 | 
			
		||||
                    session.roomService().getRoomMember(invitedUserSession.myUserId, roomId)?.membership
 | 
			
		||||
                }.all {
 | 
			
		||||
                    it == Membership.JOIN
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun waitForAndAcceptInviteInRoom(session: Session, roomId: String) {
 | 
			
		||||
        testHelper.waitWithLatch { latch ->
 | 
			
		||||
            testHelper.retryPeriodicallyWithLatch(latch) {
 | 
			
		||||
                val roomSummary = session.getRoomSummary(roomId)
 | 
			
		||||
                (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
 | 
			
		||||
                    if (it) {
 | 
			
		||||
                        Log.v("#E2E TEST", "${session.myUserId} can see the invite from ${roomSummary?.inviterId}")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // not sure why it's taking so long :/
 | 
			
		||||
        testHelper.runBlockingTest(90_000) {
 | 
			
		||||
            Log.v("#E2E TEST", "${session.myUserId} tries to join room $roomId")
 | 
			
		||||
            try {
 | 
			
		||||
                session.roomService().joinRoom(roomId)
 | 
			
		||||
            } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
 | 
			
		||||
                // it's ok we will wait after
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log.v("#E2E TEST", "${session.myUserId} waiting for join echo ...")
 | 
			
		||||
        testHelper.waitWithLatch {
 | 
			
		||||
            testHelper.retryPeriodicallyWithLatch(it) {
 | 
			
		||||
                val roomSummary = session.getRoomSummary(roomId)
 | 
			
		||||
                roomSummary != null && roomSummary.membership == Membership.JOIN
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Alice, Bob and Sam session
 | 
			
		||||
     */
 | 
			
		||||
@ -250,7 +323,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
 | 
			
		||||
        return MegolmBackupCreationInfo(
 | 
			
		||||
                algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
 | 
			
		||||
                authData = createFakeMegolmBackupAuthData(),
 | 
			
		||||
                recoveryKey = "fake"
 | 
			
		||||
                recoveryKey = BackupRecoveryKey.fromBase58("3cnTdW")
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -320,7 +393,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
 | 
			
		||||
            aliceVerificationService.beginKeyVerification(
 | 
			
		||||
                    VerificationMethod.SAS,
 | 
			
		||||
                    roomId,
 | 
			
		||||
                    bob.myUserId,)
 | 
			
		||||
                    bob.myUserId,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // we should reach SHOW SAS on both
 | 
			
		||||
@ -445,7 +519,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
 | 
			
		||||
            // Save it for gossiping
 | 
			
		||||
            session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
 | 
			
		||||
 | 
			
		||||
            extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
 | 
			
		||||
            extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret ->
 | 
			
		||||
                ssssService.storeSecret(
 | 
			
		||||
                        KEYBACKUP_SECRET_SSSS_NAME,
 | 
			
		||||
                        secret,
 | 
			
		||||
 | 
			
		||||
@ -43,11 +43,8 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
 | 
			
		||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 | 
			
		||||
import org.matrix.android.sdk.api.session.events.model.toModel
 | 
			
		||||
import org.matrix.android.sdk.api.session.getRoom
 | 
			
		||||
import org.matrix.android.sdk.api.session.getRoomSummary
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.Room
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.model.Membership
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.send.SendState
 | 
			
		||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 | 
			
		||||
@ -88,36 +85,14 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
 | 
			
		||||
        // add some more users and invite them
 | 
			
		||||
        val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
 | 
			
		||||
                .map {
 | 
			
		||||
                    testHelper.createAccount(it, SessionTestParams(true)).also {
 | 
			
		||||
                        it.cryptoService().enableKeyGossiping(false)
 | 
			
		||||
                    }
 | 
			
		||||
                .let {
 | 
			
		||||
                    cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        Log.v("#E2E TEST", "All accounts created")
 | 
			
		||||
        // we want to invite them in the room
 | 
			
		||||
        otherAccounts.forEach {
 | 
			
		||||
            testHelper.runBlockingTest {
 | 
			
		||||
                Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
 | 
			
		||||
                aliceRoomPOV.membershipService().invite(it.myUserId)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // All user should accept invite
 | 
			
		||||
        otherAccounts.forEach { otherSession ->
 | 
			
		||||
            waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID)
 | 
			
		||||
            Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check that alice see them as joined (not really necessary?)
 | 
			
		||||
        ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID)
 | 
			
		||||
 | 
			
		||||
        Log.v("#E2E TEST", "All users have joined the room")
 | 
			
		||||
        Log.v("#E2E TEST", "Alice is sending the message")
 | 
			
		||||
 | 
			
		||||
        val text = "This is my message"
 | 
			
		||||
        val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
 | 
			
		||||
        //        val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
 | 
			
		||||
        Assert.assertTrue("Message should be sent", sentEventId != null)
 | 
			
		||||
 | 
			
		||||
        // All should be able to decrypt
 | 
			
		||||
@ -131,31 +106,13 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log.v("#E2E TEST", "Everybody received the encrypted message and could decrypt")
 | 
			
		||||
        // Add a new user to the room, and check that he can't decrypt
 | 
			
		||||
        val newAccount = listOf("adam") // , "adam", "manu")
 | 
			
		||||
                .map {
 | 
			
		||||
                    testHelper.createAccount(it, SessionTestParams(true))
 | 
			
		||||
                .let {
 | 
			
		||||
                    cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        newAccount.forEach {
 | 
			
		||||
            testHelper.runBlockingTest {
 | 
			
		||||
                Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
 | 
			
		||||
                aliceRoomPOV.membershipService().invite(it.myUserId)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newAccount.forEach {
 | 
			
		||||
            waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
 | 
			
		||||
 | 
			
		||||
        // wait a bit
 | 
			
		||||
        testHelper.runBlockingTest {
 | 
			
		||||
            delay(3_000)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check that messages are encrypted (uisi)
 | 
			
		||||
        newAccount.forEach { otherSession ->
 | 
			
		||||
            testHelper.waitWithLatch { latch ->
 | 
			
		||||
@ -165,7 +122,7 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
                    }
 | 
			
		||||
                    timelineEvent != null &&
 | 
			
		||||
                            timelineEvent.root.getClearType() == EventType.ENCRYPTED &&
 | 
			
		||||
                            timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
 | 
			
		||||
                            timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNABLE_TO_DECRYPT
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -639,6 +596,7 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
                        // for the test we just accept?
 | 
			
		||||
                        oldCode = sasTx.getDecimalCodeRepresentation()
 | 
			
		||||
                        testHelper.runBlockingTest {
 | 
			
		||||
                            delay(500)
 | 
			
		||||
                            sasTx.userHasVerifiedShortCode()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@ -665,11 +623,12 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
                val sasTx = tx as SasVerificationTransaction
 | 
			
		||||
                when (sasTx.state) {
 | 
			
		||||
                    VerificationTxState.ShortCodeReady -> {
 | 
			
		||||
                        newCode = sasTx.getDecimalCodeRepresentation()
 | 
			
		||||
                        if (matchOnce) {
 | 
			
		||||
                            testHelper.runBlockingTest {
 | 
			
		||||
                                delay(500)
 | 
			
		||||
                                sasTx.userHasVerifiedShortCode()
 | 
			
		||||
                            }
 | 
			
		||||
                            newCode = sasTx.getDecimalCodeRepresentation()
 | 
			
		||||
                            matchOnce = false
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@ -711,6 +670,7 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
                aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        testHelper.runBlockingTest {
 | 
			
		||||
            assertEquals(
 | 
			
		||||
                    "MSK Private parts should be the same",
 | 
			
		||||
@ -746,49 +706,6 @@ class E2eeSanityTests : InstrumentedTest {
 | 
			
		||||
        testHelper.signOutAndClose(aliceNewSession)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
 | 
			
		||||
        testHelper.waitWithLatch { latch ->
 | 
			
		||||
            testHelper.retryPeriodicallyWithLatch(latch) {
 | 
			
		||||
                otherAccounts.map {
 | 
			
		||||
                    aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
 | 
			
		||||
                }.all {
 | 
			
		||||
                    it == Membership.JOIN
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) {
 | 
			
		||||
        testHelper.waitWithLatch { latch ->
 | 
			
		||||
            testHelper.retryPeriodicallyWithLatch(latch) {
 | 
			
		||||
                val roomSummary = otherSession.getRoomSummary(e2eRoomID)
 | 
			
		||||
                (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
 | 
			
		||||
                    if (it) {
 | 
			
		||||
                        Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // not sure why it's taking so long :/
 | 
			
		||||
        testHelper.runBlockingTest(90_000) {
 | 
			
		||||
            Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
 | 
			
		||||
            try {
 | 
			
		||||
                otherSession.roomService().joinRoom(e2eRoomID)
 | 
			
		||||
            } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
 | 
			
		||||
                // it's ok we will wait after
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
 | 
			
		||||
        testHelper.waitWithLatch {
 | 
			
		||||
            testHelper.retryPeriodicallyWithLatch(it) {
 | 
			
		||||
                val roomSummary = otherSession.getRoomSummary(e2eRoomID)
 | 
			
		||||
                roomSummary != null && roomSummary.membership == Membership.JOIN
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) {
 | 
			
		||||
        testHelper.waitWithLatch { latch ->
 | 
			
		||||
            sentEventIds.forEach { sentEventId ->
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import org.junit.runners.MethodSorters
 | 
			
		||||
import org.matrix.android.sdk.InstrumentedTest
 | 
			
		||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
 | 
			
		||||
import org.matrix.android.sdk.api.listeners.StepProgressListener
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
 | 
			
		||||
@ -505,7 +506,7 @@ class KeysBackupTest : InstrumentedTest {
 | 
			
		||||
            try {
 | 
			
		||||
                testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
 | 
			
		||||
                        testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
 | 
			
		||||
                        "Bad recovery key"
 | 
			
		||||
                        BackupRecoveryKey.fromBase58("Bad recovery key")
 | 
			
		||||
                )
 | 
			
		||||
                fail("Should have failed to trust")
 | 
			
		||||
            } catch (failure: Throwable) {
 | 
			
		||||
@ -645,7 +646,7 @@ class KeysBackupTest : InstrumentedTest {
 | 
			
		||||
        var importRoomKeysResult: ImportRoomKeysResult? = null
 | 
			
		||||
        testHelper.runBlockingTest {
 | 
			
		||||
            testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
 | 
			
		||||
                    "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
 | 
			
		||||
                    BackupRecoveryKey.fromBase58("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d"),
 | 
			
		||||
                    null,
 | 
			
		||||
                    null,
 | 
			
		||||
                    null
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.verification
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import org.junit.Assert.assertEquals
 | 
			
		||||
import org.junit.Assert.assertFalse
 | 
			
		||||
@ -446,7 +447,8 @@ class SASTest : InstrumentedTest {
 | 
			
		||||
            aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
 | 
			
		||||
        }
 | 
			
		||||
        testHelper.await(latch)
 | 
			
		||||
        val aliceTx = aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
 | 
			
		||||
        val aliceTx =
 | 
			
		||||
                aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
 | 
			
		||||
        val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
 | 
			
		||||
 | 
			
		||||
        assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
 | 
			
		||||
@ -467,62 +469,71 @@ class SASTest : InstrumentedTest {
 | 
			
		||||
        val aliceVerificationService = aliceSession.cryptoService().verificationService()
 | 
			
		||||
        val bobVerificationService = bobSession!!.cryptoService().verificationService()
 | 
			
		||||
 | 
			
		||||
        val aliceSASLatch = CountDownLatch(1)
 | 
			
		||||
        val verifiedLatch = CountDownLatch(2)
 | 
			
		||||
        val aliceListener = object : VerificationService.Listener {
 | 
			
		||||
 | 
			
		||||
            override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
 | 
			
		||||
                Timber.v("RequestUpdated pr=$pr")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var matchOnce = true
 | 
			
		||||
            var matched = false
 | 
			
		||||
            var verified = false
 | 
			
		||||
            override fun transactionUpdated(tx: VerificationTransaction) {
 | 
			
		||||
                Timber.v("Alice transactionUpdated: ${tx.state}")
 | 
			
		||||
                Timber.v("Alice transactionUpdated: ${tx.state} on thread:${Thread.currentThread()}")
 | 
			
		||||
                if (tx !is SasVerificationTransaction) return
 | 
			
		||||
                when (tx.state) {
 | 
			
		||||
                    VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
 | 
			
		||||
                        tx.userHasVerifiedShortCode()
 | 
			
		||||
                    }
 | 
			
		||||
                    VerificationTxState.Verified       -> {
 | 
			
		||||
                        if (matchOnce) {
 | 
			
		||||
                            matchOnce = false
 | 
			
		||||
                            aliceSASLatch.countDown()
 | 
			
		||||
                    VerificationTxState.ShortCodeReady    -> testHelper.runBlockingTest {
 | 
			
		||||
                        if (!matched) {
 | 
			
		||||
                            matched = true
 | 
			
		||||
                            delay(500)
 | 
			
		||||
                            tx.userHasVerifiedShortCode()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else                               -> Unit
 | 
			
		||||
                    VerificationTxState.Verified          -> {
 | 
			
		||||
                        if (!verified) {
 | 
			
		||||
                            verified = true
 | 
			
		||||
                            verifiedLatch.countDown()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else                                  -> Unit
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        aliceVerificationService.addListener(aliceListener)
 | 
			
		||||
 | 
			
		||||
        val bobSASLatch = CountDownLatch(1)
 | 
			
		||||
        val bobListener = object : VerificationService.Listener {
 | 
			
		||||
            var acceptOnce = true
 | 
			
		||||
            var matchOnce = true
 | 
			
		||||
            var accepted = false
 | 
			
		||||
            var matched = false
 | 
			
		||||
            var verified = false
 | 
			
		||||
 | 
			
		||||
            override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
 | 
			
		||||
                Timber.v("RequestUpdated: pr=$pr")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun transactionUpdated(tx: VerificationTransaction) {
 | 
			
		||||
                Timber.v("Bob transactionUpdated: ${tx.state}")
 | 
			
		||||
                Timber.v("Bob transactionUpdated: ${tx.state} on thread: ${Thread.currentThread()}")
 | 
			
		||||
                if (tx !is SasVerificationTransaction) return
 | 
			
		||||
                when (tx.state) {
 | 
			
		||||
                    VerificationTxState.OnStarted      -> testHelper.runBlockingTest {
 | 
			
		||||
                        if (acceptOnce) {
 | 
			
		||||
                            acceptOnce = false
 | 
			
		||||
                    VerificationTxState.OnStarted         -> testHelper.runBlockingTest {
 | 
			
		||||
                        if (!accepted) {
 | 
			
		||||
                            accepted = true
 | 
			
		||||
                            tx.acceptVerification()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
 | 
			
		||||
                        if (matchOnce) {
 | 
			
		||||
                            matchOnce = false
 | 
			
		||||
                    VerificationTxState.ShortCodeReady    -> testHelper.runBlockingTest {
 | 
			
		||||
                        if (!matched) {
 | 
			
		||||
                            matched = true
 | 
			
		||||
                            delay(500)
 | 
			
		||||
                            tx.userHasVerifiedShortCode()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    VerificationTxState.ShortCodeAccepted       -> {
 | 
			
		||||
                        bobSASLatch.countDown()
 | 
			
		||||
                    VerificationTxState.Verified          -> {
 | 
			
		||||
                        if (!verified) {
 | 
			
		||||
                            verified = true
 | 
			
		||||
                            verifiedLatch.countDown()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else                               -> Unit
 | 
			
		||||
                    else                                  -> Unit
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -535,8 +546,8 @@ class SASTest : InstrumentedTest {
 | 
			
		||||
        testHelper.runBlockingTest {
 | 
			
		||||
            aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
 | 
			
		||||
        }
 | 
			
		||||
        testHelper.await(aliceSASLatch)
 | 
			
		||||
        testHelper.await(bobSASLatch)
 | 
			
		||||
        Timber.v("Await after beginKey ${Thread.currentThread()}")
 | 
			
		||||
        testHelper.await(verifiedLatch)
 | 
			
		||||
 | 
			
		||||
        // Assert that devices are verified
 | 
			
		||||
        val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,60 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2022 New Vector Ltd
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.matrix.android.sdk.api.session.crypto.keysbackup
 | 
			
		||||
 | 
			
		||||
import uniffi.olm.BackupRecoveryKey as InnerBackupRecoveryKey
 | 
			
		||||
 | 
			
		||||
class BackupRecoveryKey internal constructor(internal val inner: InnerBackupRecoveryKey) {
 | 
			
		||||
 | 
			
		||||
    constructor() : this(InnerBackupRecoveryKey())
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        fun fromBase58(key: String): BackupRecoveryKey {
 | 
			
		||||
            val inner = InnerBackupRecoveryKey.fromBase58(key)
 | 
			
		||||
            return BackupRecoveryKey(inner)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun fromBase64(key: String): BackupRecoveryKey {
 | 
			
		||||
            val inner = InnerBackupRecoveryKey.fromBase64(key)
 | 
			
		||||
            return BackupRecoveryKey(inner)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun fromPassphrase(passphrase: String, salt: String, rounds: Int): BackupRecoveryKey {
 | 
			
		||||
            val inner = InnerBackupRecoveryKey.fromPassphrase(passphrase, salt, rounds)
 | 
			
		||||
            return BackupRecoveryKey(inner)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun newFromPassphrase(passphrase: String): BackupRecoveryKey {
 | 
			
		||||
            val inner = InnerBackupRecoveryKey.newFromPassphrase(passphrase)
 | 
			
		||||
            return BackupRecoveryKey(inner)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun equals(other: Any?): Boolean {
 | 
			
		||||
        if (other !is BackupRecoveryKey) return false
 | 
			
		||||
        return this.toBase58() == other.toBase58()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toBase58() = inner.toBase58()
 | 
			
		||||
 | 
			
		||||
    fun toBase64() = inner.toBase64()
 | 
			
		||||
 | 
			
		||||
    fun decryptV1(ephemeralKey: String, mac: String, ciphertext: String) = inner.decryptV1(ephemeralKey, mac, ciphertext)
 | 
			
		||||
 | 
			
		||||
    fun megolmV1PublicKey() = inner.megolmV1PublicKey()
 | 
			
		||||
}
 | 
			
		||||
@ -142,8 +142,7 @@ interface KeysBackupService {
 | 
			
		||||
     * @param keysBackupVersion the backup version to check.
 | 
			
		||||
     * @param recoveryKey the recovery key to challenge with the key backup public key.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
 | 
			
		||||
                                              recoveryKey: String)
 | 
			
		||||
    suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: BackupRecoveryKey)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set trust on a keys backup version.
 | 
			
		||||
@ -167,7 +166,7 @@ interface KeysBackupService {
 | 
			
		||||
     * @param callback             Callback. It provides the number of found keys and the number of successfully imported keys.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
 | 
			
		||||
                                   recoveryKey: String, roomId: String?,
 | 
			
		||||
                                   recoveryKey: BackupRecoveryKey, roomId: String?,
 | 
			
		||||
                                   sessionId: String?,
 | 
			
		||||
                                   stepProgressListener: StepProgressListener?): ImportRoomKeysResult
 | 
			
		||||
 | 
			
		||||
@ -194,10 +193,10 @@ interface KeysBackupService {
 | 
			
		||||
    val state: KeysBackupState
 | 
			
		||||
 | 
			
		||||
    // For gossiping
 | 
			
		||||
    fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
 | 
			
		||||
    fun saveBackupRecoveryKey(recoveryKey: BackupRecoveryKey?, version: String?)
 | 
			
		||||
    suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
 | 
			
		||||
 | 
			
		||||
    suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean
 | 
			
		||||
    suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: BackupRecoveryKey): Boolean
 | 
			
		||||
 | 
			
		||||
    fun computePrivateKey(passphrase: String,
 | 
			
		||||
                          privateKeySalt: String,
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ data class MegolmBackupCreationInfo(
 | 
			
		||||
        val authData: MegolmBackupAuthData,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The Base58 recovery key.
 | 
			
		||||
         * The recovery key.
 | 
			
		||||
         */
 | 
			
		||||
        val recoveryKey: String
 | 
			
		||||
        val recoveryKey: BackupRecoveryKey
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,6 @@
 | 
			
		||||
package org.matrix.android.sdk.api.session.crypto.keysbackup
 | 
			
		||||
 | 
			
		||||
data class SavedKeyBackupKeyInfo(
 | 
			
		||||
        val recoveryKey: String,
 | 
			
		||||
        val recoveryKey: BackupRecoveryKey,
 | 
			
		||||
        val version: String
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
 | 
			
		||||
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
 | 
			
		||||
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.network.NetworkRequestsProcessor
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.network.RequestSender
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 | 
			
		||||
@ -131,7 +131,7 @@ internal class DefaultCryptoService @Inject constructor(
 | 
			
		||||
 | 
			
		||||
    private val olmMachine by lazy { olmMachineProvider.olmMachine }
 | 
			
		||||
 | 
			
		||||
    private val outgoingRequestsProcessor = NetworkRequestsProcessor(
 | 
			
		||||
    private val outgoingRequestsProcessor = OutgoingRequestsProcessor(
 | 
			
		||||
            requestSender = requestSender,
 | 
			
		||||
            coroutineScope = cryptoCoroutineScope,
 | 
			
		||||
            cryptoSessionInfoProvider = cryptoSessionInfoProvider,
 | 
			
		||||
@ -486,7 +486,7 @@ internal class DefaultCryptoService @Inject constructor(
 | 
			
		||||
     */
 | 
			
		||||
    private fun onRoomMembershipEvent(roomId: String, event: Event) {
 | 
			
		||||
        // We only care about the memberships if this room is encrypted
 | 
			
		||||
        if (isRoomEncrypted(roomId)) {
 | 
			
		||||
        if (!isRoomEncrypted(roomId)) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -625,11 +625,11 @@ internal class DefaultCryptoService @Inject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun enableKeyGossiping(enable: Boolean) {
 | 
			
		||||
        TODO("Not yet implemented")
 | 
			
		||||
        cryptoStore.enableKeyGossiping(enable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun isKeyGossipingEnabled(): Boolean {
 | 
			
		||||
        TODO("Not yet implemented")
 | 
			
		||||
        return cryptoStore.isKeyGossipingEnabled()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import dagger.Lazy
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 | 
			
		||||
import org.matrix.android.sdk.api.logger.LoggerTag
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
 | 
			
		||||
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
 | 
			
		||||
@ -99,12 +100,12 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor(
 | 
			
		||||
                        (now - lastTry.timestamp) > MIN_TRY_BACKUP_PERIOD_MILLIS
 | 
			
		||||
 | 
			
		||||
        if (!shouldQuery) return false
 | 
			
		||||
 | 
			
		||||
        val recoveryKey = savedKeyBackupKeyInfo?.recoveryKey ?: return false
 | 
			
		||||
        val successfullyImported = withContext(coroutineDispatchers.io) {
 | 
			
		||||
            try {
 | 
			
		||||
                    keysBackupService.get().restoreKeysWithRecoveryKey(
 | 
			
		||||
                            currentVersion,
 | 
			
		||||
                            savedKeyBackupKeyInfo?.recoveryKey ?: "",
 | 
			
		||||
                            recoveryKey,
 | 
			
		||||
                            roomId,
 | 
			
		||||
                            sessionId,
 | 
			
		||||
                            null,
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure
 | 
			
		||||
import org.matrix.android.sdk.api.failure.MatrixError
 | 
			
		||||
import org.matrix.android.sdk.api.listeners.ProgressListener
 | 
			
		||||
import org.matrix.android.sdk.api.listeners.StepProgressListener
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
 | 
			
		||||
@ -60,7 +61,6 @@ import org.matrix.android.sdk.internal.session.SessionScope
 | 
			
		||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 | 
			
		||||
import org.matrix.olm.OlmException
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import uniffi.olm.BackupRecoveryKey
 | 
			
		||||
import uniffi.olm.Request
 | 
			
		||||
import uniffi.olm.RequestType
 | 
			
		||||
import java.security.InvalidParameterException
 | 
			
		||||
@ -150,7 +150,7 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
            MegolmBackupCreationInfo(
 | 
			
		||||
                    algorithm = publicKey.backupAlgorithm,
 | 
			
		||||
                    authData = signedMegolmBackupAuthData,
 | 
			
		||||
                    recoveryKey = key.toBase58()
 | 
			
		||||
                    recoveryKey = key
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -189,9 +189,11 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) {
 | 
			
		||||
    override fun saveBackupRecoveryKey(recoveryKey: BackupRecoveryKey?, version: String?) {
 | 
			
		||||
        cryptoCoroutineScope.launch {
 | 
			
		||||
            olmMachine.saveRecoveryKey(recoveryKey, version)
 | 
			
		||||
            val recoveryKeyStr = recoveryKey?.toBase64()
 | 
			
		||||
            // TODO : change rust API to use BackupRecoveryKey
 | 
			
		||||
            olmMachine.saveRecoveryKey(recoveryKeyStr, version)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -312,7 +314,8 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
                val body = UpdateKeysBackupVersionBody(
 | 
			
		||||
                        algorithm = keysBackupVersion.algorithm,
 | 
			
		||||
                        authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
 | 
			
		||||
                        version = keysBackupVersion.version)
 | 
			
		||||
                        version = keysBackupVersion.version
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                withContext(coroutineDispatchers.io) {
 | 
			
		||||
                    sender.updateBackup(keysBackupVersion, body)
 | 
			
		||||
@ -353,14 +356,13 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String) {
 | 
			
		||||
    override suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: BackupRecoveryKey) {
 | 
			
		||||
        Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
 | 
			
		||||
        withContext(coroutineDispatchers.crypto) {
 | 
			
		||||
            // This is ~nowhere mentioned, the string here is actually a base58 encoded key.
 | 
			
		||||
            // This not really supported by the spec for the backup key, the 4S key supports
 | 
			
		||||
            // base58 encoding and the same method seems to be used here.
 | 
			
		||||
            val key = BackupRecoveryKey.fromBase58(recoveryKey)
 | 
			
		||||
            checkRecoveryKey(key, keysBackupVersion)
 | 
			
		||||
            checkRecoveryKey(recoveryKey, keysBackupVersion)
 | 
			
		||||
            trustKeysBackupVersion(keysBackupVersion, true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -378,7 +380,7 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
        withContext(coroutineDispatchers.crypto) {
 | 
			
		||||
            try {
 | 
			
		||||
                val version = sender.getKeyBackupLastVersion()?.toKeysVersionResult()
 | 
			
		||||
 | 
			
		||||
                Timber.v("Keybackup version: $version")
 | 
			
		||||
                if (version != null) {
 | 
			
		||||
                    val key = BackupRecoveryKey.fromBase64(secret)
 | 
			
		||||
                    if (isValidRecoveryKey(key, version)) {
 | 
			
		||||
@ -395,7 +397,9 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        // we can save, it's valid
 | 
			
		||||
                        saveBackupRecoveryKey(key.toBase64(), version.version)
 | 
			
		||||
                        saveBackupRecoveryKey(key, version.version)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Timber.d("Invalid recovery key")
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server")
 | 
			
		||||
@ -480,7 +484,7 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Save for next time and for gossiping
 | 
			
		||||
            saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version)
 | 
			
		||||
            saveBackupRecoveryKey(recoveryKey, keysVersionResult.version)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        withContext(coroutineDispatchers.main) {
 | 
			
		||||
@ -519,14 +523,18 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
                stepProgressListener?.onStepProgress(stepProgress)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
 | 
			
		||||
                    " of ${data.roomIdToRoomKeysBackupData.size} rooms from the backup store on the homeserver")
 | 
			
		||||
            Timber.v(
 | 
			
		||||
                    "restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
 | 
			
		||||
                            " of ${data.roomIdToRoomKeysBackupData.size} rooms from the backup store on the homeserver"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            // Do not trigger a backup for them if they come from the backup version we are using
 | 
			
		||||
            val backUp = keysVersionResult.version != keysBackupVersion?.version
 | 
			
		||||
            if (backUp) {
 | 
			
		||||
                Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" +
 | 
			
		||||
                        " to backup version: ${keysBackupVersion?.version}")
 | 
			
		||||
                Timber.v(
 | 
			
		||||
                        "restoreKeysWithRecoveryKey: Those keys will be backed up" +
 | 
			
		||||
                                " to backup version: ${keysBackupVersion?.version}"
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Import them into the crypto store
 | 
			
		||||
@ -557,15 +565,12 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
 | 
			
		||||
                                                    recoveryKey: String,
 | 
			
		||||
                                                    recoveryKey: BackupRecoveryKey,
 | 
			
		||||
                                                    roomId: String?,
 | 
			
		||||
                                                    sessionId: String?,
 | 
			
		||||
                                                    stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
 | 
			
		||||
        Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
 | 
			
		||||
 | 
			
		||||
        val key = BackupRecoveryKey.fromBase58(recoveryKey)
 | 
			
		||||
 | 
			
		||||
        return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
 | 
			
		||||
        return restoreBackup(keysVersionResult, recoveryKey, roomId, sessionId, stepProgressListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
 | 
			
		||||
@ -577,7 +582,6 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
        val recoveryKey = withContext(coroutineDispatchers.crypto) {
 | 
			
		||||
            recoveryKeyFromPassword(password, keysBackupVersion)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -703,16 +707,15 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
    private fun isValidRecoveryKey(recoveryKey: BackupRecoveryKey, version: KeysVersionResult): Boolean {
 | 
			
		||||
        val publicKey = recoveryKey.megolmV1PublicKey().publicKey
 | 
			
		||||
        val authData = getMegolmBackupAuthData(version) ?: return false
 | 
			
		||||
        Timber.v("recoveryKey.megolmV1PublicKey().publicKey $publicKey == getMegolmBackupAuthData(version).publicKey ${authData.publicKey}")
 | 
			
		||||
        return authData.publicKey == publicKey
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean {
 | 
			
		||||
    override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: BackupRecoveryKey): Boolean {
 | 
			
		||||
        return withContext(coroutineDispatchers.crypto) {
 | 
			
		||||
            val keysBackupVersion = keysBackupVersion ?: return@withContext false
 | 
			
		||||
 | 
			
		||||
            val key = BackupRecoveryKey.fromBase64(recoveryKey)
 | 
			
		||||
            try {
 | 
			
		||||
                isValidRecoveryKey(key, keysBackupVersion)
 | 
			
		||||
                isValidRecoveryKey(recoveryKey, keysBackupVersion)
 | 
			
		||||
            } catch (failure: Throwable) {
 | 
			
		||||
                Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key")
 | 
			
		||||
                false
 | 
			
		||||
@ -726,7 +729,9 @@ internal class RustKeyBackupService @Inject constructor(
 | 
			
		||||
 | 
			
		||||
    override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
 | 
			
		||||
        val info = olmMachine.getBackupKeys() ?: return null
 | 
			
		||||
        return SavedKeyBackupKeyInfo(info.recoveryKey, info.backupVersion)
 | 
			
		||||
        // TODO change rust ffi to return BackupRecoveryKey instead of base64 string
 | 
			
		||||
        val backupRecoveryKey = BackupRecoveryKey.fromBase64(info.recoveryKey)
 | 
			
		||||
        return SavedKeyBackupKeyInfo(backupRecoveryKey, info.backupVersion)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -34,10 +34,10 @@ import uniffi.olm.RequestType
 | 
			
		||||
 | 
			
		||||
private val loggerTag = LoggerTag("OutgoingRequestsProcessor", LoggerTag.CRYPTO)
 | 
			
		||||
 | 
			
		||||
internal class NetworkRequestsProcessor(private val requestSender: RequestSender,
 | 
			
		||||
                                        private val coroutineScope: CoroutineScope,
 | 
			
		||||
                                        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
 | 
			
		||||
                                        private val shieldComputer: ShieldComputer,) {
 | 
			
		||||
internal class OutgoingRequestsProcessor(private val requestSender: RequestSender,
 | 
			
		||||
                                         private val coroutineScope: CoroutineScope,
 | 
			
		||||
                                         private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
 | 
			
		||||
                                         private val shieldComputer: ShieldComputer,) {
 | 
			
		||||
 | 
			
		||||
    fun interface ShieldComputer {
 | 
			
		||||
        suspend fun compute(userIds: List<String>): RoomEncryptionTrustLevel
 | 
			
		||||
@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 | 
			
		||||
@ -469,7 +470,8 @@ internal class RealmCryptoStore @Inject constructor(
 | 
			
		||||
                        val key = it.keyBackupRecoveryKey
 | 
			
		||||
                        val version = it.keyBackupRecoveryKeyVersion
 | 
			
		||||
                        if (!key.isNullOrBlank() && !version.isNullOrBlank()) {
 | 
			
		||||
                            SavedKeyBackupKeyInfo(recoveryKey = key, version = version)
 | 
			
		||||
                            val backupRecoveryKey = BackupRecoveryKey.fromBase58(key)
 | 
			
		||||
                            SavedKeyBackupKeyInfo(recoveryKey = backupRecoveryKey, version = version)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            null
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi
 | 
			
		||||
import dagger.Module
 | 
			
		||||
import dagger.Provides
 | 
			
		||||
import okhttp3.ConnectionSpec
 | 
			
		||||
import okhttp3.Dispatcher
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.Protocol
 | 
			
		||||
import okhttp3.logging.HttpLoggingInterceptor
 | 
			
		||||
@ -70,7 +71,9 @@ internal object NetworkModule {
 | 
			
		||||
                             curlLoggingInterceptor: CurlLoggingInterceptor,
 | 
			
		||||
                             apiInterceptor: ApiInterceptor): OkHttpClient {
 | 
			
		||||
        val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build()
 | 
			
		||||
 | 
			
		||||
        val dispatcher = Dispatcher().apply {
 | 
			
		||||
            maxRequestsPerHost = 64
 | 
			
		||||
        }
 | 
			
		||||
        return OkHttpClient.Builder()
 | 
			
		||||
                // workaround for #4669
 | 
			
		||||
                .protocols(listOf(Protocol.HTTP_1_1))
 | 
			
		||||
@ -94,6 +97,7 @@ internal object NetworkModule {
 | 
			
		||||
                        proxy(it)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .dispatcher(dispatcher)
 | 
			
		||||
                .connectionSpecs(Collections.singletonList(spec))
 | 
			
		||||
                .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -520,10 +520,9 @@ internal class RoomSyncHandler @Inject constructor(
 | 
			
		||||
 | 
			
		||||
    private fun decryptIfNeeded(event: Event, roomId: String) {
 | 
			
		||||
        try {
 | 
			
		||||
            // Event from sync does not have roomId, so add it to the event first
 | 
			
		||||
            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
 | 
			
		||||
            val result = runBlocking {
 | 
			
		||||
                cryptoService.decryptEvent(event.copy(roomId = roomId), "")
 | 
			
		||||
                cryptoService.decryptEvent(event, "")
 | 
			
		||||
            }
 | 
			
		||||
            event.mxDecryptionResult = OlmDecryptionResult(
 | 
			
		||||
                    payload = result.clearEvent,
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ import im.vector.app.core.platform.WaitingViewData
 | 
			
		||||
import im.vector.app.core.resources.StringProvider
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class KeysBackupRestoreFromKeyViewModel @Inject constructor(
 | 
			
		||||
@ -42,7 +43,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor(
 | 
			
		||||
        sharedViewModel.loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
 | 
			
		||||
        recoveryCodeErrorText.value = null
 | 
			
		||||
        viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
            val recoveryKey = recoveryCode.value!!
 | 
			
		||||
            val recoveryKey = BackupRecoveryKey.fromBase58(recoveryCode.value!!)
 | 
			
		||||
            try {
 | 
			
		||||
                sharedViewModel.recoverUsingBackupRecoveryKey(recoveryKey)
 | 
			
		||||
            } catch (failure: Throwable) {
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 | 
			
		||||
import org.matrix.android.sdk.api.listeners.StepProgressListener
 | 
			
		||||
import org.matrix.android.sdk.api.session.Session
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
 | 
			
		||||
@ -88,7 +89,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
    private val progressObserver = object : StepProgressListener {
 | 
			
		||||
        override fun onStepProgress(step: StepProgressListener.Step) {
 | 
			
		||||
            when (step) {
 | 
			
		||||
                is StepProgressListener.Step.ComputingKey  -> {
 | 
			
		||||
                is StepProgressListener.Step.ComputingKey   -> {
 | 
			
		||||
                    loadingEvent.postValue(
 | 
			
		||||
                            WaitingViewData(
 | 
			
		||||
                                    stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
 | 
			
		||||
@ -107,7 +108,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
                            )
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                is StepProgressListener.Step.ImportingKey  -> {
 | 
			
		||||
                is StepProgressListener.Step.ImportingKey   -> {
 | 
			
		||||
                    Timber.d("backupKeys.ImportingKey.progress: ${step.progress}")
 | 
			
		||||
                    // Progress 0 can take a while, display an indeterminate progress in this case
 | 
			
		||||
                    if (step.progress == 0) {
 | 
			
		||||
@ -131,14 +132,22 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
                }
 | 
			
		||||
                is StepProgressListener.Step.DecryptingKey  -> {
 | 
			
		||||
                    if (step.progress == 0) {
 | 
			
		||||
                        loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
 | 
			
		||||
                                "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
 | 
			
		||||
                                isIndeterminate = true))
 | 
			
		||||
                        loadingEvent.postValue(
 | 
			
		||||
                                WaitingViewData(
 | 
			
		||||
                                        stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
 | 
			
		||||
                                                "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
 | 
			
		||||
                                        isIndeterminate = true
 | 
			
		||||
                                )
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
 | 
			
		||||
                                "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
 | 
			
		||||
                                step.progress,
 | 
			
		||||
                                step.total))
 | 
			
		||||
                        loadingEvent.postValue(
 | 
			
		||||
                                WaitingViewData(
 | 
			
		||||
                                        stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
 | 
			
		||||
                                                "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
 | 
			
		||||
                                        step.progress,
 | 
			
		||||
                                        step.total
 | 
			
		||||
                                )
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -170,7 +179,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
                )
 | 
			
		||||
                // Go and use it!!
 | 
			
		||||
                try {
 | 
			
		||||
                    recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version)
 | 
			
		||||
                    recoverUsingBackupRecoveryKey(savedSecret.recoveryKey, version)
 | 
			
		||||
                } catch (failure: Throwable) {
 | 
			
		||||
                    Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED")
 | 
			
		||||
                    keySourceModel.postValue(
 | 
			
		||||
@ -212,7 +221,9 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
 | 
			
		||||
                viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64()))
 | 
			
		||||
                        val computedRecoveryKey = computeRecoveryKey(secret.fromBase64())
 | 
			
		||||
                        val backupRecoveryKey = BackupRecoveryKey.fromBase58(computedRecoveryKey)
 | 
			
		||||
                        recoverUsingBackupRecoveryKey(backupRecoveryKey)
 | 
			
		||||
                    } catch (failure: Throwable) {
 | 
			
		||||
                        _navigateEvent.postValue(
 | 
			
		||||
                                LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
 | 
			
		||||
@ -234,7 +245,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
        loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val result = keysBackup.restoreKeyBackupWithPassword(keyVersion,
 | 
			
		||||
            val result = keysBackup.restoreKeyBackupWithPassword(
 | 
			
		||||
                    keyVersion,
 | 
			
		||||
                    passphrase,
 | 
			
		||||
                    null,
 | 
			
		||||
                    session.myUserId,
 | 
			
		||||
@ -249,7 +261,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String, keyVersion: KeysVersionResult? = null) {
 | 
			
		||||
    suspend fun recoverUsingBackupRecoveryKey(recoveryKey: BackupRecoveryKey, keyVersion: KeysVersionResult? = null) {
 | 
			
		||||
        val keysBackup = session.cryptoService().keysBackupService()
 | 
			
		||||
        // This is badddddd
 | 
			
		||||
        val version = keyVersion ?: keyVersionResult.value ?: return
 | 
			
		||||
@ -257,7 +269,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
 | 
			
		||||
        loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val result = keysBackup.restoreKeysWithRecoveryKey(version,
 | 
			
		||||
            val result = keysBackup.restoreKeysWithRecoveryKey(
 | 
			
		||||
                    version,
 | 
			
		||||
                    recoveryKey,
 | 
			
		||||
                    null,
 | 
			
		||||
                    session.myUserId,
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ import im.vector.app.core.time.Clock
 | 
			
		||||
import im.vector.app.core.utils.LiveEvent
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import org.matrix.android.sdk.api.session.Session
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
 | 
			
		||||
@ -71,7 +72,7 @@ class KeysBackupSetupSharedViewModel @Inject constructor(
 | 
			
		||||
    // Step 3
 | 
			
		||||
    // Var to ignore events from previous request(s) to generate a recovery key
 | 
			
		||||
    private var currentRequestId: MutableLiveData<Long> = MutableLiveData()
 | 
			
		||||
    var recoveryKey: MutableLiveData<String?> = MutableLiveData(null)
 | 
			
		||||
    var recoveryKey: MutableLiveData<BackupRecoveryKey?> = MutableLiveData(null)
 | 
			
		||||
    var prepareRecoverFailError: MutableLiveData<Throwable?> = MutableLiveData(null)
 | 
			
		||||
    var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
 | 
			
		||||
    var copyHasBeenMade = false
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
 | 
			
		||||
                views.keysBackupSetupStep3Label2.text = getString(R.string.keys_backup_setup_step3_text_line2_no_passphrase)
 | 
			
		||||
                views.keysBackupSetupStep3FinishButton.text = getString(R.string.keys_backup_setup_step3_button_title_no_passphrase)
 | 
			
		||||
 | 
			
		||||
                views.keysBackupSetupStep3RecoveryKeyText.text = viewModel.recoveryKey.value!!
 | 
			
		||||
                views.keysBackupSetupStep3RecoveryKeyText.text = viewModel.recoveryKey.value!!.toBase58()
 | 
			
		||||
                        .replace(" ", "")
 | 
			
		||||
                        .chunked(16)
 | 
			
		||||
                        .joinToString("\n") {
 | 
			
		||||
@ -114,7 +114,8 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
 | 
			
		||||
        } else {
 | 
			
		||||
            dialog.findViewById<TextView>(R.id.keys_backup_recovery_key_text)?.let {
 | 
			
		||||
                it.isVisible = true
 | 
			
		||||
                it.text = recoveryKey.replace(" ", "")
 | 
			
		||||
                it.text = recoveryKey.toBase58()
 | 
			
		||||
                        .replace(" ", "")
 | 
			
		||||
                        .chunked(16)
 | 
			
		||||
                        .joinToString("\n") {
 | 
			
		||||
                            it
 | 
			
		||||
@ -123,7 +124,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                it.debouncedClicks {
 | 
			
		||||
                    copyToClipboard(requireActivity(), recoveryKey)
 | 
			
		||||
                    copyToClipboard(requireActivity(), recoveryKey.toBase58())
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -145,7 +146,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
 | 
			
		||||
                    fragment = this,
 | 
			
		||||
                    activityResultLauncher = null,
 | 
			
		||||
                    chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
 | 
			
		||||
                    text = recoveryKey,
 | 
			
		||||
                    text = recoveryKey.toBase58(),
 | 
			
		||||
                    subject = context?.getString(R.string.recovery_key)
 | 
			
		||||
            )
 | 
			
		||||
            viewModel.copyHasBeenMade = true
 | 
			
		||||
@ -159,7 +160,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
 | 
			
		||||
        viewModel.recoveryKey.value?.let {
 | 
			
		||||
            viewModel.copyHasBeenMade = true
 | 
			
		||||
 | 
			
		||||
            copyToClipboard(requireActivity(), it)
 | 
			
		||||
            copyToClipboard(requireActivity(), it.toBase58())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -202,7 +203,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
 | 
			
		||||
        val uri = activityRessult.data?.data ?: return@registerStartForActivityResult
 | 
			
		||||
        if (activityRessult.resultCode == Activity.RESULT_OK) {
 | 
			
		||||
            viewModel.recoveryKey.value?.let {
 | 
			
		||||
                exportRecoveryKeyToFile(uri, it)
 | 
			
		||||
                exportRecoveryKeyToFile(uri, it.toBase58())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ import im.vector.app.core.resources.StringProvider
 | 
			
		||||
import org.matrix.android.sdk.api.listeners.ProgressListener
 | 
			
		||||
import org.matrix.android.sdk.api.session.Session
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
 | 
			
		||||
@ -91,8 +92,8 @@ class BackupToQuadSMigrationTask @Inject constructor(
 | 
			
		||||
 | 
			
		||||
            reportProgress(params, R.string.bootstrap_progress_compute_curve_key)
 | 
			
		||||
            val recoveryKey = computeRecoveryKey(curveKey)
 | 
			
		||||
 | 
			
		||||
            val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey)
 | 
			
		||||
            val backupRecoveryKey = BackupRecoveryKey.fromBase58(recoveryKey)
 | 
			
		||||
            val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(backupRecoveryKey)
 | 
			
		||||
 | 
			
		||||
            if (!isValid) return Result.InvalidRecoverySecret
 | 
			
		||||
 | 
			
		||||
@ -143,7 +144,7 @@ class BackupToQuadSMigrationTask @Inject constructor(
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            // save for gossiping
 | 
			
		||||
            keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version)
 | 
			
		||||
            keysBackupService.saveBackupRecoveryKey(backupRecoveryKey, version.version)
 | 
			
		||||
            // It's not a good idea to download the full backup, it might take very long
 | 
			
		||||
            // and use a lot of resources
 | 
			
		||||
            return Result.Success
 | 
			
		||||
 | 
			
		||||
@ -236,7 +236,7 @@ class BootstrapCrossSigningTask @Inject constructor(
 | 
			
		||||
                Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping")
 | 
			
		||||
                session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
 | 
			
		||||
 | 
			
		||||
                extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
 | 
			
		||||
                extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret ->
 | 
			
		||||
                    ssssService.storeSecret(
 | 
			
		||||
                            KEYBACKUP_SECRET_SSSS_NAME,
 | 
			
		||||
                            secret,
 | 
			
		||||
@ -251,7 +251,7 @@ class BootstrapCrossSigningTask @Inject constructor(
 | 
			
		||||
                    val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey)
 | 
			
		||||
                    if (isValid) {
 | 
			
		||||
                        Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
 | 
			
		||||
                        extractCurveKeyFromRecoveryKey(knownMegolmSecret.recoveryKey)?.toBase64NoPadding()?.let { secret ->
 | 
			
		||||
                        extractCurveKeyFromRecoveryKey(knownMegolmSecret.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret ->
 | 
			
		||||
                            ssssService.storeSecret(
 | 
			
		||||
                                    KEYBACKUP_SECRET_SSSS_NAME,
 | 
			
		||||
                                    secret,
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
 | 
			
		||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
 | 
			
		||||
@ -431,9 +432,10 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
 | 
			
		||||
                val version = session.cryptoService().keysBackupService().getCurrentVersion()?.toKeysVersionResult() ?: return@launch
 | 
			
		||||
 | 
			
		||||
                val recoveryKey = computeRecoveryKey(secret.fromBase64())
 | 
			
		||||
                val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(recoveryKey)
 | 
			
		||||
                val backupRecoveryKey = BackupRecoveryKey.fromBase58(recoveryKey)
 | 
			
		||||
                val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(backupRecoveryKey)
 | 
			
		||||
                if (isValid) {
 | 
			
		||||
                    session.cryptoService().keysBackupService().saveBackupRecoveryKey(recoveryKey, version.version)
 | 
			
		||||
                    session.cryptoService().keysBackupService().saveBackupRecoveryKey(backupRecoveryKey, version.version)
 | 
			
		||||
                }
 | 
			
		||||
                session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true)
 | 
			
		||||
            } catch (failure: Throwable) {
 | 
			
		||||
@ -502,6 +504,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
 | 
			
		||||
        Timber.v("transactionUpdated: $tx")
 | 
			
		||||
        handleTransactionUpdate(state, tx)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -510,7 +513,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
 | 
			
		||||
 | 
			
		||||
        Timber.v("VerificationRequestUpdated: $pr")
 | 
			
		||||
        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) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user