Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion KeychainExample/e2e/testCases/storageTypesTest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ describe(':android:Storage Types', () => {
await matchLoadInfo(
'testUsernameFB',
'testPasswordFB',
'FacebookConceal',
type === 'internetCredentials' ? 'https://example.com' : undefined
);
await element(by.text('Automatic upgrade')).tap();
Expand Down
4 changes: 0 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ dependencies {
// Needed for BiometricPrompt in androidx.biometric
implementation "androidx.fragment:fragment:1.3.2@aar"

/* version higher 1.1.3 has problems with included soloader packages,
https://github.com/facebook/conceal/releases */
implementation "com.facebook.conceal:conceal:1.1.3@aar"

// Used to store encrypted data
implementation("androidx.datastore:datastore-preferences:1.1.1")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,7 @@ class DataStorePrefsStorage(
var cipherStorageName = getCipherStorageName(service)

// in case of wrong password or username
if (bytesForUsername == null || bytesForPassword == null) return null
if (cipherStorageName == null) {
// If the CipherStorage name is not found, we assume it is because the entry was written by an
// older version of this library which used Facebook Conceal, so we default to that.
cipherStorageName = KnownCiphers.FB
}
if (bytesForUsername == null || bytesForPassword == null || cipherStorageName == null) return null
return ResultSet(cipherStorageName, bytesForUsername, bytesForPassword)
}

Expand Down
23 changes: 4 additions & 19 deletions android/src/main/java/com/oblador/keychain/KeychainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.facebook.react.module.annotations.ReactModule
import com.oblador.keychain.cipherStorage.CipherStorage
import com.oblador.keychain.cipherStorage.CipherStorage.DecryptionResult
import com.oblador.keychain.cipherStorage.CipherStorageBase
import com.oblador.keychain.cipherStorage.CipherStorageFacebookConceal
import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc
import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesGcm
import com.oblador.keychain.cipherStorage.CipherStorageKeystoreRsaEcb
Expand Down Expand Up @@ -103,12 +102,9 @@ class KeychainModule(reactContext: ReactApplicationContext) :
}

/** Supported ciphers. */
@StringDef(KnownCiphers.FB, KnownCiphers.AES_CBC, KnownCiphers.AES_GCM, KnownCiphers.RSA)
@StringDef(KnownCiphers.AES_CBC, KnownCiphers.AES_GCM, KnownCiphers.RSA)
annotation class KnownCiphers {
companion object {
/** Facebook conceal compatibility lib in use. */
const val FB = "FacebookConceal"

/** AES CBC encryption. */
const val AES_CBC = "KeystoreAESCBC"

Expand Down Expand Up @@ -151,10 +147,9 @@ class KeychainModule(reactContext: ReactApplicationContext) :
/** Default constructor. */
init {
prefsStorage = DataStorePrefsStorage(reactContext, coroutineScope)
addCipherStorageToMap(CipherStorageFacebookConceal(reactContext))
addCipherStorageToMap(CipherStorageKeystoreAesCbc(reactContext))
addCipherStorageToMap(CipherStorageKeystoreAesGcm(reactContext, false))
addCipherStorageToMap(CipherStorageKeystoreAesGcm(reactContext, true))
// addCipherStorageToMap(CipherStorageKeystoreAesGcm(reactContext, false))
// addCipherStorageToMap(CipherStorageKeystoreAesGcm(reactContext, true))

// we have a references to newer api that will fail load of app classes in old androids OS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Expand Down Expand Up @@ -289,17 +284,7 @@ class KeychainModule(reactContext: ReactApplicationContext) :
val promptInfo = getPromptInfo(options)
var cipher: CipherStorage? = null

// Only check for upgradable ciphers for FacebookConseal as that
// is the only cipher that can be upgraded
cipher =
if (rules == Rules.AUTOMATIC_UPGRADE && storageName == KnownCiphers.FB) {
// get the best storage
val accessControl = getAccessControlOrDefault(options)
val useBiometry = getUseBiometry(accessControl)
getCipherStorageForCurrentAPILevel(useBiometry)
} else {
getCipherStorageByName(storageName)
}
cipher = getCipherStorageByName(storageName)
val decryptionResult = decryptCredentials(alias, cipher!!, resultSet, rules, promptInfo)
val credentials = Arguments.createMap()
credentials.putString(Maps.SERVICE, alias)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ class CipherStorageKeystoreAesCbc(reactContext: ReactApplicationContext) :
cipher.init(Cipher.DECRYPT_MODE, key, spec)

// Decrypt the bytes using cipher.doFinal()
val decryptedBytes = cipher.doFinal(bytes, IV.IV_LENGTH, bytes.size - IV.IV_LENGTH)
// Using a CipherInputStream for decryption has historically led to issues on the Pixel family of devices
// see https://github.com/oblador/react-native-keychain/issues/383
val _decryptedBytes = cipher.doFinal(bytes, IV.IV_LENGTH, bytes.size - IV.IV_LENGTH)

val decryptedBytes = maybeRemovePKCS7Padding(_decryptedBytes, IV.IV_LENGTH)

String(decryptedBytes, UTF8)
} catch (fail: Throwable) {
Log.w(LOG_TAG, fail.message, fail)
Expand Down Expand Up @@ -270,4 +275,22 @@ class CipherStorageKeystoreAesCbc(reactContext: ReactApplicationContext) :
decryptBytes(key, bytes, IV.decrypt)

// endregion

private fun maybeRemovePKCS7Padding(paddedBytes: ByteArray, maxPaddingLength: Int): ByteArray {
val paddingLength = paddedBytes.last().toInt()

// Validate the padding
if (paddingLength < 1 || paddingLength > paddedBytes.size || paddingLength > maxPaddingLength) {
return paddedBytes
}

for (i in paddedBytes.size - paddingLength until paddedBytes.size) {
if (paddedBytes[i] != paddingLength.toByte()) {
return paddedBytes
}
}

// Remove the padding
return paddedBytes.copyOfRange(0, paddedBytes.size - paddingLength)
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-keychain",
"version": "9.2.3",
"name": "@exodus/react-native-keychain",
"version": "9.2.4",
"description": "Keychain Access for React Native",
"main": "./lib/commonjs/index.js",
"module": "./lib/module/index",
Expand Down
12 changes: 2 additions & 10 deletions src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export enum SECURITY_LEVEL {
* */
SECURE_HARDWARE = RNKeychainManager &&
RNKeychainManager.SECURITY_LEVEL_SECURE_HARDWARE,
/** No security guarantees needed (default value). Credentials can be stored in FB Secure Storage. */
/** No security guarantees needed (default value). */
ANY = RNKeychainManager && RNKeychainManager.SECURITY_LEVEL_ANY,
}

Expand Down Expand Up @@ -109,17 +109,9 @@ export enum BIOMETRY_TYPE {
* 2. Medium Security (No Authentication):
* - AES_GCM_NO_AUTH: For app-level secrets and cached data
*
* 3. Legacy/Deprecated:
* - AES_CBC: Outdated, use AES_GCM_NO_AUTH instead
* - FB: Archived Facebook Conceal implementation
*
* @platform Android
*/
export enum STORAGE_TYPE {
/** Facebook compatibility cipher.
* @deprecated Facebook Conceal was deprecated and archived in Mar 3, 2020. https://github.com/facebookarchive/conceal
*/
FB = 'FacebookConceal',
/** Encryptions without human interaction.
* @deprecated Use AES_GCM_NO_AUTH instead.
*/
Expand Down Expand Up @@ -154,6 +146,6 @@ export enum STORAGE_TYPE {
export enum SECURITY_RULES {
/** No special security rules applied. */
NONE = 'none',
/** Upgrade secret to the best available storage as soon as it is available and user request secret extraction. Upgrade not applied till we request the secret. This rule only applies to secrets stored with FacebookConseal. */
/** Upgrade secret to the best available storage as soon as it is available and user request secret extraction. Upgrade not applied till we request the secret. */
AUTOMATIC_UPGRADE = 'automaticUpgradeToMoreSecuredStorage',
}
5 changes: 0 additions & 5 deletions website/docs/choosing-storage-type.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ We offer three security levels for data storage:
- **AES_GCM_NO_AUTH**: Symmetric encryption without biometric requirements
- Best for: Cached data, non-sensitive encrypted data

### 3. Legacy/Deprecated

- **AES_CBC**, **FB** (Facebook Conceal)
- ⚠️ Not recommended for new implementations

## Storage Type Selection Guide

### Use AES_GCM (High Security) for:
Expand Down
14 changes: 1 addition & 13 deletions website/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: Frequently Asked Questions

**Q: How does the library handle encryption when storing secrets, and can it upgrade the encryption?**

**A:** The library automatically applies the highest possible encryption when storing secrets. However, once a secret is stored, it does not attempt to upgrade the encryption unless **Facebook Conceal** was used and the `SECURITY_RULES` option is set to `AUTOMATIC_UPGRADE`.
**A:** The library automatically applies the highest possible encryption when storing secrets. However, once a secret is stored, it does not attempt to upgrade the encryption.

---

Expand All @@ -23,18 +23,6 @@ title: Frequently Asked Questions

---

**Q: How do I enable automatic upgrades for Facebook Conceal?**

**A:** Use the following call:

```tsx
getGenericPassword({ ...otherProps, rules: "AUTOMATIC_UPGRADE" });
```

Ensure the `rules` property is set to the string value `AUTOMATIC_UPGRADE`.

---

**Q: How do I force a specific level of encryption when saving a secret?**

**A:** To force a specific encryption level, call:
Expand Down
Loading