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

Commit

Permalink
Refactor utils
Browse files Browse the repository at this point in the history
  • Loading branch information
M3DZIK committed Oct 14, 2023
1 parent 6b1bea0 commit 0e0fc27
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import dev.medzik.librepass.android.utils.TextInputField
import dev.medzik.librepass.android.utils.TopBar
import dev.medzik.librepass.android.utils.TopBarBackIcon
import dev.medzik.librepass.android.utils.UserSecrets
import dev.medzik.librepass.android.utils.exception.handle
import dev.medzik.librepass.android.utils.showErrorToast
import dev.medzik.librepass.client.Server
import dev.medzik.librepass.client.api.AuthClient
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -108,7 +108,7 @@ fun LoginScreen(navController: NavController) {
}
} catch (e: Exception) {
loading = false
e.handle(context)
e.showErrorToast(context)
}
}
}
Expand Down Expand Up @@ -154,7 +154,7 @@ fun LoginScreen(navController: NavController) {

context.showToast(context.getString(R.string.Toast_Password_Hint_Sent))
} catch (e: Exception) {
e.handle(context)
e.showErrorToast(context)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import dev.medzik.librepass.android.utils.StoreKey
import dev.medzik.librepass.android.utils.TextInputField
import dev.medzik.librepass.android.utils.TopBar
import dev.medzik.librepass.android.utils.TopBarBackIcon
import dev.medzik.librepass.android.utils.exception.handle
import dev.medzik.librepass.android.utils.showErrorToast
import dev.medzik.librepass.client.Server
import dev.medzik.librepass.client.api.AuthClient
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -75,8 +75,7 @@ fun RegisterScreen(navController: NavController) {
}
} catch (e: Exception) {
loading = false

e.handle(context)
e.showErrorToast(context)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ import dev.medzik.libcrypto.X25519
import dev.medzik.librepass.android.R
import dev.medzik.librepass.android.data.getRepository
import dev.medzik.librepass.android.ui.Screen
import dev.medzik.librepass.android.utils.Biometric
import dev.medzik.librepass.android.utils.BiometricAlias
import dev.medzik.librepass.android.utils.KeyAlias
import dev.medzik.librepass.android.utils.SecretStore
import dev.medzik.librepass.android.utils.TextInputField
import dev.medzik.librepass.android.utils.TopBar
import dev.medzik.librepass.android.utils.UserSecrets
import dev.medzik.librepass.android.utils.exception.EncryptException
import dev.medzik.librepass.android.utils.showBiometricPrompt
import dev.medzik.librepass.client.utils.Cryptography
import dev.medzik.librepass.client.utils.Cryptography.computePasswordHash
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -78,7 +77,7 @@ fun UnlockScreen(navController: NavController) {
val publicKey = X25519.publicFromPrivate(passwordHash.hash)

if (Hex.encode(publicKey) != credentials.publicKey)
throw EncryptException("Invalid password")
throw Exception("Invalid password")

val secretKey =
Cryptography.computeSharedKey(passwordHash.hash, Hex.decode(credentials.publicKey))
Expand All @@ -100,7 +99,7 @@ fun UnlockScreen(navController: NavController) {
)
}
}
} catch (e: EncryptException) {
} catch (e: Exception) {
// if password is invalid
loading = false
context.showToast(R.string.Error_InvalidCredentials)
Expand All @@ -109,10 +108,10 @@ fun UnlockScreen(navController: NavController) {
}

fun showBiometric() {
Biometric.showBiometricPrompt(
showBiometricPrompt(
context = context,
cipher = KeyStore.initForDecryption(
alias = BiometricAlias.PrivateKey,
alias = KeyAlias.BiometricPrivateKey,
initializationVector = Hex.decode(credentials.biometricProtectedPrivateKeyIV!!),
deviceAuthentication = true
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import dev.medzik.librepass.android.utils.SecretStore.getUserSecrets
import dev.medzik.librepass.android.utils.TextInputFieldBase
import dev.medzik.librepass.android.utils.TopBar
import dev.medzik.librepass.android.utils.TopBarBackIcon
import dev.medzik.librepass.android.utils.exception.handle
import dev.medzik.librepass.android.utils.shorten
import dev.medzik.librepass.android.utils.showErrorToast
import dev.medzik.librepass.client.Server
import dev.medzik.librepass.client.api.CipherClient
import dev.medzik.librepass.types.cipher.Cipher
Expand Down Expand Up @@ -141,7 +141,7 @@ fun CipherAddEditView(
scope.launch(Dispatchers.Main) { navController.popBackStack() }
} catch (e: Exception) {
loading = false
e.handle(context)
e.showErrorToast(context)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import dev.medzik.librepass.android.ui.Argument
import dev.medzik.librepass.android.ui.Screen
import dev.medzik.librepass.android.ui.composables.CipherCard
import dev.medzik.librepass.android.utils.SecretStore.getUserSecrets
import dev.medzik.librepass.android.utils.exception.handle
import dev.medzik.librepass.android.utils.showErrorToast
import dev.medzik.librepass.client.Server
import dev.medzik.librepass.client.api.CipherClient
import dev.medzik.librepass.types.cipher.Cipher
Expand Down Expand Up @@ -129,7 +129,7 @@ fun DashboardScreen(navController: NavController) {
}
}
} catch (e: Exception) {
e.handle(context)
e.showErrorToast(context)
} finally {
// get cipher from local repository and update UI
updateLocalCiphers()
Expand Down Expand Up @@ -176,7 +176,7 @@ fun DashboardScreen(navController: NavController) {

ciphers = ciphers.filter { it.id != cipher.id }
} catch (e: Exception) {
e.handle(context)
e.showErrorToast(context)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ import dev.medzik.libcrypto.Hex
import dev.medzik.librepass.android.MainActivity
import dev.medzik.librepass.android.R
import dev.medzik.librepass.android.data.getRepository
import dev.medzik.librepass.android.utils.Biometric
import dev.medzik.librepass.android.utils.BiometricAlias
import dev.medzik.librepass.android.utils.KeyAlias
import dev.medzik.librepass.android.utils.SecretStore.getUserSecrets
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.TopBar
import dev.medzik.librepass.android.utils.TopBarBackIcon
import dev.medzik.librepass.android.utils.VaultTimeoutValues
import dev.medzik.librepass.android.utils.showBiometricPrompt
import kotlinx.coroutines.launch

@Composable
Expand All @@ -56,7 +56,7 @@ fun SettingsSecurity(navController: NavController) {
var vaultTimeout by remember { mutableIntStateOf(context.readKey(StoreKey.VaultTimeout)) }

// Biometric checked event handler (enable/disable biometric authentication)
fun showBiometricPrompt() {
fun biometricHandler() {
if (biometricEnabled) {
biometricEnabled = false

Expand All @@ -71,10 +71,10 @@ fun SettingsSecurity(navController: NavController) {
return
}

Biometric.showBiometricPrompt(
showBiometricPrompt(
context = context as MainActivity,
cipher = KeyStore.initForEncryption(
BiometricAlias.PrivateKey,
KeyAlias.BiometricPrivateKey,
deviceAuthentication = true
),
onAuthenticationSucceeded = { cipher ->
Expand Down Expand Up @@ -152,7 +152,7 @@ fun SettingsSecurity(navController: NavController) {
title = stringResource(R.string.Settings_BiometricUnlock),
icon = { Icon(Icons.Default.Fingerprint, contentDescription = null) },
checked = biometricEnabled,
onCheckedChange = { showBiometricPrompt() }
onCheckedChange = { biometricHandler() }
)

PropertyPreference(
Expand Down
53 changes: 23 additions & 30 deletions app/src/main/java/dev/medzik/librepass/android/utils/Biometric.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,34 @@ package dev.medzik.librepass.android.utils

import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import dev.medzik.android.crypto.KeyStoreAlias
import dev.medzik.librepass.android.R
import javax.crypto.Cipher

enum class BiometricAlias : KeyStoreAlias {
PrivateKey
}

object Biometric {
fun showBiometricPrompt(
context: FragmentActivity,
cipher: Cipher,
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()
fun showBiometricPrompt(
context: FragmentActivity,
cipher: Cipher,
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()
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 onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) =
onAuthenticationSucceeded(result.cryptoObject?.cipher!!)

override fun onAuthenticationFailed() =
onAuthenticationFailed()
}
)
override fun onAuthenticationFailed() =
onAuthenticationFailed()
}
)

biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
48 changes: 48 additions & 0 deletions app/src/main/java/dev/medzik/librepass/android/utils/Exception.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.medzik.librepass.android.utils

import android.content.Context
import dev.medzik.android.utils.runOnUiThread
import dev.medzik.android.utils.showToast
import dev.medzik.librepass.android.BuildConfig
import dev.medzik.librepass.android.R
import dev.medzik.librepass.client.errors.ApiException
import dev.medzik.librepass.client.errors.ClientException
import dev.medzik.librepass.responses.ResponseError

/** Log exception if debugging is enabled. */
fun Exception.debugLog() {
if (BuildConfig.DEBUG) {
printStackTrace()
}
}

/** Handle exceptions. Show toast with an error message. */
fun Exception.showErrorToast(context: Context) {
// log exception trace if debugging is enabled
debugLog()

val message = when (this) {
// // handle encrypt exception
// is EncryptException -> { context.getString(R.string.Error_EncryptionError) }
// handle client exception (network error)
is ClientException -> { context.getString(R.string.Error_NetworkError) }
// handle api exceptions
is ApiException -> { getTranslatedErrorMessage(context) }
// handle other exceptions
else -> { context.getString(R.string.Error_UnknownError) }
}

runOnUiThread { context.showToast(message) }
}

fun ApiException.getTranslatedErrorMessage(context: Context): String {
return when (responseError) {
ResponseError.INVALID_CREDENTIALS -> context.getString(R.string.API_Error_INVALID_CREDENTIALS)
ResponseError.RE_LOGIN_REQUIRED -> context.getString(R.string.API_Error_RE_LOGIN_REQUIRED)
ResponseError.EMAIL_NOT_VERIFIED -> context.getString(R.string.API_Error_EMAIL_NOT_VERIFIED)
ResponseError.TOO_MANY_REQUESTS -> context.getString(R.string.API_Error_TOO_MANY_REQUESTS)
ResponseError.DATABASE_DUPLICATED_KEY -> context.getString(R.string.API_Error_DATABASE_DUPLICATED_KEY)
ResponseError.UNEXPECTED_SERVER_ERROR -> context.getString(R.string.API_Error_UNEXPECTED_SERVER_ERROR)
else -> message
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.medzik.librepass.android.utils

import dev.medzik.android.crypto.KeyStoreAlias

enum class KeyAlias : KeyStoreAlias {
BiometricPrivateKey,
DataStoreEncrypted
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import dev.medzik.android.crypto.DataStore.read
import dev.medzik.android.crypto.DataStore.readEncrypted
import dev.medzik.android.crypto.DataStore.write
import dev.medzik.android.crypto.DataStore.writeEncrypted
import dev.medzik.android.crypto.KeyStoreAlias
import dev.medzik.libcrypto.Hex
import dev.medzik.librepass.android.MainActivity
import kotlinx.coroutines.runBlocking

val Context.dataStore by preferencesDataStore(name = "librepass")
val Context.dataStore by preferencesDataStore("librepass")

object SecretStore {
inline fun <reified T> Context.readKey(key: StoreKey<T>): T {
Expand All @@ -37,13 +36,13 @@ object SecretStore {
UserSecrets(
privateKey = Hex.encode(
context.dataStore.readEncrypted(
KeyStore.DataStoreEncrypted,
KeyAlias.DataStoreEncrypted,
UserSecrets.PrivateKeyStoreKey
) ?: ByteArray(0)
),
secretKey = Hex.encode(
context.dataStore.readEncrypted(
KeyStore.DataStoreEncrypted,
KeyAlias.DataStoreEncrypted,
UserSecrets.SecretKeyStoreKey
) ?: ByteArray(0)
)
Expand All @@ -56,12 +55,12 @@ object SecretStore {
fun save(context: Context, userSecrets: UserSecrets): UserSecrets {
val saveUserSecrets = suspend {
context.dataStore.writeEncrypted(
KeyStore.DataStoreEncrypted,
KeyAlias.DataStoreEncrypted,
UserSecrets.PrivateKeyStoreKey,
Hex.decode(userSecrets.privateKey)
)
context.dataStore.writeEncrypted(
KeyStore.DataStoreEncrypted,
KeyAlias.DataStoreEncrypted,
UserSecrets.SecretKeyStoreKey,
Hex.decode(userSecrets.secretKey)
)
Expand Down Expand Up @@ -116,7 +115,3 @@ data class UserSecrets(
const val SecretKeyStoreKey = "secret_key"
}
}

enum class KeyStore : KeyStoreAlias {
DataStoreEncrypted,
}

This file was deleted.

Loading

0 comments on commit 0e0fc27

Please sign in to comment.