expo-secure-store 13.0.1 → 13.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 13.0.2 — 2024-06-27
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Improve error message for unhandled errors ([#29394](https://github.com/expo/expo/pull/29394) by [@hassankhan](https://github.com/hassankhan))
|
|
18
|
+
- [Android] Fix decryption errors after Android Auto Backup has restored `expo-secure-store` data. ([#29943](https://github.com/expo/expo/pull/29943) by [@behenate](https://github.com/behenate))
|
|
19
|
+
|
|
13
20
|
## 13.0.1 — 2024-04-23
|
|
14
21
|
|
|
15
22
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '13.0.
|
|
4
|
+
version = '13.0.2'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -14,7 +14,7 @@ android {
|
|
|
14
14
|
namespace "expo.modules.securestore"
|
|
15
15
|
defaultConfig {
|
|
16
16
|
versionCode 17
|
|
17
|
-
versionName '13.0.
|
|
17
|
+
versionName '13.0.2'
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -133,13 +133,20 @@ open class SecureStoreModule : Module() {
|
|
|
133
133
|
try {
|
|
134
134
|
when (scheme) {
|
|
135
135
|
AESEncryptor.NAME -> {
|
|
136
|
-
val secretKeyEntry =
|
|
137
|
-
|
|
136
|
+
val secretKeyEntry = getKeyEntryCompat(SecretKeyEntry::class.java, mAESEncryptor, options, requireAuthentication, usesKeystoreSuffix) ?: run {
|
|
137
|
+
Log.w(
|
|
138
|
+
TAG,
|
|
139
|
+
"An entry was found for key $key under keychain ${options.keychainService}, but there is no corresponding KeyStore key. " +
|
|
140
|
+
"This situation occurs when the app is reinstalled. The value will be removed to avoid future errors. Returning null"
|
|
141
|
+
)
|
|
142
|
+
deleteItemImpl(key, options)
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
138
145
|
return mAESEncryptor.decryptItem(key, encryptedItem, secretKeyEntry, options, authenticationHelper)
|
|
139
146
|
}
|
|
140
147
|
HybridAESEncryptor.NAME -> {
|
|
141
|
-
val privateKeyEntry =
|
|
142
|
-
?:
|
|
148
|
+
val privateKeyEntry = getKeyEntryCompat(PrivateKeyEntry::class.java, hybridAESEncryptor, options, requireAuthentication, usesKeystoreSuffix)
|
|
149
|
+
?: return null
|
|
143
150
|
return hybridAESEncryptor.decryptItem(key, encryptedItem, privateKeyEntry, options, authenticationHelper)
|
|
144
151
|
}
|
|
145
152
|
else -> {
|
|
@@ -150,7 +157,15 @@ open class SecureStoreModule : Module() {
|
|
|
150
157
|
Log.w(TAG, "The requested key has been permanently invalidated. Returning null")
|
|
151
158
|
return null
|
|
152
159
|
} catch (e: BadPaddingException) {
|
|
153
|
-
|
|
160
|
+
// The key from the KeyStore is unable to decode the entry. This is because a new key was generated, but the entries are encrypted using the old one.
|
|
161
|
+
// This usually means that the user has reinstalled the app. We can safely remove the old value and return null as it's impossible to decrypt it.
|
|
162
|
+
Log.w(
|
|
163
|
+
TAG,
|
|
164
|
+
"Failed to decrypt the entry for $key under keychain ${options.keychainService}. " +
|
|
165
|
+
"The entry in shared preferences is out of sync with the keystore. It will be removed, returning null."
|
|
166
|
+
)
|
|
167
|
+
deleteItemImpl(key, options)
|
|
168
|
+
return null
|
|
154
169
|
} catch (e: GeneralSecurityException) {
|
|
155
170
|
throw (DecryptException(e.message, key, options.keychainService, e))
|
|
156
171
|
} catch (e: CodedException) {
|
|
@@ -185,7 +200,7 @@ open class SecureStoreModule : Module() {
|
|
|
185
200
|
use in the encrypted JSON item so that we know how to decode and decrypt it when reading
|
|
186
201
|
back a value.
|
|
187
202
|
*/
|
|
188
|
-
val secretKeyEntry: SecretKeyEntry =
|
|
203
|
+
val secretKeyEntry: SecretKeyEntry = getOrCreateKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication)
|
|
189
204
|
val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper)
|
|
190
205
|
encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
|
|
191
206
|
saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
|
|
@@ -249,11 +264,14 @@ open class SecureStoreModule : Module() {
|
|
|
249
264
|
}
|
|
250
265
|
|
|
251
266
|
private fun removeKeyFromKeystore(keyStoreAlias: String, keychainService: String) {
|
|
267
|
+
keyStore.deleteEntry(keyStoreAlias)
|
|
268
|
+
removeAllEntriesUnderKeychainService(keychainService)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun removeAllEntriesUnderKeychainService(keychainService: String) {
|
|
252
272
|
val sharedPreferences = getSharedPreferences()
|
|
253
273
|
val allEntries: Map<String, *> = sharedPreferences.all
|
|
254
274
|
|
|
255
|
-
keyStore.deleteEntry(keyStoreAlias)
|
|
256
|
-
|
|
257
275
|
// In order to avoid decryption failures we need to remove all entries that are using the deleted encryption key
|
|
258
276
|
for ((key: String, value) in allEntries) {
|
|
259
277
|
val valueString = value as? String ?: continue
|
|
@@ -302,26 +320,36 @@ open class SecureStoreModule : Module() {
|
|
|
302
320
|
encryptor: KeyBasedEncryptor<E>,
|
|
303
321
|
options: SecureStoreOptions,
|
|
304
322
|
requireAuthentication: Boolean
|
|
305
|
-
): E {
|
|
323
|
+
): E? {
|
|
306
324
|
val keystoreAlias = encryptor.getExtendedKeyStoreAlias(options, requireAuthentication)
|
|
307
|
-
|
|
308
|
-
// Android won't allow us to generate the keys if the device doesn't support biometrics or no biometrics are enrolled
|
|
309
|
-
if (requireAuthentication) {
|
|
310
|
-
authenticationHelper.assertBiometricsSupport()
|
|
311
|
-
}
|
|
312
|
-
encryptor.initializeKeyStoreEntry(keyStore, options)
|
|
313
|
-
} else {
|
|
325
|
+
return if (keyStore.containsAlias(keystoreAlias)) {
|
|
314
326
|
val entry = keyStore.getEntry(keystoreAlias, null)
|
|
315
327
|
if (!keyStoreEntryClass.isInstance(entry)) {
|
|
316
328
|
throw KeyStoreException("The entry for the keystore alias \"$keystoreAlias\" is not a ${keyStoreEntryClass.simpleName}")
|
|
317
329
|
}
|
|
318
330
|
keyStoreEntryClass.cast(entry)
|
|
319
331
|
?: throw KeyStoreException("The entry for the keystore alias \"$keystoreAlias\" couldn't be cast to correct class")
|
|
332
|
+
} else {
|
|
333
|
+
null
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private fun <E : KeyStore.Entry> getOrCreateKeyEntry(
|
|
338
|
+
keyStoreEntryClass: Class<E>,
|
|
339
|
+
encryptor: KeyBasedEncryptor<E>,
|
|
340
|
+
options: SecureStoreOptions,
|
|
341
|
+
requireAuthentication: Boolean
|
|
342
|
+
): E {
|
|
343
|
+
return getKeyEntry(keyStoreEntryClass, encryptor, options, requireAuthentication) ?: run {
|
|
344
|
+
// Android won't allow us to generate the keys if the device doesn't support biometrics or no biometrics are enrolled
|
|
345
|
+
if (requireAuthentication) {
|
|
346
|
+
authenticationHelper.assertBiometricsSupport()
|
|
347
|
+
}
|
|
348
|
+
encryptor.initializeKeyStoreEntry(keyStore, options)
|
|
320
349
|
}
|
|
321
|
-
return keyStoreEntry
|
|
322
350
|
}
|
|
323
351
|
|
|
324
|
-
private fun <E : KeyStore.Entry>
|
|
352
|
+
private fun <E : KeyStore.Entry> getKeyEntryCompat(
|
|
325
353
|
keyStoreEntryClass: Class<E>,
|
|
326
354
|
encryptor: KeyBasedEncryptor<E>,
|
|
327
355
|
options: SecureStoreOptions,
|
|
@@ -55,6 +55,9 @@ internal class KeyChainException: GenericException<OSStatus> {
|
|
|
55
55
|
return "Authentication failed. Provided passphrase/PIN is incorrect or there is no user authentication method configured for this device."
|
|
56
56
|
|
|
57
57
|
default:
|
|
58
|
+
if let errorMessage = SecCopyErrorMessageString(param, nil) as? String {
|
|
59
|
+
return errorMessage
|
|
60
|
+
}
|
|
58
61
|
return "Unknown Keychain Error."
|
|
59
62
|
}
|
|
60
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-secure-store",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.2",
|
|
4
4
|
"description": "Provides a way to encrypt and securely store key-value pairs locally on the device.",
|
|
5
5
|
"main": "build/SecureStore.js",
|
|
6
6
|
"types": "build/SecureStore.d.ts",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"expo": "*"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "09b2d97bbc0f70f7c811ff9b6c9ad8808c5ad84b"
|
|
46
46
|
}
|