使用 CryptoTokenKit 访问安全令牌与智能卡。适用于开发 TKTokenDriver 或 TKSmartCardTokenDriver 扩展、通过 TKSmartCard/TKSmartCardSlotManager 与智能卡通信、使用 iOS 26+ NFC 智能卡会话、注册智能卡、使用 kSecAttrTokenID 查询令牌关联的 Keychain 项、监听 TKTokenWatcher 状态,以及配置基于证书的智能卡身份验证。
CryptoTokenKit
在 Swift 6.3 应用中使用 CryptoTokenKit 实现令牌驱动扩展(token driver extensions)、智能卡通信、令牌会话(token sessions)、基于令牌的 Keychain 集成以及基于证书的身份验证。
平台可用性: CryptoTokenKit 的类在 Apple 各个平台上均可用,但具体功能取决于扩展点(extension point)、Entitlement 权限、硬件设备和 OS 版本。用于系统登录/Keychain 解锁的智能卡 App 扩展流程仅支持 macOS。TKSmartCardSlotManager.default 是可选的(optional),除非已启用智能卡访问权限,否则会返回 nil。iOS/iPadOS 26+ 新增了 NFC 智能卡插槽和注册支持。
目录
架构概览
CryptoTokenKit 充当硬件安全令牌(智能卡、USB 令牌等)与系统身份验证及 Keychain 服务之间的桥梁。该框架主要有三种使用模式:
智能卡令牌扩展 —— macOS App 扩展,可将硬件令牌的密码学能力提供给系统登录和 Keychain 解锁使用。驱动程序负责处理令牌的生命周期、会话管理和密码学操作。
客户端令牌访问 —— 应用查询由令牌支持的 Keychain 项。当存在令牌时,CryptoTokenKit 会将令牌中的条目暴露为标准 Keychain 记录。
NFC 智能卡访问 —— iOS/iPadOS 26+ 应用可创建临时的 NFC 智能卡插槽,并通过 TKSmartCard 与贴近的非接触式智能卡进行通信。
边界路由: 自身令牌/智能卡会话、令牌关联的 Keychain 项以及基于证书的智能卡认证交由本框架;Passkey/WebAuthn 及账号登录请路由至 authentication;Secure Enclave、CryptoKit 密码学原语、Keychain 整体架构、证书固定(certificate pinning)及信任策略请路由至 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 智能卡以供后续 Keychain 使用 | iOS/iPadOS 26+ |
令牌扩展
在 macOS 上进行系统登录和 Keychain 解锁时,令牌驱动是一个 App 扩展,它负责向系统暴露硬件令牌的密码学能力。宿主 App(host app)仅作为交付该扩展的载体。
一个智能卡令牌扩展包含三个核心类:
- 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
)
}
}
令牌类
令牌类负责从硬件读取证书和密钥,并填充其 Keychain 内容:
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 用户身份运行一次宿主 App 来完成扩展注册:
sudo -u _securityagent /Applications/TokenHost.app/Contents/MacOS/TokenHost
令牌会话
TKTokenSession 用于管理身份验证状态,并通过其 Delegate 执行密码学操作。
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 时,应视为无可用硬件、缺失 Entitlement 访问权限或运行时环境不支持。
发现读卡器
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(...)
}
}
Keychain 集成
当插入令牌时,CryptoTokenKit 会将其条目暴露为标准的 Keychain 记录。可通过 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。
对于 Keychain 解锁,令牌需要具备:
- 支持
ecdhKeyExchangeStandard的 256 位 EC 密钥(kSecAttrKeyTypeECSECPrimeRandom),或 - 支持
rsaEncryptionOAEPSHA256解密的 2048/3072/4096 位 RSA 密钥(kSecAttrKeyTypeRSA)
智能卡身份验证偏好设置(macOS)
在 com.apple.security.smartcard 域名下配置(通过 MDM 或全局系统配置):
| 键名 | 默认值 | 描述




