expo-device-crypto 1.0.1 → 1.2.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/README.md +83 -42
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/devicecrypto/{DeviceCryptoModule.kt → DeviceCrypto.kt} +78 -41
- package/android/src/main/java/expo/modules/devicecrypto/DeviceCryptoUtils.kt +40 -0
- package/build/DeviceCrypto.types.d.ts +12 -3
- package/build/DeviceCrypto.types.d.ts.map +1 -1
- package/build/DeviceCrypto.types.js +4 -0
- package/build/DeviceCrypto.types.js.map +1 -1
- package/build/DeviceCryptoModule.d.ts.map +1 -1
- package/build/DeviceCryptoModule.js +1 -0
- package/build/DeviceCryptoModule.js.map +1 -1
- package/ios/DeviceCrypto.podspec +1 -1
- package/ios/DeviceCrypto.swift +46 -92
- package/ios/DeviceCryptoUtils.swift +84 -0
- package/package.json +6 -1
- package/src/DeviceCrypto.types.ts +18 -4
- package/src/DeviceCryptoModule.ts +1 -0
package/README.md
CHANGED
|
@@ -24,44 +24,83 @@ If you want to allow Face ID on iOS, add this to your app config:
|
|
|
24
24
|
|
|
25
25
|
## Supported Algorithms
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
27
|
+
<table>
|
|
28
|
+
<thead>
|
|
29
|
+
<tr>
|
|
30
|
+
<th width="50%">✍️ Signature Algorithms</th>
|
|
31
|
+
<th width="50%">🔐 Encryption Algorithms</th>
|
|
32
|
+
</tr>
|
|
33
|
+
</thead>
|
|
34
|
+
<tbody>
|
|
35
|
+
<tr>
|
|
36
|
+
<td>
|
|
37
|
+
<code>ECDSA_SECP256R1_SHA256</code><br>
|
|
38
|
+
<strong>Curve:</strong> P-256 / secp256r1<br>
|
|
39
|
+
<strong>Hash:</strong> SHA-256
|
|
40
|
+
</td>
|
|
41
|
+
<td>
|
|
42
|
+
<code>ECIES_P256_AES256_GCM</code><br>
|
|
43
|
+
<strong>Curve:</strong> P-256 (secp256r1)<br>
|
|
44
|
+
<strong>Symmetric cipher:</strong> AES-256-GCM<br>
|
|
45
|
+
<strong>Key derivation:</strong> HKDF-SHA256 (32-byte key from ECDH shared secret)
|
|
46
|
+
<a href="https://github.com/piotr-pietras/expo-device-crypto/blob/master/examples/ecies.ts">see simple usage</a>
|
|
47
|
+
</td>
|
|
48
|
+
</tr>
|
|
49
|
+
<tr>
|
|
50
|
+
<td>
|
|
51
|
+
<code>RSA_SHA256</code><br>
|
|
52
|
+
<strong>Key size:</strong> 2048 bits<br>
|
|
53
|
+
<strong>Padding:</strong> PKCS#1 v1.5<br>
|
|
54
|
+
<strong>Hash:</strong> SHA-256
|
|
55
|
+
</td>
|
|
56
|
+
<td>
|
|
57
|
+
<code>RSA_2048_OAEP_SHA1</code><br>
|
|
58
|
+
<strong>Key size:</strong> 2048 bits<br>
|
|
59
|
+
<strong>Padding:</strong> OAEP with SHA-1 and MGF1
|
|
60
|
+
</td>
|
|
61
|
+
</tr>
|
|
62
|
+
<tr>
|
|
63
|
+
<td>
|
|
64
|
+
<code>RSA_SHA256_PSS</code><br>
|
|
65
|
+
<strong>Key size:</strong> 2048 bits<br>
|
|
66
|
+
<strong>Padding:</strong> RSA-PSS (MGF1)<br>
|
|
67
|
+
<strong>Hash:</strong> SHA-256
|
|
68
|
+
</td>
|
|
69
|
+
<td>
|
|
70
|
+
<code>RSA_2048_PKCS1</code><br>
|
|
71
|
+
<strong>Key size:</strong> 2048 bits<br>
|
|
72
|
+
<strong>Padding:</strong> PKCS#1 v1.5
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<tr>
|
|
76
|
+
<td>
|
|
77
|
+
<code>RSA_4096_SHA256</code><br>
|
|
78
|
+
<strong>Key size:</strong> 4096 bits<br>
|
|
79
|
+
<strong>Padding:</strong> PKCS#1 v1.5<br>
|
|
80
|
+
<strong>Hash:</strong> SHA-256
|
|
81
|
+
</td>
|
|
82
|
+
<td>
|
|
83
|
+
<code>RSA_4096_OAEP_SHA1</code><br>
|
|
84
|
+
<strong>Key size:</strong> 4096 bits<br>
|
|
85
|
+
<strong>Padding:</strong> OAEP with SHA-1 and MGF1
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
<tr>
|
|
89
|
+
<td>
|
|
90
|
+
<code>RSA_4096_SHA256_PSS</code><br>
|
|
91
|
+
<strong>Key size:</strong> 4096 bits<br>
|
|
92
|
+
<strong>Padding:</strong> RSA-PSS (MGF1)<br>
|
|
93
|
+
<strong>Hash:</strong> SHA-256
|
|
94
|
+
</td>
|
|
95
|
+
<td>
|
|
96
|
+
<code>RSA_4096_PKCS1</code><br>
|
|
97
|
+
<strong>Key size:</strong> 4096 bits<br>
|
|
98
|
+
<strong>Padding:</strong> PKCS#1 v1.5
|
|
99
|
+
</td>
|
|
100
|
+
</tr>
|
|
101
|
+
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
65
104
|
|
|
66
105
|
## Example
|
|
67
106
|
|
|
@@ -109,11 +148,13 @@ const decrypted = await DeviceCrypto.decrypt(alias, encrypted, {
|
|
|
109
148
|
});
|
|
110
149
|
```
|
|
111
150
|
|
|
112
|
-
>
|
|
113
|
-
|
|
114
|
-
### 📚 More examples
|
|
151
|
+
> [!WARNING]
|
|
152
|
+
> Because iOS Keychain/Secure Enclave binds authentication policy to key creation, `authMethod` must be set in `generateKeyPair`. On Android Keystore, authentication is applied at key usage time, so `authMethod` is provided in operations like `sign` and `decrypt`.
|
|
115
153
|
|
|
116
|
-
|
|
154
|
+
> [!TIP]
|
|
155
|
+
> ### 📚💡📦 More examples
|
|
156
|
+
>
|
|
157
|
+
> Looking for more examples? Browse the **[`examples` directory](https://github.com/piotr-pietras/expo-device-crypto/tree/master/examples)** for additional usage patterns (ECIES, signing, encryption, and more).
|
|
117
158
|
|
|
118
159
|
## Methods
|
|
119
160
|
|
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'expo.modules.devicecrypto'
|
|
7
|
-
version = '1.0
|
|
7
|
+
version = '1.2.0'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.devicecrypto"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 1
|
|
13
|
-
versionName "1.0
|
|
13
|
+
versionName "1.2.0"
|
|
14
14
|
}
|
|
15
15
|
lintOptions {
|
|
16
16
|
abortOnError false
|
package/android/src/main/java/expo/modules/devicecrypto/{DeviceCryptoModule.kt → DeviceCrypto.kt}
RENAMED
|
@@ -67,9 +67,13 @@ enum class AlgorithmType {
|
|
|
67
67
|
ECDSA_SECP256R1_SHA256,
|
|
68
68
|
RSA_2048_PKCS1,
|
|
69
69
|
RSA_2048_OAEP_SHA1,
|
|
70
|
+
RSA_4096_PKCS1,
|
|
71
|
+
RSA_4096_OAEP_SHA1,
|
|
70
72
|
ECIES_P256_AES256_GCM,
|
|
71
73
|
RSA_SHA256,
|
|
72
|
-
RSA_SHA256_PSS
|
|
74
|
+
RSA_SHA256_PSS,
|
|
75
|
+
RSA_4096_SHA256,
|
|
76
|
+
RSA_4096_SHA256_PSS
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
enum class Purpose {
|
|
@@ -82,52 +86,22 @@ class DeviceCryptoModule : Module() {
|
|
|
82
86
|
return when (algorithm) {
|
|
83
87
|
AlgorithmType.ECDSA_SECP256R1_SHA256 -> "SHA256withECDSA"
|
|
84
88
|
AlgorithmType.RSA_2048_PKCS1 -> "RSA/ECB/PKCS1Padding"
|
|
89
|
+
AlgorithmType.RSA_4096_PKCS1 -> "RSA/ECB/PKCS1Padding"
|
|
85
90
|
AlgorithmType.RSA_2048_OAEP_SHA1 -> "RSA/ECB/OAEPwithSHA-1AndMGF1Padding"
|
|
91
|
+
AlgorithmType.RSA_4096_OAEP_SHA1 -> "RSA/ECB/OAEPwithSHA-1AndMGF1Padding"
|
|
86
92
|
AlgorithmType.ECIES_P256_AES256_GCM -> "AES/GCM/NoPadding"
|
|
87
93
|
AlgorithmType.RSA_SHA256 -> "SHA256withRSA"
|
|
94
|
+
AlgorithmType.RSA_4096_SHA256 -> "SHA256withRSA"
|
|
88
95
|
AlgorithmType.RSA_SHA256_PSS -> "SHA256withRSA/PSS"
|
|
96
|
+
AlgorithmType.RSA_4096_SHA256_PSS -> "SHA256withRSA/PSS"
|
|
89
97
|
else -> throw Exception("INVALID_ALGORITHM_TYPE")
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
|
|
93
|
-
private fun hkdfSha256(
|
|
94
|
-
ikm: ByteArray,
|
|
95
|
-
length: Int,
|
|
96
|
-
salt: ByteArray? = null,
|
|
97
|
-
info: ByteArray? = null): ByteArray {
|
|
98
|
-
val mac = Mac.getInstance("HmacSHA256")
|
|
99
|
-
val actualSalt = salt ?: ByteArray(32)
|
|
100
|
-
mac.init(SecretKeySpec(actualSalt, "HmacSHA256"))
|
|
101
|
-
val prk = mac.doFinal(ikm)
|
|
102
|
-
|
|
103
|
-
val result = ByteArray(length)
|
|
104
|
-
var t = ByteArray(0)
|
|
105
|
-
var offset = 0
|
|
106
|
-
var counter = 1
|
|
107
|
-
|
|
108
|
-
while (offset < length) {
|
|
109
|
-
val macExpand = Mac.getInstance("HmacSHA256")
|
|
110
|
-
macExpand.init(SecretKeySpec(prk, "HmacSHA256"))
|
|
111
|
-
|
|
112
|
-
macExpand.update(t)
|
|
113
|
-
if (info != null) macExpand.update(info)
|
|
114
|
-
macExpand.update(counter.toByte())
|
|
115
|
-
|
|
116
|
-
t = macExpand.doFinal()
|
|
117
|
-
|
|
118
|
-
val toCopy = minOf(t.size, length - offset)
|
|
119
|
-
System.arraycopy(t, 0, result, offset, toCopy)
|
|
120
|
-
|
|
121
|
-
offset += toCopy
|
|
122
|
-
counter++
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return result
|
|
126
|
-
}
|
|
127
|
-
|
|
128
101
|
private fun getOaepSpec(algoType: AlgorithmType): AlgorithmParameterSpec? {
|
|
129
102
|
return when (algoType) {
|
|
130
|
-
AlgorithmType.RSA_2048_OAEP_SHA1
|
|
103
|
+
AlgorithmType.RSA_2048_OAEP_SHA1,
|
|
104
|
+
AlgorithmType.RSA_4096_OAEP_SHA1 -> OAEPParameterSpec(
|
|
131
105
|
"SHA-1",
|
|
132
106
|
"MGF1",
|
|
133
107
|
MGF1ParameterSpec.SHA1,
|
|
@@ -165,6 +139,7 @@ class DeviceCryptoModule : Module() {
|
|
|
165
139
|
alias: String,
|
|
166
140
|
digest: String?,
|
|
167
141
|
reqAuth: Boolean,
|
|
142
|
+
reqUnlocked: Boolean,
|
|
168
143
|
strongBox: Boolean): KeyPairGenerator {
|
|
169
144
|
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
|
|
170
145
|
KEY_ALGORITHM_EC,
|
|
@@ -180,6 +155,7 @@ class DeviceCryptoModule : Module() {
|
|
|
180
155
|
}
|
|
181
156
|
setIsStrongBoxBacked(strongBox)
|
|
182
157
|
setUserAuthenticationRequired(reqAuth)
|
|
158
|
+
setUnlockedDeviceRequired(reqUnlocked)
|
|
183
159
|
if (reqAuth) {
|
|
184
160
|
setUserAuthenticationParameters(
|
|
185
161
|
30, // 30 seconds
|
|
@@ -197,7 +173,9 @@ class DeviceCryptoModule : Module() {
|
|
|
197
173
|
digest: String?,
|
|
198
174
|
purpose: Purpose,
|
|
199
175
|
padding: String?,
|
|
176
|
+
keySize: Int,
|
|
200
177
|
reqAuth: Boolean,
|
|
178
|
+
reqUnlocked: Boolean,
|
|
201
179
|
strongBox: Boolean): KeyPairGenerator {
|
|
202
180
|
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
|
|
203
181
|
KEY_ALGORITHM_RSA,
|
|
@@ -221,7 +199,9 @@ class DeviceCryptoModule : Module() {
|
|
|
221
199
|
setSignaturePaddings(padding)
|
|
222
200
|
}
|
|
223
201
|
setIsStrongBoxBacked(strongBox)
|
|
202
|
+
setKeySize(keySize)
|
|
224
203
|
setUserAuthenticationRequired(reqAuth)
|
|
204
|
+
setUnlockedDeviceRequired(reqUnlocked)
|
|
225
205
|
if (reqAuth) {
|
|
226
206
|
setUserAuthenticationParameters(
|
|
227
207
|
30, // 30 seconds
|
|
@@ -238,6 +218,7 @@ class DeviceCryptoModule : Module() {
|
|
|
238
218
|
alias: String,
|
|
239
219
|
digest: String?,
|
|
240
220
|
reqAuth: Boolean,
|
|
221
|
+
reqUnlocked: Boolean,
|
|
241
222
|
strongBox: Boolean): KeyPairGenerator {
|
|
242
223
|
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
|
|
243
224
|
KEY_ALGORITHM_EC,
|
|
@@ -253,6 +234,7 @@ class DeviceCryptoModule : Module() {
|
|
|
253
234
|
}
|
|
254
235
|
setIsStrongBoxBacked(strongBox)
|
|
255
236
|
setUserAuthenticationRequired(reqAuth)
|
|
237
|
+
setUnlockedDeviceRequired(reqUnlocked)
|
|
256
238
|
if (reqAuth) {
|
|
257
239
|
setUserAuthenticationParameters(
|
|
258
240
|
30, // 30 seconds
|
|
@@ -329,6 +311,7 @@ class DeviceCryptoModule : Module() {
|
|
|
329
311
|
|
|
330
312
|
private fun generateKeyPair(alias: String, o: Map<String, Any?>): GenerateKeyPairResult {
|
|
331
313
|
val reqAuth = o["reqAuth"] as Boolean
|
|
314
|
+
val reqUnlocked = o["reqUnlocked"] as Boolean
|
|
332
315
|
if (reqAuth && isAuthCheckAvailable() != AuthCheckResult.AVAILABLE) {
|
|
333
316
|
throw Exception("NO_AUTH_AVAILABLE")
|
|
334
317
|
}
|
|
@@ -348,6 +331,7 @@ class DeviceCryptoModule : Module() {
|
|
|
348
331
|
alias = alias,
|
|
349
332
|
digest = DIGEST_SHA256,
|
|
350
333
|
reqAuth = reqAuth,
|
|
334
|
+
reqUnlocked = reqUnlocked,
|
|
351
335
|
strongBox = strongBox
|
|
352
336
|
)
|
|
353
337
|
AlgorithmType.RSA_2048_PKCS1 -> buildRSA(
|
|
@@ -355,7 +339,9 @@ class DeviceCryptoModule : Module() {
|
|
|
355
339
|
digest = null,
|
|
356
340
|
purpose = Purpose.ENCRYPT,
|
|
357
341
|
padding = ENCRYPTION_PADDING_RSA_PKCS1,
|
|
342
|
+
keySize = 2048,
|
|
358
343
|
reqAuth = reqAuth,
|
|
344
|
+
reqUnlocked = reqUnlocked,
|
|
359
345
|
strongBox = strongBox
|
|
360
346
|
)
|
|
361
347
|
AlgorithmType.RSA_2048_OAEP_SHA1 -> buildRSA(
|
|
@@ -363,13 +349,36 @@ class DeviceCryptoModule : Module() {
|
|
|
363
349
|
digest = DIGEST_SHA1,
|
|
364
350
|
purpose = Purpose.ENCRYPT,
|
|
365
351
|
padding = ENCRYPTION_PADDING_RSA_OAEP,
|
|
352
|
+
keySize = 2048,
|
|
353
|
+
reqAuth = reqAuth,
|
|
354
|
+
reqUnlocked = reqUnlocked,
|
|
355
|
+
strongBox = strongBox
|
|
356
|
+
)
|
|
357
|
+
AlgorithmType.RSA_4096_PKCS1 -> buildRSA(
|
|
358
|
+
alias = alias,
|
|
359
|
+
digest = null,
|
|
360
|
+
purpose = Purpose.ENCRYPT,
|
|
361
|
+
padding = ENCRYPTION_PADDING_RSA_PKCS1,
|
|
362
|
+
keySize = 4096,
|
|
363
|
+
reqAuth = reqAuth,
|
|
364
|
+
reqUnlocked = reqUnlocked,
|
|
365
|
+
strongBox = strongBox
|
|
366
|
+
)
|
|
367
|
+
AlgorithmType.RSA_4096_OAEP_SHA1 -> buildRSA(
|
|
368
|
+
alias = alias,
|
|
369
|
+
digest = DIGEST_SHA1,
|
|
370
|
+
purpose = Purpose.ENCRYPT,
|
|
371
|
+
padding = ENCRYPTION_PADDING_RSA_OAEP,
|
|
372
|
+
keySize = 4096,
|
|
366
373
|
reqAuth = reqAuth,
|
|
374
|
+
reqUnlocked = reqUnlocked,
|
|
367
375
|
strongBox = strongBox
|
|
368
376
|
)
|
|
369
377
|
AlgorithmType.ECIES_P256_AES256_GCM -> buildECIES(
|
|
370
378
|
alias = alias,
|
|
371
379
|
digest = null,
|
|
372
380
|
reqAuth = reqAuth,
|
|
381
|
+
reqUnlocked = reqUnlocked,
|
|
373
382
|
strongBox = strongBox
|
|
374
383
|
)
|
|
375
384
|
AlgorithmType.RSA_SHA256 -> buildRSA(
|
|
@@ -377,7 +386,9 @@ class DeviceCryptoModule : Module() {
|
|
|
377
386
|
digest = DIGEST_SHA256,
|
|
378
387
|
purpose = Purpose.SIGN,
|
|
379
388
|
padding = SIGNATURE_PADDING_RSA_PKCS1,
|
|
389
|
+
keySize = 2048,
|
|
380
390
|
reqAuth = reqAuth,
|
|
391
|
+
reqUnlocked = reqUnlocked,
|
|
381
392
|
strongBox = strongBox
|
|
382
393
|
)
|
|
383
394
|
AlgorithmType.RSA_SHA256_PSS -> buildRSA(
|
|
@@ -385,7 +396,29 @@ class DeviceCryptoModule : Module() {
|
|
|
385
396
|
digest = DIGEST_SHA256,
|
|
386
397
|
purpose = Purpose.SIGN,
|
|
387
398
|
padding = SIGNATURE_PADDING_RSA_PSS,
|
|
399
|
+
keySize = 2048,
|
|
400
|
+
reqAuth = reqAuth,
|
|
401
|
+
reqUnlocked = reqUnlocked,
|
|
402
|
+
strongBox = strongBox
|
|
403
|
+
)
|
|
404
|
+
AlgorithmType.RSA_4096_SHA256 -> buildRSA(
|
|
405
|
+
alias = alias,
|
|
406
|
+
digest = DIGEST_SHA256,
|
|
407
|
+
purpose = Purpose.SIGN,
|
|
408
|
+
padding = SIGNATURE_PADDING_RSA_PKCS1,
|
|
409
|
+
keySize = 4096,
|
|
410
|
+
reqAuth = reqAuth,
|
|
411
|
+
reqUnlocked = reqUnlocked,
|
|
412
|
+
strongBox = strongBox
|
|
413
|
+
)
|
|
414
|
+
AlgorithmType.RSA_4096_SHA256_PSS -> buildRSA(
|
|
415
|
+
alias = alias,
|
|
416
|
+
digest = DIGEST_SHA256,
|
|
417
|
+
purpose = Purpose.SIGN,
|
|
418
|
+
padding = SIGNATURE_PADDING_RSA_PSS,
|
|
419
|
+
keySize = 4096,
|
|
388
420
|
reqAuth = reqAuth,
|
|
421
|
+
reqUnlocked = reqUnlocked,
|
|
389
422
|
strongBox = strongBox
|
|
390
423
|
)
|
|
391
424
|
else -> throw Exception("INVALID_ALGORITHM_TYPE")
|
|
@@ -505,13 +538,15 @@ class DeviceCryptoModule : Module() {
|
|
|
505
538
|
|
|
506
539
|
fun runEncryption(cipher: Cipher) {
|
|
507
540
|
when (algoType) {
|
|
508
|
-
AlgorithmType.RSA_2048_OAEP_SHA1
|
|
541
|
+
AlgorithmType.RSA_2048_OAEP_SHA1,
|
|
542
|
+
AlgorithmType.RSA_4096_OAEP_SHA1 -> {
|
|
509
543
|
val oaepSpec = getOaepSpec(algoType)
|
|
510
544
|
cipher.init(Cipher.ENCRYPT_MODE, entry.certificate.publicKey, oaepSpec)
|
|
511
545
|
val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
|
|
512
546
|
promise.resolve(Base64.encodeToString(encrypted, Base64.NO_WRAP))
|
|
513
547
|
}
|
|
514
|
-
AlgorithmType.RSA_2048_PKCS1
|
|
548
|
+
AlgorithmType.RSA_2048_PKCS1,
|
|
549
|
+
AlgorithmType.RSA_4096_PKCS1 -> {
|
|
515
550
|
cipher.init(Cipher.ENCRYPT_MODE, entry.certificate.publicKey)
|
|
516
551
|
val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
|
|
517
552
|
promise.resolve(Base64.encodeToString(encrypted, Base64.NO_WRAP))
|
|
@@ -572,14 +607,16 @@ class DeviceCryptoModule : Module() {
|
|
|
572
607
|
|
|
573
608
|
fun runDecryption(cipher: Cipher) {
|
|
574
609
|
when (algoType) {
|
|
575
|
-
AlgorithmType.RSA_2048_OAEP_SHA1
|
|
610
|
+
AlgorithmType.RSA_2048_OAEP_SHA1,
|
|
611
|
+
AlgorithmType.RSA_4096_OAEP_SHA1 -> {
|
|
576
612
|
val oaepSpec = getOaepSpec(algoType)
|
|
577
613
|
cipher.init(Cipher.DECRYPT_MODE, entry.privateKey, oaepSpec)
|
|
578
614
|
val decrypted = cipher.doFinal(ciphertext)
|
|
579
615
|
promise.resolve(String(decrypted, Charsets.UTF_8))
|
|
580
616
|
return
|
|
581
617
|
}
|
|
582
|
-
AlgorithmType.RSA_2048_PKCS1
|
|
618
|
+
AlgorithmType.RSA_2048_PKCS1,
|
|
619
|
+
AlgorithmType.RSA_4096_PKCS1 -> {
|
|
583
620
|
cipher.init(Cipher.DECRYPT_MODE, entry.privateKey)
|
|
584
621
|
val decrypted = cipher.doFinal(ciphertext)
|
|
585
622
|
promise.resolve(String(decrypted, Charsets.UTF_8))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package expo.modules.devicecrypto
|
|
2
|
+
|
|
3
|
+
import javax.crypto.Mac
|
|
4
|
+
import javax.crypto.spec.SecretKeySpec
|
|
5
|
+
|
|
6
|
+
fun hkdfSha256(
|
|
7
|
+
ikm: ByteArray,
|
|
8
|
+
length: Int,
|
|
9
|
+
salt: ByteArray? = null,
|
|
10
|
+
info: ByteArray? = null
|
|
11
|
+
): ByteArray {
|
|
12
|
+
val mac = Mac.getInstance("HmacSHA256")
|
|
13
|
+
val actualSalt = salt ?: ByteArray(32)
|
|
14
|
+
mac.init(SecretKeySpec(actualSalt, "HmacSHA256"))
|
|
15
|
+
val prk = mac.doFinal(ikm)
|
|
16
|
+
|
|
17
|
+
val result = ByteArray(length)
|
|
18
|
+
var t = ByteArray(0)
|
|
19
|
+
var offset = 0
|
|
20
|
+
var counter = 1
|
|
21
|
+
|
|
22
|
+
while (offset < length) {
|
|
23
|
+
val macExpand = Mac.getInstance("HmacSHA256")
|
|
24
|
+
macExpand.init(SecretKeySpec(prk, "HmacSHA256"))
|
|
25
|
+
|
|
26
|
+
macExpand.update(t)
|
|
27
|
+
if (info != null) macExpand.update(info)
|
|
28
|
+
macExpand.update(counter.toByte())
|
|
29
|
+
|
|
30
|
+
t = macExpand.doFinal()
|
|
31
|
+
|
|
32
|
+
val toCopy = minOf(t.size, length - offset)
|
|
33
|
+
System.arraycopy(t, 0, result, offset, toCopy)
|
|
34
|
+
|
|
35
|
+
offset += toCopy
|
|
36
|
+
counter++
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
@@ -15,15 +15,19 @@ export declare enum AuthMethod {
|
|
|
15
15
|
export declare enum SigningAlgorithm {
|
|
16
16
|
ECDSA_SECP256R1_SHA256 = "ECDSA_SECP256R1_SHA256",
|
|
17
17
|
RSA_SHA256 = "RSA_SHA256",
|
|
18
|
-
RSA_SHA256_PSS = "RSA_SHA256_PSS"
|
|
18
|
+
RSA_SHA256_PSS = "RSA_SHA256_PSS",
|
|
19
|
+
RSA_4096_SHA256 = "RSA_4096_SHA256",
|
|
20
|
+
RSA_4096_SHA256_PSS = "RSA_4096_SHA256_PSS"
|
|
19
21
|
}
|
|
20
|
-
type EciesAlgorithm = "ECIES_P256_AES256_GCM";
|
|
21
|
-
type RsaAlgorithm = "RSA_2048_PKCS1" | "RSA_2048_OAEP_SHA1";
|
|
22
22
|
export declare enum EncryptionAlgorithm {
|
|
23
23
|
RSA_2048_PKCS1 = "RSA_2048_PKCS1",
|
|
24
24
|
RSA_2048_OAEP_SHA1 = "RSA_2048_OAEP_SHA1",
|
|
25
|
+
RSA_4096_PKCS1 = "RSA_4096_PKCS1",
|
|
26
|
+
RSA_4096_OAEP_SHA1 = "RSA_4096_OAEP_SHA1",
|
|
25
27
|
ECIES_P256_AES256_GCM = "ECIES_P256_AES256_GCM"
|
|
26
28
|
}
|
|
29
|
+
type EciesAlgorithm = EncryptionAlgorithm.ECIES_P256_AES256_GCM;
|
|
30
|
+
type RsaAlgorithm = EncryptionAlgorithm.RSA_2048_PKCS1 | EncryptionAlgorithm.RSA_2048_OAEP_SHA1 | EncryptionAlgorithm.RSA_4096_PKCS1 | EncryptionAlgorithm.RSA_4096_OAEP_SHA1;
|
|
27
31
|
export type GenerateKeyPairOptions = {
|
|
28
32
|
/**
|
|
29
33
|
* The algorithm type of the key to generate.
|
|
@@ -60,6 +64,11 @@ export type GenerateKeyPairOptions = {
|
|
|
60
64
|
* @platform android 🤖
|
|
61
65
|
*/
|
|
62
66
|
preferStrongBox?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Whether to require the device to be unlocked to use the key pair.
|
|
69
|
+
* @default true
|
|
70
|
+
*/
|
|
71
|
+
requireUnlockedDevice?: boolean;
|
|
63
72
|
};
|
|
64
73
|
type BaseAuthOptions = {
|
|
65
74
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCrypto.types.d.ts","sourceRoot":"","sources":["../src/DeviceCrypto.types.ts"],"names":[],"mappings":"AAAA,oBAAY,qBAAqB;IAC/B,kBAAkB,uBAAuB;IACzC,uBAAuB,4BAA4B;IACnD,aAAa,kBAAkB;CAChC;AAED,oBAAY,eAAe;IACzB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;CAC5B;AAED,oBAAY,UAAU;IACpB,QAAQ,aAAa;IACrB,qBAAqB,0BAA0B;CAChD;AAED,oBAAY,gBAAgB;IAC1B,sBAAsB,2BAA2B;IACjD,UAAU,eAAe;IACzB,cAAc,mBAAmB;
|
|
1
|
+
{"version":3,"file":"DeviceCrypto.types.d.ts","sourceRoot":"","sources":["../src/DeviceCrypto.types.ts"],"names":[],"mappings":"AAAA,oBAAY,qBAAqB;IAC/B,kBAAkB,uBAAuB;IACzC,uBAAuB,4BAA4B;IACnD,aAAa,kBAAkB;CAChC;AAED,oBAAY,eAAe;IACzB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;CAC5B;AAED,oBAAY,UAAU;IACpB,QAAQ,aAAa;IACrB,qBAAqB,0BAA0B;CAChD;AAED,oBAAY,gBAAgB;IAC1B,sBAAsB,2BAA2B;IACjD,UAAU,eAAe;IACzB,cAAc,mBAAmB;IACjC,eAAe,oBAAoB;IACnC,mBAAmB,wBAAwB;CAC5C;AAED,oBAAY,mBAAmB;IAC7B,cAAc,mBAAmB;IACjC,kBAAkB,uBAAuB;IACzC,cAAc,mBAAmB;IACjC,kBAAkB,uBAAuB;IACzC,qBAAqB,0BAA0B;CAChD;AAED,KAAK,cAAc,GAAG,mBAAmB,CAAC,qBAAqB,CAAC;AAChE,KAAK,YAAY,GACb,mBAAmB,CAAC,cAAc,GAClC,mBAAmB,CAAC,kBAAkB,GACtC,mBAAmB,CAAC,cAAc,GAClC,mBAAmB,CAAC,kBAAkB,CAAC;AAE3C,MAAM,MAAM,sBAAsB,GAAG;IACnC;;OAEG;IACH,aAAa,EAAE,gBAAgB,GAAG,mBAAmB,CAAC;IACtD;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB;;OAEG;IACH,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AACF,KAAK,iBAAiB,GAAG,kBAAkB,CAAC;AAE5C,KAAK,wBAAwB,GAAG;IAC9B;;OAEG;IACH,aAAa,EAAE,YAAY,CAAC;CAC7B,CAAC;AACF,KAAK,wBAAwB,GAAG,wBAAwB,CAAC;AAEzD,KAAK,0BAA0B,GAAG;IAChC;;OAEG;IACH,aAAa,EAAE,cAAc,CAAC;IAC9B;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AACF,KAAK,0BAA0B,GAAG,0BAA0B,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,kBAAkB,CAAC;AAC/D,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAE9C,MAAM,MAAM,cAAc,GACtB,CAAC,eAAe,GAAG,0BAA0B,CAAC,GAC9C,CAAC,eAAe,GAAG,wBAAwB,CAAC,CAAC;AACjD,MAAM,MAAM,cAAc,GACtB,CAAC,eAAe,GAAG,0BAA0B,CAAC,GAC9C,wBAAwB,CAAC;AAE7B,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC3B"}
|
|
@@ -20,11 +20,15 @@ export var SigningAlgorithm;
|
|
|
20
20
|
SigningAlgorithm["ECDSA_SECP256R1_SHA256"] = "ECDSA_SECP256R1_SHA256";
|
|
21
21
|
SigningAlgorithm["RSA_SHA256"] = "RSA_SHA256";
|
|
22
22
|
SigningAlgorithm["RSA_SHA256_PSS"] = "RSA_SHA256_PSS";
|
|
23
|
+
SigningAlgorithm["RSA_4096_SHA256"] = "RSA_4096_SHA256";
|
|
24
|
+
SigningAlgorithm["RSA_4096_SHA256_PSS"] = "RSA_4096_SHA256_PSS";
|
|
23
25
|
})(SigningAlgorithm || (SigningAlgorithm = {}));
|
|
24
26
|
export var EncryptionAlgorithm;
|
|
25
27
|
(function (EncryptionAlgorithm) {
|
|
26
28
|
EncryptionAlgorithm["RSA_2048_PKCS1"] = "RSA_2048_PKCS1";
|
|
27
29
|
EncryptionAlgorithm["RSA_2048_OAEP_SHA1"] = "RSA_2048_OAEP_SHA1";
|
|
30
|
+
EncryptionAlgorithm["RSA_4096_PKCS1"] = "RSA_4096_PKCS1";
|
|
31
|
+
EncryptionAlgorithm["RSA_4096_OAEP_SHA1"] = "RSA_4096_OAEP_SHA1";
|
|
28
32
|
EncryptionAlgorithm["ECIES_P256_AES256_GCM"] = "ECIES_P256_AES256_GCM";
|
|
29
33
|
})(EncryptionAlgorithm || (EncryptionAlgorithm = {}));
|
|
30
34
|
//# sourceMappingURL=DeviceCrypto.types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCrypto.types.js","sourceRoot":"","sources":["../src/DeviceCrypto.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,qBAIX;AAJD,WAAY,qBAAqB;IAC/B,kEAAyC,CAAA;IACzC,4EAAmD,CAAA;IACnD,wDAA+B,CAAA;AACjC,CAAC,EAJW,qBAAqB,KAArB,qBAAqB,QAIhC;AAED,MAAM,CAAN,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,0CAAuB,CAAA;IACvB,8CAA2B,CAAA;IAC3B,8CAA2B,CAAA;AAC7B,CAAC,EAJW,eAAe,KAAf,eAAe,QAI1B;AAED,MAAM,CAAN,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,mCAAqB,CAAA;IACrB,6DAA+C,CAAA;AACjD,CAAC,EAHW,UAAU,KAAV,UAAU,QAGrB;AAED,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"DeviceCrypto.types.js","sourceRoot":"","sources":["../src/DeviceCrypto.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,qBAIX;AAJD,WAAY,qBAAqB;IAC/B,kEAAyC,CAAA;IACzC,4EAAmD,CAAA;IACnD,wDAA+B,CAAA;AACjC,CAAC,EAJW,qBAAqB,KAArB,qBAAqB,QAIhC;AAED,MAAM,CAAN,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,0CAAuB,CAAA;IACvB,8CAA2B,CAAA;IAC3B,8CAA2B,CAAA;AAC7B,CAAC,EAJW,eAAe,KAAf,eAAe,QAI1B;AAED,MAAM,CAAN,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,mCAAqB,CAAA;IACrB,6DAA+C,CAAA;AACjD,CAAC,EAHW,UAAU,KAAV,UAAU,QAGrB;AAED,MAAM,CAAN,IAAY,gBAMX;AAND,WAAY,gBAAgB;IAC1B,qEAAiD,CAAA;IACjD,6CAAyB,CAAA;IACzB,qDAAiC,CAAA;IACjC,uDAAmC,CAAA;IACnC,+DAA2C,CAAA;AAC7C,CAAC,EANW,gBAAgB,KAAhB,gBAAgB,QAM3B;AAED,MAAM,CAAN,IAAY,mBAMX;AAND,WAAY,mBAAmB;IAC7B,wDAAiC,CAAA;IACjC,gEAAyC,CAAA;IACzC,wDAAiC,CAAA;IACjC,gEAAyC,CAAA;IACzC,sEAA+C,CAAA;AACjD,CAAC,EANW,mBAAmB,KAAnB,mBAAmB,QAM9B","sourcesContent":["export enum GenerateKeyPairResult {\n KEY_PAIR_GENERATED = \"KEY_PAIR_GENERATED\",\n KEY_PAIR_ALREADY_EXISTS = \"KEY_PAIR_ALREADY_EXISTS\",\n NOT_AVAILABLE = \"NOT_AVAILABLE\",\n}\n\nexport enum AuthCheckResult {\n AVAILABLE = \"AVAILABLE\",\n NO_HARDWARE = \"NO_HARDWARE\",\n UNAVAILABLE = \"UNAVAILABLE\",\n}\n\nexport enum AuthMethod {\n PASSCODE = \"PASSCODE\",\n PASSCODE_OR_BIOMETRIC = \"PASSCODE_OR_BIOMETRIC\",\n}\n\nexport enum SigningAlgorithm {\n ECDSA_SECP256R1_SHA256 = \"ECDSA_SECP256R1_SHA256\",\n RSA_SHA256 = \"RSA_SHA256\",\n RSA_SHA256_PSS = \"RSA_SHA256_PSS\",\n RSA_4096_SHA256 = \"RSA_4096_SHA256\",\n RSA_4096_SHA256_PSS = \"RSA_4096_SHA256_PSS\",\n}\n\nexport enum EncryptionAlgorithm {\n RSA_2048_PKCS1 = \"RSA_2048_PKCS1\",\n RSA_2048_OAEP_SHA1 = \"RSA_2048_OAEP_SHA1\",\n RSA_4096_PKCS1 = \"RSA_4096_PKCS1\",\n RSA_4096_OAEP_SHA1 = \"RSA_4096_OAEP_SHA1\",\n ECIES_P256_AES256_GCM = \"ECIES_P256_AES256_GCM\",\n}\n\ntype EciesAlgorithm = EncryptionAlgorithm.ECIES_P256_AES256_GCM;\ntype RsaAlgorithm =\n | EncryptionAlgorithm.RSA_2048_PKCS1\n | EncryptionAlgorithm.RSA_2048_OAEP_SHA1\n | EncryptionAlgorithm.RSA_4096_PKCS1\n | EncryptionAlgorithm.RSA_4096_OAEP_SHA1;\n\nexport type GenerateKeyPairOptions = {\n /**\n * The algorithm type of the key to generate.\n */\n algorithmType: SigningAlgorithm | EncryptionAlgorithm;\n /**\n * Whether to require authentication to sign.\n * Setting this to true will prompt biometric or passcode authentication before signing.\n *\n * Please check the *`isAuthCheckAvailable`* function to see if authentication is available.\n * If authentication is not available, generating a key pair will throw an error.\n * @default false\n */\n requireAuthentication?: boolean;\n /**\n * The method of authentication to use.\n * Note that on Android you have to define it when signing or decrypting.\n *\n * If you want to allow to use Face ID, you need to add the following to your app.json config file:\n * ```json\n * \"ios\": {\n * \"infoPlist\": {\n * \"NSFaceIDUsageDescription\": \"We use Face ID to secure your data.\",\n * }\n * }\n * ```\n * @default AuthMethod.PASSCODE_OR_BIOMETRIC\n * @platform ios 🍏\n */\n authMethod?: AuthMethod;\n /**\n * If the Strong Box is available, it will be used instead of the Trusted Execution Environment (TEE).\n * @default false\n * @platform android 🤖\n */\n preferStrongBox?: boolean;\n /**\n * Whether to require the device to be unlocked to use the key pair.\n * @default true\n */\n requireUnlockedDevice?: boolean;\n};\n\ntype BaseAuthOptions = {\n /**\n * The title of the prompt to show when authentication is required.\n * @default \"Unlock\"\n * @platform android 🤖\n */\n promptTitle?: string;\n /**\n * The subtitle of the prompt to show when authentication is required.\n * @default \"Enter your PIN to continue\"\n * @platform android 🤖\n */\n promptSubtitle?: string;\n /**\n * The method of authentication to use.\n * Note that on iOS you have to define it when generating the key pair.\n * @default AuthMethod.PASSCODE_OR_BIOMETRIC\n * @platform android 🤖\n */\n authMethod?: AuthMethod;\n};\n\ntype BaseSigningOptions = {\n /**\n * The algorithm type of the key to use for signing.\n */\n algorithmType: SigningAlgorithm;\n};\ntype BaseVerifyOptions = BaseSigningOptions;\n\ntype BaseRsaEncryptionOptions = {\n /**\n * The algorithm type of the key to use for encrypting.\n */\n algorithmType: RsaAlgorithm;\n};\ntype BaseRsaDecryptionOptions = BaseRsaEncryptionOptions;\n\ntype BaseEciesEncryptionOptions = {\n /**\n * The algorithm type of the key to use for encrypting.\n */\n algorithmType: EciesAlgorithm;\n /**\n * The peer public key to use for ECIES.\n */\n peerPublicKey: string;\n};\ntype BaseEciesDecryptionOptions = BaseEciesEncryptionOptions;\n\nexport type SignOptions = BaseAuthOptions & BaseSigningOptions;\nexport type VerifyOptions = BaseVerifyOptions;\n\nexport type DecryptOptions =\n | (BaseAuthOptions & BaseEciesDecryptionOptions)\n | (BaseAuthOptions & BaseRsaDecryptionOptions);\nexport type EncryptOptions =\n | (BaseAuthOptions & BaseEciesEncryptionOptions)\n | BaseRsaEncryptionOptions;\n\nexport interface GetPublicKeyOptions {\n /**\n * The format of the public key to return.\n * @default \"PEM\" (Base64 of PEM-encoded SubjectPublicKeyInfo (SPKI) for P‑256)\n */\n format?: \"PEM\" | \"BASE64\";\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCryptoModule.d.ts","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EAEnB,WAAW,EACX,cAAc,EACd,aAAa,EAEb,cAAc,EAEf,MAAM,sBAAsB,CAAC;;IA8B5B;;;OAGG;gCAvBqB,eAAe;IAyBvC;;;;OAIG;gCA5BqB,OAAO;IA8B/B;;;;OAIG;6BAC4B,MAAM,YAAY,sBAAsB;
|
|
1
|
+
{"version":3,"file":"DeviceCryptoModule.d.ts","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EAEnB,WAAW,EACX,cAAc,EACd,aAAa,EAEb,cAAc,EAEf,MAAM,sBAAsB,CAAC;;IA8B5B;;;OAGG;gCAvBqB,eAAe;IAyBvC;;;;OAIG;gCA5BqB,OAAO;IA8B/B;;;;OAIG;6BAC4B,MAAM,YAAY,sBAAsB;IAUvE;;;;;OAKG;0BACmB,MAAM,YAAY,mBAAmB;IAW3D;;;;OAIG;2BA/DkB,MAAM,KAAG,OAAO;IAiErC;;;OAGG;mBAnEQ,MAAM,EAAE;IAqEnB;;;;;;OAMG;kBACiB,MAAM,QAAQ,MAAM,YAAY,WAAW;IAU/D;;;;;;;OAOG;oBAEM,MAAM,QACP,MAAM,aACD,MAAM,YACP,aAAa;IAQzB;;;;;;;;;OASG;qBACoB,MAAM,QAAQ,MAAM,YAAY,cAAc;IAcrE;;;;;;OAMG;qBACoB,MAAM,QAAQ,MAAM,YAAY,cAAc;;AA3HvE,wBAsIE"}
|
|
@@ -29,6 +29,7 @@ export default {
|
|
|
29
29
|
reqAuth: options?.requireAuthentication ?? false,
|
|
30
30
|
authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,
|
|
31
31
|
preferStrongBox: options?.preferStrongBox ?? false,
|
|
32
|
+
reqUnlocked: options?.requireUnlockedDevice ?? true,
|
|
32
33
|
};
|
|
33
34
|
return module.generateKeyPair(alias, o);
|
|
34
35
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCryptoModule.js","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAEzD,OAAO,EAKL,UAAU,EAIV,gBAAgB,EAEhB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,SAAS,WAAW,CAAC,MAAc,EAAE,KAAK,GAAG,MAAM;IACjD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAE/D,OAAO,cAAc,KAAK,UAAU,OAAO,cAAc,KAAK,SAAS,CAAC;AAC1E,CAAC;AAoBD,MAAM,MAAM,GAAG,mBAAmB,CAAqB,cAAc,CAAC,CAAC;AAEvE,eAAe;IACb;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC,oBAAoB;IACjD;;;;OAIG;IACH,oBAAoB,EAAE,MAAM,CAAC,oBAAoB;IACjD;;;;OAIG;IACH,eAAe,EAAE,KAAK,EAAE,KAAa,EAAE,OAAgC,EAAE,EAAE;QACzE,MAAM,CAAC,GAAG;YACR,QAAQ,EAAE,OAAO,EAAE,aAAa;YAChC,OAAO,EAAE,OAAO,EAAE,qBAAqB,IAAI,KAAK;YAChD,UAAU,EAAE,OAAO,EAAE,UAAU,IAAI,UAAU,CAAC,qBAAqB;YACnE,eAAe,EAAE,OAAO,EAAE,eAAe,IAAI,KAAK;
|
|
1
|
+
{"version":3,"file":"DeviceCryptoModule.js","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport {\n AuthCheckResult,\n GenerateKeyPairOptions,\n GenerateKeyPairResult,\n GetPublicKeyOptions,\n AuthMethod,\n SignOptions,\n DecryptOptions,\n VerifyOptions,\n SigningAlgorithm,\n EncryptOptions,\n EncryptionAlgorithm,\n} from \"./DeviceCrypto.types\";\n\nfunction base64ToPem(base64: string, label = \"DATA\") {\n const normalized = base64.replace(/\\s+/g, \"\");\n const wrapped = normalized.match(/.{1,64}/g)?.join(\"\\n\") ?? \"\";\n\n return `-----BEGIN ${label}-----\\n${wrapped}\\n-----END ${label}-----\\n`;\n}\n\ndeclare class DeviceCryptoModule extends NativeModule {\n isAuthCheckAvailable(): AuthCheckResult;\n isStrongBoxAvailable(): boolean;\n generateKeyPair(alias: string, o: any): GenerateKeyPairResult;\n getPublicKey(alias: string): string | null;\n removeKeyPair(alias: string): boolean;\n aliases(): string[];\n sign(alias: string, data: string, o: any): Promise<string | null>;\n verify(\n alias: string,\n data: string,\n signature: string,\n o: any\n ): boolean | null;\n encrypt(alias: string, data: string, o: any): Promise<string | null>;\n decrypt(alias: string, data: string, o: any): Promise<string | null>;\n}\n\nconst module = requireNativeModule<DeviceCryptoModule>(\"DeviceCrypto\");\n\nexport default {\n /**\n * Checks if the biometric or passcode authentication is available.\n * @returns The result of the operation.\n */\n isAuthCheckAvailable: module.isAuthCheckAvailable,\n /**\n * Checks if the strong box is available .\n * @returns The result of the operation.\n * @platform android 🤖\n */\n isStrongBoxAvailable: module.isStrongBoxAvailable,\n /**\n * Creates a new ECDSA P‑256 key pair for the given alias, if it doesn’t already exist.\n * @param alias - The alias to use for the key pair.\n * @returns The result of the operation.\n */\n generateKeyPair: async (alias: string, options?: GenerateKeyPairOptions) => {\n const o = {\n algoType: options?.algorithmType,\n reqAuth: options?.requireAuthentication ?? false,\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n preferStrongBox: options?.preferStrongBox ?? false,\n reqUnlocked: options?.requireUnlockedDevice ?? true,\n };\n return module.generateKeyPair(alias, o);\n },\n /**\n * Returns the public key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param options - The options for the operation.\n * @returns The public key.\n */\n getPublicKey: (alias: string, options?: GetPublicKeyOptions) => {\n const publicKey = module.getPublicKey(alias);\n if (!publicKey) return null;\n if (options?.format === \"PEM\") {\n return base64ToPem(publicKey, \"PUBLIC KEY\");\n } else if (options?.format === \"BASE64\") {\n return publicKey;\n }\n\n return base64ToPem(publicKey, \"PUBLIC KEY\");\n },\n /**\n * Removes the key pair for the given alias.\n * @param alias - The alias to use for the key pair.\n * @returns True if the key pair was removed, false otherwise.\n */\n removeKeyPair: module.removeKeyPair,\n /**\n * Lists the aliases for the key pairs.\n * @returns The aliases.\n */\n aliases: module.aliases,\n /**\n * Signs the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to sign.\n * @param options - The options for the operation.\n * @returns The signature.\n */\n sign: async (alias: string, data: string, options?: SignOptions) => {\n const o = {\n title: options?.promptTitle ?? \"Unlock\",\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n algoType:\n options?.algorithmType ?? SigningAlgorithm.ECDSA_SECP256R1_SHA256,\n };\n return module.sign(alias, data, o);\n },\n /**\n * Verifies the given data with the signature for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to verify in UTF-8 format.\n * @param signature - The signature to verify in Base64 format.\n * @param options - The options for the operation.\n * @returns True if the signature is valid, false otherwise.\n */\n verify: async (\n alias: string,\n data: string,\n signature: string,\n options?: VerifyOptions\n ) => {\n const o = {\n algoType:\n options?.algorithmType ?? SigningAlgorithm.ECDSA_SECP256R1_SHA256,\n };\n return module.verify(alias, data, signature, o);\n },\n /**\n * Signs the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to sign in UTF-8 format.\n * @param options - The options for the operation.\n * @default {\n * algoType: EncryptionAlgorithm.RSA_2048_PKCS1,\n * }\n * @returns The signature.\n */\n encrypt: async (alias: string, data: string, options?: EncryptOptions) => {\n const o = {\n algoType: options?.algorithmType ?? EncryptionAlgorithm.RSA_2048_PKCS1,\n // @ts-ignore\n title: options?.promptTitle ?? \"Unlock\",\n // @ts-ignore\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n // @ts-ignore\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n // @ts-ignore\n peerPublicKey: options?.peerPublicKey ?? \"\",\n };\n return module.encrypt(alias, data, o);\n },\n /**\n * Decrypts the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to decrypt in Base64 format.\n * @param options - The options for the operation.\n * @returns The decrypted data.\n */\n decrypt: async (alias: string, data: string, options?: DecryptOptions) => {\n const o = {\n title: options?.promptTitle ?? \"Unlock\",\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n algoType: options?.algorithmType ?? EncryptionAlgorithm.RSA_2048_PKCS1,\n // @ts-ignore\n peerPublicKey: options?.peerPublicKey ?? \"\",\n };\n return module.decrypt(alias, data, o);\n },\n};\n"]}
|
package/ios/DeviceCrypto.podspec
CHANGED
package/ios/DeviceCrypto.swift
CHANGED
|
@@ -25,9 +25,13 @@ enum AlgorithmType: String {
|
|
|
25
25
|
case ECDSA_SECP256R1_SHA256 = "ECDSA_SECP256R1_SHA256"
|
|
26
26
|
case RSA_2048_PKCS1 = "RSA_2048_PKCS1"
|
|
27
27
|
case RSA_2048_OAEP_SHA1 = "RSA_2048_OAEP_SHA1"
|
|
28
|
+
case RSA_4096_PKCS1 = "RSA_4096_PKCS1"
|
|
29
|
+
case RSA_4096_OAEP_SHA1 = "RSA_4096_OAEP_SHA1"
|
|
28
30
|
case ECIES_P256_AES256_GCM = "ECIES_P256_AES256_GCM"
|
|
29
31
|
case RSA_SHA256 = "RSA_SHA256"
|
|
30
32
|
case RSA_SHA256_PSS = "RSA_SHA256_PSS"
|
|
33
|
+
case RSA_4096_SHA256 = "RSA_4096_SHA256"
|
|
34
|
+
case RSA_4096_SHA256_PSS = "RSA_4096_SHA256_PSS"
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
public class DeviceCryptoModule: Module {
|
|
@@ -39,94 +43,23 @@ public class DeviceCryptoModule: Module {
|
|
|
39
43
|
return .rsaEncryptionPKCS1
|
|
40
44
|
case .RSA_2048_OAEP_SHA1:
|
|
41
45
|
return .rsaEncryptionOAEPSHA1
|
|
46
|
+
case .RSA_4096_PKCS1:
|
|
47
|
+
return .rsaEncryptionPKCS1
|
|
48
|
+
case .RSA_4096_OAEP_SHA1:
|
|
49
|
+
return .rsaEncryptionOAEPSHA1
|
|
42
50
|
case .ECIES_P256_AES256_GCM:
|
|
43
51
|
return .eciesEncryptionCofactorX963SHA256AESGCM
|
|
44
52
|
case .RSA_SHA256:
|
|
45
53
|
return .rsaSignatureMessagePKCS1v15SHA256
|
|
46
54
|
case .RSA_SHA256_PSS:
|
|
47
55
|
return .rsaSignatureMessagePSSSHA256
|
|
56
|
+
case .RSA_4096_SHA256:
|
|
57
|
+
return .rsaSignatureMessagePKCS1v15SHA256
|
|
58
|
+
case .RSA_4096_SHA256_PSS:
|
|
59
|
+
return .rsaSignatureMessagePSSSHA256
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
// Converts ANSI x962 EC point to P‑256 SPKI DER format
|
|
52
|
-
private func x962ECPointToP256SPKI(_ publicKey: SecKey) -> Data? {
|
|
53
|
-
var error: Unmanaged<CFError>?
|
|
54
|
-
guard let raw = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
|
|
55
|
-
return nil
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
guard raw.count == 65 else { return nil }
|
|
59
|
-
let prefix = Data([
|
|
60
|
-
0x30, 0x59,
|
|
61
|
-
0x30, 0x13,
|
|
62
|
-
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
|
|
63
|
-
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
|
|
64
|
-
0x03, 0x42, 0x00
|
|
65
|
-
])
|
|
66
|
-
return prefix + raw
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Converts PKCS#1 RSA public key bytes to SPKI DER format.
|
|
70
|
-
private func rsaPKCS1ToSPKI(_ publicKey: SecKey) -> Data? {
|
|
71
|
-
guard let pkcs1 = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
|
|
72
|
-
return nil
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// rsaEncryption OID: 1.2.840.113549.1.1.1 with NULL params
|
|
76
|
-
let algorithmIdentifier = Data([
|
|
77
|
-
0x30, 0x0D,
|
|
78
|
-
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
|
|
79
|
-
0x05, 0x00
|
|
80
|
-
])
|
|
81
|
-
|
|
82
|
-
let bitStringPayload = Data([0x00]) + pkcs1
|
|
83
|
-
guard let bitString = asn1Wrap(tag: 0x03, content: bitStringPayload) else {
|
|
84
|
-
return nil
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return asn1Wrap(tag: 0x30, content: algorithmIdentifier + bitString)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private func asn1Wrap(tag: UInt8, content: Data) -> Data? {
|
|
91
|
-
guard let length = asn1Length(content.count) else {
|
|
92
|
-
return nil
|
|
93
|
-
}
|
|
94
|
-
return Data([tag]) + length + content
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private func asn1Length(_ length: Int) -> Data? {
|
|
98
|
-
if length < 0x80 {
|
|
99
|
-
return Data([UInt8(length)])
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
var value = length
|
|
103
|
-
var bytes: [UInt8] = []
|
|
104
|
-
while value > 0 {
|
|
105
|
-
bytes.insert(UInt8(value & 0xff), at: 0)
|
|
106
|
-
value >>= 8
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
guard bytes.count <= 4 else {
|
|
110
|
-
return nil
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return Data([0x80 | UInt8(bytes.count)]) + Data(bytes)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Converts P‑256 SPKI DER format to ANSI x962 EC point format.
|
|
117
|
-
private func spkiP256ToX962(_ spki: Data) -> Data? {
|
|
118
|
-
let prefix = Data([
|
|
119
|
-
0x30, 0x59,
|
|
120
|
-
0x30, 0x13,
|
|
121
|
-
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
|
|
122
|
-
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
|
|
123
|
-
0x03, 0x42, 0x00
|
|
124
|
-
])
|
|
125
|
-
guard spki.count == prefix.count + 65 else { return nil }
|
|
126
|
-
guard spki.starts(with: prefix) else { return nil }
|
|
127
|
-
return spki.suffix(65)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
63
|
private func getSecKeyQuery(_ alias: String, keyClass: CFString, returnRef: Bool = true) -> [String: Any] {
|
|
131
64
|
var query: [String: Any] = [
|
|
132
65
|
kSecClass as String: kSecClassKey,
|
|
@@ -147,7 +80,7 @@ public class DeviceCryptoModule: Module {
|
|
|
147
80
|
return (item as! SecKey)
|
|
148
81
|
}
|
|
149
82
|
|
|
150
|
-
private func buildECDSA(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
83
|
+
private func buildECDSA(alias: String, reqAuth: Bool, authMethod: AuthMethod, reqUnlocked: Bool) -> SecKey? {
|
|
151
84
|
let accessFlags: SecAccessControlCreateFlags
|
|
152
85
|
if reqAuth {
|
|
153
86
|
switch authMethod {
|
|
@@ -160,9 +93,13 @@ public class DeviceCryptoModule: Module {
|
|
|
160
93
|
accessFlags = .privateKeyUsage
|
|
161
94
|
}
|
|
162
95
|
|
|
96
|
+
let lockPolicy = reqUnlocked
|
|
97
|
+
? kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
98
|
+
: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
|
99
|
+
|
|
163
100
|
let access = SecAccessControlCreateWithFlags(
|
|
164
101
|
kCFAllocatorDefault,
|
|
165
|
-
|
|
102
|
+
lockPolicy,
|
|
166
103
|
accessFlags,
|
|
167
104
|
nil
|
|
168
105
|
)
|
|
@@ -185,7 +122,7 @@ public class DeviceCryptoModule: Module {
|
|
|
185
122
|
return SecKeyCreateRandomKey(attributes, nil)
|
|
186
123
|
}
|
|
187
124
|
|
|
188
|
-
private func buildRSA(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
125
|
+
private func buildRSA(alias: String, keySize: Int, reqAuth: Bool, authMethod: AuthMethod, reqUnlocked: Bool) -> SecKey? {
|
|
189
126
|
let accessFlags: SecAccessControlCreateFlags
|
|
190
127
|
if reqAuth {
|
|
191
128
|
switch authMethod {
|
|
@@ -198,16 +135,20 @@ public class DeviceCryptoModule: Module {
|
|
|
198
135
|
accessFlags = []
|
|
199
136
|
}
|
|
200
137
|
|
|
138
|
+
let lockPolicy = reqUnlocked
|
|
139
|
+
? kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
140
|
+
: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
|
141
|
+
|
|
201
142
|
let access = SecAccessControlCreateWithFlags(
|
|
202
143
|
kCFAllocatorDefault,
|
|
203
|
-
|
|
144
|
+
lockPolicy,
|
|
204
145
|
accessFlags,
|
|
205
146
|
nil
|
|
206
147
|
)
|
|
207
148
|
|
|
208
149
|
let attributes: NSDictionary = [
|
|
209
150
|
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
210
|
-
kSecAttrKeySizeInBits:
|
|
151
|
+
kSecAttrKeySizeInBits: keySize,
|
|
211
152
|
kSecPrivateKeyAttrs: [
|
|
212
153
|
kSecAttrIsPermanent: true,
|
|
213
154
|
kSecAttrApplicationTag: alias,
|
|
@@ -222,7 +163,7 @@ public class DeviceCryptoModule: Module {
|
|
|
222
163
|
return SecKeyCreateRandomKey(attributes, nil)
|
|
223
164
|
}
|
|
224
165
|
|
|
225
|
-
private func buildECIES(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
166
|
+
private func buildECIES(alias: String, reqAuth: Bool, authMethod: AuthMethod, reqUnlocked: Bool) -> SecKey? {
|
|
226
167
|
let accessFlags: SecAccessControlCreateFlags
|
|
227
168
|
if reqAuth {
|
|
228
169
|
switch authMethod {
|
|
@@ -235,9 +176,13 @@ public class DeviceCryptoModule: Module {
|
|
|
235
176
|
accessFlags = .privateKeyUsage
|
|
236
177
|
}
|
|
237
178
|
|
|
179
|
+
let lockPolicy = reqUnlocked
|
|
180
|
+
? kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
181
|
+
: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
|
182
|
+
|
|
238
183
|
let access = SecAccessControlCreateWithFlags(
|
|
239
184
|
kCFAllocatorDefault,
|
|
240
|
-
|
|
185
|
+
lockPolicy,
|
|
241
186
|
accessFlags,
|
|
242
187
|
nil
|
|
243
188
|
)
|
|
@@ -304,6 +249,7 @@ public class DeviceCryptoModule: Module {
|
|
|
304
249
|
|
|
305
250
|
private func generateKeyPair(alias: String, o: [String: Any]) throws -> String {
|
|
306
251
|
let reqAuth = o["reqAuth"] as! Bool
|
|
252
|
+
let reqUnlocked = o["reqUnlocked"] as! Bool
|
|
307
253
|
let authMethod = AuthMethod(rawValue: o["authMethod"] as! String)
|
|
308
254
|
let algoType = AlgorithmType(rawValue: o["algoType"] as! String)
|
|
309
255
|
|
|
@@ -322,17 +268,25 @@ public class DeviceCryptoModule: Module {
|
|
|
322
268
|
|
|
323
269
|
switch algoType {
|
|
324
270
|
case .ECDSA_SECP256R1_SHA256:
|
|
325
|
-
self.buildECDSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod
|
|
271
|
+
self.buildECDSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
326
272
|
case .RSA_2048_PKCS1:
|
|
327
|
-
self.buildRSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod
|
|
273
|
+
self.buildRSA(alias: alias, keySize: 2048, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
328
274
|
case .RSA_2048_OAEP_SHA1:
|
|
329
|
-
self.buildRSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod
|
|
275
|
+
self.buildRSA(alias: alias, keySize: 2048, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
276
|
+
case .RSA_4096_PKCS1:
|
|
277
|
+
self.buildRSA(alias: alias, keySize: 4096, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
278
|
+
case .RSA_4096_OAEP_SHA1:
|
|
279
|
+
self.buildRSA(alias: alias, keySize: 4096, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
330
280
|
case .ECIES_P256_AES256_GCM:
|
|
331
|
-
self.buildECIES(alias: alias, reqAuth: reqAuth, authMethod: authMethod
|
|
281
|
+
self.buildECIES(alias: alias, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
332
282
|
case .RSA_SHA256:
|
|
333
|
-
self.buildRSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod
|
|
283
|
+
self.buildRSA(alias: alias, keySize: 2048, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
334
284
|
case .RSA_SHA256_PSS:
|
|
335
|
-
self.buildRSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod
|
|
285
|
+
self.buildRSA(alias: alias, keySize: 2048, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
286
|
+
case .RSA_4096_SHA256:
|
|
287
|
+
self.buildRSA(alias: alias, keySize: 4096, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
288
|
+
case .RSA_4096_SHA256_PSS:
|
|
289
|
+
self.buildRSA(alias: alias, keySize: 4096, reqAuth: reqAuth, authMethod: authMethod!, reqUnlocked: reqUnlocked)
|
|
336
290
|
default:
|
|
337
291
|
throw NSError(
|
|
338
292
|
domain: "DeviceCrypto",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
/// Converts ANSI x9.62 EC point to P-256 SPKI DER format.
|
|
5
|
+
func x962ECPointToP256SPKI(_ publicKey: SecKey) -> Data? {
|
|
6
|
+
var error: Unmanaged<CFError>?
|
|
7
|
+
guard let raw = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
|
|
8
|
+
return nil
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
guard raw.count == 65 else { return nil }
|
|
12
|
+
let prefix = Data([
|
|
13
|
+
0x30, 0x59,
|
|
14
|
+
0x30, 0x13,
|
|
15
|
+
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
|
|
16
|
+
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
|
|
17
|
+
0x03, 0x42, 0x00,
|
|
18
|
+
])
|
|
19
|
+
return prefix + raw
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Converts PKCS#1 RSA public key bytes to SPKI DER format.
|
|
23
|
+
func rsaPKCS1ToSPKI(_ publicKey: SecKey) -> Data? {
|
|
24
|
+
guard let pkcs1 = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
|
|
25
|
+
return nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// rsaEncryption OID: 1.2.840.113549.1.1.1 with NULL params
|
|
29
|
+
let algorithmIdentifier = Data([
|
|
30
|
+
0x30, 0x0D,
|
|
31
|
+
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
|
|
32
|
+
0x05, 0x00,
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
let bitStringPayload = Data([0x00]) + pkcs1
|
|
36
|
+
guard let bitString = asn1Wrap(tag: 0x03, content: bitStringPayload) else {
|
|
37
|
+
return nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return asn1Wrap(tag: 0x30, content: algorithmIdentifier + bitString)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Wraps ASN.1 content with a tag and length.
|
|
44
|
+
func asn1Wrap(tag: UInt8, content: Data) -> Data? {
|
|
45
|
+
guard let length = asn1Length(content.count) else {
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
return Data([tag]) + length + content
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Converts a length to ASN.1 length format.
|
|
52
|
+
func asn1Length(_ length: Int) -> Data? {
|
|
53
|
+
if length < 0x80 {
|
|
54
|
+
return Data([UInt8(length)])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var value = length
|
|
58
|
+
var bytes: [UInt8] = []
|
|
59
|
+
while value > 0 {
|
|
60
|
+
bytes.insert(UInt8(value & 0xff), at: 0)
|
|
61
|
+
value >>= 8
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
guard bytes.count <= 4 else {
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Data([0x80 | UInt8(bytes.count)]) + Data(bytes)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Converts P-256 SPKI DER format to ANSI x9.62 EC point format.
|
|
72
|
+
func spkiP256ToX962(_ spki: Data) -> Data? {
|
|
73
|
+
let prefix = Data([
|
|
74
|
+
0x30, 0x59,
|
|
75
|
+
0x30, 0x13,
|
|
76
|
+
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
|
|
77
|
+
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
|
|
78
|
+
0x03, 0x42, 0x00,
|
|
79
|
+
])
|
|
80
|
+
guard spki.count == prefix.count + 65 else { return nil }
|
|
81
|
+
guard spki.starts(with: prefix) else { return nil }
|
|
82
|
+
return spki.suffix(65)
|
|
83
|
+
}
|
|
84
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-device-crypto",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Crypto for Expo using device hardware capabilities",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -48,5 +48,10 @@
|
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"expo": "*"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"expo": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -19,16 +19,25 @@ export enum SigningAlgorithm {
|
|
|
19
19
|
ECDSA_SECP256R1_SHA256 = "ECDSA_SECP256R1_SHA256",
|
|
20
20
|
RSA_SHA256 = "RSA_SHA256",
|
|
21
21
|
RSA_SHA256_PSS = "RSA_SHA256_PSS",
|
|
22
|
+
RSA_4096_SHA256 = "RSA_4096_SHA256",
|
|
23
|
+
RSA_4096_SHA256_PSS = "RSA_4096_SHA256_PSS",
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
type EciesAlgorithm = "ECIES_P256_AES256_GCM";
|
|
25
|
-
type RsaAlgorithm = "RSA_2048_PKCS1" | "RSA_2048_OAEP_SHA1";
|
|
26
26
|
export enum EncryptionAlgorithm {
|
|
27
27
|
RSA_2048_PKCS1 = "RSA_2048_PKCS1",
|
|
28
28
|
RSA_2048_OAEP_SHA1 = "RSA_2048_OAEP_SHA1",
|
|
29
|
+
RSA_4096_PKCS1 = "RSA_4096_PKCS1",
|
|
30
|
+
RSA_4096_OAEP_SHA1 = "RSA_4096_OAEP_SHA1",
|
|
29
31
|
ECIES_P256_AES256_GCM = "ECIES_P256_AES256_GCM",
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
type EciesAlgorithm = EncryptionAlgorithm.ECIES_P256_AES256_GCM;
|
|
35
|
+
type RsaAlgorithm =
|
|
36
|
+
| EncryptionAlgorithm.RSA_2048_PKCS1
|
|
37
|
+
| EncryptionAlgorithm.RSA_2048_OAEP_SHA1
|
|
38
|
+
| EncryptionAlgorithm.RSA_4096_PKCS1
|
|
39
|
+
| EncryptionAlgorithm.RSA_4096_OAEP_SHA1;
|
|
40
|
+
|
|
32
41
|
export type GenerateKeyPairOptions = {
|
|
33
42
|
/**
|
|
34
43
|
* The algorithm type of the key to generate.
|
|
@@ -65,7 +74,12 @@ export type GenerateKeyPairOptions = {
|
|
|
65
74
|
* @platform android 🤖
|
|
66
75
|
*/
|
|
67
76
|
preferStrongBox?: boolean;
|
|
68
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Whether to require the device to be unlocked to use the key pair.
|
|
79
|
+
* @default true
|
|
80
|
+
*/
|
|
81
|
+
requireUnlockedDevice?: boolean;
|
|
82
|
+
};
|
|
69
83
|
|
|
70
84
|
type BaseAuthOptions = {
|
|
71
85
|
/**
|
|
@@ -87,7 +101,7 @@ type BaseAuthOptions = {
|
|
|
87
101
|
* @platform android 🤖
|
|
88
102
|
*/
|
|
89
103
|
authMethod?: AuthMethod;
|
|
90
|
-
}
|
|
104
|
+
};
|
|
91
105
|
|
|
92
106
|
type BaseSigningOptions = {
|
|
93
107
|
/**
|
|
@@ -64,6 +64,7 @@ export default {
|
|
|
64
64
|
reqAuth: options?.requireAuthentication ?? false,
|
|
65
65
|
authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,
|
|
66
66
|
preferStrongBox: options?.preferStrongBox ?? false,
|
|
67
|
+
reqUnlocked: options?.requireUnlockedDevice ?? true,
|
|
67
68
|
};
|
|
68
69
|
return module.generateKeyPair(alias, o);
|
|
69
70
|
},
|