Skip to content

Commit 1b6837a

Browse files
Version 5.4.0 (#13)
Co-authored-by: Gematik <[email protected]>
1 parent 38ad9b5 commit 1b6837a

File tree

242 files changed

+1583
-995
lines changed

Some content is hidden

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

242 files changed

+1583
-995
lines changed

.github/README.adoc

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
////
2+
Execute `make readme` after editing <project_root>/README.adoc
3+
////
4+
:toc-title: Table of Contents
5+
:toc:
6+
:toclevels: 2
7+
:source-highlighter: prettify
8+
9+
:testdir: ../../Tests
10+
:integrationtestdir: ../../IntegrationTests
11+
:sourcedir: ../../Sources
12+
13+
= OpenHealthCardKit
14+
15+
Controlling/Use-case framework for accessing smart cards of the telematic infrastructure.
16+
17+
== Introduction
18+
19+
The OpenHealthCardKit module is intended for reference purposes
20+
when implementing a system that performs the communication between an iOS based mobile device
21+
and a German Health Card (elektronische Gesundheitskarte) using an NFC, Blue Tooth oder USB interface.
22+
23+
This document describes the functionalitiy and structure of OpenHealthCardKit.
24+
== API Documentation
25+
26+
Generated API docs are available at https://gematik.github.io/ref-OpenHealthCardKit.
27+
== Getting Started
28+
29+
OpenHealthCardKit requires Swift 5.1.
30+
31+
=== Setup for integration
32+
33+
- **Swift Package Manager:** Put this in your `Package.swift`:
34+
35+
`.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.3.0"),`
36+
37+
- **Carthage:** Put this in your `Cartfile`:
38+
39+
github "gematik/ref-openHealthCardKit" ~> 5.0
40+
41+
=== Setup for development
42+
43+
Run `$ make setup` to start developing locally. This will make sure all the dependencies are put in place and the Xcode-project will be generated and/or overwritten.
44+
45+
Dependencies are a mix of SPM (Swift Package Manager) and Carthage right now. The Xcode-project is generated using `xcodegen`.
46+
The more complex build configuration(s) is done with the help of Fastlane. See the `./fastlane` directory for full setup.
47+
48+
== Overview
49+
50+
OpenHealthCardKit bundles submodules that provide the functionality
51+
necessary for accessing and interacting with German Health Cards via a mobile iOS device.
52+
53+
OpenHealthCardKit consists of the submodules
54+
55+
- CardReaderProviderApi
56+
- HealthCardAccess
57+
- HealthCardControl
58+
- NFCCardReaderProvider
59+
60+
As a reference for each submodule see also the `IntegrationTests`.
61+
Also see a https://github.com/gematik/ref-OpenHealthCardApp-iOS[Demo App] on GitHub using this framework.
62+
[#CardReaderProviderApi]
63+
=== CardReaderProviderApi
64+
65+
(Smart)CardReader protocols for interacting with `HealthCardAccess`.
66+
[#HealthCardAccess]
67+
=== HealthCardAccess
68+
This library contains the classes for cards, commands, card file systems and error handling.
69+
70+
==== HealthCardAccess API
71+
72+
The HealthCardAccessKit API Structure contains the `HealthCard` class representing all supported card types,
73+
the `Commands` and `Responses` groups with all supported commands and responses for health cards,
74+
the `CardObjects` group with the possible objects on a health cards
75+
and the `Operation` group for cascading and executing commands on health cards.
76+
77+
===== Health Cards
78+
The class `HealthCard` represents the potential types of health cards by storing a `HealthCardStatus` property which in
79+
case of being _valid_ by itself stores a `HealthCardPropertyType` which at the time of writing is represented by either
80+
one of the following
81+
82+
- egk ("elektronische Gesundheitskarte")
83+
- hba ("Heilberufeausweis")
84+
- smcb ("Security Module Card Typ B").
85+
86+
The `HealthCardPropertyType` by itself stores the `CardGeneration` (G1, G1P, G2, G2.1) as well.
87+
88+
Furthermore the `HealthCard` object contains the physical card from a card reader and the current card channel.
89+
90+
===== Commands
91+
92+
The `Commands` groups contains all available `HealthCardCommand` objects for health cards through the `HealthCardCommandBuilder`.
93+
94+
95+
==== Code Samples
96+
97+
===== Create a command
98+
The design of this API follows the link:https://en.wikipedia.org/wiki/Command_pattern[command design pattern]
99+
leveraging Swift's https://developer.apple.com/documentation/combine/[Combine Framework].
100+
The command objects are designed to fulfil the use-cases described in the link:https://www.vesta-gematik.de/standards/detail/standards/spezifikation-des-card-operating-system-cos-elektrische-schnittstelle-1/[Gematik COS specification].
101+
After creating a command object resp. sequence you can execute it on a Healthcard with the help of `publisher(for:)`.
102+
More information on how to configure the commands can also be found in the Gematik COS specification.
103+
104+
Following example shall send a +SELECT+ and a +READ+ command to a smart card
105+
in order to select and read the certificate stored in the file +EF.C.CH.AUT.R2048+ in the application +ESIGN+.
106+
107+
First we want to to create a `SelectCommand` object passing a `ApplicationIdentifier`. We use one of the predefined
108+
helper functions by using `HealthCardCommand.Select`.
109+
110+
One could also use the `HealthCardCommandBuilder` to construct a customized `HealthCardCommand`
111+
by setting the APDU-bytes manually.
112+
113+
[source,swift]
114+
----
115+
let eSign = EgkFileSystem.DF.ESIGN
116+
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
117+
----
118+
119+
===== Setting an execution target
120+
121+
We execute the created command `CardType` instance which has been typically provided by a `CardReaderType`.
122+
123+
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+
126+
[source,swift]
127+
----
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)
134+
----
135+
136+
A created command can be lifted to the Combine framework with `publisher(for:writetimeout:readtimeout)`.
137+
The result of the command execution can be validated against an expected `ResponseStatus`,
138+
e.g. +SUCCESS+ (+0x9000+).
139+
140+
[source,swift]
141+
----
142+
let checkResponse = publisher.tryMap { healthCardResponse -> HealthCardResponseType in
143+
guard healthCardResponse.responseStatus == ResponseStatus.success else {
144+
throw HealthCard.Error.operational // throw a meaningful Error
145+
}
146+
return healthCardResponse
147+
}
148+
----
149+
150+
===== Create a Command Sequence
151+
152+
It is possible to chain further commands via the `flatMap` operator for subsequent execution:
153+
First create a command and lift it onto a Combine monad, then create a publisher using the `flatMap` operator, e.g.
154+
155+
```
156+
Just(AnyHealthCardCommand.build())
157+
.flatMap { command in command.pusblisher(for: card) }
158+
```
159+
160+
Eventually use `eraseToAnyPublisher()`.
161+
162+
[source,swift]
163+
----
164+
let readCertificate = checkResponse
165+
.tryMap { _ -> HealthCardCommandType in
166+
let sfi = EgkFileSystem.EF.esignCChAutR2048.sfid!
167+
return try HealthCardCommand.Read.readFileCommand(with: sfi, ne: 0x076C - 1)
168+
}
169+
.flatMap { command in
170+
command.publisher(for: eGk)
171+
}
172+
.eraseToAnyPublisher()
173+
----
174+
175+
===== Process Execution result
176+
177+
When the whole command chain is set up we have to subscribe to it.
178+
We really only will receive one value before completion, so something as simple as this `sink()`
179+
convenience publisher is useful.
180+
181+
[source,swift]
182+
----
183+
readCertificate
184+
.sink(
185+
receiveCompletion: { completion in
186+
switch completion {
187+
case .finished:
188+
DLog("Completed")
189+
case let .failure(error):
190+
DLog("Error: \(error)")
191+
}
192+
},
193+
receiveValue: { healthCardResponse in
194+
DLog("Got a certifcate")
195+
let certificate = healthCardResponse.data!
196+
// proceed with certificate data here
197+
// use swiftUI to a show success message on screen etc.
198+
}
199+
)
200+
----
201+
[#HealthCardControl]
202+
=== HealthCardControl
203+
204+
This library can be used to realize use cases for interacting with a German Health Card
205+
(eGk, elektronische Gesundheitskarte) via a mobile device.
206+
207+
Typically you would use this library as the high level API gateway for your mobile application
208+
to send predefined command chains to the Health Card and interpret the responses.
209+
210+
For more info, please find the low level part `HealthCardAccess`.
211+
and a https://github.com/gematik/ref-OpenHealthCardApp-iOS[Demo App] on GitHub.
212+
213+
See the https://gematik.github.io/[Gematik GitHub IO] page for a more general overview.
214+
215+
216+
==== Code Samples
217+
218+
Take the necessary preparatory steps for signing a challenge on the Health Card, then sign it.
219+
220+
[source,swift]
221+
----
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
233+
----
234+
235+
236+
Encapsulate the https://www.bsi.bund.de/DE/Publikationen/TechnischeRichtlinien/tr03110/index_htm.html[PACE protocol]
237+
steps for establishing a secure channel with the Health Card and expose only a simple API call .
238+
239+
[source,swift]
240+
----
241+
try KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKey(
242+
card: CardSimulationTerminalTestCase.healthCard,
243+
can: can,
244+
writeTimeout: 0,
245+
readTimeout: 10
246+
)
247+
----
248+
249+
See the integration tests link:include::{integrationtestdir}/HealthCardControl/[IntegrationTests/HealthCardControl/]
250+
for more already implemented use cases.
251+
[#NFCCardReaderProvider]
252+
=== NFCCardReaderProvider
253+
254+
A `CardReaderProvider` implementation that handles the
255+
communication with the Apple iPhone NFC interface.
256+
[#NFCDemo]
257+
=== NFCDemo
258+
259+
The NFCDemo iOS App target demonstrates the use of OHCKit and the NFCCardReader[Provider] specifically by utilizing
260+
said framework to connect to and establish a secure communications channel with an eGK Card via NFC.
261+
262+
The App consist out of two screens/views. The first one will prompt the user for the CAN number.
263+
The second prompts for the PIN. This PIN is verified on the card against `mrpinHome` when the `connect` button is tapped.
264+
265+
== License
266+
267+
Copyright 2023 gematik GmbH
268+
269+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
270+
271+
See the link:./LICENSE[LICENSE] for the specific language governing permissions and limitations under the License.
272+
273+
Unless required by applicable law the software is provided "as is" without warranty of any kind, either express or implied, including, but not limited to, the warranties of fitness for a particular purpose, merchantability, and/or non-infringement. The authors or copyright holders shall not be liable in any manner whatsoever for any damages or other claims arising from, out of or in connection with the software or the use or other dealings with the software, whether in an action of contract, tort, or otherwise.
274+
275+
The software is the result of research and development activities, therefore not necessarily quality assured and without the character of a liable product. For this reason, gematik does not provide any support or other user assistance (unless otherwise stated in individual cases and without justification of a legal obligation). Furthermore, there is no claim to further development and adaptation of the results to a more current state of the art.
276+
277+
Gematik may remove published results temporarily or permanently from the place of publication at any time without prior notice or justification.

.gitignore

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.DS_Store
22
/.build
33
/Packages
4+
.vscode
45

56
# AppCode project files
67
.idea/
@@ -19,6 +20,7 @@ vendor/
1920
## Build generated
2021
build/
2122
DerivedData/
23+
Resources/*_Info.plist
2224

2325
## Various settings
2426
*.pbxuser
@@ -53,6 +55,7 @@ playground.xcworkspace
5355
# Package.pins
5456
# Package.resolved
5557
.build/
58+
/*.xcodeproj
5659

5760
# CocoaPods
5861
#
@@ -94,7 +97,8 @@ fastlane/test_output
9497

9598
iOSInjectionProject/
9699

97-
scripts/publish
98-
devops
99100
CardSimulationTestKit
101+
devops
102+
103+
jenkinsfiles
100104

.licenceignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./Package.swift

.swiftlint.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ opt_in_rules:
3333
- empty_string
3434
- discouraged_optional_collection
3535
- closure_end_indentation
36+
- file_header
3637
excluded: # paths to ignore during linting. Takes precedence over `included`.
3738
- Package.swift
3839
- .build/
@@ -68,3 +69,8 @@ custom_rules:
6869
message: "Source must not contain author"
6970
severity: warning
7071

72+
file_header:
73+
required_pattern: |
74+
\/\/
75+
\/\/ Copyright \(c\) \d{4} gematik GmbH
76+
\/\/

Brewfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
brew 'mint'
2-
brew 'xcodesorg/made/xcodes'
2+
brew 'xcodesorg/made/xcodes'

Cartfile.private

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
github "Quick/Nimble" ~> 9.0
2-
github "tadija/AEXML" ~> 4.0
3-
github "hectr/swift-stream-reader" "0.3.0"
1+
github "Quick/Nimble" ~> 11.0
42
github "swiftsocket/SwiftSocket" "2e6ba27140a29fae8a6331ba4463312e0c71a6b0"

Cartfile.resolved

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
github "Quick/Nimble" "v9.2.1"
1+
github "Quick/Nimble" "v11.2.2"
22
github "SwiftCommon/DataKit" "1.1.0"
3-
github "gematik/ASN1Kit" "1.1.0"
3+
github "gematik/ASN1Kit" "1.2.1"
44
github "gematik/OpenSSL-Swift" "4.1.0"
55
github "gematik/ref-GemCommonsKit" "1.3.0"
6-
github "hectr/swift-stream-reader" "0.3.0"
76
github "swiftsocket/SwiftSocket" "2e6ba27140a29fae8a6331ba4463312e0c71a6b0"
8-
github "tadija/AEXML" "4.6.1"

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ gem "fastlane", "~>2.213"
66
gem "jazzy", "~>0.13"
77
gem "xcodeproj", "~>1.7"
88
gem "xcode-install", "~> 2.6.6"
9+
gem "asciidoctor-reducer"
910

1011
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
1112
eval_gemfile(plugins_path) if File.exist?(plugins_path)

0 commit comments

Comments
 (0)