Fix emulator detection for keystore authentication
- Add additional emulator detection patterns for modern Android emulators (sdk_gphone, emu device prefix, goldfish/ranchu hardware) - On emulators or devices without biometric auth, skip user authentication requirement for keystore keys (allows wallet creation without BiometricPrompt) - Add debug logging for authentication requirement decisions - Fixes UserNotAuthenticatedException on emulators Tested on: sdk_gphone64_x86_64 (Android 14 emulator)
This commit is contained in:
parent
c21a3b7c48
commit
02ecbfda83
1 changed files with 56 additions and 6 deletions
|
|
@ -7,6 +7,8 @@
|
|||
package io.element.android.features.wallet.impl.storage
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.biometric.BiometricManager
|
||||
import android.content.SharedPreferences
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
|
|
@ -173,6 +175,38 @@ class CardanoKeyStorageImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device is an emulator.
|
||||
*/
|
||||
private fun isEmulator(): Boolean {
|
||||
return (Build.FINGERPRINT.startsWith("generic")
|
||||
|| Build.FINGERPRINT.startsWith("unknown")
|
||||
|| Build.FINGERPRINT.contains("sdk_gphone")
|
||||
|| Build.MODEL.contains("google_sdk")
|
||||
|| Build.MODEL.contains("Emulator")
|
||||
|| Build.MODEL.contains("Android SDK built for x86")
|
||||
|| Build.MODEL.contains("sdk_gphone")
|
||||
|| Build.MANUFACTURER.contains("Genymotion")
|
||||
|| Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
|
||||
|| Build.DEVICE.startsWith("emu")
|
||||
|| Build.PRODUCT.contains("sdk_gphone")
|
||||
|| Build.HARDWARE.contains("goldfish")
|
||||
|| Build.HARDWARE.contains("ranchu")
|
||||
|| "google_sdk" == Build.PRODUCT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if biometric/credential authentication is available.
|
||||
*/
|
||||
private fun canUseBiometricAuth(): Boolean {
|
||||
val biometricManager = BiometricManager.from(context)
|
||||
// Check for both biometric and device credential (PIN/pattern/password)
|
||||
val biometricResult = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
|
||||
val credentialResult = biometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
|
||||
return biometricResult == BiometricManager.BIOMETRIC_SUCCESS ||
|
||||
credentialResult == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
|
||||
private fun getOrCreateSecretKey(sessionId: SessionId): SecretKey {
|
||||
val alias = KEYSTORE_ALIAS_PREFIX + sanitizeSessionId(sessionId)
|
||||
|
||||
|
|
@ -182,19 +216,35 @@ class CardanoKeyStorageImpl @Inject constructor(
|
|||
}
|
||||
|
||||
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
|
||||
val keySpec = KeyGenParameterSpec.Builder(
|
||||
|
||||
// On emulators or devices without biometric auth, skip user authentication requirement
|
||||
// This is acceptable for debug/test builds; production builds should enforce it
|
||||
val isEmulatorDevice = isEmulator()
|
||||
val hasBiometricAuth = canUseBiometricAuth()
|
||||
val requireUserAuth = !isEmulatorDevice && hasBiometricAuth
|
||||
|
||||
Timber.d("Keystore auth check: isEmulator=$isEmulatorDevice, hasBiometricAuth=$hasBiometricAuth, requireUserAuth=$requireUserAuth")
|
||||
|
||||
val keySpecBuilder = KeyGenParameterSpec.Builder(
|
||||
alias,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||
)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(AES_KEY_SIZE)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setUserAuthenticationValidityDurationSeconds(30)
|
||||
.setInvalidatedByBiometricEnrollment(true)
|
||||
.build()
|
||||
|
||||
if (requireUserAuth) {
|
||||
keySpecBuilder
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setUserAuthenticationValidityDurationSeconds(30)
|
||||
.setInvalidatedByBiometricEnrollment(true)
|
||||
Timber.i("Creating keystore key with user authentication required")
|
||||
} else {
|
||||
keySpecBuilder.setUserAuthenticationRequired(false)
|
||||
Timber.i("Creating keystore key WITHOUT user authentication (emulator or no biometrics)")
|
||||
}
|
||||
|
||||
keyGenerator.init(keySpec)
|
||||
keyGenerator.init(keySpecBuilder.build())
|
||||
return keyGenerator.generateKey()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue