From 4f54a97cf208e28f624a206ad62469966b8c726e Mon Sep 17 00:00:00 2001 From: Matt DeVillier Date: Mon, 19 Aug 2019 17:07:22 -0500 Subject: [PATCH 1/5] etc/function: add detect_boot_device() Add function to detect boot device. Start by checking CONFIG_BOOT_DEV, then iterate thru all bootable partitions. Check if partition is mountable, contains grub directory. Update CONFIG_BOOT_DEV and mount on /boot if successful. Signed-off-by: Matt DeVillier --- initrd/etc/functions | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/initrd/etc/functions b/initrd/etc/functions index d6cf0c14..056c3e82 100755 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -293,3 +293,50 @@ update_checksums() # switch back to ro mode mount -o ro,remount /boot } + +# detect and set /boot device +# mount /boot if successful +detect_boot_device() +{ + # unmount /boot to be safe + umount /boot 2>/dev/null + + # check $CONFIG_BOOT_DEV if set/valid + if [ -e "$CONFIG_BOOT_DEV" ]; then + mount -o ro $CONFIG_BOOT_DEV /boot >/dev/null 2>&1 + if [[ $? && -d /boot/grub ]]; then + # CONFIG_BOOT_DEV is valid device and contains an installed OS + return 0 + fi + fi + + # generate list of possible boot devices + fdisk -l | grep "Disk" | cut -f2 -d " " | cut -f1 -d ":" > /tmp/disklist + + # filter out extraneous options + > /tmp/boot_device_list + for i in `cat /tmp/disklist`; do + # remove block device from list if numeric partitions exist, since not bootable + let DEV_NUM_PARTITIONS=`ls -1 $i* | wc -l`-1 + if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then + echo $i >> /tmp/boot_device_list + else + ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp/boot_device_list + fi + done + + # iterate thru possible options and check for grub dir + for i in `cat /tmp/boot_device_list`; do + umount /boot 2>/dev/null + mount -o ro $i /boot >/dev/null 2>&1 + if [[ $? && -d /boot/grub ]]; then + CONFIG_BOOT_DEV="$i" + return 0 + fi + done + + # no valid boot device found + echo "Unable to locate /boot files on any mounted disk" + umount /boot 2>/dev/null + return 1 +} From f067d9af234be0985295d6b5913aee45726e8219 Mon Sep 17 00:00:00 2001 From: Matt DeVillier Date: Thu, 15 Aug 2019 13:36:05 -0500 Subject: [PATCH 2/5] initrd/bin: add OEM Factory Reset Add oem-factory-reset script which performs an unattended reset and configuration of the device's TPM, GPG security token, and boot device / boot selection. Signed-off-by: Matt DeVillier --- initrd/bin/oem-factory-reset | 355 +++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100755 initrd/bin/oem-factory-reset diff --git a/initrd/bin/oem-factory-reset b/initrd/bin/oem-factory-reset new file mode 100755 index 00000000..e5138387 --- /dev/null +++ b/initrd/bin/oem-factory-reset @@ -0,0 +1,355 @@ +#!/bin/sh +# Automated setup of TPM, GPG keys, and disk + +set -o pipefail + +# use TERM to exit on error +trap "exit 1" TERM +export TOP_PID=$$ + +## Static local variables + +CLEAR="--clear" +CONTINUE="--yes-button Continue" +CANCEL="--no-button Cancel" +HEIGHT="150" +WIDTH="220" + +USER_PIN_DEF=123456 +ADMIN_PIN_DEF=12345678 +TPM_PASS_DEF=12345678 + +## External files sourced + +. /etc/functions +. /tmp/config + +## functions + +die() { + + local msg=$1 + if [ -n "$msg" ]; then + echo -e "\n$msg" + fi + kill -s TERM $TOP_PID + exit 1 +} + +whiptail_error() +{ + local msg=$1 + if [ "$msg" = "" ]; then + die "whiptail error: An error msg is required" + fi + whiptail --msgbox "${msg}\n\n" $WIDTH $HEIGHT $CONFIG_ERROR_BG_COLOR --title "Error" +} + +whiptail_error_die() +{ + whiptail_error "$@" + die +} + +gpg_key_reset() +{ + GPG_KEY_NAME=`date +%Y%m%d%H%M%S` + # Factory reset GPG card + { + echo admin + echo factory-reset + echo y + echo yes + } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \ + > /tmp/gpg_card_edit_output 2>/dev/null + if [ $? -ne 0 ]; then + ERROR=`cat /tmp/gpg_card_edit_output` + whiptail_error_die "GPG Key factory reset failed!\n\n$ERROR" + fi + # Generate OEM GPG keys + { + echo admin + echo generate + echo n + echo ${ADMIN_PIN_DEF} + echo ${USER_PIN_DEF} + echo 0 + echo y + echo "OEM Key" + echo "oem-${GPG_KEY_NAME}@example.com" + echo "OEM-generated key" + } | gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit \ + > /tmp/gpg_card_edit_output 2>/dev/null + if [ $? -ne 0 ]; then + ERROR=`cat /tmp/gpg_card_edit_output` + whiptail_error_die "GPG Key automatic keygen failed!\n\n$ERROR" + fi +} + +generate_checksums() +{ + # ensure /boot mounted + if ! grep -q /boot /proc/mounts ; then + mount -o rw /boot || whiptail_error_die "Unable to mount /boot" + else + mount -o remount,rw /boot || whiptail_error_die "Unable to mount /boot" + fi + + # clear any existing checksums/signatures + rm /boot/kexec* 2>/dev/null + + # create Heads TPM counter + tpm counter_create \ + -pwdo "$TPM_PASS_DEF" \ + -pwdc '' \ + -la -3135106223 \ + | tee /tmp/counter \ + || whiptail_error_die "Unable to create TPM counter" + TPM_COUNTER=`cut -d: -f1 < /tmp/counter` + + # increment TPM counter + increment_tpm_counter $TPM_COUNTER >/dev/null 2>&1 \ + || whiptail_error_die "Unable to increment tpm counter" + + # create rollback file + sha256sum /tmp/counter-$TPM_COUNTER > /boot/kexec_rollback.txt 2>/dev/null \ + || whiptail_error_die "Unable to create rollback file" + + # set default boot option + set_default_boot_option + + # generate hashes + find /boot -type f ! -name '*kexec*' \ + | xargs sha256sum > /boot/kexec_hashes.txt 2>/dev/null \ + || whiptail_error_die "Error generating kexec hashes" + + param_files=`find /boot/kexec*.txt` + [ -z "$param_files" ] \ + && whiptail_error_die "No kexec parameter files to sign" + + # sign kexec boot files + if sha256sum $param_files 2>/dev/null | gpg \ + --pinentry-mode loopback \ + --passphrase $USER_PIN_DEF \ + --digest-algo SHA256 \ + --detach-sign \ + -a \ + > /boot/kexec.sig 2>/tmp/error; then + # successful - update the validated params + if ! check_config /boot >/dev/null 2>/tmp/error ; then + cat /tmp/error + ret=1 + else + ret=0 + fi + else + cat /tmp/error + ret=1 + fi + + # done writing to /boot, switch back to RO + mount -o ro,remount /boot + + if [ $ret = 1 ] ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error signing kexec boot files:\n\n$ERROR" + fi +} + +set_default_boot_option() +{ + option_file="/tmp/kexec_options.txt" + tmp_menu_file="/tmp/kexec/kexec_menu.txt" + hash_file="/boot/kexec_default_hashes.txt" + + mkdir -p /tmp/kexec/ + rm $option_file 2>/dev/null + # parse boot options from grub.cfg + for i in `find /boot -name "grub.cfg"`; do + kexec-parse-boot "/boot" "$i" >> $option_file + done + [ ! -r $option_file ] \ + && whiptail_error_die "Failed to parse any boot options" + + # sort boot options + sort -r $option_file | uniq > $tmp_menu_file + + ## save first option as default + entry=`head -n 1 $tmp_menu_file | tail -1` + + # clear existing default configs + rm "/boot/kexec_default.*.txt" 2>/dev/null + + # write new config + echo "$entry" > /boot/kexec_default.1.txt + + # validate boot option + cd /boot && /bin/kexec-boot -b "/boot" -e "$entry" -f \ + | xargs sha256sum > $hash_file 2>/dev/null \ + || whiptail_error_die "Failed to create hashes of boot files" +} + +## main script start + +# check for args +if [ "$1" != "" ]; then + title_text=$1 +else + title_text="OEM Factory Reset" +fi +if [ "$2" != "" ]; then + bg_color=$2 +else + bg_color="" +fi + +# show warning prompt +if ! whiptail --yesno " + This operation will automatically:\n\n + * ERASE the TPM and reset it with a default password\n + * ERASE any keys or passwords on the GPG smart card,\n + reset it to a factory state, and generate new keys\n + * Add the new GPG key to the firmware and reflash it\n + * Sign all of the files in /boot with the new GPG key\n\n + It requires that you already have an OS installed on a\n + dedicated /boot partition. Do you wish to continue?\n" \ + $WIDTH $HEIGHT $CONTINUE $CANCEL $CLEAR $bg_color --title "$title_text" ; then + exit 1 +fi + +## sanity check the USB, GPG key, and boot device before proceeding further + +# mount USB, then remount rw +echo -e "\nChecking for USB media...\n" +# ensure /media not mounted +umount /media 2>/dev/null +# mount-usb will detect and prompt if no USB inserted +if ! mount-usb rw 2>/tmp/error; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Unable to mount USB on /media:\n\n${ERROR}" +fi + +# ensure GPG key connected +echo -e "\nChecking for GPG Key...\n" +# USB kernel modules already loaded via mount-usb +if ! gpg --card-status >/dev/null 2>&1 ; then + whiptail_error "Can't access GPG Key; remove and reinsert, then press Enter to retry." + if ! gpg --card-status >/dev/null 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Unable to detect GPG Key:\n\n${ERROR}" + fi +fi + +# detect and set /boot device +echo -e "\nDetecting and setting boot device...\n" +if ! detect_boot_device ; then + whiptail_error_die "Unable to locate /boot files on any mounted disk" +else + echo -e "Boot device set to $CONFIG_BOOT_DEV\n" +fi + +# update configs +replace_config /etc/config.user "CONFIG_BOOT_DEV" "$CONFIG_BOOT_DEV" +combine_configs + +## reset TPM and set default password +echo -e "\nResetting TPM...\n" +{ + echo $TPM_PASS_DEF + echo $TPM_PASS_DEF +} | /bin/tpm-reset >/dev/null 2>/tmp/error +if [ $? -ne 0 ]; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error resetting TPM:\n\n${ERROR}" +fi + +# clear local keyring +rm /.gnupg/*.gpg 2>/dev/null +rm /.gnupg/*.kbx 2>/dev/null +gpg --list-keys >/dev/null 2>&1 + +## reset the GPG Key +echo -e "\nResetting GPG Key...\n(this will take a minute or two)\n" +gpg_key_reset + +## export generated key to USB +echo -e "\nExporting generated key to USB...\n" +# parse name of generated key +GPG_GEN_KEY=`grep -A1 pub /tmp/gpg_card_edit_output | tail -n1 | sed -nr 's/^([ ])*//p'` +PUBKEY="/tmp/${GPG_GEN_KEY}.asc" +# export pubkey to file +if ! gpg --export --armor $GPG_GEN_KEY > "${PUBKEY}" 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "GPG Key gpg export to file failed!\n\n$ERROR" +fi +# copy to USB +if ! cp "${PUBKEY}" "/media/${GPG_GEN_KEY}.asc" 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Key export error: unable to copy ${GPG_GEN_KEY}.asc to /media:\n\n$ERROR" +fi +umount /media 2>/dev/null + +## flash generated key to ROM +echo -e "\nReading current firmware...\n(this will take a minute or two)\n" +/bin/flash.sh -r /tmp/oem-setup.rom >/dev/null 2>/tmp/error +if [ ! -s /tmp/oem-setup.rom ]; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error reading current firmware:\n\n$ERROR" +fi + +# ensure key imported locally +if ! cat "$PUBKEY" | gpg --import >/dev/null 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error importing GPG key:\n\n$ERROR" +fi +# update /.gnupg/trustdb.gpg to ultimately trust all user provided public keys +if ! gpg --list-keys --fingerprint --with-colons 2>/dev/null \ + | sed -E -n -e 's/^fpr:::::::::([0-9A-F]+):$/\1:6:/p' \ + | gpg --import-ownertrust >/dev/null 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error importing GPG ownertrust:\n\n$ERROR" +fi +if ! gpg --update-trust >/dev/null 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error updating GPG ownertrust:\n\n$ERROR" +fi +# clear any existing heads/gpg files from current firmware +for i in `cbfs -o /tmp/oem-setup.rom -l | grep -e "heads/"`; do + cbfs -o /tmp/oem-setup.rom -d $i +done +# add heads/gpg files to current firmware +if [ -e /.gnupg/pubring.kbx ];then + cbfs -o /tmp/oem-setup.rom -a "heads/initrd/.gnupg/pubring.kbx" -f /.gnupg/pubring.kbx + if [ -e /.gnupg/pubring.gpg ];then + rm /.gnupg/pubring.gpg + fi +elif [ -e /.gnupg/pubring.gpg ];then + cbfs -o /tmp/oem-setup.rom -a "heads/initrd/.gnupg/pubring.gpg" -f /.gnupg/pubring.gpg +fi +if [ -e /.gnupg/trustdb.gpg ]; then + cbfs -o /tmp/oem-setup.rom -a "heads/initrd/.gnupg/trustdb.gpg" -f /.gnupg/trustdb.gpg +fi +# persist user config changes (boot device) +if [ -e /etc/config.user ]; then + cbfs -o /tmp/oem-setup.rom -a "heads/initrd/etc/config.user" -f /etc/config.user +fi +# flash updated firmware image +echo -e "\nAdding generated key to current firmware and re-flashing...\n" +if ! /bin/flash.sh /tmp/oem-setup.rom >/dev/null 2>/tmp/error ; then + ERROR=$(tail -n 1 /tmp/error) + whiptail_error_die "Error flashing updated firmware image:\n\n$ERROR" +fi + +## sign files in /boot and generate checksums +echo -e "\nSigning boot files and generating checksums...\n" +generate_checksums + +## all done -- reboot +whiptail --msgbox " + The OEM Factory Reset has completed successfully\n\n + After rebooting, you will need to generate new TOTP/HOTP secrets\n + when prompted in order to complete the setup process.\n\n + Press any key to reboot.\n" \ + $WIDTH $HEIGHT --title "OEM Factory Reset Complete" + +reboot From d8bcc7b841660a633f689296916302cf72d38356 Mon Sep 17 00:00:00 2001 From: Matt DeVillier Date: Fri, 16 Aug 2019 09:33:17 -0500 Subject: [PATCH 3/5] gui-init: add OEM Factory Reset to options menu Add an OEM Factory Reset menu option, which performs an unattended reset and configuration of the device's TPM, GPG security token, and boot device / boot selection. Signed-off-by: Matt DeVillier --- initrd/bin/gui-init | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index bac7a298..5ba4edac 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -195,6 +195,7 @@ while true; do 'c' ' Change configuration settings -->' \ 'f' ' Flash/Update the BIOS -->' \ 'G' ' GPG Options -->' \ + 'F' ' OEM Factory Reset -->' \ 'x' ' Exit to recovery shell' \ 'r' ' <-- Return to main menu' \ 2>/tmp/whiptail || recovery "GUI menu failed" @@ -335,6 +336,11 @@ while true; do continue fi + if [ "$totp_confirm" = "F" ]; then + oem-factory-reset + continue + fi + if [ "$totp_confirm" = "P" ]; then poweroff fi From ba23fb7ac2ce81adaf692b7cd3fc5fc380668697 Mon Sep 17 00:00:00 2001 From: Matt DeVillier Date: Fri, 16 Aug 2019 09:35:40 -0500 Subject: [PATCH 4/5] gpg-gui: remove OEM factory reset option superseded by newer version in main options menu Signed-off-by: Matt DeVillier --- initrd/bin/gpg-gui.sh | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/initrd/bin/gpg-gui.sh b/initrd/bin/gpg-gui.sh index 0b016b62..c7d179e3 100755 --- a/initrd/bin/gpg-gui.sh +++ b/initrd/bin/gpg-gui.sh @@ -164,29 +164,6 @@ gpg_post_gen_mgmt() { gpg_flash_rom fi } -gpg_sc_oem_reset() { - GPG_KEY_NAME=`date +%Y%m%d%H%M%S` - # Factory reset GPG card - { - echo admin - echo factory-reset - echo y - echo yes - } | gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit > /tmp/gpg_card_edit_output || return 1 - # Generate OEM GPG keys - { - echo admin - echo generate - echo n - echo 12345678 - echo 123456 - echo 0 - echo y - echo "OEM Key" - echo "oem-${GPG_KEY_NAME}@example.com" - echo "OEM-generated key" - } | gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit > /tmp/gpg_card_edit_output || return 2 -} gpg_add_key_reflash() { if (whiptail --title 'GPG public key required' \ @@ -229,7 +206,6 @@ while true; do 'e' ' Replace GPG key(s) in the current ROM + reflash' \ 'l' ' List GPG keys in your keyring' \ 'g' ' Generate GPG keys manually on a USB security token' \ - 'o' ' OEM Factory reset + auto keygen USB security token' \ 'x' ' Exit' \ 2>/tmp/whiptail || recovery "GUI menu failed" @@ -303,24 +279,6 @@ while true; do gpg_post_gen_mgmt fi ;; - "o" ) - if (whiptail $CONFIG_WARNING_BG_COLOR --title 'WARNING: Factory Reset USB Security Token?' \ - --yesno "This will perform a FACTORY RESET of the USB security token!\n\nThis will:\n* Reset all security token passwords to default\n* Erase any keys on the security token\n* Generate new automated GPG keys on the token\n\nAny data now on the USB security token will be LOST!\n\nDo you want to proceed?" 16 120) then - confirm_gpg_card - gpg_sc_oem_reset - if [ $? -eq 0 ]; then - gpg_post_gen_mgmt - elif [ $? -eq 1 ]; then - GPG_OUTPUT=`cat /tmp/gpg_card_edit_output` - whiptail $CONFIG_ERROR_BG_COLOR --title 'ERROR: Factory Reset Failed!' \ - --msgbox "Factory Reset Failed!\n\n$GPG_OUTPUT" 16 120 - elif [ $? -eq 2 ]; then - GPG_OUTPUT=`cat /tmp/gpg_card_edit_output` - whiptail $CONFIG_ERROR_BG_COLOR --title 'ERROR: Automatic Keygen Failed!' \ - --msgbox "Automatic Keygen Failed!\n\n$GPG_OUTPUT" 16 120 - fi - fi - ;; esac done From aab9004c5303ca37fb9adf8c86f854b3ad5fe93d Mon Sep 17 00:00:00 2001 From: Matt DeVillier Date: Mon, 19 Aug 2019 17:09:42 -0500 Subject: [PATCH 5/5] gui-init: add clean boot check Add a check to determine if first boot after flashing a cleaned ROM, and prompt user to run the OEM Factory Reset if so Signed-off-by: Matt DeVillier --- initrd/bin/gui-init | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index 5ba4edac..dfa6a923 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -102,11 +102,43 @@ update_totp() fi } +clean_boot_check() +{ + # assume /boot mounted + if ! grep -q /boot /proc/mounts ; then + return + fi + + # check for any kexec files in /boot + kexec_files=`find /boot -name kexec*.txt` + [ ! -z "$kexec_files" ] && return + + #check for GPG key in keyring + GPG_KEY_COUNT=`gpg -k 2>/dev/null | wc -l` + [ $GPG_KEY_COUNT -ne 0 ] && return + + # check for USB security token + if ! gpg --card-status > /dev/null ; then + return + fi + + # OS is installed, no kexec files present, no GPG keys in keyring, security token present + # prompt user to run OEM factory reset + oem-factory-reset \ + "Clean Boot Detected - Perform OEM Factory Reset?" "$CONFIG_WARNING_BG_COLOR" +} + # enable USB to load modules for external kb enable_usb -# ensure /boot is sane and mount it -mount_boot +if detect_boot_device ; then + # /boot device with installed OS found + clean_boot_check +else + # can't determine /boot device or no OS installed, + # so fall back to interactive selection + mount_boot +fi last_half=X while true; do