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 <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2020-03-30 00:45:41 -06:00
parent d2721f2d7d
commit 09b40cdec7
3 changed files with 39 additions and 15 deletions

View File

@ -4,6 +4,7 @@
*/ */
package com.wireguard.android.fragment package com.wireguard.android.fragment
import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
@ -83,7 +84,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
} }
override fun onDestroyView() { override fun onDestroyView() {
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
binding = null binding = null
super.onDestroyView() super.onDestroyView()
} }
@ -106,7 +107,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
InputMethodManager.HIDE_NOT_ALWAYS) InputMethodManager.HIDE_NOT_ALWAYS)
} }
// Tell the activity to finish itself or go back to the detail view. // 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 // TODO(smaeul): Remove this hack when fixing the Config ViewModel
// The selected tunnel has to actually change, but we have to remember this one. // The selected tunnel has to actually change, but we have to remember this one.
val savedTunnel = tunnel val savedTunnel = tunnel
@ -228,13 +229,17 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
super.onViewStateRestored(savedInstanceState) super.onViewStateRestored(savedInstanceState)
} }
private var showingAuthenticator = false
fun onKeyClick(view: View) = onKeyFocusChange(view, true) fun onKeyClick(view: View) = onKeyFocusChange(view, true)
fun onKeyFocusChange(view: View, isFocused: Boolean) { fun onKeyFocusChange(view: View, isFocused: Boolean) {
if (!isFocused) return if (!isFocused || showingAuthenticator) return
val edit = view as? EditText ?: return val edit = view as? EditText ?: return
if (!haveShownKeys && edit.text.isNotEmpty()) { 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) { when (it) {
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
haveShownKeys = true haveShownKeys = true
@ -255,7 +260,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
} }
private fun showPrivateKey(edit: EditText) { 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 edit.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
} }

View File

@ -83,7 +83,8 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
override fun onClick() { override fun onClick() {
val prefActivity = FragmentUtils.getPrefActivity(this) 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 (it) {
// When we have successful authentication, or when there is no biometric hardware available. // When we have successful authentication, or when there is no biometric hardware available.
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {

View File

@ -5,15 +5,21 @@
package com.wireguard.android.util 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.os.Handler
import android.util.Log import android.util.Log
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.biometric.BiometricConstants import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import com.wireguard.android.R import com.wireguard.android.R
object BiometricAuthenticator { object BiometricAuthenticator {
private const val TAG = "WireGuard/BiometricAuthenticator" private const val TAG = "WireGuard/BiometricAuthenticator"
private val handler = Handler() private val handler = Handler()
@ -25,12 +31,25 @@ object BiometricAuthenticator {
object Cancelled : Result() object Cancelled : Result()
} }
@SuppressLint("PrivateApi")
private fun isPinEnabled(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.getSystemService<KeyguardManager>()!!.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( fun authenticate(
@StringRes dialogTitleRes: Int, @StringRes dialogTitleRes: Int,
fragmentActivity: FragmentActivity, fragment: Fragment,
callback: (Result) -> Unit callback: (Result) -> Unit
) { ) {
val biometricManager = BiometricManager.from(fragmentActivity)
val authCallback = object : BiometricPrompt.AuthenticationCallback() { val authCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString) super.onAuthenticationError(errorCode, errString)
@ -44,13 +63,13 @@ object BiometricAuthenticator {
BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> { BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
Result.HardwareUnavailableOrDisabled 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() { override fun onAuthenticationFailed() {
super.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) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
@ -58,13 +77,12 @@ object BiometricAuthenticator {
callback(Result.Success(result.cryptoObject)) 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() val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(fragmentActivity.getString(dialogTitleRes)) .setTitle(fragment.getString(dialogTitleRes))
.setDeviceCredentialAllowed(true) .setDeviceCredentialAllowed(true)
.build() .build()
if (BiometricManager.from(fragment.requireContext()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isPinEnabled(fragment.requireContext())) {
if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
biometricPrompt.authenticate(promptInfo) biometricPrompt.authenticate(promptInfo)
} else { } else {
callback(Result.HardwareUnavailableOrDisabled) callback(Result.HardwareUnavailableOrDisabled)