@ -16,29 +16,120 @@ import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt
import java.util.concurrent.Executor
import java.util.concurrent.Executor
import android.util.Base64
import android.security.keystore.KeyGenParameterSpec
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 ( ) {
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 " )
@SuppressLint ( " WrongConstant " )
override fun onCreate ( savedInstanceState : Bundle ? ) {
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate ( savedInstanceState )
super . onCreate ( savedInstanceState )
setContentView ( R . layout . auth _activity )
setContentView ( R . layout . auth _activity )
val executor = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . P ) {
val promptInfo = createPromptInfo ( )
this . mainExecutor
val prompt = createBiometricPrompt ( )
} else {
Executor { command : Runnable ? ->
cipherOperation = intent . hasExtra ( BiometricPlugin . ENCRYPT _DECRYPT _OPERATION )
Handler ( this . mainLooper ) . post (
if ( ! cipherOperation ) {
command !!
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
}
val cipher = getCipher ( )
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 ( cipherOperation ) {
val cryptoObject = requireNotNull ( authenticationResult ?. cryptoObject )
val cipher = requireNotNull ( cryptoObject . cipher )
var dataToProcess = if ( cipherType == CipherType . ENCRYPT ) {
encryptDecryptData . toByteArray ( )
} else {
val ( _ , encryptedData ) = decodeEncryptedData ( encryptDecryptData )
encryptedData
}
var processedData : ByteArray = cipher . doFinal ( dataToProcess )
if ( cipherType == CipherType . ENCRYPT ) {
// Converts the encrypted data to Base64 string
val encodedString = encodeEncryptedData ( cipher , processedData )
intent . putExtra (
prefix + BiometricPlugin . RESULT _ENCRYPT _DECRYPT _DATA ,
encodedString
)
} else {
// For decryption, return the decrypted string
intent . putExtra (
prefix + BiometricPlugin . RESULT _ENCRYPT _DECRYPT _DATA ,
String ( processedData , Charset . forName ( " UTF-8 " ) )
)
)
}
}
}
}
setResult ( Activity . RESULT _OK , intent )
finish ( )
}
private fun createPromptInfo ( ) : BiometricPrompt . PromptInfo {
val builder = BiometricPrompt . PromptInfo . Builder ( )
val builder = BiometricPrompt . PromptInfo . Builder ( )
val intent = intent
val intent = intent
var title = intent . getStringExtra ( BiometricPlugin . TITLE )
var title = intent . getStringExtra ( BiometricPlugin . TITLE )
val subtitle = intent . getStringExtra ( BiometricPlugin . SUBTITLE )
val subtitle = intent . getStringExtra ( BiometricPlugin . SUBTITLE )
val description = intent . getStringExtra ( BiometricPlugin . REASON )
val description = intent . getStringExtra ( BiometricPlugin . REASON )
allowDeviceCredential = false
allowDeviceCredential = false
cipherOperation = intent . hasExtra ( BiometricPlugin . ENCRYPT _DECRYPT _OPERATION )
// Android docs say we should check if the device is secure before enabling device credential fallback
// Android docs say we should check if the device is secure before enabling device credential fallback
val manager = getSystemService (
val manager = getSystemService (
Context . KEYGUARD _SERVICE
Context . KEYGUARD _SERVICE
@ -54,7 +145,11 @@ class BiometricActivity : AppCompatActivity() {
builder . setTitle ( title ) . setSubtitle ( subtitle ) . setDescription ( description )
builder . setTitle ( title ) . setSubtitle ( subtitle ) . setDescription ( description )
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . R ) {
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 ) {
if ( allowDeviceCredential ) {
authenticators = authenticators or BiometricManager . Authenticators . DEVICE _CREDENTIAL
authenticators = authenticators or BiometricManager . Authenticators . DEVICE _CREDENTIAL
}
}
@ -76,54 +171,108 @@ class BiometricActivity : AppCompatActivity() {
builder . setConfirmationRequired (
builder . setConfirmationRequired (
intent . getBooleanExtra ( BiometricPlugin . CONFIRMATION _REQUIRED , true )
intent . getBooleanExtra ( BiometricPlugin . CONFIRMATION _REQUIRED , true )
)
)
val promptInfo = builder . build ( )
val prompt = BiometricPrompt (
return builder . build ( )
this ,
}
executor ,
object : BiometricPrompt . AuthenticationCallback ( ) {
private fun createBiometricPrompt ( ) : BiometricPrompt {
override fun onAuthenticationError (
val executor = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . P ) {
errorCode : Int ,
this . mainExecutor
errorMessage : CharSequence
} else {
) {
Executor { command : Runnable ? ->
super . onAuthenticationError ( errorCode , errorMessage )
Handler ( this . mainLooper ) . post (
finishActivity (
command !!
BiometryResultType . ERROR ,
)
errorCode ,
errorMessage as String
)
}
override fun onAuthenticationSucceeded (
result : BiometricPrompt . AuthenticationResult
) {
super . onAuthenticationSucceeded ( result )
finishActivity ( )
}
}
}
)
}
prompt . authenticate ( promptInfo )
val callback = object : BiometricPrompt . AuthenticationCallback ( ) {
override fun onAuthenticationError (
errorCode : Int ,
errorMessage : CharSequence
) {
super . onAuthenticationError ( errorCode , errorMessage )
finishActivity (
BiometryResultType . ERROR ,
errorCode ,
errorMessage as String
)
}
override fun onAuthenticationSucceeded (
result : BiometricPrompt . AuthenticationResult
) {
super . onAuthenticationSucceeded ( result )
authenticationResult = result
finishActivity ( )
}
}
return BiometricPrompt ( this , executor , callback )
}
}
@JvmOverloads
/ * *
fun finishActivity (
* Get the key if exists , or create a new one if not
resultType : BiometryResultType = BiometryResultType . SUCCESS ,
* /
errorCode : Int = 0 ,
private fun getEncryptionKey ( ) : SecretKey {
errorMessage : String ? = " "
val keyStore = KeyStore . getInstance ( KEYSTORE _NAME )
) {
keyStore . load ( null )
val intent = Intent ( )
keyStore . getKey ( KEY _ALIAS , null ) ?. let { return it as SecretKey }
val prefix = BiometricPlugin . RESULT _EXTRA _PREFIX
intent
// from here ahead, we will move only if there is not a key with the provided alias
. putExtra ( prefix + BiometricPlugin . RESULT _TYPE , resultType . toString ( ) )
val keyGenerator = KeyGenerator . getInstance ( ENCRYPTION _ALGORITHM , KEYSTORE _NAME )
. putExtra ( prefix + BiometricPlugin . RESULT _ERROR _CODE , errorCode )
. putExtra (
val builder = KeyGenParameterSpec . Builder (
prefix + BiometricPlugin . RESULT _ERROR _MESSAGE ,
KEY_ALIAS ,
errorMessage
KeyProperties . PURPOSE _ENCRYPT or KeyProperties . PURPOSE _DECRYPT
)
)
setResult ( Activity . RESULT _OK , intent )
. setBlockModes ( ENCRYPTION _BLOCK _MODE )
finish ( )
. 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 getCipher ( ) : Cipher {
val biometricKey = getEncryptionKey ( )
val cipher = Cipher . getInstance ( " $ENCRYPTION _ALGORITHM/ $ENCRYPTION _BLOCK_MODE/ $ENCRYPTION _PADDING " )
if ( cipherType == CipherType . ENCRYPT ) {
cipher . init ( Cipher . ENCRYPT _MODE , biometricKey )
} 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 {
companion object {
var allowDeviceCredential = false
var allowDeviceCredential = false
var cipherOperation = false
var encryptDecryptData : String = " "
var authenticationResult : BiometricPrompt . AuthenticationResult ? = null
var cipherType : CipherType = CipherType . ENCRYPT
}
}
}
}