From 8a4f0a0c004aeae34675a916aace2e7c2b4a25ea Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 Apr 2020 16:53:14 +0200 Subject: [PATCH] KeyBackup / Use 4S if key in quadS --- CHANGES.md | 1 + .../keysbackup/DefaultKeysBackupService.kt | 3 +- .../restore/KeysBackupRestoreActivity.kt | 63 ++++- .../KeysBackupRestoreFromKeyFragment.kt | 2 +- .../KeysBackupRestoreFromKeyViewModel.kt | 86 ++----- ...KeysBackupRestoreFromPassphraseFragment.kt | 4 +- ...eysBackupRestoreFromPassphraseViewModel.kt | 90 ++----- .../KeysBackupRestoreSharedViewModel.kt | 223 ++++++++++++++++-- 8 files changed, 298 insertions(+), 174 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0c1d209f61..b9f953b4cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -455,6 +455,7 @@ Bugfix: - Fix messages with empty `in_reply_to` not rendering (#447) - Fix clear cache (#408) and Logout (#205) - Fix `(edited)` link can be copied to clipboard (#402) + - KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163) Build: - Split APK: generate one APK per arch, to reduce APK size of about 30% diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 9245f77317..ebef751925 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -728,7 +728,8 @@ internal class DefaultKeysBackupService @Inject constructor( if (backUp) { maybeBackupKeys() } - + // Save for next time and for gossiping + saveBackupRecoveryKey(recoveryKey, keysVersionResult.version) result } }.foldToCallback(callback) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index e6d303b3aa..2b4e372166 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -20,16 +20,22 @@ import android.content.Context import android.content.Intent import androidx.appcompat.app.AlertDialog import androidx.lifecycle.Observer +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.riotx.R import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.platform.SimpleFragmentActivity +import im.vector.riotx.core.ui.views.KeysBackupBanner +import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity class KeysBackupRestoreActivity : SimpleFragmentActivity() { companion object { + private const val REQUEST_4S_SECRET = 100 + const val SECRET_ALIAS = SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS + fun intent(context: Context): Intent { return Intent(context, KeysBackupRestoreActivity::class.java) } @@ -39,14 +45,20 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { private lateinit var viewModel: KeysBackupRestoreSharedViewModel + override fun onBackPressed() { + hideWaitingView() + super.onBackPressed() + } + override fun initUiAndData() { super.initUiAndData() viewModel = viewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java) viewModel.initSession(session) - viewModel.keyVersionResult.observe(this, Observer { keyVersion -> - if (keyVersion != null && supportFragmentManager.fragments.isEmpty()) { - val isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null + viewModel.keySourceModel.observe(this, Observer { keySource -> + if (keySource != null && !keySource.isInQuadS && supportFragmentManager.fragments.isEmpty()) { + val isBackupCreatedFromPassphrase = + viewModel.keyVersionResult.value?.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null if (isBackupCreatedFromPassphrase) { replaceFragment(R.id.container, KeysBackupRestoreFromPassphraseFragment::class.java) } else { @@ -69,7 +81,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { if (viewModel.keyVersionResult.value == null) { // We need to fetch from API - viewModel.getLatestVersion(this) + viewModel.getLatestVersion() } viewModel.navigateEvent.observeEvent(this) { uxStateEvent -> @@ -78,8 +90,25 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { addFragmentToBackstack(R.id.container, KeysBackupRestoreFromKeyFragment::class.java) } KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> { + viewModel.keyVersionResult.value?.version?.let { + KeysBackupBanner.onRecoverDoneForVersion(this, it) + } replaceFragment(R.id.container, KeysBackupRestoreSuccessFragment::class.java) } + KeysBackupRestoreSharedViewModel.NAVIGATE_TO_4S -> { + launch4SActivity() + } + KeysBackupRestoreSharedViewModel.NAVIGATE_FAILED_TO_LOAD_4S -> { + AlertDialog.Builder(this) + .setTitle(R.string.unknown_error) + .setMessage(R.string.error_failed_to_import_keys) + .setCancelable(false) + .setPositiveButton(R.string.ok) { _, _ -> + // nop + launch4SActivity() + } + .show() + } } } @@ -93,4 +122,30 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { finish() } } + + private fun launch4SActivity() { + SharedSecureStorageActivity.newIntent( + context = this, + keyId = null, // default key + requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME), + resultKeyStoreAlias = SECRET_ALIAS + ).let { + startActivityForResult(it, REQUEST_4S_SECRET) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == REQUEST_4S_SECRET) { + val extraResult = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT) + if (resultCode == Activity.RESULT_OK && extraResult != null) { + viewModel.handleGotSecretFromSSSS( + extraResult, + SECRET_ALIAS + ) + } else { + finish() + } + } + super.onActivityResult(requestCode, resultCode, data) + } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt index 730c92a319..9a6e65a885 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt @@ -82,7 +82,7 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor() if (value.isNullOrBlank()) { viewModel.recoveryCodeErrorText.value = context?.getString(R.string.keys_backup_recovery_code_empty_error_message) } else { - viewModel.recoverKeys(requireContext(), sharedViewModel) + viewModel.recoverKeys(sharedViewModel) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt index 0cf297f7f1..c8406570d3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt @@ -15,21 +15,19 @@ */ package im.vector.riotx.features.crypto.keysbackup.restore -import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.listeners.StepProgressListener -import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService -import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult -import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import androidx.lifecycle.viewModelScope import im.vector.riotx.R import im.vector.riotx.core.platform.WaitingViewData -import im.vector.riotx.core.ui.views.KeysBackupBanner -import timber.log.Timber +import im.vector.riotx.core.resources.StringProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import javax.inject.Inject -class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() { +class KeysBackupRestoreFromKeyViewModel @Inject constructor( + private val stringProvider: StringProvider +) : ViewModel() { var recoveryCode: MutableLiveData = MutableLiveData() var recoveryCodeErrorText: MutableLiveData = MutableLiveData() @@ -45,66 +43,16 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() { recoveryCodeErrorText.value = null } - fun recoverKeys(context: Context, sharedViewModel: KeysBackupRestoreSharedViewModel) { - val session = sharedViewModel.session - val keysBackup = session.cryptoService().keysBackupService() - + fun recoverKeys(sharedViewModel: KeysBackupRestoreSharedViewModel) { + sharedViewModel.loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) recoveryCodeErrorText.value = null - val recoveryKey = recoveryCode.value!! - - val keysVersionResult = sharedViewModel.keyVersionResult.value!! - - keysBackup.restoreKeysWithRecoveryKey(keysVersionResult, - recoveryKey, - null, - session.myUserId, - object : StepProgressListener { - override fun onStepProgress(step: StepProgressListener.Step) { - when (step) { - is StepProgressListener.Step.DownloadingKey -> { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message), - isIndeterminate = true)) - } - is StepProgressListener.Step.ImportingKey -> { - // Progress 0 can take a while, display an indeterminate progress in this case - if (step.progress == 0) { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), - isIndeterminate = true)) - } else { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), - step.progress, - step.total)) - } - } - } - } - }, - object : MatrixCallback { - override fun onSuccess(data: ImportRoomKeysResult) { - sharedViewModel.loadingEvent.value = null - sharedViewModel.didRecoverSucceed(data) - - KeysBackupBanner.onRecoverDoneForVersion(context, keysVersionResult.version!!) - trustOnDecrypt(keysBackup, keysVersionResult) - } - - override fun onFailure(failure: Throwable) { - sharedViewModel.loadingEvent.value = null - recoveryCodeErrorText.value = context.getString(R.string.keys_backup_recovery_code_error_decrypt) - Timber.e(failure, "## onUnexpectedError") - } - }) - } - - private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) { - keysBackup.trustKeysBackupVersion(keysVersionResult, true, - object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("##### trustKeysBackupVersion onSuccess") - } - }) + viewModelScope.launch(Dispatchers.IO) { + val recoveryKey = recoveryCode.value!! + try { + sharedViewModel.recoverUsingBackupPass(recoveryKey) + } catch (failure: Throwable) { + recoveryCodeErrorText.value = stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt) + } + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt index 8dc8855583..0947c144d8 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt @@ -36,7 +36,7 @@ import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.platform.VectorBaseFragment import javax.inject.Inject -class KeysBackupRestoreFromPassphraseFragment @Inject constructor(): VectorBaseFragment() { +class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_passphrase @@ -119,7 +119,7 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor(): VectorBaseF if (value.isNullOrBlank()) { viewModel.passphraseErrorText.value = context?.getString(R.string.passphrase_empty_error_message) } else { - viewModel.recoverKeys(context!!, sharedViewModel) + viewModel.recoverKeys(sharedViewModel) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt index 69c5e70740..46e8d5fa18 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt @@ -15,21 +15,18 @@ */ package im.vector.riotx.features.crypto.keysbackup.restore -import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.listeners.StepProgressListener -import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService -import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult -import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import androidx.lifecycle.viewModelScope import im.vector.riotx.R -import im.vector.riotx.core.platform.WaitingViewData -import im.vector.riotx.core.ui.views.KeysBackupBanner -import timber.log.Timber +import im.vector.riotx.core.resources.StringProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import javax.inject.Inject -class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel() { +class KeysBackupRestoreFromPassphraseViewModel @Inject constructor( + private val stringProvider: StringProvider +) : ViewModel() { var passphrase: MutableLiveData = MutableLiveData() var passphraseErrorText: MutableLiveData = MutableLiveData() @@ -48,71 +45,14 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel passphraseErrorText.value = null } - fun recoverKeys(context: Context, sharedViewModel: KeysBackupRestoreSharedViewModel) { - val keysBackup = sharedViewModel.session.cryptoService().keysBackupService() - + fun recoverKeys(sharedViewModel: KeysBackupRestoreSharedViewModel) { passphraseErrorText.value = null - - val keysVersionResult = sharedViewModel.keyVersionResult.value!! - - keysBackup.restoreKeyBackupWithPassword(keysVersionResult, - passphrase.value!!, - null, - sharedViewModel.session.myUserId, - object : StepProgressListener { - override fun onStepProgress(step: StepProgressListener.Step) { - when (step) { - is StepProgressListener.Step.ComputingKey -> { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - step.progress, - step.total)) - } - is StepProgressListener.Step.DownloadingKey -> { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message), - isIndeterminate = true)) - } - is StepProgressListener.Step.ImportingKey -> { - Timber.d("backupKeys.ImportingKey.progress: ${step.progress}") - // Progress 0 can take a while, display an indeterminate progress in this case - if (step.progress == 0) { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), - isIndeterminate = true)) - } else { - sharedViewModel.loadingEvent.postValue(WaitingViewData(context.getString(R.string.keys_backup_restoring_waiting_message) - + "\n" + context.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), - step.progress, - step.total)) - } - } - } - } - }, - object : MatrixCallback { - override fun onSuccess(data: ImportRoomKeysResult) { - sharedViewModel.loadingEvent.value = null - sharedViewModel.didRecoverSucceed(data) - - KeysBackupBanner.onRecoverDoneForVersion(context, keysVersionResult.version!!) - trustOnDecrypt(keysBackup, keysVersionResult) - } - - override fun onFailure(failure: Throwable) { - sharedViewModel.loadingEvent.value = null - passphraseErrorText.value = context.getString(R.string.keys_backup_passphrase_error_decrypt) - Timber.e(failure, "## onUnexpectedError") - } - }) - } - - private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) { - keysBackup.trustKeysBackupVersion(keysVersionResult, true, - object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("##### trustKeysBackupVersion onSuccess") - } - }) + viewModelScope.launch(Dispatchers.IO) { + try { + sharedViewModel.recoverUsingBackupPass(passphrase.value!!) + } catch (failure: Throwable) { + passphraseErrorText.value = stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt) + } + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt index 5586d0cf05..3852d1035f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt @@ -15,30 +15,52 @@ */ package im.vector.riotx.features.crypto.keysbackup.restore -import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService +import im.vector.matrix.android.api.session.securestorage.KeyInfoResult +import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.util.awaitCallback import im.vector.riotx.R import im.vector.riotx.core.platform.WaitingViewData +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.LiveEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject -class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() { +class KeysBackupRestoreSharedViewModel @Inject constructor( + private val stringProvider: StringProvider +) : ViewModel() { + + data class KeySource( + val isInMemory: Boolean, + val isInQuadS: Boolean + ) companion object { const val NAVIGATE_TO_RECOVER_WITH_KEY = "NAVIGATE_TO_RECOVER_WITH_KEY" const val NAVIGATE_TO_SUCCESS = "NAVIGATE_TO_SUCCESS" + const val NAVIGATE_TO_4S = "NAVIGATE_TO_4S" + const val NAVIGATE_FAILED_TO_LOAD_4S = "NAVIGATE_FAILED_TO_LOAD_4S" } lateinit var session: Session var keyVersionResult: MutableLiveData = MutableLiveData() + var keySourceModel: MutableLiveData = MutableLiveData() + private var _keyVersionResultError: MutableLiveData> = MutableLiveData() val keyVersionResultError: LiveData> get() = _keyVersionResultError @@ -62,30 +84,187 @@ class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() { this.session = session } - fun getLatestVersion(context: Context) { - val keysBackup = session.cryptoService().keysBackupService() - - loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restore_is_getting_backup_version)) - - keysBackup.getCurrentVersion(object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult?) { - loadingEvent.value = null - if (data?.version.isNullOrBlank()) { - // should not happen - _keyVersionResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, "")) - } else { - keyVersionResult.value = data + val progressObserver = object : StepProgressListener { + override fun onStepProgress(step: StepProgressListener.Step) { + when (step) { + is StepProgressListener.Step.ComputingKey -> { + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + step.progress, + step.total)) + } + is StepProgressListener.Step.DownloadingKey -> { + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message), + isIndeterminate = true)) + } + is StepProgressListener.Step.ImportingKey -> { + Timber.d("backupKeys.ImportingKey.progress: ${step.progress}") + // Progress 0 can take a while, display an indeterminate progress in this case + if (step.progress == 0) { + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), + isIndeterminate = true)) + } else { + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), + step.progress, + step.total)) + } } } + } + } - override fun onFailure(failure: Throwable) { - loadingEvent.value = null - _keyVersionResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, failure.localizedMessage)) + fun getLatestVersion() { + val keysBackup = session.cryptoService().keysBackupService() - // TODO For network error - // _keyVersionResultError.value = LiveEvent(context.getString(R.string.network_error_please_check_and_retry)) + loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)) + + viewModelScope.launch(Dispatchers.IO) { + try { + val version = awaitCallback { + keysBackup.getCurrentVersion(it) + } + if (version?.version == null) { + loadingEvent.postValue(null) + _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, ""))) + return@launch + } + + keyVersionResult.postValue(version) + // Let's check if there is quads + val isBackupKeyInQuadS = isBackupKeyInQuadS() + + val savedSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() + if (savedSecret != null && savedSecret.version == version.version) { + // key is in memory! + keySourceModel.postValue( + KeySource(isInMemory = true, isInQuadS = true) + ) + // Go and use it!! + try { + recoverUsingBackupRecoveryKey(savedSecret.recoveryKey) + } catch (failure: Throwable) { + keySourceModel.postValue( + KeySource(isInMemory = false, isInQuadS = true) + ) + } + } else if (isBackupKeyInQuadS) { + // key is in QuadS! + keySourceModel.postValue( + KeySource(isInMemory = false, isInQuadS = true) + ) + _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_4S)) + } else { + // we need to restore directly + keySourceModel.postValue( + KeySource(isInMemory = false, isInQuadS = false) + ) + } + + loadingEvent.postValue(null) + } catch (failure: Throwable) { + loadingEvent.postValue(null) + _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))) } - }) + } + } + + fun handleGotSecretFromSSSS(cipherData: String, alias: String) { + try { + cipherData.fromBase64().inputStream().use { ins -> + val res = session.loadSecureSecret>(ins, alias) + val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) + if (secret == null) { + _navigateEvent.postValue( + LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S) + ) + return + } + loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version)) + + viewModelScope.launch(Dispatchers.IO) { + try { + recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64())) + } catch (failure: Throwable) { + _navigateEvent.postValue( + LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S) + ) + } + } + } + } catch (failure: Throwable) { + } + } + + suspend fun recoverUsingBackupPass(passphrase: String) { + val keysBackup = session.cryptoService().keysBackupService() + val keyVersion = keyVersionResult.value ?: return + + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) + + try { + val result = awaitCallback { + keysBackup.restoreKeyBackupWithPassword(keyVersion, + passphrase, + null, + session.myUserId, + progressObserver, + it + ) + } + loadingEvent.postValue(null) + didRecoverSucceed(result) + trustOnDecrypt(keysBackup, keyVersion) + } catch (failure: Throwable) { + throw failure + } + } + + suspend fun recoverUsingBackupRecoveryKey(recoveryKey: String) { + val keysBackup = session.cryptoService().keysBackupService() + val keyVersion = keyVersionResult.value ?: return + + loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading))) + + try { + val result = awaitCallback { + keysBackup.restoreKeysWithRecoveryKey(keyVersion, + recoveryKey, + null, + session.myUserId, + progressObserver, + it + ) + } + loadingEvent.postValue(null) + didRecoverSucceed(result) + trustOnDecrypt(keysBackup, keyVersion) + } catch (failure: Throwable) { + throw failure + } + } + + private fun isBackupKeyInQuadS(): Boolean { + val sssBackupSecret = session.getAccountDataEvent(KEYBACKUP_SECRET_SSSS_NAME) + ?: return false + + // Some sanity ? + val defaultKeyResult = session.sharedSecretStorageService.getDefaultKey() + val keyInfo = (defaultKeyResult as? KeyInfoResult.Success)?.keyInfo + ?: return false + + return (sssBackupSecret.content["encrypted"] as? Map<*, *>)?.containsKey(keyInfo.id) == true + } + + private fun trustOnDecrypt(keysBackup: KeysBackupService, keysVersionResult: KeysVersionResult) { + keysBackup.trustKeysBackupVersion(keysVersionResult, true, + object : MatrixCallback { + override fun onSuccess(data: Unit) { + Timber.v("##### trustKeysBackupVersion onSuccess") + } + }) } fun moveToRecoverWithKey() { @@ -94,6 +273,6 @@ class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() { fun didRecoverSucceed(result: ImportRoomKeysResult) { importKeyResult = result - _navigateEvent.value = LiveEvent(NAVIGATE_TO_SUCCESS) + _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS)) } }