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()) }
|
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(), get(), get()) }
|
viewModel { RecoveryCodeViewModel(this@App, get(), 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()) }
|
||||||
|
|
|
@ -126,6 +126,8 @@ internal class RestoreViewModel(
|
||||||
|
|
||||||
@Throws(RemoteException::class)
|
@Throws(RemoteException::class)
|
||||||
private fun getOrStartSession(): IRestoreSession {
|
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
|
val session = this.session
|
||||||
?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID)
|
?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID)
|
||||||
?: throw RemoteException("beginRestoreSessionForUser returned null")
|
?: throw RemoteException("beginRestoreSessionForUser returned null")
|
||||||
|
|
|
@ -54,12 +54,19 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
backup = findPreference("backup")!!
|
backup = findPreference("backup")!!
|
||||||
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||||
val enabled = newValue as Boolean
|
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 {
|
try {
|
||||||
backupManager.isBackupEnabled = enabled
|
backupManager.isBackupEnabled = enabled
|
||||||
if (enabled) viewModel.enableCallLogBackup()
|
if (enabled) viewModel.enableCallLogBackup()
|
||||||
return@OnPreferenceChangeListener true
|
return@OnPreferenceChangeListener true
|
||||||
} catch (e: RemoteException) {
|
} catch (e: RemoteException) {
|
||||||
e.printStackTrace()
|
Log.e(TAG, "Error setting backup enabled to $enabled", e)
|
||||||
backup.isChecked = !enabled
|
backup.isChecked = !enabled
|
||||||
return@OnPreferenceChangeListener false
|
return@OnPreferenceChangeListener false
|
||||||
}
|
}
|
||||||
|
@ -222,12 +229,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
.setTitle(R.string.settings_backup_storage_dialog_title)
|
.setTitle(R.string.settings_backup_storage_dialog_title)
|
||||||
.setMessage(R.string.settings_backup_storage_dialog_message)
|
.setMessage(R.string.settings_backup_storage_dialog_message)
|
||||||
.setPositiveButton(R.string.settings_backup_storage_dialog_ok) { dialog, _ ->
|
.setPositiveButton(R.string.settings_backup_storage_dialog_ok) { dialog, _ ->
|
||||||
if (viewModel.hasMainKey()) {
|
|
||||||
viewModel.enableStorageBackup()
|
viewModel.enableStorageBackup()
|
||||||
backupStorage.isChecked = true
|
backupStorage.isChecked = true
|
||||||
} else {
|
|
||||||
showCodeVerificationNeededDialog()
|
|
||||||
}
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.settings_backup_apk_dialog_cancel) { dialog, _ ->
|
.setNegativeButton(R.string.settings_backup_apk_dialog_cancel) { dialog, _ ->
|
||||||
|
@ -236,12 +239,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showCodeVerificationNeededDialog() {
|
private fun showCodeRegenerationNeededDialog() {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setIcon(R.drawable.ic_vpn_key)
|
.setIcon(R.drawable.ic_vpn_key)
|
||||||
.setTitle(R.string.settings_backup_storage_code_dialog_title)
|
.setTitle(R.string.settings_backup_new_code_dialog_title)
|
||||||
.setMessage(R.string.settings_backup_storage_code_dialog_message)
|
.setMessage(R.string.settings_backup_new_code_dialog_message)
|
||||||
.setPositiveButton(R.string.settings_backup_storage_code_dialog_ok) { dialog, _ ->
|
.setPositiveButton(R.string.settings_backup_new_code_code_dialog_ok) { dialog, _ ->
|
||||||
val callback = (requireActivity() as OnPreferenceStartFragmentCallback)
|
val callback = (requireActivity() as OnPreferenceStartFragmentCallback)
|
||||||
callback.onPreferenceStartFragment(this, backupRecoveryCode)
|
callback.onPreferenceStartFragment(this, backupRecoveryCode)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.os.RemoteException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.BackupMonitor
|
import com.stevesoltys.seedvault.BackupMonitor
|
||||||
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.PackageService
|
import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver
|
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver
|
||||||
|
@ -27,6 +28,8 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
|
||||||
|
|
||||||
private var transport: ConfigurableBackupTransport? = null
|
private var transport: ConfigurableBackupTransport? = null
|
||||||
|
|
||||||
|
private val keyManager: KeyManager by inject()
|
||||||
|
private val backupManager: IBackupManager by inject()
|
||||||
private val notificationManager: BackupNotificationManager by inject()
|
private val notificationManager: BackupNotificationManager by inject()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
@ -35,7 +38,13 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
|
||||||
Log.d(TAG, "Service created.")
|
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()")
|
val transport = this.transport ?: throw IllegalStateException("no transport in onBind()")
|
||||||
return transport.binder.apply {
|
return transport.binder.apply {
|
||||||
Log.d(TAG, "Transport bound.")
|
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_HIGH
|
||||||
import android.app.NotificationManager.IMPORTANCE_LOW
|
import android.app.NotificationManager.IMPORTANCE_LOW
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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_ERROR = 2
|
||||||
private const val NOTIFICATION_ID_RESTORE_ERROR = 3
|
private const val NOTIFICATION_ID_RESTORE_ERROR = 3
|
||||||
private const val NOTIFICATION_ID_BACKGROUND = 4
|
private const val NOTIFICATION_ID_BACKGROUND = 4
|
||||||
|
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 5
|
||||||
|
|
||||||
private val TAG = BackupNotificationManager::class.java.simpleName
|
private val TAG = BackupNotificationManager::class.java.simpleName
|
||||||
|
|
||||||
|
@ -269,4 +271,28 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
nm.cancel(NOTIFICATION_ID_RESTORE_ERROR)
|
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.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 com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -33,6 +34,7 @@ internal class RecoveryCodeViewModel(
|
||||||
private val keyManager: KeyManager,
|
private val keyManager: KeyManager,
|
||||||
private val backupManager: IBackupManager,
|
private val backupManager: IBackupManager,
|
||||||
private val backupCoordinator: BackupCoordinator,
|
private val backupCoordinator: BackupCoordinator,
|
||||||
|
private val notificationManager: BackupNotificationManager,
|
||||||
private val storageBackup: StorageBackup
|
private val storageBackup: StorageBackup
|
||||||
) : AndroidViewModel(app) {
|
) : AndroidViewModel(app) {
|
||||||
|
|
||||||
|
@ -77,6 +79,7 @@ internal class RecoveryCodeViewModel(
|
||||||
// store main key at this opportunity if it is still missing
|
// store main key at this opportunity if it is still missing
|
||||||
if (verified && !keyManager.hasMainKey()) keyManager.storeMainKey(seed)
|
if (verified && !keyManager.hasMainKey()) keyManager.storeMainKey(seed)
|
||||||
mExistingCodeChecked.setEvent(verified)
|
mExistingCodeChecked.setEvent(verified)
|
||||||
|
if (verified) notificationManager.onNoMainKeyErrorFixed()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,6 +91,7 @@ internal class RecoveryCodeViewModel(
|
||||||
keyManager.storeBackupKey(seed)
|
keyManager.storeBackupKey(seed)
|
||||||
keyManager.storeMainKey(seed)
|
keyManager.storeMainKey(seed)
|
||||||
mRecoveryCodeSaved.setEvent(true)
|
mRecoveryCodeSaved.setEvent(true)
|
||||||
|
notificationManager.onNoMainKeyErrorFixed()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,7 +113,7 @@ internal class RecoveryCodeViewModel(
|
||||||
backupCoordinator.startNewRestoreSet()
|
backupCoordinator.startNewRestoreSet()
|
||||||
|
|
||||||
// initialize the new location
|
// initialize the new location
|
||||||
backupManager.initializeTransportsForUser(
|
if (backupManager.isBackupEnabled) backupManager.initializeTransportsForUser(
|
||||||
UserHandle.myUserId(),
|
UserHandle.myUserId(),
|
||||||
arrayOf(TRANSPORT_ID),
|
arrayOf(TRANSPORT_ID),
|
||||||
null
|
null
|
||||||
|
|
|
@ -41,13 +41,15 @@ internal class BackupStorageViewModel(
|
||||||
// will also generate a new backup token for the new restore set
|
// will also generate a new backup token for the new restore set
|
||||||
backupCoordinator.startNewRestoreSet()
|
backupCoordinator.startNewRestoreSet()
|
||||||
|
|
||||||
// initialize the new location
|
// initialize the new location (if backups are enabled)
|
||||||
backupManager.initializeTransportsForUser(
|
if (backupManager.isBackupEnabled) backupManager.initializeTransportsForUser(
|
||||||
UserHandle.myUserId(),
|
UserHandle.myUserId(),
|
||||||
arrayOf(TRANSPORT_ID),
|
arrayOf(TRANSPORT_ID),
|
||||||
// if storage is on USB and this is not SetupWizard, do a backup right away
|
// if storage is on USB and this is not SetupWizard, do a backup right away
|
||||||
InitializationObserver(isUsb && !isSetupWizard)
|
InitializationObserver(isUsb && !isSetupWizard)
|
||||||
)
|
) else {
|
||||||
|
InitializationObserver(false).backupFinished(0)
|
||||||
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "Error starting new RestoreSet", e)
|
Log.e(TAG, "Error starting new RestoreSet", e)
|
||||||
onInitializationError()
|
onInitializationError()
|
||||||
|
|
|
@ -38,9 +38,9 @@
|
||||||
<string name="settings_backup_storage_dialog_title">Experimental feature</string>
|
<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_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_dialog_ok">Enable anyway</string>
|
||||||
<string name="settings_backup_storage_code_dialog_title">Recovery code verification required</string>
|
<string name="settings_backup_new_code_dialog_title">New recovery code 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_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_storage_code_dialog_ok">Verify code</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_title">Expert settings</string>
|
||||||
<string name="settings_expert_quota_title">Unlimited app quota</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_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_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 -->
|
<!-- App Backup and Restore State -->
|
||||||
|
|
||||||
<string name="backup_section_system">System apps</string>
|
<string name="backup_section_system">System apps</string>
|
||||||
|
|
Loading…
Reference in a new issue