关于 cryptotokenkit
cryptotokenkit 是面向 Swift 6.3 Apple 应用开发者的 CryptoTokenKit 技能,涵盖智能卡与硬件令牌接入、令牌密钥链和证书认证。内容还说明平台能力边界、会话处理及常见错误。
主要功能
- 01构建智能卡令牌扩展
- 02管理令牌会话与 PIN 认证
- 03通过 APDU 与智能卡通信
- 04查询令牌支持的密钥链项目
- 05监视令牌插入与移除
- 06实现证书认证
适用场景
- 01为 macOS 登录和密钥链解锁接入硬件令牌
- 02在应用中读取令牌密钥和证书
- 03通过 NFC 智能卡执行卡片通信
- 04排查令牌通信与认证错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill cryptotokenkit安装后在 Claude Code 中输入/cryptotokenkit确认已加载。
附带文件evals/evals.jsonreferences/cryptotokenkit-patterns.md
详细介绍
CryptoTokenKit
在 Swift 6.3 应用中使用 CryptoTokenKit 扩展令牌驱动程序、与智能卡通信、管理令牌会话、集成令牌支持的钥匙串,以及进行基于证书的身份验证。
平台可用性: CryptoTokenKit 类可用于各种 Apple 平台,但具体功能取决于扩展点、授权、硬件和 OS 版本。用于登录/解锁钥匙串的智能卡应用扩展流程适用于 macOS。除非启用了智能卡访问,否则 TKSmartCardSlotManager.default 是可选值,并会返回 nil。iOS/iPadOS 26+ 新增 NFC 智能卡槽和注册功能。
目录
架构概述
CryptoTokenKit 将硬件安全令牌(智能卡、USB 令牌)与身份验证和钥匙串服务连接起来。该框架主要有三种用法:
智能卡令牌扩展 -- macOS 应用扩展,可使硬件令牌的加密项目用于系统登录和钥匙串解锁。驱动程序负责处理令牌生命周期、会话管理和加密操作。
客户端令牌访问 -- 应用查询钥匙串,获取由令牌支持的项目。令牌存在时,CryptoTokenKit 会将令牌项目作为标准钥匙串条目提供。
NFC 智能卡访问 -- iOS/iPadOS 26+ 应用可创建临时 NFC 智能卡槽,并通过 TKSmartCard 与放置在读卡器上的非接触式卡片通信。
边界路由: 管理令牌/智能卡会话、令牌支持的钥匙串项目,以及基于证书的智能卡身份验证。将 passkey/WebAuthn 和账户登录路由至 authentication;将 Secure Enclave、CryptoKit 原语、钥匙串架构、证书固定和信任策略路由至 swift-security。
关键类型
| 类型 | 作用 | 平台 |
|---|---|---|
TKTokenDriver / TKToken / TKTokenSession | 令牌驱动程序、令牌和会话原语 | iOS 10+、macOS 10.12+ |
TKSmartCardTokenDriver | 智能卡令牌扩展的入口点 | iOS 10+、macOS 10.12+;macOS 扩展流程 |
TKSmartCard / TKSmartCardSlotManager | 低级 APDU 通信和插槽发现 | iOS 9+、macOS 10.10+;default 是可选值 |
TKTokenWatcher | 监视令牌插入和移除 | iOS 10+、macOS 10.12+ |
TKSmartCardSlotNFCSession | NFC 支持的智能卡槽会话 | iOS/iPadOS 26+ |
TKSmartCardTokenRegistrationManager | 注册 NFC 智能卡,以便稍后用于钥匙串 | iOS/iPadOS 26+ |
令牌扩展
在 macOS 上进行系统登录和钥匙串解锁时,令牌驱动程序是一个应用扩展,可向系统提供硬件令牌的加密功能。宿主应用仅用作分发扩展的载体。
智能卡令牌扩展包含三个核心类:
- TokenDriver(
TKSmartCardTokenDriver的子类)-- 入口点 - Token(
TKSmartCardToken的子类)-- 表示令牌 - TokenSession(
TKSmartCardTokenSession的子类)-- 处理操作
驱动程序类
import CryptoTokenKit
final class TokenDriver: TKSmartCardTokenDriver, TKSmartCardTokenDriverDelegate {
func tokenDriver(
_ driver: TKSmartCardTokenDriver,
createTokenFor smartCard: TKSmartCard,
aid: Data?
) throws -> TKSmartCardToken {
return try Token(
smartCard: smartCard,
aid: aid,
instanceID: "com.example.token:\(smartCard.slot.name)",
tokenDriver: driver
)
}
}
令牌类
令牌从硬件读取证书和密钥,并填充其钥匙串内容:
final class Token: TKSmartCardToken, TKTokenDelegate {
init(
smartCard: TKSmartCard, aid: Data?,
instanceID: String, tokenDriver: TKSmartCardTokenDriver
) throws {
try super.init(
smartCard: smartCard, aid: aid,
instanceID: instanceID, tokenDriver: tokenDriver
)
self.delegate = self
let certData = try readCertificate(from: smartCard)
guard let cert = SecCertificateCreateWithData(nil, certData as CFData) else {
throw TKError(.corruptedData)
}
let certItem = TKTokenKeychainCertificate(certificate: cert, objectID: "cert-auth")
let keyItem = TKTokenKeychainKey(certificate: cert, objectID: "key-auth")
keyItem?.canSign = true
keyItem?.canDecrypt = false
keyItem?.isSuitableForLogin = true
self.keychainContents?.fill(with: [certItem!, keyItem!])
}
func createSession(_ token: TKToken) throws -> TKTokenSession {
TokenSession(token: token)
}
}
Info.plist 和注册
扩展的 Info.plist 必须指定驱动程序类:
NSExtension
NSExtensionAttributes
com.apple.ctk.driver-class = $(PRODUCT_MODULE_NAME).TokenDriver
NSExtensionPointIdentifier = com.apple.ctk-tokens
通过以 _securityagent 身份启动宿主应用,注册一次扩展:
sudo -u _securityagent /Applications/TokenHost.app/Contents/MacOS/TokenHost
令牌会话
TKTokenSession 管理身份验证状态,并通过其代理执行加密操作。
final class TokenSession: TKSmartCardTokenSession, TKTokenSessionDelegate {
func tokenSession(
_ session: TKTokenSession,
supports operation: TKTokenOperation,
keyObjectID: TKToken.ObjectID,
algorithm: TKTokenKeyAlgorithm
) -> Bool {
switch operation {
case .signData:
return algorithm.isAlgorithm(.rsaSignatureDigestPKCS1v15SHA256)
|| algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA256)
case .decryptData:
return algorithm.isAlgorithm(.rsaEncryptionOAEPSHA256)
case .performKeyExchange:
return algorithm.isAlgorithm(.ecdhKeyExchangeStandard)
default:
return false
}
}
func tokenSession(
_ session: TKTokenSession,
sign dataToSign: Data,
keyObjectID: TKToken.ObjectID,
algorithm: TKTokenKeyAlgorithm
) throws -> Data {
let smartCard = try getSmartCard()
return try smartCard.withSession {
try performCardSign(smartCard: smartCard, data: dataToSign, keyID: keyObjectID)
}
}
func tokenSession(
_ session: TKTokenSession,
decrypt ciphertext: Data,
keyObjectID: TKToken.ObjectID,
algorithm: TKTokenKeyAlgorithm
) throws -> Data {
let smartCard = try getSmartCard()
return try smartCard.withSession {
try performCardDecrypt(smartCard: smartCard, data: ciphertext, keyID: keyObjectID)
}
}
}
PIN 身份验证
从 beginAuthFor: 返回一个 TKTokenAuthOperation,在执行加密操作前提示用户输入 PIN:
func tokenSession(
_ session: TKTokenSession,
beginAuthFor operation: TKTokenOperation,
constraint: Any
) throws -> TKTokenAuthOperation {
let pinAuth = TKTokenSmartCardPINAuthOperation()
pinAuth.pinFormat.charset = .numeric
pinAuth.pinFormat.minPINLength = 4
pinAuth.pinFormat.maxPINLength = 8
pinAuth.smartCard = (session as? TKSmartCardTokenSession)?.smartCard
pinAuth.apduTemplate = buildVerifyAPDU()
pinAuth.pinByteOffset = 5
return pinAuth
}
智能卡通信
TKSmartCard 提供与智能卡进行低级 APDU 通信的能力。
TKSmartCardSlotManager.default 是可选的;如果为 nil,请视为硬件不可用、缺少授权/访问权限或运行时不支持。
发现读卡器
import CryptoTokenKit
func discoverSmartCards() {
guard let slotManager = TKSmartCardSlotManager.default else {
print("Smart card services unavailable")
return
}
for slotName in slotManager.slotNames {
slotManager.getSlot(withName: slotName) { slot in
guard let slot else { return }
if slot.state == .validCard, let card = slot.makeSmartCard() {
communicateWith(card: card)
}
}
}
}
发送 APDU 命令
使用 send(ins:p1:p2:data:le:) 进行结构化 APDU 通信。
始终在 withSession 中包装调用:
func selectApplication(card: TKSmartCard, aid: Data) throws {
try card.withSession {
let (sw, response) = try card.send(
ins: 0xA4, p1: 0x04, p2: 0x00, data: aid, le: nil
)
guard sw == 0x9000 else {
throw TKError(.communicationError)
}
}
}
对于原始 APDU 字节或非标准格式,请使用 transmit(_:reply:),并手动管理 beginSession/endSession 生命周期。
NFC 智能卡会话(iOS/iPadOS 26+)
在 iOS/iPadOS 26+ 上,调用 createNFCSlot(message:completion:) 与非接触式卡片通信前,请先检查 isNFCSupported():
@available(iOS 26.0, iPadOS 26.0, *)
func readNFCSmartCard() {
guard let slotManager = TKSmartCardSlotManager.default,
slotManager.isNFCSupported() else { return }
slotManager.createNFCSlot(message: "Hold card near iPhone") { session, error in
guard let session else {
handleNFCError(error)
return
}
defer { session.end() }
guard let slotName = session.slotName,
let slot = slotManager.slotNamed(slotName),
let card = slot.makeSmartCard() else { return }
// Communicate with the NFC card using card.send(...)
}
}
钥匙串集成
有令牌时,CryptoTokenKit 会将其项目公开为标准钥匙串条目。使用 kSecAttrTokenID 属性查询:
import Security
func findTokenKey(tokenID: String) throws -> SecKey {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrTokenID as String: tokenID,
kSecReturnRef as String: true
]
var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let key = result else {
throw TKError(.objectNotFound)
}
return key as! SecKey
}
使用 kSecReturnPersistentRef 而不是 kSecReturnRef,以获取可跨应用启动保留的持久引用。移除令牌后,该引用会失效——遇到 errSecItemNotFound 时,请提示用户重新插入令牌。
使用相同方式查询证书,并设置 kSecClass: kSecClassCertificate。
证书身份验证
令牌密钥要求
对于用户登录,令牌必须至少包含一个支持以下签名方式的密钥:EC 签名摘要 X962、RSA 签名摘要 PSS,或 RSA 签名摘要 PKCS1v15。
对于钥匙串解锁,令牌需要:
- 支持
ecdhKeyExchangeStandard的 256 位 EC 密钥(kSecAttrKeyTypeECSECPrimeRandom),或 - 支持使用
rsaEncryptionOAEPSHA256解密的 2048/3072/4096 位 RSA 密钥(kSecAttrKeyTypeRSA)
智能卡身份验证偏好设置(macOS)
在 com.apple.security.smartcard 域中配置(通过 MDM 或系统范围配置):
| 键 | 默认值 | 说明 |
|---|---|---|
allowSmartCard | true | 启用智能卡身份验证 |
checkCertificateTrust | 0 | 证书信任级别(0-3) |
oneCardPerUser | false | 将单张智能卡与帐户配对 |
enforceSmartCard | false | 登录时要求使用智能卡 |
信任级别:0 = 信任所有证书,1 = 有效性 + 颁发者,2 = + 软撤销检查,3 = + 硬撤销检查。
令牌监视
TKTokenWatcher 监视令牌的插入和移除。适用于 iOS 10+ 和 macOS 10.12+。枚举 tokenIDs,安装插入处理程序,然后为每个已观察到的令牌添加移除处理程序。只要需要监视,就应保持监视器处于活动状态。如需获取插槽级读卡器状态,请使用智能卡插槽监视。
错误处理
CryptoTokenKit 操作会抛出 TKError。重要错误代码:
| 代码 | 含义 |
|---|---|
.notImplemented | 此令牌不支持该操作 |
.communicationError | 与令牌通信失败 |
.corruptedData | 令牌返回的数据已损坏 |
.canceledByUser | 用户取消了操作 |
.authenticationFailed | PIN 或密码不正确 |
.objectNotFound | 未找到请求的密钥或证书 |
.tokenNotFound | 令牌已不存在 |
.authenticationNeeded | 执行操作前需要进行身份验证 |
常见错误
不要:未检查令牌是否存在就查询令牌钥匙串项目
// WRONG -- query may fail if token was removed
let key = try findTokenKey(tokenID: savedTokenID)
// CORRECT -- verify the token is still present first
let watcher = TKTokenWatcher()
guard watcher.tokenIDs.contains(savedTokenID) else {
promptUserToInsertToken()
return
}
let key = try findTokenKey(tokenID: savedTokenID)
不要:将 API 可用性当作访问权限保证
// WRONG -- may be nil without entitlement, hardware, or runtime support
let manager = TKSmartCardSlotManager.default! // Crashes when unavailable
// CORRECT -- guard availability/access before using smart card slots
guard let manager = TKSmartCardSlotManager.default else {
print("Smart card services unavailable")
return
}
不要:跳过卡片通信的会话管理
// WRONG -- sending commands without a session
card.transmit(apdu) { response, error in /* may fail */ }
// CORRECT -- use withSession or beginSession/endSession
try card.withSession {
let (sw, response) = try card.send(
ins: 0xCA, p1: 0x00, p2: 0x6E, data: nil, le: 0
)
}
不要:忽略 APDU 响应中的状态字
// WRONG -- assuming success
let (_, response) = try card.send(ins: 0xA4, p1: 0x04, p2: 0x00, data: aid, le: nil)
// CORRECT -- check status word
let (sw, response) = try card.send(ins: 0xA4, p1: 0x04, p2: 0x00, data: aid, le: nil)
guard sw == 0x9000 else {
throw SmartCardError.commandFailed(statusWord: sw)
}
不要:硬编码一概适用的算法支持
supports 委托方法必须如实反映硬件实际实现的功能。无条件返回 true 会导致系统尝试执行不受支持的操作时发生运行时错误。
审查清单
- 已针对确切功能验证平台可用性(
TKTokenWatcher支持 iOS 10+,NFC 智能卡会话支持 iOS/iPadOS 26+) - 对缺少授权、硬件或运行时支持的情况进行了
TKSmartCardSlotManager.default防护 - macOS 令牌扩展目标使用
NSExtensionPointIdentifier=com.apple.ctk-tokens - 在 Info.plist 中将
com.apple.ctk.driver-class设为正确的驱动程序类 - 安装期间通过启动
_securityagent注册扩展 -
TKTokenSessionDelegate检查特定算法,而不是一概返回true - 已打开并关闭智能卡会话(使用
withSession或beginSession/endSession) - 每次调用
send后均检查 APDU 状态字 - 查询钥匙串前,通过
TKTokenWatcher验证令牌是否存在 - 使用适当的用户反馈处理
TKError情况 - 使用正确的
objectID值填充钥匙串内容 -
TKTokenKeychainKey的功能(canSign、canDecrypt)与硬件相符 - 根据部署环境正确配置证书信任级别
- 令牌移除后,处理持久引用对应的
errSecItemNotFound - iOS 26+ NFC 会话通过
TKSmartCardSlotNFCSession.end()结束
参考资料
- 扩展模式(PIV 命令、TLV 解析、通用令牌驱动程序、APDU 辅助工具、安全 PIN):references/cryptotokenkit-patterns.md
- TKTokenDriver
- TKToken
- TKTokenSession
- TKSmartCard
- TKSmartCardSlotManager
- com.apple.security.smartcard entitlement
- TKSmartCardSlotNFCSession
- TKSmartCardTokenRegistrationManager
- TKTokenWatcher
- 使用加密令牌验证用户身份
- 使用存储在智能卡上的加密资产
- 配置智能卡身份验证