Swift-ios-skills cryptotokenkit
Access security tokens and smart cards using CryptoTokenKit. Use when building token driver extensions with TKTokenDriver and TKToken, communicating with smart cards via TKSmartCard, implementing certificate-based authentication, managing token sessions, or integrating hardware security tokens with the system keychain.
git clone https://github.com/dpearson2699/swift-ios-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/dpearson2699/swift-ios-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/cryptotokenkit" ~/.claude/skills/dpearson2699-swift-ios-skills-cryptotokenkit && rm -rf "$T"
skills/cryptotokenkit/SKILL.mdCryptoTokenKit
Access security tokens and the cryptographic assets they store using the CryptoTokenKit framework. Covers token driver extensions, smart card communication, token sessions, keychain integration, and certificate-based authentication. Targets Swift 6.3.
Platform availability: CryptoTokenKit is primarily a macOS framework. Smart card reader access (
TKSmartCard, TKSmartCardSlotManager) requires
macOS. Token extension APIs (TKTokenDriver, TKToken, TKTokenSession)
are macOS-only. Client-side token watching (TKTokenWatcher) and keychain
queries filtered by kSecAttrTokenID are available on iOS 14+/macOS 11+.
NFC smart card slot sessions are available on iOS 16.4+.
Contents
- Architecture Overview
- Token Extensions
- Token Sessions
- Smart Card Communication
- Keychain Integration
- Certificate Authentication
- Token Watching
- Error Handling
- Common Mistakes
- Review Checklist
- References
Architecture Overview
CryptoTokenKit bridges hardware security tokens (smart cards, USB tokens) with macOS authentication and keychain services. The framework has two main usage modes:
Token driver extensions (macOS only) -- App extensions that make a hardware token's cryptographic items available to the system. The driver handles token lifecycle, session management, and cryptographic operations.
Client-side token access (macOS + iOS) -- Apps query the keychain for items backed by tokens. CryptoTokenKit automatically exposes token items as standard keychain entries when a token is present.
Key Types
| Type | Role | Platform |
|---|---|---|
| Base class for token driver extensions | macOS |
| Represents a hardware cryptographic token | macOS |
| Manages authentication state for a token | macOS |
| Entry point for smart card extensions | macOS |
| Low-level smart card communication | macOS |
| Discovers and manages card reader slots | macOS |
| Observes token insertion and removal | macOS, iOS 14+ |
| A key stored on a token | macOS |
| A certificate stored on a token | macOS |
Token Extensions
A token driver is a macOS app extension that makes a hardware token's cryptographic capabilities available to the system. The host app exists only as a delivery mechanism for the extension.
A smart card token extension has three core classes:
- TokenDriver (subclass of
) -- entry pointTKSmartCardTokenDriver - Token (subclass of
) -- represents the tokenTKSmartCardToken - TokenSession (subclass of
) -- handles operationsTKSmartCardTokenSession
Driver Class
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 Class
The token reads certificates and keys from hardware and populates its keychain contents:
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 and Registration
The extension's
Info.plist must name the driver class:
NSExtension NSExtensionAttributes com.apple.ctk.driver-class = $(PRODUCT_MODULE_NAME).TokenDriver NSExtensionPointIdentifier = com.apple.ctk-tokens
Register the extension once by launching the host app as
_securityagent:
sudo -u _securityagent /Applications/TokenHost.app/Contents/MacOS/TokenHost
Token Sessions
TKTokenSession manages authentication state and performs cryptographic
operations via its 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 Authentication
Return a
TKTokenAuthOperation from beginAuthFor: to prompt the user
for PIN entry before cryptographic operations:
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 }
Smart Card Communication
TKSmartCard provides low-level APDU communication with smart cards
connected via readers (macOS-only).
Discovering Card Readers
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) } } } }
Sending APDU Commands
Use
send(ins:p1:p2:data:le:) for structured APDU communication.
Always wrap calls in 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) } } }
For raw APDU bytes or non-standard formats, use
transmit(_:reply:) with
manual beginSession/endSession lifecycle management.
NFC Smart Card Sessions (iOS 16.4+)
On supported iOS devices, create NFC smart card sessions to communicate with contactless smart cards:
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 { 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 Integration
When a token is present, CryptoTokenKit exposes its items as standard keychain entries. Query them using the
kSecAttrTokenID attribute:
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 }
Use
kSecReturnPersistentRef instead of kSecReturnRef to obtain a
persistent reference that survives across app launches. The reference
becomes invalid when the token is removed -- handle errSecItemNotFound
by prompting the user to reinsert the token.
Query certificates the same way with
kSecClass: kSecClassCertificate.
Certificate Authentication
Token Key Requirements
For user login, the token must contain at least one key capable of signing with: EC signature digest X962, RSA signature digest PSS, or RSA signature digest PKCS1v15.
For keychain unlock, the token needs:
- 256-bit EC key (
) supportingkSecAttrKeyTypeECSECPrimeRandom
, orecdhKeyExchangeStandard - 2048/3072/4096-bit RSA key (
) supportingkSecAttrKeyTypeRSA
decryptionrsaEncryptionOAEPSHA256
Smart Card Authentication Preferences (macOS)
Configure in the
com.apple.security.smartcard domain (MDM or systemwide):
| Key | Default | Description |
|---|---|---|
| | Enable smart card authentication |
| | Certificate trust level (0-3) |
| | Pair a single smart card to an account |
| | Require smart card for login |
Trust levels:
0 = trust all, 1 = validity + issuer, 2 = + soft
revocation, 3 = + hard revocation.
Token Watching
TKTokenWatcher monitors token insertion and removal. Available on both
macOS and iOS 14+.
import CryptoTokenKit final class TokenMonitor { private let watcher = TKTokenWatcher() func startMonitoring() { for tokenID in watcher.tokenIDs { print("Token present: \(tokenID)") if let info = watcher.tokenInfo(forTokenID: tokenID) { print(" Driver: \(info.driverName ?? "unknown")") print(" Slot: \(info.slotName ?? "unknown")") } } watcher.setInsertionHandler { [weak self] tokenID in print("Token inserted: \(tokenID)") self?.watcher.addRemovalHandler({ removedTokenID in print("Token removed: \(removedTokenID)") }, forTokenID: tokenID) } } }
Error Handling
CryptoTokenKit operations throw
TKError. Key error codes:
| Code | Meaning |
|---|---|
| Operation not supported by this token |
| Communication with token failed |
| Data from token is corrupted |
| User canceled the operation |
| PIN or password incorrect |
| Requested key or certificate not found |
| Token is no longer present |
| Authentication required before operation |
Common Mistakes
DON'T: Query token keychain items without checking token presence
// 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)
DON'T: Assume smart card APIs work on iOS
// WRONG -- TKSmartCardSlotManager.default is nil on iOS let manager = TKSmartCardSlotManager.default! // Crashes on iOS // CORRECT -- guard availability guard let manager = TKSmartCardSlotManager.default else { print("Smart card services unavailable on this platform") return }
DON'T: Skip session management for card communication
// 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 ) }
DON'T: Ignore status words in APDU responses
// 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) }
DON'T: Hard-code blanket algorithm support
The
supports delegate method must reflect what the hardware actually
implements. Returning true unconditionally causes runtime failures when
the system attempts unsupported operations.
Review Checklist
- Platform availability verified (
macOS-only,TKSmartCard
iOS 14+)TKTokenWatcher - Token extension target uses
=NSExtensionPointIdentifiercom.apple.ctk-tokens -
set to the correct driver class in Info.plistcom.apple.ctk.driver-class - Extension registered via
launch during installation_securityagent -
checks specific algorithms, not blanketTKTokenSessionDelegatetrue - Smart card sessions opened and closed (
orwithSession
/beginSession
)endSession - APDU status words checked after every
callsend - Token presence verified via
before keychain queriesTKTokenWatcher -
cases handled with appropriate user feedbackTKError - Keychain contents populated with correct
valuesobjectID -
capabilities (TKTokenKeychainKey
,canSign
) match hardwarecanDecrypt - Certificate trust level configured appropriately for deployment environment
-
handled for persistent references when token is removederrSecItemNotFound
References
- Extended patterns (PIV commands, TLV parsing, generic token drivers, APDU helpers, secure PIN): references/cryptotokenkit-patterns.md
- CryptoTokenKit framework
- TKTokenDriver
- TKToken
- TKTokenSession
- TKSmartCard
- TKSmartCardSlotManager
- TKTokenWatcher
- Authenticating Users with a Cryptographic Token
- Using Cryptographic Assets Stored on a Smart Card
- Configuring Smart Card Authentication