Skip to content

Commit

Permalink
Version 5.6.0 (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
gematik1 authored Feb 19, 2024
1 parent 311991d commit dbfcc96
Show file tree
Hide file tree
Showing 59 changed files with 981 additions and 599 deletions.
103 changes: 80 additions & 23 deletions .github/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ This document describes the functionalitiy and structure of OpenHealthCardKit.
Generated API docs are available at https://gematik.github.io/ref-OpenHealthCardKit.
== Getting Started

OpenHealthCardKit requires Swift 5.1.
OpenHealthCardKit requires Swift 5.6.

=== Setup for integration

- **Swift Package Manager:** Put this in your `Package.swift`:

`.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.3.0"),`
`.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.6.0"),`

- **Carthage:** Put this in your `Cartfile`:

Expand Down Expand Up @@ -116,29 +116,30 @@ let eSign = EgkFileSystem.DF.ESIGN
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
----

===== Setting an execution target
===== Command execution

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

In the next example we use a `HealthCard` object representing an eGK (elektronische Gesundheitskarte)
as one kind of a `HealthCardType` implementing the `CardType` protocol.

as one kind of a `HealthCardType` implementing the `CardType` protocol and then send the command to the card (or card's channel):
[source,swift]
----
// initialize your CardReaderType instance
let cardReader: CardReaderType = CardSimulationTerminalTestCase.reader
let card = try cardReader.connect([:])!
let healthCardStatus = HealthCardStatus.valid(cardType: .egk(generation: .g2))
let eGk = try HealthCard(card: card, status: healthCardStatus)
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
let healthCardResponse = try await selectEsignCommand.transmit(to: Self.healthCard)
guard healthCardResponse.responseStatus == ResponseStatus.success else {
throw HealthCard.Error.operational // TO-DO: handle this or throw a meaningful Error
}
----


*Following paragraphs describe the deprecated way of executung commands via the _Combine_ inteface:*

A created command can be lifted to the Combine framework with `publisher(for:writetimeout:readtimeout)`.
The result of the command execution can be validated against an expected `ResponseStatus`,
e.g. +SUCCESS+ (+0x9000+).

[source,swift]
----
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
let checkResponse = publisher.tryMap { healthCardResponse -> HealthCardResponseType in
guard healthCardResponse.responseStatus == ResponseStatus.success else {
throw HealthCard.Error.operational // throw a meaningful Error
Expand Down Expand Up @@ -219,17 +220,11 @@ Take the necessary preparatory steps for signing a challenge on the Health Card,

[source,swift]
----
expect {
let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
let format2Pin = try Format2Pin(pincode: "123456")
return try Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
.flatMap { _ in
Self.healthCard.sign(data: challenge)
}
.eraseToAnyPublisher()
.test()
.responseStatus
} == ResponseStatus.success
let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
let format2Pin = try Format2Pin(pincode: "123456")
_ = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
let signResponse = try await Self.healthCard.sign(data: challenge)
expect(signResponse.responseStatus) == ResponseStatus.success
----


Expand All @@ -238,7 +233,7 @@ steps for establishing a secure channel with the Health Card and expose only a s

[source,swift]
----
try KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKey(
let secureMessaging = try await KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKey(
card: CardSimulationTerminalTestCase.healthCard,
can: can,
writeTimeout: 0,
Expand All @@ -253,6 +248,68 @@ for more already implemented use cases.

A `CardReaderProvider` implementation that handles the
communication with the Apple iPhone NFC interface.

==== NFCCardReaderSession

For convience, the `NFCCardReaderSession` combines the usage of the NFC inteface with the `HealthCardAccess/HealthCardControl` layers.

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.

[source,swift]
----
guard let nfcHealthCardSession = NFCHealthCardSession(messages: messages, can: can, operation: { session in
session.updateAlert(message: NSLocalizedString("nfc_txt_msg_verify_pin", comment: ""))
let verifyPinResponse = try await session.card.verifyAsync(
pin: format2Pin,
type: EgkFileSystem.Pin.mrpinHome
)
if case let VerifyPinResponse.wrongSecretWarning(retryCount: count) = verifyPinResponse {
throw NFCLoginController.Error.wrongPin(retryCount: count)
} else if case VerifyPinResponse.passwordBlocked = verifyPinResponse {
throw NFCLoginController.Error.passwordBlocked
} else if VerifyPinResponse.success != verifyPinResponse {
throw NFCLoginController.Error.verifyPinResponse
}
session.updateAlert(message: NSLocalizedString("nfc_txt_msg_signing", comment: ""))
let outcome = try await session.card.sign(
payload: "ABC".data(using: .utf8)!, // swiftlint:disable:this force_unwrapping
checkAlgorithm: checkBrainpoolAlgorithm
)
session.updateAlert(message: NSLocalizedString("nfc_txt_msg_success", comment: ""))
return outcome
})
else {
// handle the case the Session could not be initialized
----

Execute the operation on the NFC HealthCard. The secure channel (PACE) is established initially before executing the operation.

[source,swift]
----
signedData = try await nfcHealthCardSession.executeOperation()
----

The thrown error will be of type `NFCHealthCardSessionError`.
The `NFCHealthCardSession` also gives you an endpoint to invalidate the underlying `TagReaderSession`.

[source,swift]
----
} catch NFCHealthCardSessionError.coreNFC(.userCanceled) {
// error type is always `NFCHealthCardSessionError`
// here we especially handle when the user canceled the session
Task { @MainActor in self.pState = .idle } // Do some view-property update
// Calling .invalidateSession() is not strictly necessary
// since nfcHealthCardSession does it while it's de-initializing.
nfcHealthCardSession.invalidateSession(with: nil)
return
} catch {
Task { @MainActor in self.pState = .error(error) }
nfcHealthCardSession.invalidateSession(with: error.localizedDescription)
return
}
----
[#NFCDemo]
=== NFCDemo

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.DS_Store
/.build
/.build
/Packages
.vscode
.swiftpm
Expand Down
26 changes: 19 additions & 7 deletions IntegrationTests/HealthCardAccess/PublisherIntegrationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,43 @@ final class PublisherIntegrationTest: CardSimulationTerminalTestCase {
} == ResponseStatus.endOfFileWarning
}

// swiftlint:disable force_unwrapping
func codeForUserManual() {
func testCodeForUserManual() async throws {
// tag::createCommand[]
let eSign = EgkFileSystem.DF.ESIGN
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
// end::createCommand[]

// tag::evaluateResponseStatus[]
let healthCardResponse = try await selectEsignCommand.transmitAsync(to: Self.healthCard)
guard healthCardResponse.responseStatus == ResponseStatus.success else {
throw HealthCard.Error.operational // TO-DO: handle this or throw a meaningful Error
}
// end::evaluateResponseStatus[]

// expect that no error has been thrown
}

// swiftlint:disable force_unwrapping
func codeForUserManual_publisher() {
let eSign = EgkFileSystem.DF.ESIGN
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)

expect {
// tag::setExecutionTarget[]
// initialize your CardReaderType instance
let cardReader: CardReaderType = CardSimulationTerminalTestCase.reader
let card = try cardReader.connect([:])!
let healthCardStatus = HealthCardStatus.valid(cardType: .egk(generation: .g2))
let eGk = try HealthCard(card: card, status: healthCardStatus)
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
// end::setExecutionTarget[]

// tag::evaluateResponseStatus[]
// tag::evaluateResponseStatus_publisher[]
let publisher: AnyPublisher<HealthCardResponseType, Error> = selectEsignCommand.publisher(for: eGk)
let checkResponse = publisher.tryMap { healthCardResponse -> HealthCardResponseType in
guard healthCardResponse.responseStatus == ResponseStatus.success else {
throw HealthCard.Error.operational // throw a meaningful Error
}
return healthCardResponse
}
// end::evaluateResponseStatus[]
// end::evaluateResponseStatus_publisher[]

// tag::createCommandSequence[]
let readCertificate = checkResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class AuthenticateChallengeE256Test: CardSimulationTerminalTestCase {
let challenge = "1234567890".data(using: .utf8)!
_ = try await Self.healthCard
.verify(pin: "123456", type: .mrpinHome)
let authenticatedResult = try await Self.healthCard.authenticate(challenge: challenge)
let authenticatedResult = try await Self.healthCard.authenticateAsync(challenge: challenge)

expect(authenticatedResult.certificate.signatureAlgorithm) == .ecdsaSha256
expect(authenticatedResult.certificate.certificate.count) == 885
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class AuthenticateChallengeR2048Test: CardSimulationTerminalTestCase {
let challenge = "1234567890".data(using: .utf8)!
_ = try await Self.healthCard
.verify(pin: "123456", type: .mrpinHome)
let authenticatedResult = try await Self.healthCard.authenticate(challenge: challenge)
let authenticatedResult = try await Self.healthCard.authenticateAsync(challenge: challenge)

expect(authenticatedResult.certificate.signatureAlgorithm) == .sha256RsaMgf1
expect(authenticatedResult.certificate.certificate.count) == 1242
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class CardChannelTypeExtVersionIntegrationTest: CardSimulationTerminalTest
}

func testReadCardTypeFromVersion() async throws {
let cardType = try await Self.healthCard.currentCardChannel.readCardType()
let cardType = try await Self.healthCard.currentCardChannel.readCardTypeAsync()
expect(cardType) == HealthCardPropertyType.egk(generation: .g2_1)
}

Expand All @@ -51,8 +51,8 @@ final class CardChannelTypeExtVersionIntegrationTest: CardSimulationTerminalTest
}

func testDetermineCardAidThenReadCardTypeFromVersion() async throws {
let cardAid = try await Self.healthCard.currentCardChannel.determineCardAid()
let cardType = try await Self.healthCard.currentCardChannel.readCardType(cardAid: cardAid)
let cardAid = try await Self.healthCard.currentCardChannel.determineCardAidAsync()
let cardType = try await Self.healthCard.currentCardChannel.readCardTypeAsync(cardAid: cardAid)
expect(cardType) == HealthCardPropertyType.egk(generation: .g2_1)
}

Expand All @@ -66,7 +66,7 @@ final class CardChannelTypeExtVersionIntegrationTest: CardSimulationTerminalTest

func testReadCardTypeFromVersionWithKnownCardAid() async throws {
let cardAid = CardAid.egk
let cardType = try await Self.healthCard.currentCardChannel.readCardType(cardAid: cardAid)
let cardType = try await Self.healthCard.currentCardChannel.readCardTypeAsync(cardAid: cardAid)
expect(cardType) == HealthCardPropertyType.egk(generation: .g2_1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class DetermineCardAidIntegrationTest: CardSimulationTerminalTestCase {
}

func testDetermineCardAid() async throws {
let result = try await Self.healthCard.currentCardChannel.determineCardAid()
let result = try await Self.healthCard.currentCardChannel.determineCardAidAsync()
expect(result) == CardAid.egk
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ final class HealthCardTypeExtESIGNIntegrationTest: CardSimulationTerminalTestCas
let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
let format2Pin = try Format2Pin(pincode: "123456")
_ = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
let signResponse = try await Self.healthCard.sign(data: challenge)
let signResponse = try await Self.healthCard.signAsync(data: challenge)
expect(signResponse.responseStatus) == ResponseStatus.success
// end::signChallenge[]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class HealthCardTypeExtEfCardAccessIntTest: CardSimulationTerminalTestCase
}

func testReadEfCardAccess() async throws {
let algorithm = try await Self.healthCard.currentCardChannel.readKeyAgreementAlgorithm(
let algorithm = try await Self.healthCard.currentCardChannel.readKeyAgreementAlgorithmAsync(
writeTimeout: 30,
readTimeout: 30
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ final class HealthCardTypeExtResetRetryCounterIntegrationTest: CardSimulationTer
let puk = "12345678" as Format2Pin
let newPin = "654321" as Format2Pin

let response = try await Self.healthCard.resetRetryCounterAndSetNewPin(
let response = try await Self.healthCard.resetRetryCounterAndSetNewPinAsync(
puk: puk,
newPin: newPin,
type: EgkFileSystem.Pin.mrpinHome,
Expand Down Expand Up @@ -99,7 +99,7 @@ final class HealthCardTypeExtResetRetryCounterIntegrationTest: CardSimulationTer
let puk = "12345678" as Format2Pin
let tooLongNewPin = "654112341234" as Format2Pin

let response = try await Self.healthCard.resetRetryCounterAndSetNewPin(
let response = try await Self.healthCard.resetRetryCounterAndSetNewPinAsync(
puk: puk,
newPin: tooLongNewPin,
type: EgkFileSystem.Pin.mrpinHome,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ final class HealthCardTypeExtResetRetryCounterIntegrationTestContCont: CardSimul
// With setting a new PIN
var response: ResetRetryCounterResponse

response = try await Self.healthCard.resetRetryCounterAndSetNewPin(
response = try await Self.healthCard.resetRetryCounterAndSetNewPinAsync(
puk: wrongPuk,
newPin: newPin,
type: EgkFileSystem.Pin.mrpinHome,
dfSpecific: false
)
expect(response) == ResetRetryCounterResponse.wrongSecretWarning(retryCount: 9)

response = try await Self.healthCard.resetRetryCounterAndSetNewPin(
response = try await Self.healthCard.resetRetryCounterAndSetNewPinAsync(
puk: wrongPuk,
newPin: newPin,
type: EgkFileSystem.Pin.mrpinHome,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class HealthCardTypeExtVerifyPinTest: CardSimulationTerminalTestCase {
func testVerifyMrPinHomeEgk21() async throws {
let pinCode = "123456"
let format2Pin = try Format2Pin(pincode: pinCode)
let response = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
let response = try await Self.healthCard.verifyAsync(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
expect(response) == VerifyPinResponse.success
}

Expand All @@ -56,7 +56,7 @@ final class HealthCardTypeExtVerifyPinTest: CardSimulationTerminalTestCase {
func testVerifyMrPinHomeEgk21_WarningRetryCounter() async throws {
let pinCode = "654321"
let format2Pin = try Format2Pin(pincode: pinCode)
let response = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
let response = try await Self.healthCard.verifyAsync(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
// Note: The retry counter is not reset after each test case. Therefore, the retry counter is 1 here.
expect(response) == VerifyPinResponse.wrongSecretWarning(retryCount: 1)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class OpenSecureSessionIntegrationTest: CardSimulationTerminalTestCase {
func testOpenSecureSession() async throws {
let can = try! CAN.from(Data("123123".utf8)) // swiftlint:disable:this force_try

let secureHealthCard = try await Self.card.openSecureSession(can: can, writeTimeout: 0, readTimeout: 0)
let secureHealthCard = try await Self.card.openSecureSessionAsync(can: can, writeTimeout: 0, readTimeout: 0)
expect(secureHealthCard.status.type) == .egk(generation: .g2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ final class ReadAutCertificateE256Test: CardSimulationTerminalTestCase {

func testReadAutCertificateE256() async throws {
var autCertificateResponse: AutCertificateResponse?
autCertificateResponse = try await Self.healthCard.readAutCertificate()
autCertificateResponse = try await Self.healthCard.readAutCertificateAsync()

expect(autCertificateResponse?.info) == .efAutE256
expect(autCertificateResponse?.certificate) == expectedCertificate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ final class ReadAutCertificateR2048Test: CardSimulationTerminalTestCase {
func testReadAutCertificate2048() async throws {
var autCertificateResponse: AutCertificateResponse?
autCertificateResponse = try await CardSimulationTerminalTestCase.healthCard
.readAutCertificate()
.readAutCertificateAsync()
expect(autCertificateResponse?.info) == .efAutR2048
expect(autCertificateResponse?.certificate) == expectedCertificate
}
Expand Down
Loading

0 comments on commit dbfcc96

Please sign in to comment.