Remove CryptoAsyncHelper and use only coroutine

This commit is contained in:
Benoit Marty 2019-06-14 13:43:37 +02:00
parent 907a1d1a4b
commit 659ba34fb3
12 changed files with 530 additions and 541 deletions

View File

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


}

View File

@ -19,6 +19,8 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto


import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
@ -138,6 +140,8 @@ internal class CryptoManager(
private val taskExecutor: TaskExecutor private val taskExecutor: TaskExecutor
) : CryptoService { ) : CryptoService {


private val uiHandler = Handler(Looper.getMainLooper())

// MXEncrypting instance for each room. // MXEncrypting instance for each room.
private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap() private val roomEncryptors: MutableMap<String, IMXEncrypting> = HashMap()
private val isStarting = AtomicBoolean(false) private val isStarting = AtomicBoolean(false)
@ -825,41 +829,33 @@ internal class CryptoManager(
password: String, password: String,
progressListener: ProgressListener?, progressListener: ProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>) { callback: MatrixCallback<ImportRoomKeysResult>) {
// TODO Use coroutines GlobalScope.launch(coroutineDispatchers.main) {
Timber.v("## importRoomKeys starts") withContext(coroutineDispatchers.crypto) {
Try {
Timber.v("## importRoomKeys starts")


val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
val roomKeys: String val roomKeys: String = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)


try { val importedSessions: List<MegolmSessionData>
roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
} catch (e: Exception) { val t1 = System.currentTimeMillis()
callback.onFailure(e)
return Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms")

val list = MoshiProvider.providesMoshi()
.adapter(List::class.java)
.fromJson(roomKeys)
importedSessions = list as List<MegolmSessionData>

val t2 = System.currentTimeMillis()

Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms")

megolmSessionDataImporter.handle(importedSessions, true, uiHandler, progressListener)
}
}.foldToCallback(callback)
} }

val importedSessions: List<MegolmSessionData>

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<MegolmSessionData>
} 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) .executeBy(taskExecutor)
} }


/* ========================================================================================== /* ==========================================================================================
* DEBUG INFO * DEBUG INFO
* ========================================================================================== */ * ========================================================================================== */


override fun toString(): String { override fun toString(): String {
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")" return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"

View File

@ -277,6 +277,7 @@ internal class CryptoModule {
// Task // Task
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
// Task executor // Task executor
get(),
get()) get())
} }



View File

@ -16,9 +16,9 @@


