cryptotokenkit について
cryptotokenkitは、CryptoTokenKitを使うSwift 6.3のAppleアプリ開発者向けスキルです。スマートカードやハードウェアトークン、トークン連携キーチェーン、証明書認証に加え、プラットフォームの制約とセッション管理を扱います。
主な機能
- 01スマートカードトークン拡張の構築
- 02トークンセッションとPIN認証の管理
- 03APDUによるカード通信
- 04トークン連携キーチェーン項目の検索
- 05トークンの挿入・取り外しの監視
- 06証明書認証の実装
活用シーン
- 01macOSのログインとキーチェーン解除にハードウェアトークンを接続
- 02アプリでトークンの鍵や証明書を読み取る
- 03NFCスマートカードと通信する
- 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 以降 |
TKSmartCardSlotNFCSession | NFC によるスマートカードスロットセッション | iOS/iPadOS 26 以降 |
TKSmartCardTokenRegistrationManager | 後でキーチェーンで使用する NFC スマートカードを登録 | iOS/iPadOS 26 以降 |
トークン拡張
macOS でのシステムログインとキーチェーンのロック解除に使用するトークンドライバーは、ハードウェアトークンの暗号機能をシステムで利用できるようにするアプリ拡張です。ホストアプリは、拡張を配布するためだけに存在します。
スマートカードトークン拡張には、次の 3 つの主要なクラスがあります。
- 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 認証
暗号操作の前にユーザーに 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 またはシステム全体)で設定します。
| キー | デフォルト | 説明 |
|---|---|---|
allowSmartCard | true | スマートカード認証を有効にします |
checkCertificateTrust | 0 | 証明書の信頼レベル(0-3) |
oneCardPerUser | false | 1 枚のスマートカードをアカウントに紐付けます |
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 エンタイトルメント
- TKSmartCardSlotNFCSession
- TKSmartCardTokenRegistrationManager
- TKTokenWatcher
- 暗号トークンを使用したユーザー認証
- スマートカードに保存された暗号資産の使用
- スマートカード認証の設定