Skip to content

Commit a1bf81d

Browse files
authored
Merge pull request #2088 from tlaurion/fix_nk3_secrets_app-not-GPG_admin_pin
NK3: use device-specific branding and PIN labels consistently across codebase
2 parents 522e331 + bd0f786 commit a1bf81d

File tree

5 files changed

+122
-54
lines changed

5 files changed

+122
-54
lines changed

initrd/bin/gui-init.sh

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,17 +249,17 @@ gate_reseal_with_integrity_report() {
249249
# starting the NK3 CCID teardown. This safety call covers the
250250
# case where scdaemon was restarted between then and now.
251251
release_scdaemon
252-
STATUS "Checking USB security dongle presence before sealing"
252+
STATUS "Checking $DONGLE_BRAND presence before sealing"
253253
DEBUG "gate_reseal_with_integrity_report: checking HOTP token presence"
254254
if hotp_verification info >/dev/null 2>&1; then
255255
token_ok="y"
256-
STATUS_OK "USB security dongle present and accessible"
256+
STATUS_OK "$DONGLE_BRAND present and accessible"
257257
break
258258
fi
259259
DEBUG "gate_reseal_with_integrity_report: HOTP token not accessible"
260-
if ! whiptail_warning --title "USB Security Dongle Required" \
260+
if ! whiptail_warning --title "$DONGLE_BRAND Required" \
261261
--yes-button "Retry" --no-button "Abort" \
262-
--yesno "Your USB security dongle must be present before sealing new secrets.\n\nInsert the dongle and choose Retry, or Abort." 0 80; then
262+
--yesno "Your $DONGLE_BRAND must be present before sealing new secrets.\n\nInsert the dongle and choose Retry, or Abort." 0 80; then
263263
return 1
264264
fi
265265
done
@@ -292,7 +292,7 @@ generate_totp_hotp() {
292292
if [ -x /bin/hotp_verification ]; then
293293
# If we have a TPM and a HOTP USB Security dongle
294294
if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then
295-
INPUT "Once you have scanned the QR code, press Enter to configure your HOTP USB Security dongle (e.g. Librem Key or Nitrokey)"
295+
INPUT "Once you have scanned the QR code, press Enter to configure your $DONGLE_BRAND"
296296
fi
297297
TRACE_FUNC
298298
/bin/seal-hotpkey.sh || DIE "Failed to generate HOTP secret"
@@ -314,11 +314,11 @@ prompt_missing_gpg_key_action() {
314314
TRACE_FUNC
315315
local retry_label retry_msg
316316
if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" = "y" ]; then
317-
retry_label=' Retry (insert signing card or backup USB drive)'
318-
retry_msg="Cannot sign /boot because no private GPG signing key is available (card not inserted, wiped, or key not set up).\n\nInsert your signing card or backup USB drive and retry.\n\nHow would you like to proceed?"
317+
retry_label=" Retry (insert $DONGLE_BRAND or backup USB drive)"
318+
retry_msg="Cannot sign /boot because no private GPG signing key is available ($DONGLE_BRAND not inserted, wiped, or key not set up).\n\nInsert your $DONGLE_BRAND or backup USB drive and retry.\n\nHow would you like to proceed?"
319319
else
320-
retry_label=' Retry (after connecting the correct signing card)'
321-
retry_msg="Cannot sign /boot because no private GPG signing key is available (card not inserted, wiped, or key not set up).\n\nIf you have the correct signing card, insert it and retry.\n\nHow would you like to proceed?"
320+
retry_label=" Retry (after connecting $DONGLE_BRAND)"
321+
retry_msg="Cannot sign /boot because no private GPG signing key is available ($DONGLE_BRAND not inserted, wiped, or key not set up).\n\nInsert your $DONGLE_BRAND and retry.\n\nHow would you like to proceed?"
322322
fi
323323
whiptail_error --title "ERROR: GPG signing key unavailable" \
324324
--menu "$retry_msg" 0 80 4 \

initrd/bin/oem-factory-reset.sh

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ rm -f /tmp/hotpkey_fw_shown
2323

2424
TRACE_FUNC
2525

26+
# Enable USB and detect branding early — $DONGLE_BRAND is used throughout this script.
27+
enable_usb
28+
detect_usb_security_dongle_branding
29+
2630
# use TERM to exit on error
2731
trap "exit 1" TERM
2832
export TOP_PID=$$
@@ -180,7 +184,7 @@ reset_nk3_secret_app() {
180184
else
181185
error_code=$?
182186
if [ $error_code -eq 3 ] && [ $attempt -lt 3 ]; then
183-
whiptail_warning --msgbox "Nitrokey 3 requires physical presence: touch the dongle when requested" $HEIGHT $WIDTH --title "Nk3 secrets app reset attempt: $attempt/3"
187+
whiptail_warning --msgbox "$DONGLE_BRAND requires physical presence: touch the dongle when requested" $HEIGHT $WIDTH --title "$DONGLE_BRAND secrets app reset attempt: $attempt/3"
184188
else
185189
whiptail_error_die "Nitrokey 3's Secrets app reset failed with error:$error_code. Contact Nitrokey support"
186190
fi
@@ -1043,7 +1047,7 @@ usb_security_token_capabilities_check() {
10431047
DONGLE_FW_VERSION="$(echo "$hotp_token_info" | grep "Firmware:" | sed 's/.*: *//')"
10441048
case "$DONGLE_FW_VERSION" in v*) ;; *) DONGLE_FW_VERSION="v$DONGLE_FW_VERSION" ;; esac
10451049
fi
1046-
DEBUG "Dongle firmware version: $DONGLE_FW_VERSION"
1050+
DEBUG "$DONGLE_BRAND firmware version: $DONGLE_FW_VERSION"
10471051
fi
10481052
fi
10491053
}
@@ -1222,7 +1226,7 @@ if [ "$use_defaults" == "n" -o "$use_defaults" == "N" ]; then
12221226
INPUT "Enter desired NK3 Secrets app PIN / GPG Admin PIN (6-${MAX_HOTP_GPG_PIN_LENGTH} chars):" -r ADMIN_PIN
12231227
done
12241228
else
1225-
NOTE "GPG Admin PIN: management tasks on USB Security dongle, seal measurements under HOTP. 3 attempts max, locks Admin out. DO NOT FORGET. Recommended: 2 words"
1229+
NOTE "GPG Admin PIN: management tasks on $DONGLE_BRAND, seal measurements under HOTP. 3 attempts max, locks Admin out. DO NOT FORGET. Recommended: 2 words"
12261230
while [[ ${#ADMIN_PIN} -lt 6 ]] || [[ ${#ADMIN_PIN} -gt $MAX_HOTP_GPG_PIN_LENGTH ]]; do
12271231
INPUT "Enter desired GPG Admin PIN (6-${MAX_HOTP_GPG_PIN_LENGTH} chars):" -r ADMIN_PIN
12281232
done
@@ -1320,10 +1324,10 @@ fi
13201324
if [ "$GPG_GEN_KEY_IN_MEMORY" = "n" -o "$GPG_GEN_KEY_IN_MEMORY_COPY_TO_SMARTCARD" = "y" ]; then
13211325
enable_usb
13221326
if ! gpg --card-status >/dev/null 2>&1; then
1323-
local_whiptail_error "Can't access USB Security dongle; \nPlease remove and reinsert, then press Enter."
1327+
local_whiptail_error "Can't access $DONGLE_BRAND; \nPlease remove and reinsert, then press Enter."
13241328
if ! gpg --card-status >/dev/null 2>/tmp/error; then
13251329
ERROR=$(tail -n 1 /tmp/error | fold -s)
1326-
whiptail_error_die "Unable to detect USB Security dongle:\n\n${ERROR}"
1330+
whiptail_error_die "Unable to detect $DONGLE_BRAND:\n\n${ERROR}"
13271331
fi
13281332
fi
13291333

@@ -1605,7 +1609,7 @@ if [ "$CONFIG_TPM" = "y" ]; then
16051609
passphrases+="TPM Owner Passphrase: ${TPM_PASS}\n"
16061610
fi
16071611

1608-
#if nk3 detected, we add the NK3 Secrets App PIN
1612+
#if nk3 detected, we add the NK3 Secrets app PIN
16091613
if [ "$DONGLE_BRAND" = "Nitrokey 3" ]; then
16101614
passphrases+="Nitrokey 3 Secrets app PIN: ${ADMIN_PIN}\n"
16111615
fi

initrd/bin/seal-hotpkey.sh

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ DEBUG "Signature key was created at $(date -d "@$gpg_key_create_time")"
8888
now_date="$(date '+%s')"
8989

9090
# Get the number of HOTP related PIN retry attempts remaining.
91-
# NK3 uses "Secrets app PIN counter"; all pre-NK3 devices use "Card counters: Admin".
91+
# NK3 uses "Secrets app PIN counter" (factory default: 8 attempts);
92+
# all pre-NK3 devices use "Card counters: Admin" (factory default: 3 attempts).
9293
if [ "$DONGLE_BRAND" = "Nitrokey 3" ]; then
9394
admin_pin_retries=$(echo "$hotp_token_info" | grep "Secrets app PIN counter:" | cut -d ':' -f 2 | tr -d ' ')
9495
prompt_message="Secrets app"
@@ -103,6 +104,8 @@ DEBUG "HOTP related PIN retry counter is $admin_pin_retries"
103104
hotpkey_fw_display "$hotp_token_info" "$DONGLE_BRAND"
104105

105106
# Re-query and display the current PIN retry counter before each manual prompt.
107+
# Updates the global $admin_pin_retries (no local keyword) so callers can use
108+
# the fresh value for decisions (e.g. max_attempts calculation below).
106109
# prompt_message is already set for the device type (NK3 vs older), reuse it.
107110
show_pin_retries() {
108111
local info
@@ -113,7 +116,7 @@ show_pin_retries() {
113116
admin_pin_retries=$(echo "$info" | grep "Card counters: Admin" | grep -o 'Admin [0-9]*' | grep -o '[0-9]*')
114117
fi
115118
admin_pin_retries="${admin_pin_retries:-0}"
116-
STATUS "$DONGLE_BRAND GPG Admin PIN retries remaining: $(pin_color "$admin_pin_retries")${admin_pin_retries}\033[0m"
119+
STATUS "$DONGLE_BRAND ${prompt_message} PIN retries remaining: $(pin_color "$admin_pin_retries")${admin_pin_retries}\033[0m"
117120
}
118121

119122
# Try using factory default admin PIN for 1 month following OEM reset to ease
@@ -131,7 +134,7 @@ if [ "$((now_date - gpg_key_create_time))" -gt "$month_secs" ]; then
131134
elif [ "$admin_pin_retries" -lt 3 ]; then
132135
DEBUG "Not trying default PIN ($admin_pin): only $admin_pin_retries attempt(s) left"
133136
else
134-
STATUS "Trying GPG Admin PIN to seal HOTP secret on $DONGLE_BRAND"
137+
STATUS "Trying ${prompt_message} PIN to seal HOTP secret on $DONGLE_BRAND"
135138
# NK3 requires physical touch confirmation for the initialize operation
136139
if [ "$DONGLE_BRAND" = "Nitrokey 3" ]; then
137140
NOTE "Nitrokey 3 requires physical presence: touch the dongle when prompted"
@@ -144,38 +147,79 @@ fi
144147

145148
if [ "$admin_pin_status" -ne 0 ]; then
146149

150+
# If the default PIN was tried and failed, we consumed 1 attempt.
151+
# Re-read the counter and limit user attempts accordingly.
152+
# Leave at least 1 attempt unconsumed to avoid accidental lockout.
153+
#
154+
# max_attempts calculation:
155+
# - Read current retry counter (may be lower if default PIN consumed 1)
156+
# - Subtract 1 to preserve one final attempt for the user
157+
# - Cap at 3 to match the pre-NK3 factory default, so the user experience
158+
# is consistent regardless of device.
159+
# - If the counter read is unreliable (0 or 1), fall back to 3 attempts
160+
# so the user is never blocked from sealing.
161+
#
162+
# Example outcomes for NK3 (factory default: 8):
163+
# Default PIN skipped (key >1 month old) -> max_attempts = min(8-1, 3) = 3
164+
# Default PIN tried & failed (8 -> 7 remaining) -> max_attempts = min(7-1, 3) = 3
165+
# Default PIN tried & failed (4 -> 3 remaining) -> max_attempts = min(3-1, 3) = 2
166+
# Default PIN tried & failed (2 -> 1 remaining) -> max_attempts = 3 (fallback, don't block)
167+
# Counter read failed (0 or empty) -> max_attempts = 3 (fallback, don't block)
168+
#
169+
# Example outcomes for pre-NK3 (factory default: 3):
170+
# Default PIN skipped (key >1 month old) -> max_attempts = min(3-1, 3) = 2
171+
# Default PIN tried & failed (3 -> 2 remaining) -> max_attempts = min(2-1, 3) = 1
172+
# Counter read failed (0 or empty) -> max_attempts = 3 (fallback, don't block)
173+
# Re-read counter without displaying (loop will show it)
174+
info="$(hotp_verification info 2>/dev/null)" || true
175+
if [ "$DONGLE_BRAND" = "Nitrokey 3" ]; then
176+
admin_pin_retries=$(echo "$info" | grep "Secrets app PIN counter:" | cut -d ':' -f 2 | tr -d ' ')
177+
else
178+
admin_pin_retries=$(echo "$info" | grep "Card counters: Admin" | grep -o 'Admin [0-9]*' | grep -o '[0-9]*')
179+
fi
180+
admin_pin_retries="${admin_pin_retries:-0}"
181+
if [ "$admin_pin_retries" -ge 2 ]; then
182+
max_attempts=$((admin_pin_retries - 1))
183+
[ "$max_attempts" -gt 3 ] && max_attempts=3
184+
else
185+
max_attempts=3
186+
fi
187+
147188
# prompt user for PIN; re-query counter before each attempt so the user
148189
# sees the decremented count after a wrong PIN (same pattern as kexec-sign-config.sh)
149-
for tries in 1 2 3; do
190+
for tries in $(seq 1 $max_attempts); do
150191
show_pin_retries
151192
if [ "$tries" -eq 1 ]; then
152-
INPUT "Enter your $DONGLE_BRAND GPG Admin PIN (attempt $tries/3):" -r -s admin_pin
193+
INPUT "Enter your $DONGLE_BRAND ${prompt_message} PIN (attempt $tries/$max_attempts):" -r -s admin_pin
153194
else
154-
INPUT "Wrong PIN - re-enter your $DONGLE_BRAND GPG Admin PIN (attempt $tries/3):" -r -s admin_pin
195+
INPUT "Wrong PIN - re-enter your $DONGLE_BRAND ${prompt_message} PIN (attempt $tries/$max_attempts):" -r -s admin_pin
155196
fi
156197
if hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$DONGLE_BRAND"; then
157198
break
158199
fi
159-
if [ "$tries" -eq 3 ]; then
200+
if [ "$tries" -eq "$max_attempts" ]; then
160201
# don't leak key on failure
161202
shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null
162203
case "$DONGLE_BRAND" in
163-
"Nitrokey Pro" | "Nitrokey Storage" | "Nitrokey 3")
164-
DIE "Setting HOTP secret on $DONGLE_BRAND failed after 3 attempts. To reset GPG Admin PIN: redo Re-Ownership, or use Nitrokey App 2, or contact Nitrokey support."
204+
"Nitrokey 3")
205+
DIE "Setting HOTP secret on $DONGLE_BRAND failed after $max_attempts attempts. To reset ${prompt_message} PIN: redo Re-Ownership, or use Nitrokey App 2, or contact Nitrokey support."
206+
;;
207+
"Nitrokey Pro" | "Nitrokey Storage")
208+
DIE "Setting HOTP secret on $DONGLE_BRAND failed after $max_attempts attempts. To reset GPG Admin PIN: redo Re-Ownership, or use Nitrokey App 2, or contact Nitrokey support."
165209
;;
166210
"Librem Key")
167-
DIE "Setting HOTP secret on $DONGLE_BRAND failed after 3 attempts. To reset GPG Admin PIN: redo Re-Ownership or contact Purism support."
211+
DIE "Setting HOTP secret on $DONGLE_BRAND failed after $max_attempts attempts. To reset GPG Admin PIN: redo Re-Ownership or contact Purism support."
168212
;;
169213
*)
170-
DIE "Setting HOTP secret failed after 3 attempts"
214+
DIE "Setting HOTP secret failed after $max_attempts attempts"
171215
;;
172216
esac
173217
fi
174218
done
175219
else
176220
# Default PIN was accepted — security reminder, not a fatal error.
177221
# NOTE prints blank lines before/after and is always visible; no INPUT needed.
178-
NOTE "Default GPG Admin PIN detected. Change it via Options --> OEM Factory Reset / Re-Ownership."
222+
NOTE "Default ${prompt_message} PIN detected. Change it via Options --> OEM Factory Reset / Re-Ownership."
179223
fi
180224

181225
# HOTP key no longer needed

initrd/etc/functions.sh

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,22 @@ pin_color() {
500500
# 1050:0404 Yubikey 5 (FIDO+CCID)
501501
detect_usb_security_dongle_branding() {
502502
TRACE_FUNC
503+
local usb_was_enabled="${_USB_ENABLED:-n}"
504+
# Fast path: avoid USB re-init and lsusb scan when branding is already known
505+
# and USB has already been initialized in this process.
506+
if [ "$DONGLE_BRAND" != "USB Security dongle" ] \
507+
&& [ -n "$DONGLE_BRAND" ] \
508+
&& [ "$usb_was_enabled" = "y" ]; then
509+
return
510+
fi
511+
512+
# Child scripts can inherit DONGLE_BRAND while _USB_ENABLED resets, so always
513+
# initialize USB unless the fast path above was taken.
514+
enable_usb
515+
[ "$usb_was_enabled" != "y" ] && wait_for_usb_devices
516+
517+
# If branding is already specific, USB is now ready and no re-scan is needed.
518+
[ "$DONGLE_BRAND" != "USB Security dongle" ] && [ -n "$DONGLE_BRAND" ] && return
503519
local lsusb_out
504520
lsusb_out="$(lsusb)"
505521
DEBUG "lsusb output: $lsusb_out"
@@ -644,7 +660,7 @@ cache_gpg_signing_pin() {
644660
# keystrokes from previous prompts cannot silently satisfy this read.
645661
local card_confirm=""
646662
if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]; then
647-
INPUT "Use your GPG security dongle (Enter/y) or backup thumb drive (b)? [Y/b]:" -n 1 -r card_confirm
663+
INPUT "Use your $DONGLE_BRAND (Enter/y) or backup thumb drive (b)? [Y/b]:" -n 1 -r card_confirm
648664
while [ "$card_confirm" != "y" \
649665
-a "$card_confirm" != "Y" \
650666
-a "$card_confirm" != "b" \
@@ -831,6 +847,8 @@ cache_gpg_signing_pin() {
831847
}
832848

833849
confirm_gpg_card() {
850+
enable_usb
851+
detect_usb_security_dongle_branding
834852
cache_gpg_signing_pin "$@"
835853
}
836854

0 commit comments

Comments
 (0)