package im.vector.matrix.android.internal.crypto.actions 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.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.MXOlmDevice
import im.vector.matrix.android.internal.crypto.MegolmSessionData import im.vector.matrix.android.internal.crypto.MegolmSessionData
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager 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. * Import a list of megolm session keys.
* Must be call on the crypto coroutine thread
* *
* @param megolmSessionsData megolm sessions. * @param megolmSessionsData megolm sessions.
* @param backUpKeys true to back up them to the homeserver. * @param backUpKeys true to back up them to the homeserver.
* @param progressListener the progress listener * @param progressListener the progress listener
* @param callback * @return import room keys result
*/ */
@WorkerThread
fun handle(megolmSessionsData: List<MegolmSessionData>, fun handle(megolmSessionsData: List<MegolmSessionData>,
fromBackup: Boolean, fromBackup: Boolean,
progressListener: ProgressListener?, uiHandler: Handler,
callback: MatrixCallback<ImportRoomKeysResult>) { progressListener: ProgressListener?): ImportRoomKeysResult {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()


val totalNumbersOfKeys = megolmSessionsData.size val totalNumbersOfKeys = megolmSessionsData.size
var cpt = 0
var lastProgress = 0 var lastProgress = 0
var totalNumbersOfImportedKeys = 0 var totalNumbersOfImportedKeys = 0


if (progressListener != null) { if (progressListener != null) {
CryptoAsyncHelper.getUiHandler().post { uiHandler.post {
progressListener.onProgress(0, 100) progressListener.onProgress(0, 100)
} }
} }
val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData) val olmInboundGroupSessionWrappers = olmDevice.importInboundGroupSessions(megolmSessionsData)


for (megolmSessionData in megolmSessionsData) { megolmSessionsData.forEachIndexed { cpt, megolmSessionData ->
cpt++


val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm) val decrypting = roomDecryptorProvider.getOrCreateRoomDecryptor(megolmSessionData.roomId, megolmSessionData.algorithm)


if (null != decrypting) { if (null != decrypting) {
@ -90,7 +88,7 @@ internal class MegolmSessionDataImporter(private val olmDevice: MXOlmDevice,
} }


if (progressListener != null) { if (progressListener != null) {
CryptoAsyncHelper.getUiHandler().post { uiHandler.post {
val progress = 100 * cpt / totalNumbersOfKeys val progress = 100 * cpt / totalNumbersOfKeys


if (lastProgress != progress) { 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)") Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")


val finalTotalNumbersOfImportedKeys = totalNumbersOfImportedKeys return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys)

CryptoAsyncHelper.getUiHandler().post {
callback.onSuccess(ImportRoomKeysResult(totalNumbersOfKeys, finalTotalNumbersOfImportedKeys))
}
} }
} }

View File

@ -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.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState 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.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.actions.MegolmSessionDataImporter
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature 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.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity 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.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.Task
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith 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.OlmException
import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkDecryption
import org.matrix.olm.OlmPkEncryption import org.matrix.olm.OlmPkEncryption
@ -87,7 +95,8 @@ internal class KeysBackup(
private val storeSessionDataTask: StoreSessionsDataTask, private val storeSessionDataTask: StoreSessionsDataTask,
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask, private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
// Task executor // Task executor
private val taskExecutor: TaskExecutor private val taskExecutor: TaskExecutor,
private val coroutineDispatchers: MatrixCoroutineDispatchers
) : KeysBackupService { ) : KeysBackupService {


private val uiHandler = Handler(Looper.getMainLooper()) private val uiHandler = Handler(Looper.getMainLooper())
@ -130,55 +139,53 @@ internal class KeysBackup(
override fun prepareKeysBackupVersion(password: String?, override fun prepareKeysBackupVersion(password: String?,
progressListener: ProgressListener?, progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>) { callback: MatrixCallback<MegolmBackupCreationInfo>) {
CryptoAsyncHelper.getDecryptBackgroundHandler().post { GlobalScope.launch(coroutineDispatchers.main) {
try { withContext(coroutineDispatchers.crypto) {
val olmPkDecryption = OlmPkDecryption() Try {
val megolmBackupAuthData = MegolmBackupAuthData() val olmPkDecryption = OlmPkDecryption()
val megolmBackupAuthData = MegolmBackupAuthData()


if (password != null) { if (password != null) {
// Generate a private key from the password // Generate a private key from the password
val backgroundProgressListener = if (progressListener == null) { val backgroundProgressListener = if (progressListener == null) {
null null
} else { } else {
object : ProgressListener { object : ProgressListener {
override fun onProgress(progress: Int, total: Int) { override fun onProgress(progress: Int, total: Int) {
uiHandler.post { uiHandler.post {
try { try {
progressListener.onProgress(progress, total) progressListener.onProgress(progress, total)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "prepareKeysBackupVersion: onProgress failure") 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) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
megolmBackupAuthData.publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey)
megolmBackupAuthData.privateKeySalt = generatePrivateKeyResult.salt
megolmBackupAuthData.privateKeyIterations = generatePrivateKeyResult.iterations
} else {
val publicKey = olmPkDecryption.generateKey()


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
} }

}.foldToCallback(callback)
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) }
}
} }
} }


@ -221,37 +228,39 @@ internal class KeysBackup(
} }


override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) { override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
CryptoAsyncHelper.getDecryptBackgroundHandler().post { GlobalScope.launch(coroutineDispatchers.main) {
// If we're currently backing up to this backup... stop. withContext(coroutineDispatchers.crypto) {
// (We start using it automatically in createKeysBackupVersion so this is symmetrical). // If we're currently backing up to this backup... stop.
if (keysBackupVersion != null && version == keysBackupVersion!!.version) { // (We start using it automatically in createKeysBackupVersion so this is symmetrical).
resetKeysBackupData() if (keysBackupVersion != null && version == keysBackupVersion!!.version) {
keysBackupVersion = null resetKeysBackupData()
keysBackupStateManager.state = KeysBackupState.Unknown keysBackupVersion = null
} keysBackupStateManager.state = KeysBackupState.Unknown
}


deleteBackupTask.configureWith(DeleteBackupTask.Params(version)) deleteBackupTask.configureWith(DeleteBackupTask.Params(version))
.dispatchTo(object : MatrixCallback<Unit> { .dispatchTo(object : MatrixCallback<Unit> {
private fun eventuallyRestartBackup() { private fun eventuallyRestartBackup() {
// Do not stay in KeysBackupState.Unknown but check what is available on the homeserver // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
if (state == KeysBackupState.Unknown) { if (state == KeysBackupState.Unknown) {
checkAndStartKeysBackup() checkAndStartKeysBackup()
}
} }
}


override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
eventuallyRestartBackup() eventuallyRestartBackup()


uiHandler.post { callback?.onSuccess(Unit) } uiHandler.post { callback?.onSuccess(Unit) }
} }


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
eventuallyRestartBackup() eventuallyRestartBackup()


uiHandler.post { callback?.onFailure(failure) } uiHandler.post { callback?.onFailure(failure) }
} }
}) })
.executeBy(taskExecutor) .executeBy(taskExecutor)
}
} }
} }


@ -426,85 +435,80 @@ internal class KeysBackup(
callback: MatrixCallback<Unit>) { callback: MatrixCallback<Unit>) {
Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}")


CryptoAsyncHelper.getDecryptBackgroundHandler().post { // Get auth data to update it
val myUserId = credentials.userId val authData = getMegolmBackupAuthData(keysBackupVersion)


// Get auth data to update it if (authData == null) {
val authData = getMegolmBackupAuthData(keysBackupVersion) Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")


if (authData == null) { callback.onFailure(IllegalArgumentException("Missing element"))
Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data") } else {
GlobalScope.launch(coroutineDispatchers.main) {
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
val myUserId = credentials.userId


uiHandler.post { // Get current signatures, or create an empty set
callback.onFailure(IllegalArgumentException("Missing element")) 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<String, Any>?

updateKeysBackupVersionBody
} }


return@post // And send it to the homeserver
} updateKeysBackupVersionTask
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
.dispatchTo(object : MatrixCallback<Unit> {
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 newKeysBackupVersion.version = keysBackupVersion.version
val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap() ?: HashMap()) newKeysBackupVersion.algorithm = keysBackupVersion.algorithm
newKeysBackupVersion.count = keysBackupVersion.count
newKeysBackupVersion.hash = keysBackupVersion.hash
newKeysBackupVersion.authData = updateKeysBackupVersionBody.authData


if (trust) { checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
// 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<String, Any>?

// And send it to the homeserver
updateKeysBackupVersionTask
.configureWith(UpdateKeysBackupVersionTask.Params(keysBackupVersion.version!!, updateKeysBackupVersionBody))
.dispatchTo(object : MatrixCallback<Unit> {
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) callback.onSuccess(data)
} }
}


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
uiHandler.post {
callback.onFailure(failure) callback.onFailure(failure)
} }
} })
}) .executeBy(taskExecutor)
.executeBy(taskExecutor) }
} }
} }


