diff --git a/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt b/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt index 7db8cd480..b59021999 100644 --- a/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt @@ -8,10 +8,10 @@ import android.content.Intent import android.os.Bundle import android.os.Handler import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.zeapo.pwdstore.crypto.PgpActivity -import com.zeapo.pwdstore.utils.auth.AuthenticationResult -import com.zeapo.pwdstore.utils.auth.Authenticator +import com.zeapo.pwdstore.utils.BiometricAuthenticator class LaunchActivity : AppCompatActivity() { @@ -19,18 +19,20 @@ class LaunchActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val prefs = PreferenceManager.getDefaultSharedPreferences(this) if (prefs.getBoolean("biometric_auth", false)) { - Authenticator(this) { + BiometricAuthenticator.authenticate(this) { when (it) { - is AuthenticationResult.Success -> { + is BiometricAuthenticator.Result.Success -> { startTargetActivity(false) } - is AuthenticationResult.UnrecoverableError -> { + is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { + prefs.edit { remove("biometric_auth") } + startTargetActivity(false) + } + is BiometricAuthenticator.Result.Failure, BiometricAuthenticator.Result.Cancelled -> { finish() } - else -> { - } } - }.authenticate() + } } else { startTargetActivity(true) } diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 32d1e87a5..a9629c6c1 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -46,9 +46,8 @@ import com.zeapo.pwdstore.git.GitServerConfigActivity import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity +import com.zeapo.pwdstore.utils.BiometricAuthenticator import com.zeapo.pwdstore.utils.PasswordRepository -import com.zeapo.pwdstore.utils.auth.AuthenticationResult -import com.zeapo.pwdstore.utils.auth.Authenticator import com.zeapo.pwdstore.utils.autofillManager import com.zeapo.pwdstore.utils.getEncryptedPrefs import java.io.File @@ -297,9 +296,9 @@ class UserPreference : AppCompatActivity() { isEnabled = false sharedPreferences.edit { val checked = isChecked - Authenticator(requireActivity()) { result -> + BiometricAuthenticator.authenticate(requireActivity()) { result -> when (result) { - is AuthenticationResult.Success -> { + is BiometricAuthenticator.Result.Success -> { // Apply the changes putBoolean("biometric_auth", checked) isEnabled = true @@ -312,7 +311,7 @@ class UserPreference : AppCompatActivity() { isEnabled = true } } - }.authenticate() + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { requireContext().getSystemService()?.apply { removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt new file mode 100644 index 000000000..36d1f8f4b --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt @@ -0,0 +1,74 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.utils + +import android.app.KeyguardManager +import android.os.Handler +import androidx.annotation.StringRes +import androidx.biometric.BiometricConstants +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.core.content.getSystemService +import androidx.fragment.app.FragmentActivity +import com.github.ajalt.timberkt.Timber.tag +import com.github.ajalt.timberkt.d +import com.zeapo.pwdstore.R + +object BiometricAuthenticator { + private const val TAG = "BiometricAuthenticator" + private val handler = Handler() + + sealed class Result { + data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result() + data class Failure(val code: Int?, val message: CharSequence) : Result() + object HardwareUnavailableOrDisabled : Result() + object Cancelled : Result() + } + + fun authenticate( + activity: FragmentActivity, + @StringRes dialogTitleRes: Int = R.string.biometric_prompt_title, + callback: (Result) -> Unit + ) { + val authCallback = object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + tag(TAG).d { "BiometricAuthentication error: errorCode=$errorCode, msg=$errString" } + callback(when (errorCode) { + BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED, + BiometricConstants.ERROR_NEGATIVE_BUTTON -> { + Result.Cancelled + } + BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE, + BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> { + Result.HardwareUnavailableOrDisabled + } + else -> Result.Failure(errorCode, activity.getString(R.string.biometric_auth_error_reason, errString)) + }) + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + callback(Result.Failure(null, activity.getString(R.string.biometric_auth_error))) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + callback(Result.Success(result.cryptoObject)) + } + } + val biometricPrompt = BiometricPrompt(activity, { handler.post(it) }, authCallback) + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(activity.getString(dialogTitleRes)) + .setDeviceCredentialAllowed(true) + .build() + if (BiometricManager.from(activity).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || + activity.getSystemService()?.isDeviceSecure == true) { + biometricPrompt.authenticate(promptInfo) + } else { + callback(Result.HardwareUnavailableOrDisabled) + } + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt b/app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt deleted file mode 100644 index 6bde6360f..000000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils.auth - -import androidx.biometric.BiometricPrompt - -internal sealed class AuthenticationResult { - internal data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : - AuthenticationResult() - - internal data class RecoverableError(val code: Int, val message: CharSequence) : - AuthenticationResult() - - internal data class UnrecoverableError(val code: Int, val message: CharSequence) : - AuthenticationResult() - - internal object Failure : AuthenticationResult() - internal object Cancelled : AuthenticationResult() -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt deleted file mode 100644 index 34fc94555..000000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils.auth - -import android.os.Handler -import androidx.biometric.BiometricManager -import androidx.biometric.BiometricPrompt -import androidx.fragment.app.FragmentActivity -import com.github.ajalt.timberkt.Timber.tag -import com.github.ajalt.timberkt.d -import com.zeapo.pwdstore.R - -internal class Authenticator( - private val fragmentActivity: FragmentActivity, - private val callback: (AuthenticationResult) -> Unit -) { - private val handler = Handler() - private val biometricManager = BiometricManager.from(fragmentActivity) - - private val authCallback = object : BiometricPrompt.AuthenticationCallback() { - - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - super.onAuthenticationError(errorCode, errString) - tag(TAG).d { "Error: $errorCode: $errString" } - callback(AuthenticationResult.UnrecoverableError(errorCode, errString)) - } - - override fun onAuthenticationFailed() { - super.onAuthenticationFailed() - tag(TAG).d { "Failed" } - callback(AuthenticationResult.Failure) - } - - override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - tag(TAG).d { "Success" } - callback(AuthenticationResult.Success(result.cryptoObject)) - } - } - - private val biometricPrompt = BiometricPrompt( - fragmentActivity, - { runnable -> handler.post(runnable) }, - authCallback - ) - - private val promptInfo = BiometricPrompt.PromptInfo.Builder() - .setTitle(fragmentActivity.getString(R.string.biometric_prompt_title)) - .setDeviceCredentialAllowed(true) - .build() - - fun authenticate() { - if (biometricManager.canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) { - callback(AuthenticationResult.UnrecoverableError( - 0, - fragmentActivity.getString(R.string.biometric_prompt_no_hardware) - )) - } else { - biometricPrompt.authenticate(promptInfo) - } - } - - companion object { - private const val TAG = "Authenticator" - } -} diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 07c4dca7d..9e192c1d6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -296,9 +296,6 @@ Вы выбрали корень вашей sd-карты для хранения. Это очень опасно и вы потеряете ваши данные, поскольку они будут в конечном итоге удалены Прервать и записать изменения Запрос биометрии - Повторить - Аутентификация отменена - Биометрические сенсоры не обнаружены Включить биометрическую аутентификацию Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения Сенсор отпечатка пальца не доступен или отсутствует diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33c0b1038..37dbfef64 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,9 +326,8 @@ You have selected the root of your sdcard for the store. This is extremely dangerous and you will lose your data as its content will, eventually, be deleted Abort and Push Biometric Prompt - Retry - Authentication canceled - No Biometric hardware was found + Authentication failure + Authentication failure: %s Enable biometric authentication When enabled, Password Store will prompt you for your fingerprint when launching the app Fingerprint hardware not accessible or missing