diff --git a/README.md b/README.md index 49a97dcf..f9f6aa44 100644 --- a/README.md +++ b/README.md @@ -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.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.USE_BIOMETRIC` to authenticate saving a new recovery code ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c8a8da5e..d3fa7189 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,9 @@ android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" /> + + + = 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) + } } else { viewModel.verifyExistingCode(input) } } + @RequiresApi(30) + private fun storeNewCodeAfterAuth(input: List) { + 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): Boolean { for (i in input.indices) { if (input[i].isNotEmpty()) continue diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7682402e..046ee7ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -94,6 +94,8 @@ Wait one second… 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? New recovery code has been created successfully + Confirm it\'s really you + This ensures that nobody else can get your data when finding your phone unlocked. Backup notification