{"id":18985549,"url":"https://github.com/gematik/ref-openhealthcardkit","last_synced_at":"2025-09-04T00:06:40.066Z","repository":{"id":48591405,"uuid":"256525621","full_name":"gematik/ref-OpenHealthCardKit","owner":"gematik","description":"Controlling/Use-case framework for accessing smart cards of the telematic infrastructure. API Documentation: https://swiftpackageindex.com/gematik/ref-OpenHealthCardKit/main/documentation/healthcardaccess","archived":false,"fork":false,"pushed_at":"2025-07-21T08:06:14.000Z","size":33141,"stargazers_count":18,"open_issues_count":1,"forks_count":6,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-08-22T13:18:18.607Z","etag":null,"topics":["e-rezept","ios","reference-implementation"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gematik.png","metadata":{"files":{"readme":".github/README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-04-17T14:28:55.000Z","updated_at":"2025-07-21T08:06:18.000Z","dependencies_parsed_at":"2023-12-28T14:24:47.863Z","dependency_job_id":"f6e7564c-0db5-46dd-be6f-ef5ca3ffd218","html_url":"https://github.com/gematik/ref-OpenHealthCardKit","commit_stats":{"total_commits":8,"total_committers":1,"mean_commits":8.0,"dds":0.0,"last_synced_commit":"969b50b314bd932efdd5370b79c89790c0883480"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/gematik/ref-OpenHealthCardKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gematik%2Fref-OpenHealthCardKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gematik%2Fref-OpenHealthCardKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gematik%2Fref-OpenHealthCardKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gematik%2Fref-OpenHealthCardKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gematik","download_url":"https://codeload.github.com/gematik/ref-OpenHealthCardKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gematik%2Fref-OpenHealthCardKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273529565,"owners_count":25121830,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["e-rezept","ios","reference-implementation"],"created_at":"2024-11-08T16:27:11.002Z","updated_at":"2025-09-04T00:06:38.807Z","avatar_url":"https://github.com/gematik.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"////\nExecute `make readme` after editing \u003cproject_root\u003e/README.adoc\n////\n:toc-title: Table of Contents\n:toc:\n:toclevels: 2\n:source-highlighter: prettify\n\n:testdir: ../../Tests\n:integrationtestdir: ../../IntegrationTests\n:sourcedir: ../../Sources\n\n= OpenHealthCardKit\n\nControlling/Use-case framework for accessing smart cards of the telematic infrastructure.\n\n== Introduction\n\nThe OpenHealthCardKit module is intended for reference purposes\nwhen implementing a system that performs the communication between an iOS based mobile device\nand a German Health Card (elektronische Gesundheitskarte) using an NFC, Blue Tooth oder USB interface.\n\nThis document describes the functionalitiy and structure of OpenHealthCardKit.\n\n== API Documentation\n\nGenerated API docs are available at https://swiftpackageindex.com/gematik/ref-OpenHealthCardKit[Swift Package Index]:\n\n* HealthCardControl (tbd)\n* https://swiftpackageindex.com/gematik/ref-OpenHealthCardKit/main/documentation/healthcardaccess[HealthCardAccess]\n* https://swiftpackageindex.com/gematik/ref-OpenHealthCardKit/main/documentation/nfccardreaderprovider[NFCCardReaderProvider]\n* https://swiftpackageindex.com/gematik/ref-OpenHealthCardKit/main/documentation/cardreaderproviderapi[CardReaderProviderApi]\n\nNOTE: As of now the automatic API doc generation for `HealthCardControl` is broken. It's possible to generate it manually via Xcode: Select the target `HealthCardControl` and select `Product -\u003e Build Documentation`.\n\n== Getting Started\n\nOpenHealthCardKit requires Swift 5.6.\n\n=== Setup for integration\n\n- **Swift Package Manager:** Put this in your `Package.swift`:\n\n    .package(url: \"https://github.com/gematik/ref-OpenHealthCardKit\", from: \"5.6.0\"),\n\n- **Carthage:** Put this in your `Cartfile`:\n\n    github \"gematik/ref-openHealthCardKit\" ~\u003e 5.0\n\n=== Setup for development\n\nRun `$ 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.\n\nDependencies are a mix of SPM (Swift Package Manager) and Carthage right now. The Xcode-project is generated using `xcodegen`.\nThe more complex build configuration(s) is done with the help of Fastlane. See the `./fastlane` directory for full setup.\n\n== Overview\n\nOpenHealthCardKit bundles submodules that provide the functionality\nnecessary for accessing and interacting with German Health Cards via a mobile iOS device.\n\nOpenHealthCardKit consists of the submodules\n\n- CardReaderProviderApi\n- HealthCardAccess\n- HealthCardControl\n- NFCCardReaderProvider\n\nAs a reference for the usage of each submodule see also the `IntegrationTests`.\n\n[#CardReaderProviderApi]\n=== CardReaderProviderApi\n\n(Smart)CardReader protocols for interacting with `HealthCardAccess`.\n\n[#HealthCardAccess]\n=== HealthCardAccess\nThis library contains the classes for cards, commands, card file systems and error handling.\n\n==== HealthCardAccess API\n\nThe HealthCardAccessKit API Structure contains the `HealthCard` class representing all supported card types,\nthe `Commands` and `Responses` groups with all supported commands and responses for health cards,\nthe `CardObjects` group with the possible objects on a health cards\nand the `Operation` group for cascading and executing commands on health cards.\n\n===== Health Cards\nThe class `HealthCard` represents the potential types of health cards by storing a `HealthCardStatus` property which in\ncase of being _valid_ by itself stores a `HealthCardPropertyType` which at the time of writing is represented by either\none of the following\n\n- egk (\"elektronische Gesundheitskarte\")\n- hba (\"Heilberufeausweis\")\n- smcb (\"Security Module Card Typ B\").\n\nThe `HealthCardPropertyType` by itself stores the `CardGeneration` (G1, G1P, G2, G2.1) as well.\n\nFurthermore the `HealthCard` object contains the physical card from a card reader and the current card channel.\n\n===== Commands\n\nThe `Commands` groups contains all available `HealthCardCommand` objects for health cards through the `HealthCardCommandBuilder`.\n\n\n==== Code Samples\n\n===== Create a command\nThe design of this API follows the link:https://en.wikipedia.org/wiki/Command_pattern[command design pattern]\nleveraging Swift's https://developer.apple.com/documentation/combine/[Combine Framework].\nThe 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].\nAfter creating a command object resp. sequence you can execute it on a Healthcard with the help of `publisher(for:)`.\nMore information on how to configure the commands can also be found in the Gematik COS specification.\n\nFollowing example shall send a +SELECT+ and a +READ+ command to a smart card\nin order to select and read the certificate stored in the file +EF.C.CH.AUT.R2048+ in the application +ESIGN+.\n\nFirst we want to to create a `SelectCommand` object passing a `ApplicationIdentifier`. We use one of the predefined\nhelper functions by using `HealthCardCommand.Select`.\n\nOne could also use the `HealthCardCommandBuilder` to construct a customized `HealthCardCommand`\nby setting the APDU-bytes manually.\n\n[source,swift]\n----\nlet eSign = EgkFileSystem.DF.ESIGN\nlet selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)\n----\n\n===== Command execution\n\nWe execute the created command `CardType` instance which has been typically provided by a `CardReaderType`.\n\nIn the next example we use a `HealthCard` object representing an eGK (elektronische Gesundheitskarte)\nas one kind of a `HealthCardType` implementing the `CardType` protocol and then send the command to the card (or card's channel):\n[source,swift]\n----\nlet healthCardResponse = try await selectEsignCommand.transmit(to: Self.healthCard)\nguard healthCardResponse.responseStatus == ResponseStatus.success else {\n    throw HealthCard.Error.operational // TO-DO: handle this or throw a meaningful Error\n}\n----\n\n\n*Following paragraphs describe the deprecated way of executung commands via the _Combine_ inteface:*\n\nA created command can be lifted to the Combine framework with `publisher(for:writetimeout:readtimeout)`.\nThe result of the command execution can be validated against an expected `ResponseStatus`,\ne.g. +SUCCESS+ (+0x9000+).\n\n[source,swift]\n----\nlet publisher: AnyPublisher\u003cHealthCardResponseType, Error\u003e = selectEsignCommand.publisher(for: eGk)\nlet checkResponse = publisher.tryMap { healthCardResponse -\u003e HealthCardResponseType in\n    guard healthCardResponse.responseStatus == ResponseStatus.success else {\n        throw HealthCard.Error.operational // throw a meaningful Error\n    }\n    return healthCardResponse\n}\n----\n\n===== Create a Command Sequence\n\nIt is possible to chain further commands via the `flatMap` operator for subsequent execution:\nFirst create a command and lift it onto a Combine monad, then create a publisher using the `flatMap` operator, e.g.\n\n```\nJust(AnyHealthCardCommand.build())\n    .flatMap { command in command.pusblisher(for: card) }\n```\n\nEventually use `eraseToAnyPublisher()`.\n\n[source,swift]\n----\nlet readCertificate = checkResponse\n    .tryMap { _ -\u003e HealthCardCommandType in\n        let sfi = EgkFileSystem.EF.esignCChAutR2048.sfid!\n        return try HealthCardCommand.Read.readFileCommand(with: sfi, ne: 0x076C - 1)\n    }\n    .flatMap { command in\n        command.publisher(for: eGk)\n    }\n    .eraseToAnyPublisher()\n----\n\n===== Process Execution result\n\nWhen the whole command chain is set up we have to subscribe to it.\nWe really only will receive one value before completion, so something as simple as this `sink()`\nconvenience publisher is useful.\n\n[source,swift]\n----\n_ = readCertificate\n    .sink(\n        receiveCompletion: { completion in\n            switch completion {\n            case .finished:\n                Logger.integrationTest.debug(\"Completed\")\n            case let .failure(error):\n                Logger.integrationTest.debug(\"Error: \\(error)\")\n            }\n        },\n        receiveValue: { healthCardResponse in\n            Logger.integrationTest.debug(\"Got a certifcate\")\n            let certificate = healthCardResponse.data!\n            Logger.integrationTest.debug(\"Certificate: \\(certificate.hexString())\")\n            // proceed with certificate data here\n            // use swiftUI to a show success message on screen etc.\n        }\n    )\n----\n\n[#HealthCardControl]\n=== HealthCardControl\n\nThis library can be used to realize use cases for interacting with a German Health Card\n(eGk, elektronische Gesundheitskarte) via a mobile device.\n\nTypically you would use this library as the high level API gateway for your mobile application\nto send predefined command chains to the Health Card and interpret the responses.\n\nFor more info, please find the low level part `HealthCardAccess`.\nand a https://github.com/gematik/ref-OpenHealthCardApp-iOS[Demo App] on GitHub.\n\nSee the https://gematik.github.io/[Gematik GitHub IO] page for a more general overview.\n\n\n==== Code Samples\n\nTake the necessary preparatory steps for signing a challenge on the Health Card, then sign it.\n\n[source,swift]\n----\nlet challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])\nlet format2Pin = try Format2Pin(pincode: \"123456\")\n_ = try await Self.healthCard.verifyAsync(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)\nlet signResponse = try await Self.healthCard.signAsync(data: challenge)\nexpect(signResponse.responseStatus) == ResponseStatus.success\n----\n\n\nEncapsulate the https://www.bsi.bund.de/DE/Publikationen/TechnischeRichtlinien/tr03110/index_htm.html[PACE protocol]\nsteps for establishing a secure channel with the Health Card and expose only a simple API call .\n\n[source,swift]\n----\nlet secureMessaging = try await KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKeyAsync(\n    card: CardSimulationTerminalTestCase.healthCard,\n    can: can,\n    writeTimeout: 0,\n    readTimeout: 10\n)\n----\n\nSee the integration tests link:include::{integrationtestdir}/HealthCardControl/[IntegrationTests/HealthCardControl/]\nfor more already implemented use cases.\n\n[#NFCCardReaderProvider]\n=== NFCCardReaderProvider\n\nA `CardReaderProvider` implementation that handles the\ncommunication with the Apple iPhone NFC interface.\n\n==== NFCCardReaderSession\n\nFor convience, the `NFCCardReaderSession` combines the usage of the NFC inteface with the `HealthCardAccess/HealthCardControl` layers.\n\nThe 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.\n\n[source,swift]\n----\nguard let nfcHealthCardSession = NFCHealthCardSession(messages: messages, can: can, operation: { session in\n    session.updateAlert(message: NSLocalizedString(\"nfc_txt_msg_verify_pin\", comment: \"\"))\n    let verifyPinResponse = try await session.card.verifyAsync(\n        pin: format2Pin,\n        type: EgkFileSystem.Pin.mrpinHome\n    )\n    if case let VerifyPinResponse.wrongSecretWarning(retryCount: count) = verifyPinResponse {\n        throw NFCSigningFunctionController.Error.wrongPin(retryCount: count)\n    } else if case VerifyPinResponse.passwordBlocked = verifyPinResponse {\n        throw NFCSigningFunctionController.Error.passwordBlocked\n    } else if VerifyPinResponse.success != verifyPinResponse {\n        throw NFCSigningFunctionController.Error.verifyPinResponse\n    }\n\n    session.updateAlert(message: NSLocalizedString(\"nfc_txt_msg_signing\", comment: \"\"))\n    let outcome = try await session.card.sign(\n        payload: \"ABC\".data(using: .utf8)!, // swiftlint:disable:this force_unwrapping\n        checkAlgorithm: checkBrainpoolAlgorithm\n    )\n\n    session.updateAlert(message: NSLocalizedString(\"nfc_txt_msg_success\", comment: \"\"))\n    return outcome\n})\nelse {\n    // handle the case the Session could not be initialized\n----\n\nExecute the operation on the NFC HealthCard. The secure channel (PACE) is established initially before executing the operation.\n\n[source,swift]\n----\nsignedData = try await nfcHealthCardSession.executeOperation()\n----\n\nThe thrown error will be of type `NFCHealthCardSessionError`.\nThe `NFCHealthCardSession` also gives you an endpoint to invalidate the underlying `TagReaderSession`.\n\n[source,swift]\n----\n} catch NFCHealthCardSessionError.coreNFC(.userCanceled) {\n    // error type is always `NFCHealthCardSessionError`\n    // here we especially handle when the user canceled the session\n    Task { @MainActor in self.pState = .idle } // Do some view-property update\n    // Calling .invalidateSession() is not strictly necessary\n    //  since nfcHealthCardSession does it while it's de-initializing.\n    nfcHealthCardSession.invalidateSession(with: nil)\n    return\n} catch {\n    Task { @MainActor in self.pState = .error(error) }\n    nfcHealthCardSession.invalidateSession(with: error.localizedDescription)\n    return\n}\n----\n[#NFCDemo]\n=== NFCDemo\n\nThe NFCDemo iOS App target demonstrates the use of OHCKit and the NFCCardReader[Provider] specifically by utilizing\nsaid framework to connect to and establish a secure communications channel with an eGK Card via NFC.\n\nThe App consist out of two screens/views. The first one will prompt the user for the CAN number.\nThe second prompts for the PIN. This PIN is verified on the card against `mrpinHome` when the `connect` button is tapped.\n\n== License\n\nCopyright 2023 gematik GmbH\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License.\n\nSee the link:./LICENSE[LICENSE] for the specific language governing permissions and limitations under the License.\n\nUnless 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.\n\nThe 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.\n\nGematik may remove published results temporarily or permanently from the place of publication at any time without prior notice or justification.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgematik%2Fref-openhealthcardkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgematik%2Fref-openhealthcardkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgematik%2Fref-openhealthcardkit/lists"}