Skip to content

Commit

Permalink
SDKS-2948 crash fix devicepin
Browse files Browse the repository at this point in the history
  • Loading branch information
jeyanthanperiyasamy committed Jan 25, 2024
1 parent e724054 commit 0ba294b
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import android.content.Context
import android.os.Build
import android.security.keystore.KeyProperties
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback
import org.forgerock.android.auth.callback.Attestation
import org.forgerock.android.auth.callback.DeviceBindingAuthenticationType
import java.security.PrivateKey
Expand All @@ -22,9 +22,9 @@ import java.security.interfaces.RSAPublicKey
* Settings for all the biometric authentication and device credential is configured
*/
open class BiometricAndDeviceCredential : BiometricAuthenticator() {
override fun authenticate(authenticationCallback: BiometricPrompt.AuthenticationCallback,
override fun authenticate(biometricAuthenticationCallback: BiometricAuthenticationCallback,
privateKey: PrivateKey) {
biometricInterface.authenticate(authenticationCallback)
biometricInterface.authenticate(biometricAuthenticationCallback)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.biometric.BiometricPrompt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.forgerock.android.auth.CryptoKey
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback
import java.security.PrivateKey
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
Expand Down Expand Up @@ -50,7 +51,7 @@ abstract class BiometricAuthenticator : CryptoAware, DeviceAuthenticator {
if (privateKey == null) {
continuation.resume(DeviceBindingErrorStatus.ClientNotRegistered())
} else {
val listener = object : BiometricPrompt.AuthenticationCallback() {
val listener = object : BiometricAuthenticationCallback() {

override fun onAuthenticationError(errorCode: Int,
errString: CharSequence) {
Expand Down Expand Up @@ -84,8 +85,8 @@ abstract class BiometricAuthenticator : CryptoAware, DeviceAuthenticator {
}
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
result.cryptoObject?.signature?.let {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
result?.cryptoObject?.signature?.let {
continuation.resume(Success(privateKey, it))
return
}
Expand All @@ -104,9 +105,9 @@ abstract class BiometricAuthenticator : CryptoAware, DeviceAuthenticator {

/**
* Launch the Biometric Prompt.
* @param authenticationCallback [BiometricPrompt.AuthenticationCallback] to handle the result.
* @param biometricAuthenticationCallback [BiometricPrompt.AuthenticationCallback] to handle the result.
* @param privateKey The private key to unlock
*/
abstract fun authenticate(authenticationCallback: BiometricPrompt.AuthenticationCallback,
abstract fun authenticate(biometricAuthenticationCallback: BiometricAuthenticationCallback,
privateKey: PrivateKey)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
*/
package org.forgerock.android.auth.devicebind

import androidx.biometric.BiometricManager.Authenticators.*
import androidx.biometric.BiometricPrompt.AuthenticationCallback
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt.CryptoObject
import androidx.fragment.app.FragmentActivity
import org.forgerock.android.auth.InitProvider
import org.forgerock.android.auth.biometric.AuthenticatorType
import org.forgerock.android.auth.biometric.BiometricAuth
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback
import org.forgerock.android.auth.callback.DeviceBindingAuthenticationType
import java.util.*


/**
Expand All @@ -35,9 +35,10 @@ interface BiometricHandler {

/**
* display biometric prompt for Biometric and device credential
* @param authenticationCallback Result of biometric action in callback
* @param biometricAuthenticationCallback Result of biometric action in callback
*/
fun authenticate(authenticationCallback: AuthenticationCallback, cryptoObject: CryptoObject? = null)
fun authenticate(biometricAuthenticationCallback: BiometricAuthenticationCallback,
cryptoObject: CryptoObject? = null)

}

Expand All @@ -55,7 +56,7 @@ internal class BiometricBindingHandler(titleValue: String,
descriptionValue: String,
fragmentActivity: FragmentActivity = InitProvider.getCurrentActivityAsFragmentActivity(),
deviceBindAuthenticationType: DeviceBindingAuthenticationType,
var biometricListener: AuthenticationCallback? = null,
var biometricListener: BiometricAuthenticationCallback? = null,
private var biometricAuth: BiometricAuth? = null) :
BiometricHandler {

Expand All @@ -70,12 +71,12 @@ internal class BiometricBindingHandler(titleValue: String,

/**
* display biometric prompt for Biometric and device credential
* @param authenticationCallback Result of biometric action in callback
* @param biometricAuthenticationCallback Result of biometric action in callback
*/
override fun authenticate(authenticationCallback: AuthenticationCallback, cryptoObject: CryptoObject?) {
override fun authenticate(biometricAuthenticationCallback: BiometricAuthenticationCallback, cryptoObject: CryptoObject?) {

biometricListener = authenticationCallback
biometricAuth?.biometricAuthListener = authenticationCallback
biometricListener = biometricAuthenticationCallback
biometricAuth?.biometricAuthenticationCallback = biometricAuthenticationCallback
biometricAuth?.authenticate(cryptoObject)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.biometric.BiometricPrompt
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.crypto.impl.RSASSAProvider
import org.forgerock.android.auth.Logger
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback
import org.forgerock.android.auth.callback.Attestation
import org.forgerock.android.auth.callback.DeviceBindingAuthenticationType
import java.security.PrivateKey
Expand All @@ -36,16 +37,16 @@ open class BiometricOnly : BiometricAuthenticator() {
}.signature(JWSAlgorithm.parse(getAlgorithm()), privateKey)
}

override fun authenticate(authenticationCallback: BiometricPrompt.AuthenticationCallback,
override fun authenticate(biometricAuthenticationCallback: BiometricAuthenticationCallback,
privateKey: PrivateKey) {
try {
biometricInterface.authenticate(authenticationCallback,
biometricInterface.authenticate(biometricAuthenticationCallback,
BiometricPrompt.CryptoObject(getSignature(privateKey)))
} catch (e: Exception) {
//Failed because the key was generated with
//KeyGenParameterSpec.Builder.setUserAuthenticationParameters
Logger.warn(TAG, "Fallback to time-based key", e)
biometricInterface.authenticate(authenticationCallback)
biometricInterface.authenticate(biometricAuthenticationCallback)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.biometric.BiometricPrompt.AuthenticationResult
import androidx.biometric.BiometricPrompt.ERROR_TIMEOUT
import androidx.fragment.app.FragmentActivity
import org.forgerock.android.auth.biometric.BiometricAuth
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback
import org.forgerock.android.auth.callback.DeviceBindingAuthenticationType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
Expand Down Expand Up @@ -113,9 +114,9 @@ class BiometricBindingHandlerTests {
@Test
fun testListener() {
val testObject = BiometricBindingHandler("title", "subtitle", "description", deviceBindAuthenticationType = DeviceBindingAuthenticationType.BIOMETRIC_ALLOW_FALLBACK, fragmentActivity = activity, biometricAuth = biometricAuth)
val result = object : AuthenticationCallback() {}
val result = object : BiometricAuthenticationCallback() {}
testObject.authenticate(result)
verify(biometricAuth).biometricAuthListener = testObject.biometricListener
verify(biometricAuth).biometricAuthenticationCallback = testObject.biometricListener
verify(biometricAuth).authenticate()
}

Expand All @@ -125,8 +126,8 @@ class BiometricBindingHandlerTests {
val latch = CountDownLatch(1)
val testObject = BiometricBindingHandler("title", "subtitle", "description", deviceBindAuthenticationType = DeviceBindingAuthenticationType.BIOMETRIC_ALLOW_FALLBACK, fragmentActivity = activity, biometricAuth = biometricAuth)

val result = object : AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
val result = object : BiometricAuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult?) {
latch.countDown()
}
}
Expand All @@ -140,7 +141,7 @@ class BiometricBindingHandlerTests {
fun testBiometricListenerForTimeoutFailure() {
val latch = CountDownLatch(1)
val testObject = BiometricBindingHandler("title", "subtitle", "description", deviceBindAuthenticationType = DeviceBindingAuthenticationType.BIOMETRIC_ALLOW_FALLBACK, fragmentActivity = activity, biometricAuth = biometricAuth)
val result = object : AuthenticationCallback() {
val result = object : BiometricAuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
assertEquals(ERROR_TIMEOUT, errorCode)
latch.countDown()
Expand All @@ -156,7 +157,7 @@ class BiometricBindingHandlerTests {
val latch = CountDownLatch(1)
val testObject = BiometricBindingHandler("title", "subtitle", "description", deviceBindAuthenticationType = DeviceBindingAuthenticationType.BIOMETRIC_ALLOW_FALLBACK, fragmentActivity = activity, biometricAuth = biometricAuth)

val result = object : AuthenticationCallback() {
val result = object : BiometricAuthenticationCallback() {
override fun onAuthenticationFailed() {
latch.countDown()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.forgerock.android.auth.CryptoKey
import org.forgerock.android.auth.FRLogger
import org.forgerock.android.auth.Logger
import org.forgerock.android.auth.Logger.Companion.set
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback
import org.forgerock.android.auth.callback.Attestation
import org.forgerock.android.auth.callback.DeviceBindingAuthenticationType
import org.forgerock.android.auth.devicebind.DeviceBindingErrorStatus.ClientNotRegistered
Expand Down Expand Up @@ -362,7 +363,7 @@ class DeviceBindAuthenticationTests {
return true
}

override fun authenticate(authenticationCallback: AuthenticationCallback,
override fun authenticate(authenticationCallback: BiometricAuthenticationCallback,
cryptoObject: BiometricPrompt.CryptoObject?) {
result = true
authenticationCallback.onAuthenticationSucceeded(authenticationResult)
Expand Down Expand Up @@ -401,7 +402,7 @@ class DeviceBindAuthenticationTests {
return true
}

override fun authenticate(authenticationCallback: AuthenticationCallback,
override fun authenticate(authenticationCallback: BiometricAuthenticationCallback,
cryptoObject: BiometricPrompt.CryptoObject?) {
result = true
authenticationCallback.onAuthenticationSucceeded(authenticationResult)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
package org.forgerock.android.auth;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.biometric.BiometricPrompt;
import androidx.biometric.BiometricPrompt.AuthenticationCallback;
import androidx.fragment.app.FragmentActivity;

import org.forgerock.android.auth.biometric.BiometricAuth;
import org.forgerock.android.auth.biometric.BiometricAuthenticationCallback;
import org.forgerock.android.auth.exception.AccountLockException;
import org.forgerock.android.auth.exception.InvalidNotificationException;
import org.forgerock.android.auth.exception.PushMechanismException;
Expand Down Expand Up @@ -363,10 +364,10 @@ public final void accept(String title,
} else if (this.pushType == PushType.BIOMETRIC) {
final PushNotification pushNotification = this;
BiometricAuth biometricAuth = new BiometricAuth(title,
subtitle, allowDeviceCredentials, activity, new AuthenticationCallback() {
subtitle, allowDeviceCredentials, activity, new BiometricAuthenticationCallback() {

@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
public void onAuthenticationSucceeded(@Nullable BiometricPrompt.AuthenticationResult result) {
Logger.debug(TAG, "Respond the challenge for message: %s", getMessageId());
PushResponder.getInstance().authentication(pushNotification, true, listener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationCallback
import androidx.biometric.BiometricPrompt.CryptoObject
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
Expand Down Expand Up @@ -62,20 +61,22 @@ class BiometricAuth @JvmOverloads constructor(
* Return the Biometric Authentication listener used to return authentication result.
* @return the Biometric Authentication listener.
*/
var biometricAuthListener: AuthenticationCallback? = null,
var biometricAuthenticationCallback: BiometricAuthenticationCallback? = null,

/**
* The description to be displayed on the prompt.
* @return the description as String.
*/
private val description: String? = null,

) {
) {

private var biometricManager: BiometricManager? = null

private var fingerprintManager: FingerprintManager? = null

private val authCallback: AuthenticationDecorator = AuthenticationDecorator(biometricAuthenticationCallback)

/**
* Return the mechanism used to lock and unlock the device.
* @return the KeyguardManager instance.
Expand All @@ -87,7 +88,7 @@ class BiometricAuth @JvmOverloads constructor(

private fun handleError(logMessage: String, biometricErrorMessage: String, errorType: Int) {
debug(TAG, logMessage)
biometricAuthListener?.onAuthenticationError(
biometricAuthenticationCallback?.onAuthenticationError(
errorType, biometricErrorMessage)
}

Expand Down Expand Up @@ -162,14 +163,14 @@ class BiometricAuth @JvmOverloads constructor(
keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as? KeyguardManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerprintManager =
context.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
context.getSystemService(Context.FINGERPRINT_SERVICE) as? FingerprintManager
}
}

private fun initBiometricPrompt(): BiometricPrompt {
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = biometricAuthListener?.let {
BiometricPrompt(activity, executor, it)
val biometricPrompt = biometricAuthenticationCallback?.let {
BiometricPrompt(activity, executor, authCallback.getAuthCallback())
} ?: kotlin.run {
throw IllegalStateException("AuthenticationCallback is not set.")
}
Expand Down Expand Up @@ -265,8 +266,3 @@ class BiometricAuth @JvmOverloads constructor(
setServicesFromActivity(activity)
}
}

enum class AuthenticatorType {
STRONG,
WEAK
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.forgerock.android.auth.biometric

import androidx.biometric.BiometricPrompt

abstract class BiometricAuthenticationCallback {
/**
* Called when an unrecoverable error has been encountered and authentication has stopped.
*
*
* After this method is called, no further events will be sent for the current
* authentication session.
*
* @param errorCode An integer ID associated with the error.
* @param errString A human-readable string that describes the error.
*/
open fun onAuthenticationError(
@BiometricPrompt.AuthenticationError errorCode: Int, errString: CharSequence
) {}

/**
* Called when a biometric (e.g. fingerprint, face, etc.) is recognized, indicating that the
* user has successfully authenticated.
*
*
* After this method is called, no further events will be sent for the current
* authentication session.
*
* @param result An object containing authentication-related data.
*/
open fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {}

/**
* Called when a biometric (e.g. fingerprint, face, etc.) is presented but not recognized as
* belonging to the user.
*/
open fun onAuthenticationFailed() {}
}

internal class AuthenticationDecorator(private val listener: BiometricAuthenticationCallback? = null) {
fun getAuthCallback() = object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(@BiometricPrompt.AuthenticationError errorCode: Int, errString: CharSequence) {
listener?.onAuthenticationError(errorCode, errString)
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
listener?.onAuthenticationSucceeded(result)
}
override fun onAuthenticationFailed() {
listener?.onAuthenticationFailed()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d
}
if (requestCode == LOCK_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
this.biometricAuth.getBiometricAuthListener().onAuthenticationSucceeded(null);
// Eventually we need to uncouple this logic in future
this.biometricAuth.getBiometricAuthenticationCallback().onAuthenticationSucceeded(null);
} else {
Logger.debug(TAG, "Fail to approve using device Credentials. requestCode " +
"is %s", resultCode);
this.biometricAuth.getBiometricAuthListener().onAuthenticationError(
this.biometricAuth.getBiometricAuthenticationCallback().onAuthenticationError(
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL,
"Fail to approve using device Credentials.");
}
Expand Down
Loading

0 comments on commit 0ba294b

Please sign in to comment.