Merge branch 'usb-boot' of https://github.com/flammit/heads into flammit-usb-boot

This commit is contained in:
Trammell Hudson 2017-07-17 08:52:48 -04:00
commit ba98d5dda6
Failed to extract signature
20 changed files with 1401 additions and 46 deletions

View File

@ -19,6 +19,8 @@ CONFIG_LINUX_USB=y
CONFIG_BOOTSCRIPT=/bin/qubes-init
CONFIG_USB_BOOT_DEV="/dev/sdb1"
# Disks encrypted by the TPM LUKS key
CONFIG_QUBES_BOOT_DEV="/dev/sda1"
CONFIG_QUBES_VG="qubes_dom0"

View File

@ -0,0 +1,26 @@
# Configuration for a x230 running non-Qubes
BOARD=x230
CONFIG_CRYPTSETUP=y
CONFIG_FLASHROM=y
CONFIG_GPG=y
CONFIG_KEXEC=y
CONFIG_UTIL_LINUX=y
CONFIG_LVM2=y
CONFIG_MBEDTLS=y
CONFIG_PCIUTILS=y
CONFIG_POPT=y
CONFIG_QRENCODE=y
CONFIG_TPMTOTP=y
CONFIG_XEN=y
CONFIG_DROPBEAR=y
CONFIG_LINUX_USB=y
CONFIG_LINUX_E1000E=y
CONFIG_BOOTSCRIPT=/bin/generic-init
CONFIG_BOOT_REQ_HASH=n
CONFIG_BOOT_REQ_ROLLBACK=n
CONFIG_BOOT_DEV="/dev/sda1"
CONFIG_USB_BOOT_DEV="/dev/sdb1"

View File

@ -20,6 +20,8 @@ CONFIG_LINUX_E1000E=y
CONFIG_BOOTSCRIPT=/bin/qubes-init
CONFIG_USB_BOOT_DEV="/dev/sdb1"
# Disks encrypted by the TPM LUKS key
CONFIG_QUBES_BOOT_DEV="/dev/sda1"
CONFIG_QUBES_VG="qubes_dom0"

25
initrd/bin/generic-init Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
# Boot from a local disk installation
. /etc/functions
. /etc/config
# Confirm we have a good TOTP unseal
if ! confirm_totp ; then
recovery 'Failed to unseal TOTP'
fi
if [ ! "$totp_confirm" = "y" ]; then
recovery "Failed to confirm validity of TOTP"
fi
# Mount local disk
if ! grep -q /boot /proc/mounts ; then
mount -o ro "$CONFIG_BOOT_DEV" /boot \
|| recovery "$CONFIG_BOOT_DEV: Unable to mount /boot"
fi
# Attempt to pull verified config from device
kexec-select-boot -b /boot/ -c "grub.cfg"
recovery "Something failed during boot"

137
initrd/bin/kexec-boot Executable file
View File

@ -0,0 +1,137 @@
#!/bin/sh
# Launches kexec from saved configuration entries
set -e -o pipefail
. /etc/functions
dryrun="n"
printfiles="n"
printinitrd="n"
while getopts "b:e:r:a:o:fi" arg; do
case $arg in
b) bootdir="$OPTARG" ;;
e) entry="$OPTARG" ;;
r) cmdremove="$OPTARG" ;;
a) cmdadd="$OPTARG" ;;
o) override_initrd="$OPTARG" ;;
f) dryrun="y"; printfiles="y" ;;
i) dryrun="y"; printinitrd="y" ;;
esac
done
if [ -z "$bootdir" -o -z "$entry" ]; then
die "Usage: $0 -b /boot/ -e 'kexec params|...|...'"
fi
bootdir=${bootdir%%"/"}
kexectype=`echo $entry | cut -d\| -f2`
kexecparams=`echo $entry | cut -d\| -f3- | tr '|' '\n'`
kexeccmd="kexec"
# TODO: make this configurable
cmdadd="intel_iommu=on $cmdadd"
cmdremove="quiet $cmdremove"
fix_file_path() {
if [ "$printfiles" = "y" ]; then
# output file relative to local boot directory
echo ".$firstval"
fi
filepath="$bootdir$firstval"
if ! [ -r $filepath ]; then
die "Failed to find file $firstval"
fi
}
adjusted_cmd_line="n"
adjust_cmd_line() {
if [ -n "$cmdremove" ]; then
for i in $cmdremove; do
cmdline="${cmdline//$i/}"
done
fi
if [ -n "$cmdadd" ]; then
cmdline="$cmdline $cmdadd"
fi
adjusted_cmd_line="y"
}
module_number="1"
while read line
do
key=`echo $line | cut -d\ -f1`
firstval=`echo $line | cut -d\ -f2`
restval=`echo $line | cut -d\ -f3-`
if [ "$key" = "kernel" ]; then
if [ "$kexectype" = "xen" ]; then
# always overload xen and with custom arguments
kexeccmd="$kexeccmd -l /bin/xen.gz"
kexeccmd="$kexeccmd --command-line \"no-real-mode reboot=no\""
elif [ "$kexectype" = "multiboot" ]; then
fix_file_path
kexeccmd="$kexeccmd -l $filepath"
kexeccmd="$kexeccmd --command-line \"$restval\""
else
fix_file_path
kexeccmd="$kexeccmd -l $filepath"
fi
fi
if [ "$key" = "module" ]; then
fix_file_path
cmdline="$restval"
if [ "$kexectype" = "xen" ]; then
if [ "$module_number" -eq 1 ]; then
adjust_cmd_line
elif [ "$module_number" -eq 2 ]; then
if [ "$printinitrd" = "y" ]; then
# output the current path to initrd
echo $filepath
fi
if [ -n "$override_initrd" ]; then
filepath="$override_initrd"
fi
fi
fi
module_number=`expr $module_number + 1`
kexeccmd="$kexeccmd --module \"$filepath $cmdline\""
fi
if [ "$key" = "initrd" ]; then
fix_file_path
if [ "$printinitrd" = "y" ]; then
# output the current path to initrd
echo $filepath
fi
if [ -n "$override_initrd" ]; then
filepath="$override_initrd"
fi
kexeccmd="$kexeccmd --initrd=$filepath"
fi
if [ "$key" = "append" ]; then
cmdline="$firstval $restval"
adjust_cmd_line
kexeccmd="$kexeccmd --append=\"$cmdline\""
fi
done << EOF
$kexecparams
EOF
if [ "$adjusted_cmd_line" = "n" ]; then
if [ "$kexectype" = "elf" ]; then
kexeccmd="$kexeccmd --append=\"$cmdadd\""
else
die "Failed to add required kernel commands: $cmdadd"
fi
fi
if [ "$dryrun" = "y" ]; then exit 0; fi
echo "Loading the new kernel:"
echo "$kexeccmd"
eval "$kexeccmd" \
|| die "Failed to load the new kernel"
echo "Starting the new kernel"
exec kexec -e

