React-native-keychain: Keychain on Android always reports Fingeprint Biometry Type

Created on 4 Mar 2020  路  27Comments  路  Source: oblador/react-native-keychain

Testing out the new Android support on a Pixel 4 and the library always reports Fingerprint as the supported biometry type.
You should use getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE): more info https://developer.android.com/reference/android/content/pm/PackageManager#FEATURE_FACE

I can try making a PR for this later this week / early next week.

Most helpful comment

We did some more testing and here we are - 7.0.0 is released with strong biometric by default. So now majority of users will be prompted with Fingerprint to unlock the Keystore. Please try it out.

All 27 comments

having the same issue with pixel 4. It is misinterpreting the FEATURE_FACE as a fingerprint, and then crashing when i try to get generic password because fingerprint is not set up. Same issue with Galaxy A70. I think PackageManager.FEATURE_FACE completely wreaks this library on android

I'm also experiencing the same issue.

Without waiting for Face biometry to be implemented is there a way to check if a fingerprint has been setup? Ideally getSupportedBiometryType() would only return Fingerprint if it has been configured by the user.

Correction to my post above. It turns out that fingerprints are in fact enrolled and getSupportedBiometryType() returns Fingerprint as it should. However the preferred biometric on the device is set to Face recognition.

The library then allows the setGenericPassword to be called with { accessControl: ACCESS_CONTROL.BIOMETRY_CURRENT_SET }. A subsequent call to getGenericPassword then raises the permissions dialog only showing Face and even if granted the call fails.

Screenshot_20200331-085418_Settings
Screenshot_20200331-085428_Settings
Screen Shot 2020-03-31 at 9 14 19 am

@kuhnza not sure this is the full story, as I was testing with a Pixel 4 which doesn't have a fingerprint scanner, only face recognition, and experienced the same.

Interesting. Diving into the code I think I can see the issue.

https://github.com/oblador/react-native-keychain/blob/9dd18b17e34c966c9b8856a312f828f2fd646d09/android/src/main/java/com/oblador/keychain/DeviceAvailability.java#L20

This code doesn't check if Fingerprint is available, it checks if _any_ biometry is available on the device. This means it could be Fingerprint, Face, both or anything else Android chooses to add support for in future for that matter (see https://developer.android.com/reference/android/hardware/biometrics/BiometricManager#canAuthenticate()).

There's a couple of options as far as I can tell:
1) Restrict test to only return non-null response for Fingerprint
2) Add support for Face and return preferred from getSupportedBiometryType()

The first option is likely simpler to implement and fixes the immediate problem. It does rely on older APIs but something like this might get the job done:

FingerprintManager fm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
return fm.isHardwareDetected() && fm.hasEnrolledFingerprints();

The second option is far better from a user and device support standpoint. Obviously you'd eventually want to support both Face and Fingerprint across both platforms. Judging by the code it looks like it's a lot more involved but TBH I haven't studied it that closely.

@OleksandrKucherenko or @vonovak do either of you have a view on this? Supporting biometrics on Android in the wild is pretty much a non-starter until this is resolved.

After sleeping on it I realised the code for the first option doesn't completely resolve the issue since it doesn't tell you that the device _only_ supports fingerprint. Back to the drawing board.

hello, from a maintainer viewpoint, I prefer _the right solution_ rather than a quick workaround. If you open a PR, please provide as much context as reasonably possible (and ideally add tests) so that it can be properly reviewed. The android code is indeed not straightforward and OleksandrKucherenko has the most up-to-date knowledge of it, so he'd be best reviewer.

will be good to catch crash logs from the device... without logs its hard to repeat the crash, especially when you don't have a pixel4 device ;)

Hello everyone,

I got the same problem reported from a Pixel 4 device. From my understanding the Android SDK does not allow to know which specific authentication method was enabled (face/finger/iris), in Android R it only added an API for checking if it's BIOMETRIC_WEAK or BIOMETRIC_STRONG ( from here )

I was thinking there are a few happy cases where the device has hardware for only one type of biometric, but for those who have at least two there would probably be needed some 'Generic Biometric' value

Code for checking I was thinking of (Kotlin):

val hasFaceAuth = packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
val hasFingerprintAuth = packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
val hasIrisAuth = packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)
val isEnabled = BiometricManager.from(this).canAuthenticate() == BIOMETRIC_SUCCESS

if (isEnabled && hasFingerprintAuth && !hasFaceAuth && !hasIrisAuth) {
    // has only fingerprint
} else if (isEnabled && !hasFingerprintAuth && hasFaceAuth && !hasIrisAuth) {
    // has only face
} else if (isEnabled && !hasFingerprintAuth && !hasFaceAuth && hasIrisAuth) {
    // has only iris
} else if (isEnabled) {
    // generic Biometric; can be any of them
}

What are your thoughts on this?

Thank you.

@tudormarze I've actually used just hasSystemFeature(PackageManager.FEATURE_FACE) in the past with success, on a device with both face recognition and a fingerprint sensor (Galaxy S9).
It returned true when I had enrolled face recognition and false when not.

@vascofg ok, I think that's a bit odd. The documentation for this method states it returns true if the devices supports the feature, else false. I don't have any device with face recognition on which I could test

@oblador could we bump this up? If you have any thoughts on this issue we could try to fork and create a fix for it. At the moment there doesn't seem to be a clear way to handle this one. Users with Face Unlock simply can't use it at the moment.

