Ask for system authentication before storing a new recovery code

This will help to prevent data extraction via seedvault when somebody gets hold of an unlocked phone. However, it will not help against someone able to force you to provide fingerprints or other device secrets.
This commit is contained in:
Torsten Grote 2021-08-11 11:54:28 +02:00 committed by Chirayu Desai
parent e0f728205d
commit 6e838978e3
4 changed files with 39 additions and 1 deletions

View file

@ -42,6 +42,7 @@ It uses the same internal APIs as `adb backup` which is deprecated and thus need
* `android.permission.ACCESS_MEDIA_LOCATION` to backup original media files e.g. without stripped EXIF metadata. * `android.permission.ACCESS_MEDIA_LOCATION` to backup original media files e.g. without stripped EXIF metadata.
* `android.permission.FOREGROUND_SERVICE` to do periodic storage backups without interruption. * `android.permission.FOREGROUND_SERVICE` to do periodic storage backups without interruption.
* `android.permission.MANAGE_DOCUMENTS` to retrieve the available storage roots (optional) for better UX. * `android.permission.MANAGE_DOCUMENTS` to retrieve the available storage roots (optional) for better UX.
* `android.permission.USE_BIOMETRIC` to authenticate saving a new recovery code
## Contributing ## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault. Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault.

View file

@ -47,6 +47,9 @@
android:name="android.permission.QUERY_ALL_PACKAGES" android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" /> tools:ignore="QueryAllPackagesPermission" />
<!-- Used to authenticate saving a new recovery code -->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application <application
android:name=".App" android:name=".App"
android:allowBackup="false" android:allowBackup="false"

View file

@ -1,8 +1,14 @@
package com.stevesoltys.seedvault.ui.recoverycode package com.stevesoltys.seedvault.ui.recoverycode
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.app.KeyguardManager
import android.content.Intent import android.content.Intent
import android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG
import android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import android.hardware.biometrics.BiometricPrompt
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle import android.os.Bundle
import android.os.CancellationSignal
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
@ -16,8 +22,10 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat.getMainExecutor
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.Mnemonics.ChecksumException import cash.z.ecc.android.bip39.Mnemonics.ChecksumException
@ -143,12 +151,36 @@ class RecoveryCodeInputFragment : Fragment() {
return return
} }
if (forStoringNewCode) { if (forStoringNewCode) {
val keyguardManager = requireContext().getSystemService(KeyguardManager::class.java)
if (SDK_INT >= 30 && keyguardManager.isDeviceSecure) {
// if we have a lock-screen secret, we can ask for it before storing the code
storeNewCodeAfterAuth(input)
} else {
// user doesn't seem to care about security, store key without auth
viewModel.storeNewCode(input) viewModel.storeNewCode(input)
}
} else { } else {
viewModel.verifyExistingCode(input) viewModel.verifyExistingCode(input)
} }
} }
@RequiresApi(30)
private fun storeNewCodeAfterAuth(input: List<CharSequence>) {
val biometricPrompt = BiometricPrompt.Builder(context)
.setConfirmationRequired(true)
.setTitle(getString(R.string.recovery_code_auth_title))
.setDescription(getString(R.string.recovery_code_auth_description))
// BIOMETRIC_STRONG could be made optional in the future, setting guarded by credentials
.setAllowedAuthenticators(DEVICE_CREDENTIAL or BIOMETRIC_STRONG)
.build()
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
viewModel.storeNewCode(input)
}
}
biometricPrompt.authenticate(CancellationSignal(), getMainExecutor(context), callback)
}
private fun allFilledOut(input: List<CharSequence>): Boolean { private fun allFilledOut(input: List<CharSequence>): Boolean {
for (i in input.indices) { for (i in input.indices) {
if (input[i].isNotEmpty()) continue if (input[i].isNotEmpty()) continue

View file

@ -94,6 +94,8 @@
<string name="recovery_code_verification_new_dialog_title">Wait one second…</string> <string name="recovery_code_verification_new_dialog_title">Wait one second…</string>
<string name="recovery_code_verification_new_dialog_message">Generating a new code will make your existing backups inaccessible. We\'ll try to delete them if possible.\n\nAre you sure you want to do this?</string> <string name="recovery_code_verification_new_dialog_message">Generating a new code will make your existing backups inaccessible. We\'ll try to delete them if possible.\n\nAre you sure you want to do this?</string>
<string name="recovery_code_recreated">New recovery code has been created successfully</string> <string name="recovery_code_recreated">New recovery code has been created successfully</string>
<string name="recovery_code_auth_title">Confirm it\'s really you</string>
<string name="recovery_code_auth_description">This ensures that nobody else can get your data when finding your phone unlocked.</string>
<!-- Notification --> <!-- Notification -->
<string name="notification_channel_title">Backup notification</string> <string name="notification_channel_title">Backup notification</string>