diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Try.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Try.kt deleted file mode 100644 index 365d5472..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Try.kt +++ /dev/null @@ -1,30 +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.api.util - -import arrow.core.* - -inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() - .fold( - { - f(it) - Failure(it) - }, - { Success(it) } - ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt index d2474f37..15520835 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticator.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.auth.data.ThreePidMedium +import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.util.CancelableCoroutine @@ -57,7 +58,7 @@ internal class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builde val job = GlobalScope.launch(coroutineDispatchers.main) { val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password) - sessionOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) }) + sessionOrFailure.foldToCallback(callback) } return CancelableCoroutine(job) 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 411bcbf6..00000000 --- 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 5e422571..cb5ed968 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,8 +19,11 @@ 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.squareup.moshi.Types import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials @@ -64,6 +67,7 @@ import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificat import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.session.cache.ClearCacheTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.RoomMembers @@ -137,6 +141,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) @@ -560,10 +566,10 @@ internal class CryptoManager( } else { val algorithm = getEncryptionAlgorithm(roomId) val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, - algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) + algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) Timber.e("## encryptEventContent() : $reason") callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, reason))) + MXCryptoError.UNABLE_TO_ENCRYPT, reason))) } } } @@ -597,10 +603,7 @@ internal class CryptoManager( val result = withContext(coroutineDispatchers.crypto) { internalDecryptEvent(event, timeline) } - result.fold( - { callback.onFailure(it) }, - { callback.onSuccess(it) } - ) + result.foldToCallback(callback) } } @@ -700,7 +703,7 @@ internal class CryptoManager( monarchy.doWithRealm { realm -> // Check whether the event content must be encrypted for the invited members. val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() - && shouldEncryptForInvitedMembers(roomId) + && shouldEncryptForInvitedMembers(roomId) userIds = if (encryptForInvitedMembers) { RoomMembers(realm, roomId).getActiveRoomMemberIds() @@ -787,35 +790,31 @@ internal class CryptoManager( * @param anIterationCount the encryption iteration count (0 means no encryption) * @param callback the exported keys */ - fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback) { - val iterationCount = Math.max(0, anIterationCount) + private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback) { + GlobalScope.launch(coroutineDispatchers.main) { + withContext(coroutineDispatchers.crypto) { + Try { + val iterationCount = Math.max(0, anIterationCount) - val exportedSessions = ArrayList() + val exportedSessions = ArrayList() - val inboundGroupSessions = cryptoStore.getInboundGroupSessions() + val inboundGroupSessions = cryptoStore.getInboundGroupSessions() - for (session in inboundGroupSessions) { - val megolmSessionData = session.exportKeys() + for (session in inboundGroupSessions) { + val megolmSessionData = session.exportKeys() - if (null != megolmSessionData) { - exportedSessions.add(megolmSessionData) - } + if (null != megolmSessionData) { + exportedSessions.add(megolmSessionData) + } + } + + val adapter = MoshiProvider.providesMoshi() + .adapter(List::class.java) + + MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount) + } + }.foldToCallback(callback) } - - val encryptedRoomKeys: ByteArray - - try { - val adapter = MoshiProvider.providesMoshi() - .adapter(List::class.java) - - encryptedRoomKeys = MXMegolmExportEncryption - .encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount) - } catch (e: Exception) { - callback.onFailure(e) - return - } - - callback.onSuccess(encryptedRoomKeys) } /** @@ -830,40 +829,33 @@ internal class CryptoManager( password: String, progressListener: ProgressListener?, callback: MatrixCallback) { - 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 = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) + val t1 = System.currentTimeMillis() - try { - roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - } catch (e: Exception) { - callback.onFailure(e) - return + Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms") + + val importedSessions = MoshiProvider.providesMoshi() + .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) + .fromJson(roomKeys) + + val t2 = System.currentTimeMillis() + + Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms") + + if (importedSessions == null) { + throw Exception("Error") + } + + 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) } /** @@ -898,7 +890,7 @@ internal class CryptoManager( // trigger an an unknown devices exception callback.onFailure( Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) + MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) } } ) @@ -1061,10 +1053,7 @@ internal class CryptoManager( CoroutineScope(coroutineDispatchers.crypto).launch { deviceListManager .downloadKeys(userIds, forceDownload) - .fold( - { callback.onFailure(it) }, - { callback.onSuccess(it) } - ) + .foldToCallback(callback) } } 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 761318ac..495bd6b1 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/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index 2217a796..ffda9507 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -21,7 +21,7 @@ import android.text.TextUtils import arrow.core.Try import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.util.onError +import im.vector.matrix.android.internal.extensions.onError 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.store.IMXCryptoStore diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt index 6a991018..82b2c95d 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXMegolmExportEncryption.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto import android.text.TextUtils import android.util.Base64 +import im.vector.matrix.android.internal.extensions.toUnsignedInt import timber.log.Timber import java.io.ByteArrayOutputStream import java.nio.charset.Charset @@ -43,16 +44,6 @@ object MXMegolmExportEncryption { // default iteration count to export the e2e keys const val DEFAULT_ITERATION_COUNT = 500000 - /** - * Convert a signed byte to a int value - * - * @param bVal the byte value to convert - * @return the matched int value - */ - private fun byteToInt(bVal: Byte): Int { - return (bVal and 0xFF.toByte()).toInt() - } - /** * Extract the AES key from the deriveKeys result. * @@ -108,7 +99,8 @@ object MXMegolmExportEncryption { val salt = Arrays.copyOfRange(body, 1, 1 + 16) val iv = Arrays.copyOfRange(body, 17, 17 + 16) - val iterations = byteToInt(body[33]) shl 24 or (byteToInt(body[34]) shl 16) or (byteToInt(body[35]) shl 8) or byteToInt(body[36]) + val iterations = + (body[33].toUnsignedInt() shl 24) or (body[34].toUnsignedInt() shl 16) or (body[35].toUnsignedInt() shl 8) or body[36].toUnsignedInt() val ciphertext = Arrays.copyOfRange(body, 37, 37 + ciphertextLength) val hmac = Arrays.copyOfRange(body, body.size - 32, body.size) 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 07c8b465..433114df 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 b80b063f..60e19e46 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 81556052..a1595c10 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 1d40337b..2a4f0a1d 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 aa18133f..2210c25a 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 858c48b4..5390c5ed 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 @@ -31,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap 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.extensions.toUnsignedInt import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import org.matrix.olm.OlmSAS @@ -278,21 +278,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) @@ -359,11 +355,11 @@ internal abstract class SASVerificationTransaction( * or with the three numbers on separate lines. */ fun getDecimalCodeRepresentation(byteArray: ByteArray): String { - val b0 = byteArray[0].toInt().and(0xff) //need unsigned byte - val b1 = byteArray[1].toInt().and(0xff) //need unsigned byte - val b2 = byteArray[2].toInt().and(0xff) //need unsigned byte - val b3 = byteArray[3].toInt().and(0xff) //need unsigned byte - val b4 = byteArray[4].toInt().and(0xff) //need unsigned byte + val b0 = byteArray[0].toUnsignedInt() //need unsigned byte + val b1 = byteArray[1].toUnsignedInt() //need unsigned byte + val b2 = byteArray[2].toUnsignedInt() //need unsigned byte + val b3 = byteArray[3].toUnsignedInt() //need unsigned byte + val b4 = byteArray[4].toUnsignedInt() //need unsigned byte //(B0 << 5 | B1 >> 3) + 1000 val first = (b0.shl(5) or b1.shr(3)) + 1000 //((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 @@ -384,12 +380,12 @@ internal abstract class SASVerificationTransaction( * to that number 7 emoji are selected from a list of 64 emoji (see Appendix A) */ fun getEmojiCodeRepresentation(byteArray: ByteArray): List { - val b0 = byteArray[0].toInt().and(0xff) - val b1 = byteArray[1].toInt().and(0xff) - val b2 = byteArray[2].toInt().and(0xff) - val b3 = byteArray[3].toInt().and(0xff) - val b4 = byteArray[4].toInt().and(0xff) - val b5 = byteArray[5].toInt().and(0xff) + val b0 = byteArray[0].toUnsignedInt() + val b1 = byteArray[1].toUnsignedInt() + val b2 = byteArray[2].toUnsignedInt() + val b3 = byteArray[3].toUnsignedInt() + val b4 = byteArray[4].toUnsignedInt() + val b5 = byteArray[5].toUnsignedInt() return listOf( getEmojiForCode((b0 and 0xFC).shr(2)), getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)), 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 8f9447c6..84f6786b 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/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Primitives.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Primitives.kt new file mode 100644 index 00000000..0a0ca1a1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Primitives.kt @@ -0,0 +1,22 @@ +/* + * 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.extensions + +/** + * Convert a signed byte to a int value + */ +fun Byte.toUnsignedInt() = toInt() and 0xff diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Try.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Try.kt new file mode 100644 index 00000000..18ab0e47 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Try.kt @@ -0,0 +1,33 @@ +/* + * 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.extensions + +import arrow.core.* +import im.vector.matrix.android.api.MatrixCallback + +inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() + .fold( + { + f(it) + Failure(it) + }, + { Success(it) } + ) + +fun Try.foldToCallback(callback: MatrixCallback): Unit = fold( + { callback.onFailure(it) }, + { callback.onSuccess(it) }) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt index 986fe81b..263c381d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt @@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.task import arrow.core.Try import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.extensions.foldToCallback +import im.vector.matrix.android.internal.extensions.onError import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope @@ -38,12 +40,10 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis task.execute(task.params) } } - resultOrFailure.fold({ + resultOrFailure.onError { Timber.d(it, "Task failed") - task.callback.onFailure(it) - }, { - task.callback.onSuccess(it) - }) + } + .foldToCallback(task.callback) } return CancelableCoroutine(job) } diff --git a/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt b/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt index 90c12de4..22ec2f90 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/dialogs/ExportKeysDialog.kt @@ -19,34 +19,30 @@ package im.vector.riotredesign.core.dialogs import android.app.Activity import android.text.Editable import android.text.TextUtils -import android.text.TextWatcher import android.widget.Button +import android.widget.ImageView import androidx.appcompat.app.AlertDialog import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.showPassword +import im.vector.riotredesign.core.platform.SimpleTextWatcher class ExportKeysDialog { + var passwordVisible = false + fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null) val builder = AlertDialog.Builder(activity) .setTitle(R.string.encryption_export_room_keys) .setView(dialogLayout) - val passPhrase1EditText = dialogLayout.findViewById(R.id.dialog_e2e_keys_passphrase_edit_text) - val passPhrase2EditText = dialogLayout.findViewById(R.id.dialog_e2e_keys_confirm_passphrase_edit_text) - val passPhrase2Til = dialogLayout.findViewById(R.id.dialog_e2e_keys_confirm_passphrase_til) - val exportButton = dialogLayout.findViewById