Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions initrd/bin/gui-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,17 @@ gate_reseal_with_integrity_report() {
# starting the NK3 CCID teardown. This safety call covers the
# case where scdaemon was restarted between then and now.
release_scdaemon
STATUS "Checking USB security dongle presence before sealing"
STATUS "Checking $DONGLE_BRAND presence before sealing"
DEBUG "gate_reseal_with_integrity_report: checking HOTP token presence"
if hotp_verification info >/dev/null 2>&1; then
token_ok="y"
STATUS_OK "USB security dongle present and accessible"
STATUS_OK "$DONGLE_BRAND present and accessible"
break
fi
DEBUG "gate_reseal_with_integrity_report: HOTP token not accessible"
if ! whiptail_warning --title "USB Security Dongle Required" \
if ! whiptail_warning --title "$DONGLE_BRAND Required" \
--yes-button "Retry" --no-button "Abort" \
--yesno "Your USB security dongle must be present before sealing new secrets.\n\nInsert the dongle and choose Retry, or Abort." 0 80; then
--yesno "Your $DONGLE_BRAND must be present before sealing new secrets.\n\nInsert the dongle and choose Retry, or Abort." 0 80; then
return 1
fi
done
Expand Down Expand Up @@ -292,7 +292,7 @@ generate_totp_hotp() {
if [ -x /bin/hotp_verification ]; then
# If we have a TPM and a HOTP USB Security dongle
if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then
INPUT "Once you have scanned the QR code, press Enter to configure your HOTP USB Security dongle (e.g. Librem Key or Nitrokey)"
INPUT "Once you have scanned the QR code, press Enter to configure your $DONGLE_BRAND"
fi
TRACE_FUNC
/bin/seal-hotpkey.sh || DIE "Failed to generate HOTP secret"
Expand All @@ -314,11 +314,11 @@ prompt_missing_gpg_key_action() {
TRACE_FUNC
local retry_label retry_msg
if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" = "y" ]; then
retry_label=' Retry (insert signing card or backup USB drive)'
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?"
retry_label=" Retry (insert $DONGLE_BRAND or backup USB drive)"
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?"
else
retry_label=' Retry (after connecting the correct signing card)'
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?"
retry_label=" Retry (after connecting $DONGLE_BRAND)"
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?"
fi
whiptail_error --title "ERROR: GPG signing key unavailable" \
--menu "$retry_msg" 0 80 4 \
Expand Down
16 changes: 10 additions & 6 deletions initrd/bin/oem-factory-reset.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ rm -f /tmp/hotpkey_fw_shown

TRACE_FUNC

# Enable USB and detect branding early — $DONGLE_BRAND is used throughout this script.
enable_usb
detect_usb_security_dongle_branding

# use TERM to exit on error
trap "exit 1" TERM
export TOP_PID=$$
Expand Down Expand Up @@ -180,7 +184,7 @@ reset_nk3_secret_app() {
else
error_code=$?
if [ $error_code -eq 3 ] && [ $attempt -lt 3 ]; then
whiptail_warning --msgbox "Nitrokey 3 requires physical presence: touch the dongle when requested" $HEIGHT $WIDTH --title "Nk3 secrets app reset attempt: $attempt/3"
whiptail_warning --msgbox "$DONGLE_BRAND requires physical presence: touch the dongle when requested" $HEIGHT $WIDTH --title "$DONGLE_BRAND secrets app reset attempt: $attempt/3"
else
whiptail_error_die "Nitrokey 3's Secrets app reset failed with error:$error_code. Contact Nitrokey support"
fi
Expand Down Expand Up @@ -1043,7 +1047,7 @@ usb_security_token_capabilities_check() {
DONGLE_FW_VERSION="$(echo "$hotp_token_info" | grep "Firmware:" | sed 's/.*: *//')"
case "$DONGLE_FW_VERSION" in v*) ;; *) DONGLE_FW_VERSION="v$DONGLE_FW_VERSION" ;; esac
fi
DEBUG "Dongle firmware version: $DONGLE_FW_VERSION"
DEBUG "$DONGLE_BRAND firmware version: $DONGLE_FW_VERSION"
fi
fi
}
Expand Down Expand Up @@ -1222,7 +1226,7 @@ if [ "$use_defaults" == "n" -o "$use_defaults" == "N" ]; then
INPUT "Enter desired NK3 Secrets app PIN / GPG Admin PIN (6-${MAX_HOTP_GPG_PIN_LENGTH} chars):" -r ADMIN_PIN
done
else
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"
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"
while [[ ${#ADMIN_PIN} -lt 6 ]] || [[ ${#ADMIN_PIN} -gt $MAX_HOTP_GPG_PIN_LENGTH ]]; do
INPUT "Enter desired GPG Admin PIN (6-${MAX_HOTP_GPG_PIN_LENGTH} chars):" -r ADMIN_PIN
done
Expand Down Expand Up @@ -1320,10 +1324,10 @@ fi
if [ "$GPG_GEN_KEY_IN_MEMORY" = "n" -o "$GPG_GEN_KEY_IN_MEMORY_COPY_TO_SMARTCARD" = "y" ]; then
enable_usb
if ! gpg --card-status >/dev/null 2>&1; then
local_whiptail_error "Can't access USB Security dongle; \nPlease remove and reinsert, then press Enter."
local_whiptail_error "Can't access $DONGLE_BRAND; \nPlease remove and reinsert, then press Enter."
if ! gpg --card-status >/dev/null 2>/tmp/error; then
ERROR=$(tail -n 1 /tmp/error | fold -s)
whiptail_error_die "Unable to detect USB Security dongle:\n\n${ERROR}"
whiptail_error_die "Unable to detect $DONGLE_BRAND:\n\n${ERROR}"
fi
fi

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

#if nk3 detected, we add the NK3 Secrets App PIN
#if nk3 detected, we add the NK3 Secrets app PIN
if [ "$DONGLE_BRAND" = "Nitrokey 3" ]; then
passphrases+="Nitrokey 3 Secrets app PIN: ${ADMIN_PIN}\n"
fi
Expand Down
68 changes: 56 additions & 12 deletions initrd/bin/seal-hotpkey.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ DEBUG "Signature key was created at $(date -d "@$gpg_key_create_time")"
now_date="$(date '+%s')"

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

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

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

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

# If the default PIN was tried and failed, we consumed 1 attempt.
# Re-read the counter and limit user attempts accordingly.
# Leave at least 1 attempt unconsumed to avoid accidental lockout.
#
# max_attempts calculation:
# - Read current retry counter (may be lower if default PIN consumed 1)
# - Subtract 1 to preserve one final attempt for the user
# - Cap at 3 to match the pre-NK3 factory default, so the user experience
# is consistent regardless of device.
# - If the counter read is unreliable (0 or 1), fall back to 3 attempts
# so the user is never blocked from sealing.
#
# Example outcomes for NK3 (factory default: 8):
# Default PIN skipped (key >1 month old) -> max_attempts = min(8-1, 3) = 3
# Default PIN tried & failed (8 -> 7 remaining) -> max_attempts = min(7-1, 3) = 3
# Default PIN tried & failed (4 -> 3 remaining) -> max_attempts = min(3-1, 3) = 2
# Default PIN tried & failed (2 -> 1 remaining) -> max_attempts = 3 (fallback, don't block)
# Counter read failed (0 or empty) -> max_attempts = 3 (fallback, don't block)
#
# Example outcomes for pre-NK3 (factory default: 3):
# Default PIN skipped (key >1 month old) -> max_attempts = min(3-1, 3) = 2
# Default PIN tried & failed (3 -> 2 remaining) -> max_attempts = min(2-1, 3) = 1
# Counter read failed (0 or empty) -> max_attempts = 3 (fallback, don't block)
# Re-read counter without displaying (loop will show it)
info="$(hotp_verification info 2>/dev/null)" || true
if [ "$DONGLE_BRAND" = "Nitrokey 3" ]; then
admin_pin_retries=$(echo "$info" | grep "Secrets app PIN counter:" | cut -d ':' -f 2 | tr -d ' ')
else
admin_pin_retries=$(echo "$info" | grep "Card counters: Admin" | grep -o 'Admin [0-9]*' | grep -o '[0-9]*')
fi
admin_pin_retries="${admin_pin_retries:-0}"
if [ "$admin_pin_retries" -ge 2 ]; then
max_attempts=$((admin_pin_retries - 1))
[ "$max_attempts" -gt 3 ] && max_attempts=3
else
max_attempts=3
fi

# prompt user for PIN; re-query counter before each attempt so the user
# sees the decremented count after a wrong PIN (same pattern as kexec-sign-config.sh)
for tries in 1 2 3; do
for tries in $(seq 1 $max_attempts); do
show_pin_retries
if [ "$tries" -eq 1 ]; then
INPUT "Enter your $DONGLE_BRAND GPG Admin PIN (attempt $tries/3):" -r -s admin_pin
INPUT "Enter your $DONGLE_BRAND ${prompt_message} PIN (attempt $tries/$max_attempts):" -r -s admin_pin
else
INPUT "Wrong PIN - re-enter your $DONGLE_BRAND GPG Admin PIN (attempt $tries/3):" -r -s admin_pin
INPUT "Wrong PIN - re-enter your $DONGLE_BRAND ${prompt_message} PIN (attempt $tries/$max_attempts):" -r -s admin_pin
fi
if hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$DONGLE_BRAND"; then
break
fi
if [ "$tries" -eq 3 ]; then
if [ "$tries" -eq "$max_attempts" ]; then
# don't leak key on failure
shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null
case "$DONGLE_BRAND" in
"Nitrokey Pro" | "Nitrokey Storage" | "Nitrokey 3")
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."
"Nitrokey 3")
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."
;;
"Nitrokey Pro" | "Nitrokey Storage")
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."
;;
"Librem Key")
DIE "Setting HOTP secret on $DONGLE_BRAND failed after 3 attempts. To reset GPG Admin PIN: redo Re-Ownership or contact Purism support."
DIE "Setting HOTP secret on $DONGLE_BRAND failed after $max_attempts attempts. To reset GPG Admin PIN: redo Re-Ownership or contact Purism support."
;;
*)
DIE "Setting HOTP secret failed after 3 attempts"
DIE "Setting HOTP secret failed after $max_attempts attempts"
;;
esac
fi
done
else
# Default PIN was accepted — security reminder, not a fatal error.
# NOTE prints blank lines before/after and is always visible; no INPUT needed.
NOTE "Default GPG Admin PIN detected. Change it via Options --> OEM Factory Reset / Re-Ownership."
NOTE "Default ${prompt_message} PIN detected. Change it via Options --> OEM Factory Reset / Re-Ownership."
fi

