mcpField
C
cryptotokenkit

cryptotokenkit

github.com/dpearson2699安全1.2k3.1k 次安装13 周前更新
代码仓库

关于 cryptotokenkit

cryptotokenkit 是面向 Swift 6.3 Apple 应用开发者的 CryptoTokenKit 技能,涵盖智能卡与硬件令牌接入、令牌密钥链和证书认证。内容还说明平台能力边界、会话处理及常见错误。

主要功能

  1. 01构建智能卡令牌扩展
  2. 02管理令牌会话与 PIN 认证
  3. 03通过 APDU 与智能卡通信
  4. 04查询令牌支持的密钥链项目
  5. 05监视令牌插入与移除
  6. 06实现证书认证

适用场景

  1. 01为 macOS 登录和密钥链解锁接入硬件令牌
  2. 02在应用中读取令牌密钥和证书
  3. 03通过 NFC 智能卡执行卡片通信
  4. 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+
TKSmartCardSlotNFCSessionNFC 支持的智能卡槽会话iOS/iPadOS 26+
TKSmartCardTokenRegistrationManager注册 NFC 智能卡,以便稍后用于钥匙串iOS/iPadOS 26+

令牌扩展

在 macOS 上进行系统登录和钥匙串解锁时,令牌驱动程序是一个应用扩展,可向系统提供硬件令牌的加密功能。宿主应用仅用作分发扩展的载体。

智能卡令牌扩展包含三个核心类:

  1. TokenDriver(TKSmartCardTokenDriver 的子类)-- 入口点
  2. Token(TKSmartCardToken 的子类)-- 表示令牌
  3. 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 或系统范围配置):

键默认值说明
allowSmartCardtrue启用智能卡身份验证
checkCertificateTrust0证书信任级别(0-3)
oneCardPerUserfalse将单张智能卡与帐户配对
enforceSmartCardfalse登录时要求使用智能卡

信任级别:0 = 信任所有证书,1 = 有效性 + 颁发者,2 = + 软撤销检查,3 = + 硬撤销检查。

令牌监视

TKTokenWatcher 监视令牌的插入和移除。适用于 iOS 10+ 和 macOS 10.12+。枚举 tokenIDs,安装插入处理程序,然后为每个已观察到的令牌添加移除处理程序。只要需要监视,就应保持监视器处于活动状态。如需获取插槽级读卡器状态,请使用智能卡插槽监视。

错误处理

CryptoTokenKit 操作会抛出 TKError。重要错误代码:

代码含义
.notImplemented此令牌不支持该操作
.communicationError与令牌通信失败
.corruptedData令牌返回的数据已损坏
.canceledByUser用户取消了操作
.authenticationFailedPIN 或密码不正确
.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() 结束

参考资料

评论

支持 Markdown