81 lines
3.3 KiB
Kotlin
81 lines
3.3 KiB
Kotlin
/*
|
|
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package com.wireguard.android.util
|
|
|
|
import android.os.Handler
|
|
import android.os.Looper
|
|
import android.util.Log
|
|
import androidx.annotation.StringRes
|
|
import androidx.biometric.BiometricManager
|
|
import androidx.biometric.BiometricManager.Authenticators
|
|
import androidx.biometric.BiometricPrompt
|
|
import androidx.fragment.app.Fragment
|
|
import com.wireguard.android.R
|
|
|
|
|
|
object BiometricAuthenticator {
|
|
private const val TAG = "WireGuard/BiometricAuthenticator"
|
|
|
|
// Not all devices support strong biometric auth so we're allowing both device credentials as
|
|
// well as weak biometrics.
|
|
private const val allowedAuthenticators = Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK
|
|
|
|
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(
|
|
@StringRes dialogTitleRes: Int,
|
|
fragment: Fragment,
|
|
callback: (Result) -> Unit
|
|
) {
|
|
val authCallback = object : BiometricPrompt.AuthenticationCallback() {
|
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
|
super.onAuthenticationError(errorCode, errString)
|
|
Log.d(TAG, "BiometricAuthentication error: errorCode=$errorCode, msg=$errString")
|
|
callback(
|
|
when (errorCode) {
|
|
BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED,
|
|
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
|
|
Result.Cancelled
|
|
}
|
|
|
|
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE,
|
|
BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
|
Result.HardwareUnavailableOrDisabled
|
|
}
|
|
|
|
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
|
|
}
|
|
)
|
|
}
|
|
|
|
override fun onAuthenticationFailed() {
|
|
super.onAuthenticationFailed()
|
|
callback(Result.Failure(null, fragment.getString(R.string.biometric_auth_error)))
|
|
}
|
|
|
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
|
super.onAuthenticationSucceeded(result)
|
|
callback(Result.Success(result.cryptoObject))
|
|
}
|
|
}
|
|
val biometricPrompt = BiometricPrompt(fragment, { Handler(Looper.getMainLooper()).post(it) }, authCallback)
|
|
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
|
.setTitle(fragment.getString(dialogTitleRes))
|
|
.setAllowedAuthenticators(allowedAuthenticators)
|
|
.build()
|
|
if (BiometricManager.from(fragment.requireContext()).canAuthenticate(allowedAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS) {
|
|
biometricPrompt.authenticate(promptInfo)
|
|
} else {
|
|
callback(Result.HardwareUnavailableOrDisabled)
|
|
}
|
|
}
|
|
}
|