# HOTP key no longer needed
Expand Down
20 changes: 19 additions & 1 deletion initrd/etc/functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,22 @@ pin_color() {
# 1050:0404 Yubikey 5 (FIDO+CCID)
detect_usb_security_dongle_branding() {
TRACE_FUNC
local usb_was_enabled="${_USB_ENABLED:-n}"
# Fast path: avoid USB re-init and lsusb scan when branding is already known
# and USB has already been initialized in this process.
if [ "$DONGLE_BRAND" != "USB Security dongle" ] \
&& [ -n "$DONGLE_BRAND" ] \
&& [ "$usb_was_enabled" = "y" ]; then
return
fi

# Child scripts can inherit DONGLE_BRAND while _USB_ENABLED resets, so always
# initialize USB unless the fast path above was taken.
enable_usb
[ "$usb_was_enabled" != "y" ] && wait_for_usb_devices

# If branding is already specific, USB is now ready and no re-scan is needed.
[ "$DONGLE_BRAND" != "USB Security dongle" ] && [ -n "$DONGLE_BRAND" ] && return
local lsusb_out
lsusb_out="$(lsusb)"
Comment thread
tlaurion marked this conversation as resolved.
DEBUG "lsusb output: $lsusb_out"
Expand Down Expand Up @@ -644,7 +660,7 @@ cache_gpg_signing_pin() {
# keystrokes from previous prompts cannot silently satisfy this read.
local card_confirm=""
if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]; then
INPUT "Use your GPG security dongle (Enter/y) or backup thumb drive (b)? [Y/b]:" -n 1 -r card_confirm
INPUT "Use your $DONGLE_BRAND (Enter/y) or backup thumb drive (b)? [Y/b]:" -n 1 -r card_confirm
while [ "$card_confirm" != "y" \
-a "$card_confirm" != "Y" \
-a "$card_confirm" != "b" \
Expand Down Expand Up @@ -831,6 +847,8 @@ cache_gpg_signing_pin() {
}

confirm_gpg_card() {
enable_usb
detect_usb_security_dongle_branding
cache_gpg_signing_pin "$@"
}

Expand Down
Loading