diff --git a/README.md b/README.md
index c3be50b7..3d0747e7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[![Travis](https://img.shields.io/travis/oblador/react-native-keychain.svg)](https://travis-ci.org/oblador/react-native-keychain) [![npm](https://img.shields.io/npm/v/react-native-keychain.svg)](https://npmjs.com/package/react-native-keychain) [![npm](https://img.shields.io/npm/dm/react-native-keychain.svg)](https://npmjs.com/package/react-native-keychain)
-Keychain/Keystore Access for React Native.
+Keychain/Keystore Access for React Native.
## Installation
@@ -91,9 +91,9 @@ Inquire if the type of local authentication policy is supported on this device w
Get what type of hardware biometry support the device has. Resolves to a `Keychain.BIOMETRY_TYPE` value when supported, otherwise `null`.
> This method returns `null`, if the device haven't enrolled into fingerprint/FaceId. Even though it has hardware for it.
-### `getSecurityLevel()` (Android only)
+### `getSecurityLevel({ accessControl })` (Android only)
-Get security level that is supported on the current device with the current OS.
+Get security level that is supported on the current device with the current OS for the `accessControl` option.
### Security Levels (Android only)
@@ -107,11 +107,11 @@ If set, `securityLevel` parameter specifies minimum security level that the encr
| Key | Platform | Description | Default |
|---|---|---|---|
-|**`accessControl`**|iOS only|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*None*|
+|**`accessControl`**|All|This dictates how a keychain item may be used, see possible values in `Keychain.ACCESS_CONTROL`. |*None*|
|**`accessible`**|iOS only|This dictates when a keychain item is accessible, see possible values in `Keychain.ACCESSIBLE`. |*`Keychain.ACCESSIBLE.WHEN_UNLOCKED`*|
|**`accessGroup`**|iOS only|In which App Group to share the keychain. Requires additional setup with entitlements. |*None*|
|**`authenticationPrompt`**|iOS only|What to prompt the user when unlocking the keychain with biometry or device password. |`Authenticate to retrieve secret`|
-|**`authenticationType`**|iOS only|Policies specifying which forms of authentication are acceptable. |`Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS`|
+|**`authenticationType`**|All|Policies specifying which forms of authentication are acceptable. |`Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS`|
|**`service`**|All|Reverse domain name qualifier for the service associated with password. |*App bundle ID*|
#### `Keychain.ACCESS_CONTROL` enum
@@ -200,7 +200,7 @@ include ':app'
+ project(':react-native-keychain').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keychain/android')
```
-* Edit `android/app/build.gradle` (note: **app** folder) to look like this:
+* Edit `android/app/build.gradle` (note: **app** folder) to look like this:
```diff
apply plugin: 'com.android.application'
@@ -238,7 +238,7 @@ public class MainActivity extends extends ReactActivity {
...
}
```
-
+
#### Proguard Rules
On Android builds that use proguard (like release), you may see the following error:
@@ -298,7 +298,7 @@ Now your tests should run successfully, though note that writing and reading to
## Notes
-### Android
+### Android
The module will automatically use the appropriate CipherStorage implementation based on API level:
diff --git a/RNKeychainManager/RNKeychainManager.m b/RNKeychainManager/RNKeychainManager.m
index c4ad7e59..da6d8b04 100644
--- a/RNKeychainManager/RNKeychainManager.m
+++ b/RNKeychainManager/RNKeychainManager.m
@@ -285,7 +285,7 @@ - (OSStatus)deleteCredentialsForServer:(NSString *)server
}
#endif
-RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password withSecurityLevel:(__unused NSString *)level resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
+RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password withSecurityLevel:(__unused NSString *)level withAccessControl:(__unused NSString *)accessControl resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
NSDictionary *attributes = attributes = @{
@@ -359,7 +359,7 @@ - (OSStatus)deleteCredentialsForServer:(NSString *)server
return resolve(@(YES));
}
-RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsername:(NSString*)username withPassword:(NSString*)password withSecurityLevel:(__unused NSString *)level withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
+RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsername:(NSString*)username withPassword:(NSString*)password withSecurityLevel:(__unused NSString *)level withAccessControl:(__unused NSString *)accessControl withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
[self deleteCredentialsForServer:server];
diff --git a/android/build.gradle b/android/build.gradle
index bd2fe043..2cb95242 100755
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -52,4 +52,5 @@ dependencies {
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'com.facebook.conceal:conceal:1.1.3@aar'
+ implementation "androidx.biometric:biometric:1.0.0-rc01"
}
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f6b961fd
Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..2d5100df
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Oct 19 13:24:56 BST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/android/gradlew b/android/gradlew
new file mode 100644
index 00000000..cccdd3d5
--- /dev/null
+++ b/android/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 00000000..e95643d6
--- /dev/null
+++ b/android/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index ddbe5066..dbefd667 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,4 +1,6 @@
-
+
+
+
diff --git a/android/src/main/java/com/oblador/keychain/DeviceAvailability.java b/android/src/main/java/com/oblador/keychain/DeviceAvailability.java
index 090d47bb..e42dc100 100644
--- a/android/src/main/java/com/oblador/keychain/DeviceAvailability.java
+++ b/android/src/main/java/com/oblador/keychain/DeviceAvailability.java
@@ -1,5 +1,7 @@
package com.oblador.keychain;
+import android.Manifest;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.content.Context;
import android.app.KeyguardManager;
@@ -10,7 +12,9 @@ public static boolean isFingerprintAuthAvailable(Context context) {
if (android.os.Build.VERSION.SDK_INT >= 23) {
FingerprintManager fingerprintManager =
(FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
- return fingerprintManager != null && fingerprintManager.isHardwareDetected() &&
+ return fingerprintManager != null &&
+ context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED &&
+ fingerprintManager.isHardwareDetected() &&
fingerprintManager.hasEnrolledFingerprints();
}
return false;
diff --git a/android/src/main/java/com/oblador/keychain/KeychainModule.java b/android/src/main/java/com/oblador/keychain/KeychainModule.java
index 61d061e8..39044022 100644
--- a/android/src/main/java/com/oblador/keychain/KeychainModule.java
+++ b/android/src/main/java/com/oblador/keychain/KeychainModule.java
@@ -11,23 +11,28 @@
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
+
import com.oblador.keychain.PrefsStorage.ResultSet;
import com.oblador.keychain.cipherStorage.CipherStorage;
import com.oblador.keychain.cipherStorage.CipherStorage.DecryptionResult;
import com.oblador.keychain.cipherStorage.CipherStorage.EncryptionResult;
+import com.oblador.keychain.cipherStorage.CipherStorage.DecryptionResultHandler;
import com.oblador.keychain.cipherStorage.CipherStorageFacebookConceal;
import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAESCBC;
+import com.oblador.keychain.cipherStorage.CipherStorageKeystoreRSAECB;
import com.oblador.keychain.exceptions.CryptoFailedException;
import com.oblador.keychain.exceptions.EmptyParameterException;
import com.oblador.keychain.exceptions.KeyStoreAccessException;
+import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
-public class KeychainModule extends ReactContextBaseJavaModule {
+import androidx.fragment.app.FragmentActivity;
+public class KeychainModule extends ReactContextBaseJavaModule {
public static final String E_EMPTY_PARAMETERS = "E_EMPTY_PARAMETERS";
public static final String E_CRYPTO_FAILED = "E_CRYPTO_FAILED";
public static final String E_KEYSTORE_ACCESS_ERROR = "E_KEYSTORE_ACCESS_ERROR";
@@ -36,6 +41,14 @@ public class KeychainModule extends ReactContextBaseJavaModule {
public static final String FINGERPRINT_SUPPORTED_NAME = "Fingerprint";
public static final String EMPTY_STRING = "";
+
+ public static final String AUTHENTICATION_TYPE_KEY = "authenticationType";
+ public static final String AUTHENTICATION_TYPE_DEVICE_PASSCODE_OR_BIOMETRICS = "AuthenticationWithBiometricsDevicePasscode";
+ public static final String AUTHENTICATION_TYPE_BIOMETRICS = "AuthenticationWithBiometrics";
+
+ public static final String ACCESS_CONTROL_BIOMETRY_ANY = "BiometryAny";
+ public static final String ACCESS_CONTROL_BIOMETRY_CURRENT_SET = "BiometryCurrentSet";
+
private final Map cipherStorageMap = new HashMap<>();
private final PrefsStorage prefsStorage;
@@ -50,6 +63,9 @@ public KeychainModule(ReactApplicationContext reactContext) {
addCipherStorageToMap(new CipherStorageFacebookConceal(reactContext));
addCipherStorageToMap(new CipherStorageKeystoreAESCBC());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ addCipherStorageToMap(new CipherStorageKeystoreRSAECB());
+ }
}
private void addCipherStorageToMap(CipherStorage cipherStorage) {
@@ -67,20 +83,22 @@ public Map getConstants() {
}
@ReactMethod
- public void getSecurityLevel(Promise promise) {
- promise.resolve(getSecurityLevel().name());
- }
+ public void getSecurityLevel(String accessControl, Promise promise) {
+ boolean useBiometry = getUseBiometry(accessControl);
+ promise.resolve(getSecurityLevel(useBiometry).name());
+ }
@ReactMethod
- public void setGenericPasswordForOptions(String service, String username, String password, String minimumSecurityLevel, Promise promise) {
+ public void setGenericPasswordForOptions(String service, String username, String password, String minimumSecurityLevel, String accessControl, Promise promise) {
try {
SecurityLevel level = SecurityLevel.valueOf(minimumSecurityLevel);
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
throw new EmptyParameterException("you passed empty or null username/password");
}
+
service = getDefaultServiceIfNull(service);
- CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel();
+ CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel(getUseBiometry(accessControl));
validateCipherStorageSecurityLevel(currentCipherStorage, level);
EncryptionResult result = currentCipherStorage.encrypt(service, username, password, level);
@@ -97,59 +115,96 @@ public void setGenericPasswordForOptions(String service, String username, String
}
@ReactMethod
- public void getGenericPasswordForOptions(String service, Promise promise) {
+ public void getGenericPasswordForOptions(String service, final Promise promise) {
+ final String serviceOrDefault = getDefaultServiceIfNull(service);
+ CipherStorage cipherStorage = null;
try {
- service = getDefaultServiceIfNull(service);
-
- CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel();
-
- ResultSet resultSet = prefsStorage.getEncryptedEntry(service);
+ ResultSet resultSet = prefsStorage.getEncryptedEntry(serviceOrDefault);
if (resultSet == null) {
- Log.e(KEYCHAIN_MODULE, "No entry found for service: " + service);
+ Log.e(KEYCHAIN_MODULE, "No entry found for service: " + serviceOrDefault);
promise.resolve(false);
return;
}
- final DecryptionResult decryptionResult = decryptCredentials(service, currentCipherStorage, resultSet);
-
- WritableMap credentials = Arguments.createMap();
-
- credentials.putString("service", service);
- credentials.putString("username", decryptionResult.username);
- credentials.putString("password", decryptionResult.password);
+ // Android < M will throw an exception as biometry is not supported.
+ CipherStorage biometryCipherStorage = null;
+ try {
+ biometryCipherStorage = getCipherStorageForCurrentAPILevel(true);
+ } catch(Exception e) { }
+ final CipherStorage nonBiometryCipherStorage = getCipherStorageForCurrentAPILevel(false);
+ if (biometryCipherStorage != null && resultSet.cipherStorageName.equals(biometryCipherStorage.getCipherStorageName())) {
+ cipherStorage = biometryCipherStorage;
+ } else if (nonBiometryCipherStorage != null && resultSet.cipherStorageName.equals(nonBiometryCipherStorage.getCipherStorageName())) {
+ cipherStorage = nonBiometryCipherStorage;
+ }
- promise.resolve(credentials);
- } catch (KeyStoreAccessException e) {
- Log.e(KEYCHAIN_MODULE, e.getMessage());
- promise.reject(E_KEYSTORE_ACCESS_ERROR, e);
+ final CipherStorage currentCipherStorage = cipherStorage;
+ if (currentCipherStorage != null) {
+ DecryptionResultHandler decryptionHandler = new DecryptionResultHandler() {
+ @Override
+ public void onDecrypt(DecryptionResult decryptionResult, String error) {
+ if (decryptionResult != null) {
+ WritableMap credentials = Arguments.createMap();
+
+ credentials.putString("service", serviceOrDefault);
+ credentials.putString("username", decryptionResult.username);
+ credentials.putString("password", decryptionResult.password);
+
+ promise.resolve(credentials);
+ } else {
+ promise.reject(E_CRYPTO_FAILED, error);
+ }
+ }
+ };
+ // The encrypted data is encrypted using the current CipherStorage, so we just decrypt and return
+ currentCipherStorage.decrypt(decryptionHandler, serviceOrDefault, resultSet.usernameBytes, resultSet.passwordBytes, (FragmentActivity) this.getCurrentActivity());
+ }
+ else {
+ // The encrypted data is encrypted using an older CipherStorage, so we need to decrypt the data first, then encrypt it using the current CipherStorage, then store it again and return
+ final CipherStorage oldCipherStorage = getCipherStorageByName(resultSet.cipherStorageName);
+
+ DecryptionResultHandler decryptionHandler = new DecryptionResultHandler() {
+ @Override
+ public void onDecrypt(DecryptionResult decryptionResult, String error) {
+ if (decryptionResult != null) {
+ WritableMap credentials = Arguments.createMap();
+
+ credentials.putString("service", serviceOrDefault);
+ credentials.putString("username", decryptionResult.username);
+ credentials.putString("password", decryptionResult.password);
+
+ try {
+ migrateCipherStorage(serviceOrDefault, nonBiometryCipherStorage, oldCipherStorage, decryptionResult);
+ } catch (CryptoFailedException e) {
+ Log.e(KEYCHAIN_MODULE, "Migrating to a less safe storage is not allowed. Keeping the old one");
+ } catch (KeyStoreAccessException e) {
+ Log.e(KEYCHAIN_MODULE, e.getMessage());
+ promise.reject(E_KEYSTORE_ACCESS_ERROR, e);
+ }
+
+ promise.resolve(credentials);
+ } else {
+ promise.reject(E_CRYPTO_FAILED, error);
+ }
+ }
+ };
+ // decrypt using the older cipher storage
+ oldCipherStorage.decrypt(decryptionHandler, serviceOrDefault, resultSet.usernameBytes, resultSet.passwordBytes, (FragmentActivity) this.getCurrentActivity());
+ }
+ } catch (InvalidKeyException e) {
+ Log.e(KEYCHAIN_MODULE, String.format("Key for service %s permanently invalidated", serviceOrDefault));
+ try {
+ cipherStorage.removeKey(serviceOrDefault);
+ } catch (Exception error) {
+ Log.e(KEYCHAIN_MODULE, "Failed removing invalidated key: " + error.getMessage());
+ }
+ promise.resolve(false);
} catch (CryptoFailedException e) {
Log.e(KEYCHAIN_MODULE, e.getMessage());
promise.reject(E_CRYPTO_FAILED, e);
}
}
- private DecryptionResult decryptCredentials(String service, CipherStorage currentCipherStorage, ResultSet resultSet) throws CryptoFailedException, KeyStoreAccessException {
- if (resultSet.cipherStorageName.equals(currentCipherStorage.getCipherStorageName())) {
- // The encrypted data is encrypted using the current CipherStorage, so we just decrypt and return
- return currentCipherStorage.decrypt(service, resultSet.usernameBytes, resultSet.passwordBytes);
- }
-
- // The encrypted data is encrypted using an older CipherStorage, so we need to decrypt the data first, then encrypt it using the current CipherStorage, then store it again and return
- CipherStorage oldCipherStorage = getCipherStorageByName(resultSet.cipherStorageName);
- // decrypt using the older cipher storage
-
- DecryptionResult decryptionResult = oldCipherStorage.decrypt(service, resultSet.usernameBytes, resultSet.passwordBytes);
- // encrypt using the current cipher storage
-
- try {
- migrateCipherStorage(service, currentCipherStorage, oldCipherStorage, decryptionResult);
- } catch (CryptoFailedException e) {
- Log.e(KEYCHAIN_MODULE, "Migrating to a less safe storage is not allowed. Keeping the old one");
- }
-
- return decryptionResult;
- }
-
private void migrateCipherStorage(String service, CipherStorage newCipherStorage, CipherStorage oldCipherStorage, DecryptionResult decryptionResult) throws KeyStoreAccessException, CryptoFailedException {
// don't allow to degrade security level when transferring, the new storage should be as safe as the old one.
EncryptionResult encryptionResult = newCipherStorage.encrypt(service, decryptionResult.username, decryptionResult.password, decryptionResult.getSecurityLevel());
@@ -197,8 +252,8 @@ public void hasInternetCredentialsForServer(@NonNull String server, Promise prom
}
@ReactMethod
- public void setInternetCredentialsForServer(@NonNull String server, String username, String password, String minimumSecurityLevel, ReadableMap unusedOptions, Promise promise) {
- setGenericPasswordForOptions(server, username, password, minimumSecurityLevel, promise);
+ public void setInternetCredentialsForServer(@NonNull String server, String username, String password, String minimumSecurityLevel, String accessControl, ReadableMap unusedOptions, Promise promise) {
+ setGenericPasswordForOptions(server, username, password, minimumSecurityLevel, accessControl, promise);
}
@ReactMethod
@@ -211,6 +266,28 @@ public void resetInternetCredentialsForServer(@NonNull String server, ReadableMa
resetGenericPasswordForOptions(server, promise);
}
+ @ReactMethod
+ public void canCheckAuthentication(ReadableMap options, Promise promise) {
+ String authenticationType = null;
+ if (options != null && options.hasKey(AUTHENTICATION_TYPE_KEY)) {
+ authenticationType = options.getString(AUTHENTICATION_TYPE_KEY);
+ }
+
+ if (authenticationType == null
+ || (!authenticationType.equals(AUTHENTICATION_TYPE_DEVICE_PASSCODE_OR_BIOMETRICS)
+ && !authenticationType.equals(AUTHENTICATION_TYPE_BIOMETRICS))) {
+ promise.resolve(false);
+ return;
+ }
+
+ try {
+ boolean fingerprintAuthAvailable = isFingerprintAuthAvailable();
+ promise.resolve(fingerprintAuthAvailable);
+ } catch (Exception e) {
+ promise.resolve(false);
+ }
+ }
+
@ReactMethod
public void getSupportedBiometryType(Promise promise) {
try {
@@ -226,14 +303,22 @@ public void getSupportedBiometryType(Promise promise) {
}
}
+ private boolean getUseBiometry(String accessControl) {
+ return accessControl != null
+ && (accessControl.equals(ACCESS_CONTROL_BIOMETRY_ANY)
+ || accessControl.equals(ACCESS_CONTROL_BIOMETRY_CURRENT_SET));
+ }
+
// The "Current" CipherStorage is the cipherStorage with the highest API level that is lower than or equal to the current API level
- private CipherStorage getCipherStorageForCurrentAPILevel() throws CryptoFailedException {
+ private CipherStorage getCipherStorageForCurrentAPILevel(boolean useBiometry) throws CryptoFailedException {
int currentAPILevel = Build.VERSION.SDK_INT;
CipherStorage currentCipherStorage = null;
for (CipherStorage cipherStorage : cipherStorageMap.values()) {
int cipherStorageAPILevel = cipherStorage.getMinSupportedApiLevel();
+ boolean biometrySupported = cipherStorage.getCipherBiometrySupported();
// Is the cipherStorage supported on the current API level?
- boolean isSupported = (cipherStorageAPILevel <= currentAPILevel);
+ boolean isSupported = (cipherStorageAPILevel <= currentAPILevel)
+ && (biometrySupported == useBiometry);
if (!isSupported) {
continue;
}
@@ -245,6 +330,7 @@ private CipherStorage getCipherStorageForCurrentAPILevel() throws CryptoFailedEx
if (currentCipherStorage == null) {
throw new CryptoFailedException("Unsupported Android SDK " + Build.VERSION.SDK_INT);
}
+
return currentCipherStorage;
}
@@ -262,29 +348,31 @@ private void validateCipherStorageSecurityLevel(CipherStorage cipherStorage, Sec
private CipherStorage getCipherStorageByName(String cipherStorageName) {
- return cipherStorageMap.get(cipherStorageName);
+ CipherStorage storage = cipherStorageMap.get(cipherStorageName);
+
+ return storage;
}
private boolean isFingerprintAuthAvailable() {
return DeviceAvailability.isFingerprintAuthAvailable(getReactApplicationContext());
}
- private boolean isSecureHardwareAvailable() {
+ private boolean isSecureHardwareAvailable(boolean useBiometry) {
try {
- return getCipherStorageForCurrentAPILevel().supportsSecureHardware();
+ return getCipherStorageForCurrentAPILevel(useBiometry).supportsSecureHardware();
} catch (CryptoFailedException e) {
return false;
}
}
- private SecurityLevel getSecurityLevel() {
+ private SecurityLevel getSecurityLevel(boolean useBiometry) {
try {
- CipherStorage storage = getCipherStorageForCurrentAPILevel();
+ CipherStorage storage = getCipherStorageForCurrentAPILevel(useBiometry);
if (!storage.securityLevel().satisfiesSafetyThreshold(SecurityLevel.SECURE_SOFTWARE)) {
return SecurityLevel.ANY;
}
- if (isSecureHardwareAvailable()) {
+ if (isSecureHardwareAvailable(useBiometry)) {
return SecurityLevel.SECURE_HARDWARE;
} else {
return SecurityLevel.SECURE_SOFTWARE;
diff --git a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorage.java b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorage.java
index 7557ce16..2e26d5a8 100644
--- a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorage.java
+++ b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorage.java
@@ -1,11 +1,15 @@
package com.oblador.keychain.cipherStorage;
-import androidx.annotation.NonNull;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import com.oblador.keychain.SecurityLevel;
import com.oblador.keychain.exceptions.CryptoFailedException;
import com.oblador.keychain.exceptions.KeyStoreAccessException;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
public interface CipherStorage {
abstract class CipherResult {
public final T username;
@@ -39,14 +43,20 @@ public SecurityLevel getSecurityLevel() {
}
}
+ interface DecryptionResultHandler {
+ void onDecrypt(DecryptionResult decryptionResult, String error);
+ }
+
EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException;
- DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException;
+ void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password, FragmentActivity activity) throws CryptoFailedException, KeyPermanentlyInvalidatedException;
void removeKey(@NonNull String service) throws KeyStoreAccessException;
String getCipherStorageName();
+ boolean getCipherBiometrySupported();
+
int getMinSupportedApiLevel();
SecurityLevel securityLevel();
diff --git a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
index 3162f994..587ae325 100644
--- a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
+++ b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
@@ -1,7 +1,7 @@
package com.oblador.keychain.cipherStorage;
import android.os.Build;
-import androidx.annotation.NonNull;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import com.facebook.android.crypto.keychain.AndroidConceal;
import com.facebook.android.crypto.keychain.SharedPrefsBackedKeyChain;
@@ -15,6 +15,9 @@
import java.nio.charset.Charset;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
public class CipherStorageFacebookConceal implements CipherStorage {
public static final String CIPHER_STORAGE_NAME = "FacebookConceal";
public static final String KEYCHAIN_DATA = "RN_KEYCHAIN";
@@ -30,11 +33,17 @@ public String getCipherStorageName() {
return CIPHER_STORAGE_NAME;
}
+ @Override
+ public boolean getCipherBiometrySupported() {
+ return false;
+ }
+
@Override
public int getMinSupportedApiLevel() {
return Build.VERSION_CODES.JELLY_BEAN;
}
+
@Override
public SecurityLevel securityLevel() {
return SecurityLevel.ANY;
@@ -69,7 +78,7 @@ public EncryptionResult encrypt(@NonNull String service, @NonNull String usernam
}
@Override
- public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException {
+ public void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password, FragmentActivity activity) throws CryptoFailedException, KeyPermanentlyInvalidatedException {
if (!crypto.isAvailable()) {
throw new CryptoFailedException("Crypto is missing");
}
@@ -80,10 +89,9 @@ public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] usernam
byte[] decryptedUsername = crypto.decrypt(username, usernameEntity);
byte[] decryptedPassword = crypto.decrypt(password, passwordEntity);
- return new DecryptionResult(
+ decryptionResultHandler.onDecrypt(new DecryptionResult(
new String(decryptedUsername, Charset.forName("UTF-8")),
- new String(decryptedPassword, Charset.forName("UTF-8")),
- SecurityLevel.ANY);
+ new String(decryptedPassword, Charset.forName("UTF-8")), SecurityLevel.ANY), null);
} catch (Exception e) {
throw new CryptoFailedException("Decryption failed for service " + service, e);
}
diff --git a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreAESCBC.java b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreAESCBC.java
index d7c9ab5a..4ae160b6 100644
--- a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreAESCBC.java
+++ b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreAESCBC.java
@@ -4,8 +4,11 @@
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
import android.util.Log;
import com.oblador.keychain.SecurityLevel;
@@ -55,6 +58,11 @@ public String getCipherStorageName() {
return CIPHER_STORAGE_NAME;
}
+ @Override
+ public boolean getCipherBiometrySupported() {
+ return false;
+ }
+
@Override
public int getMinSupportedApiLevel() {
return Build.VERSION_CODES.M;
@@ -165,7 +173,7 @@ private void generateKeyAndStoreUnderAlias(@NonNull String service, SecurityLeve
}
@Override
- public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException {
+ public void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password, FragmentActivity activity) throws CryptoFailedException, KeyPermanentlyInvalidatedException {
service = getDefaultServiceIfEmpty(service);
try {
@@ -179,7 +187,9 @@ public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] usernam
String decryptedUsername = decryptBytes(key, username);
String decryptedPassword = decryptBytes(key, password);
- return new DecryptionResult(decryptedUsername, decryptedPassword, getSecurityLevel((SecretKey) key));
+ decryptionResultHandler.onDecrypt(new DecryptionResult(
+ decryptedUsername,
+ decryptedPassword, SecurityLevel.ANY), null);
} catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) {
throw new CryptoFailedException("Could not get key from Keystore", e);
} catch (KeyStoreAccessException e) {
diff --git a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreMarshmallowBase.java b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreMarshmallowBase.java
new file mode 100644
index 00000000..52402124
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreMarshmallowBase.java
@@ -0,0 +1,158 @@
+package com.oblador.keychain.cipherStorage;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.StrongBoxUnavailableException;
+import androidx.annotation.NonNull;
+import android.util.Log;
+
+import com.oblador.keychain.SecurityLevel;
+import com.oblador.keychain.exceptions.CryptoFailedException;
+import com.oblador.keychain.exceptions.KeyStoreAccessException;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+import java.security.spec.InvalidKeySpecException;
+
+import androidx.biometric.BiometricPrompt;
+
+@TargetApi(Build.VERSION_CODES.M)
+public abstract class CipherStorageKeystoreMarshmallowBase implements CipherStorage {
+ public static final String TAG = "Keystore";
+ public static final String DEFAULT_SERVICE = "RN_KEYCHAIN_DEFAULT_ALIAS";
+ public static final String KEYSTORE_TYPE = "AndroidKeyStore";
+
+ @Override
+ public int getMinSupportedApiLevel() {
+ return Build.VERSION_CODES.M;
+ }
+
+ @Override
+ public SecurityLevel securityLevel() {
+ // it can guarantee security levels up to SECURE_HARDWARE/SE/StrongBox
+ return SecurityLevel.SECURE_HARDWARE;
+ }
+
+ @Override
+ public boolean supportsSecureHardware() {
+ final String testKeyAlias = "AndroidKeyStore#supportsSecureHardware";
+
+ try {
+ Key key = tryGenerateRegularSecurityKey(testKeyAlias);
+ return validateKeySecurityLevel(SecurityLevel.SECURE_HARDWARE, key);
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
+ return false;
+ } finally {
+ try {
+ removeKey(testKeyAlias);
+ } catch (KeyStoreAccessException e) {
+ Log.e(TAG, "Unable to remove temp key from keychain", e);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ protected boolean validateKeySecurityLevel(SecurityLevel level, Key generatedKey) {
+ return getSecurityLevel(generatedKey).satisfiesSafetyThreshold(level);
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ protected SecurityLevel getSecurityLevel(Key key) {
+ try {
+ KeyInfo keyInfo = getKeyInfo(key);
+ return keyInfo.isInsideSecureHardware() ? SecurityLevel.SECURE_HARDWARE : SecurityLevel.SECURE_SOFTWARE;
+ } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
+ return SecurityLevel.ANY;
+ }
+ }
+
+ protected abstract KeyInfo getKeyInfo(Key key) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException;
+
+ @Override
+ public void removeKey(@NonNull String service) throws KeyStoreAccessException {
+ service = getDefaultServiceIfEmpty(service);
+
+ try {
+ KeyStore keyStore = getKeyStoreAndLoad();
+
+ if (keyStore.containsAlias(service)) {
+ keyStore.deleteEntry(service);
+ }
+ } catch (KeyStoreException e) {
+ throw new KeyStoreAccessException("Failed to access Keystore", e);
+ } catch (Exception e) {
+ throw new KeyStoreAccessException("Unknown error " + e.getMessage(), e);
+ }
+ }
+
+ protected KeyStore getKeyStoreAndLoad() throws KeyStoreException, KeyStoreAccessException {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ keyStore.load(null);
+ return keyStore;
+ } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
+ throw new KeyStoreAccessException("Could not access Keystore", e);
+ }
+ }
+
+ @NonNull
+ protected String getDefaultServiceIfEmpty(@NonNull String service) {
+ return service.isEmpty() ? DEFAULT_SERVICE : service;
+ }
+
+ protected void generateKeyAndStoreUnderAlias(@NonNull String service, SecurityLevel requiredLevel) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CryptoFailedException {
+ // Firstly, try to generate the key as safe as possible (strongbox).
+ // see https://developer.android.com/training/articles/keystore#HardwareSecurityModule
+ Key key = tryGenerateStrongBoxSecurityKey(service);
+ if (key == null) {
+ // If that is not possible, we generate the key in a regular way
+ // (it still might be generated in hardware, but not in StrongBox)
+ key = tryGenerateRegularSecurityKey(service);
+ }
+
+ if(!validateKeySecurityLevel(requiredLevel, key)) {
+ throw new CryptoFailedException("Cannot generate keys with required security guarantees");
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.P)
+ protected Key tryGenerateStrongBoxSecurityKey(String service) throws NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, NoSuchProviderException {
+ // StrongBox is only supported on Android P and higher
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ return null;
+ }
+ try {
+ return generateKey(getKeyGenSpecBuilder(service).setIsStrongBoxBacked(true).build());
+ } catch (Exception e) {
+ if (e instanceof StrongBoxUnavailableException) {
+ Log.i(TAG, "StrongBox is unavailable on this device");
+ } else {
+ Log.e(TAG, "An error occurred when trying to generate a StrongBoxSecurityKey: " + e.getMessage());
+ }
+ return null;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ protected Key tryGenerateRegularSecurityKey(String service) throws NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, NoSuchProviderException {
+ return generateKey(getKeyGenSpecBuilder(service).build());
+ }
+
+ // returns true if the key was generated successfully
+ @TargetApi(Build.VERSION_CODES.M)
+ protected abstract Key generateKey(KeyGenParameterSpec spec) throws NoSuchProviderException,
+ NoSuchAlgorithmException, InvalidAlgorithmParameterException;
+
+ @TargetApi(Build.VERSION_CODES.M)
+ protected abstract KeyGenParameterSpec.Builder getKeyGenSpecBuilder(String service);
+}
diff --git a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreRSAECB.java b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreRSAECB.java
new file mode 100644
index 00000000..a501adc4
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreRSAECB.java
@@ -0,0 +1,270 @@
+package com.oblador.keychain.cipherStorage;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserNotAuthenticatedException;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+
+import com.facebook.react.bridge.ReactContext;
+import com.oblador.keychain.SecurityLevel;
+import com.oblador.keychain.components.BiometricPromptHelper;
+import com.oblador.keychain.exceptions.CryptoFailedException;
+import com.oblador.keychain.exceptions.KeyStoreAccessException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.concurrent.Executors;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+@RequiresApi(Build.VERSION_CODES.M)
+public class CipherStorageKeystoreRSAECB extends CipherStorageKeystoreMarshmallowBase implements BiometricPromptHelper.BiometricAuthenticationResult {
+ private static final String CIPHER_STORAGE_NAME = "KeystoreRSAECB";
+ private static final String KEYSTORE_TYPE = "AndroidKeyStore";
+ private static final String ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_RSA;
+ private static final String ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_ECB;
+ private static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
+ private static final String ENCRYPTION_TRANSFORMATION =
+ ENCRYPTION_ALGORITHM + "/" +
+ ENCRYPTION_BLOCK_MODE + "/" +
+ ENCRYPTION_PADDING;
+ private static final int ENCRYPTION_KEY_SIZE = 3072;
+
+ private class CipherDecryptionParams {
+ private final DecryptionResultHandler resultHandler;
+ private final Key key;
+ private final byte[] username;
+ private final byte[] password;
+
+ private CipherDecryptionParams(DecryptionResultHandler handler, Key key, byte[] username, byte[] password) {
+ this.resultHandler = handler;
+ this.key = key;
+ this.username = username;
+ this.password = password;
+ }
+ }
+
+ private CipherDecryptionParams mDecryptParams;
+
+ @Override
+ public void onError(int errorCode, @Nullable CharSequence errString) {
+ if (mDecryptParams != null && mDecryptParams.resultHandler != null) {
+ mDecryptParams.resultHandler.onDecrypt(null, errString != null ? errString.toString() : "Impossible to authenticate");
+ mDecryptParams = null;
+ }
+ }
+
+ @Override
+ public void onSuccess() {
+ if (mDecryptParams != null && mDecryptParams.resultHandler != null) {
+ try {
+ String decryptedUsername = decryptBytes(mDecryptParams.key, mDecryptParams.username);
+ String decryptedPassword = decryptBytes(mDecryptParams.key, mDecryptParams.password);
+ mDecryptParams.resultHandler.onDecrypt(new DecryptionResult(decryptedUsername, decryptedPassword, SecurityLevel.ANY), null);
+ } catch (Exception e) {
+ mDecryptParams.resultHandler.onDecrypt(null, e.getMessage());
+ }
+ mDecryptParams = null;
+ }
+ }
+
+ // returns true if the key was generated successfully
+ @TargetApi(Build.VERSION_CODES.M)
+ protected Key generateKey(KeyGenParameterSpec spec) throws NoSuchProviderException,
+ NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance(ENCRYPTION_ALGORITHM, KEYSTORE_TYPE);
+ generator.initialize(spec);
+ return generator.generateKeyPair().getPrivate();
+ }
+
+ protected KeyInfo getKeyInfo(Key key) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
+ KeyFactory factory = KeyFactory.getInstance(key.getAlgorithm(), KEYSTORE_TYPE);
+ KeyInfo keyInfo = factory.getKeySpec(key, KeyInfo.class);
+ return keyInfo;
+ }
+
+ @Override
+ public String getCipherStorageName() {
+ return CIPHER_STORAGE_NAME;
+ }
+
+ @Override
+ public boolean getCipherBiometrySupported() {
+ return true;
+ }
+
+ @Override
+ public int getMinSupportedApiLevel() {
+ return Build.VERSION_CODES.M;
+ }
+
+ @Override
+ public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException {
+ service = getDefaultServiceIfEmpty(service);
+
+ try {
+ KeyStore keyStore = getKeyStoreAndLoad();
+
+ if (!keyStore.containsAlias(service)) {
+ generateKeyAndStoreUnderAlias(service, level);
+ }
+
+ KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPTION_ALGORITHM);
+ PublicKey publicKey = keyStore.getCertificate(service).getPublicKey();
+ KeySpec spec = new X509EncodedKeySpec(publicKey.getEncoded());
+ Key key = keyFactory.generatePublic(spec);
+
+ byte[] encryptedUsername = encryptString(key, service, username);
+ byte[] encryptedPassword = encryptString(key, service, password);
+
+ return new EncryptionResult(encryptedUsername, encryptedPassword, this);
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
+ throw new CryptoFailedException("Could not encrypt data for service " + service, e);
+ } catch (KeyStoreException | KeyStoreAccessException e) {
+ throw new CryptoFailedException("Could not access Keystore for service " + service, e);
+ } catch (Exception e) {
+ throw new CryptoFailedException("Unknown error: " + e.getMessage(), e);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ protected KeyGenParameterSpec.Builder getKeyGenSpecBuilder(String service) {
+ return new KeyGenParameterSpec.Builder(
+ service,
+ KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
+ .setBlockModes(ENCRYPTION_BLOCK_MODE)
+ .setEncryptionPaddings(ENCRYPTION_PADDING)
+ .setRandomizedEncryptionRequired(true)
+ .setUserAuthenticationRequired(true)
+ .setUserAuthenticationValidityDurationSeconds(1)
+ .setKeySize(ENCRYPTION_KEY_SIZE);
+ }
+
+ @Override
+ public void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password, FragmentActivity activity) throws CryptoFailedException, KeyPermanentlyInvalidatedException {
+ service = getDefaultServiceIfEmpty(service);
+
+ BiometricPromptHelper mBiometricHelper = new BiometricPromptHelper(activity);
+ KeyStore keyStore;
+ Key key;
+
+ try {
+ keyStore = getKeyStoreAndLoad();
+ key = keyStore.getKey(service, null);
+ } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) {
+ throw new CryptoFailedException("Could not get key from Keystore", e);
+ } catch (KeyStoreAccessException e) {
+ throw new CryptoFailedException("Could not access Keystore", e);
+ } catch (Exception e) {
+ throw new CryptoFailedException("Unknown error: " + e.getMessage(), e);
+ }
+
+ String decryptedUsername;
+ String decryptedPassword;
+ try {
+ // try to get a Cipher, if exception is thrown, authentication is needed
+ decryptedUsername = decryptBytes(key, username);
+ decryptedPassword = decryptBytes(key, password);
+ } catch (UserNotAuthenticatedException e) {
+ mDecryptParams = new CipherDecryptionParams(decryptionResultHandler, key, username, password);
+ if (!mBiometricHelper.canStartFingerprintAuthentication()) {
+ throw new CryptoFailedException("Could not start fingerprint Authentication");
+ }
+ try {
+ // The Cipher is locked, we will decrypt once fingerprint is recognised.
+ mBiometricHelper.startFingerprintAuthentication(this);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ throw new CryptoFailedException("Could not start fingerprint Authentication", e1);
+ }
+ return;
+ }
+
+ // The Cipher is unlocked, we can decrypt straight away.
+ decryptionResultHandler.onDecrypt(new DecryptionResult(decryptedUsername, decryptedPassword, SecurityLevel.ANY), null);
+ }
+
+ private byte[] encryptString(Key key, String service, String value) throws CryptoFailedException {
+ try {
+ Cipher cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ // encrypt the value using a CipherOutputStream
+ CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
+ cipherOutputStream.write(value.getBytes("UTF-8"));
+ cipherOutputStream.close();
+ return outputStream.toByteArray();
+ } catch (Exception e) {
+ throw new CryptoFailedException("Could not encrypt value for service " + service, e);
+ }
+ }
+
+ private Cipher getDecryptionCipher(Key key) throws CryptoFailedException, UserNotAuthenticatedException, KeyPermanentlyInvalidatedException {
+ try {
+ Cipher cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION);
+ // read the initialization vector from the beginning of the stream
+ cipher.init(Cipher.DECRYPT_MODE, key);
+
+ return cipher;
+ } catch (UserNotAuthenticatedException | KeyPermanentlyInvalidatedException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CryptoFailedException("Could not generate cipher", e);
+ }
+ }
+
+ private String decryptBytes(Key key, byte[] bytes) throws CryptoFailedException, UserNotAuthenticatedException, KeyPermanentlyInvalidatedException {
+ try {
+ Cipher cipher = getDecryptionCipher(key);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ // decrypt the bytes using a CipherInputStream
+ CipherInputStream cipherInputStream = new CipherInputStream(
+ inputStream, cipher);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ while (true) {
+ int n = cipherInputStream.read(buffer, 0, buffer.length);
+ if (n <= 0) {
+ break;
+ }
+ output.write(buffer, 0, n);
+ }
+ return new String(output.toByteArray(), Charset.forName("UTF-8"));
+ } catch (IOException e) {
+ throw new CryptoFailedException("Could not decrypt bytes", e);
+ }
+ }
+}
diff --git a/android/src/main/java/com/oblador/keychain/components/BiometricPromptHelper.java b/android/src/main/java/com/oblador/keychain/components/BiometricPromptHelper.java
new file mode 100644
index 00000000..f4c39285
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/components/BiometricPromptHelper.java
@@ -0,0 +1,83 @@
+package com.oblador.keychain.components;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.CancellationSignal;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.oblador.keychain.SecurityLevel;
+
+import java.security.Key;
+import java.util.concurrent.Executors;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.fragment.app.FragmentActivity;
+
+@TargetApi(Build.VERSION_CODES.M)
+public class BiometricPromptHelper extends BiometricPrompt.AuthenticationCallback {
+ private CancellationSignal mBiometricPromptCancellationSignal;
+ private BiometricPrompt mBiometricPrompt;
+ private FragmentActivity mActivity;
+ private BiometricAuthenticationResult mBiometricAuthenticationResult;
+
+ public interface BiometricAuthenticationResult {
+ void onError(int errorCode, @Nullable CharSequence errString);
+ void onSuccess ();
+ }
+
+ public BiometricPromptHelper(FragmentActivity activity) {
+ mActivity = activity;
+ }
+
+ // We don't really want to do anything here
+ // the error message is handled by the info view.
+ // And we don't want to throw an error, as the user can still retry.
+ @Override
+ public void onAuthenticationFailed() {}
+
+ @Override
+ public void onAuthenticationError(int errorCode, @Nullable CharSequence errString) {
+ mBiometricPromptCancellationSignal.cancel();
+ mBiometricAuthenticationResult.onError(errorCode, errString);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
+ mBiometricAuthenticationResult.onSuccess();
+ }
+
+ public boolean canStartFingerprintAuthentication() {
+ return BiometricManager.from(mActivity).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS;
+ }
+
+ public void startFingerprintAuthentication(BiometricAuthenticationResult biometricAuthenticationResult) throws Exception {
+ mBiometricAuthenticationResult = biometricAuthenticationResult;
+ // If we have a previous cancellationSignal, cancel it.
+ if (mBiometricPromptCancellationSignal != null) {
+ mBiometricPromptCancellationSignal.cancel();
+ }
+
+ mBiometricPrompt = new BiometricPrompt(mActivity, Executors.newSingleThreadExecutor(), this);
+ mBiometricPromptCancellationSignal = new CancellationSignal();
+
+ final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
+ .setTitle("Authentication required")
+ .setNegativeButtonText("Cancel")
+ .setSubtitle("Please use biometric authentication to unlock the app")
+ .build();
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mBiometricPrompt.authenticate(promptInfo);
+ }
+ });
+ }
+}
diff --git a/android/src/main/res/color-v26/biometric_error_color.xml b/android/src/main/res/color-v26/biometric_error_color.xml
new file mode 100644
index 00000000..443e7f5a
--- /dev/null
+++ b/android/src/main/res/color-v26/biometric_error_color.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/res/drawable-v23/fingerprint_dialog_error_to_fp.xml b/android/src/main/res/drawable-v23/fingerprint_dialog_error_to_fp.xml
new file mode 100644
index 00000000..ef2bfaa3
--- /dev/null
+++ b/android/src/main/res/drawable-v23/fingerprint_dialog_error_to_fp.xml
@@ -0,0 +1,354 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/res/drawable-v23/fingerprint_dialog_fp_to_error.xml b/android/src/main/res/drawable-v23/fingerprint_dialog_fp_to_error.xml
new file mode 100644
index 00000000..1b788582
--- /dev/null
+++ b/android/src/main/res/drawable-v23/fingerprint_dialog_fp_to_error.xml
@@ -0,0 +1,589 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/res/layout/fingerprint_dialog_layout.xml b/android/src/main/res/layout/fingerprint_dialog_layout.xml
new file mode 100644
index 00000000..a29c1a9d
--- /dev/null
+++ b/android/src/main/res/layout/fingerprint_dialog_layout.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/src/main/res/values-af/strings.xml b/android/src/main/res/values-af/strings.xml
new file mode 100644
index 00000000..f82e5feb
--- /dev/null
+++ b/android/src/main/res/values-af/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Raak die vingerafdruksensor"
+ "Nie herken nie"
+ "Hulpboodskapgebied"
+ "Vingerafdrukhardeware is nie beskikbaar nie."
+ "Geen vingerafdrukke is geregistreer nie."
+ "Hierdie toetstel het nie \'n vingerafdruksensor nie"
+ "Vingerafdrukhandeling is deur gebruiker gekanselleer."
+ "Te veel pogings. Probeer later weer."
+ "Onbekende fout"
+
diff --git a/android/src/main/res/values-am/strings.xml b/android/src/main/res/values-am/strings.xml
new file mode 100644
index 00000000..378326da
--- /dev/null
+++ b/android/src/main/res/values-am/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "የጣት አሻራ ዳሳሹን ይንኩ"
+ "አልታወቀም"
+ "የእገዛ መልዕክት አካባቢ"
+ "የጣት አሻራ ሃርድዌር የለም።"
+ "ምንም የጣት አሻራዎች አልተመዘገቡም።"
+ "ይህ መሣሪያ የጣት አሻራ ዳሳሽ የለውም"
+ "የጣት አሻራ ክወና በተጠቃሚ ተሰርዟል።"
+ "በጣም ብዙ ሙከራዎች። እባክዎ ቆይተው እንደገና ይሞክሩ።"
+ "ያልታወቀ ስህተት"
+
diff --git a/android/src/main/res/values-ar/strings.xml b/android/src/main/res/values-ar/strings.xml
new file mode 100644
index 00000000..902ba58d
--- /dev/null
+++ b/android/src/main/res/values-ar/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "المس زر استشعار بصمات الإصبع"
+ "لم يتم التعرف عليها."
+ "منطقة رسالة المساعدة"
+ "جهاز بصمة الإصبع غير متاح."
+ "ليست هناك بصمات إصبع مسجَّلة."
+ "لا يحتوي هذا الجهاز على جهاز استشعار بصمات الأصابع."
+ "تم إلغاء تشغيل بصمة الإصبع بواسطة المستخدم."
+ "تم إجراء محاولات كثيرة جدًا. يُرجى المحاولة مرة أخرى لاحقًا."
+ "خطأ غير معروف"
+
diff --git a/android/src/main/res/values-as/strings.xml b/android/src/main/res/values-as/strings.xml
new file mode 100644
index 00000000..2ab09ac3
--- /dev/null
+++ b/android/src/main/res/values-as/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো স্পৰ্শ কৰক"
+ "চিনাক্ত কৰিব পৰা নাই"
+ "সহায় বাৰ্তাৰ ক্ষেত্ৰ"
+ "ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ নাই।"
+ "কোনো ফিংগাৰপ্ৰিণ্ট যোগ কৰা নহ\'ল।"
+ "এই ডিভাইচটোত ফিংগাৰপ্ৰিণ্ট ছেন্সৰ নাই"
+ "ব্যৱহাৰকাৰীয়ে ফিংগাৰপ্ৰিণ্টৰ দ্বাৰা বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কাৰ্য বাতিল কৰিছে।"
+ "অতি বেছি চেষ্টা অনুগ্ৰহ কৰি পিছত আকৌ চেষ্টা কৰক।"
+ "অজ্ঞাত আসোঁৱাহ"
+
diff --git a/android/src/main/res/values-az/strings.xml b/android/src/main/res/values-az/strings.xml
new file mode 100644
index 00000000..1d430a70
--- /dev/null
+++ b/android/src/main/res/values-az/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Barmaq izi sensoruna klikləyin"
+ "Tanınmır"
+ "Yardım mesajı bölməsi"
+ "Barmaq izi avadanlığı əlçatan deyil."
+ "Barmaq izi qeydə alınmayıb."
+ "Bu cihazda barmaq izi sensoru yoxdur"
+ "Barmaq izi əməliyyatı istifadəçi tərəfindən ləğv edildi."
+ "Həddən çox cəhd oldu. Sonra sınayın."
+ "Naməlum xəta"
+
diff --git a/android/src/main/res/values-b+sr+Latn/strings.xml b/android/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 00000000..1b92557e
--- /dev/null
+++ b/android/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dodirn. senzor za otisak prsta"
+ "Nije prepoznat"
+ "Oblast poruke za pomoć"
+ "Hardver za otiske prstiju nije dostupan."
+ "Nije registrovan nijedan otisak prsta."
+ "Ovaj uređaj nema senzor za otisak prsta"
+ "Korisnik je otkazao radnju sa otiskom prsta."
+ "Previše pokušaja. Probajte ponovo kasnije."
+ "Nepoznata greška"
+
diff --git a/android/src/main/res/values-be/strings.xml b/android/src/main/res/values-be/strings.xml
new file mode 100644
index 00000000..077483c4
--- /dev/null
+++ b/android/src/main/res/values-be/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Дакраніцеся да сканера адбіткаў пальцаў"
+ "Не распазнана"
+ "Поле даведачнага паведамлення"
+ "Апаратныя сродкі для зняцця адбіткаў пальцаў недаступныя."
+ "Адбіткі пальцаў не зарэгістраваны."
+ "На гэтай прыладзе няма сканера адбіткаў пальцаў"
+ "Аўтэнтыфікацыя па адбітках пальцаў скасавана карыстальнікам."
+ "Занадта шмат спроб. Паўтарыце спробу пазней."
+ "Невядомая памылка"
+
diff --git a/android/src/main/res/values-bg/strings.xml b/android/src/main/res/values-bg/strings.xml
new file mode 100644
index 00000000..1c7e60b7
--- /dev/null
+++ b/android/src/main/res/values-bg/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Докоснете сензора за отпечатъци"
+ "Не е разпознато"
+ "Област за помощно съобщение"
+ "Хардуерът за отпечатъци не е налице."
+ "Няма регистрирани отпечатъци."
+ "Това устройство няма сензор за отпечатъци"
+ "Операцията за удостоверяване чрез отпечатък бе анулирана от потребителя."
+ "Твърде много опити. Моля, опитайте отново по-късно."
+ "Неизвестна грешка"
+
diff --git a/android/src/main/res/values-bn/strings.xml b/android/src/main/res/values-bn/strings.xml
new file mode 100644
index 00000000..f336e0de
--- /dev/null
+++ b/android/src/main/res/values-bn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "আঙ্গুলের ছাপের সেন্সর টাচ করুন"
+ "শনাক্ত করা যায়নি"
+ "সহায়তার মেসেজ দেখানোর জায়গা"
+ "আঙ্গুলের ছাপ নেওয়ার হার্ডওয়্যার উপলভ্য নয়।"
+ "কোনও আঙ্গুলের ছাপ নথিভুক্ত নেই।"
+ "এই ডিভাইসে আঙ্গুলের ছাপ নেওয়ার সেন্সর নেই"
+ "ব্যবহারকারী আঙ্গুলের ছাপ নেওয়ার অপারেশনটি বাতিল করেছেন।"
+ "অনেকবার চেষ্টা করেছেন। পরে আবার চেষ্টা করুন।"
+ "অজানা সমস্যা"
+
diff --git a/android/src/main/res/values-bs/strings.xml b/android/src/main/res/values-bs/strings.xml
new file mode 100644
index 00000000..d5243421
--- /dev/null
+++ b/android/src/main/res/values-bs/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dodirnite senzor za otisak prsta"
+ "Nije prepoznato"
+ "Prostor za poruku za pomoć"
+ "Hardver za otisak prsta nije dostupan."
+ "Nije prijavljen nijedan otisak prsta."
+ "Ovaj uređaj nema senzor za otisak prsta"
+ "Korisnik je otkazao radnju s otiskom prsta."
+ "Previše pokušaja. Pokušajte ponovo kasnije."
+ "Nepoznata greška"
+
diff --git a/android/src/main/res/values-ca/strings.xml b/android/src/main/res/values-ca/strings.xml
new file mode 100644
index 00000000..b682b3d0
--- /dev/null
+++ b/android/src/main/res/values-ca/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toca sensor d\'empremtes digitals"
+ "No s\'ha reconegut"
+ "Àrea de missatge d\'ajuda"
+ "El maquinari per a empremtes digitals no està disponible."
+ "No s\'ha registrat cap empremta digital."
+ "Aquest dispositiu no té sensor d\'empremtes digitals"
+ "L\'usuari ha cancel·lat l\'operació d\'empremta digital."
+ "Massa intents. Torna-ho a provar més tard."
+ "Error desconegut"
+
diff --git a/android/src/main/res/values-cs/strings.xml b/android/src/main/res/values-cs/strings.xml
new file mode 100644
index 00000000..ec8335dd
--- /dev/null
+++ b/android/src/main/res/values-cs/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dotkněte se snímače otisků prstů"
+ "Nerozpoznáno"
+ "Oblast pro zprávu nápovědy"
+ "Není k dispozici hardware ke snímání otisků prstů."
+ "Nejsou zaregistrovány žádné otisky prstů."
+ "Toto zařízení nemá snímač otisků prstů"
+ "Uživatel operaci s otiskem prstu zrušil."
+ "Příliš mnoho pokusů. Zkuste to později."
+ "Neznámá chyba"
+
diff --git a/android/src/main/res/values-da/strings.xml b/android/src/main/res/values-da/strings.xml
new file mode 100644
index 00000000..276bcf30
--- /dev/null
+++ b/android/src/main/res/values-da/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Sæt finger på fingeraftrykslæser"
+ "Ikke genkendt"
+ "Område med hjælpemeddelelse"
+ "Hardwaren til fingeraftryk er ikke tilgængelig."
+ "Der er ikke registreret nogen fingeraftryk."
+ "Denne enhed har ingen fingeraftrykslæser"
+ "Fingeraftrykshandlingen blev annulleret af brugeren."
+ "Der var for mange forsøg Prøv igen senere."
+ "Ukendt fejl"
+
diff --git a/android/src/main/res/values-de/strings.xml b/android/src/main/res/values-de/strings.xml
new file mode 100644
index 00000000..7a957d72
--- /dev/null
+++ b/android/src/main/res/values-de/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Fingerabdrucksensor berühren"
+ "Nicht erkannt"
+ "Bereich für die Hilfemeldung"
+ "Fingerabdruckhardware nicht verfügbar."
+ "Keine Fingerabdrücke erfasst."
+ "Dieses Gerät hat keinen Fingerabdrucksensor"
+ "Vorgang der Fingerabdruckauthentifizierung vom Nutzer abgebrochen."
+ "Zu viele Versuche. Versuche es bitte später noch einmal."
+ "Unbekannter Fehler"
+
diff --git a/android/src/main/res/values-el/strings.xml b/android/src/main/res/values-el/strings.xml
new file mode 100644
index 00000000..ce9ff06f
--- /dev/null
+++ b/android/src/main/res/values-el/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Αγγίξτε τον αισθ. δακτ. αποτ."
+ "Δεν αναγνωρίστηκε"
+ "Περιοχή μηνυμάτων βοήθειας"
+ "Ο εξοπλισμός δακτυλικού αποτυπώματος δεν είναι διαθέσιμος."
+ "Δεν έχουν καταχωριστεί δακτυλικά αποτυπώματα."
+ "Αυτή η συσκευή δεν διαθέτει αισθητήρα δακτυλικών αποτυπωμάτων"
+ "Η λειτουργία δακτυλικού αποτυπώματος ακυρώθηκε από τον χρήστη."
+ "Υπερβολικά πολλές προσπάθειες. Δοκιμάστε ξανά αργότερα."
+ "Άγνωστο σφάλμα"
+
diff --git a/android/src/main/res/values-en-rAU/strings.xml b/android/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 00000000..6aca8292
--- /dev/null
+++ b/android/src/main/res/values-en-rAU/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Touch the fingerprint sensor"
+ "Not recognised"
+ "Help message area"
+ "Fingerprint hardware not available."
+ "No fingerprints enrolled."
+ "This device does not have a fingerprint sensor"
+ "Fingerprint operation cancelled by user."
+ "Too many attempts. Please try again later."
+ "Unknown error"
+
diff --git a/android/src/main/res/values-en-rGB/strings.xml b/android/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 00000000..6aca8292
--- /dev/null
+++ b/android/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Touch the fingerprint sensor"
+ "Not recognised"
+ "Help message area"
+ "Fingerprint hardware not available."
+ "No fingerprints enrolled."
+ "This device does not have a fingerprint sensor"
+ "Fingerprint operation cancelled by user."
+ "Too many attempts. Please try again later."
+ "Unknown error"
+
diff --git a/android/src/main/res/values-en-rIN/strings.xml b/android/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 00000000..6aca8292
--- /dev/null
+++ b/android/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Touch the fingerprint sensor"
+ "Not recognised"
+ "Help message area"
+ "Fingerprint hardware not available."
+ "No fingerprints enrolled."
+ "This device does not have a fingerprint sensor"
+ "Fingerprint operation cancelled by user."
+ "Too many attempts. Please try again later."
+ "Unknown error"
+
diff --git a/android/src/main/res/values-es-rUS/strings.xml b/android/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 00000000..c11d24ca
--- /dev/null
+++ b/android/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toca el sensor de huellas dig."
+ "No se reconoció"
+ "Área de mensajes de ayuda"
+ "El hardware para detectar huellas digitales no está disponible."
+ "No se registraron huellas digitales."
+ "Este dispositivo no tiene sensor de huellas digitales"
+ "El usuario canceló la operación de huella digital."
+ "Demasiados intentos. Vuelve a intentarlo más tarde."
+ "Error desconocido"
+
diff --git a/android/src/main/res/values-es/strings.xml b/android/src/main/res/values-es/strings.xml
new file mode 100644
index 00000000..cac5baf2
--- /dev/null
+++ b/android/src/main/res/values-es/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toca sensor huellas digitales"
+ "No se reconoce"
+ "Área de mensaje de ayuda"
+ "El hardware de huella digital no está disponible."
+ "No se ha registrado ninguna huella digital."
+ "El dispositivo no tiene ningún sensor de huellas digitales"
+ "El usuario ha cancelado la operación de huella digital."
+ "Has realizado demasiados intentos. Vuelve a probar más tarde."
+ "Error desconocido"
+
diff --git a/android/src/main/res/values-et/strings.xml b/android/src/main/res/values-et/strings.xml
new file mode 100644
index 00000000..a6715fec
--- /dev/null
+++ b/android/src/main/res/values-et/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Puudutage sõrmejäljeandurit"
+ "Ei tuvastatud"
+ "Abisõnumi ala"
+ "Sõrmejälje riistvara pole saadaval."
+ "Ühtegi sõrmejälge pole registreeritud."
+ "Selles seadmes pole sõrmejäljeandurit"
+ "Kasutaja tühistas sõrmejälje kasutamise."
+ "Liiga palju katseid. Proovige hiljem uuesti."
+ "Tundmatu viga"
+
diff --git a/android/src/main/res/values-eu/strings.xml b/android/src/main/res/values-eu/strings.xml
new file mode 100644
index 00000000..28a28dc0
--- /dev/null
+++ b/android/src/main/res/values-eu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Sakatu hatz-marken sentsorea"
+ "Ez da ezagutu"
+ "Laguntza-mezuaren eremua"
+ "Hatz-marken hardwarea ez dago erabilgarri."
+ "Ez da erregistratu hatz-markarik."
+ "Gailu honek ez du hatz-marken sentsorerik"
+ "Erabiltzaileak bertan behera utzi du hatz-marka bidezko eragiketa."
+ "Saiakera gehiegi egin dira. Saiatu berriro geroago."
+ "Errore ezezaguna"
+
diff --git a/android/src/main/res/values-fa/strings.xml b/android/src/main/res/values-fa/strings.xml
new file mode 100644
index 00000000..182ba44f
--- /dev/null
+++ b/android/src/main/res/values-fa/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "حسگر اثر انگشت را لمس کنید"
+ "شناسایی نشد"
+ "بخش پیام راهنما"
+ "سختافزار اثرانگشت در دسترس نیست."
+ "اثر انگشتی ثبت نشده است."
+ "این دستگاه حسگر اثر انگشت ندارد"
+ "کاربر عملیات اثر انگشت را لغو کرد"
+ "تعداد تلاشها بیش از حد مجاز است. لطفاً بعداً دوباره امتحان کنید."
+ "خطای ناشناس"
+
diff --git a/android/src/main/res/values-fi/strings.xml b/android/src/main/res/values-fi/strings.xml
new file mode 100644
index 00000000..e90defad
--- /dev/null
+++ b/android/src/main/res/values-fi/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Kosketa sormenjälkitunnistinta"
+ "Ei tunnistettu"
+ "Ohjeviestialue"
+ "Sormenjälkilaitteisto ei ole käytettävissä."
+ "Sormenjälkiä ei ole lisätty."
+ "Laitteessa ei ole sormenjälkitunnistinta."
+ "Käyttäjä peruutti sormenjälkitoiminnon."
+ "Liian monta epäonnistunutta yritystä. Yritä myöhemmin uudelleen."
+ "Tuntematon virhe"
+
diff --git a/android/src/main/res/values-fr-rCA/strings.xml b/android/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 00000000..9cb2e9a0
--- /dev/null
+++ b/android/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Touch. capteur empr. digitales"
+ "Doigt non reconnu"
+ "Zone de message d\'aide"
+ "Le matériel de lecture d\'empreintes digitales n\'est pas accessible."
+ "Aucune empreinte digitale enregistrée."
+ "Cet appareil ne possède pas de capteur d\'empreintes digitales"
+ "L\'opération d\'authentification par empreinte digitale a été annulée par l\'utilisateur."
+ "Trop de tentatives. Veuillez réessayer plus tard."
+ "Erreur inconnue"
+
diff --git a/android/src/main/res/values-fr/strings.xml b/android/src/main/res/values-fr/strings.xml
new file mode 100644
index 00000000..4127fe30
--- /dev/null
+++ b/android/src/main/res/values-fr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Appuyez sur lecteur d\'empreinte"
+ "Non reconnue"
+ "Zone de message d\'aide"
+ "Matériel de lecture d\'empreinte digitale indisponible."
+ "Aucune empreinte digitale enregistrée."
+ "Aucun lecteur d\'empreinte digitale n\'est installé sur cet appareil"
+ "Opération d\'authentification par empreinte digitale annulée par l\'utilisateur."
+ "Tentatives trop nombreuses. Veuillez réessayer plus tard."
+ "Erreur inconnue"
+
diff --git a/android/src/main/res/values-gl/strings.xml b/android/src/main/res/values-gl/strings.xml
new file mode 100644
index 00000000..ed87183b
--- /dev/null
+++ b/android/src/main/res/values-gl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toca o sensor de impresión dixital"
+ "Non se recoñeceu"
+ "Área de mensaxes de axuda"
+ "O hardware de impresión dixital non está dispoñible."
+ "Non se rexistraron impresións dixitais."
+ "Este dispositivo non ten sensor de impresión dixital"
+ "O usuario cancelou a operación da impresión dixital."
+ "Tentáchelo demasiadas veces. Proba de novo máis tarde."
+ "Produciuse un erro descoñecido"
+
diff --git a/android/src/main/res/values-gu/strings.xml b/android/src/main/res/values-gu/strings.xml
new file mode 100644
index 00000000..9cbceb6c
--- /dev/null
+++ b/android/src/main/res/values-gu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ફિંગરપ્રિન્ટના સેન્સરને સ્પર્શ કરો"
+ "ઓળખાયેલ નથી"
+ "સહાય સંદેશનું ક્ષેત્ર"
+ "ફિંગરપ્રિન્ટ હાર્ડવેર ઉપલબ્ધ નથી."
+ "કોઈ ફિંગરપ્રિન્ટની નોંધણી કરવામાં આવી નથી."
+ "આ ડિવાઇસમાં કોઈ ફિંગરપ્રિન્ટ સેન્સર નથી"
+ "ફિંગરપ્રિન્ટ ચકાસવાની પ્રક્રિયા વપરાશકર્તાએ રદ કરી."
+ "ખૂબ વધારે પ્રયત્નો કર્યા. કૃપા કરીને પછીથી ફરી પ્રયાસ કરો."
+ "અજાણી ભૂલ"
+
diff --git a/android/src/main/res/values-hi/strings.xml b/android/src/main/res/values-hi/strings.xml
new file mode 100644
index 00000000..ee692ba6
--- /dev/null
+++ b/android/src/main/res/values-hi/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "फ़िंगरप्रिंट सेंसर को छुएं"
+ "पहचान नहीं हो पाई"
+ "सहायता का मैसेज दिखाने की जगह"
+ "फ़िंगरप्रिंट हार्डवेयर मौजूद नहीं है."
+ "कोई फ़िंगरप्रिंट रजिस्टर नहीं किया गया है."
+ "इस डिवाइस में फ़िंगरप्रिंट सेंसर नहीं है"
+ "उपयोगकर्ता ने फिंगरप्रिंट की पुष्टि की कार्रवाई रद्द कर दी है."
+ "कई बार कोशिश की जा चुकी है. कृपया बाद में फिर से कोशिश करें."
+ "अनजान गड़बड़ी"
+
diff --git a/android/src/main/res/values-hr/strings.xml b/android/src/main/res/values-hr/strings.xml
new file mode 100644
index 00000000..953fc5dd
--- /dev/null
+++ b/android/src/main/res/values-hr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dodirnite senzor otiska prsta"
+ "Nije prepoznat"
+ "Područje poruke za pomoć"
+ "Hardver za otisak prsta nije dostupan."
+ "Nije registriran nijedan otisak prsta."
+ "Ovaj uređaj nema senzor otiska prsta"
+ "Radnju s otiskom prsta otkazao je korisnik."
+ "Previše pokušaja. Pokušajte ponovno kasnije."
+ "Nepoznata pogreška"
+
diff --git a/android/src/main/res/values-hu/strings.xml b/android/src/main/res/values-hu/strings.xml
new file mode 100644
index 00000000..326f7d98
--- /dev/null
+++ b/android/src/main/res/values-hu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Érintse meg az ujjlenyomat-érzékelőt"
+ "Nem ismerhető fel"
+ "Súgószöveg területe"
+ "Az ujjlenyomathoz szükséges hardverhez nem lehet hozzáférni."
+ "Nincsenek regisztrált ujjlenyomatok."
+ "Ez az eszköz nem rendelkezik ujjlenyomat-érzékelővel"
+ "Az ujjlenyomattal kapcsolatos műveletet a felhasználó megszakította."
+ "Túl sok próbálkozás. Próbálja újra később."
+ "Ismeretlen hiba"
+
diff --git a/android/src/main/res/values-hy/strings.xml b/android/src/main/res/values-hy/strings.xml
new file mode 100644
index 00000000..b10d90d9
--- /dev/null
+++ b/android/src/main/res/values-hy/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Հպեք մատնահետքերի սկաներին"
+ "Չհաջողվեց ճանաչել"
+ "Օգնության հաղորդագրության դաշտ"
+ "Մատնահետքերի սարքն անհասանելի է:"
+ "Գրանցված մատնահետքեր չկան:"
+ "Սարքը չունի մատնահետքերի սկաներ"
+ "Մատնահետքով նույնականացման գործողությունը չեղարկվել է օգտատիրոջ կողմից:"
+ "Չափից շատ փորձեր եք կատարել: Փորձեք ավելի ուշ:"
+ "Անհայտ սխալ"
+
diff --git a/android/src/main/res/values-in/strings.xml b/android/src/main/res/values-in/strings.xml
new file mode 100644
index 00000000..4096d25b
--- /dev/null
+++ b/android/src/main/res/values-in/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Sentuh sensor sidik jari"
+ "Tidak dikenali"
+ "Area pesan bantuan"
+ "Hardware sidik jari tidak tersedia."
+ "Tidak ada sidik jari yang terdaftar."
+ "Perangkat ini tidak memiliki sensor sidik jari"
+ "Operasi sidik jari dibatalkan oleh pengguna."
+ "Terlalu banyak upaya yang gagal. Coba lagi nanti."
+ "Error tidak diketahui"
+
diff --git a/android/src/main/res/values-is/strings.xml b/android/src/main/res/values-is/strings.xml
new file mode 100644
index 00000000..0326ad40
--- /dev/null
+++ b/android/src/main/res/values-is/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Snertu fingrafaralesarann"
+ "Þekktist ekki"
+ "Svæði hjálparskilaboða"
+ "Fingrafarsvélbúnaður ekki til staðar."
+ "Engin fingraför hafa verið skráð."
+ "Þetta tæki er ekki með fingrafaralesara"
+ "Notandi hætti við að nota fingrafar."
+ "Of margar tilraunir. Reyndu aftur síðar."
+ "Óþekkt villa"
+
diff --git a/android/src/main/res/values-it/strings.xml b/android/src/main/res/values-it/strings.xml
new file mode 100644
index 00000000..52f31b9a
--- /dev/null
+++ b/android/src/main/res/values-it/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tocca sensore impronte digitali"
+ "Non riconosciuta"
+ "Area dei messaggi di assistenza"
+ "Hardware per l\'impronta digitale non disponibile."
+ "Nessuna impronta digitale registrata."
+ "Questo dispositivo non è dotato di sensore di impronte digitali"
+ "Operazione di autenticazione dell\'impronta digitale annullata dall\'utente."
+ "È stato effettuato un numero eccessivo di tentativi. Riprova più tardi."
+ "Errore sconosciuto"
+
diff --git a/android/src/main/res/values-iw/strings.xml b/android/src/main/res/values-iw/strings.xml
new file mode 100644
index 00000000..8b7e3c31
--- /dev/null
+++ b/android/src/main/res/values-iw/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "יש לגעת בחיישן טביעות האצבע"
+ "לא זוהתה"
+ "אזור הודעת עזרה"
+ "החומרה בשביל טביעת אצבע אינה זמינה."
+ "לא נרשמו טביעות אצבע."
+ "במכשיר זה אין חיישן טביעות אצבע"
+ "פעולת טביעת האצבע בוטלה בידי המשתמש."
+ "ניסית יותר מדי פעמים. יש לנסות שוב מאוחר יותר."
+ "שגיאה לא ידועה"
+
diff --git a/android/src/main/res/values-ja/strings.xml b/android/src/main/res/values-ja/strings.xml
new file mode 100644
index 00000000..ba741b9c
--- /dev/null
+++ b/android/src/main/res/values-ja/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "指紋認証センサーをタップ"
+ "認識されませんでした"
+ "ヘルプ メッセージ領域"
+ "指紋認証ハードウェアは使用できません。"
+ "指紋が登録されていません。"
+ "このデバイスには指紋認証センサーがありません"
+ "指紋認証操作がユーザーによりキャンセルされました。"
+ "入力回数が上限を超えました。しばらくしてからもう一度お試しください。"
+ "不明なエラーです"
+
diff --git a/android/src/main/res/values-ka/strings.xml b/android/src/main/res/values-ka/strings.xml
new file mode 100644
index 00000000..9780ba88
--- /dev/null
+++ b/android/src/main/res/values-ka/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "შეეხეთ თითის ანაბეჭდის სენსორს"
+ "არ არის ამოცნობილი"
+ "დამხმარე შეტყობინების არე"
+ "თითის ანაბეჭდის აპარატურა მიუწვდომელია."
+ "თითის ანაბეჭდები არ არის რეგისტრირებული."
+ "ამ მოწყობილობას არ აქვს თითის ანაბეჭდის სენსორი"
+ "თითის ანაბეჭდის ოპერაცია გააუქმა მომხმარებელმა."
+ "მეტისმეტად ბევრი მცდელობა იყო. გთხოვთ, ცადოთ მოგვიანებით."
+ "უცნობი შეცდომა"
+
diff --git a/android/src/main/res/values-kk/strings.xml b/android/src/main/res/values-kk/strings.xml
new file mode 100644
index 00000000..5ba17648
--- /dev/null
+++ b/android/src/main/res/values-kk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Саусақ ізін оқу сканерін түртіңіз"
+ "Танылмады"
+ "Анықтама хабары аумағы"
+ "Саусақ ізі жабдығы қолжетімді емес."
+ "Саусақ іздері тіркелмеген."
+ "Бұл құрылғыда саусақ ізін оқу сканері жоқ"
+ "Пайдаланушы саусақ ізі операциясынан бас тартты."
+ "Тым көп әрекет жасалды. Кейінірек қайталап көріңіз."
+ "Белгісіз қате"
+
diff --git a/android/src/main/res/values-km/strings.xml b/android/src/main/res/values-km/strings.xml
new file mode 100644
index 00000000..a7f641e6
--- /dev/null
+++ b/android/src/main/res/values-km/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ប៉ះឧបករណ៍ចាប់ស្នាមម្រាមដៃ"
+ "មិនអាចសម្គាល់បានទេ"
+ "តំបន់សារជំនួយ"
+ "មិនអាចប្រើហាតវែរស្នាមម្រាមដៃបានទេ។"
+ "មិនមានការថតបញ្ចូលស្នាមម្រាមដៃទេ។"
+ "ឧបករណ៍នេះមិនមានឧបករណ៍ចាប់ស្នាមម្រាមដៃទេ"
+ "ប្រតិបត្តិការស្នាមម្រាមដៃត្រូវបានបោះបង់ដោយអ្នកប្រើប្រាស់។"
+ "ផ្ទៀងផ្ទាត់មិនត្រឹមត្រូវច្រើនដងពេក។ សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ។"
+ "មានបញ្ហាដែលមិនស្គាល់"
+
diff --git a/android/src/main/res/values-kn/strings.xml b/android/src/main/res/values-kn/strings.xml
new file mode 100644
index 00000000..ac2a497d
--- /dev/null
+++ b/android/src/main/res/values-kn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ"
+ "ಗುರುತಿಸಲಾಗಿಲ್ಲ"
+ "ಸಹಾಯ ಸಂದೇಶ ಪ್ರದೇಶ"
+ "ಫಿಂಗರ್ ಫ್ರಿಂಟ್ ಹಾರ್ಡ್ವೇರ್ ಲಭ್ಯವಿಲ್ಲ."
+ "ಯಾವುದೇ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ನು ನೋಂದಣಿ ಮಾಡಿಲ್ಲ."
+ "ಈ ಸಾಧನವು ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆನ್ಸಾರ್ ಅನ್ನು ಹೊಂದಿಲ್ಲ"
+ "ಬಳಕೆದಾರರಿಂದ ಫಿಂಗರ್ ಫ್ರಿಂಟ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ರದ್ದುಪಡಿಸಲಾಗಿದೆ."
+ "ಹಲವು ಬಾರಿ ಪ್ರಯತ್ನಿಸಿರುವಿರಿ ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."
+ "ಅಪರಿಚಿತ ದೋಷ"
+
diff --git a/android/src/main/res/values-ko/strings.xml b/android/src/main/res/values-ko/strings.xml
new file mode 100644
index 00000000..fe338bd8
--- /dev/null
+++ b/android/src/main/res/values-ko/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "지문 센서를 터치하세요."
+ "인식할 수 없음"
+ "도움말 메시지 영역"
+ "지문 인식 하드웨어를 사용할 수 없습니다."
+ "등록된 지문이 없습니다."
+ "기기에 지문 센서가 없습니다."
+ "사용자가 지문 인식 작업을 취소했습니다."
+ "시도 횟수가 너무 많습니다. 나중에 다시 시도해 주세요."
+ "알 수 없는 오류"
+
diff --git a/android/src/main/res/values-ky/strings.xml b/android/src/main/res/values-ky/strings.xml
new file mode 100644
index 00000000..faecae05
--- /dev/null
+++ b/android/src/main/res/values-ky/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Манжа изинин сенсорун басыңыз"
+ "Таанылган жок"
+ "Жардам билдирүүсү"
+ "Манжа изинин аппараттык камсыздоосу жеткиликтүү эмес."
+ "Бир да манжа изи катталган эмес."
+ "Бул түзмөктө манжа изинин сенсору жок"
+ "Манжа изи менен аныктыгын текшерүүнү колдонуучу жокко чыгарды."
+ "Өтө көп жолу аракет кылдыңыз. Кийинчерээк кайра кайталап көрүңүз."
+ "Белгисиз ката"
+
diff --git a/android/src/main/res/values-lo/strings.xml b/android/src/main/res/values-lo/strings.xml
new file mode 100644
index 00000000..d9d50b7c
--- /dev/null
+++ b/android/src/main/res/values-lo/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ແຕະເຊັນເຊີລາຍນິ້ວມື"
+ "ບໍ່ຮັບຮູ້"
+ "ພື້ນທີ່ຂໍ້ຄວາມຊ່ວຍເຫຼືອ"
+ "ບໍ່ມີຮາດແວລາຍນີ້ວມືທີ່ສາມາດໃຊ້ໄດ້."
+ "ບໍ່ມີການລົງທະບຽນລາຍນິ້ວມື."
+ "ອຸປະກອນນີ້ບໍ່ມີເຊັນເຊີລາຍນິ້ວມື"
+ "ຜູ້ໃຊ້ໄດ້ຍົກເລີກຄຳສັ່ງລາຍນິ້ວມືແລ້ວ."
+ "ມີຄວາມພະຍາຍາມຫຼາຍເທື່ອເກີນໄປ. ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ."
+ "ຄວາມຜິດພາດທີ່ບໍ່ຮູ້ຈັກ"
+
diff --git a/android/src/main/res/values-lt/strings.xml b/android/src/main/res/values-lt/strings.xml
new file mode 100644
index 00000000..4bda21ed
--- /dev/null
+++ b/android/src/main/res/values-lt/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Palieskite piršto antspaudo jutiklį"
+ "Neatpažinta"
+ "Pagalbos pranešimo sritis"
+ "Piršto antspaudo aparatinė įranga nepasiekiama."
+ "Neužregistruota jokių kontrolinių kodų."
+ "Šiame įrenginyje nėra piršto antspaudo jutiklio"
+ "Piršto antspaudo operaciją atšaukė naudotojas."
+ "Per daug bandymų. Vėliau bandykite dar kartą."
+ "Nežinoma klaida"
+
diff --git a/android/src/main/res/values-lv/strings.xml b/android/src/main/res/values-lv/strings.xml
new file mode 100644
index 00000000..4bcda7fd
--- /dev/null
+++ b/android/src/main/res/values-lv/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Pieskarieties pirksta nospieduma sensoram"
+ "Nav atpazīts"
+ "Palīdzības ziņojuma apgabals"
+ "Pirksta nospieduma aparatūra nav pieejama."
+ "Nav reģistrēts neviens pirksta nospiedums."
+ "Šajā ierīcē nav pirksta nospieduma sensora"
+ "Lietotājs atcēla pirksta nospieduma darbību."
+ "Pārāk daudz mēģinājumu. Lūdzu, vēlāk mēģiniet vēlreiz."
+ "Nezināma kļūda"
+
diff --git a/android/src/main/res/values-mk/strings.xml b/android/src/main/res/values-mk/strings.xml
new file mode 100644
index 00000000..d3c3f98a
--- /dev/null
+++ b/android/src/main/res/values-mk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Допрете го сенз. за отпечатоци"
+ "Непознат"
+ "Поле за пораки за помош"
+ "Нема достапен хардвер за отпечатоци."
+ "Нема запишани отпечатоци."
+ "Уредов нема сензор за отпечатоци"
+ "Корисникот ја откажа потврдата со отпечаток."
+ "Премногу обиди. Обидете се повторно подоцна."
+ "Непозната грешка"
+
diff --git a/android/src/main/res/values-ml/strings.xml b/android/src/main/res/values-ml/strings.xml
new file mode 100644
index 00000000..d244e8ce
--- /dev/null
+++ b/android/src/main/res/values-ml/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "വിരലടയാള സെൻസർ സ്പർശിക്കുക"
+ "തിരിച്ചറിഞ്ഞില്ല"
+ "സഹായ സന്ദേശ ഏരിയ"
+ "ഫിംഗർപ്രിന്റ് ഹാർഡ്വെയർ ലഭ്യമല്ല."
+ "ഫിംഗർപ്രിന്റുകളൊന്നും എൻറോൾ ചെയ്തിട്ടില്ല."
+ "ഈ ഉപകരണത്തിൽ വിരലടയാള സെൻസർ ഇല്ല"
+ "ഫിംഗർപ്രിന്റിന്റെ പ്രവർത്തനം ഉപയോക്താവ് റദ്ദാക്കി."
+ "നിരവധി ശ്രമങ്ങൾ. പിന്നീട് വീണ്ടും ശ്രമിക്കുക."
+ "അജ്ഞാത പിശക്"
+
diff --git a/android/src/main/res/values-mn/strings.xml b/android/src/main/res/values-mn/strings.xml
new file mode 100644
index 00000000..da900393
--- /dev/null
+++ b/android/src/main/res/values-mn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Хурууны хээ мэдрэгчид хүрнэ үү"
+ "Таниагүй"
+ "Туслах мессежний хэсэг"
+ "Хурууны хээний техник хангамж боломжгүй байна."
+ "Бүртгүүлсэн хурууны хээ алга байна."
+ "Энэ төхөөрөмжид хурууны хээ мэдрэгч алга байна"
+ "Хэрэглэгч хурууны хээний баталгаажуулалтыг болиулсан байна."
+ "Хэт олон удаа оролдлоо. Та дараа дахин оролдоно уу."
+ "Тодорхойгүй алдаа гарлаа"
+
diff --git a/android/src/main/res/values-mr/strings.xml b/android/src/main/res/values-mr/strings.xml
new file mode 100644
index 00000000..58c9c048
--- /dev/null
+++ b/android/src/main/res/values-mr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "फिंगरप्रिंट सेन्सरला स्पर्श करा"
+ "ओळखले नाही"
+ "मदत मेसेज परिसर"
+ "फिंगरप्रिंट हार्डवेअर उपलब्ध नाही."
+ "कोणत्याही फिंगरप्रिंटची नोंद झाली नाही."
+ "या डिव्हाइसवर फिंगरप्रिंट सेन्सर नाही"
+ "वापरकर्त्याने फिंगरप्रिंट ऑपरेशन रद्द केले."
+ "खूप जास्त प्रयत्न. कृपया नंतर पुन्हा प्रयत्न करा."
+ "अज्ञात एरर"
+
diff --git a/android/src/main/res/values-ms/strings.xml b/android/src/main/res/values-ms/strings.xml
new file mode 100644
index 00000000..472319fb
--- /dev/null
+++ b/android/src/main/res/values-ms/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Sentuh penderia cap jari"
+ "Tidak dikenali"
+ "Bahagian mesej bantuan"
+ "Perkakasan cap jari tidak tersedia."
+ "Tiada cap jari didaftarkan."
+ "Peranti ini tiada penderia cap jari"
+ "Pengendalian cap jari dibatalkan oleh pengguna."
+ "Terlalu banyak percubaan. Sila cuba sebentar lagi."
+ "Ralat tidak diketahui"
+
diff --git a/android/src/main/res/values-my/strings.xml b/android/src/main/res/values-my/strings.xml
new file mode 100644
index 00000000..ac23e9d1
--- /dev/null
+++ b/android/src/main/res/values-my/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "လက်ဗွေအာရုံခံကိရိယာကို တို့ပါ"
+ "မသိပါ"
+ "အကူအညီမက်ဆေ့ဂျ် နေရာ"
+ "လက်ဗွေစက်ပစ္စည်း မရနိုင်ပါ။"
+ "မည်သည့် လက်ဗွေကိုမျှ ထည့်သွင်းမထားပါ။"
+ "ဤစက်ပစ္စည်းတွင် လက်ဗွေအာရုံခံကိရိယာ မရှိပါ"
+ "လက်ဗွေဖြင့် အထောက်အထားစိစစ်ခြင်းကို အသုံးပြုသူက ပယ်ဖျက်ထားသည်။"
+ "အကြိမ်များစွာ စမ်းပြီးပါပြီ။ နောက်မှ ထပ်စမ်းကြည့်ပါ။"
+ "အမျိုးအမည်မသိ အမှား"
+
diff --git a/android/src/main/res/values-nb/strings.xml b/android/src/main/res/values-nb/strings.xml
new file mode 100644
index 00000000..d7740688
--- /dev/null
+++ b/android/src/main/res/values-nb/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Trykk på fingeravtrykkssensoren"
+ "Ikke gjenkjent"
+ "Område for hjelpemelding"
+ "Maskinvare for fingeravtrykk er ikke tilgjengelig."
+ "Ingen fingeravtrykk er registrert."
+ "Denne enheten har ikke fingeravtrykkssensor"
+ "Fingeravtrykk-operasjonen ble avbrutt av brukeren."
+ "Du har gjort for mange forsøk. Prøv på nytt senere."
+ "Ukjent feil"
+
diff --git a/android/src/main/res/values-ne/strings.xml b/android/src/main/res/values-ne/strings.xml
new file mode 100644
index 00000000..5b6715f1
--- /dev/null
+++ b/android/src/main/res/values-ne/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "फिंगरप्रिन्ट सेन्सरमा छुनुहोस्"
+ "पहिचान भएन"
+ "मद्दतसम्बन्धी सन्देशको क्षेत्र"
+ "फिंगरप्रिन्ट हार्डवेयर उपलब्ध छैन।"
+ "कुनै पनि फिंगरप्रिन्ट दर्ता गरिएको छैन।"
+ "यो यन्त्रमा कुनै फिंगरप्रिन्ट सेन्सर छैन"
+ "प्रयोगकर्ताले फिंगरप्रिन्टसम्बन्धी कारबाही रद्द गर्नुभयो।"
+ "अत्यधिक पटक प्रयासहरू गरिए। कृपया पछि फेरि प्रयास गर्नुहोस्।"
+ "अज्ञात त्रुटि"
+
diff --git a/android/src/main/res/values-nl/strings.xml b/android/src/main/res/values-nl/strings.xml
new file mode 100644
index 00000000..dd02f57a
--- /dev/null
+++ b/android/src/main/res/values-nl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Raak de vingerafdruksensor aan"
+ "Niet herkend"
+ "Gebied voor Help-berichten"
+ "Hardware voor vingerafdruk niet beschikbaar."
+ "Geen vingerafdrukken geregistreerd."
+ "Dit apparaat heeft geen vingerafdruksensor"
+ "Vingerafdrukverificatie geannuleerd door gebruiker."
+ "Te veel pogingen. Probeer het later opnieuw."
+ "Onbekende fout"
+
diff --git a/android/src/main/res/values-or/strings.xml b/android/src/main/res/values-or/strings.xml
new file mode 100644
index 00000000..7b3eac07
--- /dev/null
+++ b/android/src/main/res/values-or/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ଟିପଚିହ୍ନ ସେନସର୍କୁ ଛୁଅଁନ୍ତୁ"
+ "ଚିହ୍ନଟ ହେଲାନାହିଁ"
+ "ସହାୟତା ମେସେଜ୍ କ୍ଷେତ୍ର"
+ "ଆଙ୍ଗୁଠି ଚିହ୍ନ ହାର୍ଡୱେର୍ ଉପଲବ୍ଧ ନାହିଁ।"
+ "କୌଣସି ଆଙ୍ଗୁଠି ଚିହ୍ନ ପଞ୍ଜୀକୃତ ହୋଇନାହିଁ।"
+ "ଏହି ଡିଭାଇସ୍ରେ ଆଙ୍ଗୁଠି ଚିହ୍ନ ସେନସର୍ ନାହିଁ"
+ "ୟୁଜର୍ଙ୍କ ଦ୍ଵାରା ଆଙ୍ଗୁଠି ଚିହ୍ନ ନେବା କାମକୁ କ୍ୟାନ୍ସଲ୍ କରିଦିଆଯାଇଛି।"
+ "ବହୁତ ଅଧିକ ପ୍ରଚେଷ୍ଟା। ଦୟାକରି ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"
+ "ଅଜଣା ତ୍ରୁଟି"
+
diff --git a/android/src/main/res/values-pa/strings.xml b/android/src/main/res/values-pa/strings.xml
new file mode 100644
index 00000000..dbc68341
--- /dev/null
+++ b/android/src/main/res/values-pa/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਪਰਸ਼ ਕਰੋ"
+ "ਪਛਾਣ ਨਹੀਂ ਹੋਈ"
+ "ਮਦਦ ਸੁਨੇਹਾ ਖੇਤਰ"
+ "ਫਿੰਗਰਪ੍ਰਿੰਟ ਹਾਰਡਵੇਅਰ ਉਪਲਬਧ ਨਹੀਂ।"
+ "ਕੋਈ ਫਿੰਗਰਪ੍ਰਿੰਟ ਦਰਜ ਨਹੀਂ ਕੀਤਾ ਗਿਆ।"
+ "ਇਸ ਡੀਵਾਈਸ ਵਿੱਚ ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨਹੀਂ ਹੈ"
+ "ਫਿੰਗਰਪ੍ਰਿੰਟ ਦੇ ਪੁਸ਼ਟੀਕਰਨ ਦੀ ਕਾਰਵਾਈ ਵਰਤੋਂਕਾਰ ਵੱਲੋਂ ਰੱਦ ਕੀਤੀ ਗਈ।"
+ "ਬਹੁਤ ਜ਼ਿਆਦਾ ਕੋਸ਼ਿਸ਼ਾਂ। ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"
+ "ਅਗਿਆਤ ਗੜਬੜ"
+
diff --git a/android/src/main/res/values-pl/strings.xml b/android/src/main/res/values-pl/strings.xml
new file mode 100644
index 00000000..959fe51e
--- /dev/null
+++ b/android/src/main/res/values-pl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dotknij czytnika linii papilarnych"
+ "Nie rozpoznano"
+ "Obszar komunikatu pomocy"
+ "Czytnik linii papilarnych nie jest dostępny."
+ "Nie zarejestrowano odcisków palców."
+ "To urządzenie nie jest wyposażone w czytnik linii papilarnych"
+ "Odczyt odcisku palca został anulowany przez użytkownika."
+ "Zbyt wiele prób. Spróbuj ponownie później."
+ "Nieznany błąd"
+
diff --git a/android/src/main/res/values-pt-rBR/strings.xml b/android/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 00000000..21ab05cf
--- /dev/null
+++ b/android/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toque no sensor de digital"
+ "Não reconhecido"
+ "Área da mensagem de ajuda"
+ "Hardware de impressão digital não disponível."
+ "Nenhuma impressão digital registrada."
+ "Este dispositivo não tem um sensor de impressão digital"
+ "Operação de impressão digital cancelada pelo usuário."
+ "Muitas tentativas. Tente novamente mais tarde."
+ "Erro desconhecido"
+
diff --git a/android/src/main/res/values-pt-rPT/strings.xml b/android/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 00000000..751843a4
--- /dev/null
+++ b/android/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toque no sensor de impressões digitais"
+ "Não reconhecida."
+ "Área da mensagem de ajuda"
+ "Hardware de impressão digital não disponível."
+ "Nenhuma impressão digital registada."
+ "Este dispositivo não tem sensor de impressões digitais."
+ "Operação de impressão digital cancelada pelo utilizador."
+ "Demasiadas tentativas. Tente novamente mais tarde."
+ "Erro desconhecido."
+
diff --git a/android/src/main/res/values-pt/strings.xml b/android/src/main/res/values-pt/strings.xml
new file mode 100644
index 00000000..21ab05cf
--- /dev/null
+++ b/android/src/main/res/values-pt/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Toque no sensor de digital"
+ "Não reconhecido"
+ "Área da mensagem de ajuda"
+ "Hardware de impressão digital não disponível."
+ "Nenhuma impressão digital registrada."
+ "Este dispositivo não tem um sensor de impressão digital"
+ "Operação de impressão digital cancelada pelo usuário."
+ "Muitas tentativas. Tente novamente mais tarde."
+ "Erro desconhecido"
+
diff --git a/android/src/main/res/values-ro/strings.xml b/android/src/main/res/values-ro/strings.xml
new file mode 100644
index 00000000..ce2ecbf6
--- /dev/null
+++ b/android/src/main/res/values-ro/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Atingeți senzorul de amprentă"
+ "Nu este recunoscut"
+ "Zona mesajelor de ajutor"
+ "Hardware-ul pentru amprenta digitală nu este disponibil."
+ "Nu au fost înregistrate amprente digitale."
+ "Acest dispozitiv nu are senzor de amprentă"
+ "Operațiunea privind amprenta digitală a fost anulată de utilizator."
+ "Prea multe încercări. Încercați din nou mai târziu."
+ "Eroare necunoscută"
+
diff --git a/android/src/main/res/values-ru/strings.xml b/android/src/main/res/values-ru/strings.xml
new file mode 100644
index 00000000..18702035
--- /dev/null
+++ b/android/src/main/res/values-ru/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Коснитесь сканера отпечатков."
+ "Не распознано"
+ "Справочное сообщение"
+ "Сканер отпечатков пальцев недоступен."
+ "Нет отсканированных отпечатков пальцев."
+ "На этом устройстве нет сканера отпечатков пальцев."
+ "Операция с отпечатком пальца отменена пользователем."
+ "Слишком много попыток входа. Попробуйте ещё раз позже."
+ "Неизвестная ошибка"
+
diff --git a/android/src/main/res/values-si/strings.xml b/android/src/main/res/values-si/strings.xml
new file mode 100644
index 00000000..9710cb70
--- /dev/null
+++ b/android/src/main/res/values-si/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ඇඟිලි සලකුණු සංවේදකය ස්පර්ශ කරන්න"
+ "හඳුනා නොගන්නා ලදී"
+ "උදවු පණිවිඩ ප්රදේශය"
+ "ඇඟිලි සලකුණු දෘඪාංගය ලද නොහැකිය."
+ "ඇඟිලි සලකුණු ඇතුළත් කර නොමැත."
+ "මෙම උපාංගයේ ඇඟිලි සලකුණු සංවේදකයක් නොමැත"
+ "පරිශීලක විසින් ඇඟිලි සලකුණු මෙහෙයුම අවසන් කරන ලදී."
+ "උත්සාහ ඉතා වැඩියි. පසුව නැවත උත්සාහ කරන්න."
+ "නොදන්නා දෝෂයකි"
+
diff --git a/android/src/main/res/values-sk/strings.xml b/android/src/main/res/values-sk/strings.xml
new file mode 100644
index 00000000..926deae6
--- /dev/null
+++ b/android/src/main/res/values-sk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dotknite sa senzora odtlačkov prstov"
+ "Nerozpoznané"
+ "Oblasť správy pomocníka"
+ "Hardvér na snímanie odtlačku prsta nie je k dispozícii."
+ "Neregistrovali ste žiadne odtlačky prstov."
+ "Toto zariadenie nemá senzor odtlačkov prstov"
+ "Overenie odtlačku prsta zrušil používateľ."
+ "Príliš veľa pokusov. Skúste to znova neskôr."
+ "Neznáma chyba"
+
diff --git a/android/src/main/res/values-sl/strings.xml b/android/src/main/res/values-sl/strings.xml
new file mode 100644
index 00000000..ec8aab3c
--- /dev/null
+++ b/android/src/main/res/values-sl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Dotaknite se tipala prst. odt."
+ "Ni prepoznano"
+ "Območje sporočila pomoči"
+ "Strojna oprema za prstne odtise ni na voljo."
+ "Ni prijavljenih prstnih odtisov."
+ "Ta naprava nima tipala prstnih odtisov"
+ "Dejanje s prstnim odtisom je preklical uporabnik."
+ "Preveč poskusov. Poskusite znova pozneje."
+ "Neznana napaka"
+
diff --git a/android/src/main/res/values-sq/strings.xml b/android/src/main/res/values-sq/strings.xml
new file mode 100644
index 00000000..66332f55
--- /dev/null
+++ b/android/src/main/res/values-sq/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Prek sensorin e gjurmës së gishtit"
+ "Nuk njihet"
+ "Zona e mesazhit të ndihmës"
+ "Hardueri i gjurmës së gishtit nuk mundësohet."
+ "Nuk ka asnjë gjurmë gishti të regjistruar."
+ "Kjo pajisje nuk ka një sensor të gjurmës së gishtit"
+ "Veprimi i gjurmës së gishtit u anulua nga përdoruesi."
+ "Janë bërë shumë përpjekje. Provo përsëri më vonë."
+ "Gabim i panjohur"
+
diff --git a/android/src/main/res/values-sr/strings.xml b/android/src/main/res/values-sr/strings.xml
new file mode 100644
index 00000000..2e85509e
--- /dev/null
+++ b/android/src/main/res/values-sr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Додирн. сензор за отисак прста"
+ "Није препознат"
+ "Област поруке за помоћ"
+ "Хардвер за отиске прстију није доступан."
+ "Није регистрован ниједан отисак прста."
+ "Овај уређај нема сензор за отисак прста"
+ "Корисник је отказао радњу са отиском прста."
+ "Превише покушаја. Пробајте поново касније."
+ "Непозната грешка"
+
diff --git a/android/src/main/res/values-sv/strings.xml b/android/src/main/res/values-sv/strings.xml
new file mode 100644
index 00000000..26a88f83
--- /dev/null
+++ b/android/src/main/res/values-sv/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tryck på fingeravtryckssensorn"
+ "Identifierades inte"
+ "Område för hjälpmeddelande"
+ "Det finns ingen maskinvara för fingeravtryck."
+ "Inga fingeravtryck har registrerats."
+ "Enheten har ingen fingeravtryckssensor"
+ "Fingeravtrycksåtgärden avbröts av användaren."
+ "För många försök. Försök igen senare."
+ "Okänt fel"
+
diff --git a/android/src/main/res/values-sw/strings.xml b/android/src/main/res/values-sw/strings.xml
new file mode 100644
index 00000000..e8c0bd97
--- /dev/null
+++ b/android/src/main/res/values-sw/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Gusa kitambua alama ya kidole"
+ "Haitambuliwi"
+ "Sehemu ya ujumbe wa usaidizi"
+ "Maunzi ya kitambulisho hayapatikani."
+ "Hakuna alama za vidole zilizojumuishwa."
+ "Kifaa hiki hakina kitambua alama ya kidole"
+ "Mtumiaji ameghairi uthibitishaji wa alama ya kidole."
+ "Umejaribu mara nyingi mno. Tafadhali jaribu tena baadaye."
+ "Hitilafu isiyojulikana"
+
diff --git a/android/src/main/res/values-ta/strings.xml b/android/src/main/res/values-ta/strings.xml
new file mode 100644
index 00000000..db04a0d6
--- /dev/null
+++ b/android/src/main/res/values-ta/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "கைரேகை சென்சாரைத் தொடுக"
+ "பொருந்தவில்லை"
+ "உதவிச் செய்திக்கான பகுதி"
+ "கைரேகை வன்பொருள் இல்லை."
+ "கைரேகைப் பதிவுகள் எதுவுமில்லை."
+ "இந்தச் சாதனத்தில் கைரேகை சென்சார் இல்லை"
+ "கைரேகைச் சரிபார்ப்பு பயனரால் ரத்துசெய்யப்பட்டது."
+ "பலமுறை முயன்றுவிட்டீர்கள். பிறகு முயலவும்."
+ "அறியப்படாத பிழை"
+
diff --git a/android/src/main/res/values-te/strings.xml b/android/src/main/res/values-te/strings.xml
new file mode 100644
index 00000000..e9627ccb
--- /dev/null
+++ b/android/src/main/res/values-te/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "వేలిముద్ర సెన్సార్ను తాకండి"
+ "గుర్తించబడలేదు"
+ "సహాయ సందేశ ప్రాంతం"
+ "వేలిముద్ర హార్డ్వేర్ అందుబాటులో లేదు."
+ "వేలిముద్రలు నమోదు చేయబడలేదు."
+ "ఈ పరికరంలో వేలిముద్ర సెన్సార్ లేదు"
+ "వేలిముద్ర చర్యని వినియోగదారు రద్దు చేసారు."
+ "చాలా ఎక్కువ ప్రయత్నాలు చేసారు. దయచేసి తర్వాత మళ్లీ ప్రయత్నించండి."
+ "తెలియని ఎర్రర్"
+
diff --git a/android/src/main/res/values-th/strings.xml b/android/src/main/res/values-th/strings.xml
new file mode 100644
index 00000000..50e92476
--- /dev/null
+++ b/android/src/main/res/values-th/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "แตะเซ็นเซอร์ลายนิ้วมือ"
+ "ไม่รู้จัก"
+ "พื้นที่ข้อความช่วยเหลือ"
+ "ฮาร์ดแวร์ลายนิ้วมือไม่พร้อมใช้งาน"
+ "ไม่มีลายนิ้วมือที่ลงทะเบียน"
+ "อุปกรณ์นี้ไม่มีเซ็นเซอร์ลายนิ้วมือ"
+ "ผู้ใช้ยกเลิกการทำงานของลายนิ้วมือ"
+ "ลองหลายครั้งเกินไป โปรดลองอีกครั้งภายหลัง"
+ "ข้อผิดพลาดที่ไม่รู้จัก"
+
diff --git a/android/src/main/res/values-tl/strings.xml b/android/src/main/res/values-tl/strings.xml
new file mode 100644
index 00000000..3efe18fa
--- /dev/null
+++ b/android/src/main/res/values-tl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Pindutin ang fingerprint sensor"
+ "Hindi nakilala"
+ "Lugar ng mensahe ng tulong"
+ "Hindi available ang hardware na ginagamitan ng fingerprint."
+ "Walang naka-enroll na fingerprint."
+ "Walang sensor para sa fingerprint ang device na ito"
+ "Kinansela ng user ang operasyon sa fingerprint."
+ "Masyadong maraming pagsubok. Pakisubukang muli sa ibang pagkakataon."
+ "Hindi alam na error"
+
diff --git a/android/src/main/res/values-tr/strings.xml b/android/src/main/res/values-tr/strings.xml
new file mode 100644
index 00000000..9d26498b
--- /dev/null
+++ b/android/src/main/res/values-tr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Parmak izi sensörüne dokunun"
+ "Tanınmadı"
+ "Yardım mesajı alanı"
+ "Parmak izi donanımı kullanılamıyor."
+ "Parmak izi kaydedilmedi."
+ "Bu cihazda parmak izi sensörü yok"
+ "Parmak izi işlemi kullanıcı tarafından iptal edildi."
+ "Çok fazla deneme yapıldı. Lütfen daha sonra tekrar deneyin."
+ "Bilinmeyen hata"
+
diff --git a/android/src/main/res/values-uk/strings.xml b/android/src/main/res/values-uk/strings.xml
new file mode 100644
index 00000000..98c3093b
--- /dev/null
+++ b/android/src/main/res/values-uk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Торкніться сканера відбитків пальців"
+ "Не розпізнано"
+ "Область довідкового повідомлення"
+ "Апаратне забезпечення для сканування відбитка недоступне."
+ "Відбитки пальців не зареєстровано."
+ "На цьому пристрої немає сканера відбитків пальців"
+ "Користувач скасував дію з відбитком пальця."
+ "Забагато спроб. Зачекайте."
+ "Невідома помилка"
+
diff --git a/android/src/main/res/values-ur/strings.xml b/android/src/main/res/values-ur/strings.xml
new file mode 100644
index 00000000..1900936b
--- /dev/null
+++ b/android/src/main/res/values-ur/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "فنگر پرنٹ سینسر کو ٹچ کریں"
+ "شناخت نہیں ہو سکی"
+ "مدد کے پیغام کا علاقہ"
+ "فنگر پرنٹ ہارڈ ویئر دستیاب نہیں ہے۔"
+ "کوئی فنگر پرنٹ مندرج نہیں ہے۔"
+ "اس آلہ میں فنگر پرنٹ سینسر نہیں ہے"
+ "صارف نے فنگر پرنٹ کی کارروائی منسوخ کر دی۔"
+ "کافی زیادہ کوششیں۔ براہ کرم بعد میں دوبارہ کوشش کریں۔"
+ "نامعلوم خرابی"
+
diff --git a/android/src/main/res/values-uz/strings.xml b/android/src/main/res/values-uz/strings.xml
new file mode 100644
index 00000000..f7ac87b0
--- /dev/null
+++ b/android/src/main/res/values-uz/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Barmoq izi skaneriga tegining"
+ "Aniqlanmadi"
+ "Yordam xabari"
+ "Barmoq izi skaneri ish holatida emas."
+ "Hech qanday barmoq izi qayd qilinmagan."
+ "Bu qurilmada barmoq izi skaneri yo‘q"
+ "Barmoq izi amali foydalanuvchi tomonidan bekor qilindi"
+ "Juda koʻp urinish amalga oshirildi. Keyinroq qaytadan urining."
+ "Notanish xato"
+
diff --git a/android/src/main/res/values-vi/strings.xml b/android/src/main/res/values-vi/strings.xml
new file mode 100644
index 00000000..89bee557
--- /dev/null
+++ b/android/src/main/res/values-vi/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Chạm vào cảm biến vân tay"
+ "Không nhận dạng được"
+ "Vùng thông báo trợ giúp"
+ "Không dùng được phần cứng vân tay."
+ "Chưa đăng ký vân tay."
+ "Thiết bị này không có cảm biến vân tay"
+ "Người dùng đã hủy thao tác dùng dấu vân tay."
+ "Bạn đã thử quá nhiều lần. Vui lòng thử lại sau."
+ "Lỗi không xác định"
+
diff --git a/android/src/main/res/values-zh-rCN/strings.xml b/android/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 00000000..5b493801
--- /dev/null
+++ b/android/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "请轻触指纹传感器"
+ "无法识别"
+ "帮助消息区域"
+ "指纹硬件无法使用。"
+ "未注册任何指纹。"
+ "此设备没有指纹传感器"
+ "用户取消了指纹操作。"
+ "尝试次数过多,请稍后重试。"
+ "未知错误"
+
diff --git a/android/src/main/res/values-zh-rHK/strings.xml b/android/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 00000000..602cf505
--- /dev/null
+++ b/android/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "請輕觸指紋感應器"
+ "未能識別"
+ "說明訊息區域"
+ "無法使用指紋硬件。"
+ "尚未註冊任何指紋。"
+ "此裝置沒有指紋感應器"
+ "使用者已取消指紋操作。"
+ "嘗試次數過多。請稍後再試。"
+ "不明錯誤"
+
diff --git a/android/src/main/res/values-zh-rTW/strings.xml b/android/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 00000000..43d6fdc2
--- /dev/null
+++ b/android/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "請輕觸指紋感應器"
+ "無法辨識"
+ "說明訊息區域"
+ "指紋硬體無法使用。"
+ "未登錄任何指紋。"
+ "這個裝置沒有指紋感應器"
+ "使用者已取消指紋驗證作業。"
+ "嘗試次數過多,請稍後再試。"
+ "不明的錯誤"
+
diff --git a/android/src/main/res/values-zu/strings.xml b/android/src/main/res/values-zu/strings.xml
new file mode 100644
index 00000000..ce71b984
--- /dev/null
+++ b/android/src/main/res/values-zu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Thinta inzwa yesigxivizo somunwe"
+ "Akwaziwa"
+ "Indawo yosizo lomlayezo"
+ "Izingxenyekazi zekhompuyutha zezingxivizo zeminwe azitholakali."
+ "Azikho izigxivizo zeminwe ezibhalisiwe."
+ "Le divayisi ayinayo inzwa yezigxivizo zeminwe"
+ "Umsebenzi wesigxivizo somunwe sikhanselwe umsebenzisi."
+ "Imizamo eminingi kakhulu. Sicela uzame futhi ngokuhamba kwesikhathi."
+ "Iphutha elingaziwe"
+
diff --git a/android/src/main/res/values/colors.xml b/android/src/main/res/values/colors.xml
new file mode 100644
index 00000000..931d36b5
--- /dev/null
+++ b/android/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ #ff5722
+
diff --git a/android/src/main/res/values/dimens.xml b/android/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..17cee36d
--- /dev/null
+++ b/android/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ 64dp
+
diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml
new file mode 100644
index 00000000..1ee15347
--- /dev/null
+++ b/android/src/main/res/values/strings.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+ Touch the fingerprint sensor
+
+ Not recognized
+
+ Help message area
+
+ Fingerprint hardware not available.
+
+ No fingerprints enrolled.
+
+ This device does not have a fingerprint sensor
+
+ Fingerprint operation canceled by user.
+
+ Too many attempts. Please try again later.
+
+ Unknown error
+
diff --git a/index.js b/index.js
index af6c61e3..d131a279 100644
--- a/index.js
+++ b/index.js
@@ -63,17 +63,17 @@ export type Options = {
* on the current device.
* @return {Promise} Resolves to `SECURITY_LEVEL` when supported, otherwise `null`.
*/
-export function getSecurityLevel(): Promise($Values)> {
+export function getSecurityLevel(options?: Options): Promise($Values)> {
if (!RNKeychainManager.getSecurityLevel){
return Promise.resolve(null);
}
- return RNKeychainManager.getSecurityLevel();
+ return RNKeychainManager.getSecurityLevel(getAccessControl(options));
}
/**
* Inquire if the type of local authentication policy (LAPolicy) is supported
* on this device with the device settings the user chose.
- * @param {object} options LAPolicy option, iOS only
+ * @param {object} options LAPolicy option on iOS, authenticationType on Android
* @return {Promise} Resolves to `true` when supported, otherwise `false`
*/
export function canImplyAuthentication(options?: Options): Promise {
@@ -99,7 +99,6 @@ export function getSupportedBiometryType(): Promise($Values {
return RNKeychainManager.getGenericPasswordForOptions(
- getOptionsArgument(serviceOrOptions)
+ getOptionsArgument(serviceOrOptions),
);
}
diff --git a/react-native-keychain.iml b/react-native-keychain.iml
new file mode 100644
index 00000000..56db60ed
--- /dev/null
+++ b/react-native-keychain.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file