61
initrd/bin/kexec-insert-key Executable file
View File

@ -0,0 +1,61 @@
#!/bin/sh
# Unseal a disk key from TPM and add to a new initramfs
set -e -o pipefail
. /etc/functions
TMP_KEY_DEVICES="/tmp/kexec/kexec_key_devices.txt"
TMP_KEY_LVM="/tmp/kexec/kexec_key_lvm.txt"
INITRD="$1"
if [ -z "$INITRD" ]; then
die "Usage: $0 /boot/initramfs... "
fi
if [ ! -r "$TMP_KEY_DEVICES" ]; then
die "No devices defined for disk encryption"
fi
if [ -r "$TMP_KEY_LVM" ]; then
# Activate the LVM volume group
VOLUME_GROUP=`cat $TMP_KEY_LVM`
if [ -z "$TMP_KEY_LVM" ]; then
die "No LVM volume group defined for activation"
fi
lvm vgchange -a y $VOLUME_GROUP \
|| die "$VOLUME_GROUP: unable to activate volume group"
fi
# Measure the LUKS headers before we unseal the disk key
cat "$TMP_KEY_DEVICES" | cut -d\ -f1 | xargs /bin/qubes-measure-luks \
|| die "LUKS measure failed"
# Unpack the initrd and fixup the /etc/crypttab
# this is a hack to split it into two parts since
# we know that the first 0x3400 bytes are the microcode
INITRD_DIR=/tmp/secret/initrd
SECRET_CPIO=/tmp/secret/initrd.cpio
mkdir -p "$INITRD_DIR/etc"
# Attempt to unseal the disk key from the TPM
# should we give this some number of tries?
if ! kexec-unseal-key "$INITRD_DIR/secret.key" ; then
die 'Unseal disk key failed'
fi
# Override PCR 4 so that user can't read the key
tpm extend -ix 4 -ic generic \
|| die 'Unable to scramble PCR'
echo '+++ Building initrd'
# pad the initramfs (dracut doesn't pad the last gz blob)
# without this the kernel init/initramfs.c fails to read
# the subsequent uncompressed/compressed cpio
dd if="$INITRD" of="$SECRET_CPIO" bs=512 conv=sync \
|| die "Failed to copy initrd to /tmp"
# overwrite /etc/crypttab to mirror the behavior for in seal-key
for uuid in `cat "$TMP_KEY_DEVICES" | cut -d\ -f2`; do
echo "luks-$uuid UUID=$uuid /secret.key" >> "$INITRD_DIR/etc/crypttab"
done
( cd "$INITRD_DIR" ; find . -type f | cpio -H newc -o ) >> "$SECRET_CPIO"

49
initrd/bin/kexec-iso-init Executable file
View File

@ -0,0 +1,49 @@
#!/bin/sh
# Boot from signed ISO
set -e -o pipefail
. /etc/functions
. /etc/config
MOUNTED_ISO_PATH="$1"
ISO_PATH="$2"
DEV="$3"
echo '+++ Verifying ISO'
# Verify the signature on the hashes
ISOSIG="$MOUNTED_ISO_PATH.sig"
if ! [ -r "$ISOSIG" ]; then
ISOSIG="$MOUNTED_ISO_PATH.asc"
fi
gpgv "$ISOSIG" "$MOUNTED_ISO_PATH" \
|| die 'ISO signature failed'
echo '+++ Mounting ISO and booting'
mount -t iso9660 -o loop $MOUNTED_ISO_PATH /boot \
|| die '$MOUNTED_ISO_PATH: Unable to mount /boot'
DEV_UUID=`blkid $DEV | tail -1 | tr " " "\n" | grep UUID | cut -d\" -f2`
ADD="fromiso=/dev/disk/by-uuid/$DEV_UUID/$ISO_PATH"
REMOVE=""
paramsdir="/media/kexec_iso/$ISO_PATH/"
check_config $paramsdir
ADD_FILE=/tmp/kexec/kexec_iso_add.txt
if [ -r $ADD_FILE ]; then
NEW_ADD=`cat $ADD_FILE`
ADD=$(eval "echo \"$NEW_ADD\"")
echo "+++ Overriding standard ISO kernel add arguments: $ADD"
fi
REMOVE_FILE=/tmp/kexec/kexec_iso_remove.txt
if [ -r $REMOVE_FILE ]; then
NEW_REMOVE=`cat $REMOVE_FILE`
REMOVE=$(eval "echo \"$NEW_REMOVE\"")
echo "+++ Overriding standard ISO kernel remove arguments: $REMOVE"
fi
# Call kexec and indicate that hashes have been verified
kexec-select-boot -b /boot/ -d /media/ -p "$paramsdir" \
-a "$ADD" -r "$REMOVE" -c "*.cfg" -u -i
die "Something failed in selecting boot"

