Deprecate ash in favor of bash shell; /etc/ash_functions: move /etc/ash_functions under /etc/functions, replace TRACE calls by TRACE_FUNC, remove xx30-flash.init

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
This commit is contained in:
Thierry Laurion 2024-12-16 11:46:37 -05:00
parent 4354cd4c22
commit 08f52af033
No known key found for this signature in database
GPG Key ID: 9A53E1BB3FF00461
8 changed files with 402 additions and 431 deletions

View File

@ -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
"" )

View File

@ -1,14 +1,13 @@
#!/bin/sh
#!/bin/bash
# For this to work:
# - io386 module needs to be enabled in board config
# - <Skylake: coreboot config need to enable CONFIG_BOOTMEDIA_LOCK_CONTROLLER=y without enabling CONFIG_INTEL_CHIPSET_LOCKDOWN
# - >=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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

388
initrd/etc/functions Executable file → Normal file
View File

@ -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 <hidden> or <empty> depending on whether $1 is empty. Useful to mask an
# optional password parameter.

View File

@ -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