@ -513,17 +517,18 @@ internal class KeysBackup(
callback: MatrixCallback<Unit>) { callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")


CryptoAsyncHelper.getDecryptBackgroundHandler().post { GlobalScope.launch(coroutineDispatchers.main) {
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { val isValid = withContext(coroutineDispatchers.crypto) {
Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.") isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)

uiHandler.post {
callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
}
return@post
} }


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<Unit>) { callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")


CryptoAsyncHelper.getDecryptBackgroundHandler().post { GlobalScope.launch(coroutineDispatchers.main) {
val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null) val recoveryKey = withContext(coroutineDispatchers.crypto) {
recoveryKeyFromPassword(password, keysBackupVersion, null)
}


if (recoveryKey == null) { if (recoveryKey == null) {
Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data") Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")


uiHandler.post { callback.onFailure(IllegalArgumentException("Missing element"))
callback.onFailure(IllegalArgumentException("Missing element")) } else {
} // Check trust using the recovery key

trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
return@post
} }

// Check trust using the recovery key
trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
} }
} }


@ -591,12 +594,10 @@ internal class KeysBackup(
} }


override fun getBackupProgress(progressListener: ProgressListener) { override fun getBackupProgress(progressListener: ProgressListener) {
CryptoAsyncHelper.getDecryptBackgroundHandler().post { val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true)
val backedUpKeys = cryptoStore.inboundGroupSessionsCount(true) val total = cryptoStore.inboundGroupSessionsCount(false)
val total = cryptoStore.inboundGroupSessionsCount(false)


uiHandler.post { progressListener.onProgress(backedUpKeys, total) } progressListener.onProgress(backedUpKeys, total)
}
} }


