mirror of
https://github.com/linuxboot/heads.git
synced 2024-12-18 20:47:55 +00:00
9b101f1454
- boards: switch flashrom->flashprog, FLASH_OPTIONS: flashprog memory --progress --programmer internal TODO: check, Might break: - xx20 : x220/t420/t520: used hwseq: verify compat - legacy : not sure --ifd bios are support: verify compat (and drop, future PR drop legacy boards anyway...) - talos: linux_mtd is used: verify compat Tested: - x230 works with awesome progress bar on read, erase and write. Signed-off-by: Thierry Laurion <insurgo@riseup.net>
357 lines
12 KiB
Bash
357 lines
12 KiB
Bash
#!/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() {
|
|
echo "LOG: $*" >>/tmp/debug.log
|
|
}
|
|
|
|
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() {
|
|
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 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 --card-status >/dev/null
|
|
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 --card-status >/dev/null ||
|
|
die "gpg card read failed"
|
|
fi
|
|
# restore prev errexit state
|
|
if [ "$errexit" = "on" ]; then
|
|
set -e
|
|
fi
|
|
}
|
|
|
|
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
|
|
echo "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
|
|
}
|