Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Qubes OS support #156

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ build
linux-*
initramfs/response/transport.*
initramfs/response/*.hash
pkgs/*
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION ?= 0.8
VERSION ?= $(file <version)

GIT_DIRTY := $(shell if git status -s >/dev/null ; then echo dirty ; else echo clean ; fi)
GIT_HASH := $(shell git rev-parse HEAD)
Expand Down Expand Up @@ -627,4 +627,3 @@ qemu-server: \
-kill `cat $(TPM_PID)`
@-$(RM) "$(TPM_PID)" "$(TPMSOCK)"


16 changes: 16 additions & 0 deletions Makefile.builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ifeq ($(PACKAGE_SET),dom0)
RPM_SPEC_FILES := qubes-safeboot.spec
endif

NO_ARCHIVE := 1

VERSION ?= $(file <$(ORIG_SRC)/version)

SOURCES = qubes-safeboot-$(VERSION).tar.gz

SOURCE_COPY_IN := $(SOURCES)

qubes-safeboot-$(VERSION).tar.gz:
tar --xform='s:$(ORIG_SRC)/qubes:qubes-safeboot-$(VERSION):' -czhf $(CHROOT_DIR)$(DIST_SRC)/qubes-safeboot-$(VERSION).tar.gz $(ORIG_SRC)/qubes

# vim: set ft=make:
2 changes: 1 addition & 1 deletion functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ efivar_write() {

efivar_read() {
efivar_setup "${1:-}"
tail -c +5 < "$var"
cat "$var" | tail -c +5
}

efiboot_entry() {
Expand Down
69 changes: 69 additions & 0 deletions qubes-safeboot.spec.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Name: qubes-safeboot
Version: @VERSION@
Release: 1%{?dist}
Summary: Boot Qubes OS more safely
License: GPLv2 and LGPLv2 and BSD

URL: https://github.com/osresearch/safeboot
#Source0: %{url}/archive/refs/tags/release-{version}.tar.gz
Source0: %{name}-%{version}.tar.gz

BuildRequires: systemd-rpm-macros

Requires: efitools%{_isa}
Requires: sbsigntools%{_isa}
Requires: tpm2-tss%{_isa}
Requires: tpm2-tools%{_isa}
Requires: binutils%{_isa}

%description
Makes the Qubes OS boot process slightly safer by enabling UEFI Secure Boot, with packing xen, config, kernel and initrd to unified EFI binary and signing it with personal key

%prep
%setup

%install
install -m 0755 -D %{_builddir}/%{name}-%{version}/safeboot -t %{buildroot}%{_sbindir}
install -m 0644 -D %{_builddir}/%{name}-%{version}/safeboot.conf -t %{buildroot}%{_sysconfdir}/safeboot/
install -m 0644 -D %{_builddir}/%{name}-%{version}/functions.sh -t %{buildroot}/usr/lib/safeboot/
install -m 0755 -D %{_builddir}/%{name}-%{version}/qubes-hooks/kernel-safeboot.install %{buildroot}/usr/lib/kernel/install.d/99-qubes-safeboot.install
install -m 0755 -D 90safeboot/* -t %{buildroot}/usr/lib/dracut/modules.d/90safeboot/

install -m 0644 -D systemd/system/qubes-safeboot-unseal.service -t %{buildroot}%{_unitdir}/
install -m 0755 -d %{buildroot}%{_unitdir}/initrd.target.wants
ln -s ../qubes-safeboot-unseal.service %{buildroot}%{_unitdir}/initrd.target.wants/qubes-safeboot-unseal.service

# symlink since we build these packages
mkdir -p %{buildroot}%{_bindir}
ln -s sbsign %{buildroot}%{_bindir}/sbsign.safeboot
ln -s sign-efi-sig-list %{buildroot}%{_bindir}/sign-efi-sig-list.safeboot


%triggerin -- xen-hypervisor
if [ -f /boot/efi/EFI/qubes/qubes.efi ]; then
/usr/sbin/safeboot qubes-sign
if grep -q LINUX_TARGET=qubes /etc/safeboot/local.conf; then
/usr/sbin/safeboot pcrs-sign
fi
fi

%triggerin -- linux-firmware
if [ -f /boot/efi/EFI/qubes/qubes.efi ]; then
/usr/sbin/safeboot qubes-sign
if grep -q LINUX_TARGET=qubes /etc/safeboot/local.conf; then
/usr/sbin/safeboot pcrs-sign
fi
fi

%files
%config %{_sysconfdir}/safeboot/safeboot.conf
%{_sbindir}/safeboot
/usr/lib/safeboot/functions.sh
/usr/lib/kernel/install.d/99-qubes-safeboot.install
/usr/lib/dracut/modules.d/90safeboot/
%{_unitdir}
%{_bindir}/sbsign.safeboot
%{_bindir}/sign-efi-sig-list.safeboot

%changelog
@CHANGELOG@
46 changes: 46 additions & 0 deletions qubes/90safeboot/module-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash

check() {
which tpm2 >/dev/null 2>&1 || return 1
}


#depends() {
#}


install() {
# Get Safeboot variables
local DIR=/etc/safeboot
[ -f "$dracutsysrootdir"$DIR/safeboot.conf ] && . "$dracutsysrootdir"$DIR/safeboot.conf || :
[ -f "$dracutsysrootdir"$DIR/local.conf ] && . "$dracutsysrootdir"$DIR/local.conf || :

inst_script "$moddir"/qubes-safeboot-unseal /sbin/qubes-safeboot-unseal
inst_simple "$dracutsysrootdir"$DIR/safeboot.conf $DIR/safeboot.conf
inst_simple "$dracutsysrootdir"$DIR/local.conf $DIR/local.conf
inst_simple "$dracutsysrootdir"${CERT/.pem/.pub} ${CERT/.pem/.pub}
inst_simple "$dracutsysrootdir"/usr/lib/safeboot/functions.sh $DIR/functions.sh

inst $systemdsystemunitdir/cryptsetup-pre.target

dracut_install \
cat \
cut \
chmod \
chattr \
mount \
pidof \
sha256sum \
tail \
time \
touch \
tpm2 \
umount \
xxd

inst_libdir_file "libtss2-tcti-device.so*"

dracut_install \
$systemdsystemunitdir/qubes-safeboot-unseal.service \
$systemdsystemunitdir/initrd.target.wants/qubes-safeboot-unseal.service
}
115 changes: 115 additions & 0 deletions qubes/90safeboot/qubes-safeboot-unseal
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/bin/bash
# This is run in the initramfs context, not in the normal user space.
# The boot mode should have been extended in the start of the initramfs.
#
# It attempts to unseal the key from the TPM based on the PCRS passed
# on the command line using direct access since there is no resource
# manager.
#
# If successful, PCR14 will be extended to prevent later stages from
# retrieving the decryption key. The key is stored in a kernel key
# ring, so it should not be accessible to even a root user.
#
# If the unsealing fails, fall back to asking for the user's recovery key.
#
# turn off "echo flags are undefined" and external shell scripts
# shellcheck disable=SC2039 disable=SC1091

PCRS=0
BOOTMODE_PCR=14
TPM_UNSEALED_SECRET="$TMP/safeboot-keyfile"
MODE=qubes
if [ -z "$DIR" ]; then
DIR="/etc/safeboot"
fi

for script in \
safeboot.conf \
local.conf \
functions.sh \
; do
if [ -r "$DIR/$script" ]; then
. "$DIR/$script" || warn "$DIR/$script: failed to source"
fi
done

# Override die to extend the boot mode PCR to indicate the failure
die() {
echo >&2 "$@"
echo -n bootfail | tpm2_extend "$BOOTMODE_PCR"
touch "/tmp/unseal-failed"
exit 1
}

# shellcheck disable=SC2013
echo -n $MODE | tpm2_extend "$BOOTMODE_PCR"
warn "TPM mode=$MODE pcrs=$PCRS $BOOTMODE_PCR"

tpm2 pcrread >&2 \
'sha256:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16'

#
# Try to unseal the secret using the TPM,
# the current NV version and optional user PIN.
#
# Extract the signature from the UEFI variable, skipping
# the four-byte UEFI variable header.

VERSION="$(tpm2 nvread "$TPM_NV_VERSION" | bin2hex)"
TPM_SESSION_TYPE=policy
TPM_POLICY_SIG="$TMP/pcr.policy.sig"

efivar_read "$PCR_SIGNATURE" > "$TPM_POLICY_SIG"

tpm2_create_policy "" "$VERSION"

tpm2 flushcontext --transient-object

#
# Attempt and unseal, and if successful, write the key
# to stdout as well as extending the boot mode PCR
#
tpm2_unseal()
{
PIN="$1"
tpm2 unseal \
--auth "session:$TMP/session.ctx$PIN" \
--object-context "$TPM_SEALED_HANDLE" \
--output "$TPM_UNSEALED_SECRET" \
|| return $?

# Successfully unsealed, extend the bootmode PCR
warn "TPM disk key unsealed"
echo -n postboot | tpm2_extend "$BOOTMODE_PCR"
exit 0
}

if [ "$SEAL_PIN" != "1" ]; then
tpm2_unseal ""
else
for tries in 1 2 3; do
while true; do
# Use the askpass program to try to get a pin
# retrieve a tpmtotp attestation so that the user knows
# that the firmware is unmodified and that it is safe to
# enter their credentials.
#totp="$(/usr/sbin/tpm2-totp --time calculate || echo TPM TOTP FAILED)"
msg="Enter Safeboot Password (Try $tries)

Enter unseal PIN for $CRYPTTAB_SOURCE ($CRYPTTAB_NAME): "

PIN=$(systemd-ask-password --timeout=0 "$msg" )

if [ "$PIN" != "" ]; then
break
fi
done

# try to unseal with the provided PIN
tpm2_unseal "+$PIN"
done
fi

# if we ended up here, things are bad.
# The system will re-run the script to try to use the recovery key
die "UNSEALING FAILED"
1 change: 1 addition & 0 deletions qubes/functions.sh
8 changes: 8 additions & 0 deletions qubes/qubes-hooks/kernel-safeboot.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

if [ -f /boot/efi/EFI/qubes/qubes.efi ]; then
safeboot qubes-sign
if grep -q LINUX_TARGET=qubes /etc/safeboot/local.conf; then
/usr/sbin/safeboot pcrs-sign
fi
fi
1 change: 1 addition & 0 deletions qubes/safeboot
1 change: 1 addition & 0 deletions qubes/safeboot.conf
16 changes: 16 additions & 0 deletions qubes/systemd/system/qubes-safeboot-unseal.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Unit]
Description=Qubes Safeboot unsealing
DefaultDependencies=no
Wants=cryptsetup-pre.target
Before=cryptsetup-pre.target
After=plymouth-start.service
ConditionKernelCommandLine=rd.luks.key=/safeboot-keyfile

[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/sbin/qubes-safeboot-unseal
StandardInput=null
StandardOutput=tty
StandardError=journal+console
TimeoutStartSec=300
Loading