Skip to content

Commit dbfcc96

Browse files
authored
Version 5.6.0 (#19)
1 parent 311991d commit dbfcc96

File tree

59 files changed

+981
-599
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+981
-599
lines changed

.github/README.adoc

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ This document describes the functionalitiy and structure of OpenHealthCardKit.
2626
Generated 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
116116
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
117117
----
118118

119-
===== Setting an execution target
119+
===== Command execution
120120

121121
We execute the created command `CardType` instance which has been typically provided by a `CardReaderType`.
122122

123123
In 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+
136136
A created command can be lifted to the Combine framework with `publisher(for:writetimeout:readtimeout)`.
137137
The result of the command execution can be validated against an expected `ResponseStatus`,
138138
e.g. +SUCCESS+ (+0x9000+).
139139

140140
[source,swift]
141141
----
142+
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
142143
let 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

254249
A `CardReaderProvider` implementation that handles the
255250
communication 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

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.DS_Store
2-
/.build
2+
/.build
33
/Packages
44
.vscode
55
.swiftpm

IntegrationTests/HealthCardAccess/PublisherIntegrationTest.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,43 @@ final class PublisherIntegrationTest: CardSimulationTerminalTestCase {
5454
} == ResponseStatus.endOfFileWarning
5555
}
5656

57-
// swiftlint:disable force_unwrapping
58-
func codeForUserManual() {
57+
func testCodeForUserManual() async throws {
5958
// tag::createCommand[]
6059
let eSign = EgkFileSystem.DF.ESIGN
6160
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
6261
// end::createCommand[]
6362

63+
// tag::evaluateResponseStatus[]
64+
let healthCardResponse = try await selectEsignCommand.transmitAsync(to: Self.healthCard)
65+
guard healthCardResponse.responseStatus == ResponseStatus.success else {
66+
throw HealthCard.Error.operational // TO-DO: handle this or throw a meaningful Error
67+
}
68+
// end::evaluateResponseStatus[]
69+
70+
// expect that no error has been thrown
71+
}
72+
73+
// swiftlint:disable force_unwrapping
74+
func codeForUserManual_publisher() {
75+
let eSign = EgkFileSystem.DF.ESIGN
76+
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
77+
6478
expect {
65-
// tag::setExecutionTarget[]
6679
// initialize your CardReaderType instance
6780
let cardReader: CardReaderType = CardSimulationTerminalTestCase.reader
6881
let card = try cardReader.connect([:])!
6982
let healthCardStatus = HealthCardStatus.valid(cardType: .egk(generation: .g2))
7083
let eGk = try HealthCard(card: card, status: healthCardStatus)
71-
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
72-
// end::setExecutionTarget[]
7384

74-
// tag::evaluateResponseStatus[]
85+
// tag::evaluateResponseStatus_publisher[]
86+
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
7587
let checkResponse = publisher.tryMap { healthCardResponse -> HealthCardResponseType in
7688
guard healthCardResponse.responseStatus == ResponseStatus.success else {
7789
throw HealthCard.Error.operational // throw a meaningful Error
7890
}
7991
return healthCardResponse
8092
}
81-
// end::evaluateResponseStatus[]
93+
// end::evaluateResponseStatus_publisher[]
8294

8395
// tag::createCommandSequence[]
8496
let readCertificate = checkResponse

IntegrationTests/HealthCardControl/AuthenticateChallengeE256Test.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ final class AuthenticateChallengeE256Test: CardSimulationTerminalTestCase {
4848
let challenge = "1234567890".data(using: .utf8)!
4949
_ = try await Self.healthCard
5050
.verify(pin: "123456", type: .mrpinHome)
51-
let authenticatedResult = try await Self.healthCard.authenticate(challenge: challenge)
51+
let authenticatedResult = try await Self.healthCard.authenticateAsync(challenge: challenge)
5252

5353
expect(authenticatedResult.certificate.signatureAlgorithm) == .ecdsaSha256
5454
expect(authenticatedResult.certificate.certificate.count) == 885

IntegrationTests/HealthCardControl/AuthenticateChallengeR2048Test.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class AuthenticateChallengeR2048Test: CardSimulationTerminalTestCase {
4242
let challenge = "1234567890".data(using: .utf8)!
4343
_ = try await Self.healthCard
4444
.verify(pin: "123456", type: .mrpinHome)
45-
let authenticatedResult = try await Self.healthCard.authenticate(challenge: challenge)
45+
let authenticatedResult = try await Self.healthCard.authenticateAsync(challenge: challenge)
4646

4747
expect(authenticatedResult.certificate.signatureAlgorithm) == .sha256RsaMgf1
4848
expect(authenticatedResult.certificate.certificate.count) == 1242

IntegrationTests/HealthCardControl/CardChannelTypeExtVersionIntegrationTest.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class CardChannelTypeExtVersionIntegrationTest: CardSimulationTerminalTest
3535
}
3636

3737
func testReadCardTypeFromVersion() async throws {
38-
let cardType = try await Self.healthCard.currentCardChannel.readCardType()
38+
let cardType = try await Self.healthCard.currentCardChannel.readCardTypeAsync()
3939
expect(cardType) == HealthCardPropertyType.egk(generation: .g2_1)
4040
}
4141

@@ -51,8 +51,8 @@ final class CardChannelTypeExtVersionIntegrationTest: CardSimulationTerminalTest
5151
}
5252

5353
func testDetermineCardAidThenReadCardTypeFromVersion() async throws {
54-
let cardAid = try await Self.healthCard.currentCardChannel.determineCardAid()
55-
let cardType = try await Self.healthCard.currentCardChannel.readCardType(cardAid: cardAid)
54+
let cardAid = try await Self.healthCard.currentCardChannel.determineCardAidAsync()
55+
let cardType = try await Self.healthCard.currentCardChannel.readCardTypeAsync(cardAid: cardAid)
5656
expect(cardType) == HealthCardPropertyType.egk(generation: .g2_1)
5757
}
5858

@@ -66,7 +66,7 @@ final class CardChannelTypeExtVersionIntegrationTest: CardSimulationTerminalTest
6666

6767
func testReadCardTypeFromVersionWithKnownCardAid() async throws {
6868
let cardAid = CardAid.egk
69-
let cardType = try await Self.healthCard.currentCardChannel.readCardType(cardAid: cardAid)
69+
let cardType = try await Self.healthCard.currentCardChannel.readCardTypeAsync(cardAid: cardAid)
7070
expect(cardType) == HealthCardPropertyType.egk(generation: .g2_1)
7171
}
7272
}

IntegrationTests/HealthCardControl/DetermineCardAidIntegrationTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ final class DetermineCardAidIntegrationTest: CardSimulationTerminalTestCase {
3333
}
3434

3535
func testDetermineCardAid() async throws {
36-
let result = try await Self.healthCard.currentCardChannel.determineCardAid()
36+
let result = try await Self.healthCard.currentCardChannel.determineCardAidAsync()
3737
expect(result) == CardAid.egk
3838
}
3939
}

IntegrationTests/HealthCardControl/HealthCardTypeExtESIGNIntegrationTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ final class HealthCardTypeExtESIGNIntegrationTest: CardSimulationTerminalTestCas
4949
let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
5050
let format2Pin = try Format2Pin(pincode: "123456")
5151
_ = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
52-
let signResponse = try await Self.healthCard.sign(data: challenge)
52+
let signResponse = try await Self.healthCard.signAsync(data: challenge)
5353
expect(signResponse.responseStatus) == ResponseStatus.success
5454
// end::signChallenge[]
5555
}

