@@ -26,13 +26,13 @@ This document describes the functionalitiy and structure of OpenHealthCardKit.
2626Generated API docs are available at https://gematik.github.io/ref-OpenHealthCardKit.
2727== Getting Started
2828
29- OpenHealthCardKit requires Swift 5.1 .
29+ OpenHealthCardKit requires Swift 5.6 .
3030
3131=== Setup for integration
3232
3333- **Swift Package Manager:** Put this in your `Package.swift`:
3434
35- `.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.3 .0"),`
35+ `.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.6 .0"),`
3636
3737- **Carthage:** Put this in your `Cartfile`:
3838
@@ -116,29 +116,30 @@ let eSign = EgkFileSystem.DF.ESIGN
116116let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
117117----
118118
119- ===== Setting an execution target
119+ ===== Command execution
120120
121121We execute the created command `CardType` instance which has been typically provided by a `CardReaderType`.
122122
123123In the next example we use a `HealthCard` object representing an eGK (elektronische Gesundheitskarte)
124- as one kind of a `HealthCardType` implementing the `CardType` protocol.
125-
124+ as one kind of a `HealthCardType` implementing the `CardType` protocol and then send the command to the card (or card's channel):
126125[source,swift]
127126----
128- // initialize your CardReaderType instance
129- let cardReader: CardReaderType = CardSimulationTerminalTestCase.reader
130- let card = try cardReader.connect([:])!
131- let healthCardStatus = HealthCardStatus.valid(cardType: .egk(generation: .g2))
132- let eGk = try HealthCard(card: card, status: healthCardStatus)
133- let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
127+ let healthCardResponse = try await selectEsignCommand.transmit(to: Self.healthCard)
128+ guard healthCardResponse.responseStatus == ResponseStatus.success else {
129+ throw HealthCard.Error.operational // TO-DO: handle this or throw a meaningful Error
130+ }
134131----
135132
133+
134+ *Following paragraphs describe the deprecated way of executung commands via the _Combine_ inteface:*
135+
136136A created command can be lifted to the Combine framework with `publisher(for:writetimeout:readtimeout)`.
137137The result of the command execution can be validated against an expected `ResponseStatus`,
138138e.g. +SUCCESS+ (+0x9000+).
139139
140140[source,swift]
141141----
142+ let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
142143let checkResponse = publisher.tryMap { healthCardResponse -> HealthCardResponseType in
143144 guard healthCardResponse.responseStatus == ResponseStatus.success else {
144145 throw HealthCard.Error.operational // throw a meaningful Error
@@ -219,17 +220,11 @@ Take the necessary preparatory steps for signing a challenge on the Health Card,
219220
220221[source,swift]
221222----
222- expect {
223- let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
224- let format2Pin = try Format2Pin(pincode: "123456")
225- return try Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
226- .flatMap { _ in
227- Self.healthCard.sign(data: challenge)
228- }
229- .eraseToAnyPublisher()
230- .test()
231- .responseStatus
232- } == ResponseStatus.success
223+ let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
224+ let format2Pin = try Format2Pin(pincode: "123456")
225+ _ = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
226+ let signResponse = try await Self.healthCard.sign(data: challenge)
227+ expect(signResponse.responseStatus) == ResponseStatus.success
233228----
234229
235230
@@ -238,7 +233,7 @@ steps for establishing a secure channel with the Health Card and expose only a s
238233
239234[source,swift]
240235----
241- try KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKey(
236+ let secureMessaging = try await KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKey(
242237 card: CardSimulationTerminalTestCase.healthCard,
243238 can: can,
244239 writeTimeout: 0,
@@ -253,6 +248,68 @@ for more already implemented use cases.
253248
254249A `CardReaderProvider` implementation that handles the
255250communication with the Apple iPhone NFC interface.
251+
252+ ==== NFCCardReaderSession
253+
254+ For convience, the `NFCCardReaderSession` combines the usage of the NFC inteface with the `HealthCardAccess/HealthCardControl` layers.
255+
256+ The initializer takes some NFC-Display messages, the CAN (card access number) and a closure with a `NFCHealthCardSessionHandle` to send/receive commands/responses to/from the NFC HealthCard and to update the user's interface message to.
257+
258+ [source,swift]
259+ ----
260+ guard let nfcHealthCardSession = NFCHealthCardSession(messages: messages, can: can, operation: { session in
261+ session.updateAlert(message: NSLocalizedString("nfc_txt_msg_verify_pin", comment: ""))
262+ let verifyPinResponse = try await session.card.verifyAsync(
263+ pin: format2Pin,
264+ type: EgkFileSystem.Pin.mrpinHome
265+ )
266+ if case let VerifyPinResponse.wrongSecretWarning(retryCount: count) = verifyPinResponse {
267+ throw NFCLoginController.Error.wrongPin(retryCount: count)
268+ } else if case VerifyPinResponse.passwordBlocked = verifyPinResponse {
269+ throw NFCLoginController.Error.passwordBlocked
270+ } else if VerifyPinResponse.success != verifyPinResponse {
271+ throw NFCLoginController.Error.verifyPinResponse
272+ }
273+
274+ session.updateAlert(message: NSLocalizedString("nfc_txt_msg_signing", comment: ""))
275+ let outcome = try await session.card.sign(
276+ payload: "ABC".data(using: .utf8)!, // swiftlint:disable:this force_unwrapping
277+ checkAlgorithm: checkBrainpoolAlgorithm
278+ )
279+
280+ session.updateAlert(message: NSLocalizedString("nfc_txt_msg_success", comment: ""))
281+ return outcome
282+ })
283+ else {
284+ // handle the case the Session could not be initialized
285+ ----
286+
287+ Execute the operation on the NFC HealthCard. The secure channel (PACE) is established initially before executing the operation.
288+
289+ [source,swift]
290+ ----
291+ signedData = try await nfcHealthCardSession.executeOperation()
292+ ----
293+
294+ The thrown error will be of type `NFCHealthCardSessionError`.
295+ The `NFCHealthCardSession` also gives you an endpoint to invalidate the underlying `TagReaderSession`.
296+
297+ [source,swift]
298+ ----
299+ } catch NFCHealthCardSessionError.coreNFC(.userCanceled) {
300+ // error type is always `NFCHealthCardSessionError`
301+ // here we especially handle when the user canceled the session
302+ Task { @MainActor in self.pState = .idle } // Do some view-property update
303+ // Calling .invalidateSession() is not strictly necessary
304+ // since nfcHealthCardSession does it while it's de-initializing.
305+ nfcHealthCardSession.invalidateSession(with: nil)
306+ return
307+ } catch {
308+ Task { @MainActor in self.pState = .error(error) }
309+ nfcHealthCardSession.invalidateSession(with: error.localizedDescription)
310+ return
311+ }
312+ ----
256313[#NFCDemo]
257314=== NFCDemo
258315
0 commit comments