@@ -65,15 +65,41 @@ TRACE_FUNC
6565# Make sure no conflicting GPG related services are running, gpg-agent will respawn
6666DO_WITH_DEBUG killall gpg-agent scdaemon > /dev/null 2>&1 || true
6767
68- # While making sure the key is inserted, capture the status so we can check how
69- # many PIN attempts remain
68+ # Query hotp_verification info and extract numeric PIN retry counter.
69+ # Retries up to N times on communication failure. Dies if the dongle
70+ # is present but no numeric counter can be extracted.
71+ query_pin_retries () {
72+ local var_name=" $1 " dongle=" $2 " max=" ${3:- 3} "
73+ local attempt=0 info counter
74+ while [ $attempt -lt " $max " ]; do
75+ attempt=$(( attempt + 1 ))
76+ info=" $( hotp_verification info 2> /dev/null) " || true
77+ echo " $info " | grep -q " Connected device status:" || continue
78+ HOTP_TOKEN_INFO=" $info "
79+ if [ " $dongle " = " Nitrokey 3" ]; then
80+ counter=$( echo " $info " | grep " Secrets app PIN counter:" | grep -o ' [0-9][0-9]*' )
81+ else
82+ counter=$( echo " $info " | grep " Card counters: Admin" | grep -o ' Admin [0-9]*' | grep -o ' [0-9]*' )
83+ fi
84+ if [ -n " $counter " ]; then
85+ eval " $var_name =\$ counter"
86+ return 0
87+ fi
88+ # Dongle is present but counter is missing — slot not configured.
89+ DIE " $DONGLE_BRAND HOTP slot is not configured"
90+ done
91+ eval " $var_name ="
92+ return 1
93+ }
94+
95+ # Initial query — if the dongle is not responding, prompt for reinsertion
96+ # and retry once.
7097STATUS " Checking $DONGLE_BRAND presence for HOTP setup"
71- if ! hotp_token_info= " $( hotp_verification info ) " ; then
98+ if ! query_pin_retries admin_pin_retries " $DONGLE_BRAND " 1 ; then
7299 INPUT " Insert your $DONGLE_BRAND and press Enter to configure it"
73- if ! hotp_token_info=" $( hotp_verification info) " ; then
74- # don't leak key on failure
100+ if ! query_pin_retries admin_pin_retries " $DONGLE_BRAND " 1; then
75101 shred -n 10 -z -u " $HOTP_SECRET " 2> /dev/null
76- DIE " Unable to find $DONGLE_BRAND "
102+ DIE " Unable to communicate with $DONGLE_BRAND "
77103 fi
78104fi
79105STATUS_OK " $DONGLE_BRAND is present for HOTP setup"
@@ -99,31 +125,18 @@ now_date="$(date '+%s')"
99125# NK3 uses "Secrets app PIN counter" (factory default: 8 attempts);
100126# all pre-NK3 devices use "Card counters: Admin" (factory default: 3 attempts).
101127if [ " $DONGLE_BRAND " = " Nitrokey 3" ]; then
102- admin_pin_retries=$( echo " $hotp_token_info " | grep " Secrets app PIN counter:" | cut -d ' :' -f 2 | tr -d ' ' )
103128 prompt_message=" Secrets app"
104129else
105- admin_pin_retries=$( echo " $hotp_token_info " | grep " Card counters: Admin" | grep -o ' Admin [0-9]*' | grep -o ' [0-9]*' )
106130 prompt_message=" GPG Admin"
107131fi
108-
109- admin_pin_retries=" ${admin_pin_retries:- 0} "
110132DEBUG " HOTP related PIN retry counter is $admin_pin_retries "
111133# Show dongle firmware version with color coding so users know when to upgrade
112- hotpkey_fw_display " $hotp_token_info " " $DONGLE_BRAND "
134+ hotpkey_fw_display " $HOTP_TOKEN_INFO " " $DONGLE_BRAND "
113135
114136# Re-query and display the current PIN retry counter before each manual prompt.
115- # Updates the global $admin_pin_retries (no local keyword) so callers can use
116- # the fresh value for decisions (e.g. max_attempts calculation below).
117137# prompt_message is already set for the device type (NK3 vs older), reuse it.
118138show_pin_retries () {
119- local info
120- info=" $( hotp_verification info 2> /dev/null) " || true
121- if [ " $DONGLE_BRAND " = " Nitrokey 3" ]; then
122- admin_pin_retries=$( echo " $info " | grep " Secrets app PIN counter:" | cut -d ' :' -f 2 | tr -d ' ' )
123- else
124- admin_pin_retries=$( echo " $info " | grep " Card counters: Admin" | grep -o ' Admin [0-9]*' | grep -o ' [0-9]*' )
125- fi
126- admin_pin_retries=" ${admin_pin_retries:- 0} "
139+ query_pin_retries admin_pin_retries " $DONGLE_BRAND " 3
127140 STATUS " $DONGLE_BRAND ${prompt_message} PIN retries remaining: $( pin_color " $admin_pin_retries " ) ${admin_pin_retries} \033[0m"
128141}
129142
@@ -182,15 +195,9 @@ if [ "$admin_pin_status" -ne 0 ]; then
182195 # Default PIN skipped (key >1 month old) -> max_attempts = min(3-1, 3) = 2
183196 # Default PIN tried & failed (3 -> 2 remaining) -> max_attempts = min(2-1, 3) = 1
184197 # Counter read failed (0 or empty) -> max_attempts = 3 (fallback, don't block)
185- # Re-read counter without displaying (loop will show it)
186- info=" $( hotp_verification info 2> /dev/null) " || true
187- if [ " $DONGLE_BRAND " = " Nitrokey 3" ]; then
188- admin_pin_retries=$( echo " $info " | grep " Secrets app PIN counter:" | cut -d ' :' -f 2 | tr -d ' ' )
189- else
190- admin_pin_retries=$( echo " $info " | grep " Card counters: Admin" | grep -o ' Admin [0-9]*' | grep -o ' [0-9]*' )
191- fi
192- admin_pin_retries=" ${admin_pin_retries:- 0} "
193- if [ " $admin_pin_retries " -ge 2 ]; then
198+ # Re-read counter without displaying (loop will show it).
199+ query_pin_retries admin_pin_retries " $DONGLE_BRAND " 1
200+ if [ -n " $admin_pin_retries " ] && [ " $admin_pin_retries " -ge 2 ]; then
194201 max_attempts=$(( admin_pin_retries - 1 ))
195202 [ " $max_attempts " -gt 3 ] && max_attempts=3
196203 else
0 commit comments