Case study — the "lockout" that wasn't
A walkthrough of a real diagnostic session. The user reported being "locked out" of their hardware security key. Following the runbook end to end turned up something more interesting than a lockout: the key itself was fully healthy, but the user didn't know their PIN, and several gaps in the provisioning workflow surfaced along the way.
This is the kind of case where the value of a structured runbook shows up clearly — without one, the natural response to "I'm locked out" would have been a destructive reprovision. With one, the diagnosis took ten minutes and the user kept all their credentials.
The user's complaint
"I'm locked out of my hardware key. It worked before, now it doesn't. I've tried my PIN a few times and nothing works."
This is the most common opening for a hardware key support call. The word "locked out" is doing a lot of work here — it could mean any of:
- The smart card PIN counter has hit zero
- The FIDO PIN counter has hit zero
- An OATH/HOTP code isn't being accepted by the validation server
- A certificate has expired
- The user is typing the wrong PIN against a healthy counter
- The user's relying party (IdP, VPN, etc.) is rejecting them for a reason unrelated to the key itself
The first job of the diagnostic flow is to narrow which of these is actually happening — before doing anything that burns a retry.
The diagnostic sequence
Every command below is read-only or single-attempt by design. The sequence is ordered so that earlier commands can never make a later problem worse.
Step 1 — Confirm the token is detected
.\cli token-info
What this retrieves:
- Whether any tokens are connected at all
- Reader name (driver-level identification)
- Token name and ATR
- The
-tindex for targeting this specific token
What we found: One token, healthy, index 0, reader name
HID Global Crescendo Key V3 0. ATR matched a standard V3 key. No
ambiguity, no driver issues.
Why this step matters: If the token isn't visible to the OS, no further command will work. This step also catches the common case where a user has multiple readers or accidentally inserted a different key.
Step 2 — Capture the token's unique ID
.\cli token-cuid
What this retrieves:
- The card unique identifier (CUID), a 20-character hex string that uniquely identifies this physical key
What we found: A clean CUID, captured for the support ticket and for the password manager entry we'd potentially create later.
Why this step matters: Provisioning records, PUK manifests, and post-incident audit trails are all keyed off the CUID. Capturing it before doing anything destructive is non-negotiable.
Step 3 — Read the access control applet properties
.\cli aca-props-get
What this retrieves: A JSON dump describing the access control applet's state, including:
CurrentPinTryCountervsMaxPinTryCounter— how many PIN attempts remainCurrentPinUnlockCountervsMaxPinUnlockCounter— unlock counter statePUKInitialized— whether a PUK exists on this tokenXAUTHKeysInitializedStatusandXAUTHChallengeType— external auth stateMinPinLength/MaxPinLength— what PIN lengths are validPINSharedWithFIDO— whether the smart card PIN and FIDO PIN are linked or independent
What we found:
| Property | Value | Reading |
|---|---|---|
CurrentPinTryCounter | 6 of 6 | PIN is not locked at all |
PUKInitialized | false | No PUK on this token |
XAUTHKeysInitializedStatus | true | External auth key configured |
XAUTHChallengeType | Dynamic | Challenge-response mode |
PINSharedWithFIDO | false | Smart card and FIDO PINs are independent |
This is the pivotal moment in the diagnosis. The PIN counter was at full strength. The user believed they were locked out, but the token itself reported no failed attempts. Whatever they were experiencing wasn't a hardware-level PIN lockout.
The PUKInitialized: false flagged a separate concern: even if we did
later need to recover from a real lockout, the non-destructive PUK path
wasn't available on this key. That's a provisioning workflow gap, not a
user issue — but worth noting for the rollout owner.
Step 4 — Test the PIN the user claimed
.\cli pin-verify -p <user_claimed_pin> -v
What this retrieves:
- A response code indicating whether the PIN is correct (
9000) or wrong (63Cxwherexis retries remaining) - Implicitly burns a retry on failure — used carefully
What we found: The user-claimed PIN returned 63C5 — wrong PIN,
5 retries remaining. The user didn't know their actual PIN. Notably,
the value they offered was derived from the visible CUID tail — a hint
that they were guessing based on something printed on the key, not
recalling a PIN they had set.
Why we stopped after one attempt: With PUKInitialized: false,
running through all 6 retries would have landed us at zero with no
recovery path other than a full destructive reprovision. The runbook
rule — never guess a PIN past one attempt — directly prevents this.
Step 5 — Try the documented default
The user couldn't recall their PIN, so we exhausted the non-destructive record-based options first. Enterprise rollouts commonly seed keys with a uniform default PIN that users change on first use.
.\cli pin-verify -p 000000 -v
What we found: 9000 — success. The PIN was the unchanged
enterprise default. The user had never set their own PIN, so when
something prompted them for one, they didn't know what to enter.
An important observed behavior: a successful pin-verify resets
the failed-attempt counter back to maximum. Our previous wrong attempt
had taken the counter from 6 to 5; after this success, it was back at
6. This is worth knowing — the counter value alone isn't a reliable
record of historical wrong attempts; it only reflects consecutive
failures since the last success.