expo-device-crypto 0.1.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/.eslintrc.js +5 -0
- package/README.md +132 -0
- package/android/build.gradle +47 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/devicecrypto/DeviceCryptoModule.kt +385 -0
- package/build/DeviceCrypto.types.d.ts +101 -0
- package/build/DeviceCrypto.types.d.ts.map +1 -0
- package/build/DeviceCrypto.types.js +26 -0
- package/build/DeviceCrypto.types.js.map +1 -0
- package/build/DeviceCryptoModule.d.ts +86 -0
- package/build/DeviceCryptoModule.d.ts.map +1 -0
- package/build/DeviceCryptoModule.js +132 -0
- package/build/DeviceCryptoModule.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/DeviceCrypto.podspec +23 -0
- package/ios/DeviceCrypto.swift +404 -0
- package/package.json +55 -0
- package/src/DeviceCrypto.types.ts +106 -0
- package/src/DeviceCryptoModule.ts +171 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import Security
|
|
3
|
+
import Foundation
|
|
4
|
+
import LocalAuthentication
|
|
5
|
+
|
|
6
|
+
enum SecureSigningModuleResult: String {
|
|
7
|
+
case KEY_PAIR_GENERATED = "KEY_PAIR_GENERATED"
|
|
8
|
+
case KEY_PAIR_ALREADY_EXISTS = "KEY_PAIR_ALREADY_EXISTS"
|
|
9
|
+
case NOT_AVAILABLE = "NOT_AVAILABLE"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
enum AuthCheckResult: String {
|
|
13
|
+
case AVAILABLE = "AVAILABLE"
|
|
14
|
+
case NO_HARDWARE = "NO_HARDWARE"
|
|
15
|
+
case UNAVAILABLE = "UNAVAILABLE"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
enum AuthMethod: String {
|
|
19
|
+
case PASSCODE = "PASSCODE"
|
|
20
|
+
case PASSCODE_OR_BIOMETRIC = "PASSCODE_OR_BIOMETRIC"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
enum AlgorithmType: String {
|
|
24
|
+
case ECDSA_SECP256R1_SHA256 = "ECDSA_SECP256R1_SHA256"
|
|
25
|
+
case RSA_2048_PKCS1 = "RSA_2048_PKCS1"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public class DeviceCryptoModule: Module {
|
|
29
|
+
private func toiOSAlgo(algorithm: AlgorithmType) -> SecKeyAlgorithm {
|
|
30
|
+
switch algorithm {
|
|
31
|
+
case .ECDSA_SECP256R1_SHA256:
|
|
32
|
+
return .ecdsaSignatureMessageX962SHA256
|
|
33
|
+
case .RSA_2048_PKCS1:
|
|
34
|
+
return .rsaEncryptionPKCS1
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Converts ANSI x962 EC point to P‑256 SPKI DER format
|
|
39
|
+
private func x962ECPointToP256SPKI(_ publicKey: SecKey) -> Data? {
|
|
40
|
+
var error: Unmanaged<CFError>?
|
|
41
|
+
guard let raw = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
guard raw.count == 65 else { return nil }
|
|
46
|
+
let prefix = Data([
|
|
47
|
+
0x30, 0x59,
|
|
48
|
+
0x30, 0x13,
|
|
49
|
+
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
|
|
50
|
+
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
|
|
51
|
+
0x03, 0x42, 0x00
|
|
52
|
+
])
|
|
53
|
+
return prefix + raw
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Converts PKCS#1 RSA public key bytes to SPKI DER format.
|
|
57
|
+
private func rsaPKCS1ToSPKI(_ publicKey: SecKey) -> Data? {
|
|
58
|
+
guard let pkcs1 = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// rsaEncryption OID: 1.2.840.113549.1.1.1 with NULL params
|
|
63
|
+
let algorithmIdentifier = Data([
|
|
64
|
+
0x30, 0x0D,
|
|
65
|
+
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
|
|
66
|
+
0x05, 0x00
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
let bitStringPayload = Data([0x00]) + pkcs1
|
|
70
|
+
guard let bitString = asn1Wrap(tag: 0x03, content: bitStringPayload) else {
|
|
71
|
+
return nil
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return asn1Wrap(tag: 0x30, content: algorithmIdentifier + bitString)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private func asn1Wrap(tag: UInt8, content: Data) -> Data? {
|
|
78
|
+
guard let length = asn1Length(content.count) else {
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
return Data([tag]) + length + content
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private func asn1Length(_ length: Int) -> Data? {
|
|
85
|
+
if length < 0x80 {
|
|
86
|
+
return Data([UInt8(length)])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var value = length
|
|
90
|
+
var bytes: [UInt8] = []
|
|
91
|
+
while value > 0 {
|
|
92
|
+
bytes.insert(UInt8(value & 0xff), at: 0)
|
|
93
|
+
value >>= 8
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
guard bytes.count <= 4 else {
|
|
97
|
+
return nil
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Data([0x80 | UInt8(bytes.count)]) + Data(bytes)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func isAuthCheckAvailable() -> String {
|
|
104
|
+
let context = LAContext()
|
|
105
|
+
let available = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
|
|
106
|
+
if available {
|
|
107
|
+
return AuthCheckResult.AVAILABLE.rawValue
|
|
108
|
+
} else {
|
|
109
|
+
return AuthCheckResult.UNAVAILABLE.rawValue
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func getAliases() -> [String] {
|
|
114
|
+
let query: [String: Any] = [
|
|
115
|
+
kSecClass as String: kSecClassKey,
|
|
116
|
+
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
|
|
117
|
+
kSecMatchLimit as String: kSecMatchLimitAll,
|
|
118
|
+
kSecReturnAttributes as String: true,
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
var result: CFTypeRef?
|
|
122
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
123
|
+
guard status == errSecSuccess else { return [] }
|
|
124
|
+
|
|
125
|
+
let items = (result as? [[String: Any]]) ?? []
|
|
126
|
+
return items.compactMap { attrs in
|
|
127
|
+
let tagKey = kSecAttrApplicationTag as String
|
|
128
|
+
if let tagString = attrs[tagKey] as? String {
|
|
129
|
+
return tagString
|
|
130
|
+
}
|
|
131
|
+
return nil
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private func getSecKeyQuery(_ alias: String, keyClass: CFString, returnRef: Bool = true) -> [String: Any] {
|
|
136
|
+
var query: [String: Any] = [
|
|
137
|
+
kSecClass as String: kSecClassKey,
|
|
138
|
+
kSecAttrApplicationTag as String: alias,
|
|
139
|
+
kSecAttrKeyClass as String: keyClass,
|
|
140
|
+
]
|
|
141
|
+
if returnRef {
|
|
142
|
+
query[kSecReturnRef as String] = true
|
|
143
|
+
}
|
|
144
|
+
return query
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private func getSecKeyByAlias(_ alias: String, keyClass: CFString) -> SecKey? {
|
|
148
|
+
let query: [String: Any] = self.getSecKeyQuery(alias, keyClass: keyClass)
|
|
149
|
+
var item: CFTypeRef?
|
|
150
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
151
|
+
guard status == errSecSuccess else { return nil }
|
|
152
|
+
return (item as! SecKey)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private func retrievePublicKey(_ secKey: SecKey) -> String? {
|
|
156
|
+
let publicKey = SecKeyCopyPublicKey(secKey)!
|
|
157
|
+
|
|
158
|
+
guard
|
|
159
|
+
let attrs = SecKeyCopyAttributes(publicKey) as? [String: Any],
|
|
160
|
+
let keyType = attrs[kSecAttrKeyType as String] as? String
|
|
161
|
+
else {
|
|
162
|
+
return nil
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if keyType == (kSecAttrKeyTypeECSECPrimeRandom as String) {
|
|
166
|
+
return x962ECPointToP256SPKI(publicKey)?.base64EncodedString()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if keyType == (kSecAttrKeyTypeRSA as String) {
|
|
170
|
+
return rsaPKCS1ToSPKI(publicKey)?.base64EncodedString()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
|
|
174
|
+
return nil
|
|
175
|
+
}
|
|
176
|
+
return publicKeyData.base64EncodedString()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private func removeKeyStoreEntry(_ alias: String) -> Bool {
|
|
180
|
+
let privateQuery = self.getSecKeyQuery(alias, keyClass: kSecAttrKeyClassPrivate, returnRef: false)
|
|
181
|
+
let privateStatus = SecItemDelete(privateQuery as CFDictionary)
|
|
182
|
+
|
|
183
|
+
let publicQuery = self.getSecKeyQuery(alias, keyClass: kSecAttrKeyClassPublic, returnRef: false)
|
|
184
|
+
let publicStatus = SecItemDelete(publicQuery as CFDictionary)
|
|
185
|
+
|
|
186
|
+
return privateStatus == errSecSuccess || publicStatus == errSecSuccess
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private func buildECDSA_SECP256R1_SHA256(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
190
|
+
let accessFlags: SecAccessControlCreateFlags
|
|
191
|
+
if reqAuth {
|
|
192
|
+
switch authMethod {
|
|
193
|
+
case .PASSCODE:
|
|
194
|
+
accessFlags = [.privateKeyUsage, .devicePasscode]
|
|
195
|
+
case .PASSCODE_OR_BIOMETRIC:
|
|
196
|
+
accessFlags = [.privateKeyUsage, .userPresence]
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
accessFlags = .privateKeyUsage
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let access = SecAccessControlCreateWithFlags(
|
|
203
|
+
kCFAllocatorDefault,
|
|
204
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
205
|
+
accessFlags,
|
|
206
|
+
nil
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
let attributes: NSDictionary = [
|
|
210
|
+
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
|
211
|
+
kSecAttrKeySizeInBits: 256,
|
|
212
|
+
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
|
|
213
|
+
kSecPrivateKeyAttrs: [
|
|
214
|
+
kSecAttrIsPermanent: true,
|
|
215
|
+
kSecAttrApplicationTag: alias,
|
|
216
|
+
kSecAttrAccessControl: access
|
|
217
|
+
],
|
|
218
|
+
kSecPublicKeyAttrs: [
|
|
219
|
+
kSecAttrIsPermanent: true,
|
|
220
|
+
kSecAttrApplicationTag: alias
|
|
221
|
+
]
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
return SecKeyCreateRandomKey(attributes, nil)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private func buildRSA_2048_PKCS1(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
228
|
+
let accessFlags: SecAccessControlCreateFlags
|
|
229
|
+
if reqAuth {
|
|
230
|
+
switch authMethod {
|
|
231
|
+
case .PASSCODE:
|
|
232
|
+
accessFlags = [.devicePasscode]
|
|
233
|
+
case .PASSCODE_OR_BIOMETRIC:
|
|
234
|
+
accessFlags = [.userPresence]
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
accessFlags = []
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let access = SecAccessControlCreateWithFlags(
|
|
241
|
+
kCFAllocatorDefault,
|
|
242
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
243
|
+
accessFlags,
|
|
244
|
+
nil
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
let attributes: NSDictionary = [
|
|
248
|
+
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
249
|
+
kSecAttrKeySizeInBits: 2048,
|
|
250
|
+
kSecPrivateKeyAttrs: [
|
|
251
|
+
kSecAttrIsPermanent: true,
|
|
252
|
+
kSecAttrApplicationTag: alias,
|
|
253
|
+
kSecAttrAccessControl: access
|
|
254
|
+
],
|
|
255
|
+
kSecPublicKeyAttrs: [
|
|
256
|
+
kSecAttrIsPermanent: true,
|
|
257
|
+
kSecAttrApplicationTag: alias
|
|
258
|
+
]
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
return SecKeyCreateRandomKey(attributes, nil)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public func definition() -> ModuleDefinition {
|
|
265
|
+
|
|
266
|
+
Name("DeviceCrypto")
|
|
267
|
+
|
|
268
|
+
Function("isAuthCheckAvailable") { () -> String in
|
|
269
|
+
return self.isAuthCheckAvailable()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
Function("generateKeyPair") { (alias: String, o: [String: Any]) -> String in
|
|
273
|
+
let reqAuth = o["reqAuth"] as! Bool
|
|
274
|
+
let authMethod = AuthMethod(rawValue: o["authMethod"] as! String)
|
|
275
|
+
let algoType = AlgorithmType(rawValue: o["algoType"] as! String)
|
|
276
|
+
|
|
277
|
+
if reqAuth && self.isAuthCheckAvailable() != AuthCheckResult.AVAILABLE.rawValue {
|
|
278
|
+
throw NSError(
|
|
279
|
+
domain: "DeviceCrypto",
|
|
280
|
+
code: 1,
|
|
281
|
+
userInfo: [NSLocalizedDescriptionKey: "NO_AUTH_AVAILABLE"]
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let secKey = self.getSecKeyByAlias(alias, keyClass: kSecAttrKeyClassPrivate)
|
|
286
|
+
if secKey != nil {
|
|
287
|
+
return SecureSigningModuleResult.KEY_PAIR_ALREADY_EXISTS.rawValue
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
switch algoType {
|
|
291
|
+
case .ECDSA_SECP256R1_SHA256:
|
|
292
|
+
self.buildECDSA_SECP256R1_SHA256(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
|
|
293
|
+
case .RSA_2048_PKCS1:
|
|
294
|
+
self.buildRSA_2048_PKCS1(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
|
|
295
|
+
default:
|
|
296
|
+
throw NSError(
|
|
297
|
+
domain: "DeviceCrypto",
|
|
298
|
+
code: 1,
|
|
299
|
+
userInfo: [NSLocalizedDescriptionKey: "INVALID_ALGORITHM_TYPE"]
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return SecureSigningModuleResult.KEY_PAIR_GENERATED.rawValue
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
Function("removeKeyPair") { (alias: String) -> Bool in
|
|
307
|
+
return self.removeKeyStoreEntry(alias)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
Function("aliases") { () -> [String] in
|
|
311
|
+
return self.getAliases()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
Function("getPublicKey") { (alias: String) -> String? in
|
|
315
|
+
let secKey = self.getSecKeyByAlias(alias, keyClass: kSecAttrKeyClassPublic)
|
|
316
|
+
guard let secKey else { return nil }
|
|
317
|
+
return self.retrievePublicKey(secKey)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
AsyncFunction("sign") { (alias: String, data: String, o: [String: Any]) -> String? in
|
|
321
|
+
let secKey = self.getSecKeyByAlias(alias, keyClass: kSecAttrKeyClassPrivate)
|
|
322
|
+
guard let secKey else { return nil }
|
|
323
|
+
|
|
324
|
+
let algoType = AlgorithmType(rawValue: o["algoType"] as! String)
|
|
325
|
+
let algo = self.toiOSAlgo(algorithm: algoType!)
|
|
326
|
+
|
|
327
|
+
var signingError: Unmanaged<CFError>?
|
|
328
|
+
let signatureCF = SecKeyCreateSignature(
|
|
329
|
+
secKey,
|
|
330
|
+
algo,
|
|
331
|
+
Data(data.utf8) as CFData,
|
|
332
|
+
&signingError
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if let error = signingError?.takeRetainedValue() {
|
|
336
|
+
throw error as Error
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
guard let signatureCF else { return nil }
|
|
340
|
+
let signature = signatureCF as Data
|
|
341
|
+
return signature.base64EncodedString()
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
Function("verify") { (alias: String, data: String, signature: String, o: [String: Any]) -> Bool? in
|
|
345
|
+
let secKey = self.getSecKeyByAlias(alias, keyClass: kSecAttrKeyClassPrivate)
|
|
346
|
+
guard let secKey else { return nil }
|
|
347
|
+
|
|
348
|
+
guard let publicKey = SecKeyCopyPublicKey(secKey) else { return nil }
|
|
349
|
+
guard let signatureData = Data(base64Encoded: signature) else { return nil }
|
|
350
|
+
|
|
351
|
+
let algoType = AlgorithmType(rawValue: o["algoType"] as! String)
|
|
352
|
+
let algo = self.toiOSAlgo(algorithm: algoType!)
|
|
353
|
+
|
|
354
|
+
let valid = SecKeyVerifySignature(
|
|
355
|
+
publicKey,
|
|
356
|
+
algo,
|
|
357
|
+
Data(data.utf8) as CFData,
|
|
358
|
+
signatureData as CFData,
|
|
359
|
+
nil
|
|
360
|
+
)
|
|
361
|
+
return valid
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
AsyncFunction("encrypt") { (alias: String, data: String, o: [String: Any]) -> String? in
|
|
365
|
+
let secKey = self.getSecKeyByAlias(alias, keyClass: kSecAttrKeyClassPublic)
|
|
366
|
+
guard let secKey else { return nil }
|
|
367
|
+
let publicKey = SecKeyCopyPublicKey(secKey)!
|
|
368
|
+
|
|
369
|
+
let algoType = AlgorithmType(rawValue: o["algoType"] as! String)
|
|
370
|
+
let algo = self.toiOSAlgo(algorithm: algoType!)
|
|
371
|
+
|
|
372
|
+
guard let encrypted = SecKeyCreateEncryptedData(
|
|
373
|
+
publicKey,
|
|
374
|
+
algo,
|
|
375
|
+
Data(data.utf8) as CFData,
|
|
376
|
+
nil
|
|
377
|
+
) as Data? else {
|
|
378
|
+
return nil
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return encrypted.base64EncodedString()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
AsyncFunction("decrypt") { (alias: String, data: String, o: [String: Any]) -> String? in
|
|
385
|
+
let secKey = self.getSecKeyByAlias(alias, keyClass: kSecAttrKeyClassPrivate)
|
|
386
|
+
guard let secKey else { return nil }
|
|
387
|
+
guard let encryptedData = Data(base64Encoded: data) else { return nil }
|
|
388
|
+
|
|
389
|
+
let algoType = AlgorithmType(rawValue: o["algoType"] as! String)
|
|
390
|
+
let algo = self.toiOSAlgo(algorithm: algoType!)
|
|
391
|
+
|
|
392
|
+
guard let decrypted = SecKeyCreateDecryptedData(
|
|
393
|
+
secKey,
|
|
394
|
+
algo,
|
|
395
|
+
encryptedData as CFData,
|
|
396
|
+
nil
|
|
397
|
+
) as Data? else {
|
|
398
|
+
return nil
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return String(data: decrypted, encoding: .utf8)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "expo-device-crypto",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Crypto for Expo using device hardware capabilities",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "expo-module build",
|
|
9
|
+
"clean": "expo-module clean",
|
|
10
|
+
"lint": "expo-module lint",
|
|
11
|
+
"test": "expo-module test",
|
|
12
|
+
"prepare": "expo-module prepare",
|
|
13
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
14
|
+
"expo-module": "expo-module",
|
|
15
|
+
"open:ios": "xed example/ios",
|
|
16
|
+
"open:android": "open -a \"Android Studio\" example/android"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"expo",
|
|
20
|
+
"expo-module",
|
|
21
|
+
"react-native",
|
|
22
|
+
"native-module",
|
|
23
|
+
"crypto",
|
|
24
|
+
"cryptography",
|
|
25
|
+
"device-crypto",
|
|
26
|
+
"hardware-backed",
|
|
27
|
+
"secure-enclave",
|
|
28
|
+
"android-keystore",
|
|
29
|
+
"ios-keychain",
|
|
30
|
+
"key-management",
|
|
31
|
+
"secure-key-storage",
|
|
32
|
+
"digital-signature",
|
|
33
|
+
"ecdsa",
|
|
34
|
+
"secp256r1",
|
|
35
|
+
"rsa",
|
|
36
|
+
"rsa-pkcs1"
|
|
37
|
+
],
|
|
38
|
+
"repository": "https://github.com/piotr-pietras/expo-device-crypto",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/piotr-pietras/expo-device-crypto/issues"
|
|
41
|
+
},
|
|
42
|
+
"author": "Piotr <pietraspiotr94@gmail.com> (https://github.com/piotr-pietras)",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"homepage": "https://github.com/piotr-pietras/expo-device-crypto#readme",
|
|
45
|
+
"dependencies": {},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"expo-module-scripts": "55.0.2"
|
|
48
|
+
},
|
|
49
|
+
"overrides": {
|
|
50
|
+
"@react-native/dev-middleware": "0.84.1"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"expo": "*"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export enum GenerateKeyPairResult {
|
|
2
|
+
KEY_PAIR_GENERATED = "KEY_PAIR_GENERATED",
|
|
3
|
+
KEY_PAIR_ALREADY_EXISTS = "KEY_PAIR_ALREADY_EXISTS",
|
|
4
|
+
NOT_AVAILABLE = "NOT_AVAILABLE",
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum AuthCheckResult {
|
|
8
|
+
AVAILABLE = "AVAILABLE",
|
|
9
|
+
NO_HARDWARE = "NO_HARDWARE",
|
|
10
|
+
UNAVAILABLE = "UNAVAILABLE",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum AuthMethod {
|
|
14
|
+
PASSCODE = "PASSCODE",
|
|
15
|
+
PASSCODE_OR_BIOMETRIC = "PASSCODE_OR_BIOMETRIC",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export enum SigningAlgorithm {
|
|
19
|
+
ECDSA_SECP256R1_SHA256 = "ECDSA_SECP256R1_SHA256",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export enum EncryptionAlgorithm {
|
|
23
|
+
RSA_2048_PKCS1 = "RSA_2048_PKCS1",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GenerateKeyPairOptions {
|
|
27
|
+
/**
|
|
28
|
+
* The algorithm type of the key to generate.
|
|
29
|
+
*/
|
|
30
|
+
algorithmType: SigningAlgorithm | EncryptionAlgorithm;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to require authentication to sign.
|
|
33
|
+
* Setting this to true will prompt biometric or passcode authentication before signing.
|
|
34
|
+
*
|
|
35
|
+
* Please check the *`isAuthCheckAvailable`* function to see if authentication is available.
|
|
36
|
+
* If authentication is not available, generating a key pair will throw an error.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
requireAuthentication?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The method of authentication to use.
|
|
42
|
+
* Note that on Android you have to define it when signing.
|
|
43
|
+
*
|
|
44
|
+
* If you want to allow to use Face ID, you need to add the following to your app.json config file:
|
|
45
|
+
* ```json
|
|
46
|
+
* "ios": {
|
|
47
|
+
* "infoPlist": {
|
|
48
|
+
* "NSFaceIDUsageDescription": "We use Face ID to secure your data.",
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
* @default AuthMethod.PASSCODE_OR_BIOMETRIC
|
|
53
|
+
* @platform ios
|
|
54
|
+
*/
|
|
55
|
+
authMethod?: AuthMethod;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface BaseSignDecryptOptions {
|
|
59
|
+
/**
|
|
60
|
+
* The title of the prompt to show when authentication is required.
|
|
61
|
+
* @default "Unlock"
|
|
62
|
+
* @platform android
|
|
63
|
+
*/
|
|
64
|
+
promptTitle?: string;
|
|
65
|
+
/**
|
|
66
|
+
* The subtitle of the prompt to show when authentication is required.
|
|
67
|
+
* @default "Enter your PIN to continue"
|
|
68
|
+
* @platform android
|
|
69
|
+
*/
|
|
70
|
+
promptSubtitle?: string;
|
|
71
|
+
/**
|
|
72
|
+
* The method of authentication to use.
|
|
73
|
+
* Note that on iOS you have to define it when generating the key pair.
|
|
74
|
+
* @default AuthMethod.PASSCODE_OR_BIOMETRIC
|
|
75
|
+
* @platform android
|
|
76
|
+
*/
|
|
77
|
+
authMethod?: AuthMethod;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface BaseSigningOptions {
|
|
81
|
+
/**
|
|
82
|
+
* The algorithm type of the key to use for signing.
|
|
83
|
+
*/
|
|
84
|
+
algorithmType?: SigningAlgorithm;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface BaseEncryptionOptions {
|
|
88
|
+
/**
|
|
89
|
+
* The algorithm type of the key to use for encrypting.
|
|
90
|
+
*/
|
|
91
|
+
algorithmType?: EncryptionAlgorithm;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SignOptions extends BaseSignDecryptOptions, BaseSigningOptions {}
|
|
95
|
+
export interface VerifyOptions extends BaseSigningOptions {}
|
|
96
|
+
|
|
97
|
+
export interface DecryptOptions extends BaseSignDecryptOptions, BaseEncryptionOptions {}
|
|
98
|
+
export interface EncryptOptions extends BaseEncryptionOptions {}
|
|
99
|
+
|
|
100
|
+
export interface GetPublicKeyOptions {
|
|
101
|
+
/**
|
|
102
|
+
* The format of the public key to return.
|
|
103
|
+
* @default "PEM" (Base64 of PEM-encoded SubjectPublicKeyInfo (SPKI) for P‑256)
|
|
104
|
+
*/
|
|
105
|
+
format?: "PEM";
|
|
106
|
+
}
|