expo-secure-store 12.5.0 β 12.7.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 +21 -0
- package/android/build.gradle +48 -29
- package/android/src/main/java/expo/modules/securestore/SecureStoreModule.kt +20 -32
- package/android/src/main/java/expo/modules/securestore/encryptors/AESEncryptor.kt +6 -0
- package/android/src/main/java/expo/modules/securestore/encryptors/HybridAESEncryptor.kt +22 -89
- package/android/src/main/java/expo/modules/securestore/encryptors/KeyBasedEncryptor.kt +1 -0
- package/ios/ExpoSecureStore.podspec +2 -2
- package/ios/SecureStoreModule.swift +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,27 @@
|
|
|
10
10
|
|
|
11
11
|
### π‘ Others
|
|
12
12
|
|
|
13
|
+
## 12.7.0 β 2023-11-14
|
|
14
|
+
|
|
15
|
+
### π Breaking changes
|
|
16
|
+
|
|
17
|
+
- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
18
|
+
- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
|
|
19
|
+
|
|
20
|
+
### π‘ Others
|
|
21
|
+
|
|
22
|
+
- [Android] Enforce minimum authentication tag length for the `AESEncryptor` for improved security. ([#25294](https://github.com/expo/expo/pull/25294) by [@behenate](https://github.com/behenate))
|
|
23
|
+
|
|
24
|
+
## 12.6.0 β 2023-10-17
|
|
25
|
+
|
|
26
|
+
### π Breaking changes
|
|
27
|
+
|
|
28
|
+
- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate))
|
|
29
|
+
|
|
30
|
+
### π Bug fixes
|
|
31
|
+
|
|
32
|
+
- Fixed the 'WHEN_UNLOCKED_THIS_DEVICE_ONLY' constraint being incorrectly mapped to wrong secure store accessible ([#24831](https://github.com/expo/expo/pull/24831) by [@mmmguitar](https://github.com/mmmguitar))
|
|
33
|
+
|
|
13
34
|
## 12.5.0 β 2023-09-04
|
|
14
35
|
|
|
15
36
|
### π New features
|
package/android/build.gradle
CHANGED
|
@@ -3,15 +3,20 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '12.
|
|
6
|
+
version = '12.7.0'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
9
|
+
if (expoModulesCorePlugin.exists()) {
|
|
10
|
+
apply from: expoModulesCorePlugin
|
|
11
|
+
applyKotlinExpoModulesCorePlugin()
|
|
12
|
+
// Remove this check, but keep the contents after SDK49 support is dropped
|
|
13
|
+
if (safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
14
|
+
useExpoPublishing()
|
|
15
|
+
useCoreDependencies()
|
|
13
16
|
}
|
|
17
|
+
}
|
|
14
18
|
|
|
19
|
+
buildscript {
|
|
15
20
|
// Simple helper that allows the root project to override versions declared by this library.
|
|
16
21
|
ext.safeExtGet = { prop, fallback ->
|
|
17
22
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
@@ -35,23 +40,44 @@ buildscript {
|
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
44
|
+
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
45
|
+
afterEvaluate {
|
|
46
|
+
publishing {
|
|
47
|
+
publications {
|
|
48
|
+
release(MavenPublication) {
|
|
49
|
+
from components.release
|
|
50
|
+
}
|
|
43
51
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
repositories {
|
|
53
|
+
maven {
|
|
54
|
+
url = mavenLocal().url
|
|
55
|
+
}
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
android {
|
|
54
|
-
|
|
62
|
+
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
63
|
+
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
64
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
|
65
|
+
|
|
66
|
+
defaultConfig {
|
|
67
|
+
minSdkVersion safeExtGet("minSdkVersion", 23)
|
|
68
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
publishing {
|
|
72
|
+
singleVariant("release") {
|
|
73
|
+
withSourcesJar()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
lintOptions {
|
|
78
|
+
abortOnError false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
55
81
|
|
|
56
82
|
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
|
57
83
|
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
|
@@ -67,25 +93,18 @@ android {
|
|
|
67
93
|
|
|
68
94
|
namespace "expo.modules.securestore"
|
|
69
95
|
defaultConfig {
|
|
70
|
-
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
71
|
-
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
72
96
|
versionCode 17
|
|
73
|
-
versionName '12.
|
|
74
|
-
}
|
|
75
|
-
lintOptions {
|
|
76
|
-
abortOnError false
|
|
77
|
-
}
|
|
78
|
-
publishing {
|
|
79
|
-
singleVariant("release") {
|
|
80
|
-
withSourcesJar()
|
|
81
|
-
}
|
|
97
|
+
versionName '12.7.0'
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
dependencies {
|
|
86
|
-
|
|
102
|
+
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
103
|
+
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
104
|
+
implementation project(':expo-modules-core')
|
|
105
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
106
|
+
}
|
|
87
107
|
|
|
88
108
|
api "androidx.biometric:biometric:1.1.0"
|
|
89
109
|
|
|
90
|
-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
91
110
|
}
|
|
@@ -2,7 +2,6 @@ package expo.modules.securestore
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.SharedPreferences
|
|
5
|
-
import android.os.Build
|
|
6
5
|
import android.preference.PreferenceManager
|
|
7
6
|
import android.security.keystore.KeyPermanentlyInvalidatedException
|
|
8
7
|
import android.util.Log
|
|
@@ -105,7 +104,9 @@ open class SecureStoreModule : Module() {
|
|
|
105
104
|
"but it might be raised because the keychain used to decrypt this key has been invalidated and deleted." +
|
|
106
105
|
" If you are confident that the keychain you provided is correct and want to avoid this error in the " +
|
|
107
106
|
"future you should save a new value under this key or use `deleteItemImpl()` and remove the existing one."
|
|
108
|
-
} else {
|
|
107
|
+
} else {
|
|
108
|
+
""
|
|
109
|
+
}
|
|
109
110
|
|
|
110
111
|
encryptedItemString ?: return null
|
|
111
112
|
|
|
@@ -125,24 +126,23 @@ open class SecureStoreModule : Module() {
|
|
|
125
126
|
AESEncryptor.NAME -> {
|
|
126
127
|
val secretKeyEntry = getPreferredKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, requireAuthentication, usesKeystoreSuffix)
|
|
127
128
|
?: throw DecryptException("Could not find a keychain for key $key$legacyReadFailedWarning", key, options.keychainService)
|
|
128
|
-
return mAESEncryptor.decryptItem(encryptedItem, secretKeyEntry, options, authenticationHelper)
|
|
129
|
+
return mAESEncryptor.decryptItem(key, encryptedItem, secretKeyEntry, options, authenticationHelper)
|
|
129
130
|
}
|
|
130
131
|
HybridAESEncryptor.NAME -> {
|
|
131
132
|
val privateKeyEntry = getPreferredKeyEntry(PrivateKeyEntry::class.java, hybridAESEncryptor, options, requireAuthentication, usesKeystoreSuffix)
|
|
132
133
|
?: throw DecryptException("Could not find a keychain for key $key$legacyReadFailedWarning", key, options.keychainService)
|
|
133
|
-
return hybridAESEncryptor.decryptItem(encryptedItem, privateKeyEntry, options, authenticationHelper)
|
|
134
|
+
return hybridAESEncryptor.decryptItem(key, encryptedItem, privateKeyEntry, options, authenticationHelper)
|
|
134
135
|
}
|
|
135
136
|
else -> {
|
|
136
137
|
throw DecryptException("The item for key $key in SecureStore has an unknown encoding scheme $scheme)", key, options.keychainService)
|
|
137
138
|
}
|
|
138
139
|
}
|
|
140
|
+
} catch (e: KeyPermanentlyInvalidatedException) {
|
|
141
|
+
Log.w(TAG, "The requested key has been permanently invalidated. Returning null")
|
|
142
|
+
return null
|
|
143
|
+
} catch (e: BadPaddingException) {
|
|
144
|
+
throw (DecryptException("Could not decrypt the value with provided keychain $legacyReadFailedWarning", key, options.keychainService, e))
|
|
139
145
|
} catch (e: GeneralSecurityException) {
|
|
140
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && e is KeyPermanentlyInvalidatedException) {
|
|
141
|
-
Log.w(TAG, "The requested key has been permanently invalidated. Returning null")
|
|
142
|
-
return null
|
|
143
|
-
} else if (e is BadPaddingException) {
|
|
144
|
-
throw (DecryptException("Could not decrypt the value with provided keychain $legacyReadFailedWarning", key, options.keychainService, e))
|
|
145
|
-
}
|
|
146
146
|
throw (DecryptException(e.message, key, options.keychainService, e))
|
|
147
147
|
} catch (e: CodedException) {
|
|
148
148
|
throw e
|
|
@@ -176,35 +176,23 @@ open class SecureStoreModule : Module() {
|
|
|
176
176
|
use in the encrypted JSON item so that we know how to decode and decrypt it when reading
|
|
177
177
|
back a value.
|
|
178
178
|
*/
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
|
|
184
|
-
} else {
|
|
185
|
-
val privateKeyEntry: PrivateKeyEntry = getKeyEntry(PrivateKeyEntry::class.java, hybridAESEncryptor, options, options.requireAuthentication)
|
|
186
|
-
val encryptedItem = hybridAESEncryptor.createEncryptedItem(value, privateKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper)
|
|
187
|
-
encryptedItem.put(SCHEME_PROPERTY, HybridAESEncryptor.NAME)
|
|
188
|
-
saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
|
|
189
|
-
}
|
|
179
|
+
val secretKeyEntry: SecretKeyEntry = getKeyEntry(SecretKeyEntry::class.java, mAESEncryptor, options, options.requireAuthentication)
|
|
180
|
+
val encryptedItem = mAESEncryptor.createEncryptedItem(value, secretKeyEntry, options.requireAuthentication, options.authenticationPrompt, authenticationHelper)
|
|
181
|
+
encryptedItem.put(SCHEME_PROPERTY, AESEncryptor.NAME)
|
|
182
|
+
saveEncryptedItem(encryptedItem, prefs, keychainAwareKey, options.requireAuthentication, options.keychainService)
|
|
190
183
|
|
|
191
184
|
// If a legacy value exists under this key we remove it to avoid unexpected errors in the future
|
|
192
185
|
if (prefs.contains(key)) {
|
|
193
186
|
prefs.edit().remove(key).apply()
|
|
194
187
|
}
|
|
195
|
-
} catch (e:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (isInvalidationException && !keyIsInvalidated) {
|
|
199
|
-
// If the key has been invalidated by the OS we try to reinitialize it in the save retry.
|
|
188
|
+
} catch (e: KeyPermanentlyInvalidatedException) {
|
|
189
|
+
if (!keyIsInvalidated) {
|
|
200
190
|
Log.w(TAG, "Key has been invalidated, retrying with the key deleted")
|
|
201
|
-
setItemImpl(key, value, options, true)
|
|
202
|
-
} else if (isInvalidationException) {
|
|
203
|
-
// If reinitialization of the key fails, reject the promise
|
|
204
|
-
throw EncryptException("Encryption Failed. The key $key has been permanently invalidated and cannot be reinitialized", key, options.keychainService, e)
|
|
205
|
-
} else {
|
|
206
|
-
throw EncryptException(e.message, key, options.keychainService, e)
|
|
191
|
+
return setItemImpl(key, value, options, true)
|
|
207
192
|
}
|
|
193
|
+
throw EncryptException("Encryption Failed. The key $key has been permanently invalidated and cannot be reinitialized", key, options.keychainService, e)
|
|
194
|
+
} catch (e: GeneralSecurityException) {
|
|
195
|
+
throw EncryptException(e.message, key, options.keychainService, e)
|
|
208
196
|
} catch (e: CodedException) {
|
|
209
197
|
throw e
|
|
210
198
|
} catch (e: Exception) {
|
|
@@ -5,6 +5,7 @@ import android.security.keystore.KeyGenParameterSpec
|
|
|
5
5
|
import android.security.keystore.KeyProperties
|
|
6
6
|
import android.util.Base64
|
|
7
7
|
import expo.modules.securestore.AuthenticationHelper
|
|
8
|
+
import expo.modules.securestore.DecryptException
|
|
8
9
|
import expo.modules.securestore.SecureStoreModule
|
|
9
10
|
import expo.modules.securestore.SecureStoreOptions
|
|
10
11
|
import org.json.JSONException
|
|
@@ -108,6 +109,7 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
|
|
|
108
109
|
|
|
109
110
|
@Throws(GeneralSecurityException::class, JSONException::class)
|
|
110
111
|
override suspend fun decryptItem(
|
|
112
|
+
key: String,
|
|
111
113
|
encryptedItem: JSONObject,
|
|
112
114
|
keyStoreEntry: KeyStore.SecretKeyEntry,
|
|
113
115
|
options: SecureStoreOptions,
|
|
@@ -122,6 +124,9 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
|
|
|
122
124
|
val cipher = Cipher.getInstance(AES_CIPHER)
|
|
123
125
|
val requiresAuthentication = encryptedItem.optBoolean(AuthenticationHelper.REQUIRE_AUTHENTICATION_PROPERTY)
|
|
124
126
|
|
|
127
|
+
if (authenticationTagLength < MIN_GCM_AUTHENTICATION_TAG_LENGTH) {
|
|
128
|
+
throw DecryptException("Authentication tag length must be at least $MIN_GCM_AUTHENTICATION_TAG_LENGTH bits long", key, options.keychainService)
|
|
129
|
+
}
|
|
125
130
|
cipher.init(Cipher.DECRYPT_MODE, keyStoreEntry.secretKey, gcmSpec)
|
|
126
131
|
val unlockedCipher = authenticationHelper.authenticateCipher(cipher, requiresAuthentication, options.authenticationPrompt)
|
|
127
132
|
return String(unlockedCipher.doFinal(ciphertextBytes), StandardCharsets.UTF_8)
|
|
@@ -134,5 +139,6 @@ class AESEncryptor : KeyBasedEncryptor<KeyStore.SecretKeyEntry> {
|
|
|
134
139
|
private const val CIPHERTEXT_PROPERTY = "ct"
|
|
135
140
|
const val IV_PROPERTY = "iv"
|
|
136
141
|
private const val GCM_AUTHENTICATION_TAG_LENGTH_PROPERTY = "tlen"
|
|
142
|
+
private const val MIN_GCM_AUTHENTICATION_TAG_LENGTH = 96
|
|
137
143
|
}
|
|
138
144
|
}
|
|
@@ -2,35 +2,25 @@ package expo.modules.securestore.encryptors
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import android.os.Build
|
|
6
|
-
import android.security.KeyPairGeneratorSpec
|
|
7
5
|
import android.security.keystore.KeyProperties
|
|
8
6
|
import android.util.Base64
|
|
9
|
-
import android.util.Log
|
|
10
7
|
import expo.modules.securestore.AuthenticationHelper
|
|
8
|
+
import expo.modules.securestore.EncryptException
|
|
9
|
+
import expo.modules.securestore.KeyStoreException
|
|
11
10
|
import expo.modules.securestore.SecureStoreModule
|
|
12
11
|
import expo.modules.securestore.SecureStoreOptions
|
|
13
12
|
import org.json.JSONException
|
|
14
13
|
import org.json.JSONObject
|
|
15
|
-
import java.math.BigInteger
|
|
16
14
|
import java.security.GeneralSecurityException
|
|
17
|
-
import java.security.InvalidAlgorithmParameterException
|
|
18
|
-
import java.security.KeyPairGenerator
|
|
19
15
|
import java.security.KeyStore
|
|
20
16
|
import java.security.NoSuchAlgorithmException
|
|
21
17
|
import java.security.NoSuchProviderException
|
|
22
18
|
import java.security.SecureRandom
|
|
23
|
-
import java.security.UnrecoverableEntryException
|
|
24
|
-
import java.security.spec.AlgorithmParameterSpec
|
|
25
|
-
import java.security.spec.InvalidParameterSpecException
|
|
26
19
|
import java.util.*
|
|
27
20
|
import javax.crypto.Cipher
|
|
28
|
-
import javax.crypto.KeyGenerator
|
|
29
21
|
import javax.crypto.NoSuchPaddingException
|
|
30
22
|
import javax.crypto.SecretKey
|
|
31
|
-
import javax.crypto.spec.GCMParameterSpec
|
|
32
23
|
import javax.crypto.spec.SecretKeySpec
|
|
33
|
-
import javax.security.auth.x500.X500Principal
|
|
34
24
|
|
|
35
25
|
/**
|
|
36
26
|
* An AES encryptor that works with Android L (API 22) and below, which cannot store symmetric
|
|
@@ -66,26 +56,11 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
|
|
|
66
56
|
|
|
67
57
|
@Throws(GeneralSecurityException::class)
|
|
68
58
|
override fun initializeKeyStoreEntry(keyStore: KeyStore, options: SecureStoreOptions): KeyStore.PrivateKeyEntry {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
val algorithmSpec: AlgorithmParameterSpec = KeyPairGeneratorSpec.Builder(mContext)
|
|
75
|
-
.setAlias(keystoreAlias)
|
|
76
|
-
.setSubject(X500Principal("CN=$escapedCommonName, OU=SecureStore"))
|
|
77
|
-
.setSerialNumber(BigInteger(X509_SERIAL_NUMBER_LENGTH_BITS, mSecureRandom))
|
|
78
|
-
.setStartDate(Date(0))
|
|
79
|
-
.setEndDate(Date(Long.MAX_VALUE))
|
|
80
|
-
.build()
|
|
81
|
-
|
|
82
|
-
@SuppressLint("InlinedApi") // constant value will be copied
|
|
83
|
-
val keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, keyStore.provider)
|
|
84
|
-
|
|
85
|
-
keyPairGenerator.initialize(algorithmSpec)
|
|
86
|
-
keyPairGenerator.generateKeyPair()
|
|
87
|
-
return keyStore.getEntry(keystoreAlias, null) as? KeyStore.PrivateKeyEntry
|
|
88
|
-
?: throw UnrecoverableEntryException("Could not retrieve the newly generated private key entry")
|
|
59
|
+
// This should never be called after we dropped Android SDK 22 support.
|
|
60
|
+
throw KeyStoreException(
|
|
61
|
+
"Tried to initialize HybridAESEncryptor key store entry on Android SDK >= 23. This shouldn't happen. " +
|
|
62
|
+
"If you see this message report an issue at https://github.com/expo/expo."
|
|
63
|
+
)
|
|
89
64
|
}
|
|
90
65
|
|
|
91
66
|
@Throws(GeneralSecurityException::class, JSONException::class)
|
|
@@ -96,60 +71,22 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
|
|
|
96
71
|
authenticationPrompt: String,
|
|
97
72
|
authenticationHelper: AuthenticationHelper
|
|
98
73
|
): JSONObject {
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
keyGenerator.init(AESEncryptor.AES_KEY_SIZE_BITS)
|
|
106
|
-
val secretKey = keyGenerator.generateKey()
|
|
107
|
-
|
|
108
|
-
// Encrypt the value with the symmetric key. We need to specify the GCM parameters since the
|
|
109
|
-
// our secret key isn't tied to the keystore and the cipher can't use the secret key to
|
|
110
|
-
// generate the parameters.
|
|
111
|
-
val gcmSpec = GCMParameterSpec(GCM_AUTHENTICATION_TAG_LENGTH_BITS, ivBytes)
|
|
112
|
-
val aesCipher = Cipher.getInstance(AESEncryptor.AES_CIPHER)
|
|
113
|
-
|
|
114
|
-
aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec)
|
|
115
|
-
val chosenSpec = try {
|
|
116
|
-
aesCipher.parameters.getParameterSpec(GCMParameterSpec::class.java)
|
|
117
|
-
} catch (e: InvalidParameterSpecException) {
|
|
118
|
-
// BouncyCastle tried to instantiate GCMParameterSpec using invalid constructor
|
|
119
|
-
// https://github.com/bcgit/bc-java/commit/507c3917c0c469d10b9f033ad641c1da195e2039#diff-c90a59e823805b6c0dcfeaf7bae65f53
|
|
120
|
-
|
|
121
|
-
// Let's do some sanity checks and use the spec we've initialized the cipher with.
|
|
122
|
-
if ("GCM" != aesCipher.parameters.algorithm) {
|
|
123
|
-
throw InvalidAlgorithmParameterException("Algorithm chosen by the cipher (" + aesCipher.parameters.algorithm + ") doesn't match requested (GCM).")
|
|
124
|
-
}
|
|
125
|
-
gcmSpec
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
val authenticatedCipher = authenticationHelper.authenticateCipher(aesCipher, requireAuthentication, authenticationPrompt)
|
|
129
|
-
|
|
130
|
-
val aesResult = mAESEncryptor.createEncryptedItemWithCipher(plaintextValue, authenticatedCipher, chosenSpec)
|
|
131
|
-
|
|
132
|
-
// Ensure the IV in the encrypted item matches our generated IV
|
|
133
|
-
val ivString = aesResult.getString(AESEncryptor.IV_PROPERTY)
|
|
134
|
-
val expectedIVString = Base64.encodeToString(ivBytes, Base64.NO_WRAP)
|
|
135
|
-
if (ivString != expectedIVString) {
|
|
136
|
-
Log.e(SecureStoreModule.TAG, String.format("HybridAESEncrypter generated two different IVs: %s and %s", expectedIVString, ivString))
|
|
137
|
-
throw IllegalStateException("HybridAESEncryptor must store the same IV as the one used to parameterize the secret key")
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Encrypt the symmetric key with the asymmetric public key
|
|
141
|
-
val secretKeyBytes = secretKey.encoded
|
|
142
|
-
val cipher: Cipher = rSACipher
|
|
143
|
-
cipher.init(Cipher.ENCRYPT_MODE, keyStoreEntry.certificate)
|
|
144
|
-
val encryptedSecretKeyBytes = cipher.doFinal(secretKeyBytes)
|
|
145
|
-
val encryptedSecretKeyString = Base64.encodeToString(encryptedSecretKeyBytes, Base64.NO_WRAP)
|
|
146
|
-
aesResult.put(ENCRYPTED_SECRET_KEY_PROPERTY, encryptedSecretKeyString)
|
|
147
|
-
|
|
148
|
-
return aesResult
|
|
74
|
+
// This should never be called after we dropped Android SDK 22 support.
|
|
75
|
+
throw EncryptException(
|
|
76
|
+
"HybridAESEncryption should not be used on Android SDK >= 23. This shouldn't happen. " +
|
|
77
|
+
"If you see this message report an issue at https://github.com/expo/expo.",
|
|
78
|
+
"unknown", "unknown"
|
|
79
|
+
)
|
|
149
80
|
}
|
|
150
81
|
|
|
151
82
|
@Throws(GeneralSecurityException::class, JSONException::class)
|
|
152
|
-
override suspend fun decryptItem(
|
|
83
|
+
override suspend fun decryptItem(
|
|
84
|
+
key: String,
|
|
85
|
+
encryptedItem: JSONObject,
|
|
86
|
+
keyStoreEntry: KeyStore.PrivateKeyEntry,
|
|
87
|
+
options: SecureStoreOptions,
|
|
88
|
+
authenticationHelper: AuthenticationHelper
|
|
89
|
+
): String {
|
|
153
90
|
// Decrypt the encrypted symmetric key
|
|
154
91
|
val encryptedSecretKeyString = encryptedItem.getString(ENCRYPTED_SECRET_KEY_PROPERTY)
|
|
155
92
|
val encryptedSecretKeyBytes = Base64.decode(encryptedSecretKeyString, Base64.DEFAULT)
|
|
@@ -161,20 +98,16 @@ class HybridAESEncryptor(private var mContext: Context, private val mAESEncrypto
|
|
|
161
98
|
|
|
162
99
|
// Decrypt the value with the symmetric key
|
|
163
100
|
val secretKeyEntry = KeyStore.SecretKeyEntry(secretKey)
|
|
164
|
-
return mAESEncryptor.decryptItem(encryptedItem, secretKeyEntry, options, authenticationHelper)
|
|
101
|
+
return mAESEncryptor.decryptItem(key, encryptedItem, secretKeyEntry, options, authenticationHelper)
|
|
165
102
|
}
|
|
166
103
|
|
|
167
104
|
@get:Throws(NoSuchAlgorithmException::class, NoSuchProviderException::class, NoSuchPaddingException::class)
|
|
168
105
|
private val rSACipher: Cipher
|
|
169
|
-
get() =
|
|
106
|
+
get() = Cipher.getInstance(RSA_CIPHER)
|
|
170
107
|
|
|
171
108
|
companion object {
|
|
172
109
|
const val NAME = "hybrid"
|
|
173
110
|
private const val RSA_CIPHER = "RSA/None/PKCS1Padding"
|
|
174
|
-
private const val RSA_CIPHER_LEGACY_PROVIDER = "AndroidOpenSSL"
|
|
175
|
-
private const val X509_SERIAL_NUMBER_LENGTH_BITS = 20 * 8
|
|
176
|
-
private const val GCM_IV_LENGTH_BYTES = 12
|
|
177
|
-
private const val GCM_AUTHENTICATION_TAG_LENGTH_BITS = 128
|
|
178
111
|
private const val ENCRYPTED_SECRET_KEY_PROPERTY = "esk"
|
|
179
112
|
}
|
|
180
113
|
}
|
|
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package['license']
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.homepage = package['homepage']
|
|
13
|
-
s.platform = :ios, '13.
|
|
13
|
+
s.platform = :ios, '13.4'
|
|
14
14
|
s.swift_version = '5.4'
|
|
15
15
|
s.source = { git: 'https://github.com/expo/expo.git' }
|
|
16
16
|
s.static_framework = true
|
|
@@ -22,7 +22,7 @@ Pod::Spec.new do |s|
|
|
|
22
22
|
'DEFINES_MODULE' => 'YES',
|
|
23
23
|
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
|
|
26
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')
|
|
27
27
|
s.source_files = "**/*.h"
|
|
28
28
|
s.vendored_frameworks = "#{s.name}.xcframework"
|
|
@@ -13,7 +13,7 @@ public final class SecureStoreModule: Module {
|
|
|
13
13
|
"WHEN_PASSCODE_SET_THIS_DEVICE_ONLY": SecureStoreAccessible.whenPasscodeSetThisDeviceOnly.rawValue,
|
|
14
14
|
"ALWAYS_THIS_DEVICE_ONLY": SecureStoreAccessible.alwaysThisDeviceOnly.rawValue,
|
|
15
15
|
"WHEN_UNLOCKED": SecureStoreAccessible.whenUnlocked.rawValue,
|
|
16
|
-
"WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.
|
|
16
|
+
"WHEN_UNLOCKED_THIS_DEVICE_ONLY": SecureStoreAccessible.whenUnlockedThisDeviceOnly.rawValue
|
|
17
17
|
])
|
|
18
18
|
|
|
19
19
|
AsyncFunction("getValueWithKeyAsync") { (key: String, options: SecureStoreOptions) -> String? in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-secure-store",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.7.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": "3142a086578deffd8704a8f1b6f0f661527d836c"
|
|
46
46
|
}
|