mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-10-20 23:11:24 +02:00
[feat] [biometric] Adding the capability of encrypting/decrypting data using biometric authentication on Android (Ref. #2306) (#2454)
* feat: adding the ability to encrypt/decrypt data based on the biometric auth Adding the method biometricCipher() that allows to use the biometric authentication to use a generated key from android that can be accessed only through the biometric authentication. The method uses this key to encrypt/decrypt the data provided by the caller in the "options" parameter, with "dataToEncrypt" or "dataToDecrypt" filled. Changes were done both on Kotlin, Rust and JS sides. * Improving the way we encrypt data by allowing only if the biometric was successful. * Adding a warning on the JS side * Adding doc changes in readme.md * Adding doc changes in readme.md * Update README.md - it was updated incomplete * update docs * enhance error handling * update example --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
1 parent
b3e52ce036
commit
0374bec1e2
19 files changed
+915
-141
No files matched your search
@@ -16,23 +16,131 @@ import android.os.Handler
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import java.util.concurrent.Executor
|
||||
import android.util.Base64
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
//import javax.crypto.spec.IvParameterSpec
|
||||
import java.nio.charset.Charset
|
||||
import app.tauri.biometric.CipherType
|
||||
|
||||
class BiometricActivity : AppCompatActivity() {
|
||||
private var ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
||||
private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
|
||||
private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
|
||||
private val KEYSTORE_NAME = "AndroidKeyStore"
|
||||
private val KEY_ALIAS = "tauri-plugin-biometric-key"
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.auth_activity)
|
||||
|
||||
val executor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
this.mainExecutor
|
||||
} else {
|
||||
Executor { command: Runnable? ->
|
||||
Handler(this.mainLooper).post(
|
||||
command!!
|
||||
)
|
||||
}
|
||||
cipherOperation = intent.hasExtra(BiometricPlugin.ENCRYPT_DECRYPT_OPERATION)
|
||||
|
||||
// null when the activity already finished with an error
|
||||
val promptInfo = createPromptInfo() ?: return
|
||||
val prompt = createBiometricPrompt()
|
||||
|
||||
if (!cipherOperation) {
|
||||
prompt.authenticate(promptInfo)
|
||||
return
|
||||
}
|
||||
|
||||
intent.getStringExtra(BiometricPlugin.ENCRYPT_DECRYPT_DATA)?.let {
|
||||
encryptDecryptData = it
|
||||
}
|
||||
|
||||
try {
|
||||
val type = CipherType.values()[intent.getIntExtra(BiometricPlugin.CIPHER_OPERATION_TYPE, CipherType.ENCRYPT.ordinal)]
|
||||
cipherType = type
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
finishActivity(
|
||||
BiometryResultType.ERROR,
|
||||
0,
|
||||
"Couldn't identify the cipher operation type (encrypt/decrypt)!"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Fails on malformed encrypted data or a key that is no longer valid
|
||||
// (e.g. invalidated by a new biometric enrollment).
|
||||
val cipher = try {
|
||||
getCipher()
|
||||
} catch (e: Exception) {
|
||||
finishActivity(
|
||||
BiometryResultType.ERROR,
|
||||
0,
|
||||
"Failed to initialize the cipher: ${e.message ?: e.javaClass.simpleName}"
|
||||
)
|
||||
return
|
||||
}
|
||||
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun finishActivity(
|
||||
resultType: BiometryResultType = BiometryResultType.SUCCESS,
|
||||
errorCode: Int = 0,
|
||||
errorMessage: String? = ""
|
||||
) {
|
||||
val intent = Intent()
|
||||
val prefix = BiometricPlugin.RESULT_EXTRA_PREFIX
|
||||
intent
|
||||
.putExtra(prefix + BiometricPlugin.RESULT_TYPE, resultType.toString())
|
||||
.putExtra(prefix + BiometricPlugin.RESULT_ERROR_CODE, errorCode)
|
||||
.putExtra(
|
||||
prefix + BiometricPlugin.RESULT_ERROR_MESSAGE,
|
||||
errorMessage
|
||||
)
|
||||
.putExtra(
|
||||
prefix + BiometricPlugin.ENCRYPT_DECRYPT_OPERATION,
|
||||
cipherOperation
|
||||
)
|
||||
|
||||
if (resultType == BiometryResultType.SUCCESS && cipherOperation) {
|
||||
// Decrypting data that was tampered with or encrypted with another key fails here.
|
||||
val processedData = try {
|
||||
processCipherData()
|
||||
} catch (e: Exception) {
|
||||
finishActivity(
|
||||
BiometryResultType.ERROR,
|
||||
0,
|
||||
"Failed to ${if (cipherType == CipherType.ENCRYPT) "encrypt" else "decrypt"} the data: ${e.message ?: e.javaClass.simpleName}"
|
||||
)
|
||||
return
|
||||
}
|
||||
intent.putExtra(
|
||||
prefix + BiometricPlugin.RESULT_ENCRYPT_DECRYPT_DATA,
|
||||
processedData
|
||||
)
|
||||
}
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts [encryptDecryptData] with the cipher unlocked by the authentication.
|
||||
* Encrypted data is returned as Base64 `data;iv`, decrypted data as a UTF-8 string.
|
||||
*/
|
||||
private fun processCipherData(): String {
|
||||
val cryptoObject = requireNotNull(authenticationResult?.cryptoObject)
|
||||
val cipher = requireNotNull(cryptoObject.cipher)
|
||||
return if (cipherType == CipherType.ENCRYPT) {
|
||||
encodeEncryptedData(cipher, cipher.doFinal(encryptDecryptData.toByteArray()))
|
||||
} else {
|
||||
val (_, encryptedData) = decodeEncryptedData(encryptDecryptData)
|
||||
String(cipher.doFinal(encryptedData), Charset.forName("UTF-8"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPromptInfo(): BiometricPrompt.PromptInfo? {
|
||||
val builder = BiometricPrompt.PromptInfo.Builder()
|
||||
val intent = intent
|
||||
var title = intent.getStringExtra(BiometricPlugin.TITLE)
|
||||
@@ -54,7 +162,7 @@ class BiometricActivity : AppCompatActivity() {
|
||||
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL,
|
||||
getString(R.string.tauri_biometric_no_device_credential)
|
||||
)
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
if (title.isNullOrEmpty()) {
|
||||
@@ -63,7 +171,11 @@ class BiometricActivity : AppCompatActivity() {
|
||||
|
||||
builder.setTitle(title).setSubtitle(subtitle).setDescription(description)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
var authenticators = if (cipherOperation) {
|
||||
BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||
} else {
|
||||
BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
}
|
||||
if (allowDeviceCredential) {
|
||||
authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
}
|
||||
@@ -86,50 +198,119 @@ class BiometricActivity : AppCompatActivity() {
|
||||
builder.setConfirmationRequired(
|
||||
intent.getBooleanExtra(BiometricPlugin.CONFIRMATION_REQUIRED, true)
|
||||
)
|
||||
val promptInfo = builder.build()
|
||||
val prompt = BiometricPrompt(
|
||||
this,
|
||||
executor,
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(
|
||||
errorCode: Int,
|
||||
errorMessage: CharSequence
|
||||
) {
|
||||
super.onAuthenticationError(errorCode, errorMessage)
|
||||
finishActivity(
|
||||
BiometryResultType.ERROR,
|
||||
errorCode,
|
||||
errorMessage.toString()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(
|
||||
result: BiometricPrompt.AuthenticationResult
|
||||
) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
finishActivity()
|
||||
}
|
||||
}
|
||||
)
|
||||
prompt.authenticate(promptInfo)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun finishActivity(
|
||||
resultType: BiometryResultType = BiometryResultType.SUCCESS,
|
||||
errorCode: Int = 0,
|
||||
errorMessage: String? = ""
|
||||
) {
|
||||
val intent = Intent()
|
||||
val prefix = BiometricPlugin.RESULT_EXTRA_PREFIX
|
||||
intent
|
||||
.putExtra(prefix + BiometricPlugin.RESULT_TYPE, resultType.toString())
|
||||
.putExtra(prefix + BiometricPlugin.RESULT_ERROR_CODE, errorCode)
|
||||
.putExtra(
|
||||
prefix + BiometricPlugin.RESULT_ERROR_MESSAGE,
|
||||
errorMessage
|
||||
private fun createBiometricPrompt(): BiometricPrompt {
|
||||
val executor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
this.mainExecutor
|
||||
} else {
|
||||
Executor { command: Runnable? ->
|
||||
Handler(this.mainLooper).post(
|
||||
command!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val callback = object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(
|
||||
errorCode: Int,
|
||||
errorMessage: CharSequence
|
||||
) {
|
||||
super.onAuthenticationError(errorCode, errorMessage)
|
||||
finishActivity(
|
||||
BiometryResultType.ERROR,
|
||||
errorCode,
|
||||
errorMessage.toString()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(
|
||||
result: BiometricPrompt.AuthenticationResult
|
||||
) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
authenticationResult = result
|
||||
finishActivity()
|
||||
}
|
||||
}
|
||||
|
||||
return BiometricPrompt(this, executor, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key if exists, or create a new one if not
|
||||
*/
|
||||
private fun getEncryptionKey(): SecretKey {
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_NAME)
|
||||
keyStore.load(null)
|
||||
keyStore.getKey(KEY_ALIAS, null)?.let { return it as SecretKey }
|
||||
|
||||
// from here ahead, we will move only if there is not a key with the provided alias
|
||||
val keyGenerator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, KEYSTORE_NAME)
|
||||
|
||||
val builder = KeyGenParameterSpec.Builder(
|
||||
KEY_ALIAS,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||
)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
.setBlockModes(ENCRYPTION_BLOCK_MODE)
|
||||
.setEncryptionPaddings(ENCRYPTION_PADDING)
|
||||
.setKeySize(256)
|
||||
.setRandomizedEncryptionRequired(true)
|
||||
.setUserAuthenticationRequired(true) // Forces to use the biometric authentication to create/retrieve the key
|
||||
|
||||
keyGenerator.init(builder.build())
|
||||
return keyGenerator.generateKey()
|
||||
}
|
||||
|
||||
private fun deleteEncryptionKey() {
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_NAME)
|
||||
keyStore.load(null)
|
||||
keyStore.deleteEntry(KEY_ALIAS)
|
||||
}
|
||||
|
||||
private fun getCipher(): Cipher {
|
||||
val biometricKey = getEncryptionKey()
|
||||
|
||||
val cipher = Cipher.getInstance("$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING")
|
||||
|
||||
if (cipherType == CipherType.ENCRYPT) {
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, biometricKey)
|
||||
} catch (e: KeyPermanentlyInvalidatedException) {
|
||||
// The key was invalidated by a biometric enrollment change or the lock screen
|
||||
// being removed. Whatever it encrypted can no longer be decrypted, so replace it.
|
||||
deleteEncryptionKey()
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getEncryptionKey())
|
||||
}
|
||||
} else {
|
||||
// Decodes the Base64 string to get the encrypted data and IV
|
||||
val (iv, _) = decodeEncryptedData(encryptDecryptData)
|
||||
cipher.init(Cipher.DECRYPT_MODE, biometricKey, GCMParameterSpec(128, iv))
|
||||
}
|
||||
return cipher
|
||||
}
|
||||
|
||||
private fun encodeEncryptedData(cipher: Cipher, rawEncryptedData: ByteArray): String {
|
||||
val encodedData = Base64.encodeToString(rawEncryptedData, Base64.NO_WRAP)
|
||||
val encodedIv = Base64.encodeToString(cipher.iv, Base64.NO_WRAP)
|
||||
|
||||
val encodedString = encodedData + ";" + encodedIv
|
||||
return encodedString
|
||||
}
|
||||
|
||||
private fun decodeEncryptedData(encryptedDataEncoded: String): List<ByteArray> {
|
||||
val (data, iv) = encryptedDataEncoded.split(";")
|
||||
val decodedData = Base64.decode(data, Base64.NO_WRAP)
|
||||
val decodedIv = Base64.decode(iv, Base64.NO_WRAP)
|
||||
|
||||
val ret = listOf(decodedIv, decodedData)
|
||||
return ret
|
||||
}
|
||||
|
||||
companion object {
|
||||
var cipherOperation = false
|
||||
var encryptDecryptData: String = ""
|
||||
var authenticationResult: BiometricPrompt.AuthenticationResult? = null
|
||||
var cipherType: CipherType = CipherType.ENCRYPT
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ enum class BiometryResultType {
|
||||
SUCCESS, FAILURE, ERROR
|
||||
}
|
||||
|
||||
enum class CipherType {
|
||||
ENCRYPT, DECRYPT
|
||||
}
|
||||
|
||||
private const val MAX_ATTEMPTS = "maxAttemps"
|
||||
private const val BIOMETRIC_FAILURE = "authenticationFailed"
|
||||
private const val INVALID_CONTEXT_ERROR = "invalidContext"
|
||||
@@ -41,6 +45,8 @@ class AuthOptions {
|
||||
var cancelTitle: String? = null
|
||||
var confirmationRequired: Boolean? = null
|
||||
var maxAttemps: Int = 3
|
||||
var dataToEncrypt: String? = null
|
||||
var dataToDecrypt: String? = null
|
||||
}
|
||||
|
||||
@TauriPlugin
|
||||
@@ -58,32 +64,36 @@ class BiometricPlugin(private val activity: Activity): Plugin(activity) {
|
||||
const val RESULT_ERROR_MESSAGE = "errorMessage"
|
||||
const val DEVICE_CREDENTIAL = "allowDeviceCredential"
|
||||
const val CONFIRMATION_REQUIRED = "confirmationRequired"
|
||||
const val ENCRYPT_DECRYPT_OPERATION = "cipherOperation"
|
||||
const val ENCRYPT_DECRYPT_DATA = "encryptDecryptData"
|
||||
const val CIPHER_OPERATION_TYPE = "cipherOperationType"
|
||||
const val RESULT_ENCRYPT_DECRYPT_DATA = "resultEncryptDecryptData"
|
||||
|
||||
// Maps biometry error numbers to string error codes
|
||||
private var biometryErrorCodeMap: MutableMap<Int, String> = HashMap()
|
||||
private var biometryNameMap: MutableMap<BiometryType, String> = EnumMap(BiometryType::class.java)
|
||||
|
||||
init {
|
||||
biometryErrorCodeMap[BiometricManager.BIOMETRIC_SUCCESS] = ""
|
||||
biometryErrorCodeMap[BiometricManager.BIOMETRIC_SUCCESS] = ""
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_CANCELED] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_HW_NOT_PRESENT] = "biometryNotAvailable"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_HW_UNAVAILABLE] = "biometryNotAvailable"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT] = "biometryLockout"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT_PERMANENT] = "biometryLockout"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NEGATIVE_BUTTON] = "userCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NO_BIOMETRICS] = "biometryNotEnrolled"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL] = "passcodeNotSet"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NO_SPACE] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_TIMEOUT] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_UNABLE_TO_PROCESS] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_USER_CANCELED] = "userCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_VENDOR] = "systemCancel"
|
||||
init {
|
||||
biometryErrorCodeMap[BiometricManager.BIOMETRIC_SUCCESS] = ""
|
||||
biometryErrorCodeMap[BiometricManager.BIOMETRIC_SUCCESS] = ""
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_CANCELED] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_HW_NOT_PRESENT] = "biometryNotAvailable"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_HW_UNAVAILABLE] = "biometryNotAvailable"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT] = "biometryLockout"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT_PERMANENT] = "biometryLockout"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NEGATIVE_BUTTON] = "userCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NO_BIOMETRICS] = "biometryNotEnrolled"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL] = "passcodeNotSet"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_NO_SPACE] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_TIMEOUT] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_UNABLE_TO_PROCESS] = "systemCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_USER_CANCELED] = "userCancel"
|
||||
biometryErrorCodeMap[BiometricPrompt.ERROR_VENDOR] = "systemCancel"
|
||||
|
||||
biometryNameMap[BiometryType.NONE] = "No Authentication"
|
||||
biometryNameMap[BiometryType.FINGERPRINT] = "Fingerprint Authentication"
|
||||
biometryNameMap[BiometryType.FACE] = "Face Authentication"
|
||||
biometryNameMap[BiometryType.IRIS] = "Iris Authentication"
|
||||
biometryNameMap[BiometryType.NONE] = "No Authentication"
|
||||
biometryNameMap[BiometryType.FINGERPRINT] = "Fingerprint Authentication"
|
||||
biometryNameMap[BiometryType.FACE] = "Face Authentication"
|
||||
biometryNameMap[BiometryType.IRIS] = "Iris Authentication"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,18 +167,15 @@ class BiometricPlugin(private val activity: Activity): Plugin(activity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for biometric authentication.
|
||||
* sets up the options for BiometricPrompt.
|
||||
*/
|
||||
@Command
|
||||
fun authenticate(invoke: Invoke) {
|
||||
private fun configBiometricPrompt(args: AuthOptions): Intent {
|
||||
// The result of an intent is supposed to have the package name as a prefix
|
||||
RESULT_EXTRA_PREFIX = activity.packageName + "."
|
||||
val intent = Intent(
|
||||
activity,
|
||||
BiometricActivity::class.java
|
||||
)
|
||||
|
||||
val args = invoke.parseArgs(AuthOptions::class.java)
|
||||
|
||||
// Pass the options to the activity
|
||||
intent.putExtra(
|
||||
@@ -186,9 +193,52 @@ class BiometricPlugin(private val activity: Activity): Plugin(activity) {
|
||||
val maxAttemptsConfig = args.maxAttemps
|
||||
val maxAttempts = max(maxAttemptsConfig, 1)
|
||||
intent.putExtra(MAX_ATTEMPTS, maxAttempts)
|
||||
|
||||
return intent
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for biometric authentication.
|
||||
*/
|
||||
@Command
|
||||
fun authenticate(invoke: Invoke) {
|
||||
val args: AuthOptions = invoke.parseArgs(AuthOptions::class.java)
|
||||
|
||||
val intent = configBiometricPrompt(args)
|
||||
startActivityForResult(invoke, intent, "authenticateResult")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prompt the user for biometric authentication to encrypt/decrypt some provided data]
|
||||
*/
|
||||
@Command
|
||||
fun biometricCipher(invoke: Invoke) {
|
||||
val args: AuthOptions = invoke.parseArgs(AuthOptions::class.java)
|
||||
|
||||
if (args.dataToEncrypt == null && args.dataToDecrypt == null) {
|
||||
invoke.reject("Either dataToEncrypt or dataToDecrypt must be provided")
|
||||
return
|
||||
}
|
||||
|
||||
val intent = configBiometricPrompt(args)
|
||||
var operationType: CipherType
|
||||
|
||||
val data = if (args.dataToEncrypt != null) {
|
||||
operationType = CipherType.ENCRYPT
|
||||
args.dataToEncrypt
|
||||
} else {
|
||||
operationType = CipherType.DECRYPT
|
||||
args.dataToDecrypt
|
||||
}
|
||||
intent.putExtra(ENCRYPT_DECRYPT_DATA, data)
|
||||
intent.putExtra(ENCRYPT_DECRYPT_OPERATION, true)
|
||||
intent.putExtra(CIPHER_OPERATION_TYPE, operationType.ordinal)
|
||||
|
||||
startActivityForResult(invoke, intent, "authenticateResult")
|
||||
}
|
||||
|
||||
|
||||
@ActivityCallback
|
||||
private fun authenticateResult(invoke: Invoke, result: ActivityResult) {
|
||||
val resultCode = result.resultCode
|
||||
@@ -231,8 +281,29 @@ class BiometricPlugin(private val activity: Activity): Plugin(activity) {
|
||||
var errorMessage = data.getStringExtra(
|
||||
RESULT_EXTRA_PREFIX + RESULT_ERROR_MESSAGE
|
||||
)
|
||||
|
||||
val cipherOperation = data.getBooleanExtra(RESULT_EXTRA_PREFIX + ENCRYPT_DECRYPT_OPERATION, false)
|
||||
var resolvedData = JSObject()
|
||||
|
||||
if (cipherOperation) {
|
||||
val processedData = data.getStringExtra(
|
||||
RESULT_EXTRA_PREFIX + RESULT_ENCRYPT_DECRYPT_DATA
|
||||
)
|
||||
|
||||
resolvedData.put(
|
||||
"data",
|
||||
processedData
|
||||
)
|
||||
}
|
||||
|
||||
when (resultType) {
|
||||
BiometryResultType.SUCCESS -> invoke.resolve()
|
||||
BiometryResultType.SUCCESS -> {
|
||||
if (cipherOperation) {
|
||||
invoke.resolve(resolvedData)
|
||||
} else {
|
||||
invoke.resolve()
|
||||
}
|
||||
}
|
||||
BiometryResultType.FAILURE -> // Biometry was successfully presented but was not recognized
|
||||
invoke.reject(errorMessage, BIOMETRIC_FAILURE)
|
||||
|
||||
|
||||
Reference in new issue
Block a user