expo-secure-store 12.1.1 β 12.3.0
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 +16 -0
- package/README.md +2 -2
- package/android/build.gradle +8 -10
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/expo/modules/securestore/SecureStoreExceptions.kt +27 -0
- package/android/src/main/java/expo/modules/securestore/SecureStoreModule.java +60 -31
- package/build/ExpoSecureStore.d.ts +1 -1
- package/build/ExpoSecureStore.d.ts.map +1 -1
- package/build/ExpoSecureStore.js +2 -2
- package/build/ExpoSecureStore.js.map +1 -1
- package/build/SecureStore.d.ts +13 -8
- package/build/SecureStore.d.ts.map +1 -1
- package/build/SecureStore.js +7 -3
- package/build/SecureStore.js.map +1 -1
- package/expo-module.config.json +6 -0
- package/ios/{EXSecureStore.podspec β ExpoSecureStore.podspec} +10 -3
- package/ios/SecureStoreAccessible.swift +11 -0
- package/ios/SecureStoreExceptions.swift +55 -0
- package/ios/SecureStoreModule.swift +160 -0
- package/ios/SecureStoreOptions.swift +15 -0
- package/package.json +2 -2
- package/src/ExpoSecureStore.ts +2 -2
- package/src/SecureStore.ts +13 -8
- package/ios/EXSecureStore/EXSecureStore.h +0 -18
- package/ios/EXSecureStore/EXSecureStore.m +0 -298
- package/unimodule.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,22 @@
|
|
|
10
10
|
|
|
11
11
|
### π‘ Others
|
|
12
12
|
|
|
13
|
+
## 12.3.0 β 2023-06-13
|
|
14
|
+
|
|
15
|
+
### π Bug fixes
|
|
16
|
+
|
|
17
|
+
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
|
|
18
|
+
|
|
19
|
+
### π‘ Others
|
|
20
|
+
|
|
21
|
+
- Added automatic invalidated key handling on Android. ([#22716](https://github.com/expo/expo/pull/22716) by [@behenate](https://github.com/behenate))
|
|
22
|
+
|
|
23
|
+
## 12.2.0 β 2023-05-08
|
|
24
|
+
|
|
25
|
+
### π New features
|
|
26
|
+
|
|
27
|
+
- Migrated iOS codebase to use Expo modules API. ([#21393](https://github.com/expo/expo/pull/21393) by [@alanjhughes](https://github.com/alanjhughes))
|
|
28
|
+
|
|
13
29
|
## 12.1.1 β 2023-02-09
|
|
14
30
|
|
|
15
31
|
_This version does not introduce any user-facing changes._
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Provides a way to encrypt and securely store keyβvalue pairs locally on the de
|
|
|
16
16
|
|
|
17
17
|
# Installation in managed Expo projects
|
|
18
18
|
|
|
19
|
-
For [managed](https://docs.expo.dev/
|
|
19
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/securestore/).
|
|
20
20
|
|
|
21
21
|
# Installation in bare React Native projects
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ For bare React Native projects, you must ensure that you have [installed and con
|
|
|
25
25
|
### Add the package to your npm dependencies
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
expo install expo-secure-store
|
|
28
|
+
npx expo install expo-secure-store
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
### Configure for iOS
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '12.
|
|
6
|
+
version = '12.3.0'
|
|
7
7
|
|
|
8
8
|
buildscript {
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
@@ -35,19 +35,11 @@ buildscript {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Creating sources with comments
|
|
39
|
-
task androidSourcesJar(type: Jar) {
|
|
40
|
-
classifier = 'sources'
|
|
41
|
-
from android.sourceSets.main.java.srcDirs
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
afterEvaluate {
|
|
45
39
|
publishing {
|
|
46
40
|
publications {
|
|
47
41
|
release(MavenPublication) {
|
|
48
42
|
from components.release
|
|
49
|
-
// Add additional sourcesJar to artifacts
|
|
50
|
-
artifact(androidSourcesJar)
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
45
|
repositories {
|
|
@@ -70,15 +62,21 @@ android {
|
|
|
70
62
|
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
|
71
63
|
}
|
|
72
64
|
|
|
65
|
+
namespace "expo.modules.securestore"
|
|
73
66
|
defaultConfig {
|
|
74
67
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
75
68
|
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
76
69
|
versionCode 17
|
|
77
|
-
versionName '12.
|
|
70
|
+
versionName '12.3.0'
|
|
78
71
|
}
|
|
79
72
|
lintOptions {
|
|
80
73
|
abortOnError false
|
|
81
74
|
}
|
|
75
|
+
publishing {
|
|
76
|
+
singleVariant("release") {
|
|
77
|
+
withSourcesJar()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
dependencies {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<manifest
|
|
1
|
+
<manifest>
|
|
2
2
|
</manifest>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package expo.modules.securestore
|
|
2
|
+
|
|
3
|
+
import expo.modules.core.errors.CodedException
|
|
4
|
+
|
|
5
|
+
internal class NullKeyException :
|
|
6
|
+
CodedException("SecureStore keys must not be null")
|
|
7
|
+
|
|
8
|
+
internal class WriteException(message: String?, cause: Throwable?) :
|
|
9
|
+
CodedException(message ?: "An unexpected error occurred when writing to SecureStore", cause)
|
|
10
|
+
|
|
11
|
+
internal class ReadException(cause: Throwable?) :
|
|
12
|
+
CodedException("An unexpected error occurred when reading from SecureStore", cause)
|
|
13
|
+
|
|
14
|
+
internal class SecureStoreIOException(cause: Throwable?) :
|
|
15
|
+
CodedException("There was an I/O error loading the keystore for SecureStore", cause)
|
|
16
|
+
|
|
17
|
+
internal class EncryptException(message: String?, cause: Throwable?) :
|
|
18
|
+
CodedException(message ?: "Could not encrypt the value for SecureStore", cause)
|
|
19
|
+
|
|
20
|
+
internal class DecryptException(message: String?, cause: Throwable?) :
|
|
21
|
+
CodedException(message ?: "Could not decrypt the value for SecureStore", cause)
|
|
22
|
+
|
|
23
|
+
internal class SecureStoreJSONException(message: String?, cause: Throwable?) :
|
|
24
|
+
CodedException(message, cause)
|
|
25
|
+
|
|
26
|
+
internal class DeleteException(message: String?, cause: Throwable?) :
|
|
27
|
+
CodedException(message ?: "An unexpected error occurred when deleting from SecureStore", cause)
|
|
@@ -8,6 +8,7 @@ import android.os.Build;
|
|
|
8
8
|
import android.preference.PreferenceManager;
|
|
9
9
|
import android.security.KeyPairGeneratorSpec;
|
|
10
10
|
import android.security.keystore.KeyGenParameterSpec;
|
|
11
|
+
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
|
11
12
|
import android.security.keystore.KeyProperties;
|
|
12
13
|
import android.text.TextUtils;
|
|
13
14
|
import android.util.Base64;
|
|
@@ -39,6 +40,7 @@ import java.security.spec.InvalidParameterSpecException;
|
|
|
39
40
|
import java.util.Date;
|
|
40
41
|
|
|
41
42
|
import javax.crypto.Cipher;
|
|
43
|
+
import javax.crypto.IllegalBlockSizeException;
|
|
42
44
|
import javax.crypto.KeyGenerator;
|
|
43
45
|
import javax.crypto.NoSuchPaddingException;
|
|
44
46
|
import javax.crypto.SecretKey;
|
|
@@ -81,16 +83,16 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
81
83
|
@SuppressWarnings("unused")
|
|
82
84
|
public void setValueWithKeyAsync(String value, String key, ReadableArguments options, Promise promise) {
|
|
83
85
|
try {
|
|
84
|
-
setItemImpl(key, value, options, promise);
|
|
86
|
+
setItemImpl(key, value, options, promise, false);
|
|
85
87
|
} catch (Exception e) {
|
|
86
88
|
Log.e(TAG, "Caught unexpected exception when writing to SecureStore", e);
|
|
87
|
-
promise.reject(
|
|
89
|
+
promise.reject(new WriteException(null, e));
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
private void setItemImpl(String key, String value, ReadableArguments options, Promise promise) {
|
|
93
|
+
private void setItemImpl(String key, String value, ReadableArguments options, Promise promise, boolean keyIsInvalidated) {
|
|
92
94
|
if (key == null) {
|
|
93
|
-
promise.reject(
|
|
95
|
+
promise.reject(new NullKeyException());
|
|
94
96
|
return;
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -101,7 +103,7 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
101
103
|
if (success) {
|
|
102
104
|
promise.resolve(null);
|
|
103
105
|
} else {
|
|
104
|
-
promise.reject(
|
|
106
|
+
promise.reject(new WriteException("Could not write a null value to SecureStore", null));
|
|
105
107
|
}
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
@@ -114,6 +116,10 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
114
116
|
// use in the encrypted JSON item so that we know how to decode and decrypt it when reading
|
|
115
117
|
// back a value.
|
|
116
118
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
119
|
+
if (keyIsInvalidated) {
|
|
120
|
+
String alias = mAESEncrypter.getKeyStoreAlias(options);
|
|
121
|
+
keyStore.deleteEntry(alias);
|
|
122
|
+
}
|
|
117
123
|
KeyStore.SecretKeyEntry secretKeyEntry = getKeyEntry(KeyStore.SecretKeyEntry.class, mAESEncrypter, options);
|
|
118
124
|
mAESEncrypter.createEncryptedItem(promise, value, keyStore, secretKeyEntry, options, mAuthenticationHelper.getDefaultCallback(), (innerPromise, result) -> {
|
|
119
125
|
JSONObject obj = (JSONObject) result;
|
|
@@ -130,23 +136,45 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
130
136
|
}
|
|
131
137
|
} catch (IOException e) {
|
|
132
138
|
Log.w(TAG, e);
|
|
133
|
-
promise.reject(
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
promise.reject(new SecureStoreIOException(e));
|
|
140
|
+
} catch (IllegalBlockSizeException e) {
|
|
141
|
+
// Sometimes, android throws IllegalBlockSizeException when the fingerprint has been changed.
|
|
142
|
+
// https://github.com/expo/expo/issues/22312. It should be handled the same way as KeyPermanentlyInvalidatedException
|
|
143
|
+
boolean isInvalidationException = e.getCause() != null && e.getCause().getMessage() != null && e.getCause().getMessage().contains("Key user not authenticated");
|
|
144
|
+
|
|
145
|
+
if(isInvalidationException && !keyIsInvalidated) {
|
|
146
|
+
setItemImpl(key, value, options, promise, true);
|
|
147
|
+
Log.w(TAG, "IllegalBlockSizeException, retrying with the key deleted");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// If the issue persists after deleting the key it is likely not related to invalidation
|
|
151
|
+
promise.reject(new EncryptException(null, e));
|
|
136
152
|
Log.w(TAG, e);
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
} catch (GeneralSecurityException e) {
|
|
154
|
+
boolean isInvalidationException = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && e instanceof KeyPermanentlyInvalidatedException;
|
|
155
|
+
|
|
156
|
+
if (isInvalidationException && !keyIsInvalidated) {
|
|
157
|
+
// If the key has been invalidated by the OS we try to reinitialize it.
|
|
158
|
+
Log.w(TAG, "Key has been invalidated, retrying with the key deleted");
|
|
159
|
+
setItemImpl(key, value, options, promise, true);
|
|
160
|
+
} else if (isInvalidationException) {
|
|
161
|
+
Log.w(TAG, e);
|
|
162
|
+
// If reinitialization of the key fails, reject the promise
|
|
163
|
+
promise.reject(new EncryptException("Encryption Failed. The key has been permanently invalidated and cannot be reinitialized", e));
|
|
164
|
+
} else {
|
|
165
|
+
Log.w(TAG, e);
|
|
166
|
+
promise.reject(new EncryptException(null, e));
|
|
167
|
+
}
|
|
139
168
|
} catch (JSONException e) {
|
|
140
169
|
Log.w(TAG, e);
|
|
141
|
-
promise.reject(
|
|
142
|
-
return;
|
|
170
|
+
promise.reject(new SecureStoreJSONException("Could not create an encrypted JSON item for SecureStore", e));
|
|
143
171
|
}
|
|
144
172
|
}
|
|
145
173
|
|
|
146
174
|
private void saveEncryptedItem(Promise promise, JSONObject encryptedItem, SharedPreferences prefs, String key) {
|
|
147
175
|
String encryptedItemString = encryptedItem.toString();
|
|
148
176
|
if (encryptedItemString == null) { // lint warning suppressed, JSONObject#toString() may return null
|
|
149
|
-
promise.reject(
|
|
177
|
+
promise.reject(new SecureStoreJSONException("Could not JSON-encode the encrypted item for SecureStore", null));
|
|
150
178
|
return;
|
|
151
179
|
}
|
|
152
180
|
|
|
@@ -154,7 +182,7 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
154
182
|
if (success) {
|
|
155
183
|
promise.resolve(null);
|
|
156
184
|
} else {
|
|
157
|
-
promise.reject(
|
|
185
|
+
promise.reject(new WriteException("Could not write encrypted JSON to SecureStore", null));
|
|
158
186
|
}
|
|
159
187
|
}
|
|
160
188
|
|
|
@@ -165,7 +193,7 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
165
193
|
getItemImpl(key, options, promise);
|
|
166
194
|
} catch (Exception e) {
|
|
167
195
|
Log.e(TAG, "Caught unexpected exception when reading from SecureStore", e);
|
|
168
|
-
promise.reject(
|
|
196
|
+
promise.reject(new ReadException(e));
|
|
169
197
|
}
|
|
170
198
|
}
|
|
171
199
|
|
|
@@ -187,14 +215,14 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
187
215
|
encryptedItem = new JSONObject(encryptedItemString);
|
|
188
216
|
} catch (JSONException e) {
|
|
189
217
|
Log.e(TAG, String.format("Could not parse stored value as JSON (key = %s, value = %s)", key, encryptedItemString), e);
|
|
190
|
-
promise.reject(
|
|
218
|
+
promise.reject(new SecureStoreJSONException("Could not parse the encrypted JSON item in SecureStore", e));
|
|
191
219
|
return;
|
|
192
220
|
}
|
|
193
221
|
|
|
194
222
|
String scheme = encryptedItem.optString(SCHEME_PROPERTY);
|
|
195
223
|
if (scheme == null) {
|
|
196
224
|
Log.e(TAG, String.format("Stored JSON object is missing a scheme (key = %s, value = %s)", key, encryptedItemString));
|
|
197
|
-
promise.reject(
|
|
225
|
+
promise.reject(new DecryptException("Could not find the encryption scheme used for SecureStore item", null));
|
|
198
226
|
return;
|
|
199
227
|
}
|
|
200
228
|
|
|
@@ -211,21 +239,22 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
211
239
|
default:
|
|
212
240
|
String message = String.format("The item for key \"%s\" in SecureStore has an unknown encoding scheme (%s)", key, scheme);
|
|
213
241
|
Log.e(TAG, message);
|
|
214
|
-
promise.reject(
|
|
215
|
-
return;
|
|
242
|
+
promise.reject(new DecryptException(message, null));
|
|
216
243
|
}
|
|
217
244
|
} catch (IOException e) {
|
|
218
245
|
Log.w(TAG, e);
|
|
219
|
-
promise.reject(
|
|
220
|
-
return;
|
|
246
|
+
promise.reject(new SecureStoreIOException(e));
|
|
221
247
|
} catch (GeneralSecurityException e) {
|
|
248
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && e instanceof KeyPermanentlyInvalidatedException) {
|
|
249
|
+
Log.w(TAG, "The requested key has been permanently invalidated. Returning null");
|
|
250
|
+
promise.resolve(null);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
222
253
|
Log.w(TAG, e);
|
|
223
|
-
promise.reject(
|
|
224
|
-
return;
|
|
254
|
+
promise.reject(new DecryptException(null, e));
|
|
225
255
|
} catch (JSONException e) {
|
|
226
256
|
Log.w(TAG, e);
|
|
227
|
-
promise.reject(
|
|
228
|
-
return;
|
|
257
|
+
promise.reject(new SecureStoreJSONException("Could not decode the encrypted JSON item in SecureStore", e));
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
|
|
@@ -248,24 +277,24 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
248
277
|
String keystoreAlias = encrypter.getKeyStoreAlias(options);
|
|
249
278
|
|
|
250
279
|
if (!keyStore.containsAlias(keystoreAlias)) {
|
|
251
|
-
promise.reject(
|
|
280
|
+
promise.reject(new DecryptException("Could not find the keystore entry to decrypt the legacy item in SecureStore", null));
|
|
252
281
|
return;
|
|
253
282
|
}
|
|
254
283
|
|
|
255
284
|
KeyStore.Entry keyStoreEntry = keyStore.getEntry(keystoreAlias, null);
|
|
256
285
|
if (!(keyStoreEntry instanceof KeyStore.PrivateKeyEntry)) {
|
|
257
|
-
promise.reject(
|
|
286
|
+
promise.reject(new DecryptException("The keystore entry for the legacy item is not a private key entry", null));
|
|
258
287
|
return;
|
|
259
288
|
}
|
|
260
289
|
|
|
261
290
|
value = encrypter.decryptItem(encryptedItem, (KeyStore.PrivateKeyEntry) keyStoreEntry);
|
|
262
291
|
} catch (IOException e) {
|
|
263
292
|
Log.w(TAG, e);
|
|
264
|
-
promise.reject(
|
|
293
|
+
promise.reject(new SecureStoreIOException(e));
|
|
265
294
|
return;
|
|
266
295
|
} catch (GeneralSecurityException e) {
|
|
267
296
|
Log.w(TAG, e);
|
|
268
|
-
promise.reject(
|
|
297
|
+
promise.reject(new DecryptException(null, e));
|
|
269
298
|
return;
|
|
270
299
|
}
|
|
271
300
|
|
|
@@ -279,7 +308,7 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
279
308
|
deleteItemImpl(key, promise);
|
|
280
309
|
} catch (Exception e) {
|
|
281
310
|
Log.e(TAG, "Caught unexpected exception when deleting from SecureStore", e);
|
|
282
|
-
promise.reject(
|
|
311
|
+
promise.reject(new DeleteException(null, e));
|
|
283
312
|
}
|
|
284
313
|
}
|
|
285
314
|
|
|
@@ -298,7 +327,7 @@ public class SecureStoreModule extends ExportedModule {
|
|
|
298
327
|
if (success) {
|
|
299
328
|
promise.resolve(null);
|
|
300
329
|
} else {
|
|
301
|
-
promise.reject(
|
|
330
|
+
promise.reject(new DeleteException("Could not delete the item from SecureStore", null));
|
|
302
331
|
}
|
|
303
332
|
}
|
|
304
333
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoSecureStore.d.ts","sourceRoot":"","sources":["../src/ExpoSecureStore.ts"],"names":[],"mappings":";AACA,
|
|
1
|
+
{"version":3,"file":"ExpoSecureStore.d.ts","sourceRoot":"","sources":["../src/ExpoSecureStore.ts"],"names":[],"mappings":";AACA,wBAAsD"}
|
package/build/ExpoSecureStore.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
export default requireNativeModule('ExpoSecureStore');
|
|
3
3
|
//# sourceMappingURL=ExpoSecureStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoSecureStore.js","sourceRoot":"","sources":["../src/ExpoSecureStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ExpoSecureStore.js","sourceRoot":"","sources":["../src/ExpoSecureStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,eAAe,mBAAmB,CAAC,iBAAiB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\nexport default requireNativeModule('ExpoSecureStore');\n"]}
|
package/build/SecureStore.d.ts
CHANGED
|
@@ -35,16 +35,17 @@ export declare const WHEN_UNLOCKED: KeychainAccessibilityConstant;
|
|
|
35
35
|
export declare const WHEN_UNLOCKED_THIS_DEVICE_ONLY: KeychainAccessibilityConstant;
|
|
36
36
|
export type SecureStoreOptions = {
|
|
37
37
|
/**
|
|
38
|
-
* -
|
|
39
|
-
* -
|
|
38
|
+
* - Android: Equivalent of the public/private key pair `Alias`.
|
|
39
|
+
* - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
|
|
40
40
|
* > If the item is set with the `keychainService` option, it will be required to later fetch the value.
|
|
41
41
|
*/
|
|
42
42
|
keychainService?: string;
|
|
43
43
|
/**
|
|
44
44
|
* Option responsible for enabling the usage of the user authentication methods available on the device while
|
|
45
45
|
* accessing data stored in SecureStore.
|
|
46
|
-
* -
|
|
47
|
-
*
|
|
46
|
+
* - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))
|
|
47
|
+
* (requires API 23).
|
|
48
|
+
* - iOS: Equivalent to [`kSecAccessControlBiometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolbiometrycurrentset/).
|
|
48
49
|
* Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`
|
|
49
50
|
* value used for the others non-authenticated operations.
|
|
50
51
|
*/
|
|
@@ -55,7 +56,7 @@ export type SecureStoreOptions = {
|
|
|
55
56
|
authenticationPrompt?: string;
|
|
56
57
|
/**
|
|
57
58
|
* Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.
|
|
58
|
-
* @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/
|
|
59
|
+
* @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).
|
|
59
60
|
* @default SecureStore.WHEN_UNLOCKED
|
|
60
61
|
* @platform ios
|
|
61
62
|
*/
|
|
@@ -66,7 +67,7 @@ export type SecureStoreOptions = {
|
|
|
66
67
|
* permissions.
|
|
67
68
|
*
|
|
68
69
|
* @return Promise which fulfils witch `boolean`, indicating whether the SecureStore API is available
|
|
69
|
-
* on the current device. Currently this resolves `true` on
|
|
70
|
+
* on the current device. Currently, this resolves `true` on Android and iOS only.
|
|
70
71
|
*/
|
|
71
72
|
export declare function isAvailableAsync(): Promise<boolean>;
|
|
72
73
|
/**
|
|
@@ -84,8 +85,12 @@ export declare function deleteItemAsync(key: string, options?: SecureStoreOption
|
|
|
84
85
|
* @param key The key that was used to store the associated value.
|
|
85
86
|
* @param options An [`SecureStoreOptions`](#securestoreoptions) object.
|
|
86
87
|
*
|
|
87
|
-
* @return A promise that resolves to the previously stored value
|
|
88
|
-
* for the given key.
|
|
88
|
+
* @return A promise that resolves to the previously stored value. It will return `null` if there is no entry
|
|
89
|
+
* for the given key or if the key has been invalidated. It will reject if an error occurs while retrieving the value.
|
|
90
|
+
*
|
|
91
|
+
* > Keys are invalidated by the system when biometrics change, such as adding a new fingerprint or changing the face profile used for face recognition.
|
|
92
|
+
* > After a key has been invalidated, it becomes impossible to read its value.
|
|
93
|
+
* > This only applies to values stored with `requireAuthentication` set to `true`.
|
|
89
94
|
*/
|
|
90
95
|
export declare function getItemAsync(key: string, options?: SecureStoreOptions): Promise<string | null>;
|
|
91
96
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecureStore.d.ts","sourceRoot":"","sources":["../src/SecureStore.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAGnD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,6BAAkE,CAAC;AAGpG;;;GAGG;AACH,eAAO,MAAM,mCAAmC,EAAE,6BACG,CAAC;AAGtD;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,6BAAsD,CAAC;AAG5E;;;GAGG;AACH,eAAO,MAAM,kCAAkC,EAAE,6BACG,CAAC;AAGrD;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,6BACG,CAAC;AAG1C;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,6BAA6D,CAAC;AAG1F;;;GAGG;AACH,eAAO,MAAM,8BAA8B,EAAE,6BACG,CAAC;AAKjD,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB
|
|
1
|
+
{"version":3,"file":"SecureStore.d.ts","sourceRoot":"","sources":["../src/SecureStore.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAGnD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,6BAAkE,CAAC;AAGpG;;;GAGG;AACH,eAAO,MAAM,mCAAmC,EAAE,6BACG,CAAC;AAGtD;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,6BAAsD,CAAC;AAG5E;;;GAGG;AACH,eAAO,MAAM,kCAAkC,EAAE,6BACG,CAAC;AAGrD;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,6BACG,CAAC;AAG1C;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,6BAA6D,CAAC;AAG1F;;;GAGG;AACH,eAAO,MAAM,8BAA8B,EAAE,6BACG,CAAC;AAKjD,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;CACpD,CAAC;AAGF;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAEzD;AAGD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAOf;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAGD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
package/build/SecureStore.js
CHANGED
|
@@ -48,7 +48,7 @@ const VALUE_BYTES_LIMIT = 2048;
|
|
|
48
48
|
* permissions.
|
|
49
49
|
*
|
|
50
50
|
* @return Promise which fulfils witch `boolean`, indicating whether the SecureStore API is available
|
|
51
|
-
* on the current device. Currently this resolves `true` on
|
|
51
|
+
* on the current device. Currently, this resolves `true` on Android and iOS only.
|
|
52
52
|
*/
|
|
53
53
|
export async function isAvailableAsync() {
|
|
54
54
|
return !!ExpoSecureStore.getValueWithKeyAsync;
|
|
@@ -76,8 +76,12 @@ export async function deleteItemAsync(key, options = {}) {
|
|
|
76
76
|
* @param key The key that was used to store the associated value.
|
|
77
77
|
* @param options An [`SecureStoreOptions`](#securestoreoptions) object.
|
|
78
78
|
*
|
|
79
|
-
* @return A promise that resolves to the previously stored value
|
|
80
|
-
* for the given key.
|
|
79
|
+
* @return A promise that resolves to the previously stored value. It will return `null` if there is no entry
|
|
80
|
+
* for the given key or if the key has been invalidated. It will reject if an error occurs while retrieving the value.
|
|
81
|
+
*
|
|
82
|
+
* > Keys are invalidated by the system when biometrics change, such as adding a new fingerprint or changing the face profile used for face recognition.
|
|
83
|
+
* > After a key has been invalidated, it becomes impossible to read its value.
|
|
84
|
+
* > This only applies to values stored with `requireAuthentication` set to `true`.
|
|
81
85
|
*/
|
|
82
86
|
export async function getItemAsync(key, options = {}) {
|
|
83
87
|
_ensureValidKey(key);
|
package/build/SecureStore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecureStore.js","sourceRoot":"","sources":["../src/SecureStore.ts"],"names":[],"mappings":"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","sourcesContent":["import { UnavailabilityError } from 'expo-modules-core';\n\nimport ExpoSecureStore from './ExpoSecureStore';\n\nexport type KeychainAccessibilityConstant = number;\n\n// @needsAudit\n/**\n * The data in the keychain item cannot be accessed after a restart until the device has been\n * unlocked once by the user. This may be useful if you need to access the item when the phone\n * is locked.\n */\nexport const AFTER_FIRST_UNLOCK: KeychainAccessibilityConstant = ExpoSecureStore.AFTER_FIRST_UNLOCK;\n\n// @needsAudit\n/**\n * Similar to `AFTER_FIRST_UNLOCK`, except the entry is not migrated to a new device when restoring\n * from a backup.\n */\nexport const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;\n\n// @needsAudit\n/**\n * The data in the keychain item can always be accessed regardless of whether the device is locked.\n * This is the least secure option.\n */\nexport const ALWAYS: KeychainAccessibilityConstant = ExpoSecureStore.ALWAYS;\n\n// @needsAudit\n/**\n * Similar to `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode in order to\n * store an entry. If the user removes their passcode, the entry will be deleted.\n */\nexport const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;\n\n// @needsAudit\n/**\n * Similar to `ALWAYS`, except the entry is not migrated to a new device when restoring from a backup.\n */\nexport const ALWAYS_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.ALWAYS_THIS_DEVICE_ONLY;\n\n// @needsAudit\n/**\n * The data in the keychain item can be accessed only while the device is unlocked by the user.\n */\nexport const WHEN_UNLOCKED: KeychainAccessibilityConstant = ExpoSecureStore.WHEN_UNLOCKED;\n\n// @needsAudit\n/**\n * Similar to `WHEN_UNLOCKED`, except the entry is not migrated to a new device when restoring from\n * a backup.\n */\nexport const WHEN_UNLOCKED_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;\n\nconst VALUE_BYTES_LIMIT = 2048;\n\n// @needsAudit\nexport type SecureStoreOptions = {\n /**\n * - iOS: The item's service, equivalent to `kSecAttrService`\n * - Android: Equivalent of the public/private key pair `Alias`\n * > If the item is set with the `keychainService` option, it will be required to later fetch the value.\n */\n keychainService?: string;\n /**\n * Option responsible for enabling the usage of the user authentication methods available on the device while\n * accessing data stored in SecureStore.\n * - iOS: Equivalent to `kSecAccessControlBiometryCurrentSet`\n * - Android: Equivalent to `setUserAuthenticationRequired(true)` (requires API 23).\n * Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`\n * value used for the others non-authenticated operations.\n */\n requireAuthentication?: boolean;\n /**\n * Custom message displayed to the user while `requireAuthentication` option is turned on.\n */\n authenticationPrompt?: string;\n /**\n * Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.\n * @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html).\n * @default SecureStore.WHEN_UNLOCKED\n * @platform ios\n */\n keychainAccessible?: KeychainAccessibilityConstant;\n};\n\n// @needsAudit\n/**\n * Returns whether the SecureStore API is enabled on the current device. This does not check the app\n * permissions.\n *\n * @return Promise which fulfils witch `boolean`, indicating whether the SecureStore API is available\n * on the current device. Currently this resolves `true` on iOS and Android only.\n */\nexport async function isAvailableAsync(): Promise<boolean> {\n return !!ExpoSecureStore.getValueWithKeyAsync;\n}\n\n// @needsAudit\n/**\n * Delete the value associated with the provided key.\n *\n * @param key The key that was used to store the associated value.\n * @param options An [`SecureStoreOptions`](#securestoreoptions) object.\n *\n * @return A promise that will reject if the value couldn't be deleted.\n */\nexport async function deleteItemAsync(\n key: string,\n options: SecureStoreOptions = {}\n): Promise<void> {\n _ensureValidKey(key);\n\n if (!ExpoSecureStore.deleteValueWithKeyAsync) {\n throw new UnavailabilityError('SecureStore', 'deleteItemAsync');\n }\n await ExpoSecureStore.deleteValueWithKeyAsync(key, options);\n}\n\n// @needsAudit\n/**\n * Fetch the stored value associated with the provided key.\n *\n * @param key The key that was used to store the associated value.\n * @param options An [`SecureStoreOptions`](#securestoreoptions) object.\n *\n * @return A promise that resolves to the previously stored value, or `null` if there is no entry\n * for the given key. The promise will reject if an error occurred while retrieving the value.\n */\nexport async function getItemAsync(\n key: string,\n options: SecureStoreOptions = {}\n): Promise<string | null> {\n _ensureValidKey(key);\n return await ExpoSecureStore.getValueWithKeyAsync(key, options);\n}\n\n// @needsAudit\n/**\n * Store a keyβvalue pair.\n *\n * @param key The key to associate with the stored value. Keys may contain alphanumeric characters\n * `.`, `-`, and `_`.\n * @param value The value to store. Size limit is 2048 bytes.\n * @param options An [`SecureStoreOptions`](#securestoreoptions) object.\n *\n * @return A promise that will reject if value cannot be stored on the device.\n */\nexport async function setItemAsync(\n key: string,\n value: string,\n options: SecureStoreOptions = {}\n): Promise<void> {\n _ensureValidKey(key);\n if (!_isValidValue(value)) {\n throw new Error(\n `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`\n );\n }\n if (!ExpoSecureStore.setValueWithKeyAsync) {\n throw new UnavailabilityError('SecureStore', 'setItemAsync');\n }\n await ExpoSecureStore.setValueWithKeyAsync(value, key, options);\n}\n\nfunction _ensureValidKey(key: string) {\n if (!_isValidKey(key)) {\n throw new Error(\n `Invalid key provided to SecureStore. Keys must not be empty and contain only alphanumeric characters, \".\", \"-\", and \"_\".`\n );\n }\n}\n\nfunction _isValidKey(key: string) {\n return typeof key === 'string' && /^[\\w.-]+$/.test(key);\n}\n\nfunction _isValidValue(value: string) {\n if (typeof value !== 'string') {\n return false;\n }\n if (_byteCount(value) > VALUE_BYTES_LIMIT) {\n console.warn(\n 'Provided value to SecureStore is larger than 2048 bytes. An attempt to store such a value will throw an error in SDK 35.'\n );\n }\n return true;\n}\n\n// copy-pasted from https://stackoverflow.com/a/39488643\nfunction _byteCount(value: string) {\n let bytes = 0;\n\n for (let i = 0; i < value.length; i++) {\n const codePoint = value.charCodeAt(i);\n\n // Lone surrogates cannot be passed to encodeURI\n if (codePoint >= 0xd800 && codePoint < 0xe000) {\n if (codePoint < 0xdc00 && i + 1 < value.length) {\n const next = value.charCodeAt(i + 1);\n\n if (next >= 0xdc00 && next < 0xe000) {\n bytes += 4;\n i++;\n continue;\n }\n }\n }\n\n bytes += codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : 3;\n }\n\n return bytes;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SecureStore.js","sourceRoot":"","sources":["../src/SecureStore.ts"],"names":[],"mappings":"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","sourcesContent":["import { UnavailabilityError } from 'expo-modules-core';\n\nimport ExpoSecureStore from './ExpoSecureStore';\n\nexport type KeychainAccessibilityConstant = number;\n\n// @needsAudit\n/**\n * The data in the keychain item cannot be accessed after a restart until the device has been\n * unlocked once by the user. This may be useful if you need to access the item when the phone\n * is locked.\n */\nexport const AFTER_FIRST_UNLOCK: KeychainAccessibilityConstant = ExpoSecureStore.AFTER_FIRST_UNLOCK;\n\n// @needsAudit\n/**\n * Similar to `AFTER_FIRST_UNLOCK`, except the entry is not migrated to a new device when restoring\n * from a backup.\n */\nexport const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;\n\n// @needsAudit\n/**\n * The data in the keychain item can always be accessed regardless of whether the device is locked.\n * This is the least secure option.\n */\nexport const ALWAYS: KeychainAccessibilityConstant = ExpoSecureStore.ALWAYS;\n\n// @needsAudit\n/**\n * Similar to `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode in order to\n * store an entry. If the user removes their passcode, the entry will be deleted.\n */\nexport const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;\n\n// @needsAudit\n/**\n * Similar to `ALWAYS`, except the entry is not migrated to a new device when restoring from a backup.\n */\nexport const ALWAYS_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.ALWAYS_THIS_DEVICE_ONLY;\n\n// @needsAudit\n/**\n * The data in the keychain item can be accessed only while the device is unlocked by the user.\n */\nexport const WHEN_UNLOCKED: KeychainAccessibilityConstant = ExpoSecureStore.WHEN_UNLOCKED;\n\n// @needsAudit\n/**\n * Similar to `WHEN_UNLOCKED`, except the entry is not migrated to a new device when restoring from\n * a backup.\n */\nexport const WHEN_UNLOCKED_THIS_DEVICE_ONLY: KeychainAccessibilityConstant =\n ExpoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;\n\nconst VALUE_BYTES_LIMIT = 2048;\n\n// @needsAudit\nexport type SecureStoreOptions = {\n /**\n * - Android: Equivalent of the public/private key pair `Alias`.\n * - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).\n * > If the item is set with the `keychainService` option, it will be required to later fetch the value.\n */\n keychainService?: string;\n /**\n * Option responsible for enabling the usage of the user authentication methods available on the device while\n * accessing data stored in SecureStore.\n * - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))\n * (requires API 23).\n * - iOS: Equivalent to [`kSecAccessControlBiometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolbiometrycurrentset/).\n * Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`\n * value used for the others non-authenticated operations.\n */\n requireAuthentication?: boolean;\n /**\n * Custom message displayed to the user while `requireAuthentication` option is turned on.\n */\n authenticationPrompt?: string;\n /**\n * Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.\n * @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).\n * @default SecureStore.WHEN_UNLOCKED\n * @platform ios\n */\n keychainAccessible?: KeychainAccessibilityConstant;\n};\n\n// @needsAudit\n/**\n * Returns whether the SecureStore API is enabled on the current device. This does not check the app\n * permissions.\n *\n * @return Promise which fulfils witch `boolean`, indicating whether the SecureStore API is available\n * on the current device. Currently, this resolves `true` on Android and iOS only.\n */\nexport async function isAvailableAsync(): Promise<boolean> {\n return !!ExpoSecureStore.getValueWithKeyAsync;\n}\n\n// @needsAudit\n/**\n * Delete the value associated with the provided key.\n *\n * @param key The key that was used to store the associated value.\n * @param options An [`SecureStoreOptions`](#securestoreoptions) object.\n *\n * @return A promise that will reject if the value couldn't be deleted.\n */\nexport async function deleteItemAsync(\n key: string,\n options: SecureStoreOptions = {}\n): Promise<void> {\n _ensureValidKey(key);\n\n if (!ExpoSecureStore.deleteValueWithKeyAsync) {\n throw new UnavailabilityError('SecureStore', 'deleteItemAsync');\n }\n await ExpoSecureStore.deleteValueWithKeyAsync(key, options);\n}\n\n// @needsAudit\n/**\n * Fetch the stored value associated with the provided key.\n *\n * @param key The key that was used to store the associated value.\n * @param options An [`SecureStoreOptions`](#securestoreoptions) object.\n *\n * @return A promise that resolves to the previously stored value. It will return `null` if there is no entry\n * for the given key or if the key has been invalidated. It will reject if an error occurs while retrieving the value.\n *\n * > Keys are invalidated by the system when biometrics change, such as adding a new fingerprint or changing the face profile used for face recognition.\n * > After a key has been invalidated, it becomes impossible to read its value.\n * > This only applies to values stored with `requireAuthentication` set to `true`.\n */\nexport async function getItemAsync(\n key: string,\n options: SecureStoreOptions = {}\n): Promise<string | null> {\n _ensureValidKey(key);\n return await ExpoSecureStore.getValueWithKeyAsync(key, options);\n}\n\n// @needsAudit\n/**\n * Store a keyβvalue pair.\n *\n * @param key The key to associate with the stored value. Keys may contain alphanumeric characters\n * `.`, `-`, and `_`.\n * @param value The value to store. Size limit is 2048 bytes.\n * @param options An [`SecureStoreOptions`](#securestoreoptions) object.\n *\n * @return A promise that will reject if value cannot be stored on the device.\n */\nexport async function setItemAsync(\n key: string,\n value: string,\n options: SecureStoreOptions = {}\n): Promise<void> {\n _ensureValidKey(key);\n if (!_isValidValue(value)) {\n throw new Error(\n `Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.`\n );\n }\n if (!ExpoSecureStore.setValueWithKeyAsync) {\n throw new UnavailabilityError('SecureStore', 'setItemAsync');\n }\n await ExpoSecureStore.setValueWithKeyAsync(value, key, options);\n}\n\nfunction _ensureValidKey(key: string) {\n if (!_isValidKey(key)) {\n throw new Error(\n `Invalid key provided to SecureStore. Keys must not be empty and contain only alphanumeric characters, \".\", \"-\", and \"_\".`\n );\n }\n}\n\nfunction _isValidKey(key: string) {\n return typeof key === 'string' && /^[\\w.-]+$/.test(key);\n}\n\nfunction _isValidValue(value: string) {\n if (typeof value !== 'string') {\n return false;\n }\n if (_byteCount(value) > VALUE_BYTES_LIMIT) {\n console.warn(\n 'Provided value to SecureStore is larger than 2048 bytes. An attempt to store such a value will throw an error in SDK 35.'\n );\n }\n return true;\n}\n\n// copy-pasted from https://stackoverflow.com/a/39488643\nfunction _byteCount(value: string) {\n let bytes = 0;\n\n for (let i = 0; i < value.length; i++) {\n const codePoint = value.charCodeAt(i);\n\n // Lone surrogates cannot be passed to encodeURI\n if (codePoint >= 0xd800 && codePoint < 0xe000) {\n if (codePoint < 0xdc00 && i + 1 < value.length) {\n const next = value.charCodeAt(i + 1);\n\n if (next >= 0xdc00 && next < 0xe000) {\n bytes += 4;\n i++;\n continue;\n }\n }\n }\n\n bytes += codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : 3;\n }\n\n return bytes;\n}\n"]}
|
|
@@ -3,7 +3,7 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = '
|
|
6
|
+
s.name = 'ExpoSecureStore'
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = package['description']
|
|
9
9
|
s.description = package['description']
|
|
@@ -11,15 +11,22 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.homepage = package['homepage']
|
|
13
13
|
s.platform = :ios, '13.0'
|
|
14
|
+
s.swift_version = '5.4'
|
|
14
15
|
s.source = { git: 'https://github.com/expo/expo.git' }
|
|
15
16
|
s.static_framework = true
|
|
16
17
|
|
|
17
18
|
s.dependency 'ExpoModulesCore'
|
|
18
19
|
|
|
20
|
+
# Swift/Objective-C compatibility
|
|
21
|
+
s.pod_target_xcconfig = {
|
|
22
|
+
'DEFINES_MODULE' => 'YES',
|
|
23
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
|
|
20
|
-
s.source_files = "
|
|
27
|
+
s.source_files = "**/*.h"
|
|
21
28
|
s.vendored_frameworks = "#{s.name}.xcframework"
|
|
22
29
|
else
|
|
23
|
-
s.source_files = "
|
|
30
|
+
s.source_files = "**/*.{h,m,swift}"
|
|
24
31
|
end
|
|
25
32
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
enum SecureStoreAccessible: Int, Enumerable {
|
|
4
|
+
case afterFirstUnlock = 0
|
|
5
|
+
case afterFirstUnlockThisDeviceOnly = 1
|
|
6
|
+
case always = 2
|
|
7
|
+
case whenPasscodeSetThisDeviceOnly = 3
|
|
8
|
+
case alwaysThisDeviceOnly = 4
|
|
9
|
+
case whenUnlocked = 5
|
|
10
|
+
case whenUnlockedThisDeviceOnly = 6
|
|
11
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
internal class InvalidKeyException: Exception {
|
|
4
|
+
override var reason: String {
|
|
5
|
+
"Invalid key"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
internal class KeyChainException: GenericException<OSStatus> {
|
|
10
|
+
override var reason: String {
|
|
11
|
+
switch param {
|
|
12
|
+
case errSecUnimplemented:
|
|
13
|
+
return "Function or operation not implemented."
|
|
14
|
+
|
|
15
|
+
case errSecIO:
|
|
16
|
+
return "I/O error."
|
|
17
|
+
|
|
18
|
+
case errSecOpWr:
|
|
19
|
+
return "File already open with with write permission."
|
|
20
|
+
|
|
21
|
+
case errSecParam:
|
|
22
|
+
return "One or more parameters passed to a function where not valid."
|
|
23
|
+
|
|
24
|
+
case errSecAllocate:
|
|
25
|
+
return "Failed to allocate memory."
|
|
26
|
+
|
|
27
|
+
case errSecUserCanceled:
|
|
28
|
+
return "User canceled the operation."
|
|
29
|
+
|
|
30
|
+
case errSecBadReq:
|
|
31
|
+
return "Bad parameter or invalid state for operation."
|
|
32
|
+
|
|
33
|
+
case errSecNotAvailable:
|
|
34
|
+
return "No keychain is available. You may need to restart your computer."
|
|
35
|
+
|
|
36
|
+
case errSecDuplicateItem:
|
|
37
|
+
return "The specified item already exists in the keychain."
|
|
38
|
+
|
|
39
|
+
case errSecItemNotFound:
|
|
40
|
+
return "The specified item could not be found in the keychain."
|
|
41
|
+
|
|
42
|
+
case errSecInteractionNotAllowed:
|
|
43
|
+
return "User interaction is not allowed."
|
|
44
|
+
|
|
45
|
+
case errSecDecode:
|
|
46
|
+
return "Unable to decode the provided data."
|
|
47
|
+
|
|
48
|
+
case errSecAuthFailed:
|
|
49
|
+
return "Authentication failed. Provided passphrase/PIN is incorrect or there is no user authentication method configured for this device."
|
|
50
|
+
|
|
51
|
+
default:
|
|
52
|
+
return "Unknown Keychain Error."
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import LocalAuthentication
|
|
3
|
+
import Security
|
|
4
|
+
|
|
5
|
+
public final class SecureStoreModule: Module {
|
|
6
|
+
public func definition() -> ModuleDefinition {
|
|
7
|
+
Name("ExpoSecureStore")
|
|
8
|
+
|
|
9
|
+
Constants([
|
|
10
|
+
"AFTER_FIRST_UNLOCK": SecureStoreAccessible.afterFirstUnlock.rawValue,
|
|
11
|
+
"AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY": SecureStoreAccessible.afterFirstUnlockThisDeviceOnly.rawValue,
|
|
12
|
+
"ALWAYS": SecureStoreAccessible.always.rawValue,
|
|
13
|
+
"WHEN_PASSCODE_SET_THIS_DEVICE_ONLY": SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue,
|
|
14
|
+
"ALWAYS_THIS_DEVICE_ONLY": SecureStoreAccessible.alwaysThisDeviceOnly.rawValue,
|
|
15
|
+
"WHEN_UNLOCKED": SecureStoreAccessible.whenUnlocked.rawValue,
|
|
16
|
+
"WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) -> String? in
|
|
20
|
+
guard let key = validate(for: key) else {
|
|
21
|
+
throw InvalidKeyException()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let data = try searchKeyChain(with: key, options: options)
|
|
25
|
+
|
|
26
|
+
guard let data = data else {
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return String(data: data, encoding: .utf8)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
AsyncFunction("setValueWithKeyAsync") { (value: String, key: String, options: SecureStoreOptions) -> Bool in
|
|
34
|
+
guard let key = validate(for: key) else {
|
|
35
|
+
throw InvalidKeyException()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return try set(value: value, with: key, options: options)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AsyncFunction("deleteValueWithKeyAsync") { (key: String, options: SecureStoreOptions) in
|
|
42
|
+
var searchDictionary = query(with: key, options: options)
|
|
43
|
+
SecItemDelete(searchDictionary as CFDictionary)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func set(value: String, with key: String, options: SecureStoreOptions) throws -> Bool {
|
|
48
|
+
var query = query(with: key, options: options)
|
|
49
|
+
|
|
50
|
+
let valueData = value.data(using: .utf8)
|
|
51
|
+
query[kSecValueData as String] = valueData
|
|
52
|
+
|
|
53
|
+
let accessibility = attributeWith(options: options)
|
|
54
|
+
|
|
55
|
+
if !options.requireAuthentication {
|
|
56
|
+
query[kSecAttrAccessible as String] = accessibility
|
|
57
|
+
} else {
|
|
58
|
+
let accessOptions = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibility, SecAccessControlCreateFlags.biometryCurrentSet, nil)
|
|
59
|
+
query[kSecAttrAccessControl as String] = accessOptions
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
63
|
+
|
|
64
|
+
switch status {
|
|
65
|
+
case errSecSuccess:
|
|
66
|
+
return true
|
|
67
|
+
case errSecDuplicateItem:
|
|
68
|
+
return try update(value: value, with: key, options: options)
|
|
69
|
+
default:
|
|
70
|
+
throw KeyChainException(status)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func update(value: String, with key: String, options: SecureStoreOptions) throws -> Bool {
|
|
75
|
+
var query = query(with: key, options: options)
|
|
76
|
+
|
|
77
|
+
let valueData = value.data(using: .utf8)
|
|
78
|
+
let updateDictionary = [kSecValueData as String: valueData]
|
|
79
|
+
|
|
80
|
+
if let authPrompt = options.authenticationPrompt {
|
|
81
|
+
query[kSecUseOperationPrompt as String] = authPrompt
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let status = SecItemUpdate(query as CFDictionary, updateDictionary as CFDictionary)
|
|
85
|
+
|
|
86
|
+
if status == errSecSuccess {
|
|
87
|
+
return true
|
|
88
|
+
} else {
|
|
89
|
+
throw KeyChainException(status)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private func searchKeyChain(with key: String, options: SecureStoreOptions) throws -> Data? {
|
|
94
|
+
var query = query(with: key, options: options)
|
|
95
|
+
|
|
96
|
+
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
97
|
+
query[kSecReturnData as String] = kCFBooleanTrue
|
|
98
|
+
|
|
99
|
+
if let authPrompt = options.authenticationPrompt {
|
|
100
|
+
query[kSecUseOperationPrompt as String] = authPrompt
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
var item: CFTypeRef?
|
|
104
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
105
|
+
|
|
106
|
+
switch status {
|
|
107
|
+
case errSecSuccess:
|
|
108
|
+
guard let item = item as? Data else {
|
|
109
|
+
return nil
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return item
|
|
113
|
+
case errSecItemNotFound:
|
|
114
|
+
return nil
|
|
115
|
+
default:
|
|
116
|
+
throw KeyChainException(status)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private func query(with key: String, options: SecureStoreOptions) -> [String: Any] {
|
|
121
|
+
let service = options.keychainService ?? "app"
|
|
122
|
+
let encodedKey = key.data(using: .utf8)
|
|
123
|
+
|
|
124
|
+
return [
|
|
125
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
126
|
+
kSecAttrService as String: service,
|
|
127
|
+
kSecAttrGeneric as String: encodedKey,
|
|
128
|
+
kSecAttrAccount as String: encodedKey
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func attributeWith(options: SecureStoreOptions) -> CFString {
|
|
133
|
+
switch options.keychainAccessible {
|
|
134
|
+
case .afterFirstUnlock:
|
|
135
|
+
return kSecAttrAccessibleAfterFirstUnlock
|
|
136
|
+
case .afterFirstUnlockThisDeviceOnly:
|
|
137
|
+
return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
138
|
+
case .always:
|
|
139
|
+
return kSecAttrAccessibleAlways
|
|
140
|
+
case .whenPasscodeSetThisDeviceOnly:
|
|
141
|
+
return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
|
|
142
|
+
case .whenUnlocked:
|
|
143
|
+
return kSecAttrAccessibleWhenUnlocked
|
|
144
|
+
case .alwaysThisDeviceOnly:
|
|
145
|
+
return kSecAttrAccessibleAlwaysThisDeviceOnly
|
|
146
|
+
case .whenUnlockedThisDeviceOnly:
|
|
147
|
+
return kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
148
|
+
default:
|
|
149
|
+
return kSecAttrAccessibleWhenUnlocked
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func validate(for key: String) -> String? {
|
|
154
|
+
let trimmedKey = key.trimmingCharacters(in: .whitespaces)
|
|
155
|
+
if trimmedKey.isEmpty {
|
|
156
|
+
return nil
|
|
157
|
+
}
|
|
158
|
+
return key
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
internal struct SecureStoreOptions: Record {
|
|
4
|
+
@Field
|
|
5
|
+
var authenticationPrompt: String?
|
|
6
|
+
|
|
7
|
+
@Field
|
|
8
|
+
var keychainAccessible: SecureStoreAccessible = .whenUnlocked
|
|
9
|
+
|
|
10
|
+
@Field
|
|
11
|
+
var keychainService: String?
|
|
12
|
+
|
|
13
|
+
@Field
|
|
14
|
+
var requireAuthentication: Bool
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-secure-store",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.3.0",
|
|
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": "3ccd2edee9cbfed217557675cb50f0ba5e55a9e4"
|
|
46
46
|
}
|
package/src/ExpoSecureStore.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
export default requireNativeModule('ExpoSecureStore');
|
package/src/SecureStore.ts
CHANGED
|
@@ -61,16 +61,17 @@ const VALUE_BYTES_LIMIT = 2048;
|
|
|
61
61
|
// @needsAudit
|
|
62
62
|
export type SecureStoreOptions = {
|
|
63
63
|
/**
|
|
64
|
-
* -
|
|
65
|
-
* -
|
|
64
|
+
* - Android: Equivalent of the public/private key pair `Alias`.
|
|
65
|
+
* - iOS: The item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
|
|
66
66
|
* > If the item is set with the `keychainService` option, it will be required to later fetch the value.
|
|
67
67
|
*/
|
|
68
68
|
keychainService?: string;
|
|
69
69
|
/**
|
|
70
70
|
* Option responsible for enabling the usage of the user authentication methods available on the device while
|
|
71
71
|
* accessing data stored in SecureStore.
|
|
72
|
-
* -
|
|
73
|
-
*
|
|
72
|
+
* - Android: Equivalent to [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean))
|
|
73
|
+
* (requires API 23).
|
|
74
|
+
* - iOS: Equivalent to [`kSecAccessControlBiometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolbiometrycurrentset/).
|
|
74
75
|
* Complete functionality is unlocked only with a freshly generated key - this would not work in tandem with the `keychainService`
|
|
75
76
|
* value used for the others non-authenticated operations.
|
|
76
77
|
*/
|
|
@@ -81,7 +82,7 @@ export type SecureStoreOptions = {
|
|
|
81
82
|
authenticationPrompt?: string;
|
|
82
83
|
/**
|
|
83
84
|
* Specifies when the stored entry is accessible, using iOS's `kSecAttrAccessible` property.
|
|
84
|
-
* @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/
|
|
85
|
+
* @see Apple's documentation on [keychain item accessibility](https://developer.apple.com/documentation/security/ksecattraccessible/).
|
|
85
86
|
* @default SecureStore.WHEN_UNLOCKED
|
|
86
87
|
* @platform ios
|
|
87
88
|
*/
|
|
@@ -94,7 +95,7 @@ export type SecureStoreOptions = {
|
|
|
94
95
|
* permissions.
|
|
95
96
|
*
|
|
96
97
|
* @return Promise which fulfils witch `boolean`, indicating whether the SecureStore API is available
|
|
97
|
-
* on the current device. Currently this resolves `true` on
|
|
98
|
+
* on the current device. Currently, this resolves `true` on Android and iOS only.
|
|
98
99
|
*/
|
|
99
100
|
export async function isAvailableAsync(): Promise<boolean> {
|
|
100
101
|
return !!ExpoSecureStore.getValueWithKeyAsync;
|
|
@@ -128,8 +129,12 @@ export async function deleteItemAsync(
|
|
|
128
129
|
* @param key The key that was used to store the associated value.
|
|
129
130
|
* @param options An [`SecureStoreOptions`](#securestoreoptions) object.
|
|
130
131
|
*
|
|
131
|
-
* @return A promise that resolves to the previously stored value
|
|
132
|
-
* for the given key.
|
|
132
|
+
* @return A promise that resolves to the previously stored value. It will return `null` if there is no entry
|
|
133
|
+
* for the given key or if the key has been invalidated. It will reject if an error occurs while retrieving the value.
|
|
134
|
+
*
|
|
135
|
+
* > Keys are invalidated by the system when biometrics change, such as adding a new fingerprint or changing the face profile used for face recognition.
|
|
136
|
+
* > After a key has been invalidated, it becomes impossible to read its value.
|
|
137
|
+
* > This only applies to values stored with `requireAuthentication` set to `true`.
|
|
133
138
|
*/
|
|
134
139
|
export async function getItemAsync(
|
|
135
140
|
key: string,
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Copyright Β© 2018 650 Industries. All rights reserved.
|
|
2
|
-
|
|
3
|
-
#import <ExpoModulesCore/EXExportedModule.h>
|
|
4
|
-
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
|
|
5
|
-
|
|
6
|
-
typedef NS_ENUM(NSInteger, EXSecureStoreAccessible) {
|
|
7
|
-
EXSecureStoreAccessibleAfterFirstUnlock = 0,
|
|
8
|
-
EXSecureStoreAccessibleAfterFirstUnlockThisDeviceOnly = 1,
|
|
9
|
-
EXSecureStoreAccessibleAlways = 2,
|
|
10
|
-
EXSecureStoreAccessibleWhenPasscodeSetThisDeviceOnly = 3,
|
|
11
|
-
EXSecureStoreAccessibleAlwaysThisDeviceOnly = 4,
|
|
12
|
-
EXSecureStoreAccessibleWhenUnlocked = 5,
|
|
13
|
-
EXSecureStoreAccessibleWhenUnlockedThisDeviceOnly = 6
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
@interface EXSecureStore : EXExportedModule
|
|
17
|
-
|
|
18
|
-
@end
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
// Copyright Β© 2018 650 Industries. All rights reserved.
|
|
2
|
-
|
|
3
|
-
#import <EXSecureStore/EXSecureStore.h>
|
|
4
|
-
|
|
5
|
-
#import <CommonCrypto/CommonHMAC.h>
|
|
6
|
-
#import <Security/Security.h>
|
|
7
|
-
|
|
8
|
-
@implementation EXSecureStore
|
|
9
|
-
|
|
10
|
-
#pragma mark - internal
|
|
11
|
-
|
|
12
|
-
- (NSMutableDictionary *)_queryWithKey:(NSString *)key withOptions:(NSDictionary *)options
|
|
13
|
-
{
|
|
14
|
-
NSString *service = (NSString *) options[@"keychainService"] ? (NSString *) options[@"keychainService"]:@"app";
|
|
15
|
-
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
|
|
16
|
-
NSMutableDictionary *query = [@{
|
|
17
|
-
(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,
|
|
18
|
-
(__bridge id)kSecAttrService:service,
|
|
19
|
-
(__bridge id)kSecAttrGeneric:encodedKey,
|
|
20
|
-
(__bridge id)kSecAttrAccount:encodedKey
|
|
21
|
-
} mutableCopy];
|
|
22
|
-
|
|
23
|
-
return query;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
- (BOOL)_setValue:(NSString *)value withKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error
|
|
27
|
-
{
|
|
28
|
-
NSMutableDictionary *dictionary = [self _queryWithKey:key
|
|
29
|
-
withOptions:options];
|
|
30
|
-
|
|
31
|
-
NSString *requireAuth = options[@"requireAuthentication"];
|
|
32
|
-
|
|
33
|
-
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
|
|
34
|
-
[dictionary setObject:valueData forKey:(__bridge id)kSecValueData];
|
|
35
|
-
|
|
36
|
-
CFStringRef accessibility = [self _accessibilityAttributeWithOptions:options];
|
|
37
|
-
|
|
38
|
-
if (![requireAuth boolValue]) {
|
|
39
|
-
[dictionary setObject:(__bridge id)accessibility forKey:(__bridge id)kSecAttrAccessible];
|
|
40
|
-
} else {
|
|
41
|
-
SecAccessControlRef accessOptions = SecAccessControlCreateWithFlags(nil, accessibility, kSecAccessControlBiometryCurrentSet, nil);
|
|
42
|
-
[dictionary setObject:(__bridge_transfer id)accessOptions forKey:(__bridge id)kSecAttrAccessControl];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
|
|
46
|
-
|
|
47
|
-
if (status == errSecSuccess) {
|
|
48
|
-
return YES;
|
|
49
|
-
} else if (status == errSecDuplicateItem){
|
|
50
|
-
NSError *updateError;
|
|
51
|
-
BOOL update = [self _updateValue:value
|
|
52
|
-
withKey:key
|
|
53
|
-
withOptions:options
|
|
54
|
-
error:&updateError];
|
|
55
|
-
*error = updateError;
|
|
56
|
-
return update;
|
|
57
|
-
} else {
|
|
58
|
-
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
|
59
|
-
return NO;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
- (NSString *)_getValueWithKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error
|
|
64
|
-
{
|
|
65
|
-
NSError *searchError;
|
|
66
|
-
NSData *data = [self _searchKeychainWithKey:key
|
|
67
|
-
withOptions:options
|
|
68
|
-
error:&searchError];
|
|
69
|
-
if (data) {
|
|
70
|
-
NSString *value = [[NSString alloc] initWithData:data
|
|
71
|
-
encoding:NSUTF8StringEncoding];
|
|
72
|
-
return value;
|
|
73
|
-
} else {
|
|
74
|
-
*error = searchError;
|
|
75
|
-
return nil;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
- (BOOL)_updateValue:(NSString *)value withKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error
|
|
80
|
-
{
|
|
81
|
-
NSMutableDictionary *searchDictionary = [self _queryWithKey:key
|
|
82
|
-
withOptions:options];
|
|
83
|
-
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
|
|
84
|
-
NSDictionary *updateDictionary = @{(__bridge id)kSecValueData:valueData};
|
|
85
|
-
|
|
86
|
-
if ((NSString *) options[@"authenticationPrompt"]) {
|
|
87
|
-
NSString *promptText = options[@"authenticationPrompt"];
|
|
88
|
-
[searchDictionary setObject:promptText forKey:(__bridge id)kSecUseOperationPrompt];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary,
|
|
92
|
-
(__bridge CFDictionaryRef)updateDictionary);
|
|
93
|
-
|
|
94
|
-
if (status == errSecSuccess) {
|
|
95
|
-
return YES;
|
|
96
|
-
} else {
|
|
97
|
-
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
|
98
|
-
return NO;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
- (NSData *)_searchKeychainWithKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error
|
|
103
|
-
{
|
|
104
|
-
NSMutableDictionary *searchDictionary = [self _queryWithKey:key
|
|
105
|
-
withOptions:options];
|
|
106
|
-
|
|
107
|
-
[searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
|
|
108
|
-
[searchDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
|
|
109
|
-
|
|
110
|
-
if ((NSString *) options[@"authenticationPrompt"]) {
|
|
111
|
-
NSString *promptText = options[@"authenticationPrompt"];
|
|
112
|
-
[searchDictionary setObject:promptText forKey:(__bridge id)kSecUseOperationPrompt];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
CFTypeRef foundDict = NULL;
|
|
116
|
-
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &foundDict);
|
|
117
|
-
|
|
118
|
-
if (status == noErr) {
|
|
119
|
-
NSData *result = (__bridge_transfer NSData *)foundDict;
|
|
120
|
-
return result;
|
|
121
|
-
} else {
|
|
122
|
-
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
|
123
|
-
return nil;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
- (void)_deleteValueWithKey:(NSString *)key withOptions:(NSDictionary *)options
|
|
128
|
-
{
|
|
129
|
-
NSMutableDictionary *searchDictionary = [self _queryWithKey:key
|
|
130
|
-
withOptions:options];
|
|
131
|
-
CFDictionaryRef dictionary = (__bridge CFDictionaryRef)searchDictionary;
|
|
132
|
-
|
|
133
|
-
SecItemDelete(dictionary);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
- (CFStringRef)_accessibilityAttributeWithOptions:(NSDictionary *)options
|
|
137
|
-
{
|
|
138
|
-
NSInteger accessibility = [options[@"keychainAccessible"] integerValue];
|
|
139
|
-
switch (accessibility) {
|
|
140
|
-
case EXSecureStoreAccessibleAfterFirstUnlock:
|
|
141
|
-
return kSecAttrAccessibleAfterFirstUnlock;
|
|
142
|
-
case EXSecureStoreAccessibleAfterFirstUnlockThisDeviceOnly:
|
|
143
|
-
return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
|
144
|
-
case EXSecureStoreAccessibleAlways:
|
|
145
|
-
return kSecAttrAccessibleAlways;
|
|
146
|
-
case EXSecureStoreAccessibleWhenPasscodeSetThisDeviceOnly:
|
|
147
|
-
return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly;
|
|
148
|
-
case EXSecureStoreAccessibleWhenUnlocked:
|
|
149
|
-
return kSecAttrAccessibleWhenUnlocked;
|
|
150
|
-
case EXSecureStoreAccessibleAlwaysThisDeviceOnly:
|
|
151
|
-
return kSecAttrAccessibleAlwaysThisDeviceOnly;
|
|
152
|
-
case EXSecureStoreAccessibleWhenUnlockedThisDeviceOnly:
|
|
153
|
-
return kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
|
|
154
|
-
default:
|
|
155
|
-
return kSecAttrAccessibleWhenUnlocked;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
+ (NSString *) _messageForError:(NSError *)error
|
|
160
|
-
{
|
|
161
|
-
switch (error.code) {
|
|
162
|
-
case errSecUnimplemented:
|
|
163
|
-
return @"Function or operation not implemented.";
|
|
164
|
-
|
|
165
|
-
case errSecIO:
|
|
166
|
-
return @"I/O error.";
|
|
167
|
-
|
|
168
|
-
case errSecOpWr:
|
|
169
|
-
return @"File already open with with write permission.";
|
|
170
|
-
|
|
171
|
-
case errSecParam:
|
|
172
|
-
return @"One or more parameters passed to a function where not valid.";
|
|
173
|
-
|
|
174
|
-
case errSecAllocate:
|
|
175
|
-
return @"Failed to allocate memory.";
|
|
176
|
-
|
|
177
|
-
case errSecUserCanceled:
|
|
178
|
-
return @"User canceled the operation.";
|
|
179
|
-
|
|
180
|
-
case errSecBadReq:
|
|
181
|
-
return @"Bad parameter or invalid state for operation.";
|
|
182
|
-
|
|
183
|
-
case errSecNotAvailable:
|
|
184
|
-
return @"No keychain is available. You may need to restart your computer.";
|
|
185
|
-
|
|
186
|
-
case errSecDuplicateItem:
|
|
187
|
-
return @"The specified item already exists in the keychain.";
|
|
188
|
-
|
|
189
|
-
case errSecItemNotFound:
|
|
190
|
-
return @"The specified item could not be found in the keychain.";
|
|
191
|
-
|
|
192
|
-
case errSecInteractionNotAllowed:
|
|
193
|
-
return @"User interaction is not allowed.";
|
|
194
|
-
|
|
195
|
-
case errSecDecode:
|
|
196
|
-
return @"Unable to decode the provided data.";
|
|
197
|
-
|
|
198
|
-
case errSecAuthFailed:
|
|
199
|
-
return @"Authentication failed. Provided passphrase/PIN is incorrect or there is no user authentication method configured for this device.";
|
|
200
|
-
|
|
201
|
-
default:
|
|
202
|
-
return error.localizedDescription;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
#pragma mark - SecureStore API
|
|
207
|
-
|
|
208
|
-
- (NSDictionary *)constantsToExport
|
|
209
|
-
{
|
|
210
|
-
return @{
|
|
211
|
-
@"AFTER_FIRST_UNLOCK":@(EXSecureStoreAccessibleAfterFirstUnlock),
|
|
212
|
-
@"AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY":@(EXSecureStoreAccessibleAfterFirstUnlockThisDeviceOnly),
|
|
213
|
-
@"ALWAYS":@(EXSecureStoreAccessibleAlways),
|
|
214
|
-
@"WHEN_PASSCODE_SET_THIS_DEVICE_ONLY":@(EXSecureStoreAccessibleWhenPasscodeSetThisDeviceOnly),
|
|
215
|
-
@"ALWAYS_THIS_DEVICE_ONLY":@(EXSecureStoreAccessibleAlwaysThisDeviceOnly),
|
|
216
|
-
@"WHEN_UNLOCKED":@(EXSecureStoreAccessibleWhenUnlocked),
|
|
217
|
-
@"WHEN_UNLOCKED_THIS_DEVICE_ONLY":@(EXSecureStoreAccessibleWhenUnlockedThisDeviceOnly)
|
|
218
|
-
};
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
EX_EXPORT_MODULE(ExpoSecureStore);
|
|
222
|
-
|
|
223
|
-
EX_EXPORT_METHOD_AS(setValueWithKeyAsync,
|
|
224
|
-
setValueWithKeyAsync:(NSString *)value
|
|
225
|
-
key:(NSString *)key
|
|
226
|
-
options:(NSDictionary *)options
|
|
227
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
228
|
-
rejecter:(EXPromiseRejectBlock)reject)
|
|
229
|
-
{
|
|
230
|
-
NSString *validatedKey = [self validatedKey:key];
|
|
231
|
-
if (!validatedKey) {
|
|
232
|
-
reject(@"E_SECURESTORE_SETVALUEFAIL", @"Invalid key", nil);
|
|
233
|
-
} else {
|
|
234
|
-
NSError *error;
|
|
235
|
-
BOOL setValue = [self _setValue:value
|
|
236
|
-
withKey:validatedKey
|
|
237
|
-
withOptions:options
|
|
238
|
-
error:&error];
|
|
239
|
-
if (setValue) {
|
|
240
|
-
resolve(nil);
|
|
241
|
-
} else {
|
|
242
|
-
reject(@"E_SECURESTORE_SETVALUEFAIL", [[self class] _messageForError:error], nil);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
EX_EXPORT_METHOD_AS(getValueWithKeyAsync,
|
|
248
|
-
getValueWithKeyAsync:(NSString *)key
|
|
249
|
-
options:(NSDictionary *)options
|
|
250
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
251
|
-
rejecter:(EXPromiseRejectBlock)reject)
|
|
252
|
-
{
|
|
253
|
-
NSString *validatedKey = [self validatedKey:key];
|
|
254
|
-
if (!validatedKey) {
|
|
255
|
-
reject(@"E_SECURESTORE_GETVALUEFAIL", @"Invalid key", nil);
|
|
256
|
-
} else {
|
|
257
|
-
NSError *error;
|
|
258
|
-
NSString *value = [self _getValueWithKey:validatedKey
|
|
259
|
-
withOptions:options
|
|
260
|
-
error:&error];
|
|
261
|
-
if (error) {
|
|
262
|
-
if (error.code == errSecItemNotFound) {
|
|
263
|
-
resolve([NSNull null]);
|
|
264
|
-
} else {
|
|
265
|
-
reject(@"E_SECURESTORE_GETVALUEFAIL", [[self class] _messageForError:error], nil);
|
|
266
|
-
}
|
|
267
|
-
} else {
|
|
268
|
-
resolve(value);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
EX_EXPORT_METHOD_AS(deleteValueWithKeyAsync,
|
|
274
|
-
deleteValueWithKeyAsync:(NSString *)key
|
|
275
|
-
options:(NSDictionary *)options
|
|
276
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
277
|
-
rejecter:(EXPromiseRejectBlock)reject)
|
|
278
|
-
{
|
|
279
|
-
NSString *validatedKey = [self validatedKey:key];
|
|
280
|
-
if (!validatedKey) {
|
|
281
|
-
reject(@"E_SECURESTORE_DELETEVALUEFAIL", @"Invalid key", nil);
|
|
282
|
-
} else {
|
|
283
|
-
[self _deleteValueWithKey:validatedKey
|
|
284
|
-
withOptions:options];
|
|
285
|
-
resolve(nil);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
- (NSString *)validatedKey:(NSString *)key
|
|
290
|
-
{
|
|
291
|
-
NSString *trimmedKey = [key stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
292
|
-
if (!key || trimmedKey.length == 0) {
|
|
293
|
-
return nil;
|
|
294
|
-
}
|
|
295
|
-
return key;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
@end
|
package/unimodule.json
DELETED