Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Check if biometric is available
Browse files Browse the repository at this point in the history
  • Loading branch information
M3DZIK committed Oct 29, 2023
1 parent 168f6be commit cd83026
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import dev.medzik.librepass.android.ui.components.TextInputField
import dev.medzik.librepass.android.utils.KeyAlias
import dev.medzik.librepass.android.utils.SecretStore
import dev.medzik.librepass.android.utils.UserSecrets
import dev.medzik.librepass.android.utils.checkIfBiometricAvailable
import dev.medzik.librepass.android.utils.showBiometricPrompt
import dev.medzik.librepass.client.utils.Cryptography
import dev.medzik.librepass.client.utils.Cryptography.computePasswordHash
Expand Down Expand Up @@ -59,16 +60,18 @@ fun UnlockScreen(navController: NavController) {
loading = true

// compute base password hash
val passwordHash = computePasswordHash(
password = password,
email = credentials.email,
argon2Function = Argon2(
32,
credentials.parallelism,
credentials.memory,
credentials.iterations
val passwordHash =
computePasswordHash(
password = password,
email = credentials.email,
argon2Function =
Argon2(
32,
credentials.parallelism,
credentials.memory,
credentials.iterations
)
)
)

val publicKey = X25519.publicFromPrivate(passwordHash.hash)

Expand Down Expand Up @@ -106,11 +109,12 @@ fun UnlockScreen(navController: NavController) {
fun showBiometric() {
showBiometricPrompt(
context = context,
cipher = KeyStore.initForDecryption(
alias = KeyAlias.BiometricPrivateKey,
initializationVector = Hex.decode(credentials.biometricProtectedPrivateKeyIV!!),
deviceAuthentication = true
),
cipher =
KeyStore.initForDecryption(
alias = KeyAlias.BiometricPrivateKey,
initializationVector = Hex.decode(credentials.biometricProtectedPrivateKeyIV!!),
deviceAuthentication = true
),
onAuthenticationSucceeded = { cipher ->
val privateKey =
KeyStore.decrypt(cipher, credentials.biometricProtectedPrivateKey!!)
Expand All @@ -135,7 +139,7 @@ fun UnlockScreen(navController: NavController) {
}

LaunchedEffect(scope) {
if (credentials.biometricEnabled) showBiometric()
if (credentials.biometricEnabled && checkIfBiometricAvailable(context)) showBiometric()
}

TextInputField(
Expand All @@ -150,21 +154,23 @@ fun UnlockScreen(navController: NavController) {
loading = loading,
onClick = { onUnlock(password) },
enabled = password.isNotEmpty(),
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
.padding(horizontal = 80.dp)
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp)
.padding(horizontal = 80.dp)
) {
Text(stringResource(R.string.Button_Unlock))
}

if (credentials.biometricEnabled) {
if (credentials.biometricEnabled && checkIfBiometricAvailable(context)) {
OutlinedButton(
onClick = { showBiometric() },
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
.padding(horizontal = 80.dp)
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp)
.padding(horizontal = 80.dp)
) {
Text(stringResource(R.string.Button_UseBiometric))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import dev.medzik.librepass.android.utils.SecretStore.readKey
import dev.medzik.librepass.android.utils.SecretStore.writeKey
import dev.medzik.librepass.android.utils.StoreKey
import dev.medzik.librepass.android.utils.VaultTimeoutValues
import dev.medzik.librepass.android.utils.checkIfBiometricAvailable
import dev.medzik.librepass.android.utils.showBiometricPrompt
import kotlinx.coroutines.launch

Expand Down Expand Up @@ -68,15 +69,17 @@ fun SettingsSecurityScreen() {

showBiometricPrompt(
context = context as MainActivity,
cipher = KeyStore.initForEncryption(
KeyAlias.BiometricPrivateKey,
deviceAuthentication = true
),
cipher =
KeyStore.initForEncryption(
KeyAlias.BiometricPrivateKey,
deviceAuthentication = true
),
onAuthenticationSucceeded = { cipher ->
val encryptedData = KeyStore.encrypt(
cipher = cipher,
clearBytes = Hex.decode(userSecrets.privateKey)
)
val encryptedData =
KeyStore.encrypt(
cipher = cipher,
clearBytes = Hex.decode(userSecrets.privateKey)
)

biometricEnabled = true

Expand All @@ -98,55 +101,63 @@ fun SettingsSecurityScreen() {
fun getVaultTimeoutTranslation(value: VaultTimeoutValues): String {
return when (value) {
VaultTimeoutValues.INSTANT -> stringResource(R.string.Settings_Vault_Timeout_Instant)
VaultTimeoutValues.ONE_MINUTE -> pluralStringResource(
R.plurals.Time_Minutes,
1,
1
)

VaultTimeoutValues.FIVE_MINUTES -> pluralStringResource(
R.plurals.Time_Minutes,
5,
5
)

VaultTimeoutValues.FIFTEEN_MINUTES -> pluralStringResource(
R.plurals.Time_Minutes,
15,
15
)

VaultTimeoutValues.THIRTY_MINUTES -> pluralStringResource(
R.plurals.Time_Minutes,
30,
30
)

VaultTimeoutValues.ONE_HOUR -> pluralStringResource(
R.plurals.Time_Hours,
1,
1
)
VaultTimeoutValues.ONE_MINUTE ->
pluralStringResource(
R.plurals.Time_Minutes,
1,
1
)

VaultTimeoutValues.FIVE_MINUTES ->
pluralStringResource(
R.plurals.Time_Minutes,
5,
5
)

VaultTimeoutValues.FIFTEEN_MINUTES ->
pluralStringResource(
R.plurals.Time_Minutes,
15,
15
)

VaultTimeoutValues.THIRTY_MINUTES ->
pluralStringResource(
R.plurals.Time_Minutes,
30,
30
)

VaultTimeoutValues.ONE_HOUR ->
pluralStringResource(
R.plurals.Time_Hours,
1,
1
)

VaultTimeoutValues.NEVER -> stringResource(R.string.Settings_Vault_Timeout_Never)
}
}

SwitcherPreference(
title = stringResource(R.string.Settings_BiometricUnlock),
icon = { Icon(Icons.Default.Fingerprint, contentDescription = null) },
checked = biometricEnabled,
onCheckedChange = { biometricHandler() }
)
if (checkIfBiometricAvailable(context)) {
SwitcherPreference(
title = stringResource(R.string.Settings_BiometricUnlock),
icon = { Icon(Icons.Default.Fingerprint, contentDescription = null) },
checked = biometricEnabled,
onCheckedChange = { biometricHandler() }
)
}

PropertyPreference(
title = stringResource(R.string.Settings_Vault_Timeout_Modal_Title),
icon = { Icon(Icons.Default.Timer, contentDescription = null) },
currentValue = getVaultTimeoutTranslation(
VaultTimeoutValues.fromSeconds(
vaultTimeout
)
),
currentValue =
getVaultTimeoutTranslation(
VaultTimeoutValues.fromSeconds(
vaultTimeout
)
),
onClick = { timerDialogState.show() },
)

Expand All @@ -161,9 +172,10 @@ fun SettingsSecurityScreen() {
) {
Text(
text = getVaultTimeoutTranslation(it),
modifier = Modifier
.padding(vertical = 12.dp)
.fillMaxWidth()
modifier =
Modifier
.padding(vertical = 12.dp)
.fillMaxWidth()
)
}
}
50 changes: 31 additions & 19 deletions app/src/main/java/dev/medzik/librepass/android/utils/Biometric.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.medzik.librepass.android.utils

import android.content.Context
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import dev.medzik.librepass.android.R
Expand All @@ -11,25 +13,35 @@ fun showBiometricPrompt(
onAuthenticationSucceeded: (Cipher) -> Unit,
onAuthenticationFailed: () -> Unit
) {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.BiometricUnlock_Title))
.setSubtitle(context.getString(R.string.BiometricUnlock_Subtitle))
.setNegativeButtonText(context.getString(R.string.BiometricUnlock_Button_UsePassword))
.build()

val biometricPrompt = BiometricPrompt(
context,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) =
onAuthenticationFailed()

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) =
onAuthenticationSucceeded(result.cryptoObject?.cipher!!)

override fun onAuthenticationFailed() =
onAuthenticationFailed()
}
)
val promptInfo =
BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.BiometricUnlock_Title))
.setSubtitle(context.getString(R.string.BiometricUnlock_Subtitle))
.setNegativeButtonText(context.getString(R.string.BiometricUnlock_Button_UsePassword))
.build()

val biometricPrompt =
BiometricPrompt(
context,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence
) = onAuthenticationFailed()

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) =
onAuthenticationSucceeded(result.cryptoObject?.cipher!!)

override fun onAuthenticationFailed() = onAuthenticationFailed()
}
)

biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}

fun checkIfBiometricAvailable(context: Context): Boolean {
val status = BiometricManager.from(context).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)

// return true when available
return status == BiometricManager.BIOMETRIC_SUCCESS
}

0 comments on commit cd83026

Please sign in to comment.