override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
@ -607,88 +608,95 @@ internal class KeysBackup(
callback: MatrixCallback<ImportRoomKeysResult>) { callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")


CryptoAsyncHelper.getDecryptBackgroundHandler().post(Runnable { GlobalScope.launch(coroutineDispatchers.main) {
// Check if the recovery is valid before going any further withContext(coroutineDispatchers.crypto) {
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) { Try {
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version") // Check if the recovery is valid before going any further
uiHandler.post { callback.onFailure(InvalidParameterException("Invalid recovery key")) } if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
return@Runnable Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
} throw InvalidParameterException("Invalid recovery key")

// 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<KeysBackupData> {
override fun onSuccess(data: KeysBackupData) {
val sessionsData = ArrayList<MegolmSessionData>()
// 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 // Get a PK decryption instance
val progressListener = if (stepProgressListener != null) { val decryption = pkDecryptionFromRecoveryKey(recoveryKey)
object : ProgressListener { if (decryption == null) {
override fun onProgress(progress: Int, total: Int) { // This should not happen anymore
// Note: no need to post to UI thread, importMegolmSessionsData() will do it Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total)) throw InvalidParameterException("Invalid recovery key")
}
}
} else {
null
} }


megolmSessionDataImporter.handle(sessionsData, !backUp, progressListener, object : MatrixCallback<ImportRoomKeysResult> { decryption!!
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)
}
})
} }
}.fold(
{
callback.onFailure(it)
},
{ decryption ->
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)


override fun onFailure(failure: Throwable) { // Get backed up keys from the homeserver
uiHandler.post { callback.onFailure(failure) } getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback<KeysBackupData> {
} override fun onSuccess(data: KeysBackupData) {
}) GlobalScope.launch(coroutineDispatchers.main) {
}) val importRoomKeysResult = withContext(coroutineDispatchers.crypto) {
val sessionsData = ArrayList<MegolmSessionData>()
// 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, override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
@ -699,31 +707,36 @@ internal class KeysBackup(
callback: MatrixCallback<ImportRoomKeysResult>) { callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")


CryptoAsyncHelper.getDecryptBackgroundHandler().post { GlobalScope.launch(coroutineDispatchers.main) {
val progressListener = if (stepProgressListener != null) { withContext(coroutineDispatchers.crypto) {
object : ProgressListener { val progressListener = if (stepProgressListener != null) {
override fun onProgress(progress: Int, total: Int) { object : ProgressListener {
uiHandler.post { override fun onProgress(progress: Int, total: Int) {
stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total)) uiHandler.post {
stepProgressListener.onStepProgress(StepProgressListener.Step.ComputingKey(progress, total))
}
} }
} }
} } else {
} else { null
null
}

val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, progressListener)

if (recoveryKey == null) {
uiHandler.post {
Timber.v("backupKeys: Invalid configuration")
callback.onFailure(IllegalStateException("Invalid configuration"))
} }


return@post Try {
} recoveryKeyFromPassword(password, keysBackupVersion, progressListener)

}
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback) }.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. * Extract MegolmBackupAuthData data from a backup version.
@ -1190,94 +1203,96 @@ internal class KeysBackup(


keysBackupStateManager.state = KeysBackupState.BackingUp keysBackupStateManager.state = KeysBackupState.BackingUp


CryptoAsyncHelper.getEncryptBackgroundHandler().post { GlobalScope.launch(coroutineDispatchers.main) {
Timber.v("backupKeys: 2 - Encrypting keys") withContext(coroutineDispatchers.crypto) {
Timber.v("backupKeys: 2 - Encrypting keys")


// Gather data to send to the homeserver // Gather data to send to the homeserver
// roomId -> sessionId -> MXKeyBackupData // roomId -> sessionId -> MXKeyBackupData
val keysBackupData = KeysBackupData() val keysBackupData = KeysBackupData()
keysBackupData.roomIdToRoomKeysBackupData = HashMap() keysBackupData.roomIdToRoomKeysBackupData = HashMap()


for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) { for (olmInboundGroupSessionWrapper in olmInboundGroupSessionWrappers) {
val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper) val keyBackupData = encryptGroupSession(olmInboundGroupSessionWrapper)
if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) { if (keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId] == null) {
val roomKeysBackupData = RoomKeysBackupData() val roomKeysBackupData = RoomKeysBackupData()
roomKeysBackupData.sessionIdToKeyBackupData = HashMap() roomKeysBackupData.sessionIdToKeyBackupData = HashMap()
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId!!] = roomKeysBackupData
}

try {
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
} catch (e: OlmException) {
Timber.e(e, "OlmException")
}
} }


