diff --git a/initrd/bin/flash.sh b/initrd/bin/flash.sh index cd5b9a6e..c5389a11 100755 --- a/initrd/bin/flash.sh +++ b/initrd/bin/flash.sh @@ -1,14 +1,14 @@ -#!/bin/ash +#!/bin/bash # # NOTE: This script is used on legacy-flash boards and runs with busybox ash, # not bash set -e -o pipefail -. /etc/ash_functions +. /etc/functions . /tmp/config echo -TRACE "Under /bin/flash.sh" +TRACE_FUNC case "$CONFIG_FLASH_OPTIONS" in "" ) diff --git a/initrd/bin/lock_chip b/initrd/bin/lock_chip index 6085b84e..26c9c1c7 100755 --- a/initrd/bin/lock_chip +++ b/initrd/bin/lock_chip @@ -1,14 +1,13 @@ -#!/bin/sh +#!/bin/bash # For this to work: # - io386 module needs to be enabled in board config # - =Skylake: same as above and CONFIG_SOC_INTEL_COMMON_SPI_LOCKDOWN_SMM=y, CONFIG_SPI_FLASH_SMM=y and mode (eg: CONFIG_BOOTMEDIA_LOCK_WHOLE_RO=y) # - Heads is actually doing the CONFIG_INTEL_CHIPSET_LOCKDOWN equivalent here. -#include ash shell functions (TRACE requires it) -. /etc/ash_functions +. /etc/functions -TRACE "Under /bin/lock_chip" +TRACE_FUNC if [ "$CONFIG_FINALIZE_PLATFORM_LOCKING" = "y" ]; then APM_CNT=0xb2 FIN_CODE=0xcb diff --git a/initrd/bin/poweroff b/initrd/bin/poweroff index ef4bdf86..bbf0a749 100755 --- a/initrd/bin/poweroff +++ b/initrd/bin/poweroff @@ -1,7 +1,7 @@ -#!/bin/ash -. /etc/ash_functions +#!/bin/bash +. /etc/functions -TRACE "Under /bin/poweroff" +TRACE_FUNC # Shut down TPM if [ "$CONFIG_TPM" = "y" ]; then diff --git a/initrd/bin/reboot b/initrd/bin/reboot index 358931e9..ce7e6947 100755 --- a/initrd/bin/reboot +++ b/initrd/bin/reboot @@ -1,7 +1,7 @@ -#!/bin/ash -. /etc/ash_functions +#!/bin/bash +. /etc/functions -TRACE "Under /bin/reboot" +TRACE_FUNC if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then #Generalize user prompt to continue reboot or go to recovery shell diff --git a/initrd/bin/xx30-flash.init b/initrd/bin/xx30-flash.init deleted file mode 100755 index ca2fa8f6..00000000 --- a/initrd/bin/xx30-flash.init +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/ash -# Initialize the USB and network device drivers, -# invoke a recovery shell and prompt the user for how to proceed - -. /etc/ash_functions -. /tmp/config - -TRACE "Under /bin/xx30-flash.init" - -busybox insmod /lib/modules/ehci-hcd.ko -busybox insmod /lib/modules/ehci-pci.ko -busybox insmod /lib/modules/xhci-hcd.ko -busybox insmod /lib/modules/xhci-pci.ko -busybox insmod /lib/modules/e1000e.ko -busybox insmod /lib/modules/usb-storage.ko - -sleep 2 - -echo '***** Starting recovery shell' -echo '' -echo 'To install from flash drive:' -echo '' -echo ' mount -o ro /dev/sdb1 /media' -echo ' flash.sh /media/xx30-legacy.rom' -echo '' - -exec /bin/sh diff --git a/initrd/etc/ash_functions b/initrd/etc/ash_functions deleted file mode 100644 index e60c5846..00000000 --- a/initrd/etc/ash_functions +++ /dev/null @@ -1,385 +0,0 @@ -#!/bin/sh -# -# Core shell functions that do not require bash. These functions are used with -# busybox ash on legacy-flash boards, and with bash on all other boards. - -die() { - if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then - echo -e " !!! ERROR: $* !!!" | tee -a /tmp/debug.log /dev/kmsg > /dev/null; - else - echo -e >&2 "!!! ERROR: $* !!!"; - fi - sleep 2; - exit 1; -} - -warn() { - if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then - echo -e " *** WARNING: $* ***" | tee -a /tmp/debug.log /dev/kmsg > /dev/null; - else - echo -e >&2 " *** WARNING: $* ***"; - fi - sleep 1; -} - -DEBUG() { - if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then - # fold -s -w 960 will wrap lines at 960 characters on the last space before the limit - echo "DEBUG: $*" | fold -s -w 960 | 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; - fi -} - -# Write directly to the debug log (but not kmsg), never appears on console -LOG() { - # if not CONFIG_QUIET_MODE=y, output to console. If not, output to debug.log - if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then - DEBUG "$*" - elif [ "$CONFIG_QUIET_MODE" = "y" ]; then - # if in quiet mode, output solely to debug.log - echo "$*" >> /tmp/debug.log - else - # if not in quiet mode, output to console - echo "$*" - fi -} - -fw_version() { - local FW_VER=$(dmesg | grep 'DMI' | grep -o 'BIOS.*' | cut -f2- -d ' ') - # chop off date, since will always be epoch w/timeless builds - echo "${FW_VER::-10}" -} - -preserve_rom() { - TRACE "Under /etc/ash_functions:preserve_rom" - new_rom="$1" - 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` - 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" - fi - done -} - -confirm_gpg_card() { - - #TODO: ideally, we ask for confirmation only once per boot session - #TODO: even change logic here to try first and then ask user to confirm if not found - #TODO: or ask GPG user PIN once and cache it for the rest of the boot session for reusal - # This is getting in the way of unattended stuff and GPG prompts are confusing anyway, hide them from user. - - - TRACE "Under /etc/ash_functions:confirm_gpg_card" - #Skip prompts if we are currently using a known GPG key material Thumb drive backup and keys are unlocked pinentry - #TODO: probably export CONFIG_GPG_KEY_BACKUP_IN_USE but not under /etc/user.config? - #Toggle to come in next PR, but currently we don't have a way to toggle it back to n if config.user flashed back in rom - if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" && "$CONFIG_GPG_KEY_BACKUP_IN_USE" == "y" ]]; then - DEBUG "Using known GPG key material Thumb drive backup and keys are unlocked and useable through pinentry" - return - fi - - if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]; then - message="Please confirm that your GPG card is inserted(Y/n) or your GPG key material (b)backup thumbdrive is inserted [Y/n/b]: " - else - # Generic message if no known key material backup - message="Please confirm that your GPG card is inserted [Y/n]: " - fi - - read \ - -n 1 \ - -p "$message" \ - card_confirm - echo - - if [ "$card_confirm" != "y" \ - -a "$card_confirm" != "Y" \ - -a "$card_confirm" != "b" \ - -a -n "$card_confirm" ] \ - ; then - die "gpg card not confirmed" - fi - - # If user has known GPG key material Thumb drive backup and asked to use it - if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" && "$card_confirm" == "b" ]]; then - #Only mount and import GPG key material thumb drive backup once - if [ ! "$CONFIG_GPG_KEY_BACKUP_IN_USE" == "y" ]; then - CR_NONCE="/tmp/secret/cr_nonce" - CR_SIG="$CR_NONCE.sig" - - #Wipe any previous CR_NONCE and CR_SIG - shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true - - #Prompt user for configured GPG Admin PIN that will be passed along to mount-usb and to import gpg subkeys - echo - gpg_admin_pin="" - while [ -z "$gpg_admin_pin" ]; do - #TODO: change all passphrase prompts in codebase to include -r to prevent backslash escapes - read -r -s -p "Please enter GPG Admin PIN needed to use the GPG backup thumb drive: " gpg_admin_pin - echo - done - #prompt user to select the proper encrypted partition, which should the first one on next prompt - warn "Please select encrypted LUKS on GPG key material backup thumb drive (not public labeled one)" - mount-usb --pass "$gpg_admin_pin" || die "Unable to mount USB with provided GPG Admin PIN" - echo "++++ Testing detach-sign operation and verifiying against fused public key in ROM" - gpg --pinentry-mode=loopback --passphrase-file <(echo -n "${gpg_admin_pin}") --import /media/subkeys.sec >/dev/null 2>&1 || - die "Unable to import GPG private subkeys" - #Do a detach signature to ensure gpg material is usable and cache passphrase to sign /boot from caller functions - dd if=/dev/urandom of="$CR_NONCE" bs=20 count=1 >/dev/null 2>&1 || - die "Unable to create $CR_NONCE to be detach-signed with GPG private signing subkey" - gpg --pinentry-mode=loopback --passphrase-file <(echo -n "${gpg_admin_pin}") --detach-sign "$CR_NONCE" >/dev/null 2>&1 || - die "Unable to detach-sign $CR_NONCE with GPG private signing subkey using GPG Admin PIN" - #verify detached signature against public key in rom - gpg --verify "$CR_SIG" "$CR_NONCE" > /dev/null 2>&1 && \ - echo "++++ Local GPG keyring can be used to sign/encrypt/authenticate in this boot session ++++" || \ - die "Unable to verify $CR_SIG detached signature against public key in ROM" - #Wipe any previous CR_NONCE and CR_SIG - shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true - #TODO: maybe just an export instead of setting /etc/user.config otherwise could be flashed in weird corner case situation - set_user_config "CONFIG_GPG_KEY_BACKUP_IN_USE" "y" - umount /media || die "Unable to unmount USB" - return - fi - fi - - # setup the USB so we can reach the USB Security dongle's OpenPGP smartcard - enable_usb - - echo -e "\nVerifying presence of GPG card...\n" - # ensure we don't exit without retrying - errexit=$(set -o | grep errexit | awk '{print $2}') - set +e - gpg_output=$(gpg --card-status 2>&1) - if [ $? -ne 0 ]; then - # prompt for reinsertion and try a second time - read -n1 -r -p \ - "Can't access GPG key; remove and reinsert, then press Enter to retry. " \ - ignored - # restore prev errexit state - if [ "$errexit" = "on" ]; then - set -e - fi - # retry card status - gpg_output=$(gpg --card-status 2>&1) || - die "gpg card read failed" - fi - # restore prev errexit state - if [ "$errexit" = "on" ]; then - set -e - fi - - # Extract and display GPG PIN retry counters - # output excerpt: "PIN retry counter : 3 0 3" - pin_retry_counters=$(echo "$gpg_output" | grep 'PIN retry counter' | awk -F': ' '{print $2}') - user_pin_retries=$(echo "$pin_retry_counters" | awk '{print $1}') - admin_pin_retries=$(echo "$pin_retry_counters" | awk '{print $3}') - - echo "" - echo "GPG User PIN retry attempts left before becoming locked: $user_pin_retries" - echo "GPG Admin PIN retry attempts left before becoming locked: $admin_pin_retries" - echo "" - warn "Your GPG User PIN, followed by Enter key will be required for input at: 'Please unlock the card' next prompt" - echo "" -} - -gpg_auth() { - if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]]; then - TRACE "Under /etc/ash_functions:gpg_auth" - # If we have a GPG key backup, we can use it to authenticate even if the card is lost - echo >&2 "!!!!! Please authenticate with OpenPGP smartcard/backup media to prove you are the owner of this machine !!!!!" - - # Wipe any existing nonce and signature - shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" 2>/dev/null || true - - # In case of gpg_auth, we require confirmation of the card, so loop with confirm_gpg_card until we get it - false - while [ $? -ne 0 ]; do - # Call confirm_gpg_card in subshell to ensure GPG key material presence - ( confirm_gpg_card ) - done - - # Perform a signing-based challenge-response, - # to authencate that the card plugged in holding - # the key to sign the list of boot files. - - CR_NONCE="/tmp/secret/cr_nonce" - CR_SIG="$CR_NONCE.sig" - - # Generate a random nonce - dd \ - if=/dev/urandom \ - of="$CR_NONCE" \ - count=1 \ - bs=20 \ - 2>/dev/null \ - || die "Unable to generate 20 random bytes" - - # Sign the nonce - for tries in 1 2 3; do - if gpg --digest-algo SHA256 \ - --detach-sign \ - -o "$CR_SIG" \ - "$CR_NONCE" > /dev/null 2>&1 \ - && gpg --verify "$CR_SIG" "$CR_NONCE" > /dev/null 2>&1 \ - ; then - shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" 2>/dev/null || true - DEBUG "Under /etc/ash_functions:gpg_auth: success" - return 0 - else - shred -n 10 -z -u "$CR_SIG" 2>/dev/null || true - if [ "$tries" -lt 3 ]; then - echo >&2 "!!!!! GPG authentication failed, please try again !!!!!" - continue - else - die "GPG authentication failed, please reboot and try again" - fi - fi - done - return 1 - fi -} - -recovery() { - TRACE "Under /etc/ash_functions:recovery" - echo >&2 "!!!!! $*" - - # Remove any temporary secret files that might be hanging around - # 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 - rm -rf /tmp/secret - mkdir -p /tmp/secret - - # ensure /tmp/config exists for recovery scripts that depend on it - touch /tmp/config - . /tmp/config - - DEBUG "Board $CONFIG_BOARD - version $(fw_version)" - - if [ "$CONFIG_TPM" = "y" ]; then - LOG "TPM: Extending PCR[4] to prevent any further secret unsealing" - tpmr extend -ix 4 -ic recovery - fi - - if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then - echo >&2 "Restricted Boot enabled, recovery console disabled, rebooting in 5 seconds" - sleep 5 - /bin/reboot - fi - while [ true ] - do - #Going to recovery shell should be authenticated if supported - gpg_auth - - echo >&2 "!!!!! Starting recovery shell" - sleep 1 - - if [ -x /bin/setsid ]; then - /bin/setsid -c /bin/sh - else - /bin/sh - fi - done -} - -pause_recovery() { - TRACE "Under /etc/ash_functions:pause_recovery" - read -p $'!!! Hit enter to proceed to recovery shell !!!\n' - recovery $* -} - -combine_configs() { - TRACE "Under /etc/ash_functions:combine_configs" - cat /etc/config* > /tmp/config -} - -replace_config() { - TRACE "Under /etc/functions:replace_config" - CONFIG_FILE=$1 - CONFIG_OPTION=$2 - NEW_SETTING=$3 - - touch $CONFIG_FILE - # first pull out the existing option from the global config and place in a tmp file - awk "gsub(\"^export ${CONFIG_OPTION}=.*\",\"export ${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >${CONFIG_FILE}.tmp - awk "gsub(\"^${CONFIG_OPTION}=.*\",\"${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >>${CONFIG_FILE}.tmp - - # then copy any remaining settings from the existing config file, minus the option you changed - grep -v "^export ${CONFIG_OPTION}=" ${CONFIG_FILE} | grep -v "^${CONFIG_OPTION}=" >>${CONFIG_FILE}.tmp || true - sort ${CONFIG_FILE}.tmp | uniq >${CONFIG_FILE} - rm -f ${CONFIG_FILE}.tmp -} - -# Set a config variable in a specific file to a given value - replace it if it -# exists, or add it. If added, the variable will be exported. -set_config() { - CONFIG_FILE="$1" - CONFIG_OPTION="$2" - NEW_SETTING="$3" - - if grep -q "$CONFIG_OPTION" "$CONFIG_FILE"; then - replace_config "$CONFIG_FILE" "$CONFIG_OPTION" "$NEW_SETTING" - else - echo "export $CONFIG_OPTION=\"$NEW_SETTING\"" >>"$CONFIG_FILE" - fi -} - -# Set a value in config.user, re-combine configs, and update configs in the -# environment. -set_user_config() { - CONFIG_OPTION="$1" - NEW_SETTING="$2" - - set_config /etc/config.user "$CONFIG_OPTION" "$NEW_SETTING" - combine_configs - . /tmp/config -} - -# Load a config value to a variable, defaulting to empty. Does not fail if the -# config is not set (since it would expand to empty by default). -load_config_value() { - local config_name="$1" - if grep -q "$config_name=" /tmp/config; then - grep "$config_name=" /tmp/config | tail -n1 | cut -f2 -d '=' | tr -d '"' - fi -} - -enable_usb() -{ - TRACE "Under /etc/ash_functions:enable_usb" - #insmod ehci_hcd prior of uhdc_hcd and ohci_hcd to suppress dmesg warning - insmod /lib/modules/ehci-hcd.ko || die "ehci_hcd: module load failed" - - if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then - insmod /lib/modules/uhci-hcd.ko || die "uhci_hcd: module load failed" - insmod /lib/modules/ohci-hcd.ko || die "ohci_hcd: module load failed" - insmod /lib/modules/ohci-pci.ko || die "ohci_pci: module load failed" - fi - insmod /lib/modules/ehci-pci.ko || die "ehci_pci: module load failed" - insmod /lib/modules/xhci-hcd.ko || die "xhci_hcd: module load failed" - insmod /lib/modules/xhci-pci.ko || die "xhci_pci: module load failed" - sleep 2 - - # For resiliency, test CONFIG_USB_KEYBOARD_REQUIRED explicitly rather - # than having it imply CONFIG_USER_USB_KEYBOARD at build time. - # Otherwise, if a user got CONFIG_USER_USB_KEYBOARD=n in their - # config.user by mistake (say, by copying config.user from a laptop to a - # desktop/server), they could lock themselves out, only recoverable by - # hardware flash. - if [ "$CONFIG_USB_KEYBOARD_REQUIRED" = y ] || [ "$CONFIG_USER_USB_KEYBOARD" = y ]; then - insmod /lib/modules/usbhid.ko || die "usbhid: module load failed" - fi -} diff --git a/initrd/etc/functions b/initrd/etc/functions old mode 100755 new mode 100644 index 311644a8..47ea70ce --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -1,6 +1,390 @@ #!/bin/bash -# Shell functions for most initialization scripts -. /etc/ash_functions + +# ------- Start of functions coming from /etc/ash_functions + +die() { + if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then + echo -e " !!! ERROR: $* !!!" | tee -a /tmp/debug.log /dev/kmsg > /dev/null; + else + echo -e >&2 "!!! ERROR: $* !!!"; + fi + sleep 2; + exit 1; +} + +warn() { + if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then + echo -e " *** WARNING: $* ***" | tee -a /tmp/debug.log /dev/kmsg > /dev/null; + else + echo -e >&2 " *** WARNING: $* ***"; + fi + sleep 1; +} + +DEBUG() { + if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then + # fold -s -w 960 will wrap lines at 960 characters on the last space before the limit + echo "DEBUG: $*" | fold -s -w 960 | 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; + fi +} + +# Write directly to the debug log (but not kmsg), never appears on console +LOG() { + # if not CONFIG_QUIET_MODE=y, output to console. If not, output to debug.log + if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then + DEBUG "$*" + elif [ "$CONFIG_QUIET_MODE" = "y" ]; then + # if in quiet mode, output solely to debug.log + echo "$*" >> /tmp/debug.log + else + # if not in quiet mode, output to console + echo "$*" + fi +} + +fw_version() { + local FW_VER=$(dmesg | grep 'DMI' | grep -o 'BIOS.*' | cut -f2- -d ' ') + # chop off date, since will always be epoch w/timeless builds + echo "${FW_VER::-10}" +} + +preserve_rom() { + TRACE_FUNC + new_rom="$1" + 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` + 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" + fi + done +} + +confirm_gpg_card() { + + #TODO: ideally, we ask for confirmation only once per boot session + #TODO: even change logic here to try first and then ask user to confirm if not found + #TODO: or ask GPG user PIN once and cache it for the rest of the boot session for reusal + # This is getting in the way of unattended stuff and GPG prompts are confusing anyway, hide them from user. + + + TRACE_FUNC + #Skip prompts if we are currently using a known GPG key material Thumb drive backup and keys are unlocked pinentry + #TODO: probably export CONFIG_GPG_KEY_BACKUP_IN_USE but not under /etc/user.config? + #Toggle to come in next PR, but currently we don't have a way to toggle it back to n if config.user flashed back in rom + if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" && "$CONFIG_GPG_KEY_BACKUP_IN_USE" == "y" ]]; then + DEBUG "Using known GPG key material Thumb drive backup and keys are unlocked and useable through pinentry" + return + fi + + if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]; then + message="Please confirm that your GPG card is inserted(Y/n) or your GPG key material (b)backup thumbdrive is inserted [Y/n/b]: " + else + # Generic message if no known key material backup + message="Please confirm that your GPG card is inserted [Y/n]: " + fi + + read \ + -n 1 \ + -p "$message" \ + card_confirm + echo + + if [ "$card_confirm" != "y" \ + -a "$card_confirm" != "Y" \ + -a "$card_confirm" != "b" \ + -a -n "$card_confirm" ] \ + ; then + die "gpg card not confirmed" + fi + + # If user has known GPG key material Thumb drive backup and asked to use it + if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" && "$card_confirm" == "b" ]]; then + #Only mount and import GPG key material thumb drive backup once + if [ ! "$CONFIG_GPG_KEY_BACKUP_IN_USE" == "y" ]; then + CR_NONCE="/tmp/secret/cr_nonce" + CR_SIG="$CR_NONCE.sig" + + #Wipe any previous CR_NONCE and CR_SIG + shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true + + #Prompt user for configured GPG Admin PIN that will be passed along to mount-usb and to import gpg subkeys + echo + gpg_admin_pin="" + while [ -z "$gpg_admin_pin" ]; do + #TODO: change all passphrase prompts in codebase to include -r to prevent backslash escapes + read -r -s -p "Please enter GPG Admin PIN needed to use the GPG backup thumb drive: " gpg_admin_pin + echo + done + #prompt user to select the proper encrypted partition, which should the first one on next prompt + warn "Please select encrypted LUKS on GPG key material backup thumb drive (not public labeled one)" + mount-usb --pass "$gpg_admin_pin" || die "Unable to mount USB with provided GPG Admin PIN" + echo "++++ Testing detach-sign operation and verifiying against fused public key in ROM" + gpg --pinentry-mode=loopback --passphrase-file <(echo -n "${gpg_admin_pin}") --import /media/subkeys.sec >/dev/null 2>&1 || + die "Unable to import GPG private subkeys" + #Do a detach signature to ensure gpg material is usable and cache passphrase to sign /boot from caller functions + dd if=/dev/urandom of="$CR_NONCE" bs=20 count=1 >/dev/null 2>&1 || + die "Unable to create $CR_NONCE to be detach-signed with GPG private signing subkey" + gpg --pinentry-mode=loopback --passphrase-file <(echo -n "${gpg_admin_pin}") --detach-sign "$CR_NONCE" >/dev/null 2>&1 || + die "Unable to detach-sign $CR_NONCE with GPG private signing subkey using GPG Admin PIN" + #verify detached signature against public key in rom + gpg --verify "$CR_SIG" "$CR_NONCE" > /dev/null 2>&1 && \ + echo "++++ Local GPG keyring can be used to sign/encrypt/authenticate in this boot session ++++" || \ + die "Unable to verify $CR_SIG detached signature against public key in ROM" + #Wipe any previous CR_NONCE and CR_SIG + shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true + #TODO: maybe just an export instead of setting /etc/user.config otherwise could be flashed in weird corner case situation + set_user_config "CONFIG_GPG_KEY_BACKUP_IN_USE" "y" + umount /media || die "Unable to unmount USB" + return + fi + fi + + # setup the USB so we can reach the USB Security dongle's OpenPGP smartcard + enable_usb + + echo -e "\nVerifying presence of GPG card...\n" + # ensure we don't exit without retrying + errexit=$(set -o | grep errexit | awk '{print $2}') + set +e + gpg_output=$(gpg --card-status 2>&1) + if [ $? -ne 0 ]; then + # prompt for reinsertion and try a second time + read -n1 -r -p \ + "Can't access GPG key; remove and reinsert, then press Enter to retry. " \ + ignored + # restore prev errexit state + if [ "$errexit" = "on" ]; then + set -e + fi + # retry card status + gpg_output=$(gpg --card-status 2>&1) || + die "gpg card read failed" + fi + # restore prev errexit state + if [ "$errexit" = "on" ]; then + set -e + fi + + # Extract and display GPG PIN retry counters + # output excerpt: "PIN retry counter : 3 0 3" + pin_retry_counters=$(echo "$gpg_output" | grep 'PIN retry counter' | awk -F': ' '{print $2}') + user_pin_retries=$(echo "$pin_retry_counters" | awk '{print $1}') + admin_pin_retries=$(echo "$pin_retry_counters" | awk '{print $3}') + + echo "" + echo "GPG User PIN retry attempts left before becoming locked: $user_pin_retries" + echo "GPG Admin PIN retry attempts left before becoming locked: $admin_pin_retries" + echo "" + warn "Your GPG User PIN, followed by Enter key will be required for input at: 'Please unlock the card' next prompt" + echo "" +} + +gpg_auth() { + if [[ "$CONFIG_HAVE_GPG_KEY_BACKUP" == "y" ]]; then + TRACE_FUNC + # If we have a GPG key backup, we can use it to authenticate even if the card is lost + echo >&2 "!!!!! Please authenticate with OpenPGP smartcard/backup media to prove you are the owner of this machine !!!!!" + + # Wipe any existing nonce and signature + shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" 2>/dev/null || true + + # In case of gpg_auth, we require confirmation of the card, so loop with confirm_gpg_card until we get it + false + while [ $? -ne 0 ]; do + # Call confirm_gpg_card in subshell to ensure GPG key material presence + ( confirm_gpg_card ) + done + + # Perform a signing-based challenge-response, + # to authencate that the card plugged in holding + # the key to sign the list of boot files. + + CR_NONCE="/tmp/secret/cr_nonce" + CR_SIG="$CR_NONCE.sig" + + # Generate a random nonce + dd \ + if=/dev/urandom \ + of="$CR_NONCE" \ + count=1 \ + bs=20 \ + 2>/dev/null \ + || die "Unable to generate 20 random bytes" + + # Sign the nonce + for tries in 1 2 3; do + if gpg --digest-algo SHA256 \ + --detach-sign \ + -o "$CR_SIG" \ + "$CR_NONCE" > /dev/null 2>&1 \ + && gpg --verify "$CR_SIG" "$CR_NONCE" > /dev/null 2>&1 \ + ; then + shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" 2>/dev/null || true + DEBUG "Under /etc/ash_functions:gpg_auth: success" + return 0 + else + shred -n 10 -z -u "$CR_SIG" 2>/dev/null || true + if [ "$tries" -lt 3 ]; then + echo >&2 "!!!!! GPG authentication failed, please try again !!!!!" + continue + else + die "GPG authentication failed, please reboot and try again" + fi + fi + done + return 1 + fi +} + +recovery() { + TRACE_FUNC + echo >&2 "!!!!! $*" + + # Remove any temporary secret files that might be hanging around + # 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 + rm -rf /tmp/secret + mkdir -p /tmp/secret + + # ensure /tmp/config exists for recovery scripts that depend on it + touch /tmp/config + . /tmp/config + + DEBUG "Board $CONFIG_BOARD - version $(fw_version)" + + if [ "$CONFIG_TPM" = "y" ]; then + LOG "TPM: Extending PCR[4] to prevent any further secret unsealing" + tpmr extend -ix 4 -ic recovery + fi + + if [ "$CONFIG_RESTRICTED_BOOT" = y ]; then + echo >&2 "Restricted Boot enabled, recovery console disabled, rebooting in 5 seconds" + sleep 5 + /bin/reboot + fi + while [ true ] + do + #Going to recovery shell should be authenticated if supported + gpg_auth + + echo >&2 "!!!!! Starting recovery shell" + sleep 1 + + if [ -x /bin/setsid ]; then + /bin/setsid -c /bin/sh + else + /bin/sh + fi + done +} + +pause_recovery() { + TRACE_FUNC + read -p $'!!! Hit enter to proceed to recovery shell !!!\n' + recovery $* +} + +combine_configs() { + TRACE_FUNC + cat /etc/config* > /tmp/config +} + +replace_config() { + TRACE_FUNC + CONFIG_FILE=$1 + CONFIG_OPTION=$2 + NEW_SETTING=$3 + + touch $CONFIG_FILE + # first pull out the existing option from the global config and place in a tmp file + awk "gsub(\"^export ${CONFIG_OPTION}=.*\",\"export ${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >${CONFIG_FILE}.tmp + awk "gsub(\"^${CONFIG_OPTION}=.*\",\"${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >>${CONFIG_FILE}.tmp + + # then copy any remaining settings from the existing config file, minus the option you changed + grep -v "^export ${CONFIG_OPTION}=" ${CONFIG_FILE} | grep -v "^${CONFIG_OPTION}=" >>${CONFIG_FILE}.tmp || true + sort ${CONFIG_FILE}.tmp | uniq >${CONFIG_FILE} + rm -f ${CONFIG_FILE}.tmp +} + +# Set a config variable in a specific file to a given value - replace it if it +# exists, or add it. If added, the variable will be exported. +set_config() { + CONFIG_FILE="$1" + CONFIG_OPTION="$2" + NEW_SETTING="$3" + + if grep -q "$CONFIG_OPTION" "$CONFIG_FILE"; then + replace_config "$CONFIG_FILE" "$CONFIG_OPTION" "$NEW_SETTING" + else + echo "export $CONFIG_OPTION=\"$NEW_SETTING\"" >>"$CONFIG_FILE" + fi +} + +# Set a value in config.user, re-combine configs, and update configs in the +# environment. +set_user_config() { + CONFIG_OPTION="$1" + NEW_SETTING="$2" + + set_config /etc/config.user "$CONFIG_OPTION" "$NEW_SETTING" + combine_configs + . /tmp/config +} + +# Load a config value to a variable, defaulting to empty. Does not fail if the +# config is not set (since it would expand to empty by default). +load_config_value() { + local config_name="$1" + if grep -q "$config_name=" /tmp/config; then + grep "$config_name=" /tmp/config | tail -n1 | cut -f2 -d '=' | tr -d '"' + fi +} + +enable_usb() +{ + TRACE_FUNC + #insmod ehci_hcd prior of uhdc_hcd and ohci_hcd to suppress dmesg warning + insmod /lib/modules/ehci-hcd.ko || die "ehci_hcd: module load failed" + + if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then + insmod /lib/modules/uhci-hcd.ko || die "uhci_hcd: module load failed" + insmod /lib/modules/ohci-hcd.ko || die "ohci_hcd: module load failed" + insmod /lib/modules/ohci-pci.ko || die "ohci_pci: module load failed" + fi + insmod /lib/modules/ehci-pci.ko || die "ehci_pci: module load failed" + insmod /lib/modules/xhci-hcd.ko || die "xhci_hcd: module load failed" + insmod /lib/modules/xhci-pci.ko || die "xhci_pci: module load failed" + sleep 2 + + # For resiliency, test CONFIG_USB_KEYBOARD_REQUIRED explicitly rather + # than having it imply CONFIG_USER_USB_KEYBOARD at build time. + # Otherwise, if a user got CONFIG_USER_USB_KEYBOARD=n in their + # config.user by mistake (say, by copying config.user from a laptop to a + # desktop/server), they could lock themselves out, only recoverable by + # hardware flash. + if [ "$CONFIG_USB_KEYBOARD_REQUIRED" = y ] || [ "$CONFIG_USER_USB_KEYBOARD" = y ]; then + insmod /lib/modules/usbhid.ko || die "usbhid: module load failed" + fi +} + +# ------- End of functions coming from /etc/ash_functions + # Print or depending on whether $1 is empty. Useful to mask an # optional password parameter. diff --git a/initrd/init b/initrd/init index 3b308f13..578ec4a5 100755 --- a/initrd/init +++ b/initrd/init @@ -1,4 +1,4 @@ -#! /bin/ash +#! /bin/bash # Note this is used on legacy-flash boards that lack bash, it runs with busybox # ash. Calls to bash scripts must be guarded by checking config. @@ -56,7 +56,7 @@ hwclock -l -s (grep -v '^\texfat$' /proc/filesystems && echo -e '\texfat') >/etc/filesystems # Read the system configuration parameters -. /etc/ash_functions +. /etc/functions . /etc/config # report if we are in quiet mode, tell logs available under /tmp/debug.log @@ -81,7 +81,7 @@ else DEBUG "Debug output enabled from /etc/config.user's CONFIG_DEBUG_OUTPUT=y after combine_configs (Config menu enabled Debug)" fi -TRACE "Under init" +TRACE_FUNC # make sure we have sysctl requirements if [ ! -d /proc/sys ]; then @@ -173,7 +173,7 @@ if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then # config.user extracted and combined from CBFS had CONFIG_DEBUG_OUTPUT=y dmesg -n 8 DEBUG "Debug output enabled from /etc/config.user's CONFIG_DEBUG_OUTPUT=y after combine_configs (Config menu enabled Debug)" - TRACE "Under init:after combine_configs" + TRACE_FUNC fi fi