Merge pull request #10 from poljar/feature/fga/backup_recovery_key

Feature/fga/backup recovery key
This commit is contained in:
ganfra 2022-06-02 15:39:10 +02:00 committed by GitHub
commit 022057dd6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 210 additions and 113 deletions

View file

@ -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.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.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.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.MegolmBackupAuthData
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
@ -322,7 +323,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
return MegolmBackupCreationInfo( return MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = createFakeMegolmBackupAuthData(), authData = createFakeMegolmBackupAuthData(),
recoveryKey = "fake" recoveryKey = BackupRecoveryKey.fromBase58("3cnTdW")
) )
} }
@ -518,7 +519,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
// Save it for gossiping // Save it for gossiping
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret ->
ssssService.storeSecret( ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME,
secret, secret,

View file

@ -597,6 +597,7 @@ class E2eeSanityTests : InstrumentedTest {
// for the test we just accept? // for the test we just accept?
oldCode = sasTx.getDecimalCodeRepresentation() oldCode = sasTx.getDecimalCodeRepresentation()
testHelper.runBlockingTest { testHelper.runBlockingTest {
delay(500)
sasTx.userHasVerifiedShortCode() sasTx.userHasVerifiedShortCode()
} }
} }
@ -623,11 +624,12 @@ class E2eeSanityTests : InstrumentedTest {
val sasTx = tx as SasVerificationTransaction val sasTx = tx as SasVerificationTransaction
when (sasTx.state) { when (sasTx.state) {
VerificationTxState.ShortCodeReady -> { VerificationTxState.ShortCodeReady -> {
newCode = sasTx.getDecimalCodeRepresentation()
if (matchOnce) { if (matchOnce) {
testHelper.runBlockingTest { testHelper.runBlockingTest {
delay(500)
sasTx.userHasVerifiedShortCode() sasTx.userHasVerifiedShortCode()
} }
newCode = sasTx.getDecimalCodeRepresentation()
matchOnce = false matchOnce = false
} }
} }
@ -669,6 +671,7 @@ class E2eeSanityTests : InstrumentedTest {
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
} }
} }
testHelper.runBlockingTest { testHelper.runBlockingTest {
assertEquals( assertEquals(
"MSK Private parts should be the same", "MSK Private parts should be the same",

View file

@ -34,6 +34,7 @@ import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP 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.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.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
@ -505,7 +506,7 @@ class KeysBackupTest : InstrumentedTest {
try { try {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
"Bad recovery key" BackupRecoveryKey.fromBase58("Bad recovery key")
) )
fail("Should have failed to trust") fail("Should have failed to trust")
} catch (failure: Throwable) { } catch (failure: Throwable) {
@ -645,7 +646,7 @@ class KeysBackupTest : InstrumentedTest {
var importRoomKeysResult: ImportRoomKeysResult? = null var importRoomKeysResult: ImportRoomKeysResult? = null
testHelper.runBlockingTest { testHelper.runBlockingTest {
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, 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, null,
null null

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.verification
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
@ -40,10 +41,9 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -322,8 +322,6 @@ class SASTest : InstrumentedTest {
*/ */
} }
// any two devices may only have at most one key verification in flight at a time. // any two devices may only have at most one key verification in flight at a time.
// If a device has two verifications in progress with the same device, then it should cancel both verifications. // If a device has two verifications in progress with the same device, then it should cancel both verifications.
@Test @Test
@ -449,7 +447,8 @@ class SASTest : InstrumentedTest {
aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId) aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
} }
testHelper.await(latch) 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 val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation()) assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
@ -470,25 +469,30 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1) val verifiedLatch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated pr=$pr") Timber.v("RequestUpdated pr=$pr")
} }
var matchOnce = true var matched = false
var verified = false
override fun transactionUpdated(tx: VerificationTransaction) { 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 if (tx !is SasVerificationTransaction) return
when (tx.state) { when (tx.state) {
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest { VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
if (!matched) {
matched = true
delay(500)
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
} }
}
VerificationTxState.Verified -> { VerificationTxState.Verified -> {
if (matchOnce) { if (!verified) {
matchOnce = false verified = true
aliceSASLatch.countDown() verifiedLatch.countDown()
} }
} }
else -> Unit else -> Unit
@ -497,33 +501,37 @@ class SASTest : InstrumentedTest {
} }
aliceVerificationService.addListener(aliceListener) aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
var acceptOnce = true var accepted = false
var matchOnce = true var matched = false
var verified = false
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated: pr=$pr") Timber.v("RequestUpdated: pr=$pr")
} }
override fun transactionUpdated(tx: VerificationTransaction) { 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 if (tx !is SasVerificationTransaction) return
when (tx.state) { when (tx.state) {
VerificationTxState.OnStarted -> testHelper.runBlockingTest { VerificationTxState.OnStarted -> testHelper.runBlockingTest {
if (acceptOnce) { if (!accepted) {
acceptOnce = false accepted = true
tx.acceptVerification() tx.acceptVerification()
} }
} }
VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest { VerificationTxState.ShortCodeReady -> testHelper.runBlockingTest {
if (matchOnce) { if (!matched) {
matchOnce = false matched = true
delay(500)
tx.userHasVerifiedShortCode() tx.userHasVerifiedShortCode()
} }
} }
VerificationTxState.ShortCodeAccepted -> { VerificationTxState.Verified -> {
bobSASLatch.countDown() if (!verified) {
verified = true
verifiedLatch.countDown()
}
} }
else -> Unit else -> Unit
} }
@ -538,8 +546,8 @@ class SASTest : InstrumentedTest {
testHelper.runBlockingTest { testHelper.runBlockingTest {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId) aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
} }
testHelper.await(aliceSASLatch) Timber.v("Await after beginKey ${Thread.currentThread()}")
testHelper.await(bobSASLatch) testHelper.await(verifiedLatch)
// Assert that devices are verified // Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest { val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = testHelper.runBlockingTest {

View file

@ -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()
}

