Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiP: Improve checksum verification upon totp unseal errors #1508

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
89 changes: 11 additions & 78 deletions initrd/bin/gui-init
Original file line number Diff line number Diff line change
@@ -62,81 +62,6 @@ mount_boot()
done
}

verify_global_hashes()
{
TRACE "Under /bin/gui-init:verify_global_hashes"
# Check the hashes of all the files, ignoring signatures for now
check_config /boot force
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 verify_checksums /boot ; then
return 0
elif [[ ! -f "$TMP_HASH_FILE" || ! -f "$TMP_TREE_FILE" ]] ; then
if (whiptail $BG_COLOR_ERROR --title 'ERROR: Missing File!' \
--yesno "One of the files containing integrity information for /boot is missing!\n\nIf you are setting up heads for the first time or upgrading from an\nolder version, select Yes to create the missing files.\n\nOtherwise this could indicate a compromise and you should select No to\nreturn to the main menu.\n\nWould you like to create the missing files now?" 0 80) then
if update_checksums ; then
BG_COLOR_MAIN_MENU=""
return 0;
else
whiptail $BG_COLOR_ERROR --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
fi
fi
BG_COLOR_MAIN_MENU=$BG_COLOR_ERROR
return 1
else
CHANGED_FILES=$(grep -v 'OK$' /tmp/hash_output | cut -f1 -d ':' | tee -a /tmp/hash_output_mismatches)
CHANGED_FILES_COUNT=$(wc -l /tmp/hash_output_mismatches | cut -f1 -d ' ')

# if files changed before package manager started, show stern warning
if [ -f "$TMP_PACKAGE_TRIGGER_PRE" ]; then
PRE_CHANGED_FILES=$(grep '^CHANGED_FILES' $TMP_PACKAGE_TRIGGER_POST | cut -f 2 -d '=' | tr -d '"')
TEXT="The following files failed the verification process BEFORE package updates ran:\n${PRE_CHANGED_FILES}\n\nCompare against the files $CONFIG_BRAND_NAME has detected have changed:\n${CHANGED_FILES}\n\nThis could indicate a compromise!\n\nWould you like to update your checksums anyway?"

# if files changed after package manager started, probably caused by package manager
elif [ -f "$TMP_PACKAGE_TRIGGER_POST" ]; then
LAST_PACKAGE_LIST=$(grep -E "^(Install|Remove|Upgrade|Reinstall):" $TMP_PACKAGE_TRIGGER_POST)
UPDATE_INITRAMFS_PACKAGE=$(grep '^UPDATE_INITRAMFS_PACKAGE' $TMP_PACKAGE_TRIGGER_POST | cut -f 2 -d '=' | tr -d '"')

if [ "$UPDATE_INITRAMFS_PACKAGE" != "" ]; then
TEXT="The following files failed the verification process AFTER package updates ran:\n${CHANGED_FILES}\n\nThis is likely due to package triggers in$UPDATE_INITRAMFS_PACKAGE.\n\nYou will need to update your checksums for all files in /boot.\n\nWould you like to update your checksums now?"
else
TEXT="The following files failed the verification process AFTER package updates ran:\n${CHANGED_FILES}\n\nThis might be due to the following package updates:\n$LAST_PACKAGE_LIST.\n\nYou will need to update your checksums for all files in /boot.\n\nWould you like to update your checksums now?"
fi

else
if [ $CHANGED_FILES_COUNT -gt 10 ]; then
# drop to console to show full file list
whiptail $ERROR_BG_COLOR --title 'ERROR: Boot Hash Mismatch' \
--msgbox "${CHANGED_FILES_COUNT} files failed the verification process!\\n\nThis could indicate a compromise!\n\nHit OK to review the list of files.\n\nType \"q\" to exit the list and return." 0 80

echo "Type \"q\" to exit the list and return." >> /tmp/hash_output_mismatches
less /tmp/hash_output_mismatches
#move outdated hash mismatch list
mv /tmp/hash_output_mismatches /tmp/hash_output_mismatch_old
TEXT="Would you like to update your checksums now?"
else
TEXT="The following files failed the verification process:\n\n${CHANGED_FILES}\n\nThis could indicate a compromise!\n\nWould you like to update your checksums now?"
fi
fi

if (whiptail $BG_COLOR_ERROR --title 'ERROR: Boot Hash Mismatch' --yesno "$TEXT" 0 80) then
if update_checksums ; then
BG_COLOR_MAIN_MENU=""
return 0;
else
whiptail $BG_COLOR_ERROR --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
fi
fi
BG_COLOR_MAIN_MENU=$BG_COLOR_ERROR
return 1
fi
}