208
initrd/bin/kexec-parse-boot Executable file
View File

@ -0,0 +1,208 @@
#!/bin/sh
set -e -o pipefail
bootdir="$1"
file="$2"
if [ -z "$bootdir" -o -z "$file" ]; then
die "Usage: $0 /boot/ /boot/grub/grub.cfg"
fi
reset_entry() {
name=""
kexectype="elf"
kernel=""
initrd=""
modules=""
append=""
}
filedir=`dirname $file`
bootdir=${bootdir%%"/"}
bootlen=${#bootdir}
appenddir=${filedir:$bootlen}
fix_path() {
path="$@"
if [ "${path:0:1}" != "/" ]; then
path="$appenddir/$path"
fi
}
echo_entry() {
if [ "$kexectype" = "elf" ]; then
if [ -z "$kernel" ]; then return; fi
fix_path $kernel
entry="$name|$kexectype|kernel $path"
if [ -n "$initrd" ]; then
fix_path $initrd
entry="$entry|initrd $path"
fi
if [ -n "$append" ]; then
entry="$entry|append $append"
fi
echo $(eval "echo \"$entry\"")
fi
if [ "$kexectype" = "multiboot" -o "$kexectype" = "xen" ]; then
if [ -z "$kernel" ]; then return; fi
fix_path $kernel
echo $(eval "echo \"$name|$kexectype|kernel $path$modules\"")
fi
}
search_entry() {
case $line in
menuentry* | MENUENTRY* )
state="grub"
reset_entry
name=`echo $line | tr "'" "\"" | cut -d\" -f 2`
;;
label* | LABEL* )
state="syslinux"
reset_entry
name=`echo $line | cut -c6- `
esac
}
grub_entry() {
if [ "$line" = "}" ]; then
echo_entry
state="search"
return
fi
# add info to menuentry
trimcmd=`echo $line | tr '\t ' ' ' | tr -s ' '`
cmd=`echo $trimcmd | cut -d\ -f1`
val=`echo $trimcmd | cut -d\ -f2-`
case $cmd in
multiboot*)
# TODO: differentiate between Xen and other multiboot kernels
kexectype="xen"
kernel="$val"
;;
module*)
case $val in
--nounzip*) val=`echo $val | cut -d\ -f2-` ;;
esac
fix_path $val
modules="$modules|module $path"
;;
linux*)
kernel=`echo $trimcmd | cut -d\ -f2`
append=`echo $trimcmd | cut -d\ -f3-`
;;
initrd*)
initrd="$val"
;;
esac
}
syslinux_end() {
# finish menuentry
# attempt to parse out of append if missing initrd
if [ -z "$initrd" ]; then
newappend=""
for param in $append; do
case $param in
initrd=*)
initrd=`echo $param | cut -d\= -f2`
;;
*) newappend="$newappend $param" ;;
esac
done
append="${newappend##' '}"
fi
echo_entry
state="search"
}
syslinux_multiboot_append() {
splitval=`echo "${val// --- /|}" | tr '|' '\n'`
while read line
do
if [ -z "$kernel" ]; then
kernel="$line"
else
fix_path $line
modules="$modules|module $path"
fi
done << EOF
$splitval
EOF
}
syslinux_entry() {
case $line in
"")
syslinux_end
return
;;
label* | LABEL* )
syslinux_end
search_entry
return
;;
esac
# add info to menuentry
trimcmd=`echo $line | tr '\t ' ' ' | tr -s ' '`
cmd=`echo $trimcmd | cut -d\ -f1`
val=`echo $trimcmd | cut -d\ -f2-`
case $trimcmd in
menu* | MENU* )
cmd2=`echo $trimcmd | cut -d \ -f2`
if [ "$cmd2" = "label" -o "$cmd2" = "LABEL" ]; then
name=`echo $trimcmd | cut -c11- | tr -d '^'`
fi
;;
linux* | LINUX* | kernel* | KERNEL* )
case $val in
# TODO: differentiate between Xen and other multiboot kernels
*mboot.c32) kexectype="xen" ;;
*.c32)
# skip this entry
state="search"
;;
*)
kernel="$val"
esac
;;
initrd* | INITRD* )
initrd="$val"
;;
append* | APPEND* )
if [ "$kexectype" = "multiboot" -o "$kexectype" = "xen" ]; then
syslinux_multiboot_append
else
append="$val"
fi
;;
esac
}
state="search"
while read line
do
case $state in
search)
search_entry
;;
grub)
grub_entry
;;
syslinux)
syslinux_entry
;;
esac
done < "$file"
# handle EOF case
if [ "$state" = "syslinux" ]; then
syslinux_end
fi

61
initrd/bin/kexec-save-default Executable file
View File