View file

@ -142,8 +142,7 @@ interface KeysBackupService {
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param recoveryKey the recovery key to challenge with the key backup public key. * @param recoveryKey the recovery key to challenge with the key backup public key.
*/ */
suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: BackupRecoveryKey)
recoveryKey: String)
/** /**
* Set trust on a keys backup version. * 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. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: String, roomId: String?, recoveryKey: BackupRecoveryKey, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?): ImportRoomKeysResult stepProgressListener: StepProgressListener?): ImportRoomKeysResult
@ -194,10 +193,10 @@ interface KeysBackupService {
val state: KeysBackupState val state: KeysBackupState
// For gossiping // For gossiping
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: BackupRecoveryKey?, version: String?)
suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: BackupRecoveryKey): Boolean
fun computePrivateKey(passphrase: String, fun computePrivateKey(passphrase: String,
privateKeySalt: String, privateKeySalt: String,

View file

@ -31,7 +31,7 @@ data class MegolmBackupCreationInfo(
val authData: MegolmBackupAuthData, val authData: MegolmBackupAuthData,
/** /**
* The Base58 recovery key. * The recovery key.
*/ */
val recoveryKey: String val recoveryKey: BackupRecoveryKey
) )

View file

@ -17,6 +17,6 @@
package org.matrix.android.sdk.api.session.crypto.keysbackup package org.matrix.android.sdk.api.session.crypto.keysbackup
data class SavedKeyBackupKeyInfo( data class SavedKeyBackupKeyInfo(
val recoveryKey: String, val recoveryKey: BackupRecoveryKey,
val version: String val version: String
) )

View file

