cryptotokenkit

cryptotokenkit

熱門

使用 CryptoTokenKit 存取安全令牌(Security Token)与智慧卡(Smart Card)。适用于建置 TKTokenDriver 或 TKSmartCardTokenDriver 扩展、透过 TKSmartCard/TKSmartCardSlotManager 与智慧卡通讯、使用 iOS 26+ NFC 智慧卡工作阶段(Sessions)、注册智慧卡、利用 kSecAttrTokenID 查询由 Token 支持的 Keychain 项目、监视 TKTokenWatcher,以及设定基于凭证的智慧卡身份验证。

967星標
49分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
cryptotokenkit
描述

使用 CryptoTokenKit 存取安全令牌(Security Token)与智慧卡(Smart Card)。适用于建置 TKTokenDriver 或 TKSmartCardTokenDriver 扩展、透过 TKSmartCard/TKSmartCardSlotManager 与智慧卡通讯、使用 iOS 26+ NFC 智慧卡工作阶段(Sessions)、注册智慧卡、利用 kSecAttrTokenID 查询由 Token 支持的 Keychain 项目、监视 TKTokenWatcher,以及设定基于凭证的智慧卡身份验证。

CryptoTokenKit

使用 CryptoTokenKit 可以在 Swift 6.3 应用程式中实现 Token 驱动程式扩展、智慧卡通讯、Token 工作阶段、由 Token 支持的 Keychain 整合,以及基于凭证的身份验证。

平台可用性: CryptoTokenKit 类别适用于 Apple 各平台,但功能支援取决于扩展点(Extension point)、Entitlement 宣告、硬体与 OS 版本。用于登入/Keychain 解锁的智慧卡 App 扩展流程仅支援 macOS。除非已启用智慧卡存取权限,否则 TKSmartCardSlotManager.default 为可选属性且会回传 nil。iOS/iPadOS 26+ 增添了 NFC 智慧卡插槽与注册功能。

目录

架构概览

CryptoTokenKit 可在硬体安全令牌(智慧卡、USB Token)与身份验证及 Keychain 服务之间架起桥梁。此框架主要有三种使用模式:

智慧卡 Token 扩展 -- macOS App 扩展,可将硬体 Token 的加密项目开放给系统登入与 Keychain 解锁使用。驱动程式负责处理 Token 的生命周期、工作阶段管理与密码学运算。

客户端 Token 存取 -- App 查询由 Token 支持的 Keychain 项目。当 Token 存在时,CryptoTokenKit 会将 Token 项目显示为标准 Keychain 条目。

NFC 智慧卡存取 -- iOS/iPadOS 26+ App 可建立临时的 NFC 智慧卡插槽,并透过 TKSmartCard 与读取到的感应式卡片进行通讯。

边界分流: 本 Skill 负责 Token/智慧卡工作阶段、由 Token 支持的 Keychain 项目与基于凭证的智慧卡身份验证。请将 Passkey/WebAuthn 与帐号登入分流至 authentication;将 Secure Enclave、CryptoKit 原语、Keychain 架构、凭证固定(Certificate Pinning)及信任策略(Trust Policy)分流至 swift-security

关键类型

类型 角色 平台
TKTokenDriver / TKToken / TKTokenSession Token 驱动程式、Token 与工作阶段原语 iOS 10+, macOS 10.12+
TKSmartCardTokenDriver 智慧卡 Token 扩展的进入点 iOS 10+, macOS 10.12+;macOS 扩展流程
TKSmartCard / TKSmartCardSlotManager 底层 APDU 通讯与插槽侦测 iOS 9+, macOS 10.10+;default 为可选属性
TKTokenWatcher 监视 Token 的插入与移除 iOS 10+, macOS 10.12+
TKSmartCardSlotNFCSession 基于 NFC 的智慧卡插槽工作阶段 iOS/iPadOS 26+
TKSmartCardTokenRegistrationManager 注册 NFC 智慧卡以供日后 Keychain 使用 iOS/iPadOS 26+

Token 扩展

在 macOS 上进行系统登入与 Keychain 解锁时,Token 驱动程式是一种 App 扩展,能将硬体 Token 的密码学功能提供给系统使用。主应用程式(Host App)仅作为交付该扩展的载体。

智慧卡 Token 扩展包含三个核心类别:

  1. TokenDriverTKSmartCardTokenDriver 的子类别)-- 进入点
  2. TokenTKSmartCardToken 的子类别)-- 代表 Token 本身
  3. TokenSessionTKSmartCardTokenSession 的子类别)-- 处理相关运算

驱动程式类别

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
        )
    }
}

Token 类别

Token 类别从硬体读取凭证与金钥,并填入其 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 身份启动主应用程式,完成一次性的扩展注册:

sudo -u _securityagent /Applications/TokenHost.app/Contents/MacOS/TokenHost

Token 工作阶段

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 整合

当插入/存在 Token 时,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 来取得跨 App 启动依然有效的持久引用(Persistent reference)。当 Token 被拔出/移除时,该引用会失效 -- 请藉由提示使用者重新插入 Token 来处理 errSecItemNotFound

使用 kSecClass: kSecClassCertificate 也可以用同样的方式查询凭证。

凭证身份验证

Token 金钥需求

为了进行使用者登入,Token 必须包含至少一把支援以下签名算法的金钥:EC 签名杂凑 X962、RSA 签名杂凑 PSS 或 RSA 签名杂凑 PKCS1v15。

若要解密/解锁 Keychain,Token 需要:

  • 支援 ecdhKeyExchangeStandard 的 256 位元 EC 金钥(kSecAttrKeyTypeECSECPrimeRandom),或
  • 支援 rsaEncryptionOAEPSHA256 解密的 2048/3072/4096 位元 RSA 金钥(kSecAttrKeyTypeRSA

智慧卡身份验证偏好设定(macOS)

com.apple.security.smartcard 网域中设定(透过 MDM 或全系统级):

| Key | Default | Descripti