prompt_update_checksums()
{
TRACE "Under /bin/gui-init:prompt_update_checksums"
@@ -210,6 +135,10 @@ update_totp()
DEBUG "Show PCRs"
DEBUG "$(pcrs)"

#In all cases where TPM is involved in TOTP/HOTP secret unsealing errors
# Show global integrity report first to show also /boot integrity reports
report_integrity_measurements

whiptail $BG_COLOR_ERROR --title "ERROR: TOTP Generation Failed!" \
--menu " ERROR: $CONFIG_BRAND_NAME couldn't generate the TOTP code.\n
If you have just completed a Factory Reset, or just reflashed
@@ -225,7 +154,7 @@ update_totp()
2>/tmp/whiptail || recovery "GUI menu failed"

option=$(cat /tmp/whiptail)
case "$option" in
case "$option" in
g )
if (whiptail $BG_COLOR_WARNING --title 'Generate new TOTP/HOTP secret' \
--yesno "This will erase your old secret and replace it with a new one!\n\nDo you want to proceed?" 0 80) then
@@ -431,6 +360,7 @@ show_options_menu()
'b' ' Boot Options -->' \
't' ' TPM/TOTP/HOTP Options -->' \
'u' ' Update checksums and sign all files in /boot' \
'v' ' Verify checksums and signed /boot files' \
'c' ' Change configuration settings -->' \
'f' ' Flash/Update the BIOS -->' \
'g' ' GPG Options -->' \
@@ -453,6 +383,9 @@ show_options_menu()
u )
prompt_update_checksums
;;
v )
verify_global_hashes update
;;
c )
config-gui.sh
;;
@@ -606,7 +539,7 @@ select_os_boot_option()
{
TRACE "Under /bin/gui-init:select_os_boot_option"
mount_boot
if verify_global_hashes ; then
if verify_global_hashes update ; then
kexec-select-boot -m -b /boot -c "grub.cfg" -g
fi
}
@@ -616,7 +549,7 @@ attempt_default_boot()
TRACE "Under /bin/gui-init:attempt_default_boot"
mount_boot

if ! verify_global_hashes; then
if ! verify_global_hashes update; then
return
fi
DEFAULT_FILE=`find /boot/kexec_default.*.txt 2>/dev/null | head -1`
4 changes: 2 additions & 2 deletions initrd/bin/kexec-seal-key
Original file line number Diff line number Diff line change
@@ -91,7 +91,7 @@ done

# Remove all the old keys from slot 1
for dev in $(cat "$KEY_DEVICES" | cut -d\ -f1); do
echo "++++++ $dev: Removing old key slot 1"
echo "++++++ $dev: Removing $dev LUKS' old key slot 1 with provided passphrase"
cryptsetup luksKillSlot \
--key-file "$RECOVERY_KEY" \
$dev 1 ||
@@ -102,7 +102,7 @@ for dev in $(cat "$KEY_DEVICES" | cut -d\ -f1); do
--key-file "$RECOVERY_KEY" \
--key-slot 1 \
$dev "$KEY_FILE" ||
die "$dev: Unable to add key to slot 1"
die "$dev: Unable to add key to $dev LUKS's slot 1 with provided passphrase"
done

# Now that we have setup the new keys, measure the PCRs
35 changes: 0 additions & 35 deletions initrd/bin/kexec-select-boot
Original file line number Diff line number Diff line change
@@ -66,41 +66,6 @@ if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then
fi
fi