@ -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.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse 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.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.network.RequestSender
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore 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 olmMachine by lazy { olmMachineProvider.olmMachine }
private val outgoingRequestsProcessor = NetworkRequestsProcessor( private val outgoingRequestsProcessor = OutgoingRequestsProcessor(
requestSender = requestSender, requestSender = requestSender,
coroutineScope = cryptoCoroutineScope, coroutineScope = cryptoCoroutineScope,
cryptoSessionInfoProvider = cryptoSessionInfoProvider, cryptoSessionInfoProvider = cryptoSessionInfoProvider,

View file

@ -20,10 +20,9 @@ import dagger.Lazy
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag 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.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.util.awaitCallback
import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.RustKeyBackupService
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
@ -101,12 +100,12 @@ internal class PerSessionBackupQueryRateLimiter @Inject constructor(
(now - lastTry.timestamp) > MIN_TRY_BACKUP_PERIOD_MILLIS (now - lastTry.timestamp) > MIN_TRY_BACKUP_PERIOD_MILLIS
if (!shouldQuery) return false if (!shouldQuery) return false
val recoveryKey = savedKeyBackupKeyInfo?.recoveryKey ?: return false
val successfullyImported = withContext(coroutineDispatchers.io) { val successfullyImported = withContext(coroutineDispatchers.io) {
try { try {
keysBackupService.get().restoreKeysWithRecoveryKey( keysBackupService.get().restoreKeysWithRecoveryKey(
currentVersion, currentVersion,
savedKeyBackupKeyInfo?.recoveryKey ?: "", recoveryKey,
roomId, roomId,
sessionId, sessionId,
null, null,

View file

@ -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.failure.MatrixError
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.listeners.StepProgressListener 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.KeysBackupLastVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState 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.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.olm.OlmException import org.matrix.olm.OlmException
import timber.log.Timber import timber.log.Timber
import uniffi.olm.BackupRecoveryKey
import uniffi.olm.Request import uniffi.olm.Request
import uniffi.olm.RequestType import uniffi.olm.RequestType
import java.security.InvalidParameterException import java.security.InvalidParameterException
@ -150,7 +150,7 @@ internal class RustKeyBackupService @Inject constructor(
MegolmBackupCreationInfo( MegolmBackupCreationInfo(
algorithm = publicKey.backupAlgorithm, algorithm = publicKey.backupAlgorithm,
authData = signedMegolmBackupAuthData, 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 { 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( val body = UpdateKeysBackupVersionBody(
algorithm = keysBackupVersion.algorithm, algorithm = keysBackupVersion.algorithm,
authData = newAuthData.copy(signatures = newSignatures).toJsonDict(), authData = newAuthData.copy(signatures = newSignatures).toJsonDict(),
version = keysBackupVersion.version) version = keysBackupVersion.version
)
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
sender.updateBackup(keysBackupVersion, body) 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}") Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
withContext(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
// This is ~nowhere mentioned, the string here is actually a base58 encoded key. // 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 // 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. // base58 encoding and the same method seems to be used here.
val key = BackupRecoveryKey.fromBase58(recoveryKey) checkRecoveryKey(recoveryKey, keysBackupVersion)
checkRecoveryKey(key, keysBackupVersion)
trustKeysBackupVersion(keysBackupVersion, true) trustKeysBackupVersion(keysBackupVersion, true)
} }
} }
@ -378,7 +380,7 @@ internal class RustKeyBackupService @Inject constructor(
withContext(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
try { try {
val version = sender.getKeyBackupLastVersion()?.toKeysVersionResult() val version = sender.getKeyBackupLastVersion()?.toKeysVersionResult()
Timber.v("Keybackup version: $version")
if (version != null) { if (version != null) {
val key = BackupRecoveryKey.fromBase64(secret) val key = BackupRecoveryKey.fromBase64(secret)
if (isValidRecoveryKey(key, version)) { if (isValidRecoveryKey(key, version)) {
@ -395,7 +397,9 @@ internal class RustKeyBackupService @Inject constructor(
} }
} }
// we can save, it's valid // we can save, it's valid
saveBackupRecoveryKey(key.toBase64(), version.version) saveBackupRecoveryKey(key, version.version)
} else {
Timber.d("Invalid recovery key")
} }
} else { } else {
Timber.e("onSecretKeyGossip: Failed to import backup recovery key, no backup version was found on the server") 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 // Save for next time and for gossiping
saveBackupRecoveryKey(recoveryKey.toBase64(), keysVersionResult.version) saveBackupRecoveryKey(recoveryKey, keysVersionResult.version)
} }
withContext(coroutineDispatchers.main) { withContext(coroutineDispatchers.main) {
@ -519,14 +523,18 @@ internal class RustKeyBackupService @Inject constructor(
stepProgressListener?.onStepProgress(stepProgress) stepProgressListener?.onStepProgress(stepProgress)
} }
Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" + Timber.v(
" of ${data.roomIdToRoomKeysBackupData.size} rooms from the backup store on the homeserver") "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 // Do not trigger a backup for them if they come from the backup version we are using
val backUp = keysVersionResult.version != keysBackupVersion?.version val backUp = keysVersionResult.version != keysBackupVersion?.version
if (backUp) { if (backUp) {
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" + Timber.v(
" to backup version: ${keysBackupVersion?.version}") "restoreKeysWithRecoveryKey: Those keys will be backed up" +
" to backup version: ${keysBackupVersion?.version}"
)
} }
// Import them into the crypto store // Import them into the crypto store
@ -557,15 +565,12 @@ internal class RustKeyBackupService @Inject constructor(
} }
override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: String, recoveryKey: BackupRecoveryKey,
roomId: String?, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?): ImportRoomKeysResult { stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
return restoreBackup(keysVersionResult, recoveryKey, roomId, sessionId, stepProgressListener)
val key = BackupRecoveryKey.fromBase58(recoveryKey)
return restoreBackup(keysVersionResult, key, roomId, sessionId, stepProgressListener)
} }
override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
@ -577,7 +582,6 @@ internal class RustKeyBackupService @Inject constructor(
val recoveryKey = withContext(coroutineDispatchers.crypto) { val recoveryKey = withContext(coroutineDispatchers.crypto) {
recoveryKeyFromPassword(password, keysBackupVersion) recoveryKeyFromPassword(password, keysBackupVersion)
} }
return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener) return restoreBackup(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener)
} }
@ -703,16 +707,15 @@ internal class RustKeyBackupService @Inject constructor(
private fun isValidRecoveryKey(recoveryKey: BackupRecoveryKey, version: KeysVersionResult): Boolean { private fun isValidRecoveryKey(recoveryKey: BackupRecoveryKey, version: KeysVersionResult): Boolean {
val publicKey = recoveryKey.megolmV1PublicKey().publicKey val publicKey = recoveryKey.megolmV1PublicKey().publicKey
val authData = getMegolmBackupAuthData(version) ?: return false val authData = getMegolmBackupAuthData(version) ?: return false
Timber.v("recoveryKey.megolmV1PublicKey().publicKey $publicKey == getMegolmBackupAuthData(version).publicKey ${authData.publicKey}")
return authData.publicKey == publicKey return authData.publicKey == publicKey
} }
override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String): Boolean { override suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: BackupRecoveryKey): Boolean {
return withContext(coroutineDispatchers.crypto) { return withContext(coroutineDispatchers.crypto) {
val keysBackupVersion = keysBackupVersion ?: return@withContext false val keysBackupVersion = keysBackupVersion ?: return@withContext false
val key = BackupRecoveryKey.fromBase64(recoveryKey)
try { try {
isValidRecoveryKey(key, keysBackupVersion) isValidRecoveryKey(recoveryKey, keysBackupVersion)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key") Timber.i("isValidRecoveryKeyForCurrentVersion: Invalid recovery key")
false false
@ -726,7 +729,9 @@ internal class RustKeyBackupService @Inject constructor(
override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? { override suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? {
val info = olmMachine.getBackupKeys() ?: return null 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)
} }
/** /**

View file

@ -34,7 +34,7 @@ import uniffi.olm.RequestType
private val loggerTag = LoggerTag("OutgoingRequestsProcessor", LoggerTag.CRYPTO) private val loggerTag = LoggerTag("OutgoingRequestsProcessor", LoggerTag.CRYPTO)
internal class NetworkRequestsProcessor(private val requestSender: RequestSender, internal class OutgoingRequestsProcessor(private val requestSender: RequestSender,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val shieldComputer: ShieldComputer,) { private val shieldComputer: ShieldComputer,) {

View file

@ -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.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo 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.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.keysbackup.SavedKeyBackupKeyInfo
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
@ -469,7 +470,8 @@ internal class RealmCryptoStore @Inject constructor(
val key = it.keyBackupRecoveryKey val key = it.keyBackupRecoveryKey
val version = it.keyBackupRecoveryKeyVersion val version = it.keyBackupRecoveryKeyVersion
if (!key.isNullOrBlank() && !version.isNullOrBlank()) { if (!key.isNullOrBlank() && !version.isNullOrBlank()) {
SavedKeyBackupKeyInfo(recoveryKey = key, version = version) val backupRecoveryKey = BackupRecoveryKey.fromBase58(key)
SavedKeyBackupKeyInfo(recoveryKey = backupRecoveryKey, version = version)
} else { } else {
null null
} }

View file

@ -520,10 +520,9 @@ internal class RoomSyncHandler @Inject constructor(
private fun decryptIfNeeded(event: Event, roomId: String) { private fun decryptIfNeeded(event: Event, roomId: String) {
try { 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 // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
val result = runBlocking { val result = runBlocking {
cryptoService.decryptEvent(event.copy(roomId = roomId), "") cryptoService.decryptEvent(event, "")
} }
event.mxDecryptionResult = OlmDecryptionResult( event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,

View file

@ -23,6 +23,7 @@ import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
import javax.inject.Inject import javax.inject.Inject
class KeysBackupRestoreFromKeyViewModel @Inject constructor( class KeysBackupRestoreFromKeyViewModel @Inject constructor(
@ -42,7 +43,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor(
sharedViewModel.loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) sharedViewModel.loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
recoveryCodeErrorText.value = null recoveryCodeErrorText.value = null
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val recoveryKey = recoveryCode.value!! val recoveryKey = BackupRecoveryKey.fromBase58(recoveryCode.value!!)
try { try {
sharedViewModel.recoverUsingBackupRecoveryKey(recoveryKey) sharedViewModel.recoverUsingBackupRecoveryKey(recoveryKey)
} catch (failure: Throwable) { } catch (failure: Throwable) {

View file

@ -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.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.Session 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.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.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
@ -131,14 +132,22 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
} }
is StepProgressListener.Step.DecryptingKey -> { is StepProgressListener.Step.DecryptingKey -> {
if (step.progress == 0) { if (step.progress == 0) {
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + loadingEvent.postValue(
WaitingViewData(
stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
"\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message), "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
isIndeterminate = true)) isIndeterminate = true
)
)
} else { } else {
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + loadingEvent.postValue(
WaitingViewData(
stringProvider.getString(R.string.keys_backup_restoring_waiting_message) +
"\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message), "\n" + stringProvider.getString(R.string.keys_backup_restoring_decrypting_keys_waiting_message),
step.progress, step.progress,
step.total)) step.total
)
)
} }
} }
} }
@ -170,7 +179,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
) )
// Go and use it!! // Go and use it!!
try { try {
recoverUsingBackupRecoveryKey(computeRecoveryKey(savedSecret.recoveryKey.fromBase64()), version) recoverUsingBackupRecoveryKey(savedSecret.recoveryKey, version)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED") Timber.e(failure, "## recoverUsingBackupRecoveryKey FAILED")
keySourceModel.postValue( keySourceModel.postValue(
@ -212,7 +221,9 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64())) val computedRecoveryKey = computeRecoveryKey(secret.fromBase64())
val backupRecoveryKey = BackupRecoveryKey.fromBase58(computedRecoveryKey)
recoverUsingBackupRecoveryKey(backupRecoveryKey)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_navigateEvent.postValue( _navigateEvent.postValue(
LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S) LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
@ -234,7 +245,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
try { try {
val result = keysBackup.restoreKeyBackupWithPassword(keyVersion, val result = keysBackup.restoreKeyBackupWithPassword(
keyVersion,
passphrase, passphrase,
null, null,
session.myUserId, 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() val keysBackup = session.cryptoService().keysBackupService()
// This is badddddd // This is badddddd
val version = keyVersion ?: keyVersionResult.value ?: return val version = keyVersion ?: keyVersionResult.value ?: return
@ -257,7 +269,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
try { try {
val result = keysBackup.restoreKeysWithRecoveryKey(version, val result = keysBackup.restoreKeysWithRecoveryKey(
version,
recoveryKey, recoveryKey,
null, null,
session.myUserId, session.myUserId,

View file

@ -27,6 +27,7 @@ import im.vector.app.core.time.Clock
import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.LiveEvent
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session 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.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
@ -71,7 +72,7 @@ class KeysBackupSetupSharedViewModel @Inject constructor(
// Step 3 // Step 3
// Var to ignore events from previous request(s) to generate a recovery key // Var to ignore events from previous request(s) to generate a recovery key
private var currentRequestId: MutableLiveData<Long> = MutableLiveData() 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 prepareRecoverFailError: MutableLiveData<Throwable?> = MutableLiveData(null)
var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null
var copyHasBeenMade = false var copyHasBeenMade = false

View file

@ -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.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.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(" ", "") .replace(" ", "")
.chunked(16) .chunked(16)
.joinToString("\n") { .joinToString("\n") {
@ -114,7 +114,8 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
} else { } else {
dialog.findViewById<TextView>(R.id.keys_backup_recovery_key_text)?.let { dialog.findViewById<TextView>(R.id.keys_backup_recovery_key_text)?.let {
it.isVisible = true it.isVisible = true
it.text = recoveryKey.replace(" ", "") it.text = recoveryKey.toBase58()
.replace(" ", "")
.chunked(16) .chunked(16)
.joinToString("\n") { .joinToString("\n") {
it it
@ -123,7 +124,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
} }
it.debouncedClicks { it.debouncedClicks {
copyToClipboard(requireActivity(), recoveryKey) copyToClipboard(requireActivity(), recoveryKey.toBase58())
} }
} }
} }
@ -145,7 +146,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
fragment = this, fragment = this,
activityResultLauncher = null, activityResultLauncher = null,
chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title), 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) subject = context?.getString(R.string.recovery_key)
) )
viewModel.copyHasBeenMade = true viewModel.copyHasBeenMade = true
@ -159,7 +160,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
viewModel.recoveryKey.value?.let { viewModel.recoveryKey.value?.let {
viewModel.copyHasBeenMade = true 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 val uri = activityRessult.data?.data ?: return@registerStartForActivityResult
if (activityRessult.resultCode == Activity.RESULT_OK) { if (activityRessult.resultCode == Activity.RESULT_OK) {
viewModel.recoveryKey.value?.let { viewModel.recoveryKey.value?.let {
exportRecoveryKeyToFile(uri, it) exportRecoveryKeyToFile(uri, it.toBase58())
} }
} }
} }

View file

@ -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.listeners.ProgressListener
import org.matrix.android.sdk.api.session.Session 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.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.computeRecoveryKey
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner 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) reportProgress(params, R.string.bootstrap_progress_compute_curve_key)
val recoveryKey = computeRecoveryKey(curveKey) val recoveryKey = computeRecoveryKey(curveKey)
val backupRecoveryKey = BackupRecoveryKey.fromBase58(recoveryKey)
val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(recoveryKey) val isValid = keysBackupService.isValidRecoveryKeyForCurrentVersion(backupRecoveryKey)
if (!isValid) return Result.InvalidRecoverySecret if (!isValid) return Result.InvalidRecoverySecret
@ -143,7 +144,7 @@ class BackupToQuadSMigrationTask @Inject constructor(
) )
// save for gossiping // 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 // It's not a good idea to download the full backup, it might take very long
// and use a lot of resources // and use a lot of resources
return Result.Success return Result.Success

View file

@ -236,7 +236,7 @@ class BootstrapCrossSigningTask @Inject constructor(
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping")
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey.toBase58())?.toBase64NoPadding()?.let { secret ->
ssssService.storeSecret( ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME,
secret, secret,
@ -251,7 +251,7 @@ class BootstrapCrossSigningTask @Inject constructor(
val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey) val isValid = session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey)
if (isValid) { if (isValid) {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") 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( ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME,
secret, secret,

View file

@ -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.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.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified 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.computeRecoveryKey
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode 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 version = session.cryptoService().keysBackupService().getCurrentVersion()?.toKeysVersionResult() ?: return@launch
val recoveryKey = computeRecoveryKey(secret.fromBase64()) 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) { if (isValid) {
session.cryptoService().keysBackupService().saveBackupRecoveryKey(recoveryKey, version.version) session.cryptoService().keysBackupService().saveBackupRecoveryKey(backupRecoveryKey, version.version)
} }
session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true) session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true)
} catch (failure: Throwable) { } catch (failure: Throwable) {
@ -502,6 +504,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
} }
override fun transactionUpdated(tx: VerificationTransaction) = withState { state -> override fun transactionUpdated(tx: VerificationTransaction) = withState { state ->
Timber.v("transactionUpdated: $tx")
handleTransactionUpdate(state, tx) handleTransactionUpdate(state, tx)
} }
@ -510,7 +513,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
} }
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
Timber.v("VerificationRequestUpdated: $pr")
if (state.selfVerificationMode && state.pendingRequest.invoke() == null && state.transactionId == null) { if (state.selfVerificationMode && state.pendingRequest.invoke() == null && state.transactionId == null) {
// is this an incoming with that user // is this an incoming with that user
if (pr.isIncoming && pr.otherUserId == state.otherUserMxItem?.id) { if (pr.isIncoming && pr.otherUserId == state.otherUserMxItem?.id) {