@ -0,0 +1,61 @@
#!/bin/sh
# Save these options to be the persistent default
set -e -o pipefail
. /etc/functions
while getopts "b:d:p:e:i:" arg; do
case $arg in
b) bootdir="$OPTARG" ;;
d) paramsdev="$OPTARG" ;;
p) paramsdir="$OPTARG" ;;
i) index="$OPTARG" ;;
esac
done
if [ -z "$bootdir" -o -z "$index" ]; then
die "Usage: $0 -b /boot/ -i menu_option "
fi
if [ -z "$paramsdev" ]; then
paramsdev="$bootdir"
fi
if [ -z "$paramsdir" ]; then
paramsdir="$bootdir"
fi
TMP_MENU_FILE="/tmp/kexec/kexec_menu.txt"
ENTRY_FILE="$paramsdir/kexec_default.$index.txt"
HASH_FILE="$paramsdir/kexec_default_hashes.txt"
if [ ! -r "$TMP_MENU_FILE" ]; then
die "No menu options available, please run kexec-select-boot"
fi
entry=`head -n $index $TMP_MENU_FILE | tail -1`
if [ -z "$entry" ]; then
die "Invalid menu index $index"
fi
# try to switch to rw mode
mount -o rw,remount $paramsdev
if [ ! -d $paramsdir ]; then
mkdir -p $paramsdir \
|| die "Failed to create params directory"
fi
rm "$paramsdir/kexec_default.*.txt" 2>/dev/null || true
echo "$entry" > $ENTRY_FILE
cd $bootdir && kexec-boot -b "$bootdir" -e "$entry" -f | \
xargs sha256sum > $HASH_FILE \
|| die "Failed to create hashes of boot files"
if [ ! -r $ENTRY_FILE -o ! -r $HASH_FILE ]; then
die "Failed to write default config"
fi
# sign and auto-roll config counter
kexec-sign-config -p $paramsdir -u \
|| die "Failed to sign default config"
# switch back to ro mode
mount -o ro,remount $paramsdev

63
initrd/bin/kexec-save-key Executable file
View File

