用于访问远程信息处理基础设施的智能卡的控制/用例框架。
OpenHealthCardKit 模块旨在用于参考,当实现一个系统,该系统使用 NFC、蓝牙或 USB 接口执行基于 iOS 的移动设备和德国健康卡(elektronische Gesundheitskarte)之间的通信时。
本文档描述了 OpenHealthCardKit 的功能和结构。
生成的 API 文档可在Swift Package Index找到
HealthCardControl (待定)
注意 |
目前,HealthCardControl 的自动 API 文档生成已损坏。 可以通过 Xcode 手动生成:选择目标 HealthCardControl 并选择 Product → Build Documentation 。 |
OpenHealthCardKit 需要 Swift 5.6。
Swift Package Manager: 将此放入您的 Package.swift
中
.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.6.0"),
Carthage: 将此放入您的 Cartfile
中
github "gematik/ref-openHealthCardKit" ~> 5.0
OpenHealthCardKit 捆绑了子模块,这些子模块提供了通过移动 iOS 设备访问和与德国健康卡交互所需的功能。
OpenHealthCardKit 由以下子模块组成:
CardReaderProviderApi
HealthCardAccess
HealthCardControl
NFCCardReaderProvider
作为每个子模块用法的参考,另请参见 IntegrationTests
。
该库包含卡、命令、卡文件系统和错误处理的类。
HealthCardAccessKit API 结构包含 HealthCard
类,该类表示所有支持的卡类型,Commands
和 Responses
组,其中包含所有受支持的健康卡命令和响应,CardObjects
组,其中包含健康卡上可能的对象以及 Operation
组,用于级联和执行健康卡上的命令。
类 HealthCard
通过存储 HealthCardStatus
属性来表示潜在的健康卡类型,如果该属性为有效,则它本身存储一个 HealthCardPropertyType
,在撰写本文时,该属性由以下任一项表示
egk ("elektronische Gesundheitskarte")
hba ("Heilberufeausweis")
smcb ("Security Module Card Typ B")。
HealthCardPropertyType
本身也存储 CardGeneration
(G1、G1P、G2、G2.1)。
此外,HealthCard
对象包含来自读卡器的物理卡和当前卡通道。
此 API 的设计遵循 命令设计模式,并利用 Swift 的 Combine Framework。 命令对象旨在满足 Gematik COS 规范中描述的用例。 创建命令对象或序列后,您可以使用 publisher(for:)
在 Healthcard 上执行它。 有关如何配置命令的更多信息,也可以在 Gematik COS 规范中找到。
以下示例应向智能卡发送一个 SELECT 和一个 READ 命令,以便选择并读取存储在应用程序 ESIGN 中的文件 EF.C.CH.AUT.R2048 中的证书。
首先,我们要通过传递 ApplicationIdentifier
来创建一个 SelectCommand
对象。 我们通过使用 HealthCardCommand.Select
使用一个预定义的辅助函数。
也可以使用 HealthCardCommandBuilder
通过手动设置 APDU 字节来构造自定义的 HealthCardCommand
。
let eSign = EgkFileSystem.DF.ESIGN
let selectEsignCommand = HealthCardCommand.Select.selectFile(with: eSign.aid)
我们执行已创建的命令 CardType
实例,该实例通常由 CardReaderType
提供。
在下一个示例中,我们使用一个 HealthCard
对象,该对象代表 eGK(elektronische Gesundheitskarte),作为实现 CardType
协议的 HealthCardType
的一种,然后将命令发送到卡(或卡的通道)
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
}
以下段落描述了通过 Combine 接口执行命令的已弃用方式
可以使用 publisher(for:writetimeout:readtimeout)
将创建的命令提升到 Combine 框架。 可以针对预期的 ResponseStatus
(例如,SUCCESS (0x9000))验证命令执行的结果。
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
}
可以通过 flatMap
运算符链接更多命令以进行后续执行:首先创建一个命令并将其提升到 Combine monad 上,然后使用 flatMap
运算符创建一个发布者,例如
Just(AnyHealthCardCommand.build())
.flatMap { command in command.pusblisher(for: card) }
最终使用 eraseToAnyPublisher()
。
let readCertificate = checkResponse
.tryMap { _ -> HealthCardCommandType in
let sfi = EgkFileSystem.EF.esignCChAutR2048.sfid!
return try HealthCardCommand.Read.readFileCommand(with: sfi, ne: 0x076C - 1)
}
.flatMap { command in
command.publisher(for: eGk)
}
.eraseToAnyPublisher()
当整个命令链设置完毕后,我们必须订阅它。 我们实际上只会收到一个完成前的值,因此像这个 sink()
便捷发布者这样的东西非常有用。
_ = readCertificate
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
Logger.integrationTest.debug("Completed")
case let .failure(error):
Logger.integrationTest.debug("Error: \(error)")
}
},
receiveValue: { healthCardResponse in
Logger.integrationTest.debug("Got a certifcate")
let certificate = healthCardResponse.data!
Logger.integrationTest.debug("Certificate: \(certificate.hexString())")
// proceed with certificate data here
// use swiftUI to a show success message on screen etc.
}
)
可以使用此库来实现通过移动设备与德国健康卡(eGk、elektronische Gesundheitskarte)交互的用例。
通常,您会将此库用作移动应用程序的高级 API 网关,以将预定义的命令链发送到健康卡并解释响应。
有关更多信息,请找到低级部分 HealthCardAccess
和 GitHub 上的 Demo App。
请参阅 Gematik GitHub IO 页面以获取更全面的概述。
采取必要的准备步骤,以便在健康卡上签名一个挑战,然后对其进行签名。
let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
let format2Pin = try Format2Pin(pincode: "123456")
_ = try await Self.healthCard.verifyAsync(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
let signResponse = try await Self.healthCard.signAsync(data: challenge)
expect(signResponse.responseStatus) == ResponseStatus.success
封装 PACE 协议步骤,以建立与健康卡的安全通道,并仅公开一个简单的 API 调用。
let secureMessaging = try await KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128.negotiateSessionKeyAsync(
card: CardSimulationTerminalTestCase.healthCard,
can: can,
writeTimeout: 0,
readTimeout: 10
)
有关更多已实现的用例,请参见集成测试 IntegrationTests/HealthCardControl/。
CardReaderProvider
实现,用于处理与 Apple iPhone NFC 接口的通信。
为了方便起见,NFCCardReaderSession
将 NFC 接口的使用与 HealthCardAccess/HealthCardControl
层结合在一起。
初始化程序接受一些 NFC 显示消息、CAN(卡访问号码)和一个包含 NFCHealthCardSessionHandle
的闭包,以将命令/响应发送/接收到/从 NFC 健康卡,并更新用户的界面消息。
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 NFCSigningFunctionController.Error.wrongPin(retryCount: count)
} else if case VerifyPinResponse.passwordBlocked = verifyPinResponse {
throw NFCSigningFunctionController.Error.passwordBlocked
} else if VerifyPinResponse.success != verifyPinResponse {
throw NFCSigningFunctionController.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
在 NFC 健康卡上执行操作。 在执行操作之前,会首先建立安全通道 (PACE)。
signedData = try await nfcHealthCardSession.executeOperation()
抛出的错误类型为 NFCHealthCardSessionError
。 NFCHealthCardSession
还为您提供了一个端点来使底层 TagReaderSession
无效。
} 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
}
版权所有 2023 gematik GmbH
根据 Apache License, Version 2.0(“许可证”)获得许可;除非遵守许可证,否则您不得使用此文件。
有关管理权限和限制的特定语言,请参见 许可证。
除非适用法律要求,否则该软件按“原样”提供,不提供任何形式的明示或暗示的担保,包括但不限于对特定用途的适用性、适销性和/或不侵权的担保。 作者或版权所有者在任何情况下均不对因软件或软件的使用或其他交易而引起或与之相关的任何损害或其他索赔承担任何责任,无论是在合同诉讼、侵权诉讼还是其他诉讼中。
该软件是研发活动的成果,因此不一定经过质量保证,并且不具有应承担责任的产品特性。 因此,gematik 不提供任何支持或其他用户帮助(除非在个别情况下另有说明,且没有法律义务的正当理由)。 此外,不要求进一步开发和调整结果以适应最新的技术水平。
Gematik 可以在任何时候从发布地点临时或永久删除已发布的结果,而无需事先通知或说明理由。