Is this the fix? https://github.com/oblador/react-native-keychain/pull/342

bump. any update on this issue? #342 does not seem to be the fix

bump. I have the same problem. When only enrolled Iris the call of getSupportedBiometryType() returns null. When adding a fingerprint the call results to Fingerprint. When I try to read the credentials it prompts Iris because thats the preferred biometrics.

I think there is a bit of misunderstanding and confusion with the current API. To explain the current behaviour, we first need to understand how biometrics are classified in Android. I'll add a link to Android CDD that explains the classification in greater details.

There are 3 classes of biometric sensors that are recognised by Android OS:

  • __Class 1 (aka convenience)__ - the weakest biometric type, can only be used for operations that do not require strong user authentication. An example is face recognition on OnePlus 6T+ devices. You can use it to unlock your phone, but not much more. Depending on implementation, Iris biometric can also be classified as Class 1 and not being available in the API.
    Important: This biometric type is not exposed to applications because of its low-reliability rate.
  • __Class 2 (aka weak)__ - More precise and reliable than Class 1, but still allows a pretty big margin of spoofing (see link below for exact numbers). This biometric type is accessible in the API and can be used to authenticate the user (but not unlock the Keystore), by calling BiometricPrompt.authenticate(promptInfo) API docs. By default, a call to BiometricManager.canAuthenticate() is resolving to BIOMETRIC_SUCCESS if at least a Class 2 biometric sensor is available. An example of this type of sensor can be some implementations of Face recognition on some devices (with the exception of Pixel 4 which implemented a Class 3 face recognition biometric sensor).
  • __Class 3 (aka strong)__ - The most precise and spoofing-proof sensor (usually fingerprint). Same as Class 2, available for applications to use via API. Can be used to perform high-security per-operation authentications on KeyStore. For example, authenticating a decryption operation of a single value, locking the KeyStore after the operation. This sort of biometric can be invoked specifically, by calling BiometricPrompt.authenticate(promptInfo, cryptoObject) API docs which will fail if Class 3 biometric sensor is not available.

See Android CDD for details.

Addressing Preferred biometric setting mentioned above - it is a thing that is used by Android system features, like screen unlock and has no impact on the BiometricPrompt API. Only Class 2 and Class 3 are available for the use-cases of this library.

I hope this clarifies why some biometric types are not reported as available, despite being present on the device.

I created a PR with support of Class 3 biometric since it looks like it is the only class that can unlock the keystore - https://github.com/oblador/react-native-keychain/pull/449. There is an issue with the current implementation that BiometricManager.canAuthenticate() will return BIOMETRIC_SUCCESS if at least Class 2 (weak) biometric is present. Also, BiometricPrompt.authenticate is called without CryptoObject which allows the usage of Class 2 biometric, but as a consequence, does not unlock the Keystore, leaving an endless loop of "User not authenticated" errors.

If anyone could try and example app from my PR on the device with Face recognition and Fingerprint - I would be grateful. I only have OnePlus 6T, which has a Class 1 face recognition and Class 3 fingerprint, so I cannot really test the difference.

In my PR I updated androidx.biometric lib to the latest stable and it supports querying and enforcing only Class 3 biometry to make sure the Keystore is unlocked after the successful authentication.

Can we use only fingerprint and ignore face detection on Android?

@FrozenPyrozen It wouldn't cover all devices. Pixel 4 does not have a fingerprint sensor, but nonetheless has Class 3 face recognition that is capable of unlocking the Keychain. But make priority for the fingerprint if it is present on the device is for sure the way to go.

I verified this on Samsung Galaxy S10, which has Class 2 face recognition and Class 3 fingerprint. Only fingerprint is unlocking the Keystore, while face recog gives "User not authenticated" error even on successful verification.

Hi @sgal, your PR #449 is stable enough if I just want to use biometric Fingerprint onAndroid. Android biometrics is not very stable at the moment in this library and it makes sense to me to only allow Fingerprint to my users.

@fedeerbes Yes, my PR is fixing the order of supported biometric types to offer fingerprint if it is present. That should sort out most of the instabilities in biometrics detection. I'm going to test my changes more thoroughly during the coming weekend and try to finalize the PR.

Thanks @sgal. I currently have a Samsung Galaxy J6 and S8+. I'll do some tests with those devices in your PR and let you know

After some testing I was not able to achieve only Fingerprint behavior on your PR @sgal. When I try to read the credentials it prompts other BiometryType selected because thats the preferred biometrics. I want to only let the user set and read credentials through Fingerprint.

@fedeerbes Thanks for testing, I'll try to debug it during the weekend.

Following this issue. Hoping #449 to be merged soon 馃檹

@fedeerbes I tested and found the issue in the example, which is now fixed in my PR. I also verified that the fix works on Samsung Galaxy S20, see https://github.com/oblador/react-native-keychain/pull/449#issuecomment-817136644. Please try the latest commit in my PR #449 on your devices.

Hi @sgal, thanks for taking care of this. I'll run some tests and let you know. I ended up using an AES storage and another library for biometrics, but if I can get #449 working that would be ideal as it will be a more secure option.

We did some more testing and here we are - 7.0.0 is released with strong biometric by default. So now majority of users will be prompted with Fingerprint to unlock the Keystore. Please try it out.

Was this page helpful?
0 / 5 - 0 ratings