/* * Copyright © 2020 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.BiometricConstants 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) { 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, 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) } } }