IntegrationTests/HealthCardControl/HealthCardTypeExtEfCardAccessIntTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ final class HealthCardTypeExtEfCardAccessIntTest: CardSimulationTerminalTestCase
3232
}
3333

3434
func testReadEfCardAccess() async throws {
35-
let algorithm = try await Self.healthCard.currentCardChannel.readKeyAgreementAlgorithm(
35+
let algorithm = try await Self.healthCard.currentCardChannel.readKeyAgreementAlgorithmAsync(
3636
writeTimeout: 30,
3737
readTimeout: 30
3838
)

IntegrationTests/HealthCardControl/HealthCardTypeExtResetRetryCounterIntegrationTest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ final class HealthCardTypeExtResetRetryCounterIntegrationTest: CardSimulationTer
7171
let puk = "12345678" as Format2Pin
7272
let newPin = "654321" as Format2Pin
7373

74-
let response = try await Self.healthCard.resetRetryCounterAndSetNewPin(
74+
let response = try await Self.healthCard.resetRetryCounterAndSetNewPinAsync(
7575
puk: puk,
7676
newPin: newPin,
7777
type: EgkFileSystem.Pin.mrpinHome,
@@ -99,7 +99,7 @@ final class HealthCardTypeExtResetRetryCounterIntegrationTest: CardSimulationTer
9999
let puk = "12345678" as Format2Pin
100100
let tooLongNewPin = "654112341234" as Format2Pin
101101

102-
let response = try await Self.healthCard.resetRetryCounterAndSetNewPin(
102+
let response = try await Self.healthCard.resetRetryCounterAndSetNewPinAsync(
103103
puk: puk,
104104
newPin: tooLongNewPin,
105105
type: EgkFileSystem.Pin.mrpinHome,

0 commit comments

Comments
 (0)