Merge pull request #1259 from vector-im/feature/restore_backup_from_ssss

KeyBackup / Use 4S if key in quadS
This commit is contained in:
Valere 2020-04-21 14:31:26 +02:00 committed by GitHub
commit 491f0e6032
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 303 additions and 174 deletions

View file

@ -455,6 +455,7 @@ Bugfix:
- Fix messages with empty `in_reply_to` not rendering (#447) - Fix messages with empty `in_reply_to` not rendering (#447)
- Fix clear cache (#408) and Logout (#205) - Fix clear cache (#408) and Logout (#205)
- Fix `(edited)` link can be copied to clipboard (#402) - Fix `(edited)` link can be copied to clipboard (#402)
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
Build: Build:
- Split APK: generate one APK per arch, to reduce APK size of about 30% - Split APK: generate one APK per arch, to reduce APK size of about 30%

View file

@ -728,7 +728,8 @@ internal class DefaultKeysBackupService @Inject constructor(
if (backUp) { if (backUp) {
maybeBackupKeys() maybeBackupKeys()
} }
// Save for next time and for gossiping
saveBackupRecoveryKey(recoveryKey, keysVersionResult.version)
result result
} }
}.foldToCallback(callback) }.foldToCallback(callback)

View file

@ -20,16 +20,22 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer 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.R
import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.extensions.addFragmentToBackstack
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.extensions.replaceFragment
import im.vector.riotx.core.platform.SimpleFragmentActivity 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() { class KeysBackupRestoreActivity : SimpleFragmentActivity() {
companion object { companion object {
private const val REQUEST_4S_SECRET = 100
const val SECRET_ALIAS = SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
fun intent(context: Context): Intent { fun intent(context: Context): Intent {
return Intent(context, KeysBackupRestoreActivity::class.java) return Intent(context, KeysBackupRestoreActivity::class.java)
} }
@ -39,14 +45,20 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
private lateinit var viewModel: KeysBackupRestoreSharedViewModel private lateinit var viewModel: KeysBackupRestoreSharedViewModel
override fun onBackPressed() {
hideWaitingView()
super.onBackPressed()
}
override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
viewModel = viewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java) viewModel = viewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
viewModel.initSession(session) viewModel.initSession(session)
viewModel.keyVersionResult.observe(this, Observer { keyVersion ->
if (keyVersion != null && supportFragmentManager.fragments.isEmpty()) { viewModel.keySourceModel.observe(this, Observer { keySource ->
val isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null if (keySource != null && !keySource.isInQuadS && supportFragmentManager.fragments.isEmpty()) {
val isBackupCreatedFromPassphrase =
viewModel.keyVersionResult.value?.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
if (isBackupCreatedFromPassphrase) { if (isBackupCreatedFromPassphrase) {
replaceFragment(R.id.container, KeysBackupRestoreFromPassphraseFragment::class.java) replaceFragment(R.id.container, KeysBackupRestoreFromPassphraseFragment::class.java)
} else { } else {
@ -69,7 +81,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
if (viewModel.keyVersionResult.value == null) { if (viewModel.keyVersionResult.value == null) {
// We need to fetch from API // We need to fetch from API
viewModel.getLatestVersion(this) viewModel.getLatestVersion()
} }
viewModel.navigateEvent.observeEvent(this) { uxStateEvent -> viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
@ -78,8 +90,25 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
addFragmentToBackstack(R.id.container, KeysBackupRestoreFromKeyFragment::class.java) addFragmentToBackstack(R.id.container, KeysBackupRestoreFromKeyFragment::class.java)
} }
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> { KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> {
viewModel.keyVersionResult.value?.version?.let {
KeysBackupBanner.onRecoverDoneForVersion(this, it)
}
replaceFragment(R.id.container, KeysBackupRestoreSuccessFragment::class.java) 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() 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)
}
} }

View file

@ -82,7 +82,7 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
if (value.isNullOrBlank()) { if (value.isNullOrBlank()) {
viewModel.recoveryCodeErrorText.value = context?.getString(R.string.keys_backup_recovery_code_empty_error_message) viewModel.recoveryCodeErrorText.value = context?.getString(R.string.keys_backup_recovery_code_empty_error_message)
} else { } else {
viewModel.recoverKeys(requireContext(), sharedViewModel) viewModel.recoverKeys(sharedViewModel)
} }
} }

View file

@ -15,21 +15,19 @@
*/ */
package im.vector.riotx.features.crypto.keysbackup.restore package im.vector.riotx.features.crypto.keysbackup.restore
import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.MatrixCallback import androidx.lifecycle.viewModelScope
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 im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.platform.WaitingViewData
import im.vector.riotx.core.ui.views.KeysBackupBanner import im.vector.riotx.core.resources.StringProvider
import timber.log.Timber import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() { class KeysBackupRestoreFromKeyViewModel @Inject constructor(
private val stringProvider: StringProvider
) : ViewModel() {
var recoveryCode: MutableLiveData<String> = MutableLiveData() var recoveryCode: MutableLiveData<String> = MutableLiveData()
var recoveryCodeErrorText: MutableLiveData<String> = MutableLiveData() var recoveryCodeErrorText: MutableLiveData<String> = MutableLiveData()
@ -45,66 +43,16 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() {
recoveryCodeErrorText.value = null recoveryCodeErrorText.value = null
} }
fun recoverKeys(context: Context, sharedViewModel: KeysBackupRestoreSharedViewModel) { fun recoverKeys(sharedViewModel: KeysBackupRestoreSharedViewModel) {
val session = sharedViewModel.session sharedViewModel.loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.loading)))
val keysBackup = session.cryptoService().keysBackupService()
recoveryCodeErrorText.value = null recoveryCodeErrorText.value = null
viewModelScope.launch(Dispatchers.IO) {
val recoveryKey = recoveryCode.value!! val recoveryKey = recoveryCode.value!!
try {
val keysVersionResult = sharedViewModel.keyVersionResult.value!! sharedViewModel.recoverUsingBackupPass(recoveryKey)
} catch (failure: Throwable) {
keysBackup.restoreKeysWithRecoveryKey(keysVersionResult, recoveryCodeErrorText.value = stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt)
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<ImportRoomKeysResult> {
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<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("##### trustKeysBackupVersion onSuccess")
}
})
}
} }

View file

@ -36,7 +36,7 @@ import im.vector.riotx.core.extensions.showPassword
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import javax.inject.Inject 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 override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_passphrase
@ -119,7 +119,7 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor(): VectorBaseF
if (value.isNullOrBlank()) { if (value.isNullOrBlank()) {
viewModel.passphraseErrorText.value = context?.getString(R.string.passphrase_empty_error_message) viewModel.passphraseErrorText.value = context?.getString(R.string.passphrase_empty_error_message)
} else { } else {
viewModel.recoverKeys(context!!, sharedViewModel) viewModel.recoverKeys(sharedViewModel)
} }
} }
} }