@ -0,0 +1,63 @@
#!/bin/sh
# Generate a TPM key used to unlock LUKS disks
set -e -o pipefail
. /etc/functions
lvm_volume_group=""
while getopts "p:d:l:" arg; do
case $arg in
p) paramsdir="$OPTARG" ;;
d) paramsdev="$OPTARG" ;;
l) lvm_volume_group="$OPTARG" ;;
esac
done
shift `expr $OPTIND - 1`
key_devices="$@"
if [ -z "$paramsdir" ]; then
die "Usage: $0 -p /boot/ [-l qubes_dom0] [/dev/sda2 /dev/sda5 ...] "
fi
if [ -z "$paramsdev" ]; then
paramsdev="$paramsdir"
fi
if [ -n "$lvm_volume_group" ]; then
lvm vgchange -a y $lvm_volume_group \
|| die "Failed to activate the LVM group"
for dev in /dev/$lvm_volume_group/*; do
key_devices="$key_devices $dev"
done
fi
if [ -z "$key_devices" ]; then
die "No devices specified for TPM key insertion"
fi
# try to switch to rw mode
mount -o rw,remount $paramsdev
rm -f $paramsdir/kexec_key_lvm.txt || true
if [ -n "$lvm_volume_group" ]; then
echo "$lvm_volume_group" > $paramsdir/kexec_key_lvm.txt \
|| die "Failed to write lvm group to key config "
fi
rm -f $paramsdir/kexec_key_devices.txt || true
for dev in $key_devices; do
uuid=`cryptsetup luksUUID "$dev" 2>/dev/null` \
|| die "Failed to get UUID for device $dev"
echo "$dev $uuid" >> $paramsdir/kexec_key_devices.txt \
|| die "Failed to add $dev:$uuid to key devices config"
done
kexec-seal-key $paramsdir \
|| die "Failed to save and generate key in TPM"
# sign and auto-roll config counter
kexec-sign-config -p $paramsdir -u \
|| die "Failed to sign updated config"
# switch back to ro mode
mount -o ro,remount $paramsdev

145
initrd/bin/kexec-seal-key Executable file
View File

@ -0,0 +1,145 @@
#!/bin/sh
# This will generate a disk encryption key and seal / ecncrypt
# with the current PCRs and then store it in the TPM NVRAM.
# It will then need to be bundled into initrd that is booted.
set -e -o pipefail
TPM_INDEX=3
TPM_SIZE=312
KEY_FILE="/tmp/secret/secret.key"
TPM_SEALED="/tmp/secret/secret.sealed"
RECOVERY_KEY="/tmp/secret/recovery.key"
. /etc/functions
. /etc/config
paramsdir=$1
if [ -z "$paramsdir" ]; then
die "Usage $0 /boot/"
fi
KEY_DEVICES="$paramsdir/kexec_key_devices.txt"
KEY_LVM="$paramsdir/kexec_key_lvm.txt"
if [ ! -r "$KEY_DEVICES" ]; then
die "No devices defined for disk encryption"
fi
if [ -r "$KEY_LVM" ]; then
# Activate the LVM volume group
VOLUME_GROUP=`cat $KEY_LVM`
if [ -z "$VOLUME_GROUP" ]; then
die "No LVM volume group defined for activation"
fi
lvm vgchange -a y $VOLUME_GROUP \
|| die "$VOLUME_GROUP: unable to activate volume group"
fi
# Key slot 0 is the manual recovery pass phrase
# that they user entered when they installed Qubes,
# key slot 1 is the one that we've generated.
read -s -p "Enter disk recovery key: " disk_password
echo -n "$disk_password" > "$RECOVERY_KEY"
echo
read -s -p "New disk unlock password for booting: " key_password
echo
read -s -p "Repeat unlock code: " key_password2
echo
if [ "$key_password" != "$key_password2" ]; then
die "Key passwords do not match"
fi
# Generate key file
dd \
if=/dev/urandom \
of="$KEY_FILE" \
bs=1 \
count=128 \
2>/dev/null \
|| die "Unable to generate 128 random bytes"
# Remove all the old keys from slot 1
for dev in `cat "$KEY_DEVICES" | cut -d\ -f1`; do
echo "++++++ $dev: Removing old key slot"
cryptsetup luksKillSlot \
--key-file "$RECOVERY_KEY" \
$dev 1 \
|| warn "$dev: ignoring problem"
echo "++++++ $dev: Adding key"
cryptsetup luksAddKey \
--key-file "$RECOVERY_KEY" \
--key-slot 1 \
$dev "$KEY_FILE" \
|| die "$dev: Unable to add key"
done
# Now that we have setup the new keys, measure the PCRs
# We don't care what ends up in PCR 6; we just want
# to get the /tmp/luksDump.txt file. We use PCR16
# since it should still be zero
cat "$KEY_DEVICES" | cut -d\ -f1 | xargs /bin/qubes-measure-luks \
|| die "Unable to measure the LUKS headers"
luks_pcr=`tpm calcfuturepcr -ix 16 -if /tmp/luksDump.txt`
# Note that PCR 4 needs to be set with the "normal-boot"
# path value, which we do not have right now since we are
# in a recovery shell.
# used to be -ix 4 f8fa3b6e32e7c6fe04c366e74636e505b28f3b0d \
# now just all zeros in a normal boot
# PCR 5 must be all zero since no kernel modules should have
# been loaded during a normal boot, but might have been
# loaded in the recovery shell.
# Otherwise use the current values of the PCRs, which will be read
# from the TPM as part of the sealing ("X").
tpm sealfile2 \
-if "$KEY_FILE" \
-of "$TPM_SEALED" \
-pwdd "$key_password" \
-hk 40000000 \
-ix 0 X \
-ix 1 X \
-ix 2 X \
-ix 3 X \
-ix 4 0000000000000000000000000000000000000000 \
-ix 5 0000000000000000000000000000000000000000 \
-ix 6 $luks_pcr \
|| die "Unable to seal secret"
rm -f "$KEY_FILE" \
|| die "Failed to delete key file"
# try it without the owner password first
if ! tpm nv_writevalue \
-in $TPM_INDEX \
-if "$TPM_SEALED" \
; then
# to create an nvram space we need the TPM owner password
# and the TPM physical presence must be asserted.
#
# The permissions are 0 since there is nothing special
# about the sealed file
tpm physicalpresence -s \
|| warn "Warning: Unable to assert physical presence"
read -s -p "TPM Owner password: " tpm_password
echo
tpm nv_definespace \
-in $TPM_INDEX \
-sz $TPM_SIZE \
-pwdo "$tpm_password" \
-per 0 \
|| warn "Warning: Unable to define NVRAM space; trying anyway"
tpm nv_writevalue \
-in $TPM_INDEX \
-if "$TPM_SEALED" \
|| die "Unable to write sealed secret to NVRAM"
fi
rm "$TPM_SEALED" \
|| warn "Failed to delete the sealed secret - continuing"

294
initrd/bin/kexec-select-boot Executable file
View File

@ -0,0 +1,294 @@
#!/bin/sh
# Generic configurable boot script via kexec
set -e -o pipefail
. /etc/config
. /etc/functions
add=""
remove=""
config="*.cfg"
unique="n"
valid_hash="n"
valid_global_hash="n"
valid_rollback="n"
while getopts "b:d:p:a:r:c:ui" arg; do
case $arg in
b) bootdir="$OPTARG" ;;
d) paramsdev="$OPTARG" ;;
p) paramsdir="$OPTARG" ;;
a) add="$OPTARG" ;;
r) remove="$OPTARG" ;;
c) config="$OPTARG" ;;
u) unique="y" ;;
i) valid_hash="y"; valid_rollback="y" ;;
esac
done
if [ -z "$bootdir" ]; then
die "Usage: $0 -b /boot/"
fi
if [ -z "$paramsdev" ]; then
paramsdev="$bootdir"
fi
if [ -z "$paramsdir" ]; then
paramsdir="$bootdir"
fi
verify_global_hashes()
{
echo "+++ Checking verified boot hash file "
# Check the hashes of all the files
if cd $bootdir && sha256sum -c "$TMP_HASH_FILE" ; then
echo "+++ Verified boot hashes "
valid_hash='y'
valid_global_hash='y'
else
die "$TMP_HASH_FILE: boot hash mismatch"
fi
}
verify_rollback_counter()
{
TPM_COUNTER=`grep counter $TMP_ROLLBACK_FILE | cut -d- -f2`
if [ -z "$TPM_COUNTER" ]; then
die "$TMP_ROLLBACK_FILE: TPM counter not found?"
fi
read_tpm_counter $TPM_COUNTER \
|| die "Failed to read TPM counter"
sha256sum -c $TMP_ROLLBACK_FILE \
|| die "Invalid TPM counter state"
valid_rollback="y"
}
first_menu="y"
get_menu_option() {
num_options=`cat $TMP_MENU_FILE | wc -l`
if [ $num_options -eq 0 ]; then
die "No boot options"
fi
if [ $num_options -eq 1 -a $first_menu = "y" ]; then
option_index=1
else
echo "+++ Select your boot option:"
n=0
while read option
do
parse_option
n=`expr $n + 1`
echo "$n. $name [$kernel]"
done < $TMP_MENU_FILE
read \
-p "Choose the boot option [1-$n, a to abort]: " \
option_index
if [ "$option_index" = "a" ]; then
die "Aborting boot attempt"
fi
fi
first_menu="n"
option=`head -n $option_index $TMP_MENU_FILE | tail -1`
parse_option
}
confirm_menu_option() {
echo "+++ Please confirm the boot details for $name:"
echo $option
read \
-n 1 \
-p "Confirm selection by pressing 'y', make default with 'd': " \
option_confirm
echo
}
parse_option() {
name=`echo $option | cut -d\| -f1`
kernel=`echo $option | cut -d\| -f3`
}
scan_options() {
echo "+++ Scanning for unsigned boot options"
option_file="/tmp/kexec_options.txt"
if [ -r $option_file ]; then rm $option_file; fi
for i in `find $bootdir -name "$config"`; do
kexec-parse-boot "$bootdir" "$i" >> $option_file
done
if [ ! -r $option_file ]; then
die "Failed to parse any boot options"
fi
if [ "$unique" = 'y' ]; then
sort $option_file | uniq > $TMP_MENU_FILE
else
cp $option_file $TMP_MENU_FILE
fi
}
save_default_option() {
read \
-n 1 \
-p "Saving a default will modify the disk. Proceed? (y/n): " \
default_confirm
echo
if [ "$default_confirm" = "y" ]; then
if kexec-save-default \
-b "$bootdir" \
-d "$paramsdev" \
-p "$paramsdir" \
-i "$option_index" \
; then
echo "+++ Saved defaults to device"
sleep 2
default_failed="n"
return
else
echo "Failed to save defaults"
fi
fi
option_confirm="n"
}
default_select() {
# Attempt boot with expected parameters
# Check that entry matches that which is expected from menu
default_index=`basename "$TMP_DEFAULT_FILE" | cut -d. -f 2`
# Check to see if entries have changed - useful for detecting grub update
expectedoption=`cat $TMP_DEFAULT_FILE`
option=`head -n $default_index $TMP_MENU_FILE | tail -1`
if [ "$option" != "$expectedoption" ]; then
warn "!!! Boot entry has changed - please set a new default"
sleep 5
return
fi
parse_option
# Enforce that default option hashes are valid
echo "+++ Checking verified default boot hash file "
# Check the hashes of all the files
if cd $bootdir && sha256sum -c "$TMP_DEFAULT_HASH_FILE" ; then
echo "+++ Verified default boot hashes "
valid_hash='y'
else
die "$TMP_DEFAULT_HASH_FILE: default boot hash mismatch"
fi
echo "+++ Executing default boot for $name:"
do_boot
warn "Failed to boot default option"
}
user_select() {
# No default expected boot parameters, ask user
option_confirm=""
while [ "$option_confirm" != "y" -a "$option_confirm" != "d" ]
do
get_menu_option
confirm_menu_option
if [ "$option_confirm" = 'd' ]; then
save_default_option
fi
done
if [ "$option_confirm" = "d" ]; then
# reload settings to reflect new default
continue
fi
do_boot
}
do_boot()
{
if [ "$CONFIG_BOOT_REQ_ROLLBACK" = "y" -a "$valid_rollback" = "n" ]; then
warn "!!! Missing required rollback counter state"
return
fi
if [ "$CONFIG_BOOT_REQ_HASH" = "y" -a "$valid_hash" = "n" ]; then
warn "!!! Missing required boot hashes"
return
fi
if [ -r "$TMP_KEY_DEVICES" ]; then
INITRD=`kexec-boot -b "$bootdir" -e "$option" -i` \
|| die "!!! Failed to extract the initrd from boot option"
if [ -z "$INITRD" ]; then
die "!!! No initrd file found in boot option"
fi
kexec-insert-key $INITRD \
|| die "!!! Failed to insert disk key into a new initrd"
kexec-boot -b "$bootdir" -e "$option" \
-a "$add" -r "$remove" -o "/tmp/secret/initrd.cpio" \
|| die "!!! Failed to boot w/ options: $option"
else
kexec-boot -b "$bootdir" -e "$option" -a "$add" -r "$remove" \
|| die "!!! Failed to boot w/ options: $option"
fi
}
while true; do
check_config $paramsdir
TMP_DEFAULT_FILE=`find /tmp/kexec/kexec_default.*.txt 2>/dev/null | head -1` || true
TMP_MENU_FILE="/tmp/kexec/kexec_menu.txt"
TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt"
TMP_DEFAULT_HASH_FILE="/tmp/kexec/kexec_default_hashes.txt"
TMP_ROLLBACK_FILE="/tmp/kexec/kexec_rollback.txt"
TMP_KEY_DEVICES="/tmp/kexec/kexec_key_devices.txt"
TMP_KEY_LVM="/tmp/kexec/kexec_key_lvm.txt"
if [ ! -r "$TMP_KEY_DEVICES" ]; then
# Extend PCR4 as soon as possible
tpm extend -ix 4 -ic generic \
|| die "Failed to extend PCR 4"
fi
# if no saved options, scan the boot directory and generate
if [ ! -r "$TMP_MENU_FILE" ]; then
scan_options
fi
# Optionally enforce device file hashes
if [ -r "$TMP_HASH_FILE" ]; then
valid_global_hash="n"
verify_global_hashes
if [ "$valid_global_hash" = "n" ]; then
die "Failed to verify global hashes"
fi
fi
if [ -r "$TMP_ROLLBACK_FILE" ]; then
# in the case of iso boot with a rollback file, do not assume valid
valid_rollback="n"
verify_rollback_counter
fi
if [ "$default_failed" != "y" \
-a -r "$TMP_DEFAULT_FILE" \
-a -r "$TMP_DEFAULT_HASH_FILE" ] \
; then
default_select
default_failed="y"
else
user_select
fi
done
die "!!! Shouldn't get here""

59
initrd/bin/kexec-sign-config Executable file
View File

@ -0,0 +1,59 @@
#!/bin/sh
# Sign a valid directory of kexec params
set -e -o pipefail
. /etc/functions
rollback="n"
update_counter="n"
while getopts "p:c:u" arg; do
case $arg in
p) paramsdir="$OPTARG" ;;
c) counter="$OPTARG"; rollback="y" ;;
u) update_counter="y"; rollback="y" ;;
esac
done
if [ -z "$paramsdir" ]; then
die "Usage: $0 -p /boot/ [ -u | -c counter ]"
fi
confirm_gpg_card
if [ "$rollback" = "y" ]; then
rollback_file="$paramsdir/kexec_rollback.txt"
if [ -n "$counter" ]; then
# use existing counter
read_tpm_counter $counter \
|| die "$paramsdir: Unable to read tpm counter '$counter'"
else
# increment counter
check_tpm_counter $rollback_file \
|| die "$paramsdir: Unable to find/create tpm counter"
counter="$TPM_COUNTER"
increment_tpm_counter $counter \
|| die "$paramsdir: Unable to increment tpm counter"
fi
sha256sum /tmp/counter-$counter > $rollback_file \
|| die "$paramsdir: Unable to create rollback file"
fi
param_files=`find $paramsdir/kexec*.txt`
if [ -z "$param_files" ]; then
die "$paramsdir: No kexec parameter files to sign"
fi
for tries in 1 2 3; do
if sha256sum $param_files | gpg \
--digest-algo SHA256 \
--detach-sign \
-a \
> $paramsdir/kexec.sig \
; then
exit 0
fi
done
die "$paramsdir: Unable to sign kexec hashes"

45
initrd/bin/kexec-unseal-key Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
# This will unseal and unecncrypt the drive encryption key from the TPM
# The TOTP secret will be shown to the user on each encryption attempt.
# It will then need to be bundled into initrd that is booted with Qubes.
set -e -o pipefail
TPM_INDEX=3
TPM_SIZE=312
. /etc/functions
mkdir -p /tmp/secret
sealed_file="/tmp/secret/sealed.key"
key_file="$1"
if [ -z "$key_file" ]; then
key_file="/tmp/secret/secret.key"
fi
tpm nv_readvalue \
-in "$TPM_INDEX" \
-sz "$TPM_SIZE" \
-of "$sealed_file" \
|| die "Unable to read key from TPM NVRAM"
for tries in 1 2 3; do
read -s -p "Enter unlock password: " tpm_password
echo
if tpm unsealfile \
-if "$sealed_file" \
-of "$key_file" \
-pwdd "$tpm_password" \
-hk 40000000 \
; then
# should be okay if this fails
rm -f /tmp/secret/sealed || true
exit 0
fi
pcrs
warn "Unable to unseal disk encryption key"
done
die "Retry count exceeded..."

View File

@ -36,7 +36,7 @@ if [ -z "$TPM_COUNTER" ]; then
recovery "$BOOT_HASHES: TPM counter not found?"
fi
tpm counter_read -ix "$TPM_COUNTER" | tee "/tmp/counter-$TPM_COUNTER"
read_tpm_counter $TPM_COUNTER
# Check the hashes of all the files
sha256sum -c "$BOOT_HASHES" \

View File

@ -12,55 +12,14 @@ if [ -z "$XEN" -o -z "$KERNEL" -o -z "$INITRD" ]; then
die "Usage: $0 /boot/xen... /boot/vmlinuz... /boot/initramfs..."
fi
# setup the USB so we can reach the GPG card
if ! lsmod | grep -q ehci_hcd; then
insmod /lib/modules/ehci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q ehci_pci; then
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 "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q xhci_pci; then
insmod /lib/modules/xhci-pci.ko \
|| die "ehci_pci: module load failed"
sleep 2
fi
confirm_gpg_card
gpg --card-status \
|| die "gpg card read failed"
# if the /boot.hashes file already exists, read the TPM counter ID
# from it.
if [ -r "$BOOT_HASHES" ]; then
TPM_COUNTER=`grep counter- "$BOOT_HASHES" | cut -d- -f2`
else
warn "$BOOT_HASHES does not exist; creating new TPM counter"
read -s -p "TPM Owner password: " tpm_password
echo
tpm counter_create \
-pwdo "$tpm_password" \
-pwdc '' \
-la 3135106223 \
| tee /tmp/counter \
|| die "Unable to create TPM counter"
TPM_COUNTER=`cut -d: -f1 < /tmp/counter`
fi
if [ -z "$TPM_COUNTER" ]; then
die "$BOOT_HASHES: TPM Counter not found?"
fi
check_tpm_counter $BOOT_HASHES
mount -o rw,remount /boot \
|| die "Could not remount /boot"
tpm counter_increment -ix "$TPM_COUNTER" -pwdc '' \
| tee /tmp/counter-$TPM_COUNTER \
|| die "Counter increment failed"
increment_tpm_counter $TPM_COUNTER
sha256sum \
"$XEN" \

20
initrd/bin/usb-init Executable file
View File

@ -0,0 +1,20 @@
#!/bin/sh
# Boot a USB installation
. /etc/functions
. /etc/config
# Confirm we have a good TOTP unseal
if ! confirm_totp ; then
recovery 'Failed to unseal TOTP'
fi
# Extend PCR4 as soon as possible
tpm extend -ix 4 -ic usb
if [ ! "$totp_confirm" = "y" ]; then
recovery "Failed to confirm validity of TOTP"
fi
usb-scan
recovery "Something failed during USB boot"

67
initrd/bin/usb-scan Executable file
View File

@ -0,0 +1,67 @@
#!/bin/sh
# Scan for USB installation options
set -e -o pipefail
. /etc/functions
. /etc/config
# Unmount any previous boot device
if grep -q /boot /proc/mounts ; then
umount /boot \
|| die '$CONFIG_USB_BOOT_DEV: Unable to unmount /boot'
fi
# Mount the USB boot device
if ! grep -q /media /proc/mounts ; then
mount-usb "$CONFIG_USB_BOOT_DEV" \
|| die '$CONFIG_USB_BOOT_DEV: Unable to mount /media'
fi
# Check for ISO first
get_menu_option() {
echo "+++ Select your ISO boot option:"
n=0
while read option
do
n=`expr $n + 1`
echo "$n. $option"
done < /tmp/iso_menu.txt
read \
-p "Choose the ISO boot option [1-$n, s for standard boot, a to abort]: " \
option_index
if [ "$option_index" = "a" ]; then
die "Aborting boot attempt"
fi
if [ "$option_index" = "s" ]; then
option=""
return
fi
option=`head -n $option_index /tmp/iso_menu.txt | tail -1`
}
# create ISO menu options
ls -1r /media/*.iso 2>/dev/null > /tmp/iso_menu.txt || true
if [ `cat /tmp/iso_menu.txt | wc -l` -gt 0 ]; then
option_confirm=""
while [ -z "$option" -a "$option_index" != "s" ]
do
get_menu_option
done
if [ -n "$option" ]; then
MOUNTED_ISO=$option
ISO=${option:7} # remove /media/ to get device relative path
kexec-iso-init $MOUNTED_ISO $ISO $CONFIG_USB_BOOT_DEV
die "Something failed in iso init"
fi
fi
echo "!!! Could not find any ISO, trying bootable USB"
# Attempt to pull verified config from device
kexec-select-boot -b /media/ -c "*.cfg" -u
die "Something failed in selecting boot"

View File

@ -28,3 +28,127 @@ recovery() {
pcrs() {
head -7 /sys/class/tpm/tpm0/pcrs
}
confirm_totp()
{
last_half=X
while true; do
# update the TOTP code every thirty seconds
date=`date "+%Y-%m-%d %H:%M:%S"`
seconds=`date "+%s"`
half=`expr \( $seconds % 60 \) / 30`
if [ "$half" != "$last_half" ]; then
last_half=$half;
TOTP=`unseal-totp` \
|| recovery "TOTP code generation failed"
fi
echo -n "$date $TOTP: "
# read the first character, non-blocking
read \
-t 1 \
-n 1 \
-s \
-p "Confirm TOTP with a 'y': " \
totp_confirm \
&& break
# nothing typed, redraw the line
echo -ne '\r'
done
# clean up with a newline
echo
}
confirm_gpg_card()
{
# setup the USB so we can reach the GPG card
if ! lsmod | grep -q ehci_hcd; then
insmod /lib/modules/ehci-hcd.ko \
|| die "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q ehci_pci; then
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 "ehci_hcd: module load failed"
fi
if ! lsmod | grep -q xhci_pci; then
insmod /lib/modules/xhci-pci.ko \
|| die "ehci_pci: module load failed"
sleep 2
fi
gpg --card-status \
|| die "gpg card read failed"
}
check_tpm_counter()
{
# if the /boot.hashes file already exists, read the TPM counter ID
# from it.
if [ -r "$1" ]; then
TPM_COUNTER=`grep counter- "$1" | cut -d- -f2`
else
warn "$BOOT_HASHES does not exist; creating new TPM counter"
read -s -p "TPM Owner password: " tpm_password
echo
tpm counter_create \
-pwdo "$tpm_password" \
-pwdc '' \
-la 3135106223 \
| tee /tmp/counter \
|| die "Unable to create TPM counter"
TPM_COUNTER=`cut -d: -f1 < /tmp/counter`
fi
if [ -z "$TPM_COUNTER" ]; then
die "$1: TPM Counter not found?"
fi
}
read_tpm_counter()
{
tpm counter_read -ix "$1" | tee "/tmp/counter-$1" \
|| die "Counter read failed"
}
increment_tpm_counter()
{
tpm counter_increment -ix "$1" -pwdc '' \
| tee /tmp/counter-$1 \
|| die "Counter increment failed"
}
check_config() {
if [ ! -d /tmp/kexec ]; then
mkdir /tmp/kexec \
|| die 'Failed to make kexec tmp dir'
else
rm -rf /tmp/kexec/* \
|| die 'Failed to empty kexec tmp dir'
fi
if [ ! -r $1/kexec.sig ]; then
return
fi
if [ `find $1/kexec*.txt | wc -l` -eq 0 ]; then
return
fi
if ! sha256sum `find $1/kexec*.txt` | gpgv $1/kexec.sig - ; then
die 'Invalid signature on kexec boot params'
fi
echo "+++ Found verified kexec boot params"
cp $1/kexec*.txt /tmp/kexec \
|| die "Failed to copy kexec boot params to tmp"
}

View File

@ -39,7 +39,7 @@ fi
# Give the user a second to enter a recovery shell
read \
-t "1" \
-p "Press 'r' for recovery shell: " \
-p "Press 'r' for recovery shell or 'u' for usb: " \
-n 1 \
boot_option
echo
@ -52,6 +52,14 @@ if [ "$boot_option" = "r" ]; then
exec /bin/ash
fi
if [ "$boot_option" = "u" ]; then
echo '***** USB boot'
exec /bin/usb-init
# just in case...
tpm extend -ix 4 -ic recovery
exec /bin/ash
fi
echo '***** Normal boot'
exec "$CONFIG_BOOTSCRIPT"