From 09b40cdec7d096afac11d42d194934fa7c011ab1 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 30 Mar 2020 00:45:41 -0600 Subject: [PATCH] BiometricAuthenticator: rework logic and bugs Otherwise there's a frameworks bug that causes the fragment's activity to become null. Signed-off-by: Jason A. Donenfeld --- .../android/fragment/TunnelEditorFragment.kt | 15 +++++--- .../preference/ZipExporterPreference.kt | 3 +- .../android/util/BiometricAuthenticator.kt | 36 ++++++++++++++----- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt index 1b8af50e..1c0cf2bd 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt +++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt @@ -4,6 +4,7 @@ */ package com.wireguard.android.fragment +import android.app.Activity import android.content.Context import android.os.Bundle import android.text.InputType @@ -83,7 +84,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { } override fun onDestroyView() { - requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) binding = null super.onDestroyView() } @@ -106,7 +107,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { InputMethodManager.HIDE_NOT_ALWAYS) } // Tell the activity to finish itself or go back to the detail view. - requireActivity().runOnUiThread { + activity.runOnUiThread { // TODO(smaeul): Remove this hack when fixing the Config ViewModel // The selected tunnel has to actually change, but we have to remember this one. val savedTunnel = tunnel @@ -228,13 +229,17 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { super.onViewStateRestored(savedInstanceState) } + private var showingAuthenticator = false + fun onKeyClick(view: View) = onKeyFocusChange(view, true) fun onKeyFocusChange(view: View, isFocused: Boolean) { - if (!isFocused) return + if (!isFocused || showingAuthenticator) return val edit = view as? EditText ?: return if (!haveShownKeys && edit.text.isNotEmpty()) { - BiometricAuthenticator.authenticate(R.string.biometric_prompt_private_key_title, requireActivity()) { + showingAuthenticator = true + BiometricAuthenticator.authenticate(R.string.biometric_prompt_private_key_title, this) { + showingAuthenticator = false when (it) { is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { haveShownKeys = true @@ -255,7 +260,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { } private fun showPrivateKey(edit: EditText) { - requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE) edit.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD } diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt index c83c1cca..8ddce2b1 100644 --- a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt +++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt @@ -83,7 +83,8 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference override fun onClick() { val prefActivity = FragmentUtils.getPrefActivity(this) - BiometricAuthenticator.authenticate(R.string.biometric_prompt_zip_exporter_title, prefActivity) { + val fragment = prefActivity.supportFragmentManager.fragments.first() + BiometricAuthenticator.authenticate(R.string.biometric_prompt_zip_exporter_title, fragment) { when (it) { // When we have successful authentication, or when there is no biometric hardware available. is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { diff --git a/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt b/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt index cf81f768..aed8a4f2 100644 --- a/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt +++ b/ui/src/main/java/com/wireguard/android/util/BiometricAuthenticator.kt @@ -5,15 +5,21 @@ package com.wireguard.android.util +import android.annotation.SuppressLint +import android.app.KeyguardManager +import android.content.Context +import android.os.Build import android.os.Handler import android.util.Log import androidx.annotation.StringRes import androidx.biometric.BiometricConstants import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt -import androidx.fragment.app.FragmentActivity +import androidx.core.content.getSystemService +import androidx.fragment.app.Fragment import com.wireguard.android.R + object BiometricAuthenticator { private const val TAG = "WireGuard/BiometricAuthenticator" private val handler = Handler() @@ -25,12 +31,25 @@ object BiometricAuthenticator { object Cancelled : Result() } + @SuppressLint("PrivateApi") + private fun isPinEnabled(context: Context): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + return context.getSystemService()!!.isDeviceSecure + return try { + val lockUtilsClass = Class.forName("com.android.internal.widget.LockPatternUtils") + val lockUtils = lockUtilsClass.getConstructor(Context::class.java).newInstance(context) + val method = lockUtilsClass.getMethod("isLockScreenDisabled") + !(method.invoke(lockUtils) as Boolean) + } catch (e: Exception) { + false + } + } + fun authenticate( @StringRes dialogTitleRes: Int, - fragmentActivity: FragmentActivity, + fragment: Fragment, callback: (Result) -> Unit ) { - val biometricManager = BiometricManager.from(fragmentActivity) val authCallback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) @@ -44,13 +63,13 @@ object BiometricAuthenticator { BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> { Result.HardwareUnavailableOrDisabled } - else -> Result.Failure(errorCode, fragmentActivity.getString(R.string.biometric_auth_error_reason, errString)) + else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString)) }) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() - callback(Result.Failure(null, fragmentActivity.getString(R.string.biometric_auth_error))) + callback(Result.Failure(null, fragment.getString(R.string.biometric_auth_error))) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { @@ -58,13 +77,12 @@ object BiometricAuthenticator { callback(Result.Success(result.cryptoObject)) } } - val biometricPrompt = BiometricPrompt(fragmentActivity, { handler.post(it) }, authCallback) + val biometricPrompt = BiometricPrompt(fragment, { handler.post(it) }, authCallback) val promptInfo = BiometricPrompt.PromptInfo.Builder() - .setTitle(fragmentActivity.getString(dialogTitleRes)) + .setTitle(fragment.getString(dialogTitleRes)) .setDeviceCredentialAllowed(true) .build() - - if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { + if (BiometricManager.from(fragment.requireContext()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isPinEnabled(fragment.requireContext())) { biometricPrompt.authenticate(promptInfo) } else { callback(Result.HardwareUnavailableOrDisabled)