verify_global_hashes()
{
echo "+++ Checking verified boot hash file "
# Check the hashes of all the files
if verify_checksums "$bootdir" "$gui_menu" ; then
echo "+++ Verified boot hashes "
valid_hash='y'
valid_global_hash='y'
else
if [ "$gui_menu" = "y" ]; then
CHANGED_FILES=$(grep -v 'OK$' /tmp/hash_output | cut -f1 -d ':')
whiptail $BG_COLOR_ERROR --title 'ERROR: Boot Hash Mismatch' \
--msgbox "The following files failed the verification process:\n${CHANGED_FILES}\nExiting to a recovery shell" 0 80
fi
die "$TMP_HASH_FILE: boot hash mismatch"
fi
# If user enables it, check root hashes before boot as well
if [[ "$CONFIG_ROOT_CHECK_AT_BOOT" = "y" && "$force_menu" == "n" ]]; then
if root-hashes-gui.sh -c; then
echo "+++ Verified root hashes, continuing boot "
# if user re-signs, it wipes out saved options, so scan the boot directory and generate
if [ ! -r "$TMP_MENU_FILE" ]; then
scan_options
fi
else
# root-hashes-gui.sh handles the GUI error menu, just die here
if [ "$gui_menu" = "y" ]; then
whiptail $BG_COLOR_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
fi
}

verify_rollback_counter()
{
TPM_COUNTER=`grep counter $TMP_ROLLBACK_FILE | cut -d- -f2`
57 changes: 0 additions & 57 deletions initrd/bin/oem-factory-reset
Original file line number Diff line number Diff line change
@@ -321,63 +321,6 @@ set_default_boot_option()
|| whiptail_error_die "Failed to create hashes of boot files"
}

report_integrity_measurements()
{
#check for GPG key in keyring
GPG_KEY_COUNT=`gpg -k 2>/dev/null | wc -l`
if [ $GPG_KEY_COUNT -ne 0 ]; then
# Check and report TOTP
# update the TOTP code every thirty seconds
date=`date "+%Y-%m-%d %H:%M:%S %Z"`
seconds=`date "+%s"`
half=`expr \( $seconds % 60 \) / 30`
if [ "$CONFIG_TPM" != "y" ]; then
TOTP="NO TPM"
elif [ "$half" != "$last_half" ]; then
last_half=$half;
TOTP=`unseal-totp` > /dev/null 2>&1
fi

# Check and report on HOTP status
if [ -x /bin/hotp_verification ]; then
HOTP=`unseal-hotp` > /dev/null 2>&1
enable_usb
if ! hotp_verification info > /dev/null 2>&1 ; then
whiptail $CONFIG_WARNING_BG_COLOR --title 'WARNING: Please insert your HOTP enabled USB Security dongle' --msgbox "Your HOTP enabled USB Security dongle was not detected.\n\nPlease remove it and insert it again." 0 80
fi
# Don't output HOTP codes to screen, so as to make replay attacks harder
hotp_verification check $HOTP
case "$?" in
0 )
HOTP="Success"
;;
4 )
HOTP="Invalid code"
MAIN_MENU_BG_COLOR=$CONFIG_ERROR_BG_COLOR
;;
* )
HOTP="Error checking code, Insert USB Security dongle and retry"
MAIN_MENU_BG_COLOR=$CONFIG_WARNING_BG_COLOR
;;
esac
else
HOTP='N/A'
fi
# Check for detached signed digest and report on /boot integrity status
check_config /boot force
TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt"

if ( cd /boot && sha256sum -c "$TMP_HASH_FILE" > /tmp/hash_output ); then
HASH="OK"
else
HASH="ALTERED"
fi

#Show results
whiptail $MAIN_MENU_BG_COLOR --title "Measured Integrity Report" --msgbox "$date\nTOTP: $TOTP | HOTP: $HOTP\n/BOOT INTEGRITY: $HASH\n\nPress OK to continue or Ctrl+Alt+Delete to reboot" 0 80
fi
}