try { Timber.v("backupKeys: 4 - Sending request")
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
} catch (e: OlmException) {
Timber.e(e, "OlmException")
}
}


Timber.v("backupKeys: 4 - Sending request") // Make the request

storeSessionDataTask
// Make the request .configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData))
storeSessionDataTask .dispatchTo(object : MatrixCallback<BackupKeysResult> {
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) override fun onSuccess(data: BackupKeysResult) {
.dispatchTo(object : MatrixCallback<BackupKeysResult> {
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) {
uiHandler.post { uiHandler.post {
Timber.e(failure, "backupKeys: backupKeys failed.") Timber.v("backupKeys: 5a - Request complete")


when (failure.error.code) { // Mark keys as backed up
MatrixError.NOT_FOUND, cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
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 if (olmInboundGroupSessionWrappers.size < KEY_BACKUP_SEND_KEYS_MAX_COUNT) {
checkAndStartKeysBackup() Timber.v("backupKeys: All keys have been backed up")
} onServerDataRetrieved(data.count, data.hash)
else -> // Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp // 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 when (failure.error.code) {
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp MatrixError.NOT_FOUND,
maybeBackupKeys() 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 private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100
} }


/* ========================================================================================== /* ==========================================================================================
* DEBUG INFO * DEBUG INFO
* ========================================================================================== */ * ========================================================================================== */


override fun toString() = "KeysBackup for ${credentials.userId}" override fun toString() = "KeysBackup for ${credentials.userId}"
} }

View File

@ -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.Event
import im.vector.matrix.android.api.session.events.model.EventType 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.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.DeviceListManager
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction 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.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap 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.*
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.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.task.TaskExecutor 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) { override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED,
deviceID, deviceID,
userId) userId)


listeners.forEach { listeners.forEach {
try { try {
@ -206,7 +201,7 @@ internal class DefaultSasVerificationService(private val credentials: Credential
} else { } else {
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
cancelTransaction(tid, otherUserId, startReq.fromDevice 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, userId,
deviceID) deviceID)
addTransaction(tx) addTransaction(tx)
CryptoAsyncHelper.getDecryptBackgroundHandler().post {
tx.start() tx.start()
}
return txID return txID
} else { } else {
throw IllegalArgumentException("Unknown verification method") throw IllegalArgumentException("Unknown verification method")
@ -399,9 +393,9 @@ internal class DefaultSasVerificationService(private val credentials: Credential
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
dispatchTxUpdated(tx) dispatchTxUpdated(tx)
if (tx is SASVerificationTransaction if (tx is SASVerificationTransaction
&& (tx.state == SasVerificationTxState.Cancelled && (tx.state == SasVerificationTxState.Cancelled
|| tx.state == SasVerificationTxState.OnCancelled || tx.state == SasVerificationTxState.OnCancelled
|| tx.state == SasVerificationTxState.Verified) || tx.state == SasVerificationTxState.Verified)
) { ) {
//remove //remove
this.removeTransaction(tx.otherUserId, tx.transactionId) this.removeTransaction(tx.otherUserId, tx.transactionId)

View File

@ -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.SasMode
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState 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.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.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.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey 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.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart 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.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
@ -61,21 +60,21 @@ internal class IncomingSASVerificationTransaction(
override val uxState: IncomingSasVerificationTransaction.UxState override val uxState: IncomingSasVerificationTransaction.UxState
get() { get() {
return when (state) { return when (state) {
SasVerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT SasVerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT
SasVerificationTxState.SendingAccept, SasVerificationTxState.SendingAccept,
SasVerificationTxState.Accepted, SasVerificationTxState.Accepted,
SasVerificationTxState.OnKeyReceived, SasVerificationTxState.OnKeyReceived,
SasVerificationTxState.SendingKey, 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.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS
SasVerificationTxState.ShortCodeAccepted, SasVerificationTxState.ShortCodeAccepted,
SasVerificationTxState.SendingMac, SasVerificationTxState.SendingMac,
SasVerificationTxState.MacSent, SasVerificationTxState.MacSent,
SasVerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION SasVerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
SasVerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED SasVerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED
SasVerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME SasVerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME
SasVerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER SasVerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
else -> IncomingSasVerificationTransaction.UxState.UNKNOWN else -> IncomingSasVerificationTransaction.UxState.UNKNOWN
} }
} }


@ -125,9 +124,7 @@ internal class IncomingSASVerificationTransaction(
//TODO force download keys!! //TODO force download keys!!
//would be probably better to download the keys //would be probably better to download the keys
//for now I cancel //for now I cancel
CryptoAsyncHelper.getDecryptBackgroundHandler().post { cancel(CancelCode.User)
cancel(CancelCode.User)
}
} else { } else {
// val otherKey = info.identityKey() // val otherKey = info.identityKey()
//need to jump back to correct thread //need to jump back to correct thread
@ -139,9 +136,7 @@ internal class IncomingSASVerificationTransaction(
shortAuthenticationStrings = agreedShortCode, shortAuthenticationStrings = agreedShortCode,
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT) commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
) )
CryptoAsyncHelper.getDecryptBackgroundHandler().post { doAccept(accept)
doAccept(accept)
}
} }
} }



View File

@ -86,7 +86,6 @@ internal class OutgoingSASVerificationRequest(
} }


fun start() { fun start() {

if (state != SasVerificationTxState.None) { if (state != SasVerificationTxState.None) {
Timber.e("## start verification from invalid state") Timber.e("## start verification from invalid state")
//should I cancel?? //should I cancel??

View File

@ -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.SasMode
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState 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.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.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
@ -278,21 +277,17 @@ internal abstract class SASVerificationTransaction(
.dispatchTo(object : MatrixCallback<Unit> { .dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.") Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.")
CryptoAsyncHelper.getDecryptBackgroundHandler().post { if (onDone != null) {
if (onDone != null) { onDone()
onDone() } else {
} else { state = nextState
state = nextState
}
} }
} }


override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state") Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state")


CryptoAsyncHelper.getDecryptBackgroundHandler().post { cancel(onErrorReason)
cancel(onErrorReason)
}
} }
}) })
.executeBy(taskExecutor) .executeBy(taskExecutor)

