@pagopa/io-react-native-iso18013 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/IoReactNativeIso18013.podspec +29 -0
- package/LICENSE +20 -0
- package/README.md +30 -0
- package/android/build.gradle +83 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/ioreactnativeiso18013/Base64Utils.kt +56 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeCborModule.kt +209 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Module.kt +439 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Package.kt +17 -0
- package/ios/Base64Utils.swift +47 -0
- package/ios/IoReactNativeCbor.mm +32 -0
- package/ios/IoReactNativeCbor.swift +188 -0
- package/ios/IoReactNativeIso18013-Bridging-Header.h +2 -0
- package/ios/IoReactNativeIso18013.mm +43 -0
- package/ios/IoReactNativeIso18013.swift +491 -0
- package/lib/module/cbor/cbor/README.md +95 -0
- package/lib/module/cbor/cbor/decoder.js +51 -0
- package/lib/module/cbor/cbor/decoder.js.map +1 -0
- package/lib/module/cbor/cbor/failure.js +2 -0
- package/lib/module/cbor/cbor/failure.js.map +1 -0
- package/lib/module/cbor/cbor/index.js +5 -0
- package/lib/module/cbor/cbor/index.js.map +1 -0
- package/lib/module/cbor/cbor/schema.js +117 -0
- package/lib/module/cbor/cbor/schema.js.map +1 -0
- package/lib/module/cbor/cbor/schema.utils.js +16 -0
- package/lib/module/cbor/cbor/schema.utils.js.map +1 -0
- package/lib/module/cbor/cose/README.md +53 -0
- package/lib/module/cbor/cose/failure.js +2 -0
- package/lib/module/cbor/cose/failure.js.map +1 -0
- package/lib/module/cbor/cose/index.js +4 -0
- package/lib/module/cbor/cose/index.js.map +1 -0
- package/lib/module/cbor/cose/sign.js +23 -0
- package/lib/module/cbor/cose/sign.js.map +1 -0
- package/lib/module/cbor/index.js +13 -0
- package/lib/module/cbor/index.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/iso18013/index.js +14 -0
- package/lib/module/iso18013/index.js.map +1 -0
- package/lib/module/iso18013/iso18013-5/README.md +234 -0
- package/lib/module/iso18013/iso18013-5/index.js +5 -0
- package/lib/module/iso18013/iso18013-5/index.js.map +1 -0
- package/lib/module/iso18013/iso18013-5/proximity.js +112 -0
- package/lib/module/iso18013/iso18013-5/proximity.js.map +1 -0
- package/lib/module/iso18013/iso18013-5/schema.js +79 -0
- package/lib/module/iso18013/iso18013-5/schema.js.map +1 -0
- package/lib/module/iso18013/iso18013-7/README.md +59 -0
- package/lib/module/iso18013/iso18013-7/index.js +4 -0
- package/lib/module/iso18013/iso18013-7/index.js.map +1 -0
- package/lib/module/iso18013/iso18013-7/remote.js +31 -0
- package/lib/module/iso18013/iso18013-7/remote.js.map +1 -0
- package/lib/module/iso18013/types.js +2 -0
- package/lib/module/iso18013/types.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/cbor/cbor/decoder.d.ts +35 -0
- package/lib/typescript/src/cbor/cbor/decoder.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/failure.d.ts +15 -0
- package/lib/typescript/src/cbor/cbor/failure.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/index.d.ts +4 -0
- package/lib/typescript/src/cbor/cbor/index.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/schema.d.ts +2420 -0
- package/lib/typescript/src/cbor/cbor/schema.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/schema.utils.d.ts +8 -0
- package/lib/typescript/src/cbor/cbor/schema.utils.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cose/failure.d.ts +15 -0
- package/lib/typescript/src/cbor/cose/failure.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cose/index.d.ts +3 -0
- package/lib/typescript/src/cbor/cose/index.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cose/sign.d.ts +19 -0
- package/lib/typescript/src/cbor/cose/sign.d.ts.map +1 -0
- package/lib/typescript/src/cbor/index.d.ts +2 -0
- package/lib/typescript/src/cbor/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/index.d.ts +3 -0
- package/lib/typescript/src/iso18013/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-5/index.d.ts +4 -0
- package/lib/typescript/src/iso18013/iso18013-5/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts +88 -0
- package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts +85 -0
- package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-7/index.d.ts +3 -0
- package/lib/typescript/src/iso18013/iso18013-7/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts +29 -0
- package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/types.d.ts +16 -0
- package/lib/typescript/src/iso18013/types.d.ts.map +1 -0
- package/package.json +163 -0
- package/src/cbor/cbor/README.md +95 -0
- package/src/cbor/cbor/decoder.ts +56 -0
- package/src/cbor/cbor/failure.ts +18 -0
- package/src/cbor/cbor/index.ts +9 -0
- package/src/cbor/cbor/schema.ts +150 -0
- package/src/cbor/cbor/schema.utils.ts +13 -0
- package/src/cbor/cose/README.md +53 -0
- package/src/cbor/cose/failure.ts +20 -0
- package/src/cbor/cose/index.ts +2 -0
- package/src/cbor/cose/sign.ts +25 -0
- package/src/cbor/index.ts +18 -0
- package/src/index.tsx +5 -0
- package/src/iso18013/index.ts +25 -0
- package/src/iso18013/iso18013-5/README.md +234 -0
- package/src/iso18013/iso18013-5/index.ts +23 -0
- package/src/iso18013/iso18013-5/proximity.ts +134 -0
- package/src/iso18013/iso18013-5/schema.ts +87 -0
- package/src/iso18013/iso18013-7/README.md +59 -0
- package/src/iso18013/iso18013-7/index.ts +7 -0
- package/src/iso18013/iso18013-7/remote.ts +54 -0
- package/src/iso18013/types.ts +16 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import IOWalletProximity
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
@objc(IoReactNativeIso18013)
|
|
6
|
+
class IoReactNativeProximity: RCTEventEmitter {
|
|
7
|
+
|
|
8
|
+
private typealias ME = ModuleException
|
|
9
|
+
|
|
10
|
+
@objc
|
|
11
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override init() {
|
|
16
|
+
super.init()
|
|
17
|
+
setupProximityHandler()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
Specifies supported events which will be emitted.
|
|
22
|
+
- `onDeviceConnecting`: Emitted when the device is connecting to the verifier app.
|
|
23
|
+
- `onDeviceConnected`: Emitted when the device is connected to the verifier app.
|
|
24
|
+
- `onDocumentRequestReceived`: Emitted when a document request is received from the verifier app. Carries a payload containing the request data.
|
|
25
|
+
- `onDeviceDisconnected`: Emitted when the device is disconnected from the verifier app.
|
|
26
|
+
- `onError`: Emitted when an error occurs. Carries a payload containing the error data.
|
|
27
|
+
*/
|
|
28
|
+
override func supportedEvents() -> [String]! {
|
|
29
|
+
return ["onDeviceConnected", "onDeviceConnecting", "onDeviceDisconnected", "onDocumentRequestReceived", "onError", "unknown"]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
Type alias for the accepted fields during the presentation. These are the fields which the user accepted to share.
|
|
34
|
+
It can be fed to the ``IOWalletProximity.generateResponse`` function.
|
|
35
|
+
An example might be:
|
|
36
|
+
`["org.iso.18013.5.1.mDL": ["org.iso.18013.5.1": ["hair_colour": true, "given_name_national_character": true, "family_name_national_character": true, "given_name": true]]]`
|
|
37
|
+
*/
|
|
38
|
+
typealias AcceptedFieldsDict = [String: [String: [String: Bool]]]
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
Starts the proximity flow by allocating the necessary resources and initializing the Bluetooth stack.
|
|
42
|
+
Resolves to true or rejects if an error occurs.
|
|
43
|
+
|
|
44
|
+
- Parameters:
|
|
45
|
+
- certificates: Array of base64 representing DER encoded X.509 certificate which are used to authenticate the verifier app
|
|
46
|
+
- resolve: The promise to be resolved
|
|
47
|
+
- reject: The promise to be rejected
|
|
48
|
+
*/
|
|
49
|
+
@objc(start:withResolver:withRejecter:)
|
|
50
|
+
func start(
|
|
51
|
+
certificates: Array<Any>,
|
|
52
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
53
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
54
|
+
){
|
|
55
|
+
do {
|
|
56
|
+
let certsData = parseCertificates(certificates)
|
|
57
|
+
try Proximity.shared.start(certsData.isEmpty ? nil : certsData)
|
|
58
|
+
resolve(true)
|
|
59
|
+
} catch let error {
|
|
60
|
+
ME.startError.reject(reject: reject, ("error", error.localizedDescription))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
Utility function to parse an array coming from the React Native Bridge into an array of Data representing DER encoded X.509 certificates.
|
|
66
|
+
|
|
67
|
+
- Parameters:
|
|
68
|
+
- certificates:Array of base64 strings representing DER encoded X.509 certificate
|
|
69
|
+
|
|
70
|
+
- Returns: An array of Data containing DER ecnoded X.509 certificates.
|
|
71
|
+
*/
|
|
72
|
+
private func parseCertificates(_ certificates: [Any]) -> [Data] {
|
|
73
|
+
return certificates.compactMap { item in
|
|
74
|
+
guard let certString = item as? String,
|
|
75
|
+
let data = Data(base64Encoded: certString) else {
|
|
76
|
+
return nil
|
|
77
|
+
}
|
|
78
|
+
return data
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
Creates a QR code to be scanned in order to initialize the presentation.
|
|
84
|
+
Resolves with the QR code strings.
|
|
85
|
+
|
|
86
|
+
- Parameters:
|
|
87
|
+
- resolve: The promise to be resolved
|
|
88
|
+
- reject: The promise to be rejected
|
|
89
|
+
*/
|
|
90
|
+
@objc(getQrCodeString:withRejecter:)
|
|
91
|
+
func getQrCodeString(
|
|
92
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
93
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
94
|
+
) {
|
|
95
|
+
do{
|
|
96
|
+
let qrCodeString = try Proximity.shared.getQrCode()
|
|
97
|
+
resolve(qrCodeString)
|
|
98
|
+
} catch let error {
|
|
99
|
+
ME.qrCodeError.reject(reject: reject, ("error", error.localizedDescription))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
Parses an array of documents from the React Native bridge which doesn't have any typing to an array of ``IOWalletProximity.ProximityDocument``.
|
|
105
|
+
It checks if each element in the array has `issuerSignedContent`, `alias` and `docType` properties in order to build a ``IOWalletProximity.ProximityDocument``, then it appens it to the array.
|
|
106
|
+
The result can be fed to ``IOWalletProximity.generateResponse``.
|
|
107
|
+
|
|
108
|
+
- Parameters:
|
|
109
|
+
- documents: An array containing documents. Each document is defined as a map containing:
|
|
110
|
+
- issuerSignedContent which is a base64 or base64url encoded string representing the credential;
|
|
111
|
+
- alias which is the alias of the key used to sign the credential;
|
|
112
|
+
- docType which is the document type.
|
|
113
|
+
|
|
114
|
+
- Throws: `ModuleException.invalidDocRequested.error()` and `ModuleException.invalidDocRequested.error()` iif an error occurs while parsing the document
|
|
115
|
+
|
|
116
|
+
- Returns: An array of `ProximityDocument` containg the documents to be presented
|
|
117
|
+
*/
|
|
118
|
+
private func parseDocuments(documents: [Any]) throws -> [ProximityDocument] {
|
|
119
|
+
return try documents.compactMap{ (element) -> ProximityDocument in
|
|
120
|
+
guard let dict : [String: Any] = element as? [String: Any] else { throw ModuleException.unableToDecode.error() }
|
|
121
|
+
guard
|
|
122
|
+
let issuerSignedContent = dict["issuerSignedContent"] as? String,
|
|
123
|
+
let alias = dict["alias"] as? String,
|
|
124
|
+
let docType = dict["docType"] as? String,
|
|
125
|
+
let decodedIssuerSignedContent = try? Base64Utils.decodeBase64OrBase64URL(base: issuerSignedContent)
|
|
126
|
+
else { throw ModuleException.invalidDocRequested.error() }
|
|
127
|
+
|
|
128
|
+
guard let document = ProximityDocument(
|
|
129
|
+
docType: docType,
|
|
130
|
+
issuerSigned: [UInt8](decodedIssuerSignedContent),
|
|
131
|
+
deviceKeyTag: alias
|
|
132
|
+
) else {
|
|
133
|
+
throw ModuleException.invalidDocRequested.error()
|
|
134
|
+
}
|
|
135
|
+
return document
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
Parses a dictionary of accepted fields for the presentation from the React Native bridge which doesn't have any typing to a ``AcceptedFieldsDict`` dictionary.
|
|
141
|
+
It checks if each element in the array is a dictionary where the key is a string, and the value is another dictionary. This nested dictionary has a string as its key and a boolean as its value, then it appens it to the array.
|
|
142
|
+
The result can be fed to ``IOWalletProximity.generateResponse``.
|
|
143
|
+
|
|
144
|
+
- Parameters:
|
|
145
|
+
- acceptedFields: A dictionary of any elements. In order to be added to the result dictionary each element must be shaped as ``AcceptedFieldsDict`` thus as [String: [String: [String: Bool]]]
|
|
146
|
+
|
|
147
|
+
- Throws: `NSError` if a value doesn't has the ``AcceptedFieldsDict`` shape or the result dictionary is empty
|
|
148
|
+
|
|
149
|
+
- Returns: An ``AcceptedFieldsDict`` containg the accepted fields to be presented
|
|
150
|
+
|
|
151
|
+
*/
|
|
152
|
+
private func parseAcceptedFields(acceptedFields: [AnyHashable: Any]) throws -> AcceptedFieldsDict {
|
|
153
|
+
var result: AcceptedFieldsDict = [:]
|
|
154
|
+
|
|
155
|
+
for (key, value) in acceptedFields {
|
|
156
|
+
guard let keyString = key as? String else {
|
|
157
|
+
throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Key must be a String."])
|
|
158
|
+
}
|
|
159
|
+
guard let valueDict = value as? [String: [String: Bool]] else {
|
|
160
|
+
throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Value must be of type [String: [String: Bool]]."])
|
|
161
|
+
}
|
|
162
|
+
result[keyString] = valueDict
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if(result.isEmpty){
|
|
166
|
+
throw NSError(domain: "ParseAcceptedFields", code: -1, userInfo: [NSLocalizedDescriptionKey: "Error parsing documents"])
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
Generates a response containing the documents and the fields which the user decided to present.
|
|
174
|
+
It parses the untyped ``documents`` and ``acceptedFields`` parameters and feeds them to the ``IOWalletProximity.generateDeviceResponse`` function.
|
|
175
|
+
It resolves the promise with the response as a base64 encoded string.
|
|
176
|
+
It rejects the promise if an error occurs during the parameters parsing or while generating the device response.
|
|
177
|
+
|
|
178
|
+
- Parameters:
|
|
179
|
+
- documents: An array containing documents. Each document is defined as a map containing:
|
|
180
|
+
- issuerSignedContent which is a base64 or base64url encoded string representing the credential;
|
|
181
|
+
- alias which is the alias of the key used to sign the credential;
|
|
182
|
+
- docType which is the document type.
|
|
183
|
+
- issuerSignedContent which is a base64 or base64url encoded string representing the credential;
|
|
184
|
+
- alias which is the alias of the key used to sign the credential;
|
|
185
|
+
- docType which is the document type.
|
|
186
|
+
- acceptedFields: A dictionary of elements, where each element must adhere to the structure of AcceptedFieldsDict—specifically, a [String: [String: [String: Bool]]]. The outermost key represents the credentia doctypel. The inner dictionary contains namespaces, and for each namespace, there is another dictionary mapping requested claims to a boolean value, which indicates whether the user is willing to present the corresponding claim. Example:
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
"org.iso.18013.5.1.mDL": {
|
|
191
|
+
"org.iso.18013.5.1": {
|
|
192
|
+
"hair_colour": true,
|
|
193
|
+
"given_name_national_character": true,
|
|
194
|
+
"family_name_national_character": true,
|
|
195
|
+
"given_name": true,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- resolve: The promise to be resolved
|
|
201
|
+
- reject: The promise to be rejected
|
|
202
|
+
|
|
203
|
+
*/
|
|
204
|
+
@objc(generateResponse:withAcceptedFields:withResolver:withRejecter:)
|
|
205
|
+
func generateResponse(
|
|
206
|
+
documents: Array<Any>,
|
|
207
|
+
acceptedFields: [AnyHashable: Any],
|
|
208
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
209
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
210
|
+
){
|
|
211
|
+
do {
|
|
212
|
+
let parsedDocuments = try parseDocuments(documents: documents)
|
|
213
|
+
let items = try parseAcceptedFields(acceptedFields: acceptedFields)
|
|
214
|
+
let deviceResponse = try Proximity.shared.generateDeviceResponse(items: items, documents: parsedDocuments, sessionTranscript: nil)
|
|
215
|
+
let strDeviceResponse = Data(deviceResponse).base64EncodedString()
|
|
216
|
+
resolve(strDeviceResponse)
|
|
217
|
+
}catch{
|
|
218
|
+
ME.generateDeviceResponseError.reject(reject: reject, ("error", error.localizedDescription))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
Sends a response containing the documents and the fields which the user decided to present generated by ``generateResponse``.
|
|
224
|
+
It resolves to true after sending the response, otherwise it rejects if an error occurs while decoding the response.
|
|
225
|
+
Currently there's not evidence of the verifier app responding to this request, thus we don't handle the response.
|
|
226
|
+
|
|
227
|
+
- Parameters:
|
|
228
|
+
- response: A base64 encoded string containing the response generated by ``generateResponse``
|
|
229
|
+
- resolve: The promise to be resolved
|
|
230
|
+
- reject: The promise to be rejected
|
|
231
|
+
*/
|
|
232
|
+
@objc(sendResponse:withResolver:withRejecter:)
|
|
233
|
+
func sendResponse(
|
|
234
|
+
response: String,
|
|
235
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
236
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
237
|
+
){
|
|
238
|
+
do{
|
|
239
|
+
if let responseData = Data(base64Encoded: response) {
|
|
240
|
+
let decodedResponse = [UInt8](responseData)
|
|
241
|
+
try Proximity.shared.dataPresentation(decodedResponse)
|
|
242
|
+
resolve(true)
|
|
243
|
+
}
|
|
244
|
+
}catch let error {
|
|
245
|
+
ME.sendResponseError.reject(reject: reject, ("error", error.localizedDescription))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
Sends an error response during the presentation according to the SessionData status codes defined in table 20 of the ISO18013-5 standard.
|
|
251
|
+
- Parameters:
|
|
252
|
+
- status: The status error to be sent is an integer of type ``SessionDataStatus``:
|
|
253
|
+
```
|
|
254
|
+
10 -> Error: session encryption
|
|
255
|
+
11 -> Error: CBOR decoding
|
|
256
|
+
20 -> Session termination
|
|
257
|
+
```
|
|
258
|
+
- resolve: The promise to be resolved
|
|
259
|
+
- reject: The promise to be rejected
|
|
260
|
+
*/
|
|
261
|
+
@objc(sendErrorResponse:withResolver:withRejecter:)
|
|
262
|
+
func sendErrorResponse(status: UInt64, _ resolve: @escaping RCTPromiseResolveBlock,
|
|
263
|
+
reject: @escaping RCTPromiseRejectBlock){
|
|
264
|
+
do{
|
|
265
|
+
if let statusEnum = SessionDataStatus(rawValue: status) {
|
|
266
|
+
try Proximity.shared.errorPresentation(statusEnum)
|
|
267
|
+
} else {
|
|
268
|
+
ME.sendResponseError.reject(reject: reject, ("error", "Invalid status code"))
|
|
269
|
+
}
|
|
270
|
+
resolve(true)
|
|
271
|
+
}catch let error{
|
|
272
|
+
ME.sendResponseError.reject(reject: reject, ("error", error.localizedDescription))
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
Closes the bluetooth connection and clears any resource.
|
|
279
|
+
It resolves to true after closing the connection.
|
|
280
|
+
|
|
281
|
+
- Parameters:
|
|
282
|
+
- resolve: The promise to be resolved
|
|
283
|
+
- reject: The promise to be rejected
|
|
284
|
+
|
|
285
|
+
*/
|
|
286
|
+
@objc(close:withRejecter:)
|
|
287
|
+
func close(
|
|
288
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
289
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
290
|
+
) {
|
|
291
|
+
Proximity.shared.stop()
|
|
292
|
+
resolve(true)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
Converts a device requested from the `onDocumentRequestReceived` callback into a serializable JSON.
|
|
297
|
+
|
|
298
|
+
- Parameters:
|
|
299
|
+
- request: The request returned from `onDocumentRequestReceived` which contains an array of tuples consists of a doctype, namespaces and the requested claims with a boolean value indicating wether or not the device which is making the request has an intent to retain the dataß
|
|
300
|
+
|
|
301
|
+
- Returns: A JSON string representing the device request or nil if an error occurs
|
|
302
|
+
*/
|
|
303
|
+
private func deviceRequestToJson(request: [(docType: String, nameSpaces: [String: [String: Bool]], isAuthenticated: Bool)]?) -> String? {
|
|
304
|
+
var jsonRequest : [String: AnyHashable] = [:]
|
|
305
|
+
request?.forEach({
|
|
306
|
+
item in
|
|
307
|
+
var subReq: [String: AnyHashable] = [:]
|
|
308
|
+
item.nameSpaces.keys.forEach({
|
|
309
|
+
nameSpace in
|
|
310
|
+
subReq[nameSpace] = item.nameSpaces[nameSpace]
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
subReq["isAuthenticated"] = item.isAuthenticated
|
|
314
|
+
|
|
315
|
+
jsonRequest[item.docType] = subReq
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
let json: [String: AnyHashable] = [
|
|
319
|
+
"request": jsonRequest
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted),
|
|
323
|
+
let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
324
|
+
return jsonString
|
|
325
|
+
} else {
|
|
326
|
+
return nil
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
Sets the proximity handler along with the possible dispatched events and their callbacks.
|
|
332
|
+
The events are then sent to React Native via `RCTEventEmitter`.
|
|
333
|
+
*/
|
|
334
|
+
private func setupProximityHandler() {
|
|
335
|
+
Proximity.shared.proximityHandler = { [weak self] event in
|
|
336
|
+
guard let self = self else { return }
|
|
337
|
+
var eventName: String
|
|
338
|
+
var eventBody: [String: Any] = [:]
|
|
339
|
+
|
|
340
|
+
switch event {
|
|
341
|
+
case .onDeviceConnecting:
|
|
342
|
+
eventName = "onDeviceConnecting"
|
|
343
|
+
case .onDeviceConnected:
|
|
344
|
+
eventName = "onDeviceConnected"
|
|
345
|
+
case .onDocumentRequestReceived(let request):
|
|
346
|
+
eventName = "onDocumentRequestReceived"
|
|
347
|
+
if let request = request {
|
|
348
|
+
/**
|
|
349
|
+
The outermost key represents the credential doctype, the inner key represents the namespace and the innermost key represents the requested fields with a boolean value. Example:
|
|
350
|
+
{
|
|
351
|
+
"org.iso.18013.5.1.mDL": {
|
|
352
|
+
"isAuthenticated": true,
|
|
353
|
+
"org.iso.18013.5.1": {
|
|
354
|
+
"hair_colour": true,
|
|
355
|
+
"given_name_national_character": true,
|
|
356
|
+
"family_name_national_character": true,
|
|
357
|
+
"given_name": true,
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
*/
|
|
362
|
+
let jsonString = deviceRequestToJson(request: request)
|
|
363
|
+
// Here we either send the request or an empty string which signals that something went wrong.
|
|
364
|
+
eventBody = ["data": jsonString ?? ""]
|
|
365
|
+
}
|
|
366
|
+
case .onDeviceDisconnected:
|
|
367
|
+
eventName = "onDeviceDisconnected"
|
|
368
|
+
case .onError(let error):
|
|
369
|
+
eventName = "onError"
|
|
370
|
+
eventBody = ["error": error.localizedDescription]
|
|
371
|
+
default:
|
|
372
|
+
eventName = "unknown"
|
|
373
|
+
eventBody = ["error": "Received an unknown event"]
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
self.sendEvent(withName: eventName, body: eventBody)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@objc func generateOID4VPDeviceResponse(
|
|
381
|
+
_ clientId: String,
|
|
382
|
+
responseUri: String,
|
|
383
|
+
authorizationRequestNonce: String,
|
|
384
|
+
mdocGeneratedNonce: String,
|
|
385
|
+
documents: [Any],
|
|
386
|
+
fieldRequestedAndAccepted: String,
|
|
387
|
+
resolver resolve: RCTPromiseResolveBlock,
|
|
388
|
+
rejecter reject: RCTPromiseRejectBlock
|
|
389
|
+
) {
|
|
390
|
+
|
|
391
|
+
do {
|
|
392
|
+
let sessionTranscript = Proximity.shared.generateOID4VPSessionTranscriptCBOR(
|
|
393
|
+
clientId: clientId,
|
|
394
|
+
responseUri: responseUri,
|
|
395
|
+
authorizationRequestNonce: authorizationRequestNonce,
|
|
396
|
+
mdocGeneratedNonce: mdocGeneratedNonce
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
let documentsAsProximityDocument = try parseDocuments(documents: documents)
|
|
400
|
+
|
|
401
|
+
let items = try JSONDecoder().decode([String : [String : [String : Bool]]].self, from: Data(fieldRequestedAndAccepted.utf8))
|
|
402
|
+
|
|
403
|
+
do {
|
|
404
|
+
let response = try Proximity.shared.generateDeviceResponse(items: items, documents: documentsAsProximityDocument, sessionTranscript: sessionTranscript)
|
|
405
|
+
resolve(Data(response).base64EncodedString())
|
|
406
|
+
} catch {
|
|
407
|
+
ME.unableToGenerateResponse.reject(reject: reject)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
} catch let error as NSError where error.domain == ME.invalidDocRequested.rawValue {
|
|
411
|
+
ME.invalidDocRequested.reject(reject: reject)
|
|
412
|
+
} catch _ as DecodingError {
|
|
413
|
+
ME.invalidItems.reject(reject: reject)
|
|
414
|
+
} catch {
|
|
415
|
+
ME.unexpected.reject(reject: reject)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private class DocRequested {
|
|
420
|
+
var issuerSignedContent : [UInt8]
|
|
421
|
+
var alias : String
|
|
422
|
+
var docType : String
|
|
423
|
+
|
|
424
|
+
public init(issuerSignedContent: [UInt8], alias: String, docType: String) {
|
|
425
|
+
self.issuerSignedContent = issuerSignedContent
|
|
426
|
+
self.alias = alias
|
|
427
|
+
self.docType = docType
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
Wrapper for rejecting with an error.
|
|
433
|
+
Add a new case in order to extend the possible errors.
|
|
434
|
+
*/
|
|
435
|
+
private enum ModuleException: String, CaseIterable {
|
|
436
|
+
case startError = "START_ERROR"
|
|
437
|
+
case qrCodeError = "QR_CODE_ERROR"
|
|
438
|
+
case generateDeviceResponseError = "GENERATE_DEVICE_RESPONSE_ERROR"
|
|
439
|
+
case sendResponseError = "SEND_RESPONSE_ERROR"
|
|
440
|
+
case sendErrorResponse = "SEND_ERROR_RESPONSE_ERROR"
|
|
441
|
+
case invalidDocRequested = "DOC_REQUESTED_PARSING_ERROR"
|
|
442
|
+
case unableToGenerateResponse = "UNABLE_TO_GENERATE_RESPONSE_ERROR"
|
|
443
|
+
case invalidItems = "REQUESTED_ITEMS_PARSING_ERROR"
|
|
444
|
+
case unexpected = "UNEXPECTED_ERROR"
|
|
445
|
+
case unableToDecode = "UNABLE_TO_DECODE"
|
|
446
|
+
|
|
447
|
+
func error(
|
|
448
|
+
userInfo: [String : Any]? = nil
|
|
449
|
+
) -> NSError {
|
|
450
|
+
switch self {
|
|
451
|
+
case .startError:
|
|
452
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
453
|
+
case .qrCodeError:
|
|
454
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
455
|
+
case .generateDeviceResponseError:
|
|
456
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
457
|
+
case .sendResponseError:
|
|
458
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
459
|
+
case .sendErrorResponse:
|
|
460
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
461
|
+
case .invalidDocRequested:
|
|
462
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
463
|
+
case .unableToGenerateResponse:
|
|
464
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
465
|
+
case .invalidItems:
|
|
466
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
467
|
+
case .unexpected:
|
|
468
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
469
|
+
case .unableToDecode:
|
|
470
|
+
return NSError(domain: self.rawValue, code: -1, userInfo: userInfo)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
Rejects the provided promise with the appropriate error message and additional data.
|
|
476
|
+
|
|
477
|
+
- Parameters:
|
|
478
|
+
- reject: the promise to be rejected.
|
|
479
|
+
- moreUserInfo: additional key-value pairs of data to be passed along with the error.
|
|
480
|
+
|
|
481
|
+
*/
|
|
482
|
+
func reject(
|
|
483
|
+
reject: RCTPromiseRejectBlock,
|
|
484
|
+
_ moreUserInfo: (String, Any)...
|
|
485
|
+
) {
|
|
486
|
+
let userInfo = [String: Any](uniqueKeysWithValues: moreUserInfo)
|
|
487
|
+
let error = error(userInfo: userInfo)
|
|
488
|
+
reject("\(error.code)", error.domain, error)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
## CBOR
|
|
2
|
+
|
|
3
|
+
This module provides methods to decode CBOR data into readable objects.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { CBOR } from '@pagopa/io-react-native-cbor';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Methods
|
|
10
|
+
|
|
11
|
+
#### `decode`
|
|
12
|
+
|
|
13
|
+
This method allows to decode CBOR data into readable JSON objects.
|
|
14
|
+
Returns a `Promise` which resolves to a JSON object, or rejects with an instance of `CborFailure` in case of failure.
|
|
15
|
+
|
|
16
|
+
**Note**: this method does not decode nested CBOR objects and therefore complex objects needs additional manual decoding
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
try {
|
|
20
|
+
const decoded = await CBOR.decode('...');
|
|
21
|
+
} catch (e) {
|
|
22
|
+
const { message, userInfo } = e as CborFailure;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### `decodeDocuments`
|
|
27
|
+
|
|
28
|
+
This metod allows the decoding of CBOR data which contains MDOC objects.
|
|
29
|
+
Returns a promise wich resolves to a [Documents](#documents) object, or rejects with an instance of `CborFailure` in case of failure.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
try {
|
|
33
|
+
const decoded = await CBOR.decodeDocuments('...');
|
|
34
|
+
} catch (e) {
|
|
35
|
+
const { message, userInfo } = e as CborFailure;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Types
|
|
40
|
+
|
|
41
|
+
#### `Documents`
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
type Documents = {
|
|
45
|
+
status?: number;
|
|
46
|
+
version?: string;
|
|
47
|
+
documents?: Array<MDOC>;
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### `MDOC`
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
type MDOC = {
|
|
55
|
+
docType?: DocumentType;
|
|
56
|
+
issuerSigned?: IssuerSigned;
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### `IssuerSigned`
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
type IssuerSigned = {
|
|
64
|
+
nameSpaces?: Record<string, Array<DocumentValue>>;
|
|
65
|
+
issuerAuth?: string;
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### `DocumentValue`
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
type DocumentValue = {
|
|
73
|
+
digestID?: number;
|
|
74
|
+
random?: string;
|
|
75
|
+
elementIdentifier?: string;
|
|
76
|
+
elementValue?: string;
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### `DocumentType`
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
enum DocumentTypeEnum {
|
|
84
|
+
MDL = 'org.iso.18013.5.1.mDL',
|
|
85
|
+
EU_PID = 'eu.europa.ec.eudi.pid.1',
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Error Codes
|
|
90
|
+
|
|
91
|
+
| Type | Platform | Description |
|
|
92
|
+
| ----------------- | ----------- | --------------------------------------------- |
|
|
93
|
+
| INVALID_ENCODING | Android/iOS | Provided payload has incorrect encoding |
|
|
94
|
+
| UNABLE_TO_DECODE | Android/iOS | The data does not contain a valid CBOR object |
|
|
95
|
+
| UNKNOWN_EXCEPTION | Android/iOS | Unexpected failure |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { IoReactNativeCbor } from "../index.js";
|
|
4
|
+
import { DocumentsFromString, IssuerSignedFromString } from "./schema.js";
|
|
5
|
+
import { coerceToJSON } from "./schema.utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Decode base64 encoded CBOR data to JSON object.
|
|
9
|
+
*
|
|
10
|
+
* If it is not possibile to decode the provided data, the promise will be rejected with
|
|
11
|
+
* an instance of {@link CborFailure}.
|
|
12
|
+
*
|
|
13
|
+
* **NOTE**: this method does not handle nested CBOR data, which will need additional
|
|
14
|
+
* parsing.
|
|
15
|
+
*
|
|
16
|
+
* @param data - The base64 or base64url encoded CBOR data
|
|
17
|
+
* @returns The decoded data as JSON object
|
|
18
|
+
*/
|
|
19
|
+
export const decode = async data => {
|
|
20
|
+
const jsonString = await IoReactNativeCbor.decode(data);
|
|
21
|
+
return await coerceToJSON.parseAsync(jsonString);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decode base64 or base64url encoded CBOR data to mDOC object
|
|
26
|
+
*
|
|
27
|
+
* If it is not possibile to decode the provided data, the promise will be rejected with
|
|
28
|
+
* an instance of {@link CborFailure}.
|
|
29
|
+
*
|
|
30
|
+
* @param data - The base64 encoded MDOC data
|
|
31
|
+
* @returns The decoded data as mDOC object
|
|
32
|
+
*/
|
|
33
|
+
export const decodeDocuments = async data => {
|
|
34
|
+
const documentsString = await IoReactNativeCbor.decodeDocuments(data);
|
|
35
|
+
return await DocumentsFromString.parseAsync(documentsString);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract and decode the {@link IssuerSigned} with the {@link IssuerAuth} decoded from base64 encoded CBOR
|
|
40
|
+
*
|
|
41
|
+
* If it is not possibile to decode the provided data, the promise will be rejected with
|
|
42
|
+
* an instance of {@link CborFailure}.
|
|
43
|
+
*
|
|
44
|
+
* @param issuerSigned - The base64 or base64url encoded MDOC data
|
|
45
|
+
* @returns The decoded {@link IssuerSigned} contained in the mDOC object
|
|
46
|
+
*/
|
|
47
|
+
export const decodeIssuerSigned = async issuerSigned => {
|
|
48
|
+
const decodedIssuerSignedString = await IoReactNativeCbor.decodeIssuerSigned(issuerSigned);
|
|
49
|
+
return await IssuerSignedFromString.parseAsync(decodedIssuerSignedString);
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["IoReactNativeCbor","DocumentsFromString","IssuerSignedFromString","coerceToJSON","decode","data","jsonString","parseAsync","decodeDocuments","documentsString","decodeIssuerSigned","issuerSigned","decodedIssuerSignedString"],"sourceRoot":"../../../../src","sources":["cbor/cbor/decoder.ts"],"mappings":";;AAAA,SAASA,iBAAiB,QAAQ,aAAI;AACtC,SAEEC,mBAAmB,EAEnBC,sBAAsB,QACjB,aAAU;AACjB,SAASC,YAAY,QAAQ,mBAAgB;;AAE7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,MAAM,GAAG,MAAOC,IAAY,IAAmB;EAC1D,MAAMC,UAAU,GAAG,MAAMN,iBAAiB,CAACI,MAAM,CAACC,IAAI,CAAC;EACvD,OAAO,MAAMF,YAAY,CAACI,UAAU,CAACD,UAAU,CAAC;AAClD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAME,eAAe,GAAG,MAAOH,IAAY,IAAyB;EACzE,MAAMI,eAAe,GAAG,MAAMT,iBAAiB,CAACQ,eAAe,CAACH,IAAI,CAAC;EACrE,OAAO,MAAMJ,mBAAmB,CAACM,UAAU,CAACE,eAAe,CAAC;AAC9D,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,kBAAkB,GAAG,MAChCC,YAAoB,IACM;EAC1B,MAAMC,yBAAyB,GAC7B,MAAMZ,iBAAiB,CAACU,kBAAkB,CAACC,YAAY,CAAC;EAC1D,OAAO,MAAMT,sBAAsB,CAACK,UAAU,CAACK,yBAAyB,CAAC;AAC3E,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../../src","sources":["cbor/cbor/failure.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["decode","decodeDocuments","decodeIssuerSigned","DocumentValue","Documents","DocumentsFromString","IssuerSigned","MDOC"],"sourceRoot":"../../../../src","sources":["cbor/cbor/index.ts"],"mappings":";;AAAA,SAASA,MAAM,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,cAAW;AACvE,SACEC,aAAa,EACbC,SAAS,EACTC,mBAAmB,EACnBC,YAAY,EACZC,IAAI,QACC,aAAU","ignoreList":[]}
|