diff --git a/.circleci/config.yml b/.circleci/config.yml index 3bdcb9b1e..916358216 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -253,7 +253,7 @@ jobs: - save_cache: key: x86-musl-cross-make-nix-docker-heads-{{ checksum "./tmpDir/musl-cross-make.sha256sums" }}{{ .Environment.CACHE_VERSION }} paths: - - build/x86/musl-cross-make-fd6be58297ee21fcba89216ccd0d4aca1e3f1c5c + - build/x86/musl-cross-make-227df8b99103f9c59f6570babf892978e293082f - crossgcc/x86 - install/x86 - packages/x86 @@ -305,7 +305,7 @@ jobs: key: x86-coreboot-musl-cross-make-nix-docker-heads-{{ checksum "./tmpDir/coreboot_musl-cross-make.sha256sums" }}{{ .Environment.CACHE_VERSION }}-<< parameters.coreboot_dir >> paths: - build/x86/<< parameters.coreboot_dir >> - - build/x86/musl-cross-make-fd6be58297ee21fcba89216ccd0d4aca1e3f1c5c + - build/x86/musl-cross-make-227df8b99103f9c59f6570babf892978e293082f - crossgcc/x86 - install/x86 - packages/x86 @@ -346,7 +346,7 @@ jobs: - save_cache: key: ppc64-musl-cross-make-nix-docker-heads-{{ checksum "./tmpDir/musl-cross-make.sha256sums" }}{{ .Environment.CACHE_VERSION }} paths: - - build/ppc64/musl-cross-make-fd6be58297ee21fcba89216ccd0d4aca1e3f1c5c + - build/ppc64/musl-cross-make-227df8b99103f9c59f6570babf892978e293082f - crossgcc/ppc64 - install/ppc64 - packages/ppc64 @@ -406,7 +406,7 @@ jobs: key: ppc64-coreboot-musl-cross-make-nix-docker-heads-{{ checksum "./tmpDir/coreboot_musl-cross-make.sha256sums" }}{{ .Environment.CACHE_VERSION }}-<< parameters.coreboot_dir >> paths: - build/ppc64/<< parameters.coreboot_dir >> - - build/ppc64/musl-cross-make-fd6be58297ee21fcba89216ccd0d4aca1e3f1c5c + - build/ppc64/musl-cross-make-227df8b99103f9c59f6570babf892978e293082f - crossgcc/ppc64 - install/ppc64 - packages/ppc64 diff --git a/Makefile b/Makefile index 462e04c0b..d103e7ac0 100644 --- a/Makefile +++ b/Makefile @@ -472,26 +472,40 @@ define define_module = # module-specific cleanup action to get rid of it. $(build)/$($1_base_dir)/.canary: FORCE if [ ! -e "$$@" ] && [ ! -d "$(build)/$($1_base_dir)" ]; then \ - echo "INFO: .canary file and directory not found. Cloning repository $($1_repo) into $(build)/$($1_base_dir)" && \ - git clone $($1_repo) "$(build)/$($1_base_dir)" && \ + echo "INFO: .canary file and directory not found. Creating standalone clone of $($1_repo) at $(build)/$($1_base_dir)" && \ + mkdir -p "$(build)/$($1_base_dir)" && \ + cd "$(build)/$($1_base_dir)" && \ + echo "INFO: Initializing git repository" && \ + git init 2>/dev/null && \ + echo "INFO: Adding remote origin: $($1_repo)" && \ + git remote add origin $($1_repo) && \ + echo "INFO: Fetching commit $($1_commit_hash) (without recursing submodules)" && \ + git fetch origin $($1_commit_hash) --recurse-submodules=no && \ echo "INFO: Resetting repository to commit $($1_commit_hash)" && \ - git -C "$(build)/$($1_base_dir)" reset --hard $($1_commit_hash) && \ + git reset --hard $($1_commit_hash) && \ echo "INFO: Creating .canary file with repo and commit hash" && \ echo -n '$($1_repo)|$($1_commit_hash)' > "$$@" ; \ elif [ ! -e "$$@" ] || [ "$$$$(cat "$$@")" != '$($1_repo)|$($1_commit_hash)' ]; then \ - echo "INFO: .canary file missing or differs. Resetting $1 to $($1_repo) at $($1_commit_hash)" && \ - git -C "$(build)/$($1_base_dir)" reset --hard HEAD^ && \ - echo "INFO: Fetching commit $($1_commit_hash) from $($1_repo) (without recursing submodules)" && \ - git -C "$(build)/$($1_base_dir)" fetch $($1_repo) $($1_commit_hash) --recurse-submodules=no && \ + echo "INFO: .canary file missing or differs. Converting to standalone clone at $(build)/$($1_base_dir)" && \ + echo "INFO: Removing stale worktree reference if present" && \ + rm -f "$(build)/$($1_base_dir)/.git" 2>/dev/null; \ + cd "$(build)/$($1_base_dir)" && \ + echo "INFO: Initializing git repository" && \ + git init 2>/dev/null || true; \ + echo "INFO: Configuring remote origin: $($1_repo)" && \ + git remote remove origin 2>/dev/null || true; \ + git remote add origin $($1_repo) && \ + echo "INFO: Fetching commit $($1_commit_hash) (without recursing submodules)" && \ + git fetch origin $($1_commit_hash) --recurse-submodules=no && \ echo "INFO: Resetting repository to commit $($1_commit_hash)" && \ - git -C "$(build)/$($1_base_dir)" reset --hard $($1_commit_hash) && \ + git reset --hard $($1_commit_hash) && \ echo "INFO: Cleaning repository directory (including payloads and util/cbmem)" && \ - git -C "$(build)/$($1_base_dir)" clean -df && \ - git -C "$(build)/$($1_base_dir)" clean -dffx payloads util/cbmem && \ + git clean -df && \ + git clean -dffx payloads util/cbmem && \ echo "INFO: Synchronizing submodules" && \ - git -C "$(build)/$($1_base_dir)" submodule sync && \ + git submodule sync && \ echo "INFO: Updating submodules (init and checkout)" && \ - git -C "$(build)/$($1_base_dir)" submodule update --init --checkout && \ + git submodule update --init --checkout && \ echo "INFO: Cleaning board-specific build directories to prevent stale artifacts" && \ rm -rf "$(build)/$(BOARD)" "$(build)/$($1_base_dir)/$(BOARD)" && \ echo "INFO: Recreating board directories" && \ diff --git a/initrd/bin/flash.sh b/initrd/bin/flash.sh index e769c15c6..716b4f050 100755 --- a/initrd/bin/flash.sh +++ b/initrd/bin/flash.sh @@ -24,11 +24,21 @@ flash_rom() { $CONFIG_FLASH_OPTIONS -r "${ROM}" \ || recovery "Backup to $ROM failed" else + STATUS "Preparing new ROM image for flashing" cp "$ROM" /tmp/${CONFIG_BOARD}.rom + STATUS "Verifying SHA-256 checksum of ROM image" sha256sum /tmp/${CONFIG_BOARD}.rom if [ "$CLEAN" -eq 0 ]; then + # preserve_rom copies heads/ runtime config files (GPG keyring, + # TOTP/HOTP secrets, LUKS DUK slot data, runtime settings) from + # the currently running ROM's CBFS into the new ROM image before + # flashing. These files would otherwise be lost on each firmware + # update. Skip with -c (clean flash) flag. + DEBUG "flash_rom: CLEAN=$CLEAN — preserving heads/ CBFS files" preserve_rom /tmp/${CONFIG_BOARD}.rom \ || recovery "$ROM: Config preservation failed" + else + DEBUG "flash_rom: CLEAN=$CLEAN — skipping config preservation (clean flash)" fi # persist serial number from CBFS if cbfs.sh -r serial_number > /tmp/serial 2>/dev/null; then @@ -46,8 +56,10 @@ flash_rom() { fi WARN "Do not power off computer. Updating firmware, this will take a few minutes" + STATUS "Flashing ROM to chip" $CONFIG_FLASH_OPTIONS -w /tmp/${CONFIG_BOARD}.rom 2>&1 \ || recovery "$ROM: Flash failed" + STATUS_OK "ROM flashed successfully" fi } diff --git a/initrd/bin/kexec-select-boot.sh b/initrd/bin/kexec-select-boot.sh index dbf5014ce..09111a974 100755 --- a/initrd/bin/kexec-select-boot.sh +++ b/initrd/bin/kexec-select-boot.sh @@ -69,6 +69,24 @@ if [ -z "$_HEADS_TEST" ]; then paramsdir="${paramsdir%%/}" fi +PRIMHASH_FILE="$paramsdir/kexec_primhdl_hash.txt" +if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then + if [ -s "$PRIMHASH_FILE" ]; then + sha256sum -c "$PRIMHASH_FILE" >/dev/null 2>&1 || + { + WARN "Hash of TPM2 primary key handle mismatch - if you have not intentionally regenerated the TPM2 primary key, your system may have been compromised" + DEBUG "Hash of TPM2 primary key handle mismatched for $PRIMHASH_FILE" + DEBUG "Contents of $PRIMHASH_FILE:" + DEBUG "$(cat $PRIMHASH_FILE)" + DIE "Hash of TPM2 primary key handle mismatch ($PRIMHASH_FILE). If you did not intentionally regenerate the TPM2 primary key, this may indicate compromise." + } + else + WARN "Hash of TPM2 primary key handle does not exist - rebuild it by setting a default OS to boot: Options -> Boot Options -> Show OS Boot Menu -> pick OS -> Make default" + default_failed="y" + DEBUG "Hash of TPM2 primary key handle does not exist under $PRIMHASH_FILE" + fi +fi + verify_rollback_counter() { TRACE_FUNC TPM_COUNTER=$(grep counter $TMP_ROLLBACK_FILE | cut -d- -f2) @@ -156,9 +174,9 @@ get_menu_option() { fi if [ -n "$add" ]; then - local menu_prompt="Choose the boot option [1-$n, a to abort, b to select different ISO]:" + local menu_prompt="Choose the boot option [1-$n, Esc to abort, b to select different ISO]:" else - local menu_prompt="Choose the boot option [1-$n, a to abort]:" + local menu_prompt="Choose the boot option [1-$n, Esc to abort]:" fi whiptail_type $BG_COLOR_MAIN_MENU --title "Select your boot option" \ --menu "$menu_prompt" 0 80 8 \ @@ -216,19 +234,19 @@ confirm_menu_option() { # so users can change their selection without restarting the boot flow. # The full cmdline combines the entry's parsed params with the global ADD # params (injected by kexec-iso-init.sh for ISO boot). - if [ "$gui_menu" = "y" ]; then - default_text="Make default" - [[ "$CONFIG_TPM_NO_LUKS_DISK_UNLOCK" = "y" ]] && default_text="${default_text} and boot" - # Build final cmdline preview using shared function so it - # exactly matches what kexec-boot.sh will execute. - local folded_cmdline - folded_cmdline=$(_build_final_cmdline "$params" "$add" "$CONFIG_BOOT_KERNEL_REMOVE" "$CONFIG_BOOT_KERNEL_ADD") - folded_cmdline=$(echo "$folded_cmdline" | fold -s -w 78) - whiptail_warning --title "Confirm boot details" \ - --menu "$name\n\nKernel: $kernel\nInitramfs: ${initrd:--}\nOriginal kernel cmdline: ${params:--}\n${CONFIG_BOOT_KERNEL_ADD:+Board adds: $CONFIG_BOOT_KERNEL_ADD\n}${CONFIG_BOOT_KERNEL_REMOVE:+Board removes: $CONFIG_BOOT_KERNEL_REMOVE\n}${add:+ISO params: $add\n}\nFinal kernel cmdline:\n$folded_cmdline\n" 0 80 8 \ - -- 'y' "Boot" 'd' "${default_text}" 'b' "Back to menu" \ - 2>/tmp/whiptail && option_confirm=$(cat /tmp/whiptail) || option_confirm="b" - else + + # TODO : simplify to be able to use whiptail; too big for QubesOS + # No GUI for now, sorry. + # if [ "$gui_menu" = "y" ]; then + # default_text="Make default" + # [[ "$CONFIG_TPM_NO_LUKS_DISK_UNLOCK" = "y" ]] && default_text="${default_text} and boot" + # # Build final cmdline preview using shared function so it + # # exactly matches what kexec-boot.sh will execute. + # whiptail_warning --title "Confirm boot details" \ + # --menu "$name\n\nKernel: $kernel\nInitramfs: ${initrd:--}\nOriginal kernel cmdline: ${params:--}\n${CONFIG_BOOT_KERNEL_ADD:+Board adds: $CONFIG_BOOT_KERNEL_ADD\n}${CONFIG_BOOT_KERNEL_REMOVE:+Board removes: $CONFIG_BOOT_KERNEL_REMOVE\n}${add:+ISO params: $add\n}\nFinal kernel cmdline:\n$(_build_final_cmdline "$params" "$add" "$CONFIG_BOOT_KERNEL_REMOVE" "$CONFIG_BOOT_KERNEL_ADD")\n" 0 80 8 \ + # -- 'y' "Boot" 'd' "${default_text}" 'b' "Back to menu" \ + # 2>/tmp/whiptail && option_confirm=$(cat /tmp/whiptail) || option_confirm="b" + #else STATUS " Confirm boot details for $name:" STATUS " Kernel: $kernel" STATUS " Initramfs: ${initrd:--}" @@ -243,7 +261,7 @@ confirm_menu_option() { INPUT "Boot (Y), make default (d), back to menu (b) [Y/d/b]:" -n 1 option_confirm [ -z "$option_confirm" ] && option_confirm="y" return 0 - fi + #fi } parse_option() { diff --git a/initrd/bin/seal-hotpkey.sh b/initrd/bin/seal-hotpkey.sh index 85b3c3b4c..61111709a 100755 --- a/initrd/bin/seal-hotpkey.sh +++ b/initrd/bin/seal-hotpkey.sh @@ -140,22 +140,22 @@ show_pin_retries() { 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 -# initial setup. But don't do it forever to encourage changing the PIN and -# so PIN attempts are not consumed by the default attempt. +# Try the factory default admin PIN only when the GPG key still has the +# OEM default name ("OEM Key"), meaning the user hasn't customized the +# dongle yet. The GPG name is readable without consuming PIN attempts, +# unlike probing via hotp_initialize which burns a retry. +# Always require at least 3 PIN attempts remaining as a safety floor. admin_pin="12345678" month_secs="$((30 * 24 * 60 * 60))" +gpg_user_name="$(gpg --list-keys --with-colons 2>/dev/null | grep -m 1 '^uid:' | cut -d: -f10)" admin_pin_status=1 -if [ "$((now_date - gpg_key_create_time))" -gt "$month_secs" ]; then - # Remind what the default PIN was in case it still hasn't been changed - DEBUG "Not trying default PIN ($admin_pin)" -# Never consume an attempt if there are less than 3 attempts left, otherwise -# attempting the default PIN could cause an unexpected lockout before getting a -# chance to enter the correct PIN +if [ "$gpg_user_name" != "OEM Key" ] || [ "$((now_date - gpg_key_create_time))" -gt "$month_secs" ]; then + DEBUG "Not trying default PIN ($admin_pin): key_age=$(($((now_date - gpg_key_create_time)) / 86400))d, name='$gpg_user_name'" elif [ "$admin_pin_retries" -lt 3 ]; then DEBUG "Not trying default PIN ($admin_pin): only $admin_pin_retries attempt(s) left" else - STATUS "Trying ${prompt_message} PIN to seal HOTP secret on $DONGLE_BRAND" + STATUS "Trying factory default PIN ($admin_pin) to seal HOTP secret on $DONGLE_BRAND" + DEBUG "Attempting default PIN: key_age=$(($((now_date - gpg_key_create_time)) / 86400))d, GPG name='$gpg_user_name'" # 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" diff --git a/initrd/etc/functions.sh b/initrd/etc/functions.sh index ae6de9b7d..f0ecf0a90 100644 --- a/initrd/etc/functions.sh +++ b/initrd/etc/functions.sh @@ -478,19 +478,43 @@ ec_version() { preserve_rom() { TRACE_FUNC + # Preserve CBFS files matching 'heads/' from the currently running ROM + # into the new ROM being built for flashing. These files contain runtime + # configuration (GPG keyring, TOTP/HOTP secrets, LUKS key slots, etc.) + # that would otherwise be lost on each firmware update. + # + # Called from: flash.sh (when CLEAN=0, i.e. non-factory-flash). + # Controlled by: CLEAN flag (set to 1 for factory/OEM flashes to skip). + # No CONFIG_* setting directly controls this — flash.sh sets CLEAN + # based on the --clean / -c flag passed by the user or calling script. + # Files preserved: all CBFS type-50 entries under the 'heads/' prefix. new_rom="$1" old_files=$(cbfs -t 50 -l 2>/dev/null | grep "^heads/") + old_file_count=$(echo "$old_files" | wc -w) + + if [ "$old_file_count" -eq 0 ]; then + DEBUG "preserve_rom: no 'heads/' CBFS files to preserve in current ROM" + STATUS_OK "No configuration overrides or key material found in current firmware" + return 0 + fi + + STATUS "Preserving configuration overrides and key material from current firmware: $(echo $old_files)" + DEBUG "preserve_rom: scanning $new_rom for existing heads/* entries to skip" for old_file in $(echo $old_files); do - new_file=$(cbfs.sh -o $1 -l | grep -x $old_file) + new_file=$(cbfs.sh -o $1 -l | grep -Fx "$old_file") if [ -z "$new_file" ]; then - DEBUG "Adding $old_file to $1" + DEBUG "preserve_rom: $old_file not found in new ROM — copying from current CBFS" cbfs -t 50 -r $old_file >/tmp/rom.$$ || - DIE "Failed to read cbfs file from ROM" + DIE "preserve_rom: failed to read $old_file from current CBFS" cbfs.sh -o $1 -a $old_file -f /tmp/rom.$$ || - DIE "Failed to write cbfs file to new ROM file" + DIE "preserve_rom: failed to write $old_file to $1" + else + DEBUG "preserve_rom: $old_file already present in new ROM — skipped" fi done + rm -f /tmp/rom.$$ + STATUS_OK "Configuration overrides and key material preserved in new firmware" } # Color-code a PIN/security-token retry counter for the console. @@ -2229,15 +2253,28 @@ check_config() { # output matches exactly what was produced during signing, where the same # relative names were used. Absolute paths would differ between the # signing staging dir and $paramsdir, causing a spurious mismatch. - STATUS "Verifying GPG signature on kexec boot params" + STATUS "Verifying GPG signature on boot hashes" DEBUG "check_config: running (cd $paramsdir && sha256sum ${param_files[*]}) | gpgv.sh $paramsdir/kexec.sig" if ! (cd "$paramsdir" && sha256sum "${param_files[@]}") | gpgv.sh "$paramsdir/kexec.sig" - 2> >(SINK_LOG "gpgv kexec.sig"); then - DIE 'Invalid signature on kexec boot params' + DIE 'Invalid signature on boot hashes' fi + STATUS_OK "Boot hashes signature verified" + # Create a marker that verify_global_hashes (gui_functions.sh) + # reads to confirm GPG was verified, so it knows to include + # "against signed boot hashes" in its STATUS message. + # + # Lifecycle: + # 1. check_config starts with rm -rf /tmp/kexec/* — any + # stale .gpg_verified from a previous run is removed. + # 2. After this line: .gpg_verified exists only when GPG + # just passed (the path is not force). + # 3. Next check_config call: rm -rf /tmp/kexec/* removes + # it again. Or verify_global_hashes reads it before + # the next check_config runs. + touch /tmp/kexec/.gpg_verified fi - STATUS_OK "GPG signature on kexec boot params verified" DEBUG "check_config: copying kexec*.txt from $paramsdir to /tmp/kexec" cp "$paramsdir"/kexec*.txt /tmp/kexec || DIE "Failed to copy kexec boot params to tmp" @@ -3547,15 +3584,18 @@ _build_final_cmdline() { # Clean ADD: strip GRUB --- separator _clean_add=$(echo "$_param_add" | sed 's/ --- / /g;s/^--- //g;s/ ---$//g' | xargs) - # Apply REMOVE to both ADD and ISO params + # Apply REMOVE to ADD, ISO params, and Board ADD for _remove_word in $_param_remove; do _clean_add=" $_clean_add " _clean_add="${_clean_add// $_remove_word / }" _iso_params=" $_iso_params " _iso_params="${_iso_params// $_remove_word / }" + _board_add=" $_board_add " + _board_add="${_board_add// $_remove_word / }" done _clean_add=$(echo "${_clean_add# }" | xargs) _iso_params=$(echo "${_iso_params# }" | xargs) + _board_add=$(echo "${_board_add# }" | xargs) DEBUG "_build_final_cmdline: after remove on ADD='$_clean_add'" DEBUG "_build_final_cmdline: after remove on iso='$_iso_params'" @@ -3586,8 +3626,15 @@ _build_final_cmdline() { fi done - # Append Board ADD last (always wins -- never touched by enforce) - _combined=$(echo "$_combined $_board_add" | xargs) + # Append Board ADD last (always wins -- never touched by enforce). + # Only append words not already present in _combined to avoid duplicates. + for _add_word in $_board_add; do + case " $_combined " in + *" $_add_word "*) ;; + *) _combined="$_combined $_add_word" ;; + esac + done + _combined=$(echo "$_combined" | xargs) DEBUG "_build_final_cmdline: final='$_combined'" echo "$_combined" } diff --git a/initrd/etc/gui_functions.sh b/initrd/etc/gui_functions.sh index bf8b1de93..cf2ab6d42 100755 --- a/initrd/etc/gui_functions.sh +++ b/initrd/etc/gui_functions.sh @@ -37,7 +37,7 @@ mount_usb() { # Rebuild "$@" into global _WHIPTAIL_ARGS, wrapping the body text argument # (the one immediately following --msgbox, --yesno, --menu, --inputbox, etc.) -# through printf '%b' | fold -s -w 76 so \n escapes are expanded and long +# through printf '%b' | fold -s -w 75 so \n escapes are expanded and long # lines fit inside an 80-column dialog. All other arguments are passed # through unchanged. Callers must not be called recursively. _whiptail_preprocess_args() { @@ -45,15 +45,11 @@ _whiptail_preprocess_args() { local _wrap_next=0 _arg for _arg in "$@"; do if [ "$_wrap_next" = 1 ]; then - # fold -s breaks at spaces (preserves word boundaries). - # For paths/tokens with no spaces, fall back to - # character-level fold so whiptail can display them. - local _folded - _folded=$(printf '%b' "$_arg" | fold -s -w 76) - if echo "$_folded" | grep -q '.\{76\}'; then - _folded=$(printf '%b' "$_arg" | fold -w 76) - fi - _WHIPTAIL_ARGS+=("$_folded") + # fold -s breaks at spaces, preserving word boundaries. + # BusyBox fold -s also handles unbreakable tokens by + # falling back to character-level fold at the width + # limit, so no separate fallback is needed. + _WHIPTAIL_ARGS+=("$(printf '%b' "$_arg" | fold -s -w 75)") _wrap_next=0 else _WHIPTAIL_ARGS+=("$_arg") @@ -198,7 +194,7 @@ file_selector() { option_index="" while [ -z "$option_index" ]; do whiptail --title "${MENU_TITLE}" \ - --menu "${MENU_MSG} [1-$n, a to abort]:" 20 120 8 \ + --menu "${MENU_MSG}:" 20 120 8 \ -- "${CHOICE_ARGS[@]}" \ 2>/tmp/whiptail || DIE "Aborting" @@ -836,17 +832,78 @@ get_inverted_config_display_action() { # (gui_functions.sh), BG_COLOR_MAIN_MENU (exported from gui-init.sh). verify_global_hashes() { TRACE_FUNC - # Check the hashes of all the files, ignoring signatures for now - check_config /boot force + # + # Two call contexts, with different check_config semantics: + # + # Context A — gui-init.sh (attempt_default_boot / select_os_boot_option) + # /tmp/kexec/ is empty. We call check_config /boot force here to + # populate it. Because the caller is the GUI menu (not a verified + # boot path), we always pass "force" which copies the kexec files + # from /boot without verifying the GPG detached signature on them. + # The hash check STATUS will say + # "Verifying boot file checksums" (no "against signed boot hashes"). + # + # Context B — kexec-select-boot.sh's main loop + # The main loop already called check_config $paramsdir (which + # verifies the GPG signature on the kexec*.txt files) before + # calling this function. /tmp/kexec/ is already populated. + # Skipping check_config here avoids the destructive rm -rf + # /tmp/kexec/* + re-copy (which, if /boot/kexec.sig is absent, + # would leave /tmp/kexec/ empty and break the caller). + # + # Whether GPG was actually verified is determined by the + # presence of /tmp/kexec/.gpg_verified — a marker file that + # check_config creates after a successful signature verification + # (and its own rm -rf /tmp/kexec/* step cleans up on the next + # call). When present, the STATUS says + # "Verifying boot file checksums against signed boot hashes". + # + # Both files (kexec_hashes.txt and kexec_tree.txt) are required + # before skipping check_config. If only one is present (e.g. a + # partial copy from a failed run), check_config will repopulate. + # + if [ ! -r /tmp/kexec/kexec_hashes.txt -o ! -r /tmp/kexec/kexec_tree.txt ]; then + check_config /boot force + fi TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt" TMP_TREE_FILE="/tmp/kexec/kexec_tree.txt" TMP_PACKAGE_TRIGGER_PRE="/tmp/kexec/kexec_package_trigger_pre.txt" TMP_PACKAGE_TRIGGER_POST="/tmp/kexec/kexec_package_trigger_post.txt" + if [ -r /tmp/kexec/.gpg_verified ]; then + STATUS "Verifying boot file checksums against signed boot hashes" + else + STATUS "Verifying boot file checksums" + fi + DEBUG "verify_global_hashes: checking /boot files against $TMP_HASH_FILE" if verify_checksums /boot; then - DEBUG "verify_global_hashes: verify_checksums passed" + DEBUG "verify_global_hashes: /boot files match checksums in $TMP_HASH_FILE" valid_hash="y" valid_global_hash="y" + # If user enables it, check root hashes before boot as well + if [[ "$CONFIG_ROOT_CHECK_AT_BOOT" = "y" && "$force_menu" == "n" ]]; then + DEBUG "verify_global_hashes: checking root hashes" + if root-hashes-gui.sh -c; then + if [ -r /tmp/kexec/.gpg_verified ]; then + STATUS_OK "Boot file and root checksums verified against signed boot hashes" + else + STATUS_OK "Boot file and root checksums verified" + fi + else + # root-hashes-gui.sh handles the GUI error menu, just DIE here + if [ "$gui_menu" = "y" ]; then + whiptail_error --title 'ERROR: Root Hash Mismatch' \ + --msgbox "The root hash check failed!\nExiting to a recovery shell" 0 80 + fi + DIE "root hash mismatch, see /tmp/hash_output_mismatches for details" + fi + else + if [ -r /tmp/kexec/.gpg_verified ]; then + STATUS_OK "Boot file checksums verified against signed boot hashes" + else + STATUS_OK "Boot file checksums verified" + fi + fi return 0 elif [[ ! -f "$TMP_HASH_FILE" || ! -f "$TMP_TREE_FILE" ]]; then DEBUG "verify_global_hashes: missing hash or tree file"