diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoAsyncHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoAsyncHelper.kt deleted file mode 100644 index 411bcbf640..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoAsyncHelper.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * - * * Copyright 2019 New Vector Ltd - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * - */ - -package im.vector.matrix.android.internal.crypto - -import android.os.Handler -import android.os.HandlerThread -import android.os.Looper - -private const val THREAD_CRYPTO_NAME = "Crypto_Thread" - -// TODO Remove and replace by Task -internal object CryptoAsyncHelper { - - private var uiHandler: Handler? = null - private var cryptoBackgroundHandler: Handler? = null - - fun getUiHandler(): Handler { - return uiHandler - ?: Handler(Looper.getMainLooper()) - .also { uiHandler = it } - } - - - fun getDecryptBackgroundHandler(): Handler { - return getCryptoBackgroundHandler() - } - - fun getEncryptBackgroundHandler(): Handler { - return getCryptoBackgroundHandler() - } - - private fun getCryptoBackgroundHandler(): Handler { - return cryptoBackgroundHandler - ?: createCryptoBackgroundHandler() - .also { cryptoBackgroundHandler = it } - } - - private fun createCryptoBackgroundHandler(): Handler { - val handlerThread = HandlerThread(THREAD_CRYPTO_NAME) - handlerThread.start() - return Handler(handlerThread.looper) - } - - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index 2b80ea41e5..b65cfe573e 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.crypto import android.content.Context +import android.os.Handler +import android.os.Looper import android.text.TextUtils import arrow.core.Try import com.zhuinden.monarchy.Monarchy @@ -138,6 +140,8 @@ internal class CryptoManager( private val taskExecutor: TaskExecutor ) : CryptoService { + private val uiHandler = Handler(Looper.getMainLooper()) + // MXEncrypting instance for each room. private val roomEncryptors: MutableMap = HashMap() private val isStarting = AtomicBoolean(false) @@ -825,41 +829,33 @@ internal class CryptoManager( password: String, progressListener: ProgressListener?, callback: MatrixCallback) { - // TODO Use coroutines - Timber.v("## importRoomKeys starts") + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + Timber.v("## importRoomKeys starts") - val t0 = System.currentTimeMillis() - val roomKeys: String + val t0 = System.currentTimeMillis() + val roomKeys: String = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - try { - roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - } catch (e: Exception) { - callback.onFailure(e) - return + val importedSessions: List + + val t1 = System.currentTimeMillis() + + Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms") + + val list = MoshiProvider.providesMoshi() + .adapter(List::class.java) + .fromJson(roomKeys) + importedSessions = list as List + + val t2 = System.currentTimeMillis() + + Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms") + + megolmSessionDataImporter.handle(importedSessions, true, uiHandler, progressListener) + } + }.foldToCallback(callback) } - - val importedSessions: List - - val t1 = System.currentTimeMillis() - - Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms") - - try { - val list = MoshiProvider.providesMoshi() - .adapter(List::class.java) - .fromJson(roomKeys) - importedSessions = list as List - } catch (e: Exception) { - Timber.e(e, "## importRoomKeys failed") - callback.onFailure(e) - return - } - - val t2 = System.currentTimeMillis() - - Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms") - - megolmSessionDataImporter.handle(importedSessions, true, progressListener, callback) } /** @@ -1067,9 +1063,9 @@ internal class CryptoManager( .executeBy(taskExecutor) } -/* ========================================================================================== - * DEBUG INFO - * ========================================================================================== */ + /* ========================================================================================== + * DEBUG INFO + * ========================================================================================== */ override fun toString(): String { return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 761318ace6..495bd6b13f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -277,6 +277,7 @@ internal class CryptoModule { // Task get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), // Task executor + get(), get()) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt index 07c8b465f4..433114df6f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -16,9 +16,9 @@ package im.vector.matrix.android.internal.crypto.actions -import im.vector.matrix.android.api.MatrixCallback +import android.os.Handler +import androidx.annotation.WorkerThread import im.vector.matrix.android.api.listeners.ProgressListener -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MegolmSessionData import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager @@ -35,34 +35,32 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice, /** * Import a list of megolm session keys. + * Must be call on the crypto coroutine thread * * @param megolmSessionsData megolm sessions. * @param backUpKeys true to back up them to the homeserver. * @param progressListener the progress listener - * @param callback + * @return import room keys result */ + @WorkerThread fun handle(megolmSessionsData: List, fromBackup: Boolean, - progressListener: ProgressListener?, - callback: MatrixCallback) { + uiHandler: Handler, + progressListener: ProgressListener?): ImportRoomKeysResult { val t0 = System.currentTimeMillis() val totalNumbersOfKeys = megolmSessionsData.size - var cpt = 0 var lastProgress = 0 var totalNumbersOfImportedKeys = 0 if (progressListener != null) { - CryptoAsyncHelper.getUiHandler().post { + uiHandler.post { progressListener.onProgress(0, 100) } } val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData) - for (megolmSessionData in megolmSessionsData) { - cpt++ - - + megolmSessionsData.forEachIndexed { cpt, megolmSessionData -> val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm) if (null != decrypting) { @@ -90,7 +88,7 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice, } if (progressListener != null) { - CryptoAsyncHelper.getUiHandler().post { + uiHandler.post { val progress = 100 * cpt / totalNumbersOfKeys if (lastProgress != progress) { @@ -111,10 +109,6 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice, Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)") - val finalTotalNumbersOfImportedKeys = totalNumbersOfImportedKeys - - CryptoAsyncHelper.getUiHandler().post { - callback.onSuccess(ImportRoomKeysResult(totalNumbersOfKeys, finalTotalNumbersOfImportedKeys)) - } + return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index b80b063fbc..60e19e465e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -31,7 +31,10 @@ import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener -import im.vector.matrix.android.internal.crypto.* +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import im.vector.matrix.android.internal.crypto.MXOlmDevice +import im.vector.matrix.android.internal.crypto.MegolmSessionData +import im.vector.matrix.android.internal.crypto.ObjectSigner import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature @@ -47,10 +50,15 @@ import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrap import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkEncryption @@ -87,7 +95,8 @@ internal class KeysBackup( private val storeSessionDataTask: StoreSessionsDataTask, private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, // Task executor - private val taskExecutor: TaskExecutor + private val taskExecutor: TaskExecutor, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : KeysBackupService { private val uiHandler = Handler(Looper.getMainLooper()) @@ -130,55 +139,53 @@ internal class KeysBackup( override fun prepareKeysBackupVersion(password: String?, progressListener: ProgressListener?, callback: MatrixCallback) { - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - try { - val olmPkDecryption = OlmPkDecryption() - val megolmBackupAuthData = MegolmBackupAuthData() + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + val olmPkDecryption = OlmPkDecryption() + val megolmBackupAuthData = MegolmBackupAuthData() - if (password != null) { - // Generate a private key from the password - val backgroundProgressListener = if (progressListener == null) { - null - } else { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - try { - progressListener.onProgress(progress, total) - } catch (e: Exception) { - Timber.e(e, "prepareKeysBackupVersion: onProgress failure") + if (password != null) { + // Generate a private key from the password + val backgroundProgressListener = if (progressListener == null) { + null + } else { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + uiHandler.post { + try { + progressListener.onProgress(progress, total) + } catch (e: Exception) { + Timber.e(e, "prepareKeysBackupVersion: onProgress failure") + } } } } } + + val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) + megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey) + megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt + megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations + } else { + val publicKey = olmPkDecryption.generateKey() + + megolmBackupAuthData.publicKey = publicKey } - val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener) - megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey) - megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt - megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations - } else { - val publicKey = olmPkDecryption.generateKey() + val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) - megolmBackupAuthData.publicKey = publicKey + megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) + + + val megolmBackupCreationInfo = MegolmBackupCreationInfo() + megolmBackupCreationInfo.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP + megolmBackupCreationInfo.authData = megolmBackupAuthData + megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) + + megolmBackupCreationInfo } - - val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) - - megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) - - - val megolmBackupCreationInfo = MegolmBackupCreationInfo() - megolmBackupCreationInfo.algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP - megolmBackupCreationInfo.authData = megolmBackupAuthData - megolmBackupCreationInfo.recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey()) - - uiHandler.post { callback.onSuccess(megolmBackupCreationInfo) } - } catch (e: OlmException) { - Timber.e(e, "OlmException") - - uiHandler.post { callback.onFailure(e) } - } + }.foldToCallback(callback) } } @@ -221,37 +228,39 @@ internal class KeysBackup( } override fun deleteBackup(version: String, callback: MatrixCallback?) { - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - // If we're currently backing up to this backup... stop. - // (We start using it automatically in createKeysBackupVersion so this is symmetrical). - if (keysBackupVersion != null && version == keysBackupVersion!!.version) { - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Unknown - } + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + // If we're currently backing up to this backup... stop. + // (We start using it automatically in createKeysBackupVersion so this is symmetrical). + if (keysBackupVersion != null && version == keysBackupVersion!!.version) { + resetKeysBackupData() + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.Unknown + } - deleteBackupTask.configureWith(DeleteBackupTask.Params(version)) - .dispatchTo(object : MatrixCallback { - private fun eventuallyRestartBackup() { - // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { - checkAndStartKeysBackup() + deleteBackupTask.configureWith(DeleteBackupTask.Params(version)) + .dispatchTo(object : MatrixCallback { + private fun eventuallyRestartBackup() { + // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver + if (state == KeysBackupState.Unknown) { + checkAndStartKeysBackup() + } } - } - override fun onSuccess(data: Unit) { - eventuallyRestartBackup() + override fun onSuccess(data: Unit) { + eventuallyRestartBackup() - uiHandler.post { callback?.onSuccess(Unit) } - } + uiHandler.post { callback?.onSuccess(Unit) } + } - override fun onFailure(failure: Throwable) { - eventuallyRestartBackup() + override fun onFailure(failure: Throwable) { + eventuallyRestartBackup() - uiHandler.post { callback?.onFailure(failure) } - } - }) - .executeBy(taskExecutor) + uiHandler.post { callback?.onFailure(failure) } + } + }) + .executeBy(taskExecutor) + } } } @@ -426,85 +435,80 @@ internal class KeysBackup( callback: MatrixCallback) { Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val myUserId = credentials.userId + // Get auth data to update it + val authData = getMegolmBackupAuthData(keysBackupVersion) - // Get auth data to update it - val authData = getMegolmBackupAuthData(keysBackupVersion) + if (authData == null) { + Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") - if (authData == null) { - Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") + callback.onFailure(IllegalArgumentException("Missing element")) + } else { + GlobalScope.launch(coroutineDispatchers.main) { + val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { + val myUserId = credentials.userId - uiHandler.post { - callback.onFailure(IllegalArgumentException("Missing element")) + // Get current signatures, or create an empty set + val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap()) + + if (trust) { + // Add current device signature + val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + + val deviceSignatures = objectSigner.signObject(canonicalJson) + + deviceSignatures[myUserId]?.forEach { entry -> + myUserSignatures[entry.key] = entry.value + } + } else { + // Remove current device signature + myUserSignatures.remove("ed25519:${credentials.deviceId}") + } + + // Create an updated version of KeysVersionResult + val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!) + + updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm + + val newMegolmBackupAuthData = authData.copy() + + val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() + newSignatures[myUserId] = myUserSignatures + + newMegolmBackupAuthData.signatures = newSignatures + + val moshi = MoshiProvider.providesMoshi() + val adapter = moshi.adapter(Map::class.java) + + updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map? + + updateKeysBackupVersionBody } - return@post - } + // And send it to the homeserver + updateKeysBackupVersionTask + .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: Unit) { + // Relaunch the state machine on this updated backup version + val newKeysBackupVersion = KeysVersionResult() - // Get current signatures, or create an empty set - val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap()) + newKeysBackupVersion.version = keysBackupVersion.version + newKeysBackupVersion.algorithm = keysBackupVersion.algorithm + newKeysBackupVersion.count = keysBackupVersion.count + newKeysBackupVersion.hash = keysBackupVersion.hash + newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData - if (trust) { - // Add current device signature - val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) + checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - val deviceSignatures = objectSigner.signObject(canonicalJson) - - deviceSignatures[myUserId]?.forEach { entry -> - myUserSignatures[entry.key] = entry.value - } - } else { - // Remove current device signature - myUserSignatures.remove("ed25519:${credentials.deviceId}") - } - - // Create an updated version of KeysVersionResult - val updateKeysBackupVersionBody = UpdateKeysBackupVersionBody(keysBackupVersion.version!!) - - updateKeysBackupVersionBody.algorithm = keysBackupVersion.algorithm - - val newMegolmBackupAuthData = authData.copy() - - val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() - newSignatures[myUserId] = myUserSignatures - - newMegolmBackupAuthData.signatures = newSignatures - - val moshi = MoshiProvider.providesMoshi() - val adapter = moshi.adapter(Map::class.java) - - - updateKeysBackupVersionBody.authData = adapter.fromJson(newMegolmBackupAuthData.toJsonString()) as Map? - - // And send it to the homeserver - updateKeysBackupVersionTask - .configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody)) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: Unit) { - // Relaunch the state machine on this updated backup version - val newKeysBackupVersion = KeysVersionResult() - - newKeysBackupVersion.version = keysBackupVersion.version - newKeysBackupVersion.algorithm = keysBackupVersion.algorithm - newKeysBackupVersion.count = keysBackupVersion.count - newKeysBackupVersion.hash = keysBackupVersion.hash - newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData - - checkAndStartWithKeysBackupVersion(newKeysBackupVersion) - - uiHandler.post { callback.onSuccess(data) } - } - override fun onFailure(failure: Throwable) { - uiHandler.post { + override fun onFailure(failure: Throwable) { callback.onFailure(failure) } - } - }) - .executeBy(taskExecutor) + }) + .executeBy(taskExecutor) + } } } @@ -513,17 +517,18 @@ internal class KeysBackup( callback: MatrixCallback) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { - Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") - - uiHandler.post { - callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) - } - return@post + GlobalScope.launch(coroutineDispatchers.main) { + val isValid = withContext(coroutineDispatchers.crypto) { + isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion) } - trustKeysBackupVersion(keysBackupVersion, true, callback) + if (!isValid) { + Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") + + callback.onFailure(IllegalArgumentException("Invalid recovery key or password")) + } else { + trustKeysBackupVersion(keysBackupVersion, true, callback) + } } } @@ -532,21 +537,19 @@ internal class KeysBackup( callback: MatrixCallback) { Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null) + GlobalScope.launch(coroutineDispatchers.main) { + val recoveryKey = withContext(coroutineDispatchers.crypto) { + recoveryKeyFromPassword(password, keysBackupVersion, null) + } if (recoveryKey == null) { Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data") - uiHandler.post { - callback.onFailure(IllegalArgumentException("Missing element")) - } - - return@post + callback.onFailure(IllegalArgumentException("Missing element")) + } else { + // Check trust using the recovery key + trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) } - - // Check trust using the recovery key - trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback) } } @@ -591,12 +594,10 @@ internal class KeysBackup( } override fun getBackupProgress(progressListener: ProgressListener) { - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) - val total = cryptoStore.inboundGroupSessionsCount(false) + val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) + val total = cryptoStore.inboundGroupSessionsCount(false) - uiHandler.post { progressListener.onProgress(backedUpKeys, total) } - } + progressListener.onProgress(backedUpKeys, total) } override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, @@ -607,88 +608,95 @@ internal class KeysBackup( callback: MatrixCallback) { Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post(Runnable { - // Check if the recovery is valid before going any further - if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") - uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) } - return@Runnable - } - - // Get a PK decryption instance - val decryption = pkDecryptionFromRecoveryKey(recoveryKey) - if (decryption == null) { - // This should not happen anymore - Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") - uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) } - return@Runnable - } - - if (stepProgressListener != null) { - uiHandler.post { stepProgressListener.onStepProgress(StepProgressListener.Step.DownloadingKey) } - } - - // Get backed up keys from the homeserver - getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback { - override fun onSuccess(data: KeysBackupData) { - val sessionsData = ArrayList() - // Restore that data - var sessionsFromHsCount = 0 - for (roomIdLoop in data.roomIdToRoomKeysBackupData.keys) { - for (sessionIdLoop in data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData.keys) { - sessionsFromHsCount++ - - val keyBackupData = data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData[sessionIdLoop]!! - - val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) - - sessionData?.let { - sessionsData.add(it) - } - } - } - Timber.v("restoreKeysWithRecoveryKey: Decrypted " + sessionsData.size + " keys out of " - + sessionsFromHsCount + " 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) + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + // Check if the recovery is valid before going any further + if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { + Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") + throw InvalidParameterException("Invalid recovery key") } - // Import them into the crypto store - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - // Note: no need to post to UI thread, importMegolmSessionsData() will do it - stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) - } - } - } else { - null + // Get a PK decryption instance + val decryption = pkDecryptionFromRecoveryKey(recoveryKey) + if (decryption == null) { + // This should not happen anymore + Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error") + throw InvalidParameterException("Invalid recovery key") } - megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener, object : MatrixCallback { - override fun onSuccess(data: ImportRoomKeysResult) { - // Do not back up the key if it comes from a backup recovery - if (backUp) { - maybeBackupKeys() - } - - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - }) + decryption!! } + }.fold( + { + callback.onFailure(it) + }, + { decryption -> + stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey) - override fun onFailure(failure: Throwable) { - uiHandler.post { callback.onFailure(failure) } - } - }) - }) + // Get backed up keys from the homeserver + getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback { + override fun onSuccess(data: KeysBackupData) { + GlobalScope.launch(coroutineDispatchers.main) { + val importRoomKeysResult = withContext(coroutineDispatchers.crypto) { + val sessionsData = ArrayList() + // Restore that data + var sessionsFromHsCount = 0 + for (roomIdLoop in data.roomIdToRoomKeysBackupData.keys) { + for (sessionIdLoop in data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData.keys) { + sessionsFromHsCount++ + + val keyBackupData = data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData[sessionIdLoop]!! + + val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption) + + sessionData?.let { + sessionsData.add(it) + } + } + } + Timber.v("restoreKeysWithRecoveryKey: Decrypted " + sessionsData.size + " keys out of " + + sessionsFromHsCount + " 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) + } + + // Import them into the crypto store + val progressListener = if (stepProgressListener != null) { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + // Note: no need to post to UI thread, importMegolmSessionsData() will do it + stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) + } + } + } else { + null + } + + val result = megolmSessionDataImporter.handle(sessionsData, !backUp, uiHandler, progressListener) + + // Do not back up the key if it comes from a backup recovery + if (backUp) { + maybeBackupKeys() + } + + result + } + + callback.onSuccess(importRoomKeysResult) + } + } + + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + }) + } + ) + } } override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, @@ -699,31 +707,36 @@ internal class KeysBackup( callback: MatrixCallback) { Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - val progressListener = if (stepProgressListener != null) { - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - uiHandler.post { - stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + val progressListener = if (stepProgressListener != null) { + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + uiHandler.post { + stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) + } } } - } - } else { - null - } - - val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, progressListener) - - if (recoveryKey == null) { - uiHandler.post { - Timber.v("backupKeys: Invalid configuration") - callback.onFailure(IllegalStateException("Invalid configuration")) + } else { + null } - return@post - } - - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback) + Try { + recoveryKeyFromPassword(password, keysBackupVersion, progressListener) + } + }.fold( + { + callback.onFailure(it) + }, + { recoveryKey -> + if (recoveryKey == null) { + Timber.v("backupKeys: Invalid configuration") + callback.onFailure(IllegalStateException("Invalid configuration")) + } else { + restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback) + } + } + ) } } @@ -989,9 +1002,9 @@ internal class KeysBackup( } } - /* ========================================================================================== - * Private - * ========================================================================================== */ +/* ========================================================================================== + * Private + * ========================================================================================== */ /** * Extract MegolmBackupAuthData data from a backup version. @@ -1190,94 +1203,96 @@ internal class KeysBackup( keysBackupStateManager.state = KeysBackupState.BackingUp - CryptoAsyncHelper.getEncryptBackgroundHandler().post { - Timber.v("backupKeys: 2 - Encrypting keys") + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Timber.v("backupKeys: 2 - Encrypting keys") - // Gather data to send to the homeserver - // roomId -> sessionId -> MXKeyBackupData - val keysBackupData = KeysBackupData() - keysBackupData.roomIdToRoomKeysBackupData = HashMap() + // Gather data to send to the homeserver + // roomId -> sessionId -> MXKeyBackupData + val keysBackupData = KeysBackupData() + keysBackupData.roomIdToRoomKeysBackupData = HashMap() - for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { - val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) - if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { - val roomKeysBackupData = RoomKeysBackupData() - roomKeysBackupData.sessionIdToKeyBackupData = HashMap() - keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData + for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { + val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) + if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { + val roomKeysBackupData = RoomKeysBackupData() + roomKeysBackupData.sessionIdToKeyBackupData = HashMap() + keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData + } + + try { + keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData + } catch (e: OlmException) { + Timber.e(e, "OlmException") + } } - try { - keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData - } catch (e: OlmException) { - Timber.e(e, "OlmException") - } - } + Timber.v("backupKeys: 4 - Sending request") - Timber.v("backupKeys: 4 - Sending request") - - // Make the request - storeSessionDataTask - .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) - .dispatchTo(object : MatrixCallback { - override fun onSuccess(data: BackupKeysResult) { - uiHandler.post { - Timber.v("backupKeys: 5a - Request complete") - - // Mark keys as backed up - cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - - if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { - Timber.v("backupKeys: All keys have been backed up") - onServerDataRetrieved(data.count, data.hash) - - // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - } else { - Timber.v("backupKeys: Continue to back up keys") - keysBackupStateManager.state = KeysBackupState.WillBackUp - - backupKeys() - } - } - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError) { + // Make the request + storeSessionDataTask + .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: BackupKeysResult) { uiHandler.post { - Timber.e(failure, "backupKeys: backupKeys failed.") + Timber.v("backupKeys: 5a - Request complete") - when (failure.error.code) { - MatrixError.NOT_FOUND, - MatrixError.WRONG_ROOM_KEYS_VERSION -> { - // Backup has been deleted on the server, or we are not using the last backup version - keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() - resetKeysBackupData() - keysBackupVersion = null + // Mark keys as backed up + cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers) - // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver - checkAndStartKeysBackup() - } - else -> // Come back to the ready state so that we will retry on the next received key - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) { + Timber.v("backupKeys: All keys have been backed up") + onServerDataRetrieved(data.count, data.hash) + + // Note: Changing state will trigger the call to backupAllGroupSessionsCallback.onSuccess() + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + } else { + Timber.v("backupKeys: Continue to back up keys") + keysBackupStateManager.state = KeysBackupState.WillBackUp + + backupKeys() } } - } else { - uiHandler.post { - backupAllGroupSessionsCallback?.onFailure(failure) - resetBackupAllGroupSessionsListeners() + } - Timber.e("backupKeys: backupKeys failed.") + override fun onFailure(failure: Throwable) { + if (failure is Failure.ServerError) { + uiHandler.post { + Timber.e(failure, "backupKeys: backupKeys failed.") - // Retry a bit later - keysBackupStateManager.state = KeysBackupState.ReadyToBackUp - maybeBackupKeys() + when (failure.error.code) { + MatrixError.NOT_FOUND, + MatrixError.WRONG_ROOM_KEYS_VERSION -> { + // Backup has been deleted on the server, or we are not using the last backup version + keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion + backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + resetKeysBackupData() + keysBackupVersion = null + + // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver + checkAndStartKeysBackup() + } + else -> // Come back to the ready state so that we will retry on the next received key + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + } + } + } else { + uiHandler.post { + backupAllGroupSessionsCallback?.onFailure(failure) + resetBackupAllGroupSessionsListeners() + + Timber.e("backupKeys: backupKeys failed.") + + // Retry a bit later + keysBackupStateManager.state = KeysBackupState.ReadyToBackUp + maybeBackupKeys() + } } } - } - }) - .executeBy(taskExecutor) + }) + .executeBy(taskExecutor) + } } } @@ -1376,9 +1391,9 @@ internal class KeysBackup( private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100 } - /* ========================================================================================== - * DEBUG INFO - * ========================================================================================== */ +/* ========================================================================================== + * DEBUG INFO + * ========================================================================================== */ override fun toString() = "KeysBackup for ${credentials.userId}" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 81556052a7..a1595c10e8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -27,17 +27,12 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.task.TaskExecutor @@ -138,8 +133,8 @@ internal class DefaultSasVerificationService(private val credentials: Credential override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) { setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, - deviceID, - userId) + deviceID, + userId) listeners.forEach { try { @@ -206,7 +201,7 @@ internal class DefaultSasVerificationService(private val credentials: Credential } else { Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") cancelTransaction(tid, otherUserId, startReq.fromDevice - ?: event.getSenderKey()!!, CancelCode.UnknownMethod) + ?: event.getSenderKey()!!, CancelCode.UnknownMethod) } } }, @@ -372,9 +367,8 @@ internal class DefaultSasVerificationService(private val credentials: Credential userId, deviceID) addTransaction(tx) - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - tx.start() - } + + tx.start() return txID } else { throw IllegalArgumentException("Unknown verification method") @@ -399,9 +393,9 @@ internal class DefaultSasVerificationService(private val credentials: Credential override fun transactionUpdated(tx: VerificationTransaction) { dispatchTxUpdated(tx) if (tx is SASVerificationTransaction - && (tx.state == SasVerificationTxState.Cancelled - || tx.state == SasVerificationTxState.OnCancelled - || tx.state == SasVerificationTxState.Verified) + && (tx.state == SasVerificationTxState.Cancelled + || tx.state == SasVerificationTxState.OnCancelled + || tx.state == SasVerificationTxState.Verified) ) { //remove this.removeTransaction(tx.otherUserId, tx.transactionId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt index 1d40337b9f..2a4f0a1dde 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt @@ -22,13 +22,12 @@ import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTr import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.task.TaskExecutor @@ -61,21 +60,21 @@ internal class IncomingSASVerificationTransaction( override val uxState: IncomingSasVerificationTransaction.UxState get() { return when (state) { - SasVerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT + SasVerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT SasVerificationTxState.SendingAccept, SasVerificationTxState.Accepted, SasVerificationTxState.OnKeyReceived, SasVerificationTxState.SendingKey, - SasVerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT + SasVerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT SasVerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS SasVerificationTxState.ShortCodeAccepted, SasVerificationTxState.SendingMac, SasVerificationTxState.MacSent, - SasVerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION - SasVerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED - SasVerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME - SasVerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER - else -> IncomingSasVerificationTransaction.UxState.UNKNOWN + SasVerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION + SasVerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED + SasVerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME + SasVerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER + else -> IncomingSasVerificationTransaction.UxState.UNKNOWN } } @@ -125,9 +124,7 @@ internal class IncomingSASVerificationTransaction( //TODO force download keys!! //would be probably better to download the keys //for now I cancel - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - cancel(CancelCode.User) - } + cancel(CancelCode.User) } else { // val otherKey = info.identityKey() //need to jump back to correct thread @@ -139,9 +136,7 @@ internal class IncomingSASVerificationTransaction( shortAuthenticationStrings = agreedShortCode, commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT) ) - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - doAccept(accept) - } + doAccept(accept) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt index aa18133f05..2210c25abd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt @@ -86,7 +86,6 @@ internal class OutgoingSASVerificationRequest( } fun start() { - if (state != SasVerificationTxState.None) { Timber.e("## start verification from invalid state") //should I cancel?? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 858c48b4e6..1f6d759b56 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -23,7 +23,6 @@ import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXKey @@ -278,21 +277,17 @@ internal abstract class SASVerificationTransaction( .dispatchTo(object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - if (onDone != null) { - onDone() - } else { - state = nextState - } + if (onDone != null) { + onDone() + } else { + state = nextState } } override fun onFailure(failure: Throwable) { Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state") - CryptoAsyncHelper.getDecryptBackgroundHandler().post { - cancel(onErrorReason) - } + cancel(onErrorReason) } }) .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt index 8f9447c6c0..84f6786b05 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt @@ -17,7 +17,8 @@ package im.vector.matrix.android.internal.di import android.content.Context -import im.vector.matrix.android.internal.crypto.CryptoAsyncHelper +import android.os.Handler +import android.os.HandlerThread import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -36,11 +37,14 @@ class MatrixModule(private val context: Context) { } single { - val cryptoHandler = CryptoAsyncHelper.getDecryptBackgroundHandler() + val THREAD_CRYPTO_NAME = "Crypto_Thread" + val handlerThread = HandlerThread(THREAD_CRYPTO_NAME) + handlerThread.start() + MatrixCoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.IO, main = Dispatchers.Main, - crypto = cryptoHandler.asCoroutineDispatcher("crypto") + crypto = Handler(handlerThread.looper).asCoroutineDispatcher("crypto") ) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/crypto/keys/KeysImporter.kt b/vector/src/main/java/im/vector/riotredesign/features/crypto/keys/KeysImporter.kt new file mode 100644 index 0000000000..f783962d21 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/crypto/keys/KeysImporter.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.crypto.keys + +import android.content.Context +import android.net.Uri +import arrow.core.Try +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.riotredesign.core.intent.getMimeTypeFromUri +import im.vector.riotredesign.core.resources.openResource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber + +class KeysImporter(private val session: Session) { + + /** + * Export keys and return the file path with the callback + */ + fun import(context: Context, + uri: Uri, + mimetype: String?, + password: String, + callback: MatrixCallback) { + GlobalScope.launch(Dispatchers.Main) { + withContext(Dispatchers.IO) { + Try { + val resource = openResource(context, uri, mimetype ?: getMimeTypeFromUri(context, uri)) + + if (resource?.mContentStream == null) { + throw Exception("Error") + } + + val data: ByteArray + try { + data = ByteArray(resource.mContentStream!!.available()) + resource.mContentStream!!.read(data) + resource.mContentStream!!.close() + + data + } catch (e: Exception) { + try { + resource.mContentStream!!.close() + } catch (e2: Exception) { + Timber.e(e2, "## importKeys()") + } + + throw e + } + } + } + .fold( + { + callback.onFailure(it) + }, + { byteArray -> + session.importRoomKeys(byteArray, + password, + null, + callback) + } + ) + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt index 23a63869b7..472b168060 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt @@ -59,18 +59,17 @@ import im.vector.riotredesign.core.extensions.withArgs import im.vector.riotredesign.core.intent.ExternalIntentData import im.vector.riotredesign.core.intent.analyseIntent import im.vector.riotredesign.core.intent.getFilenameFromUri -import im.vector.riotredesign.core.intent.getMimeTypeFromUri import im.vector.riotredesign.core.platform.SimpleTextWatcher import im.vector.riotredesign.core.platform.VectorPreferenceFragment import im.vector.riotredesign.core.preference.BingRule import im.vector.riotredesign.core.preference.ProgressBarPreference import im.vector.riotredesign.core.preference.UserAvatarPreference import im.vector.riotredesign.core.preference.VectorPreference -import im.vector.riotredesign.core.resources.openResource import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.features.MainActivity import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.crypto.keys.KeysExporter +import im.vector.riotredesign.features.crypto.keys.KeysImporter import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotredesign.features.themes.ThemeUtils import org.koin.android.ext.android.inject @@ -2702,58 +2701,33 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref importButton.setOnClickListener(View.OnClickListener { val password = passPhraseEditText.text.toString() - val resource = openResource(appContext, uri, mimetype ?: getMimeTypeFromUri(appContext, uri)) - if (resource?.mContentStream == null) { - appContext.toast("Error") + KeysImporter(mSession) + .import(requireContext(), + uri, + mimetype, + password, + object : MatrixCallback { + override fun onSuccess(data: ImportRoomKeysResult) { + if (!isAdded) { + return + } - return@OnClickListener - } + hideLoadingView() - val data: ByteArray -// TODO BG - try { - data = ByteArray(resource.mContentStream!!.available()) - resource.mContentStream!!.read(data) - resource.mContentStream!!.close() - } catch (e: Exception) { - try { - resource.mContentStream!!.close() - } catch (e2: Exception) { - Timber.e(e2, "## importKeys()") - } + AlertDialog.Builder(thisActivity) + .setMessage(getString(R.string.encryption_import_room_keys_success, + data.successfullyNumberOfImportedKeys, + data.totalNumberOfKeys)) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() + } - appContext.toast(e.localizedMessage) - - return@OnClickListener - } - - displayLoadingView() - - mSession.importRoomKeys(data, - password, - null, - object : MatrixCallback { - override fun onSuccess(data: ImportRoomKeysResult) { - if (!isAdded) { - return - } - - hideLoadingView() - - AlertDialog.Builder(thisActivity) - .setMessage(getString(R.string.encryption_import_room_keys_success, - data.successfullyNumberOfImportedKeys, - data.totalNumberOfKeys)) - .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } - .show() - } - - override fun onFailure(failure: Throwable) { - appContext.toast(failure.localizedMessage) - hideLoadingView() - } - }) + override fun onFailure(failure: Throwable) { + appContext.toast(failure.localizedMessage) + hideLoadingView() + } + }) importDialog.dismiss() })