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); }