View file

@ -15,21 +15,18 @@
*/ */
package im.vector.riotx.features.crypto.keysbackup.restore package im.vector.riotx.features.crypto.keysbackup.restore
import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.MatrixCallback import androidx.lifecycle.viewModelScope
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 im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.views.KeysBackupBanner import kotlinx.coroutines.Dispatchers
import timber.log.Timber import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel() { class KeysBackupRestoreFromPassphraseViewModel @Inject constructor(
private val stringProvider: StringProvider
) : ViewModel() {
var passphrase: MutableLiveData<String> = MutableLiveData() var passphrase: MutableLiveData<String> = MutableLiveData()
var passphraseErrorText: MutableLiveData<String> = MutableLiveData() var passphraseErrorText: MutableLiveData<String> = MutableLiveData()
@ -48,71 +45,14 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel
passphraseErrorText.value = null passphraseErrorText.value = null
} }
fun recoverKeys(context: Context, sharedViewModel: KeysBackupRestoreSharedViewModel) { fun recoverKeys(sharedViewModel: KeysBackupRestoreSharedViewModel) {
val keysBackup = sharedViewModel.session.cryptoService().keysBackupService()
passphraseErrorText.value = null passphraseErrorText.value = null
viewModelScope.launch(Dispatchers.IO) {
val keysVersionResult = sharedViewModel.keyVersionResult.value!! try {
sharedViewModel.recoverUsingBackupPass(passphrase.value!!)
keysBackup.restoreKeyBackupWithPassword(keysVersionResult, } catch (failure: Throwable) {
passphrase.value!!, passphraseErrorText.value = stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt)
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<ImportRoomKeysResult> {
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<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("##### trustKeysBackupVersion onSuccess")
}
})
}
} }

