Ensure that we have the main key for v1 crypto
We ask the user to generate a new key, because actively asking for the old one is training bad security habits, but technically verifying the old key will also work.
This commit is contained in:
parent
7dceb98670
commit
0f241f7d25
8 changed files with 69 additions and 20 deletions
|
@ -46,7 +46,7 @@ open class App : Application() {
|
|||
factory { AppListRetriever(this@App, get(), get(), get()) }
|
||||
|
||||
viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get()) }
|
||||
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) }
|
||||
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
|
||||
viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
|
|
|
@ -126,6 +126,8 @@ internal class RestoreViewModel(
|
|||
|
||||
@Throws(RemoteException::class)
|
||||
private fun getOrStartSession(): IRestoreSession {
|
||||
// TODO consider not using the BackupManager for this, but our own API directly
|
||||
// this is less error-prone (hanging sessions) and can provide more data
|
||||
val session = this.session
|
||||
?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID)
|
||||
?: throw RemoteException("beginRestoreSessionForUser returned null")
|
||||
|
|
|
@ -54,12 +54,19 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
backup = findPreference("backup")!!
|
||||
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||
val enabled = newValue as Boolean
|
||||
// don't enable if we don't have the main key
|
||||
if (enabled && !viewModel.hasMainKey()) {
|
||||
showCodeRegenerationNeededDialog()
|
||||
backup.isChecked = false
|
||||
return@OnPreferenceChangeListener false
|
||||
}
|
||||
// main key is present, so enable or disable normally
|
||||
try {
|
||||
backupManager.isBackupEnabled = enabled
|
||||
if (enabled) viewModel.enableCallLogBackup()
|
||||
return@OnPreferenceChangeListener true
|
||||
} catch (e: RemoteException) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "Error setting backup enabled to $enabled", e)
|
||||
backup.isChecked = !enabled
|
||||
return@OnPreferenceChangeListener false
|
||||
}
|
||||
|
@ -222,12 +229,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
.setTitle(R.string.settings_backup_storage_dialog_title)
|
||||
.setMessage(R.string.settings_backup_storage_dialog_message)
|
||||
.setPositiveButton(R.string.settings_backup_storage_dialog_ok) { dialog, _ ->
|
||||
if (viewModel.hasMainKey()) {
|
||||
viewModel.enableStorageBackup()
|
||||
backupStorage.isChecked = true
|
||||
} else {
|
||||
showCodeVerificationNeededDialog()
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.settings_backup_apk_dialog_cancel) { dialog, _ ->
|
||||
|
@ -236,12 +239,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
.show()
|
||||
}
|
||||
|
||||
private fun showCodeVerificationNeededDialog() {
|
||||
private fun showCodeRegenerationNeededDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setIcon(R.drawable.ic_vpn_key)
|
||||
.setTitle(R.string.settings_backup_storage_code_dialog_title)
|
||||
.setMessage(R.string.settings_backup_storage_code_dialog_message)
|
||||
.setPositiveButton(R.string.settings_backup_storage_code_dialog_ok) { dialog, _ ->
|
||||
.setTitle(R.string.settings_backup_new_code_dialog_title)
|
||||
.setMessage(R.string.settings_backup_new_code_dialog_message)
|
||||
.setPositiveButton(R.string.settings_backup_new_code_code_dialog_ok) { dialog, _ ->
|
||||
val callback = (requireActivity() as OnPreferenceStartFragmentCallback)
|
||||
callback.onPreferenceStartFragment(this, backupRecoveryCode)
|
||||
dialog.dismiss()
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.os.RemoteException
|
|||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.stevesoltys.seedvault.BackupMonitor
|
||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||
import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver
|
||||
|
@ -27,6 +28,8 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
|
|||
|
||||
private var transport: ConfigurableBackupTransport? = null
|
||||
|
||||
private val keyManager: KeyManager by inject()
|
||||
private val backupManager: IBackupManager by inject()
|
||||
private val notificationManager: BackupNotificationManager by inject()
|
||||
|
||||
override fun onCreate() {
|
||||
|
@ -35,7 +38,13 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
|
|||
Log.d(TAG, "Service created.")
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
// refuse to work until we have the main key
|
||||
val noMainKey = keyManager.hasBackupKey() && !keyManager.hasMainKey()
|
||||
if (noMainKey && backupManager.currentTransport == TRANSPORT_ID) {
|
||||
notificationManager.onNoMainKeyError()
|
||||
backupManager.isBackupEnabled = false
|
||||
}
|
||||
val transport = this.transport ?: throw IllegalStateException("no transport in onBind()")
|
||||
return transport.binder.apply {
|
||||
Log.d(TAG, "Transport bound.")
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.app.NotificationManager.IMPORTANCE_DEFAULT
|
|||
import android.app.NotificationManager.IMPORTANCE_HIGH
|
||||
import android.app.NotificationManager.IMPORTANCE_LOW
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -33,6 +34,7 @@ private const val NOTIFICATION_ID_OBSERVER = 1
|
|||
private const val NOTIFICATION_ID_ERROR = 2
|
||||
private const val NOTIFICATION_ID_RESTORE_ERROR = 3
|
||||
private const val NOTIFICATION_ID_BACKGROUND = 4
|
||||
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 5
|
||||
|
||||
private val TAG = BackupNotificationManager::class.java.simpleName
|
||||
|
||||
|
@ -269,4 +271,28 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
nm.cancel(NOTIFICATION_ID_RESTORE_ERROR)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun onNoMainKeyError() {
|
||||
val intent = Intent(context, SettingsActivity::class.java)
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
|
||||
val actionText = context.getString(R.string.notification_error_action)
|
||||
val action = Action(0, actionText, pendingIntent)
|
||||
val notification = Builder(context, CHANNEL_ID_ERROR).apply {
|
||||
setSmallIcon(R.drawable.ic_cloud_error)
|
||||
setContentTitle(context.getString(R.string.notification_error_no_main_key_title))
|
||||
setContentText(context.getString(R.string.notification_error_no_main_key_text))
|
||||
setWhen(System.currentTimeMillis())
|
||||
setOnlyAlertOnce(true)
|
||||
setAutoCancel(false)
|
||||
setOngoing(true)
|
||||
setContentIntent(pendingIntent)
|
||||
mActions = arrayListOf(action)
|
||||
}.build()
|
||||
nm.notify(NOTIFICATION_ID_NO_MAIN_KEY_ERROR, notification)
|
||||
}
|
||||
|
||||
fun onNoMainKeyErrorFixed() {
|
||||
nm.cancel(NOTIFICATION_ID_NO_MAIN_KEY_ERROR)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ 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.MutableLiveEvent
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -33,6 +34,7 @@ internal class RecoveryCodeViewModel(
|
|||
private val keyManager: KeyManager,
|
||||
private val backupManager: IBackupManager,
|
||||
private val backupCoordinator: BackupCoordinator,
|
||||
private val notificationManager: BackupNotificationManager,
|
||||
private val storageBackup: StorageBackup
|
||||
) : AndroidViewModel(app) {
|
||||
|
||||
|
@ -77,6 +79,7 @@ internal class RecoveryCodeViewModel(
|
|||
// store main key at this opportunity if it is still missing
|
||||
if (verified && !keyManager.hasMainKey()) keyManager.storeMainKey(seed)
|
||||
mExistingCodeChecked.setEvent(verified)
|
||||
if (verified) notificationManager.onNoMainKeyErrorFixed()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,6 +91,7 @@ internal class RecoveryCodeViewModel(
|
|||
keyManager.storeBackupKey(seed)
|
||||
keyManager.storeMainKey(seed)
|
||||
mRecoveryCodeSaved.setEvent(true)
|
||||
notificationManager.onNoMainKeyErrorFixed()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,7 +113,7 @@ internal class RecoveryCodeViewModel(
|
|||
backupCoordinator.startNewRestoreSet()
|
||||
|
||||
// initialize the new location
|
||||
backupManager.initializeTransportsForUser(
|
||||
if (backupManager.isBackupEnabled) backupManager.initializeTransportsForUser(
|
||||
UserHandle.myUserId(),
|
||||
arrayOf(TRANSPORT_ID),
|
||||
null
|
||||
|
|
|
@ -41,13 +41,15 @@ internal class BackupStorageViewModel(
|
|||
// will also generate a new backup token for the new restore set
|
||||
backupCoordinator.startNewRestoreSet()
|
||||
|
||||
// initialize the new location
|
||||
backupManager.initializeTransportsForUser(
|
||||
// initialize the new location (if backups are enabled)
|
||||
if (backupManager.isBackupEnabled) backupManager.initializeTransportsForUser(
|
||||
UserHandle.myUserId(),
|
||||
arrayOf(TRANSPORT_ID),
|
||||
// if storage is on USB and this is not SetupWizard, do a backup right away
|
||||
InitializationObserver(isUsb && !isSetupWizard)
|
||||
)
|
||||
) else {
|
||||
InitializationObserver(false).backupFinished(0)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error starting new RestoreSet", e)
|
||||
onInitializationError()
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
<string name="settings_backup_storage_dialog_title">Experimental feature</string>
|
||||
<string name="settings_backup_storage_dialog_message">Backing up files is still experimental and might not work. Do not rely on it for important data.</string>
|
||||
<string name="settings_backup_storage_dialog_ok">Enable anyway</string>
|
||||
<string name="settings_backup_storage_code_dialog_title">Recovery code verification required</string>
|
||||
<string name="settings_backup_storage_code_dialog_message">To enable storage backup, you need to first verify your recovery code or generate a new one.</string>
|
||||
<string name="settings_backup_storage_code_dialog_ok">Verify code</string>
|
||||
<string name="settings_backup_new_code_dialog_title">New recovery code required</string>
|
||||
<string name="settings_backup_new_code_dialog_message">To continue using app backups, you need to generate a new recovery code.\n\nWe are sorry for the inconvenience.</string>
|
||||
<string name="settings_backup_new_code_code_dialog_ok">New code</string>
|
||||
|
||||
<string name="settings_expert_title">Expert settings</string>
|
||||
<string name="settings_expert_quota_title">Unlimited app quota</string>
|
||||
|
@ -120,6 +120,9 @@
|
|||
<string name="notification_restore_error_text">Plug in your %1$s before installing the app to restore its data from backup.</string>
|
||||
<string name="notification_restore_error_action">Uninstall app</string>
|
||||
|
||||
<string name="notification_error_no_main_key_title">Backups disabled</string>
|
||||
<string name="notification_error_no_main_key_text">Generate a new recovery code to complete upgrade and continue to use backups.</string>
|
||||
|
||||
<!-- App Backup and Restore State -->
|
||||
|
||||
<string name="backup_section_system">System apps</string>
|
||||
|
|
Loading…
Reference in a new issue