
Cryptotokenkit
- 2k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
cryptotokenkit is an agent skill for Access security tokens and smart cards using CryptoTokenKit. Use when building TKTokenDriver or TKSmartCardTokenDriver extensions, communicating with smart cards via TKSmartCard/TK
About
Access security tokens and smart cards using CryptoTokenKit. Use when building TKTokenDriver or TKSmartCardTokenDriver extensions, communicating with smart cards via TKSmartCard/TKSmartCardSlotManager, using iOS 26+ NFC smart-card sessions, registering smart cards, querying token-backed keychain items with kSecAttrTokenID, monitoring TKTokenWatcher, or configuring certificate-based smart-card auth The cryptotokenkit skill documents workflows and patterns from the repository SKILL.md. --- name: cryptotokenkit description: "Access security tokens and smart cards using CryptoTokenKit. Use when building TKTokenDriver or TKSmartCardTokenDriver extensions, communicating with smart cards via TKSmartCard/TKSmartCardSlotManager, using iOS 26+ NFC smart-card sessions, registering smart cards, querying token-backed keychain items with kSecAttrTokenID, monitoring TKTokenWatcher, or configuring certificate-based smart-card authentication." --- # CryptoTokenKit Use CryptoTokenKit for token driver extensions, smart-card communication, token sessions, token-backed keychain integration, and certificate-based authentication in Swift 6.3 apps. **Platform availability:** CryptoTokenKit classes are av.
- [Architecture Overview](#architecture-overview)
- [Token Extensions](#token-extensions)
- [Token Sessions](#token-sessions)
- [Smart Card Communication](#smart-card-communication)
- [Keychain Integration](#keychain-integration)
Cryptotokenkit by the numbers
- 2,045 all-time installs (skills.sh)
- +106 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #116 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
cryptotokenkit capabilities & compatibility
- Capabilities
- [architecture overview](#architecture overview) · [token extensions](#token extensions) · [token sessions](#token sessions) · [smart card communication](#smart card communica · [keychain integration](#keychain integration)
- Use cases
- documentation
What cryptotokenkit says it does
--- name: cryptotokenkit description: "Access security tokens and smart cards using CryptoTokenKit.
**Platform availability:** CryptoTokenKit classes are available across Apple platforms, but capability depends on extension point, entitlement, hardware, and OS version.
The smart-card app extension flow for login/keychain unlock is macOS.
`TKSmartCardSlotManager.default` is optional and returns `nil` unless smart-card access is enabled.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill cryptotokenkitAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 1 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
What problem does cryptotokenkit solve for developers using the documented workflows?
Access security tokens and smart cards using CryptoTokenKit. Use when building TKTokenDriver or TKSmartCardTokenDriver extensions, communicating with smart cards via TKSmartCard/TKSmartCardSlotManager
Who is it for?
Developers working with cryptotokenkit patterns described in the skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill documented scope.
When should I use this skill?
Access security tokens and smart cards using CryptoTokenKit. Use when building TKTokenDriver or TKSmartCardTokenDriver extensions, communicating with smart cards via TKSmartCard/TKSmartCardSlotManager
What you get
Grounded guidance and workflows from SKILL.md for cryptotokenkit.
- Corrected extension architecture
- Registration and Info.plist plan
Files
CryptoTokenKit
Use CryptoTokenKit for token driver extensions, smart-card communication, token sessions, token-backed keychain integration, and certificate-based authentication in Swift 6.3 apps.
Platform availability: CryptoTokenKit classes are available across Apple platforms, but capability depends on extension point, entitlement, hardware, and OS version. The smart-card app extension flow for login/keychain unlock is macOS. TKSmartCardSlotManager.default is optional and returns nil unless smart-card access is enabled. iOS/iPadOS 26+ add NFC smart-card slots and registration.
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 authentication and keychain services. The framework has three main usage modes:
Smart-card token extensions -- macOS app extensions that make a hardware token's cryptographic items available to system login and keychain unlock. The driver handles token lifecycle, session management, and cryptographic operations.
Client-side token access -- Apps query the keychain for items backed by tokens. CryptoTokenKit exposes token items as standard keychain entries when a token is present.
NFC smart-card access -- iOS/iPadOS 26+ apps create a temporary NFC smart card slot and communicate with the presented contactless card through TKSmartCard.
Boundary routing: Own token/smart-card sessions, token-backed keychain items, and certificate-based smart-card auth. Route passkeys/WebAuthn and account sign-in to authentication; route Secure Enclave, CryptoKit primitives, keychain architecture, certificate pinning, and trust policy to swift-security.
Key Types
| Type | Role | Platform |
|---|---|---|
TKTokenDriver / TKToken / TKTokenSession | Token driver, token, and session primitives | iOS 10+, macOS 10.12+ |
TKSmartCardTokenDriver | Entry point for smart card token extensions | iOS 10+, macOS 10.12+; macOS extension flow |
TKSmartCard / TKSmartCardSlotManager | Low-level APDU communication and slot discovery | iOS 9+, macOS 10.10+; default is optional |
TKTokenWatcher | Observes token insertion and removal | iOS 10+, macOS 10.12+ |
TKSmartCardSlotNFCSession | NFC-backed smart card slot session | iOS/iPadOS 26+ |
TKSmartCardTokenRegistrationManager | Registers NFC smart cards for later keychain use | iOS/iPadOS 26+ |
Token Extensions
For system login and keychain unlock on macOS, a token driver is an 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:
1. TokenDriver (subclass of TKSmartCardTokenDriver) -- entry point 2. Token (subclass of TKSmartCardToken) -- represents the token 3. TokenSession (subclass of TKSmartCardTokenSession) -- handles operations
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-tokensRegister the extension once by launching the host app as _securityagent:
sudo -u _securityagent /Applications/TokenHost.app/Contents/MacOS/TokenHostToken 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. TKSmartCardSlotManager.default is optional; treat nil as unavailable hardware, missing entitlement/access, or unsupported runtime capability.
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/iPadOS 26+)
On iOS/iPadOS 26+, guard isNFCSupported() before calling createNFCSlot(message:completion:) to communicate with contactless cards:
@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 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 (
kSecAttrKeyTypeECSECPrimeRandom) supporting
ecdhKeyExchangeStandard, or
- 2048/3072/4096-bit RSA key (
kSecAttrKeyTypeRSA) supporting
rsaEncryptionOAEPSHA256 decryption
Smart Card Authentication Preferences (macOS)
Configure in the com.apple.security.smartcard domain (MDM or systemwide):
| Key | Default | Description |
|---|---|---|
allowSmartCard | true | Enable smart card authentication |
checkCertificateTrust | 0 | Certificate trust level (0-3) |
oneCardPerUser | false | Pair a single smart card to an account |
enforceSmartCard | false | 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 iOS 10+ and macOS 10.12+.
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 |
|---|---|
.notImplemented | Operation not supported by this token |
.communicationError | Communication with token failed |
.corruptedData | Data from token is corrupted |
.canceledByUser | User canceled the operation |
.authenticationFailed | PIN or password incorrect |
.objectNotFound | Requested key or certificate not found |
.tokenNotFound | Token is no longer present |
.authenticationNeeded | 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: Treat API availability as an access guarantee
// 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
}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 for the exact capability (
TKTokenWatcheriOS 10+, NFC smart-card sessions iOS/iPadOS 26+) - [ ]
TKSmartCardSlotManager.defaultguarded for missing entitlement, hardware, or runtime support - [ ] macOS token extension target uses
NSExtensionPointIdentifier=com.apple.ctk-tokens - [ ]
com.apple.ctk.driver-classset to the correct driver class in Info.plist - [ ] Extension registered via
_securityagentlaunch during installation - [ ]
TKTokenSessionDelegatechecks specific algorithms, not blankettrue - [ ] Smart card sessions opened and closed (
withSessionorbeginSession/endSession) - [ ] APDU status words checked after every
sendcall - [ ] Token presence verified via
TKTokenWatcherbefore keychain queries - [ ]
TKErrorcases handled with appropriate user feedback - [ ] Keychain contents populated with correct
objectIDvalues - [ ]
TKTokenKeychainKeycapabilities (canSign,canDecrypt) match hardware - [ ] Certificate trust level configured appropriately for deployment environment
- [ ]
errSecItemNotFoundhandled for persistent references when token is removed - [ ] iOS 26+ NFC sessions ended with
TKSmartCardSlotNFCSession.end()
References
- Extended patterns (PIV commands, TLV parsing, generic token drivers, APDU helpers, secure PIN): references/cryptotokenkit-patterns.md
- TKTokenDriver
- TKToken
- TKTokenSession
- TKSmartCard
- TKSmartCardSlotManager
- com.apple.security.smartcard entitlement
- TKSmartCardSlotNFCSession
- TKSmartCardTokenRegistrationManager
- TKTokenWatcher
- Authenticating Users with a Cryptographic Token
- Using Cryptographic Assets Stored on a Smart Card
- Configuring Smart Card Authentication
{
"skill_name": "cryptotokenkit",
"evals": [
{
"id": 0,
"prompt": "Review this macOS smart-card login extension plan: create an iOS token driver target with TKTokenDriver, put com.apple.ctk.driver-class at the top level of Info.plist, share one PIN-authenticated state across token sessions, and register it by launching the normal app as the current user.",
"expected_output": "A correction-focused CryptoTokenKit review that keeps system smart-card authentication on macOS, uses the smart-card token extension classes and Info.plist nesting, registers through _securityagent, and preserves per-session authentication state.",
"files": [],
"expectations": [
"States that the smart-card app extension flow for system login and keychain unlock is a macOS workflow, not an iOS token driver target.",
"Uses TKSmartCardTokenDriver, TKSmartCardToken, and TKSmartCardTokenSession for smart-card token extensions.",
"Places com.apple.ctk.driver-class under NSExtension > NSExtensionAttributes and uses NSExtensionPointIdentifier com.apple.ctk-tokens.",
"Registers the extension by launching the host app as the _securityagent user during installation.",
"Warns not to share PIN or authentication state across TKTokenSession instances.",
"Does not drift into generic Sign in with Apple, passkey, or OAuth authentication guidance."
]
},
{
"id": 1,
"prompt": "I'm building an iOS 26 app that reads a contactless PIV smart card over NFC and wants to keep a token-backed keychain reference for later signing. What CryptoTokenKit APIs and availability checks should I use?",
"expected_output": "An iOS 26 CryptoTokenKit plan that uses TKSmartCardSlotManager NFC sessions, TKSmartCard APDU communication, TKSmartCardTokenRegistrationManager, kSecAttrTokenID keychain queries, persistent-reference handling, and explicit availability/access guards.",
"files": [],
"expectations": [
"States that createNFCSlot(message:completion:) and isNFCSupported() are iOS/iPadOS 26+ APIs.",
"Guards TKSmartCardSlotManager.default and isNFCSupported() before creating an NFC smart-card slot.",
"Uses the TKSmartCardSlotNFCSession slotName to obtain a TKSmartCardSlot and TKSmartCard for APDU communication.",
"Ends the NFC session with TKSmartCardSlotNFCSession.end().",
"Uses TKSmartCardTokenRegistrationManager for iOS 26+ smart-card registration instead of inventing custom persistence.",
"Uses kSecAttrTokenID, TKTokenWatcher, and errSecItemNotFound handling for token-backed keychain references."
]
},
{
"id": 2,
"prompt": "I need to add hardware-token certificate auth, passkeys, Secure Enclave key generation, and certificate pinning to an iOS app. Which parts belong in CryptoTokenKit, and what should move to sibling skills?",
"expected_output": "A boundary-aware routing answer that keeps CryptoTokenKit focused on token-backed smart-card/keychain workflows, routes passkeys and user-facing account auth to authentication, and routes Secure Enclave, CryptoKit, Keychain storage architecture, and certificate pinning to swift-security.",
"files": [],
"expectations": [
"Keeps CryptoTokenKit focused on security tokens, smart cards, token sessions, token-backed keychain items, and certificate-based smart-card authentication.",
"Routes passkeys/WebAuthn and user-facing account sign-in flows to the authentication skill.",
"Routes Secure Enclave key generation, CryptoKit primitives, keychain storage architecture, and certificate pinning/trust policy to swift-security.",
"Explains that token-backed keychain items are queried with kSecAttrTokenID when a token is present.",
"Mentions that normal token presence/removal should be tracked with TKTokenWatcher.",
"Does not broaden CryptoTokenKit into a general mobile security or account-authentication guide."
]
}
]
}
CryptoTokenKit Extended Patterns
Advanced patterns for CryptoTokenKit: PIV smart card operations, TLV record parsing, generic (non-smart-card) token drivers, APDU command helpers, and token configuration management.
Contents
- PIV Smart Card Operations
- TLV Record Parsing
- Generic Token Drivers
- APDU Command Helpers
- Secure PIN Operations
- Token Configuration Management
- Smart Card Slot Monitoring
- Token Registration
PIV Smart Card Operations
PIV (Personal Identity Verification, FIPS 201) smart cards use a standard application identifier and defined data objects. Common operations include selecting the PIV application, reading certificates, and performing authentication.
PIV Application Selection
import CryptoTokenKit
/// Standard PIV application identifier (NIST SP 800-73-4)
let pivAID = Data([
0xA0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00
])
func selectPIVApplication(card: TKSmartCard) throws {
try card.withSession {
let (sw, _) = try card.send(
ins: 0xA4, // SELECT
p1: 0x04, // Select by name
p2: 0x00,
data: pivAID,
le: nil
)
guard sw == 0x9000 else {
throw PIVError.selectFailed(statusWord: sw)
}
}
}Reading PIV Data Objects
PIV defines standard data objects accessed via GET DATA:
/// PIV data object tags
enum PIVObject {
/// X.509 Certificate for PIV Authentication (slot 9A)
static let certAuth = Data([0x5C, 0x03, 0x5F, 0xC1, 0x05])
/// X.509 Certificate for Digital Signature (slot 9C)
static let certSign = Data([0x5C, 0x03, 0x5F, 0xC1, 0x0A])
/// X.509 Certificate for Key Management (slot 9D)
static let certKeyMgmt = Data([0x5C, 0x03, 0x5F, 0xC1, 0x0B])
/// X.509 Certificate for Card Authentication (slot 9E)
static let certCardAuth = Data([0x5C, 0x03, 0x5F, 0xC1, 0x01])
/// Card Holder Unique Identifier (CHUID)
static let chuid = Data([0x5C, 0x03, 0x5F, 0xC1, 0x02])
/// Card Capability Container (CCC)
static let ccc = Data([0x5C, 0x03, 0x5F, 0xC1, 0x07])
}
func readPIVObject(card: TKSmartCard, tag: Data) throws -> Data {
try card.withSession {
var fullResponse = Data()
// GET DATA command (INS=CB for PIV)
let (sw, response) = try card.send(
ins: 0xCB, // GET DATA
p1: 0x3F,
p2: 0xFF,
data: tag,
le: 0
)
fullResponse.append(response)
// Handle chained responses (SW 61xx)
var currentSW = sw
while (currentSW >> 8) == 0x61 {
let remaining = Int(currentSW & 0xFF)
let (nextSW, nextResponse) = try card.send(
ins: 0xC0, // GET RESPONSE
p1: 0x00,
p2: 0x00,
data: nil,
le: remaining == 0 ? 256 : remaining
)
fullResponse.append(nextResponse)
currentSW = nextSW
}
guard currentSW == 0x9000 else {
throw PIVError.readFailed(statusWord: currentSW)
}
return fullResponse
}
}PIV Authentication (GENERAL AUTHENTICATE)
func pivAuthenticate(
card: TKSmartCard,
keySlot: UInt8,
algorithm: UInt8,
challenge: Data
) throws -> Data {
try card.withSession {
// Build dynamic authentication template (tag 7C)
var authData = Data()
// Tag 81: challenge
authData.append(0x81)
authData.append(UInt8(challenge.count))
authData.append(challenge)
// Wrap in tag 7C
var template = Data([0x7C])
template.append(UInt8(authData.count))
template.append(authData)
let (sw, response) = try card.send(
ins: 0x87, // GENERAL AUTHENTICATE
p1: algorithm, // Algorithm reference
p2: keySlot, // Key reference (9A, 9C, 9D, 9E)
data: template,
le: 0
)
guard sw == 0x9000 else {
throw PIVError.authFailed(statusWord: sw)
}
return response
}
}PIV PIN Verification
func verifyPIN(card: TKSmartCard, pin: String) throws {
try card.withSession {
// PIV PIN is padded to 8 bytes with 0xFF
var pinData = Data(pin.utf8.prefix(8))
while pinData.count < 8 {
pinData.append(0xFF)
}
let (sw, _) = try card.send(
ins: 0x20, // VERIFY
p1: 0x00,
p2: 0x80, // PIV Application PIN
data: pinData,
le: nil
)
switch sw {
case 0x9000:
return // Success
case 0x6983:
throw PIVError.pinBlocked
case let sw where (sw >> 4) == 0x63C:
let retriesLeft = Int(sw & 0x0F)
throw PIVError.wrongPIN(retriesRemaining: retriesLeft)
default:
throw PIVError.verifyFailed(statusWord: sw)
}
}
}
enum PIVError: Error {
case selectFailed(statusWord: UInt16)
case readFailed(statusWord: UInt16)
case authFailed(statusWord: UInt16)
case verifyFailed(statusWord: UInt16)
case wrongPIN(retriesRemaining: Int)
case pinBlocked
}Extracting Certificates from PIV Response
PIV data objects are BER-TLV encoded. The certificate is nested inside the response:
func extractCertificate(from pivResponse: Data) -> SecCertificate? {
// Parse outer TLV (tag 53)
guard let records = TKBERTLVRecord.sequenceOfRecords(from: pivResponse) else {
return nil
}
for record in records {
if record.tag == 0x53 {
// Inside tag 53, find tag 70 (certificate)
guard let innerRecords = TKBERTLVRecord.sequenceOfRecords(
from: record.value
) else { continue }
for inner in innerRecords {
if inner.tag == 0x70 {
return SecCertificateCreateWithData(
nil, inner.value as CFData
)
}
}
}
}
return nil
}TLV Record Parsing
CryptoTokenKit includes TLV (Tag-Length-Value) parsing classes for working with structured smart card data.
BER-TLV Records
BER-TLV is the standard encoding for ISO 7816 data objects:
import CryptoTokenKit
func parseBERTLV(data: Data) {
guard let records = TKBERTLVRecord.sequenceOfRecords(from: data) else {
print("Failed to parse TLV data")
return
}
for record in records {
print("Tag: 0x\(String(record.tag, radix: 16, uppercase: true))")
print("Value length: \(record.value.count)")
print("Value: \(record.value.map { String(format: "%02X", $0) }.joined())")
// Recursively parse constructed tags
if let nested = TKBERTLVRecord.sequenceOfRecords(from: record.value) {
print(" Nested records:")
for nestedRecord in nested {
print(" Tag: 0x\(String(nestedRecord.tag, radix: 16, uppercase: true))")
}
}
}
}Building BER-TLV Records
func buildTLVData() -> Data {
// Build a simple TLV record
let nameRecord = TKBERTLVRecord(
tag: 0x5F20,
value: Data("John Doe".utf8)
)
// Build a constructed TLV with nested records
let container = TKBERTLVRecord(
tag: 0x65,
records: [nameRecord]
)
return container.data
}Compact TLV Records
Compact TLV is used in ATR historical bytes:
func parseATRHistoricalBytes(atr: TKSmartCardATR) {
guard let records = atr.historicalRecords else {
print("No historical records in ATR")
return
}
for record in records {
// Compact TLV tags are single bytes
print("Tag: \(record.tag), Value: \(record.value.count) bytes")
}
}Simple TLV Records
func buildSimpleTLV(tag: UInt8, value: Data) -> Data {
let record = TKSimpleTLVRecord(tag: tag, value: value)
return record.data
}Generic Token Drivers
For tokens that are not smart cards (USB security keys, software tokens), use TKTokenDriver directly instead of TKSmartCardTokenDriver.
Generic Driver Implementation
import CryptoTokenKit
final class GenericTokenDriver: TKTokenDriver, TKTokenDriverDelegate {
override init() {
super.init()
self.delegate = self
}
func tokenDriver(
_ driver: TKTokenDriver,
tokenFor configuration: TKToken.Configuration
) throws -> TKToken {
return GenericToken(
tokenDriver: driver,
instanceID: configuration.instanceID
)
}
func tokenDriver(
_ driver: TKTokenDriver,
terminateToken token: TKToken
) {
// Clean up resources when token is removed
}
}Generic Token Implementation
final class GenericToken: TKToken, TKTokenDelegate {
init(tokenDriver: TKTokenDriver, instanceID: TKToken.InstanceID) {
super.init(tokenDriver: tokenDriver, instanceID: instanceID)
self.delegate = self
}
func createSession(_ token: TKToken) throws -> TKTokenSession {
return GenericTokenSession(token: token)
}
}Generic Token Session
final class GenericTokenSession: TKTokenSession, TKTokenSessionDelegate {
func tokenSession(
_ session: TKTokenSession,
supports operation: TKTokenOperation,
keyObjectID: TKToken.ObjectID,
algorithm: TKTokenKeyAlgorithm
) -> Bool {
switch operation {
case .signData:
return algorithm.isAlgorithm(.ecdsaSignatureDigestX962SHA256)
|| algorithm.isAlgorithm(.rsaSignatureDigestPKCS1v15SHA256)
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 {
// Perform signing operation with the token hardware
// Implementation depends on the specific token being supported
throw TKError(.notImplemented)
}
func tokenSession(
_ session: TKTokenSession,
decrypt ciphertext: Data,
keyObjectID: TKToken.ObjectID,
algorithm: TKTokenKeyAlgorithm
) throws -> Data {
throw TKError(.notImplemented)
}
func tokenSession(
_ session: TKTokenSession,
performKeyExchange otherPartyPublicKeyData: Data,
keyObjectID: TKToken.ObjectID,
algorithm: TKTokenKeyAlgorithm,
parameters: TKTokenKeyExchangeParameters
) throws -> Data {
throw TKError(.notImplemented)
}
func tokenSession(
_ session: TKTokenSession,
beginAuthFor operation: TKTokenOperation,
constraint: Any
) throws -> TKTokenAuthOperation {
let auth = TKTokenPasswordAuthOperation()
return auth
}
}APDU Command Helpers
Utility patterns for constructing and interpreting APDU commands.
Status Word Interpretation
struct APDUStatus {
let sw1: UInt8
let sw2: UInt8
let raw: UInt16
init(_ sw: UInt16) {
self.raw = sw
self.sw1 = UInt8(sw >> 8)
self.sw2 = UInt8(sw & 0xFF)
}
var isSuccess: Bool { raw == 0x9000 }
var hasMoreData: Bool { sw1 == 0x61 }
var bytesAvailable: Int { hasMoreData ? Int(sw2) : 0 }
var isWrongLength: Bool { sw1 == 0x6C }
var correctLength: Int { isWrongLength ? Int(sw2) : 0 }
var isAuthenticationNeeded: Bool {
raw == 0x6982 // Security status not satisfied
}
var isNotFound: Bool {
raw == 0x6A82 // File or application not found
}
var description: String {
switch raw {
case 0x9000: return "Success"
case 0x6283: return "Selected file deactivated"
case 0x6882: return "Secure messaging not supported"
case 0x6982: return "Security status not satisfied"
case 0x6983: return "Authentication method blocked"
case 0x6984: return "Reference data not usable"
case 0x6985: return "Conditions of use not satisfied"
case 0x6A82: return "File or application not found"
case 0x6A86: return "Incorrect parameters P1-P2"
case 0x6D00: return "Instruction not supported"
case 0x6E00: return "Class not supported"
default:
if sw1 == 0x61 { return "More data: \(sw2) bytes" }
if sw1 == 0x63 && (sw2 & 0xF0) == 0xC0 {
return "Wrong PIN, \(sw2 & 0x0F) tries remaining"
}
return String(format: "Unknown: %04X", raw)
}
}
}Command Chaining for Large Data
Some cards require command chaining for data larger than the maximum APDU size:
func sendChainedAPDU(
card: TKSmartCard,
ins: UInt8,
p1: UInt8,
p2: UInt8,
data: Data,
chunkSize: Int = 255
) throws -> (UInt16, Data) {
try card.withSession {
var offset = 0
var lastSW: UInt16 = 0
var fullResponse = Data()
while offset < data.count {
let end = min(offset + chunkSize, data.count)
let chunk = data[offset..<end]
let isLast = end >= data.count
// Set CLA bit 4 for chaining, clear on last command
card.cla = isLast ? 0x00 : 0x10
let (sw, response) = try card.send(
ins: ins,
p1: p1,
p2: p2,
data: Data(chunk),
le: isLast ? 0 : nil
)
fullResponse.append(response)
lastSW = sw
offset = end
}
card.cla = 0x00 // Reset CLA
return (lastSW, fullResponse)
}
}Reading Large Responses
Handle 61xx (more data available) status words:
func readFullResponse(
card: TKSmartCard,
initialSW: UInt16,
initialResponse: Data
) throws -> Data {
var fullResponse = initialResponse
var sw = initialSW
while APDUStatus(sw).hasMoreData {
let le = APDUStatus(sw).bytesAvailable
let (nextSW, nextResponse) = try card.send(
ins: 0xC0, // GET RESPONSE
p1: 0x00,
p2: 0x00,
data: nil,
le: le == 0 ? 256 : le
)
fullResponse.append(nextResponse)
sw = nextSW
}
guard sw == 0x9000 else {
throw TKError(.communicationError)
}
return fullResponse
}Secure PIN Operations
For card readers with built-in PIN pads, use secure PIN verification to prevent PIN exposure to the host system.
Secure PIN Verification
func securePINVerify(card: TKSmartCard) {
let pinFormat = TKSmartCardPINFormat()
pinFormat.charset = .numeric
pinFormat.encoding = .ascii
pinFormat.minPINLength = 4
pinFormat.maxPINLength = 8
pinFormat.pinBlockByteLength = 8
pinFormat.pinJustification = .left
pinFormat.pinBitOffset = 0
// VERIFY APDU template (PIN bytes will be inserted at offset)
let apdu = Data([
0x00, 0x20, 0x00, 0x80, // CLA INS P1 P2
0x08, // Lc (PIN block length)
0xFF, 0xFF, 0xFF, 0xFF, // PIN block placeholder
0xFF, 0xFF, 0xFF, 0xFF
])
guard let interaction = card.userInteractionForSecurePINVerification(
pinFormat,
apdu: apdu,
pinByteOffset: 5
) else {
print("Secure PIN verification not supported by this reader")
return
}
interaction.initialTimeout = 30
interaction.interactionTimeout = 30
interaction.run { success, error in
if success {
let sw = interaction.resultSW
print("PIN verify result: \(APDUStatus(sw).description)")
} else {
print("PIN entry failed: \(error?.localizedDescription ?? "")")
}
}
}Secure PIN Change
func securePINChange(card: TKSmartCard) {
let pinFormat = TKSmartCardPINFormat()
pinFormat.charset = .numeric
pinFormat.encoding = .ascii
pinFormat.minPINLength = 4
pinFormat.maxPINLength = 8
pinFormat.pinBlockByteLength = 8
// CHANGE REFERENCE DATA APDU template
let apdu = Data([
0x00, 0x24, 0x00, 0x80, // CLA INS P1 P2
0x10, // Lc (two PIN blocks)
0xFF, 0xFF, 0xFF, 0xFF, // Current PIN placeholder
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, // New PIN placeholder
0xFF, 0xFF, 0xFF, 0xFF
])
guard let interaction = card.userInteractionForSecurePINChange(
pinFormat,
apdu: apdu,
currentPINByteOffset: 5,
newPINByteOffset: 13
) else {
print("Secure PIN change not supported by this reader")
return
}
interaction.pinConfirmation = [.current, .new]
interaction.run { success, error in
if success {
let sw = interaction.resultSW
print("PIN change result: \(APDUStatus(sw).description)")
}
}
}Token Configuration Management
Manage persistent token configurations for non-smart-card token drivers.
Managing Driver Configurations
func manageTokenConfigurations() {
// Access existing driver configurations
let configs = TKTokenDriver.Configuration.driverConfigurations
for (classID, config) in configs {
print("Driver: \(classID)")
for (instanceID, tokenConfig) in config.tokenConfigurations {
print(" Token: \(instanceID)")
if let data = tokenConfig.configurationData {
print(" Config data: \(data.count) bytes")
}
// Access keychain items from configuration
for item in tokenConfig.keychainItems {
print(" Keychain item: \(item.objectID)")
}
}
}
}Adding Token Configurations
func addTokenConfiguration(
driverConfig: TKTokenDriver.Configuration,
instanceID: String,
configData: Data?
) {
let tokenConfig = driverConfig.addTokenConfiguration(
for: instanceID
)
tokenConfig.configurationData = configData
}Smart Card Slot Monitoring
Monitor smart card slot state changes for reader-aware applications. Always guard TKSmartCardSlotManager.default; Apple documents that it returns nil unless smart-card access is enabled, and available APIs still depend on device hardware and runtime support.
import CryptoTokenKit
import Combine
final class SlotMonitor {
private var observation: NSKeyValueObservation?
func monitorSlot(named slotName: String) {
guard let manager = TKSmartCardSlotManager.default else { return }
manager.getSlot(withName: slotName) { [weak self] slot in
guard let slot else {
print("Slot not found: \(slotName)")
return
}
// Observe slot state changes
self?.observation = slot.observe(\.state, options: [.new]) { slot, change in
switch slot.state {
case .missing:
print("Reader disconnected")
case .empty:
print("No card in reader")
case .probing:
print("Card detected, probing...")
case .muteCard:
print("Unresponsive card")
case .validCard:
print("Valid card detected")
if let card = slot.makeSmartCard() {
self?.handleCard(card)
}
@unknown default:
break
}
}
// Check ATR if card is present
if let atr = slot.atr {
print("ATR: \(atr.bytes.map { String(format: "%02X", $0) }.joined())")
print("Protocols: \(atr.protocols)")
}
}
}
private func handleCard(_ card: TKSmartCard) {
print("Card in slot: \(card.slot.name)")
print("Valid: \(card.isValid)")
print("Protocol: \(card.currentProtocol)")
}
}Token Registration
On iOS/iPadOS 26+, register and unregister NFC smart card tokens using TKSmartCardTokenRegistrationManager. A registered smart card remains reachable through Keychain Services, and the system can invoke an NFC slot when a cryptographic operation needs the registered card.
@available(iOS 26.0, *)
func registerSmartCardToken(tokenID: String) {
let manager = TKSmartCardTokenRegistrationManager.default
do {
try manager.registerSmartCard(
tokenID: tokenID,
promptMessage: "Insert your smart card to complete registration"
)
print("Token registered: \(tokenID)")
} catch {
print("Registration failed: \(error)")
}
}
@available(iOS 26.0, *)
func unregisterSmartCardToken(tokenID: String) {
let manager = TKSmartCardTokenRegistrationManager.default
do {
try manager.unregisterSmartCard(tokenID: tokenID)
print("Token unregistered: \(tokenID)")
} catch {
print("Unregistration failed: \(error)")
}
}
@available(iOS 26.0, *)
func listRegisteredTokens() {
let manager = TKSmartCardTokenRegistrationManager.default
for tokenID in manager.registeredSmartCardTokens {
print("Registered token: \(tokenID)")
}
}Related skills
How it compares
Pick cryptotokenkit over general iOS security skills when building CryptoTokenKit hardware token drivers rather than app-level authentication flows.
FAQ
Who is Cryptotokenkit for?
Developers and software engineers working with cryptotokenkit patterns from the skill documentation.
When should I use Cryptotokenkit?
Access security tokens and smart cards using CryptoTokenKit. Use when building TKTokenDriver or TKSmartCardTokenDriver extensions, communicating with smart cards via TKSmartCard/TKSmartCardSlotManager, using iOS 26+ NFC
Is Cryptotokenkit safe to install?
Review the Security Audits panel on this page before installing in production.