View file

@ -15,30 +15,52 @@
*/ */
package im.vector.riotx.features.crypto.keysbackup.restore package im.vector.riotx.features.crypto.keysbackup.restore
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import im.vector.matrix.android.api.MatrixCallback 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.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.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.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.platform.WaitingViewData
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject 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 { companion object {
const val NAVIGATE_TO_RECOVER_WITH_KEY = "NAVIGATE_TO_RECOVER_WITH_KEY" const val NAVIGATE_TO_RECOVER_WITH_KEY = "NAVIGATE_TO_RECOVER_WITH_KEY"
const val NAVIGATE_TO_SUCCESS = "NAVIGATE_TO_SUCCESS" 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 lateinit var session: Session
var keyVersionResult: MutableLiveData<KeysVersionResult> = MutableLiveData() var keyVersionResult: MutableLiveData<KeysVersionResult> = MutableLiveData()
var keySourceModel: MutableLiveData<KeySource> = MutableLiveData()
private var _keyVersionResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData() private var _keyVersionResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData()
val keyVersionResultError: LiveData<LiveEvent<String>> val keyVersionResultError: LiveData<LiveEvent<String>>
get() = _keyVersionResultError get() = _keyVersionResultError
@ -62,28 +84,190 @@ class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() {
this.session = session this.session = session
} }
fun getLatestVersion(context: Context) { 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))
}
}
}
}
}
fun getLatestVersion() {
val keysBackup = session.cryptoService().keysBackupService() val keysBackup = session.cryptoService().keysBackupService()
loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_restore_is_getting_backup_version)) loadingEvent.value = WaitingViewData(stringProvider.getString(R.string.keys_backup_restore_is_getting_backup_version))
keysBackup.getCurrentVersion(object : MatrixCallback<KeysVersionResult?> { viewModelScope.launch(Dispatchers.IO) {
override fun onSuccess(data: KeysVersionResult?) { try {
loadingEvent.value = null val version = awaitCallback<KeysVersionResult?> {
if (data?.version.isNullOrBlank()) { keysBackup.getCurrentVersion(it)
// should not happen }
_keyVersionResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, "")) 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 { } else {
keyVersionResult.value = data // 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)))
}
} }
} }
override fun onFailure(failure: Throwable) { fun handleGotSecretFromSSSS(cipherData: String, alias: String) {
loadingEvent.value = null try {
_keyVersionResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, failure.localizedMessage)) cipherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<Map<String, String>>(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))
// TODO For network error viewModelScope.launch(Dispatchers.IO) {
// _keyVersionResultError.value = LiveEvent(context.getString(R.string.network_error_please_check_and_retry)) try {
recoverUsingBackupRecoveryKey(computeRecoveryKey(secret.fromBase64()))
} catch (failure: Throwable) {
_navigateEvent.postValue(
LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
)
}
}
}
} catch (failure: Throwable) {
_navigateEvent.postValue(
LiveEvent(NAVIGATE_FAILED_TO_LOAD_4S)
)
}
}
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<ImportRoomKeysResult> {
keysBackup.restoreKeyBackupWithPassword(keyVersion,
passphrase,
null,
session.myUserId,
progressObserver,
it
)
}
loadingEvent.postValue(null)
didRecoverSucceed(result)
trustOnDecrypt(keysBackup, keyVersion)
} catch (failure: Throwable) {
loadingEvent.postValue(null)
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<ImportRoomKeysResult> {
keysBackup.restoreKeysWithRecoveryKey(keyVersion,
recoveryKey,
null,
session.myUserId,
progressObserver,
it
)
}
loadingEvent.postValue(null)
didRecoverSucceed(result)
trustOnDecrypt(keysBackup, keyVersion)
} catch (failure: Throwable) {
loadingEvent.postValue(null)
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<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("##### trustKeysBackupVersion onSuccess")
} }
}) })
} }
@ -94,6 +278,6 @@ class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() {
fun didRecoverSucceed(result: ImportRoomKeysResult) { fun didRecoverSucceed(result: ImportRoomKeysResult) {
importKeyResult = result importKeyResult = result
_navigateEvent.value = LiveEvent(NAVIGATE_TO_SUCCESS) _navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS))
} }
} }