diff --git a/initrd/bin/kexec-unseal-key b/initrd/bin/kexec-unseal-key index 346eda9b8..b12137a3d 100755 --- a/initrd/bin/kexec-unseal-key +++ b/initrd/bin/kexec-unseal-key @@ -26,6 +26,11 @@ DEBUG "Show PCRs" DEBUG "$(pcrs)" for tries in 1 2 3; do + # Show updating timestamp/TOTP until user presses Esc to continue to the + # passphrase prompt. This gives the user context while they prepare to + # type the LUKS passphrase. + show_totp_until_esc + read -s -p "Enter LUKS TPM Disk Unlock Key passphrase (blank to abort): " tpm_password echo if [ -z "$tpm_password" ]; then diff --git a/initrd/etc/functions b/initrd/etc/functions index fb5e35d95..8a8a07bca 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -1458,3 +1458,67 @@ load_keymap() { DO_WITH_DEBUG loadkeys /etc/board_keys.map fi } + +# Show an updating UTC timestamp and optional TOTP on a single refreshed line +# until the user presses the Escape key. Returns 0 after ESC pressed. +# Function name: show_totp_until_esc - clearly indicates this displays the +# TOTP code and waits for the user to press Escape to continue. +show_totp_until_esc() { + local now_str status_line current_totp ch + local last_totp_time=0 last_totp="" + printf "\n" # reserve a line for updates + + # Poll frequently (200ms) for responsiveness, but only refresh the + # displayed timestamp/TOTP when the displayed second changes. Cache + # the TOTP for a short interval to avoid repeated unseal calls. + local last_sec=0 + while :; do + now_str=$(date -u '+%Y-%m-%d %H:%M:%S UTC') + local now_epoch + now_epoch=$(date +%s) + local now_sec=$now_epoch + + # Refresh TOTP at most once every 1 second + if [ "$CONFIG_TPM" = "y" ] && [ "$CONFIG_TOTP_SKIP_QRCODE" != "y" ]; then + if [ $((now_epoch - last_totp_time)) -ge 1 ] || [ -z "$last_totp" ]; then + if current_totp=$(unseal-totp 2>/dev/null); then + last_totp="$current_totp" + last_totp_time=$now_epoch + else + # If unseal fails, clear cached value so we retry later + last_totp="" + last_totp_time=0 + fi + fi + fi + + # Only update display when the second changes to avoid flicker + if [ "$now_sec" -ne "$last_sec" ]; then + last_sec=$now_sec + # Build an explicit TOTP field so it's clear when no code is + # available (initial state or unseal failure). + local totp_field="" + if [ "$CONFIG_TPM" = "y" ] && [ "$CONFIG_TOTP_SKIP_QRCODE" != "y" ]; then + if [ -n "$last_totp" ]; then + totp_field=" | TOTP code: $last_totp" + else + totp_field=" | TOTP unavailable" + fi + fi + status_line="[$now_str]${totp_field} | Press Esc to continue..." + printf "\r%s\033[K" "$status_line" + fi + + # Short poll for keypress (200ms). If ESC pressed, exit and return 0. + if IFS= read -r -t 0.2 -n 1 ch; then + if [ "$ch" = $'\e' ]; then + # Print an extra blank line so the next prompt appears after + # an empty line (better UX before the passphrase prompt). + printf "\n\n" + return 0 + fi + # Ignore other keys and continue polling + fi + done +} +