From 6aef5742c84144a3e00872567e36af923e033a88 Mon Sep 17 00:00:00 2001 From: Anton Tkachev Date: Mon, 18 Mar 2024 11:29:14 +0400 Subject: [PATCH] AND-6436 [Biometrics] Added cipher for a user authentication params --- .../AndroidAuthenticationManager.kt | 37 ++++++++++++++----- .../authentication/AuthenticationManager.kt | 17 ++++++++- .../DummyAuthenticationManager.kt | 8 +++- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/tangem-sdk-android/src/main/java/com/tangem/sdk/authentication/AndroidAuthenticationManager.kt b/tangem-sdk-android/src/main/java/com/tangem/sdk/authentication/AndroidAuthenticationManager.kt index 9c157ff5..823fd940 100644 --- a/tangem-sdk-android/src/main/java/com/tangem/sdk/authentication/AndroidAuthenticationManager.kt +++ b/tangem-sdk-android/src/main/java/com/tangem/sdk/authentication/AndroidAuthenticationManager.kt @@ -19,7 +19,8 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull +import javax.crypto.Cipher import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.time.Duration @@ -76,17 +77,18 @@ internal class AndroidAuthenticationManager( biometricsStatus.value = BiometricsStatus.UNINITIALIZED } - @OptIn(ExperimentalTime::class) - override suspend fun authenticate(params: AuthenticationManager.AuthenticationParams) { - when (biometricsStatus.value) { + override suspend fun authenticate( + params: AuthenticationManager.AuthenticationParams, + ): AuthenticationManager.AuthenticationResult { + return when (biometricsStatus.value) { BiometricsStatus.READY -> { Log.biometric { "Authenticating a user: $params" } val timeout = params.timeout if (timeout != null) { - withTimeout(timeout) { authenticate() } + authenticateWithTimeout(timeout, params.cipher) } else { - authenticate() + authenticate(params.cipher) } } BiometricsStatus.AUTHENTICATING -> { @@ -114,13 +116,23 @@ internal class AndroidAuthenticationManager( } } - private suspend fun authenticate(): BiometricPrompt.AuthenticationResult = withContext(Dispatchers.Main) { + @OptIn(ExperimentalTime::class) + private suspend fun authenticateWithTimeout(timeout: Duration, cipher: Cipher?): AndroidAuthenticationResult { + return withTimeoutOrNull(timeout) { authenticate(cipher) } + ?: throw TangemSdkError.AuthenticationCanceled() + } + + private suspend fun authenticate(cipher: Cipher?): AndroidAuthenticationResult = withContext(Dispatchers.Main) { biometricsStatus.value = BiometricsStatus.AUTHENTICATING - suspendCancellableCoroutine { continuation -> + val promptResult = suspendCancellableCoroutine { continuation -> val biometricPrompt = createBiometricPrompt(continuation) - biometricPrompt.authenticate(biometricPromptInfo) + if (cipher == null) { + biometricPrompt.authenticate(biometricPromptInfo) + } else { + biometricPrompt.authenticate(biometricPromptInfo, BiometricPrompt.CryptoObject(cipher)) + } continuation.invokeOnCancellation { Log.biometric { "User authentication has been canceled" } @@ -130,6 +142,8 @@ internal class AndroidAuthenticationManager( biometricsStatus.value = BiometricsStatus.READY } } + + AndroidAuthenticationResult(promptResult.cryptoObject?.cipher) } private fun createBiometricPrompt(continuation: CancellableContinuation) = @@ -231,9 +245,14 @@ internal class AndroidAuthenticationManager( } data class AndroidAuthenticationParams( + override val cipher: Cipher? = null, override val timeout: Duration? = null, ) : AuthenticationManager.AuthenticationParams + data class AndroidAuthenticationResult( + override val cipher: Cipher?, + ) : AuthenticationManager.AuthenticationResult + private enum class BiometricsStatus { READY, AUTHENTICATING, diff --git a/tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticationManager.kt b/tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticationManager.kt index f292886a..b5aea273 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticationManager.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticationManager.kt @@ -1,6 +1,7 @@ package com.tangem.common.authentication import com.tangem.common.core.TangemSdkError +import javax.crypto.Cipher import kotlin.time.Duration /** @@ -26,19 +27,33 @@ interface AuthenticationManager { * * @param params [AuthenticationParams] that specify the authentication process. * + * @return [AuthenticationResult] that contains the successful result of the authentication process. + * + * @throws TangemSdkError.AuthenticationAlreadyInProgress if another authentication process is already in progress. * @throws TangemSdkError.AuthenticationUnavailable if authentication is unavailable. * @throws TangemSdkError.AuthenticationCanceled if the authentication was cancelled. * @throws TangemSdkError.AuthenticationFailed if authentication fails for any other reason. */ - suspend fun authenticate(params: AuthenticationParams) + suspend fun authenticate(params: AuthenticationParams): AuthenticationResult /** * Parameters that specify the authentication process. * + * @property cipher The cipher that will be checked with authentication and can be used to encrypt or decrypt data. * @property timeout The maximum time to wait for the authentication to complete. If it's reached, the * authentication process will be cancelled. */ interface AuthenticationParams { + val cipher: Cipher? val timeout: Duration? } + + /** + * The successful result of the authentication process. + * + * @property cipher The cipher that can be used to encrypt or decrypt data. + */ + interface AuthenticationResult { + val cipher: Cipher? + } } diff --git a/tangem-sdk-core/src/main/java/com/tangem/common/authentication/DummyAuthenticationManager.kt b/tangem-sdk-core/src/main/java/com/tangem/common/authentication/DummyAuthenticationManager.kt index a7dfcab5..adae91b6 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/common/authentication/DummyAuthenticationManager.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/common/authentication/DummyAuthenticationManager.kt @@ -1,5 +1,7 @@ package com.tangem.common.authentication +import javax.crypto.Cipher + class DummyAuthenticationManager : AuthenticationManager { override val canAuthenticate: Boolean get() = false @@ -7,5 +9,9 @@ class DummyAuthenticationManager : AuthenticationManager { override val needEnrollBiometrics: Boolean get() = false - override suspend fun authenticate(params: AuthenticationManager.AuthenticationParams) = Unit + override suspend fun authenticate( + params: AuthenticationManager.AuthenticationParams, + ): AuthenticationManager.AuthenticationResult = object : AuthenticationManager.AuthenticationResult { + override val cipher: Cipher? = null + } }