Re-initialize backup location when changing recovery code

* delete all storage backups for current user
* clears the storage backup cache
* start a new app data restore set and initializes it

The reason is that old backups won't be readable anymore with the new key. We also can't delete other backups safely as we did before, because we can't be sure that they don't belong to a different device or user.
This commit is contained in:
Torsten Grote 2021-06-22 10:20:04 -03:00 committed by Chirayu Desai
parent 347d2a316f
commit 1ff8e54748
5 changed files with 36 additions and 19 deletions

View file

@ -46,7 +46,7 @@ open class App : Application() {
factory { AppListRetriever(this@App, get(), get(), get()) } factory { AppListRetriever(this@App, get(), get(), get()) }
viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get()) } viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get()) }
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get()) } viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get()) }
viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) } viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) }
viewModel { RestoreStorageViewModel(this@App, get(), get()) } viewModel { RestoreStorageViewModel(this@App, get(), get()) }
viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) } viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) }

View file

@ -44,11 +44,6 @@ internal class DocumentsProviderBackupPlugin(
storage.currentFullBackupDir ?: throw IOException() storage.currentFullBackupDir ?: throw IOException()
} }
@Throws(IOException::class)
override suspend fun deleteAllBackups() {
storage.rootBackupDir?.deleteContents(context)
}
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getMetadataOutputStream(): OutputStream { override suspend fun getMetadataOutputStream(): OutputStream {
val setDir = storage.getSetDir() ?: throw IOException() val setDir = storage.getSetDir() ?: throw IOException()

View file

@ -25,12 +25,6 @@ interface BackupPlugin {
@Throws(IOException::class) @Throws(IOException::class)
suspend fun initializeDevice() suspend fun initializeDevice()
/**
* Delete all existing [RestoreSet]s from the storage medium.
*/
@Throws(IOException::class)
suspend fun deleteAllBackups()
/** /**
* Returns an [OutputStream] for writing backup metadata. * Returns an [OutputStream] for writing backup metadata.
*/ */

View file

@ -192,7 +192,7 @@ class RecoveryCodeInputFragment : Fragment() {
private val regenRequest = registerForActivityResult(StartActivityForResult()) { private val regenRequest = registerForActivityResult(StartActivityForResult()) {
if (it.resultCode == RESULT_OK) { if (it.resultCode == RESULT_OK) {
viewModel.deleteAllBackup() viewModel.reinitializeBackupLocation()
parentFragmentManager.popBackStack() parentFragmentManager.popBackStack()
Snackbar.make(requireView(), R.string.recovery_code_recreated, Snackbar.LENGTH_LONG) Snackbar.make(requireView(), R.string.recovery_code_recreated, Snackbar.LENGTH_LONG)
.show() .show()

View file

@ -1,11 +1,14 @@
package com.stevesoltys.seedvault.ui.recoverycode package com.stevesoltys.seedvault.ui.recoverycode
import android.app.backup.IBackupManager
import android.os.UserHandle
import android.util.Log import android.util.Log
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import com.stevesoltys.seedvault.App import com.stevesoltys.seedvault.App
import com.stevesoltys.seedvault.crypto.Crypto import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.crypto.KeyManager import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.transport.backup.BackupPlugin import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent
import io.github.novacrypto.bip39.JavaxPBKDF2WithHmacSHA512 import io.github.novacrypto.bip39.JavaxPBKDF2WithHmacSHA512
@ -21,6 +24,7 @@ import io.github.novacrypto.bip39.wordlists.English
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.calyxos.backup.storage.api.StorageBackup
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.ArrayList import java.util.ArrayList
@ -28,11 +32,15 @@ import java.util.ArrayList
internal const val WORD_NUM = 12 internal const val WORD_NUM = 12
internal const val WORD_LIST_SIZE = 2048 internal const val WORD_LIST_SIZE = 2048
class RecoveryCodeViewModel( private val TAG = RecoveryCodeViewModel::class.java.simpleName
internal class RecoveryCodeViewModel(
app: App, app: App,
private val crypto: Crypto, private val crypto: Crypto,
private val keyManager: KeyManager, private val keyManager: KeyManager,
private val backupPlugin: BackupPlugin private val backupManager: IBackupManager,
private val backupCoordinator: BackupCoordinator,
private val storageBackup: StorageBackup
) : AndroidViewModel(app) { ) : AndroidViewModel(app) {
internal val wordList: List<CharSequence> by lazy { internal val wordList: List<CharSequence> by lazy {
@ -79,12 +87,32 @@ class RecoveryCodeViewModel(
} }
} }
fun deleteAllBackup() { /**
* Deletes all storage backups for current user and clears the storage backup cache.
* Also starts a new app data restore set and initializes it.
*
* The reason is that old backups won't be readable anymore with the new key.
* We can't delete other backups safely, because we can't be sure
* that they don't belong to a different device or user.
*/
fun reinitializeBackupLocation() {
Log.d(TAG, "Re-initializing backup location...")
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
// remove old storage snapshots and clear cache
storageBackup.deleteAllSnapshots()
storageBackup.clearCache()
try { try {
backupPlugin.deleteAllBackups() // will also generate a new backup token for the new restore set
backupCoordinator.startNewRestoreSet()
// initialize the new location
backupManager.initializeTransportsForUser(
UserHandle.myUserId(),
arrayOf(TRANSPORT_ID),
null
)
} catch (e: IOException) { } catch (e: IOException) {
Log.e("RecoveryCodeViewModel", "Error deleting backups", e) Log.e(TAG, "Error starting new RestoreSet", e)
} }
} }
} }