View File

@ -17,7 +17,8 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di


import android.content.Context 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.task.TaskExecutor
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -36,11 +37,14 @@ class MatrixModule(private val context: Context) {
} }


single { single {
val cryptoHandler = CryptoAsyncHelper.getDecryptBackgroundHandler() val THREAD_CRYPTO_NAME = "Crypto_Thread"
val handlerThread = HandlerThread(THREAD_CRYPTO_NAME)
handlerThread.start()

MatrixCoroutineDispatchers(io = Dispatchers.IO, MatrixCoroutineDispatchers(io = Dispatchers.IO,
computation = Dispatchers.IO, computation = Dispatchers.IO,
main = Dispatchers.Main, main = Dispatchers.Main,
crypto = cryptoHandler.asCoroutineDispatcher("crypto") crypto = Handler(handlerThread.looper).asCoroutineDispatcher("crypto")
) )
} }



View File

@ -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<ImportRoomKeysResult>) {
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)
}
)
}
}
}

View File

@ -59,18 +59,17 @@ import im.vector.riotredesign.core.extensions.withArgs
import im.vector.riotredesign.core.intent.ExternalIntentData import im.vector.riotredesign.core.intent.ExternalIntentData
import im.vector.riotredesign.core.intent.analyseIntent import im.vector.riotredesign.core.intent.analyseIntent
import im.vector.riotredesign.core.intent.getFilenameFromUri 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.SimpleTextWatcher
import im.vector.riotredesign.core.platform.VectorPreferenceFragment import im.vector.riotredesign.core.platform.VectorPreferenceFragment
import im.vector.riotredesign.core.preference.BingRule import im.vector.riotredesign.core.preference.BingRule
import im.vector.riotredesign.core.preference.ProgressBarPreference import im.vector.riotredesign.core.preference.ProgressBarPreference
import im.vector.riotredesign.core.preference.UserAvatarPreference import im.vector.riotredesign.core.preference.UserAvatarPreference
import im.vector.riotredesign.core.preference.VectorPreference import im.vector.riotredesign.core.preference.VectorPreference
import im.vector.riotredesign.core.resources.openResource
import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.features.MainActivity import im.vector.riotredesign.features.MainActivity
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.crypto.keys.KeysExporter 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.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotredesign.features.themes.ThemeUtils import im.vector.riotredesign.features.themes.ThemeUtils
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -2702,58 +2701,33 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref


