The card that wasn't there
CryptoTokenKit on macOS misses 98.6% of smartcard insertion and removal events from third-party IFD drivers. An authentication stack built on CTK card-presence events will not notice when the card is gone.
Managing the network estate for CityReach across European cities in the late 1990s, I carried an Ericsson GSM handset. Early GPRS meant I could pull routing tables from trains between Budapest and Vienna, which still felt miraculous. What I understood about mobile networks then — what the experience made vivid in a way that no textbook did — was that “present” and “authenticated” are not the same thing. A SIM could be cloned. A base station could be rogue. The network's belief that a subscriber was attached to a particular cell told you something, but not everything, about whether that subscriber was actually there. The gap between presence and authentication is where mobile fraud lived.
I was reminded of that gap when I found CTK-01. The framework believed the smartcard was present. The card was not in the reader. The gap, this time, was between two frameworks running on the same machine watching the same physical hardware — and they disagreed by a factor of more than seventy.
The finding
Component: CryptoTokenKit (CTK), TKSmartCardSlot.state, macOS 15 / 26
CWE: CWE-346 — Origin Validation Error (state not updated from hardware)
Condition: Third-party IFD (Interface Device) drivers — the majority of USB smartcard readers that are not Apple's own hardware
Observation: Over twenty physical card insertions and removals, a CTK KVO observer received two state-change events. A PC/SC observer on the same device received one hundred and forty-seven events for the same operations.
Security implication: An application or authentication stack using CTK's card-presence events as a removal signal — “lock the screen when the card is removed” — will not lock when the card is physically removed.
Status: Disclosed to Apple. Silent close — no comment received.
The numbers bear repeating because they are striking. CTK reported two state changes for twenty physical card operations. PC/SC, watching the same hardware through a different framework path, reported one hundred and forty-seven. CTK consistently missed 98.6% of state changes from this third-party USB reader. On repeated tests across different sessions, the pattern held.
The practical consequence is straightforward. If you build a smartcard-based authentication flow on macOS — PIV authentication, smart-card-required login policy, a screen lock tied to card removal — and you use TKSmartCardSlot.state and KVO notifications as your signal that the card has gone, you will not reliably receive that signal for most USB smartcard readers. The card leaves the reader; the session does not lock.
Proof of concept
// CTK-01 observer — compare TKSmartCardSlot.state events against PC/SC
// Requires: a third-party USB smartcard reader (not Apple's own USB-C reader)
// Build: swiftc ctk01-observer.swift -framework CryptoTokenKit -o ctk01
// Run: ./ctk01
// Then insert and remove a smartcard 10–20 times, watching both counts.
import CryptoTokenKit
import Foundation
var ctkEventCount = 0
var observation: NSKeyValueObservation?
let manager = TKSmartCardSlotManager.default!
// Observe the first available slot
guard let slotName = manager.slotNames.first,
let slot = manager.slotNamed(slotName) else {
print("No smartcard slot found. Insert a reader.")
exit(1)
}
print("Watching CTK slot: \(slotName)")
print("Insert and remove your smartcard repeatedly.")
print("")
observation = slot.observe(\.state, options: [.new, .old]) { observed, change in
ctkEventCount += 1
let stateDescription: String
switch observed.state {
case .missing: stateDescription = "missing"
case .empty: stateDescription = "empty"
case .probing: stateDescription = "probing"
case .muteCard: stateDescription = "muteCard"
case .validCard: stateDescription = "validCard"
@unknown default: stateDescription = "unknown"
}
print("CTK event \(ctkEventCount): state = \(stateDescription)")
}
// Run the PC/SC observer in a separate terminal for comparison:
// pcsctest (install pcsc-tools via Homebrew)
// You will observe: CTK ~2 events, PC/SC ~147 events for 20 insertions/removals.
// The gap is consistent and reproducible with third-party USB IFD drivers.
//
// Expected result: CTK event count << PC/SC event count.
// Security implication: a CTK-based removal handler will not fire reliably.
RunLoop.main.run(until: Date(timeIntervalSinceNow: 120))
print("")
print("Final CTK event count: \(ctkEventCount)")
print("Compare against your PC/SC observer count.")
The investigation
CryptoTokenKit is the modern, Apple-preferred framework for smartcard and hardware token integration on macOS. It sits above the older PC/SC layer, which handles raw communication with smartcard readers at the driver level. The IFD (Interface Device) architecture means that each USB smartcard reader speaks to the OS through a driver; Apple ships drivers for Apple's own hardware, but most enterprise USB readers — the kind deployed in government and financial-sector organisations that rely on PIV cards — use third-party IFD drivers.
The problem appears to be in how CTK translates hardware state-change events from the IFD layer into TKSmartCardSlot.state updates and KVO notifications. For Apple's own hardware, this path presumably works correctly — the framework and the driver share a pedigree. For third-party IFD drivers, the translation appears unreliable. The PC/SC layer, which is lower in the stack and less dependent on the same driver-framework handshake, receives the events faithfully.
I tested across multiple sessions and multiple card types. The PC/SC event count was consistently within a reasonable range of the actual physical operations. The CTK event count was not.
Apple's response and honest assessment
Apple closed this finding without comment. No acknowledgement, no timeline, no statement that it was out of scope. A silent close is always the most ambiguous outcome — it could mean the finding was triaged as low priority, it could mean it is scheduled for a future release, or it could mean it fell through a gap in the review queue.
The silent close is frustrating but I understand the probable reasoning. The affected population is enterprise users with third-party USB IFD drivers and smartcard-based authentication policies. That is a real population — government, finance, defence contractors — but it is not the median Mac user. Apple's bounty programme prioritises findings with broad consumer impact; an IFD driver compatibility issue that affects enterprise smartcard deployments is unlikely to score highly in that model. The finding is technically real and the security implication is genuine: an organisation that has deployed smartcard-required authentication on Macs, relying on CTK's card-presence events to drive a screen-lock policy, may be surprised to discover that removing the card does not reliably trigger the lock. That is a gap worth knowing about. Whether it rises to Apple's current threshold for a remediation priority is a different question, and their prioritisation decision — if that is what the silence means — is understandable even if the outcome is not what I would have preferred.
The mitigating control for organisations in this position is to add a polling-based check alongside the CTK KVO observer, or to use PC/SC directly for the card-presence signal rather than relying on CTK's state property. Neither is elegant, but both are more reliable than a CTK KVO observer alone for third-party hardware.
Disclosure note
This finding was submitted to Apple through the Apple Security Research Framework. Apple closed it without comment. The PoC above requires your own smartcard reader and your own test card. It does not interact with any network service or remote system. The event counts described in this post were observed on the author's own hardware over multiple test sessions.
Are you there? The framework said yes. The reader said the slot was empty.