mcpField
C
cryptotokenkit

cryptotokenkit

github.com/dpearson2699セキュリティ1.2k3.1k 回インストール13 週間前に更新
リポジトリ

cryptotokenkit について

cryptotokenkitは、CryptoTokenKitを使うSwift 6.3のAppleアプリ開発者向けスキルです。スマートカードやハードウェアトークン、トークン連携キーチェーン、証明書認証に加え、プラットフォームの制約とセッション管理を扱います。

主な機能

  1. 01スマートカードトークン拡張の構築
  2. 02トークンセッションとPIN認証の管理
  3. 03APDUによるカード通信
  4. 04トークン連携キーチェーン項目の検索
  5. 05トークンの挿入・取り外しの監視
  6. 06証明書認証の実装

活用シーン

  1. 01macOSのログインとキーチェーン解除にハードウェアトークンを接続
  2. 02アプリでトークンの鍵や証明書を読み取る
  3. 03NFCスマートカードと通信する
  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 トークン)と認証サービスおよびキーチェーンサービスを連携させます。このフレームワークには、主に 3 つの利用方法があります。

スマートカードトークン拡張 -- ハードウェアトークンの暗号アイテムをシステムログインやキーチェーンのロック解除に利用できるようにする macOS アプリ拡張です。ドライバーはトークンのライフサイクル、セッション管理、暗号処理を担当します。

クライアント側のトークンアクセス -- アプリは、トークンに裏付けられたアイテムをキーチェーンに問い合わせます。トークンが存在すると、CryptoTokenKit はトークンアイテムを標準のキーチェーンエントリーとして公開します。

NFC スマートカードアクセス -- iOS/iPadOS 26 以降のアプリでは、一時的な NFC スマートカードスロットを作成し、提示された非接触型カードと TKSmartCard を介して通信できます。

境界の振り分け: トークン/スマートカードのセッション、トークンに裏付けられたキーチェーンアイテム、証明書ベースのスマートカード認証を扱います。パスキー/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 でのシステムログインとキーチェーンのロック解除に使用するトークンドライバーは、ハードウェアトークンの暗号機能をシステムで利用できるようにするアプリ拡張です。ホストアプリは、拡張を配布するためだけに存在します。

スマートカードトークン拡張には、次の 3 つの主要なクラスがあります。

  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 認証

暗号操作の前にユーザーに PIN の入力を求めるには、beginAuthFor: から TKTokenAuthOperation を返します。

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 コマンドの送信

構造化された APDU 通信には send(ins:p1:p2:data:le:) を使用します。 呼び出しは必ず 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+ では、非接触型カードと通信する前に isNFCSupported() で確認してから createNFCSlot(message:completion:) を呼び出します。

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

アプリの起動をまたいで有効な永続参照を取得するには、kSecReturnRef ではなく kSecReturnPersistentRef を使用します。トークンを取り外すと参照は無効になります。ユーザーにトークンの再挿入を求める際は、errSecItemNotFound を処理してください。

証明書も同様に、kSecClass: kSecClassCertificate を指定して検索します。

証明書認証

トークンキーの要件

ユーザーログインには、トークンに次のいずれかの方式で署名できるキーが少なくとも 1 つ必要です。EC signature digest X962、RSA signature digest PSS、または RSA signature digest PKCS1v15。

キーチェーンのロック解除には、トークンに次のいずれかのキーが必要です。

  • ecdhKeyExchangeStandard をサポートする 256-bit EC キー(kSecAttrKeyTypeECSECPrimeRandom)、または
  • rsaEncryptionOAEPSHA256 による復号をサポートする 2048/3072/4096-bit RSA キー(kSecAttrKeyTypeRSA)
スマートカード認証の環境設定(macOS)

com.apple.security.smartcard ドメイン(MDM またはシステム全体)で設定します。

キーデフォルト説明
allowSmartCardtrueスマートカード認証を有効にします
checkCertificateTrust0証明書の信頼レベル(0-3)
oneCardPerUserfalse1 枚のスマートカードをアカウントに紐付けます
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() で終了済み

参考資料

コメント