usb_security_token_capabilities_check()
{
TRACE "Under /bin/oem-factory-reset:usb_security_token_capabilities_check"
86 changes: 43 additions & 43 deletions initrd/etc/ash_functions
Original file line number Diff line number Diff line change
@@ -4,49 +4,51 @@
# busybox ash on legacy-flash boards, and with bash on all other boards.

die() {
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then
echo " !!! ERROR: $* !!!" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then
echo " !!! ERROR: $* !!!" | tee -a /tmp/debug.log /dev/kmsg >/dev/null
else
echo >&2 "!!! ERROR: $* !!!";
echo >&2 "!!! ERROR: $* !!!"
fi
sleep 2;
exit 1;
sleep 2
exit 1
}

warn() {
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then
echo " *** WARNING: $* ***" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then
echo " *** WARNING: $* ***" | tee -a /tmp/debug.log /dev/kmsg >/dev/null
else
echo >&2 " *** WARNING: $* ***";
echo >&2 " *** WARNING: $* ***"
fi
sleep 1;
sleep 1
}

DEBUG() {
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then
echo "DEBUG: $*" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then
echo "DEBUG: $*" | while read line; do
echo "$line" | tee -a /tmp/debug.log /dev/kmsg >/dev/null
done
fi
}

TRACE() {
if [ "$CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT" = "y" ];then
echo "TRACE: $*" | tee -a /tmp/debug.log /dev/kmsg > /dev/null;
if [ "$CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT" = "y" ]; then
echo "TRACE: $*" | tee -a /tmp/debug.log /dev/kmsg >/dev/null
fi
}

preserve_rom() {
TRACE "Under /etc/ash_functions:preserve_rom"
new_rom="$1"
old_files=`cbfs -t 50 -l 2>/dev/null | grep "^heads/"`
old_files=$(cbfs -t 50 -l 2>/dev/null | grep "^heads/")

for old_file in `echo $old_files`; do
new_file=`cbfs.sh -o $1 -l | grep -x $old_file`
for old_file in $(echo $old_files); do
new_file=$(cbfs.sh -o $1 -l | grep -x $old_file)
if [ -z "$new_file" ]; then
echo "+++ Adding $old_file to $1"
cbfs -t 50 -r $old_file >/tmp/rom.$$ \
|| die "Failed to read cbfs file from ROM"
cbfs.sh -o $1 -a $old_file -f /tmp/rom.$$ \
|| die "Failed to write cbfs file to new ROM file"
cbfs -t 50 -r $old_file >/tmp/rom.$$ ||
die "Failed to read cbfs file from ROM"
cbfs.sh -o $1 -a $old_file -f /tmp/rom.$$ ||
die "Failed to write cbfs file to new ROM file"
fi
done
}
@@ -59,7 +61,7 @@ recovery() {
# but recreate the directory so that new tools can use it.

#safe to always be true. Otherwise "set -e" would make it exit here
shred -n 10 -z -u /tmp/secret/* 2> /dev/null || true
shred -n 10 -z -u /tmp/secret/* 2>/dev/null || true
rm -rf /tmp/secret
mkdir -p /tmp/secret

@@ -76,8 +78,7 @@ recovery() {
sleep 5
/bin/reboot
fi
while [ true ]
do
while [ true ]; do
echo >&2 "!!!!! Starting recovery shell"
sleep 1

@@ -97,49 +98,48 @@ pause_recovery() {

combine_configs() {
TRACE "Under /etc/ash_functions:combine_configs"
cat /etc/config* > /tmp/config
cat /etc/config* >/tmp/config
}

enable_usb()
{
enable_usb() {
TRACE "Under /etc/ash_functions:enable_usb"
#insmod ehci_hcd prior of uhdc_hcd and ohci_hcd to suppress dmesg warning
#insmod ehci_hcd prior of uhdc_hcd and ohci_hcd to suppress dmesg warning
if ! lsmod | grep -q ehci_hcd; then
insmod /lib/modules/ehci-hcd.ko \
|| die "ehci_hcd: module load failed"
insmod /lib/modules/ehci-hcd.ko ||
die "ehci_hcd: module load failed"
fi
if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then
if ! lsmod | grep -q uhci_hcd; then
insmod /lib/modules/uhci-hcd.ko \
|| die "uhci_hcd: module load failed"
insmod /lib/modules/uhci-hcd.ko ||
die "uhci_hcd: module load failed"
fi
if ! lsmod | grep -q ohci_hcd; then
insmod /lib/modules/ohci-hcd.ko \
|| die "ohci_hcd: module load failed"
insmod /lib/modules/ohci-hcd.ko ||
die "ohci_hcd: module load failed"
fi
if ! lsmod | grep -q ohci_pci; then
insmod /lib/modules/ohci-pci.ko \
|| die "ohci_pci: module load failed"
insmod /lib/modules/ohci-pci.ko ||
die "ohci_pci: module load failed"
fi
fi
if ! lsmod | grep -q ehci_pci; then
insmod /lib/modules/ehci-pci.ko \
|| die "ehci_pci: module load failed"
insmod /lib/modules/ehci-pci.ko ||
die "ehci_pci: module load failed"
fi
if ! lsmod | grep -q xhci_hcd; then
insmod /lib/modules/xhci-hcd.ko \
|| die "xhci_hcd: module load failed"
insmod /lib/modules/xhci-hcd.ko ||
die "xhci_hcd: module load failed"
fi
if ! lsmod | grep -q xhci_pci; then
insmod /lib/modules/xhci-pci.ko \
|| die "xhci_pci: module load failed"
insmod /lib/modules/xhci-pci.ko ||
die "xhci_pci: module load failed"
sleep 2
fi

if [ "$CONFIG_USB_KEYBOARD" = y ]; then
if ! lsmod | grep -q usbhid; then
insmod /lib/modules/usbhid.ko \
|| die "usbhid: module load failed"
insmod /lib/modules/usbhid.ko ||
die "usbhid: module load failed"
fi
fi
}
178 changes: 175 additions & 3 deletions initrd/etc/functions
Original file line number Diff line number Diff line change
@@ -35,9 +35,9 @@ DO_WITH_DEBUG() {

pcrs() {
if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then
tpm2 pcrread sha256
tpm2 pcrread sha256 || die "tpm2 pcrread failed"
elif [ "$CONFIG_TPM" = "y" ]; then
head -8 /sys/class/tpm/tpm0/pcrs
cat /sys/class/tpm/tpm0/pcrs
fi
}

@@ -322,7 +322,8 @@ check_config() {

if [ "$2" != "force" ]; then
if ! sha256sum $(find $1/kexec*.txt) | gpgv $1/kexec.sig -; then
die 'Invalid signature on kexec boot params'
warn "Invalid signature on $1/boot/kexec*.txt files"
die "Please verify hashes/signatures or use call check_config $1 force to check hashes without signature"
fi
fi

@@ -440,6 +441,177 @@ update_checksums() {
return $rv
}

# Verify the checksums of all the files in /boot, show discrepancies, and
# prompt the user to update the checksums if they are incorrect.
verify_global_hashes() {
TRACE "Under /etc/functions:verify_global_hashes"

#Parameters: optional update flag
if [ "$1" = "update" ]; then
DEBUG "Caller requested update of checksums"
UPDATE="y"
UPDATE_TEXT="\n\nWould you like to update your checksums now?"
else
DEBUG "Caller did not request update of checksums"
UPDATE="n"
UPDATE_TEXT=""
fi

# Check the hashes of all the files, ignoring signatures for now
check_config /boot force
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 verify_checksums /boot; then
return 0
elif [[ ! -f "$TMP_HASH_FILE" || ! -f "$TMP_TREE_FILE" ]]; then
# No hashes found, prompt to create them
if (whiptail $BG_COLOR_ERROR --title 'ERROR: Missing File!' \
--yesno "One of the files containing integrity information for /boot is missing!\n\nIf you are setting up heads for the first time or upgrading from an\nolder version, select Yes to create the missing files.\n\nOtherwise this could indicate a compromise and you should select No to\nreturn to the main menu.\n\nWould you like to create the missing files now?" 0 80); then
if update_checksums; then
BG_COLOR_MAIN_MENU=""
return 0
else
whiptail $BG_COLOR_ERROR --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
fi
fi
BG_COLOR_MAIN_MENU=$BG_COLOR_ERROR
return 1
else
# Hashes found
CHANGED_FILES=$(grep -v 'OK$' /tmp/hash_output | cut -f1 -d ':' | tee -a /tmp/hash_output_mismatches)
CHANGED_FILES_COUNT=$(wc -l /tmp/hash_output_mismatches | cut -f1 -d ' ')

# if files changed before package manager started, show stern warning
if [ -f "$TMP_PACKAGE_TRIGGER_PRE" ]; then
PRE_CHANGED_FILES=$(grep '^CHANGED_FILES' $TMP_PACKAGE_TRIGGER_POST | cut -f 2 -d '=' | tr -d '"')
TEXT="The following files failed the verification process BEFORE package updates ran:\n${PRE_CHANGED_FILES}\n\nCompare against the files $CONFIG_BRAND_NAME has detected have changed:\n${CHANGED_FILES}\n\nThis could indicate a compromise!"
TEXT+="$UPDATE_TEXT"
# if files changed after package manager started, probably caused by package manager
elif [ -f "$TMP_PACKAGE_TRIGGER_POST" ]; then
LAST_PACKAGE_LIST=$(grep -E "^(Install|Remove|Upgrade|Reinstall):" $TMP_PACKAGE_TRIGGER_POST)
UPDATE_INITRAMFS_PACKAGE=$(grep '^UPDATE_INITRAMFS_PACKAGE' $TMP_PACKAGE_TRIGGER_POST | cut -f 2 -d '=' | tr -d '"')

if [ "$UPDATE_INITRAMFS_PACKAGE" != "" ]; then
TEXT="The following files failed the verification process AFTER package updates ran:\n${CHANGED_FILES}\n\nThis is likely due to package triggers in$UPDATE_INITRAMFS_PACKAGE.\n\nYou will need to update your checksums for all files in /boot."
TEXT+="$UPDATE_TEXT"
else
TEXT="The following files failed the verification process AFTER package updates ran:\n${CHANGED_FILES}\n\nThis might be due to the following package updates:\n$LAST_PACKAGE_LIST.\n\nYou will need to update your checksums for all files in /boot."
TEXT+="$UPDATE_TEXT"
fi
else
Comment on lines +489 to +505
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic isn't new (moved from gui-init), but while we're here....

This looks like logic to allow an OS package manager to place files on /boot indicating:

  • what files failed validation before updating packages
  • what files failed validation after updating packages
  • what packages were updated

This then controls the prompts shown by Heads if files do not validate. But kexec_package_trigger_{pre/post}.txt aren't themselves authenticated in any way (which would have to somehow "authenticate" that your package manager really made these changes). So as far as I can tell, somebody tampering /boot could just place these files here with whatever content they want and thereby control what Heads will say when it observes the tampering.

Does this package manager support exist in any OS?

I noted some additional details from my read-through here, but really the key question to me is - why should we trust these kexec_package_trigger_* files?

#Standard case for Heads without package manager involvement in /boot
if [ $CHANGED_FILES_COUNT -gt 10 ]; then
# drop to console to show full file list
whiptail $ERROR_BG_COLOR --title 'ERROR: Boot Hash Mismatch' \
--msgbox "${CHANGED_FILES_COUNT} files failed the verification process!\\n\nThis could indicate a compromise!\n\nHit OK to review the list of files.\n\nType \"q\" to exit the list and return." 0 80

echo "Type \"q\" to exit the list and return." >>/tmp/hash_output_mismatches
less /tmp/hash_output_mismatches
#move outdated hash mismatch list
mv /tmp/hash_output_mismatches /tmp/hash_output_mismatch_old
TEXT=""
TEXT+="$UPDATE_TEXT"
else
# show list of files direcly
TEXT="The following files failed the verification process:\n\n${CHANGED_FILES}\n\nThis could indicate a compromise!"
TEXT+="$UPDATE_TEXT"
fi
fi

# If update of checksum is not optionally selected, show stern warning
if [ "$UPDATE" == "n" ]; then
DEBUG "Checksums update not selected by caller, showing stern warning"
whiptail $BG_COLOR_ERROR --title 'ERROR: Boot Hash Mismatch' \
--msgbox "$TEXT" 0 80
BG_COLOR_MAIN_MENU=$BG_COLOR_ERROR
return 1
else
DEBUG "Checksums update selected by caller, showing stern warning with option to update"
if (whiptail $BG_COLOR_ERROR --title 'ERROR: Boot Hash Mismatch' --yesno "$TEXT" 0 80); then
if update_checksums; then
BG_COLOR_MAIN_MENU=""
return 0
else
whiptail $BG_COLOR_ERROR --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
BG_COLOR_MAIN_MENU=$BG_COLOR_ERROR
return 1
fi
fi
fi
fi
}

report_integrity_measurements() {
#check for GPG key in keyring
GPG_KEY_COUNT=$(gpg -k 2>/dev/null | wc -l)
if [ $GPG_KEY_COUNT -ne 0 ]; then
# Check and report TOTP
# update the TOTP code every thirty seconds
date=$(date "+%Y-%m-%d %H:%M:%S %Z")
seconds=$(date "+%s")
half=$(expr \( $seconds % 60 \) / 30)
if [ "$CONFIG_TPM" != "y" ]; then
TOTP="NO TPM"
elif [ "$half" != "$last_half" ]; then
last_half=$half
TOTP=$(unseal-totp) >/dev/null 2>&1 || TOTP="TPM UNSEALING OF TOTP/HOTP SECRET FAILED"
fi

# Check and report on HOTP status
if [ -x /bin/hotp_verification ]; then
HOTP=$(unseal-hotp) >/dev/null 2>&1
enable_usb
while ! hotp_verification info >/dev/null 2>&1; do
whiptail $CONFIG_WARNING_BG_COLOR --title 'WARNING: Please insert your HOTP enabled USB Security dongle' --msgbox "Your HOTP enabled USB Security dongle was not detected.\n\nPlease remove it and insert it again." 0 80
done
# Don't output HOTP codes to screen, so as to make replay attacks harder
hotp_verification check $HOTP
case "$?" in
0)
HOTP="Success"
;;
4)
HOTP="Invalid code"
MAIN_MENU_BG_COLOR=$CONFIG_ERROR_BG_COLOR
;;
*)
HOTP="HOTP VERIFICATION FAILED. Returned error: $?"
MAIN_MENU_BG_COLOR=$CONFIG_WARNING_BG_COLOR
;;
esac
else
HOTP='N/A'
fi
# Check for detached signed digest and report on /boot integrity status
SIGNED="OK"
(check_config /boot) || SIGNED="ALTERED"

TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt"

if (cd /boot && sha256sum -c "$TMP_HASH_FILE" >/tmp/hash_output); then
HASH="OK"
else
HASH="ALTERED"
fi

#Show results

#If the system is in a bad state, show the user a warning
whiptail $MAIN_MENU_BG_COLOR --title "Measured Integrity Report" --msgbox "$date\n\nTOTP: $TOTP\nHOTP: $HOTP\nBOOT INTEGRITY SIGNATURE: $SIGNED\nBOOT INTEGRITY HASHES: $HASH\n\nPress OK to continue with detailed information in case of errors" 0 80

Comment on lines +605 to +606
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO:

  • All the CAPS introduced in the error text, as well as the much longer labels for integrity, make this difficult to read. There's no longer any separation between the "labels" and "content" for errors (screenshot below)
  • I think the separation between "signature" and "hashes" should be an implementation detail of Heads - that's how we authenticate /boot. The normal user flow should just say whether /boot integrity was authenticated or not, so it's easier to understand if you are not an expert in Heads. (I'd be OK with providing detailed information as an optional thing or on the console, etc. to cater to advanced users too, but putting it front-and-center IMO makes it a requirement to be an knowledgeable in Heads internals to use Heads.)

Re: the caps, this is what I was greeted with when firing this up in qemu:

Screenshot_20231012_171341

From a glance it is really hard to parse this and know what to look at. The text-only prompt is a bit limiting but I'd suggest:

  • Don't use all caps for the errors, this doesn't add anything and just makes it harder to read
  • Go back to just "INTEGRITY: " instead of the last two lines, which would make the labels all similar-length again and reduce a lot of verbosity
  • Take out the colon from the HOTP error so the colons form a bit of separation between the labels and contents

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and also, it did not show me any detailed information when I pressed Enter :-/

#Show the user the detailed information
if [ "$SIGNED" != "OK" ] || [ "$HASH" != "OK" ]; then
# Show the user the detailed information of /boot integrity status (without asking to update them)
verify_global_hashes
fi
fi
}

print_tree() {
TRACE "Under /etc/functions:print_tree"
find ./ ! -path './kexec*' -print0 | sort -z