mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
KeyBackup / Use 4S if key in quadS
This commit is contained in:
parent
4d207e6acd
commit
8a4f0a0c00
8 changed files with 298 additions and 174 deletions
|
@ -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%
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
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))
|
||||
try {
|
||||
sharedViewModel.recoverUsingBackupPass(recoveryKey)
|
||||
} catch (failure: Throwable) {
|
||||
recoveryCodeErrorText.value = stringProvider.getString(R.string.keys_backup_recovery_code_error_decrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
sharedViewModel.recoverUsingBackupPass(passphrase.value!!)
|
||||
} catch (failure: Throwable) {
|
||||
passphraseErrorText.value = stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,28 +84,185 @@ class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() {
|
|||
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()
|
||||
|
||||
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?> {
|
||||
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, ""))
|
||||
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 {
|
||||
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) {
|
||||
loadingEvent.value = null
|
||||
_keyVersionResultError.value = LiveEvent(context.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))
|
||||
|
||||
// TODO For network error
|
||||
// _keyVersionResultError.value = LiveEvent(context.getString(R.string.network_error_please_check_and_retry))
|
||||
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<ImportRoomKeysResult> {
|
||||
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<ImportRoomKeysResult> {
|
||||
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<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("##### trustKeysBackupVersion onSuccess")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue