Rework /init and qubes setup scripts (issue #27, #155, #32, #29, #110)

This adds support for seamless booting of Qubes with a TPM disk key,
as well as signing of qubes files in /boot with a Yubikey.

The signed hashes also includes a TPM counter, which is incremented
when new hashes are signed.  This prevents rollback attacks against
the /boot filesystem.

The TPMTOTP value is presented to the user at the time of entering
the disk encryption keys.  Hitting enter will generate a new code.

The LUKS headers are included in the TPM sealing of the disk
encryption keys.
This commit is contained in:
Trammell Hudson 2017-04-12 06:57:58 -04:00
parent 8464227aa1
commit 353a0efe6f
Failed to extract signature
7 changed files with 242 additions and 236 deletions

38
initrd/bin/qubes-boot Executable file
View File

@ -0,0 +1,38 @@
#!/bin/sh
# Final stage to start qubes given a Xen, dom0 kernel and initrd
# get the UUID of the root file system
# busybox blkid doesn't have a "just the UUID" option
. /etc/functions
. /etc/config
XEN="$1"
KERNEL="$2"
INITRD="$3"
if [ -z "$XEN" -o -z "$KERNEL" -o -z "$INITRD" ]; then
die "Usage: $0 /boot/xen... /boot/vmlinuz... /boot/initramfs..."
fi
# Activate the dom0 group, if it isn't already active
lvm vgchange -a y "$CONFIG_QUBES_VG" \
|| die "$CONFIG_QUBES_VG: LVM volume group activate failed"
ROOT_UUID=`blkid /dev/$CONFIG_QUBES_VG/00 | cut -d\" -f2`
if [ -z "$ROOT_UUID" ]; then
die "$CONFIG_QUBES_VG/00: No UUID for /"
fi
echo "$CONFIG_QUBES_VG/00: UUID=$ROOT_UUID"
# command line arguments are include in the signature on this script,
echo '+++ Loading kernel and initrd'
kexec \
-l \
--module "$KERNEL root=/dev/mapper/luks-$ROOT_UUID ro rd.qubes.hide_all_usb" \
--module "$INITRD" \
--command-line "no-real-mode reboot=no" \
"${XEN}" \
|| die "kexec load failed"
echo "+++ Starting Qubes..."
exec kexec -e

View File

@ -5,85 +5,79 @@
# which is only set if the top level /init script has started
# without user intervention or dropping into a recovery shell.
recovery() {
echo >&2 "!!!!! $@"
rm -f /tmp/secret.key
tpm extend -ix 4 -ic recovery
. /etc/functions
. /etc/config
echo >&2 "!!!!! Starting recovery shell"
exec /bin/ash
}
. /config
# TODO: Allow /boot to be encrypted?
# This would require a different TPM key or a user
# passphrase to decrypt it.
mount -o ro "$CONFIG_QUBES_BOOT_DEV" /boot \
|| recovery '$CONFIG_BOOT_DEV: Unable to mount /boot'
BOOT_SCRIPT=/boot/boot.sh
if [ ! -x /boot/boot.sh ]; then
recovery "$BOOT_SCRIPT does not exist"
if [ "$1" = "recovery" ]; then
warn "Recovery mode boot; ignoring key failures"
RECOVERY=1
fi
# Hand control over to the user boot script
echo "+++ Checking $BOOT_SCRIPT"
gpgv "$BOOT_SCRIPT.asc" "$BOOT_SCRIPT" \
|| recovery 'boot script signature failed'
# TODO: Allow /boot to be encrypted?
# This would require a different TPM key, a user passphrase to decrypt it,
# or loading the USB modules to talk to a Yubikey to get the thing.
if ! grep -q /boot /proc/mounts ; then
mount -o ro "$CONFIG_QUBES_BOOT_DEV" /boot \
|| recovery '$CONFIG_BOOT_DEV: Unable to mount /boot'
fi
exec "$BOOT_SCRIPT"
BOOT_HASHES=/boot/boot.hashes
if [ ! -r "$BOOT_HASHES" ]; then
recovery "$BOOT_HASHES does not exist; re-run qubes-update"
fi
recovery 'Boot script exec failed?'
# Verify the signature on the hashes
gpgv "$BOOT_HASHES.asc" "$BOOT_HASHES" \
|| recovery 'boot hashes signature failed'
############################
# For historical reference
# Retrieve the TPM counter ID and generate its current value
TPM_COUNTER=`grep counter $BOOT_HASHES | cut -d- -f2`
if [ -z "$TPM_COUNTER" ]; then
recovery "$BOOT_HASHES: TPM counter not found?"
fi
# TODO: Allow these to be specified on the /boot device
XEN=/boot/xen-4.6.3.heads
INITRD=/boot/initramfs-4.4.31-11.pvops.qubes.x86_64.img
KERNEL=/boot/vmlinuz-4.4.31-11.pvops.qubes.x86_64
tpm counter_read -ix "$TPM_COUNTER" | tee "/tmp/counter-$TPM_COUNTER"
echo "+++ Checking $XEN"
gpgv "${XEN}.asc" "${XEN}" \
|| recovery 'Xen signature failed'
# Check the hashes of all the files
sha256sum -c "$BOOT_HASHES" \
|| recovery "$BOOT_HASHES: hash mismatch"
echo "+++ Checking $INITRD"
gpgv "${INITRD}.asc" "${INITRD}" \
|| recovery 'Initrd signature failed'
XEN=`grep /boot/xen $BOOT_HASHES | cut -d\ -f3 | tail -1`
KERNEL=`grep /boot/vmlin $BOOT_HASHES | cut -d\ -f3 | tail -1`
INITRD=`grep /boot/initram $BOOT_HASHES | cut -d\ -f3 | tail -1`
echo "+++ Checking $KERNEL"
gpgv "${KERNEL}.asc" "${KERNEL}" \
|| recovery 'Kernel signature failed'
# Activate the dom0 group
lvm vgchange -a y "$CONFIG_QUBES_VG" \
|| recovery "$CONFIG_QUBES_VG: LVM volume group activate failed"
# Measure the LUKS headers before we unseal the disk key
/bin/qubes-measure-luks $CONFIG_QUBES_DEVS \
qubes-measure-luks /dev/$CONFIG_QUBES_VG/* \
|| recovery "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?
unseal-key \
|| recovery 'Unseal disk key failed. Starting recovery shell'
if ! unseal-key "$INITRD_DIR/secret.key" ; then
warn 'Unseal disk key failed'
if [ -z "$RECOVERY" ]; then
recovery 'Starting recovery shell'
fi
fi
# command line arguments are in the hash, so they are "correct".
kexec \
-l \
--module "${KERNEL} root=LABEL=root ro rd.qubes.hide_all_usb rhgb" \
--module "${INITRD}" \
--command-line "no-real-mode reboot=no"
"${XEN}" \
|| recovery "kexec load failed"
kexec -l \
--module "${KERNEL} root=UUID=257b593f-d4ae-46ee-b499-14bc9ffd37d4 ro rd.qubes.hide_all_usb" \
--module "/boot/initramfs-4.4.31-11.pvops.qubes.x86_64.img" \
--command-line "no-real-mode reboot=no" \
/boot/xen-4.6.3.heads
# Last step is to override PCR 6 so that user can't read the key
# Override PCR 4 so that user can't read the key
tpm extend -ix 4 -ic qubes \
|| recovery 'Unable to scramble PCR'
echo "+++ Starting Qubes..."
exec kexec -e
echo '+++ Building initrd'
( cd "$INITRD_DIR" ; find . | cpio -H newc -o ) > "$SECRET_CPIO"
cat "$INITRD" >> "$SECRET_CPIO"
/bin/qubes-boot "$XEN" "$KERNEL" "$SECRET_CPIO"
recovery "Something failed..."

81
initrd/bin/qubes-update Executable file
View File

@ -0,0 +1,81 @@
#!/bin/sh
# Update the /boot partition signatures
set -o pipefail
. /etc/functions
XEN="$1"
KERNEL="$2"
INITRD="$3"
BOOT_HASHES="/boot/boot.hashes"
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
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
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"
sha256sum \
"$XEN" \
"$KERNEL" \
"$INITRD" \
"/tmp/counter-$TPM_COUNTER" \
| tee "$BOOT_HASHES"
for tries in 1 2 3; do
if gpg --detach-sign -a "$BOOT_HASHES"; then
mount -o ro,remount /boot
exit 0
fi
done
warn "$BOOT_HASHES: Unable to sign boot hashes"
mount -o ro,remount /boot
exit 1

View File

@ -1,34 +1,48 @@
#!/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.
TPM_INDEX=3
TPM_SIZE=312
die() { echo >&2 "$@"; exit 1; }
warn() { echo >&2 "$@"; }
. /etc/functions
mkdir -p /tmp/secret
sealed_file="/tmp/secret/sealed.key"
key_file="$1"
if [ -z "$key_file" ]; then
key_file=/tmp/secret.key
fi
read -s -p "Disk encryption password: " tpm_password
echo
if [ -z "$key_file" ]; then
key_file="/tmp/secret/secret.key"
fi
tpm nv_readvalue \
-in "$TPM_INDEX" \
-sz "$TPM_SIZE" \
-of /tmp/sealed \
-of "$sealed_file" \
|| die "Unable to read key from TPM NVRAM"
tpm unsealfile \
-if /tmp/sealed \
-of "$key_file" \
-pwdd "$tpm_password" \
-hk 40000000 \
|| die "Unable to unseal disk encryption key"
for tries in 1 2 3; do
tpm_password=
while [ -z "$tpm_password" ]; do
unseal-totp || die "TOTP code generation failed"
rm -f /tmp/sealed
read -s -p "Disk unlock password: " tpm_password
echo
done
exit 0
if tpm unsealfile \
-if "$sealed_file" \
-of "$key_file" \
-pwdd "$tpm_password" \
-hk 40000000 \
; then
rm -f /tmp/secret/sealed
exit 0
fi
pcrs
warn "Unable to unseal disk encryption key"
done
die "Retry count exceeded..."

View File

@ -1,10 +1,19 @@
#!/bin/ash
# This is the very first script invoked by the Linux kernel and is
# running out of the ram disk. There are no fileysstems mounted.
# It is important to have a way to invoke a recovery shell in case
# the boot scripts are messed up, but also important to modify the
# PCRs if this happens to prevent the TPM disk keys from being revealed.
# First thing it is vital to mount the /dev and other system directories
mkdir /proc /sys /dev /tmp /boot /media 2>&- 1>&-
mount /dev
mount /proc
mount /sys
# Recovery shells will erase anything from here
mkdir -p /tmp/secret
# Setup our path
export PATH=/sbin:/bin
@ -17,50 +26,37 @@ fi
hwclock -l -s
# Read the system configuration parameters
. /config
. /etc/functions
. /etc/config
if [ -z "$CONFIG_TIMEOUT" ]; then
CONFIG_TIMEOUT=10
if [ ! -x "$CONFIG_BOOTSCRIPT" ]; then
recovery 'Boot script missing? Entering recovery shell'
# just in case...
tpm extend -ix 4 recovery
exec /bin/ash
fi
while true; do
boot_option=
# Give the user a second to enter a recovery shell
read \
-t "1" \
-p "Press 'r' for recovery shell: " \
-n 1 \
boot_option
echo
# Verify the user's TPM secret
echo "TPM TOTP:"
if ! unsealtotp.sh ; then
echo '!!!!!'
echo '!!!!! TPM TOTP secret not found.'
echo '!!!!! This firmware can not be trusted.'
echo '!!!!! Entering recovery shell'
echo '!!!!!'
tpm extend -ix 4 -ic "tpm-failure"
exec /bin/ash
fi
if [ "$boot_option" = "r" ]; then
# Start an interactive shell
recovery 'User requested recovery shell'
# just in case...
tpm extend -ix 4 recovery
exec /bin/ash
fi
# Secret decrypted ok, so prompt for a next step
read \
-t "$CONFIG_TIMEOUT" \
-p "Enter for normal boot or 'r' for recovery shell: " \
-n 1 \
boot_option
echo '***** Normal boot'
exec "$CONFIG_BOOTSCRIPT"
if [ "$boot_option" = "r" ]; then
# Start an interactive shell
echo '***** Starting recovery shell'
tpm extend -ix 4 -ic "recovery"
exec /bin/ash
fi
if [ "$boot_option" = "" ]; then
if [ ! -x "$CONFIG_BOOTSCRIPT" ]; then
echo '!!!!! Boot script missing? Entering recovery shell'
tpm extend -ix 4 -ic "boot-failure"
exec /bin/ash
fi
echo '***** Normal boot'
tpm extend -ix 4 -ic "normal-boot"
exec "$CONFIG_BOOTSCRIPT"
fi
done
# We should never reach here, but just in case...
recovery 'Boot script failure? Entering recovery shell'
# belts and suspenders, just in case...
tpm extend -ix 4 recovery
exec /bin/ash

View File

@ -1,107 +0,0 @@
#!/bin/sh
# /boot/boot.sh -- Startup Qubes
#
# The signature on this script will be verified by the ROM,
# and this script lives on the /boot partition to allow
# the system owner to change the specific Qubes boot parameters
#
# This depends on the PCR 4 being "normal-boot":
# f8fa3b6e32e7c6fe04c366e74636e505b28f3b0d
# which is only set if the top level /init script has started
# without user intervention or dropping into a recovery shell.
#
# To sign this script and the other bootable components:
#
# gpg -a --sign --detach-sign boot.sh
#
XEN=/boot/xen-4.6.4.heads
INITRD=/boot/initramfs-4.4.14-11.pvops.qubes.x86_64.img
KERNEL=/boot/vmlinuz-4.4.14-11.pvops.qubes.x86_64
recovery() {
echo >&2 "!!!!! $@"
rm -f /tmp/secret.key /initrd.gz
tpm extend -ix 4 -ic recovery
echo >&2 "!!!!! Starting recovery shell"
exec /bin/ash
}
. /config
echo "+++ Checking $XEN"
gpgv "${XEN}.asc" "${XEN}" \
|| recovery 'Xen signature failed'
echo "+++ Checking $INITRD"
gpgv "${INITRD}.asc" "${INITRD}" \
|| recovery 'Initrd signature failed'
echo "+++ Checking $KERNEL"
gpgv "${KERNEL}.asc" "${KERNEL}" \
|| recovery 'Kernel signature failed'
# Activate the dom0 group
lvm vgchange -a y "$CONFIG_QUBES_VG" \
|| recovery "$CONFIG_QUBES_VG: LVM volume group activate failed"
# Measure the LUKS headers before we unseal the disk key
qubes-measure-luks /dev/$CONFIG_QUBES_VG/* \
|| recovery "LUKS measure failed"
# get the UUID of the root file system
# busybox blkid doesn't have a "just the UUID" option
ROOT_UUID=`blkid /dev/$CONFIG_QUBES_VG/00 | cut -d\" -f2`
if [ -z "$ROOT_UUID" ]; then
recovery "$CONFIG_QUBES_VG/00: No UUID for /"
fi
echo "$CONFIG_QUBES_VG/00: UUID=$ROOT_UUID"
# Attempt to unseal the disk key from the TPM
# should we give this some number of tries?
unseal-key \
|| recovery 'Unseal disk key failed. Starting recovery shell'
# 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/initrd
echo '+++ Unpacking initrd'
mkdir -p $INITRD_DIR/etc
#dd if="$INITRD" bs=256 count=52 | ( cd $INITRD_DIR ; cpio -i )
#dd if="$INITRD" bs=256 skip=52 | zcat | ( cd $INITRD_DIR ; cpio -i )
mv /tmp/secret.key $INITRD_DIR/
## Update the /etc/crypttab in the initrd and install our key
## This is no longer required, now that dom0 /etc/crypttab has
## the /secret.key specified.
#for dev in /dev/$CONFIG_QUBES_VG/*; do
# uuid=`blkid $dev | cut -d\" -f2`
# echo luks-$uuid /dev/disk/by-uuid/$uuid /secret.key
#done > $INITRD_DIR/etc/crypttab
echo '+++ Repacking initrd'
( cd $INITRD_DIR ; find . | cpio -H newc -o ) > /initrd.cpio
cat "$INITRD" >> /initrd.cpio
# command line arguments are include in the signature on this script,
echo '+++ Loading kernel and initrd'
kexec \
-l \
--module "${KERNEL} root=/dev/mapper/luks-$ROOT_UUID ro rd.qubes.hide_all_usb" \
--module /initrd.cpio \
--command-line "no-real-mode reboot=no" \
"${XEN}" \
|| recovery "kexec load failed"
# Last step is to override PCR 4 so that user can't read the key
tpm extend -ix 4 -ic qubes \
|| recovery 'Unable to scramble PCR'
echo "+++ Starting Qubes..."
sleep 2
exec kexec -e

View File

@ -1,10 +0,0 @@
-----BEGIN PGP SIGNATURE-----
iQEVAwUAWOQaag+UgFLd7L5oAQIYMQgA1W3mnxsd6Bln0ipvZtITN0cAoAdsnuG/
Kt/2Usabu7lzdYNpBp9h+jmGDj1Jg+5wvKBXgYQXiPG0TuPNXqeih+X1NJbeXO3S
BF6PXPEHkZlU7kDXUiPHVF9Hy2T6Kw45SQ5pEctATDYjO8SL/lVuxGRSXSiBdyW0
PLEOHmVNh5C9LNtoGZmmRf8BkVpNc7LCZIkDWj29wNypaxBzv1AQmWBWTvWTSK3D
CkFW10DbF3nJZNrPtTY4EOV2fynRsCZYN/O3ZyN5iZ9kAm8WXWcjqMBB7K/bE3dw
KUb3E0pwyT+uAknT1pXPbcyx8hq6mvX0Fp+46UYovgx5KU+yQunItw==
=0kHU
-----END PGP SIGNATURE-----