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

View file

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

View file

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

View file

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

View file

@ -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<String> = MutableLiveData()
var recoveryCodeErrorText: MutableLiveData<String> = 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<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")
}
})
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)
}
}
}
}

View file

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

View file

@ -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<String> = MutableLiveData()
var passphraseErrorText: MutableLiveData<String> = 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<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")
}
})
viewModelScope.launch(Dispatchers.IO) {
try {
sharedViewModel.recoverUsingBackupPass(passphrase.value!!)
} catch (failure: Throwable) {
passphraseErrorText.value = stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt)
}
}
}
}

View file

@ -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<KeysVersionResult> = MutableLiveData()
var keySourceModel: MutableLiveData<KeySource> = MutableLiveData()
private var _keyVersionResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData()
val keyVersionResultError: LiveData<LiveEvent<String>>
get() = _keyVersionResultError
@ -62,30 +84,192 @@ 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<KeysVersionResult?> {
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<KeysVersionResult?> {
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<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))
viewModelScope.launch(Dispatchers.IO) {
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")
}
})
}
fun moveToRecoverWithKey() {
@ -94,6 +278,6 @@ class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() {
fun didRecoverSucceed(result: ImportRoomKeysResult) {
importKeyResult = result
_navigateEvent.value = LiveEvent(NAVIGATE_TO_SUCCESS)
_navigateEvent.postValue(LiveEvent(NAVIGATE_TO_SUCCESS))
}
}