diff --git a/android/src/main/java/com/rnbiometrics/CreateSignatureCallback.java b/android/src/main/java/com/rnbiometrics/CreateSignatureCallback.java index 7dc9ad9..43d194b 100644 --- a/android/src/main/java/com/rnbiometrics/CreateSignatureCallback.java +++ b/android/src/main/java/com/rnbiometrics/CreateSignatureCallback.java @@ -1,24 +1,37 @@ package com.rnbiometrics; +import static com.rnbiometrics.ReactNativeBiometrics.initializeSignature; + import android.util.Base64; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.biometric.BiometricPrompt; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.Signature; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; public class CreateSignatureCallback extends BiometricPrompt.AuthenticationCallback { private Promise promise; private String payload; + private boolean allowDeviceCredentials; - public CreateSignatureCallback(Promise promise, String payload) { + public CreateSignatureCallback(Promise promise, String payload, boolean allowDeviceCredentials) { super(); this.promise = promise; this.payload = payload; + this.allowDeviceCredentials = allowDeviceCredentials; } @Override @@ -39,8 +52,7 @@ public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationRes super.onAuthenticationSucceeded(result); try { - BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject(); - Signature cryptoSignature = cryptoObject.getSignature(); + Signature cryptoSignature = getSignature(result); cryptoSignature.update(this.payload.getBytes()); byte[] signed = cryptoSignature.sign(); String signedString = Base64.encodeToString(signed, Base64.DEFAULT); @@ -54,4 +66,14 @@ public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationRes promise.reject("Error creating signature: " + e.getMessage(), "Error creating signature"); } } + + @Nullable + private Signature getSignature(@NonNull BiometricPrompt.AuthenticationResult result) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, InvalidKeyException { + if (this.allowDeviceCredentials) { + return initializeSignature(); + } + + BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject(); + return cryptoObject.getSignature(); + } } diff --git a/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java b/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java index 624ecd9..5ce9ed0 100644 --- a/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java +++ b/android/src/main/java/com/rnbiometrics/ReactNativeBiometrics.java @@ -5,6 +5,7 @@ import android.security.keystore.KeyProperties; import android.util.Base64; +import androidx.annotation.NonNull; import androidx.biometric.BiometricManager; import androidx.biometric.BiometricPrompt; import androidx.biometric.BiometricPrompt.AuthenticationCallback; @@ -20,12 +21,19 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.security.spec.RSAKeyGenParameterSpec; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -36,7 +44,8 @@ public class ReactNativeBiometrics extends ReactContextBaseJavaModule { - protected String biometricKeyAlias = "biometric_key"; + public static final String ALLOW_DEVICE_CREDENTIALS = "allowDeviceCredentials"; + private static final String biometricKeyAlias = "biometric_key"; public ReactNativeBiometrics(ReactApplicationContext reactContext) { super(reactContext); @@ -51,7 +60,7 @@ public String getName() { public void isSensorAvailable(final ReadableMap params, final Promise promise) { try { if (isCurrentSDKMarshmallowOrLater()) { - boolean allowDeviceCredentials = params.getBoolean("allowDeviceCredentials"); + boolean allowDeviceCredentials = params.getBoolean(ALLOW_DEVICE_CREDENTIALS); ReactApplicationContext reactApplicationContext = getReactApplicationContext(); BiometricManager biometricManager = BiometricManager.from(reactApplicationContext); int canAuthenticate = biometricManager.canAuthenticate(getAllowedAuthenticators(allowDeviceCredentials)); @@ -96,13 +105,14 @@ public void createKeys(final ReadableMap params, Promise promise) { if (isCurrentSDKMarshmallowOrLater()) { deleteBiometricKey(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore"); - KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(biometricKeyAlias, KeyProperties.PURPOSE_SIGN) + KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(biometricKeyAlias, KeyProperties.PURPOSE_SIGN) .setDigests(KeyProperties.DIGEST_SHA256) .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) - .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)) - .setUserAuthenticationRequired(true) - .build(); - keyPairGenerator.initialize(keyGenParameterSpec); + .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)); + + setAuthenticationParameters(params, builder); + + keyPairGenerator.initialize(builder.build()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); @@ -121,6 +131,26 @@ public void createKeys(final ReadableMap params, Promise promise) { } } + private void setAuthenticationParameters(ReadableMap params, KeyGenParameterSpec.Builder builder) { + boolean allowDeviceCredentials = params.getBoolean(ALLOW_DEVICE_CREDENTIALS); + + if (isCurrentSDKMarshmallowOrLater()) { + builder.setUserAuthenticationRequired(true); + } + + if (allowDeviceCredentials == false) return; + + if (isCurrentSDK11OrLater()) { + builder.setUserAuthenticationParameters(5, getAllowedAuthenticators(allowDeviceCredentials)); + } else if (isCurrentSDKMarshmallowOrLater()) { + builder.setUserAuthenticationValidityDurationSeconds(5); + } + } + + private boolean isCurrentSDK11OrLater() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; + } + private boolean isCurrentSDKMarshmallowOrLater() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; } @@ -155,23 +185,19 @@ public void run() { String promptMessage = params.getString("promptMessage"); String payload = params.getString("payload"); String cancelButtonText = params.getString("cancelButtonText"); - boolean allowDeviceCredentials = params.getBoolean("allowDeviceCredentials"); - - Signature signature = Signature.getInstance("SHA256withRSA"); - KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); - keyStore.load(null); - - PrivateKey privateKey = (PrivateKey) keyStore.getKey(biometricKeyAlias, null); - signature.initSign(privateKey); + boolean allowDeviceCredentials = params.getBoolean(ALLOW_DEVICE_CREDENTIALS); - BiometricPrompt.CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(signature); + AuthenticationCallback authCallback = new CreateSignatureCallback(promise, payload, allowDeviceCredentials); - AuthenticationCallback authCallback = new CreateSignatureCallback(promise, payload); - FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity(); - Executor executor = Executors.newSingleThreadExecutor(); - BiometricPrompt biometricPrompt = new BiometricPrompt(fragmentActivity, executor, authCallback); + BiometricPrompt biometricPrompt = new BiometricPrompt((FragmentActivity) getCurrentActivity(), Executors.newSingleThreadExecutor(), authCallback); + BiometricPrompt.PromptInfo info = getPromptInfo(promptMessage, cancelButtonText, allowDeviceCredentials); - biometricPrompt.authenticate(getPromptInfo(promptMessage, cancelButtonText, allowDeviceCredentials), cryptoObject); + if (allowDeviceCredentials) { + biometricPrompt.authenticate(info); + } else { + Signature signature = initializeSignature(); + biometricPrompt.authenticate(info, new BiometricPrompt.CryptoObject(signature)); + } } catch (Exception e) { promise.reject("Error signing payload: " + e.getMessage(), "Error generating signature: " + e.getMessage()); } @@ -182,6 +208,16 @@ public void run() { } } + @NonNull + protected static Signature initializeSignature() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, InvalidKeyException { + Signature signature = Signature.getInstance("SHA256withRSA"); + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + PrivateKey privateKey = (PrivateKey) keyStore.getKey(biometricKeyAlias, null); + signature.initSign(privateKey); + return signature; + } + private PromptInfo getPromptInfo(String promptMessage, String cancelButtonText, boolean allowDeviceCredentials) { PromptInfo.Builder builder = new PromptInfo.Builder().setTitle(promptMessage);