importButton.setOnClickListener(View.OnClickListener { importButton.setOnClickListener(View.OnClickListener {
val password = passPhraseEditText.text.toString() val password = passPhraseEditText.text.toString()
val resource = openResource(appContext, uri, mimetype ?: getMimeTypeFromUri(appContext, uri))


if (resource?.mContentStream == null) { KeysImporter(mSession)
appContext.toast("Error") .import(requireContext(),
uri,
mimetype,
password,
object : MatrixCallback<ImportRoomKeysResult> {
override fun onSuccess(data: ImportRoomKeysResult) {
if (!isAdded) {
return
}


return@OnClickListener hideLoadingView()
}


val data: ByteArray AlertDialog.Builder(thisActivity)
// TODO BG .setMessage(getString(R.string.encryption_import_room_keys_success,
try { data.successfullyNumberOfImportedKeys,
data = ByteArray(resource.mContentStream!!.available()) data.totalNumberOfKeys))
resource.mContentStream!!.read(data) .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
resource.mContentStream!!.close() .show()
} catch (e: Exception) { }
try {
resource.mContentStream!!.close()
} catch (e2: Exception) {
Timber.e(e2, "## importKeys()")
}


appContext.toast(e.localizedMessage) override fun onFailure(failure: Throwable) {

appContext.toast(failure.localizedMessage)
return@OnClickListener hideLoadingView()
} }

})
displayLoadingView()

mSession.importRoomKeys(data,
password,
null,
object : MatrixCallback<ImportRoomKeysResult> {
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()
}
})


importDialog.dismiss() importDialog.dismiss()
}) })