diff --git a/config/x230-qubes.config b/config/x230-qubes.config index eac343d1..c2e6658b 100644 --- a/config/x230-qubes.config +++ b/config/x230-qubes.config @@ -1,5 +1,6 @@ # Configuration for a x230 running Qubes OS BOARD=x230 + CONFIG_CRYPTSETUP=y CONFIG_FLASHROM=y CONFIG_GPG=y @@ -15,3 +16,9 @@ CONFIG_XEN=y CONFIG_LINUX_USB=y CONFIG_LINUX_E1000E=y + +CONFIG_BOOTSCRIPT=/bin/qubes-init + +# Disks encrypted by the TPM LUKS key +CONFIG_QUBES_BOOT_DEV="/dev/sda1" +CONFIG_QUBES_DEVS="/dev/sda2 /dev/sda3 /dev/sda5" diff --git a/initrd/bin/mount-usb b/initrd/bin/mount-usb new file mode 100755 index 00000000..afe5c309 --- /dev/null +++ b/initrd/bin/mount-usb @@ -0,0 +1,18 @@ +#!/bin/sh +# Mount a USB device +die() { echo >&2 "!!!!! $@"; exit 1; } + +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 [ ! -d /media ]; then + mkdir /media +fi + +mount -o ro $1 /media diff --git a/initrd/bin/qubes-init b/initrd/bin/qubes-init new file mode 100755 index 00000000..7c259b2e --- /dev/null +++ b/initrd/bin/qubes-init @@ -0,0 +1,65 @@ +#!/bin/sh +# Boot a Qubes installation that has already been setup. +# 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. + +recovery() { + echo >&2 "!!!!! $@" + rm -f /tmp/secret.key + tpm extend -ix 4 -if recovery + + 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' + +# 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 + +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' + +# Measure the LUKS headers before we unseal the disk key +/bin/qubes-measure-luks $CONFIG_QUBES_DEVS \ + || recovery "LUKS measure failed" + +# 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' + +# command line arguments are in the hash, so they are "correct". +kexec \ + -l \ + --module "${KERNEL} root=LABEL=root rhgb" \ + --module "${INITRD}" \ + --command-line "no-real-mode reboot=no console=vga dom0_mem=min:1024M dom0_mem=max:4096M" \ + "${XEN}" \ +|| recovery "kexec load failed" + +# Last step is to override PCR 6 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 diff --git a/initrd/bin/qubes-measure-luks b/initrd/bin/qubes-measure-luks new file mode 100755 index 00000000..a9acadf3 --- /dev/null +++ b/initrd/bin/qubes-measure-luks @@ -0,0 +1,14 @@ +#!/bin/sh +# Measure all of the luks disk encryption headers into +# a PCR so that we can detect disk swap attacks. + +die() { echo >&2 "$@"; exit 1; } + +# Measure the luks headers into PCR 6 +for dev in "$@"; do + cryptsetup luksDump $dev \ + || die "$dev: Unable to measure" +done > /tmp/luksDump.txt + +tpm extend -ix 6 -if /tmp/luksDump.txt \ +|| die "Unable to extend PCR" diff --git a/initrd/bin/seal-key b/initrd/bin/seal-key index 29f182fb..567db4f8 100755 --- a/initrd/bin/seal-key +++ b/initrd/bin/seal-key @@ -7,15 +7,36 @@ TPM_INDEX=3 TPM_SIZE=312 KEY_FILE=/tmp/secret.key -die() { echo >&2 "$@"; exit 1; } +. /config + +die() { + echo >&2 "$@"; + rm -f /tmp/secret.key /tmp/recovery.key /tmp/sealed + exit 1; +} warn() { echo >&2 "$@"; } -read -s -p "New key password: " key_password +# 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" > /tmp/recovery.key +echo + +for dev in $CONFIG_QUBES_DEVS; do + echo "++++++ $dev: Removing old key slot" + cryptsetup luksKillSlot \ + --key-file /tmp/recovery.key \ + $dev 1 \ + || warn "$dev: ignoring problem" +done + +read -s -p "New disk decryption password for booting: " key_password echo read -s -p "Repeat password: " key_password2 echo -if [ "$key_password" -ne "$key_password2" ]; then +if [ "$key_password" != "$key_password2" ]; then die "Key passwords do not match" fi @@ -27,11 +48,28 @@ dd \ 2>/dev/null \ || die "Unable to generate 128 random bytes" +for dev in $CONFIG_QUBES_DEVS; do + echo "+++++ $dev: Adding key" + cryptsetup luksAddKey \ + --key-file /tmp/recovery.key \ + --key-slot 1 \ + $dev "$KEY_FILE" \ + || die "$dev: Unable to add key" +done -# Use the current values of the PCRs, which will be read +# Now that we have setup the new keys, measure the PCRs +/bin/qubes-measure-luks $CONFIG_QUBES_DEVS \ + || die "Unable to measure the LUKS headers" + +# 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. +# 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"). -# should this read the storage root key? -sealfile2 \ +tpm sealfile2 \ -if "$KEY_FILE" \ -of /tmp/sealed \ -pwdd "$key_password" \ @@ -40,10 +78,12 @@ sealfile2 \ -ix 1 X \ -ix 2 X \ -ix 3 X \ - -ix 4 X \ + -ix 4 f8fa3b6e32e7c6fe04c366e74636e505b28f3b0d \ + -ix 5 0000000000000000000000000000000000000000 \ + -ix 6 X \ || die "Unable to seal secret" -rm "$KEY_FILE" +rm -f "$KEY_FILE" # to create an nvram space we need the TPM owner password @@ -51,24 +91,23 @@ rm "$KEY_FILE" # # The permissions are 0 since there is nothing special # about the sealed file -physicalpresence -s \ +tpm physicalpresence -s \ || warn "Warning: Unable to assert physical presence" read -s -p "TPM Owner password: " tpm_password echo -nv_definespace \ +tpm nv_definespace \ -in $TPM_INDEX \ -sz $TPM_SIZE \ -pwdo "$tpm_password" \ -per 0 \ -|| die "Warning: Unable to define NVRAM space; trying anyway" +|| warn "Warning: Unable to define NVRAM space; trying anyway" -nv_writevalue \ +tpm nv_writevalue \ -in $TPM_INDEX \ -if /tmp/sealed \ || die "Unable to write sealed secret to NVRAM" rm /tmp/sealed - diff --git a/initrd/bin/unseal-key b/initrd/bin/unseal-key index 387cd135..6783f5c1 100755 --- a/initrd/bin/unseal-key +++ b/initrd/bin/unseal-key @@ -13,22 +13,22 @@ if [ -z "$key_file" ]; then key_file=/tmp/secret.key fi -read -s -p "Encryption password: " tpm_password +read -s -p "Disk encryption password: " tpm_password echo -nv_readvalue \ +tpm nv_readvalue \ -in "$TPM_INDEX" \ -sz "$TPM_SIZE" \ -of /tmp/sealed \ || die "Unable to read key from TPM NVRAM" -unsealfile \ +tpm unsealfile \ -if /tmp/sealed \ -of "$key_file" \ -pwdd "$tpm_password" \ -hk 40000000 \ || die "Unable to unseal disk encryption key" -rm /tmp/sealed - +rm -f /tmp/sealed +exit 0 diff --git a/initrd/init b/initrd/init index 1e3466e9..875de9ed 100755 --- a/initrd/init +++ b/initrd/init @@ -1,6 +1,6 @@ #!/bin/ash # First thing it is vital to mount the /dev and other system directories -mkdir /proc /sys /dev /tmp /boot 2>&- 1>&- +mkdir /proc /sys /dev /tmp /boot /media 2>&- 1>&- mount -t devtmpfs none /dev mount -t proc none /proc mount -t sysfs none /sys