diff --git a/android/src/main/java/com/oblador/keychain/Keychain.java b/android/src/main/java/com/oblador/keychain/Keychain.java
new file mode 100644
index 00000000..efce9de5
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/Keychain.java
@@ -0,0 +1,64 @@
+package com.oblador.keychain;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.oblador.keychain.exceptions.CryptoFailedException;
+import com.oblador.keychain.exceptions.EmptyParameterException;
+import com.oblador.keychain.exceptions.KeyStoreAccessException;
+
+/**
+ * Keychain is an Keychain module which abstracts an encrypted the keychain related lookups and updates.
+ *
+ *
+ * The flow is the following:
+ *
+ * setGenericPassword(service1, "user1", "pass1") -> get service1 keys -> encrypt user/pass -> store user/pass.
+ *
+ *
+ * @author Miroslav Genov
+ */
+public interface Keychain {
+ /**
+ * Gets the SecurityLevel of the Keychain, e.g software, hardware and etc.
+ * @return the security level
+ */
+ SecurityLevel getSecurityLevel();
+
+ /**
+ * Gets the {@link ServiceCredentials} associated with the provided service
+ *
+ * @param service the service to which credentials are associated
+ * @return the service credentials associated with the provided service
+ * @throws CryptoFailedException in case of crypto failure
+ * @throws KeyStoreAccessException in case of error during accessing the keystore
+ */
+ @Nullable
+ ServiceCredentials getGenericPasswordForOptions(String service) throws CryptoFailedException, KeyStoreAccessException;
+
+ /**
+ * Checks whether the provided credentials exists.
+ * @param server the service id
+ * @return true if credentials exists and false in other case
+ */
+ boolean hasInternetCredentialsForServer(@NonNull String server);
+
+ /**
+ * Sets new credentials of the provided service
+ * @param service the name of the service
+ * @param username the username value
+ * @param password the password value
+ * @param minimumSecurityLevel the minimum security level
+ * @throws EmptyParameterException is password is empty
+ * @throws CryptoFailedException in case of crypto failure
+ */
+ void setGenericPasswordForOptions(String service, String username, String password, String minimumSecurityLevel) throws EmptyParameterException, CryptoFailedException;
+
+ /**
+ * Resets credentials of a given service.
+ *
+ * @param service the service of which credentials to be reset
+ * @throws KeyStoreAccessException in case of keystore access error
+ */
+ void resetGenericPasswordForOptions(String service) throws KeyStoreAccessException;
+}
diff --git a/android/src/main/java/com/oblador/keychain/KeychainModule.java b/android/src/main/java/com/oblador/keychain/KeychainModule.java
index 67ff5214..933c097d 100644
--- a/android/src/main/java/com/oblador/keychain/KeychainModule.java
+++ b/android/src/main/java/com/oblador/keychain/KeychainModule.java
@@ -1,6 +1,5 @@
package com.oblador.keychain;
-import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
@@ -11,12 +10,6 @@
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.CipherStorageFacebookConceal;
-import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAESCBC;
import com.oblador.keychain.exceptions.CryptoFailedException;
import com.oblador.keychain.exceptions.EmptyParameterException;
import com.oblador.keychain.exceptions.KeyStoreAccessException;
@@ -34,10 +27,8 @@ public class KeychainModule extends ReactContextBaseJavaModule {
public static final String E_SUPPORTED_BIOMETRY_ERROR = "E_SUPPORTED_BIOMETRY_ERROR";
public static final String KEYCHAIN_MODULE = "RNKeychainManager";
public static final String FINGERPRINT_SUPPORTED_NAME = "Fingerprint";
- public static final String EMPTY_STRING = "";
- private final Map cipherStorageMap = new HashMap<>();
- private final PrefsStorage prefsStorage;
+ private final Keychain keyChain;
@Override
public String getName() {
@@ -46,15 +37,9 @@ public String getName() {
public KeychainModule(ReactApplicationContext reactContext) {
super(reactContext);
- prefsStorage = new PrefsStorage(reactContext);
-
- addCipherStorageToMap(new CipherStorageFacebookConceal(reactContext));
- addCipherStorageToMap(new CipherStorageKeystoreAESCBC());
+ keyChain = Keychains.create(reactContext);
}
- private void addCipherStorageToMap(CipherStorage cipherStorage) {
- cipherStorageMap.put(cipherStorage.getCipherStorageName(), cipherStorage);
- }
@Nullable
@Override
@@ -68,24 +53,13 @@ public Map getConstants() {
@ReactMethod
public void getSecurityLevel(Promise promise) {
- promise.resolve(getSecurityLevel().name());
+ promise.resolve(keyChain.getSecurityLevel().name());
}
@ReactMethod
public void setGenericPasswordForOptions(String service, String username, String password, String minimumSecurityLevel, 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();
- validateCipherStorageSecurityLevel(currentCipherStorage, level);
-
- EncryptionResult result = currentCipherStorage.encrypt(service, username, password, level);
- prefsStorage.storeEncryptedEntry(service, result);
-
+ keyChain.setGenericPasswordForOptions(service, username, password, minimumSecurityLevel);
promise.resolve(true);
} catch (EmptyParameterException e) {
Log.e(KEYCHAIN_MODULE, e.getMessage());
@@ -99,24 +73,19 @@ public void setGenericPasswordForOptions(String service, String username, String
@ReactMethod
public void getGenericPasswordForOptions(String service, Promise promise) {
try {
- service = getDefaultServiceIfNull(service);
-
- CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel();
+ ServiceCredentials savedCredentials = keyChain.getGenericPasswordForOptions(service);
- ResultSet resultSet = prefsStorage.getEncryptedEntry(service);
- if (resultSet == null) {
+ if (savedCredentials == null) {
Log.e(KEYCHAIN_MODULE, "No entry found for service: " + service);
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);
+ credentials.putString("username", savedCredentials.username);
+ credentials.putString("password", savedCredentials.password);
promise.resolve(credentials);
} catch (KeyStoreAccessException e) {
@@ -128,53 +97,10 @@ public void getGenericPasswordForOptions(String service, Promise promise) {
}
}
- 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());
- // store the encryption result
- prefsStorage.storeEncryptedEntry(service, encryptionResult);
- // clean up the old cipher storage
- oldCipherStorage.removeKey(service);
- }
-
@ReactMethod
public void resetGenericPasswordForOptions(String service, Promise promise) {
try {
- service = getDefaultServiceIfNull(service);
-
- // First we clean up the cipher storage (using the cipher storage that was used to store the entry)
- ResultSet resultSet = prefsStorage.getEncryptedEntry(service);
- if (resultSet != null) {
- CipherStorage cipherStorage = getCipherStorageByName(resultSet.cipherStorageName);
- if (cipherStorage != null) {
- cipherStorage.removeKey(service);
- }
- }
- // And then we remove the entry in the shared preferences
- prefsStorage.removeEntry(service);
-
+ keyChain.resetGenericPasswordForOptions(service);
promise.resolve(true);
} catch (KeyStoreAccessException e) {
Log.e(KEYCHAIN_MODULE, e.getMessage());
@@ -184,11 +110,10 @@ public void resetGenericPasswordForOptions(String service, Promise promise) {
@ReactMethod
public void hasInternetCredentialsForServer(@NonNull String server, Promise promise) {
- final String defaultService = getDefaultServiceIfNull(server);
+ boolean hasInternetCredentials = keyChain.hasInternetCredentialsForServer(server);
- ResultSet resultSet = prefsStorage.getEncryptedEntry(defaultService);
- if (resultSet == null) {
- Log.e(KEYCHAIN_MODULE, "No entry found for service: " + defaultService);
+ if (!hasInternetCredentials) {
+ Log.e(KEYCHAIN_MODULE, "No entry found for service: " + server);
promise.resolve(false);
return;
}
@@ -226,78 +151,10 @@ public void getSupportedBiometryType(Promise promise) {
}
}
- // 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 {
- int currentAPILevel = Build.VERSION.SDK_INT;
- CipherStorage currentCipherStorage = null;
- for (CipherStorage cipherStorage : cipherStorageMap.values()) {
- int cipherStorageAPILevel = cipherStorage.getMinSupportedApiLevel();
- // Is the cipherStorage supported on the current API level?
- boolean isSupported = (cipherStorageAPILevel <= currentAPILevel);
- if (!isSupported) {
- continue;
- }
- // Is the API level better than the one we previously selected (if any)?
- if (currentCipherStorage == null || cipherStorageAPILevel > currentCipherStorage.getMinSupportedApiLevel()) {
- currentCipherStorage = cipherStorage;
- }
- }
- if (currentCipherStorage == null) {
- throw new CryptoFailedException("Unsupported Android SDK " + Build.VERSION.SDK_INT);
- }
- return currentCipherStorage;
- }
-
- private void validateCipherStorageSecurityLevel(CipherStorage cipherStorage, SecurityLevel requiredLevel) throws CryptoFailedException {
- if (cipherStorage.securityLevel().satisfiesSafetyThreshold(requiredLevel)) {
- return;
- }
-
- throw new CryptoFailedException(
- String.format(
- "Cipher Storage is too weak. Required security level is: %s, but only %s is provided",
- requiredLevel.name(),
- cipherStorage.securityLevel().name()));
- }
-
-
- private CipherStorage getCipherStorageByName(String cipherStorageName) {
- return cipherStorageMap.get(cipherStorageName);
- }
private boolean isFingerprintAuthAvailable() {
return DeviceAvailability.isFingerprintAuthAvailable(getReactApplicationContext());
}
- private boolean isSecureHardwareAvailable() {
- try {
- return getCipherStorageForCurrentAPILevel().supportsSecureHardware();
- } catch (CryptoFailedException e) {
- return false;
- }
- }
-
- private SecurityLevel getSecurityLevel() {
- try {
- CipherStorage storage = getCipherStorageForCurrentAPILevel();
- if (!storage.securityLevel().satisfiesSafetyThreshold(SecurityLevel.SECURE_SOFTWARE)) {
- return SecurityLevel.ANY;
- }
- if (isSecureHardwareAvailable()) {
- return SecurityLevel.SECURE_HARDWARE;
- } else {
- return SecurityLevel.SECURE_SOFTWARE;
- }
- } catch (CryptoFailedException e) {
- return SecurityLevel.ANY;
- }
- }
-
-
-
- @NonNull
- private String getDefaultServiceIfNull(String service) {
- return service == null ? EMPTY_STRING : service;
- }
}
diff --git a/android/src/main/java/com/oblador/keychain/KeychainPackage.java b/android/src/main/java/com/oblador/keychain/KeychainPackage.java
index d3aa6511..52a52f36 100644
--- a/android/src/main/java/com/oblador/keychain/KeychainPackage.java
+++ b/android/src/main/java/com/oblador/keychain/KeychainPackage.java
@@ -12,10 +12,6 @@
public class KeychainPackage implements ReactPackage {
- public KeychainPackage() {
-
- }
-
@Override
public List createNativeModules(
ReactApplicationContext reactContext) {
diff --git a/android/src/main/java/com/oblador/keychain/Keychains.java b/android/src/main/java/com/oblador/keychain/Keychains.java
new file mode 100644
index 00000000..e91b2b33
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/Keychains.java
@@ -0,0 +1,34 @@
+package com.oblador.keychain;
+
+import android.content.Context;
+
+import com.oblador.keychain.cipherStorage.CipherStorage;
+import com.oblador.keychain.cipherStorage.CipherStorageFacebookConceal;
+import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAESCBC;
+
+import java.util.LinkedHashMap;
+
+/**
+ * Keychains is a factory class used to create Keychain objects used for storing of secrets.
+ *
+ * @author Miroslav Genov
+ */
+public final class Keychains {
+
+ /**
+ * Creates a new {@link Keychain} instance that is using shared preferences as storage.
+ *
+ * @param context the android context
+ * @return the newly created Keychain instance
+ */
+ public static Keychain create(Context context) {
+ final CipherStorage facebookConcealCipherStorage = new CipherStorageFacebookConceal(context);
+ final CipherStorage keystoreCipherStorage = new CipherStorageKeystoreAESCBC();
+
+ return new SharedRefKeychain(context, new LinkedHashMap() {{
+ put(facebookConcealCipherStorage.getCipherStorageName(), facebookConcealCipherStorage);
+ put(keystoreCipherStorage.getCipherStorageName(), keystoreCipherStorage);
+ }});
+ }
+
+}
diff --git a/android/src/main/java/com/oblador/keychain/PrefsStorage.java b/android/src/main/java/com/oblador/keychain/PrefsStorage.java
index a9219b40..8d4f5972 100644
--- a/android/src/main/java/com/oblador/keychain/PrefsStorage.java
+++ b/android/src/main/java/com/oblador/keychain/PrefsStorage.java
@@ -5,7 +5,6 @@
import android.support.annotation.NonNull;
import android.util.Base64;
-import com.facebook.react.bridge.ReactApplicationContext;
import com.oblador.keychain.cipherStorage.CipherStorage.EncryptionResult;
import com.oblador.keychain.cipherStorage.CipherStorageFacebookConceal;
@@ -26,8 +25,8 @@ public ResultSet(String cipherStorageName, byte[] usernameBytes, byte[] password
private final SharedPreferences prefs;
- public PrefsStorage(ReactApplicationContext reactContext) {
- this.prefs = reactContext.getSharedPreferences(KEYCHAIN_DATA, Context.MODE_PRIVATE);
+ public PrefsStorage(Context context) {
+ this.prefs = context.getSharedPreferences(KEYCHAIN_DATA, Context.MODE_PRIVATE);
}
public ResultSet getEncryptedEntry(@NonNull String service) {
diff --git a/android/src/main/java/com/oblador/keychain/ServiceCredentials.java b/android/src/main/java/com/oblador/keychain/ServiceCredentials.java
new file mode 100644
index 00000000..c585a672
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/ServiceCredentials.java
@@ -0,0 +1,18 @@
+package com.oblador.keychain;
+
+/**
+ * ServiceCredentials is representing a single pair (user/pass) of credentials stored in the Keychain.
+ *
+ * @author Miroslav Genov
+ */
+public final class ServiceCredentials {
+ public final String service;
+ public final String username;
+ public final String password;
+
+ public ServiceCredentials(String service, String username, String password) {
+ this.service = service;
+ this.username = username;
+ this.password = password;
+ }
+}
diff --git a/android/src/main/java/com/oblador/keychain/SharedRefKeychain.java b/android/src/main/java/com/oblador/keychain/SharedRefKeychain.java
new file mode 100644
index 00000000..4cad0298
--- /dev/null
+++ b/android/src/main/java/com/oblador/keychain/SharedRefKeychain.java
@@ -0,0 +1,187 @@
+package com.oblador.keychain;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.oblador.keychain.cipherStorage.CipherStorage;
+import com.oblador.keychain.exceptions.CryptoFailedException;
+import com.oblador.keychain.exceptions.EmptyParameterException;
+import com.oblador.keychain.exceptions.KeyStoreAccessException;
+
+import java.util.Map;
+
+/**
+ * SharedRefKeychain an {@link Keychain} implementation which stores the the encrypted values as shared
+ * preferences maintained by the helper class {@link PrefsStorage}.
+ *
+ * @author Miroslav Genov
+ */
+class SharedRefKeychain implements Keychain {
+ private static final String EMPTY_STRING = "";
+
+ private final PrefsStorage prefsStorage;
+ private final Map nameToCipher;
+
+
+ SharedRefKeychain(Context context, Map nameToCipher) {
+ this.nameToCipher = nameToCipher;
+ this.prefsStorage = new PrefsStorage(context);
+ }
+
+ @Override
+ public SecurityLevel getSecurityLevel() {
+ try {
+ CipherStorage storage = getCipherStorageForCurrentAPILevel();
+ if (!storage.securityLevel().satisfiesSafetyThreshold(SecurityLevel.SECURE_SOFTWARE)) {
+ return SecurityLevel.ANY;
+ }
+
+ if (isSecureHardwareAvailable()) {
+ return SecurityLevel.SECURE_HARDWARE;
+ } else {
+ return SecurityLevel.SECURE_SOFTWARE;
+ }
+ } catch (CryptoFailedException e) {
+ return SecurityLevel.ANY;
+ }
+ }
+
+ @Override
+ @Nullable
+ public ServiceCredentials getGenericPasswordForOptions(String service) throws CryptoFailedException, KeyStoreAccessException {
+ String targetService = getDefaultServiceIfNull(service);
+
+ CipherStorage currentCipherStorage = getCipherStorageForCurrentAPILevel();
+
+ PrefsStorage.ResultSet resultSet = prefsStorage.getEncryptedEntry(targetService);
+ if (resultSet == null) {
+ return null;
+ }
+
+ final CipherStorage.DecryptionResult decryptionResult = decryptCredentials(targetService, currentCipherStorage, resultSet);
+
+ return new ServiceCredentials(targetService, decryptionResult.username, decryptionResult.password);
+ }
+
+ @Override
+ public boolean hasInternetCredentialsForServer(@NonNull String server) {
+ final String defaultService = getDefaultServiceIfNull(server);
+ PrefsStorage.ResultSet resultSet = prefsStorage.getEncryptedEntry(defaultService);
+ return resultSet != null;
+ }
+
+ @Override
+ public void setGenericPasswordForOptions(String service, String username, String password, String minimumSecurityLevel) throws EmptyParameterException, CryptoFailedException {
+ 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();
+ validateCipherStorageSecurityLevel(currentCipherStorage, level);
+
+ CipherStorage.EncryptionResult result = currentCipherStorage.encrypt(service, username, password, level);
+ prefsStorage.storeEncryptedEntry(service, result);
+ }
+
+ @Override
+ public void resetGenericPasswordForOptions(String service) throws KeyStoreAccessException {
+ service = getDefaultServiceIfNull(service);
+
+ // First we clean up the cipher storage (using the cipher storage that was used to store the entry)
+ PrefsStorage.ResultSet resultSet = prefsStorage.getEncryptedEntry(service);
+ if (resultSet != null) {
+ CipherStorage cipherStorage = getCipherStorageByName(resultSet.cipherStorageName);
+ if (cipherStorage != null) {
+ cipherStorage.removeKey(service);
+ }
+ }
+ // And then we remove the entry in the shared preferences
+ prefsStorage.removeEntry(service);
+ }
+
+
+ private CipherStorage.DecryptionResult decryptCredentials(String service, CipherStorage currentCipherStorage, PrefsStorage.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
+
+ CipherStorage.DecryptionResult decryptionResult = oldCipherStorage.decrypt(service, resultSet.usernameBytes, resultSet.passwordBytes);
+ // encrypt using the current cipher storage
+
+
+ migrateCipherStorage(service, currentCipherStorage, oldCipherStorage, decryptionResult);
+
+
+ return decryptionResult;
+ }
+
+ private void migrateCipherStorage(String service, CipherStorage newCipherStorage, CipherStorage oldCipherStorage, CipherStorage.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.
+ CipherStorage.EncryptionResult encryptionResult = newCipherStorage.encrypt(service, decryptionResult.username, decryptionResult.password, decryptionResult.getSecurityLevel());
+ // store the encryption result
+ prefsStorage.storeEncryptedEntry(service, encryptionResult);
+ // clean up the old cipher storage
+ oldCipherStorage.removeKey(service);
+ }
+
+
+ private void validateCipherStorageSecurityLevel(CipherStorage cipherStorage, SecurityLevel requiredLevel) throws CryptoFailedException {
+ if (cipherStorage.securityLevel().satisfiesSafetyThreshold(requiredLevel)) {
+ return;
+ }
+
+ throw new CryptoFailedException(
+ String.format(
+ "Cipher Storage is too weak. Required security level is: %s, but only %s is provided",
+ requiredLevel.name(),
+ cipherStorage.securityLevel().name()));
+ }
+
+ private boolean isSecureHardwareAvailable() {
+ try {
+ return getCipherStorageForCurrentAPILevel().supportsSecureHardware();
+ } catch (CryptoFailedException e) {
+ return false;
+ }
+ }
+
+ // 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 {
+ int currentAPILevel = Build.VERSION.SDK_INT;
+ CipherStorage currentCipherStorage = null;
+ for (CipherStorage cipherStorage : nameToCipher.values()) {
+ int cipherStorageAPILevel = cipherStorage.getMinSupportedApiLevel();
+ // Is the cipherStorage supported on the current API level?
+ boolean isSupported = (cipherStorageAPILevel <= currentAPILevel);
+ if (!isSupported) {
+ continue;
+ }
+ // Is the API level better than the one we previously selected (if any)?
+ if (currentCipherStorage == null || cipherStorageAPILevel > currentCipherStorage.getMinSupportedApiLevel()) {
+ currentCipherStorage = cipherStorage;
+ }
+ }
+ if (currentCipherStorage == null) {
+ throw new CryptoFailedException("Unsupported Android SDK " + Build.VERSION.SDK_INT);
+ }
+ return currentCipherStorage;
+ }
+
+ private CipherStorage getCipherStorageByName(String cipherStorageName) {
+ return nameToCipher.get(cipherStorageName);
+ }
+
+ @NonNull
+ private String getDefaultServiceIfNull(String service) {
+ return service == null ? EMPTY_STRING : service;
+ }
+}
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 b008d757..391feecc 100644
--- a/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
+++ b/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
@@ -1,5 +1,6 @@
package com.oblador.keychain.cipherStorage;
+import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
@@ -9,7 +10,6 @@
import com.facebook.crypto.CryptoConfig;
import com.facebook.crypto.Entity;
import com.facebook.crypto.keychain.KeyChain;
-import com.facebook.react.bridge.ReactApplicationContext;
import com.oblador.keychain.SecurityLevel;
import com.oblador.keychain.exceptions.CryptoFailedException;
@@ -20,8 +20,8 @@ public class CipherStorageFacebookConceal implements CipherStorage {
public static final String KEYCHAIN_DATA = "RN_KEYCHAIN";
private final Crypto crypto;
- public CipherStorageFacebookConceal(ReactApplicationContext reactContext) {
- KeyChain keyChain = new SharedPrefsBackedKeyChain(reactContext, CryptoConfig.KEY_256);
+ public CipherStorageFacebookConceal(Context context) {
+ KeyChain keyChain = new SharedPrefsBackedKeyChain(context, CryptoConfig.KEY_256);
this.crypto = AndroidConceal.get().createDefaultCrypto(keyChain);
}