Foreword
This blog post isn’t meant to be a definitive guide about Secure Boot in Debian. The idea is to give some context about the boot sequence on the PC architecture, about the Secure Boot technology, and about some implementation details in Debian.
Short on time? Jump to current status of Secure Boot in Debian!
How does a system boot?
Let’s start with how the PC architecture gets booted: once upon a time, the BIOS was responsible for locating boot devices and trying them in a configurable order. One would usually configure a bootable disk with a bootloader in its MBR (e.g. LILO ou GRUB), which would then check its own settings, and boot a Linux kernel passing parameters and an optional initramfs.
Things changed “a little” with the UEFI technology, aiming at replacing the BIOS (and at being usable on other architectures like ARM). The initial firmware comes with many more features, and with recommended or required settings, like the ESP partition (usually mounted on /boot/efi
) which makes it possible to exchange data between the UEFI-level implementation and the installed system. Regarding booting, there’s a boot manager implemented at the firmware level, which can be configured from the operating system (through the efibootmgr
command or through some EFI libraries).
Here’s a shortened efibootmgr -v
example showing debian
as the default operating system, with the standardized EFI/debian/grubx64.efi
path that can be found in the ESP (which is a FAT filesystem, hence the notation with backslashes), and with two PXE-based fallbacks:
$ sudo efibootmgr -v BootCurrent: 0000 Timeout: 0 seconds BootOrder: 0000,0001,0002 Boot0000* debian HD(1,GPT,0d5445e2-92b4-4a31-9629-c6c796b3e07c,0x800,0xf3800)/File(\EFI\debian\grubx64.efi) Boot0001* IBA GE Slot 00C8 v1381 BBS(Network,,0x0)AMBO Boot0002* IBA GE Slot 0200 v1321 BBS(Network,,0x0)AMBO
UEFI-enabled firmwares usually make it possible to use either “UEFI booting” or “Legacy BIOS” (also called CSM).
Booting a Linux kernel with UEFI instead of Legacy BIOS usually leads to some extra information getting exposed through /sys
, namely under the /sys/firmware/efi
directory. In particular, the efivars.ko
module makes it possible to access variables that are stored in NVRAM.
What is Secure Boot?
Secure Boot is a technology that makes it possible to check and possibly trust the boot chain. The initial firmware would check a digital signature on the bootloader; the kernel getting loaded and its modules would get a similar check. The idea is to double check that what is being run as the core of the operating system is the expected system, and that no rogue operations have been taking place. Having support for Secure Boot was a requirement for hardware targetting conformance with the Windows 8 specifications, so Secure Boot enabled devices have been spreading over the past few years…
Digital signatures are all good but who should a firmware trust? Given the market was mainly about machines getting sold with Windows, the firmware would be configured to trust keys from Microsoft by default. Some firmware implementations make it possible to enroll other keys, but that’s not supported by all devices…
Until Linux distributions have designed and implemented a plan to support Secure Boot (getting a bootloader that can check and start a Linux kernel in a suitable fashion, and signed by a trusted key), the usual solution to run a Linux installer would be to fiddle with the UEFI settings, turning Secure Boot off entirely. This can be cumbersome, as some firmware implementations only show this option after an administrator password has been set…
What does the plan look like for Debian?
Secure Boot support has been in the works for quite a long time, and there were many design issues to iron out, including some infrastructure-side changes regarding digital signatures.
Here’s a very quick summary:
- A rather small and auditable component called
shim
is the initial bootloader. It gets installed into the ESP alongside GRUB:/boot/efi/EFI/debian/shimx64.efi
. It ends up as one of theefibootmgr
entries as shown in introduction. - This
shim
component is responsible for checking the actual bootloader, and loading it in turn (chainloading):/boot/efi/EFI/debian/grubx64.efi
. - Then GRUB will load the Linux kernel, as usual.
The first step is the firmware→shim chainloading. Fortunately, only shim
needs to get a signature from Microsoft, so that the machine’s firmware can validate the shim component getting loaded. Being a minimal and auditable component means it shouldn’t need to get updates too often, which should keep the number of roundtrips to Microsoft (to get a new signature) rather low.
The shim→GRUB chainloading is done if the signature on GRUB is validated against the Debian test key or the Debian production key (more on that below). Ditto for the GRUB→Linux kernel chainloading.
From a packaging point of view, these digital signatures are a bit of a nightmare: one wants to be able to build packages on autobuilders (on build daemons, or buildds), possibly in a reliable and reproducible fashion (see the Reproducible Builds initiative). That’s why some modifications to the grub2
and linux
source packages have been floating around for some time.
As of early March 2019, the state of the shim
and shim-signed
packages in Debian unstable was a bit complicated: the shim
package was updated with new code while the matching signature from Microsoft wasn’t available for inclusion in an updated shim-signed
package yet… Steve McIntyre and Cyril have been working hard exploring various solutions allowing to get back to a set of matching packages (#922179). Some difficulties encountered in doing so highlight the need for reproducible builds on the one hand and for a carefully designed supply chain on the other hand. This is why the next section is focussed on the -signed
and -template
packages that are in place for the grub2
and linux
source packages. As of April 2019, the binaries produced by the shim
source package have been reorganized to match the setup used by grub2
and linux
.
How are -signed
and -template
packages handled in Debian?
Quick look into GRUB
Let’s start by looking at the binary packages produced by the grub2
source package. There are 48 binaries as of version 2.02+dfsg1-17
so let’s only list some of them:
grub-common
grub-efi
grub-efi-amd64
grub-efi-amd64-bin
grub-efi-amd64-signed-template
grub-efi-arm64
grub-efi-arm64-bin
grub-efi-arm64-signed-template
grub-pc
grub-pc-bin
grub2
grub2-common
The set of packages to be installed would usually be decided by the grub-installer
component of the Debian Installer; a machine installed with legacy BIOS could have this set of GRUB packages (taken from Debian Stretch):
grub-common
grub-pc
grub-pc-bin
grub2-common
… with grub-pc
pulling the relevant tools in grub-pc-bin
and both of them relying on the grub*-common
packages.
On an EFI installation (also Debian Stretch), the set would look like this:
grub-common
grub-efi
grub-efi-amd64
grub-efi-amd64-bin
grub2-common
Of course, EFI is available on more than just the amd64
architecture, so the grub-efi
package pulls the right grub-efi-$ARCH
, which then pulls binaries and the common files.
Signatures for GRUB
All binary packages mentioned above are built on autobuilders, and contain no signatures. But some of them are special, namely:
grub-efi-amd64-signed-template
grub-efi-arm64-signed-template
grub-efi-ia32-signed-template
Those are indeed binary packages produced by the grub2
source package, but they are effectively meant to be the source package for the signed binary packages!
Let’s look at the contents of grub-efi-amd64-signed-template_2.02+dfsg1-17_amd64.deb
(letting the usual /usr/share/doc
and /usr/share/lintian
directories aside):
/usr/share/code-signing/grub-efi-amd64-signed-template/ /usr/share/code-signing/grub-efi-amd64-signed-template/files.json /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/ /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/ /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/README.source /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/bug-control /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/changelog /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/compat /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/control /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/copyright /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/rules /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/source/ /usr/share/code-signing/grub-efi-amd64-signed-template/source-template/debian/source/format
There seems to be a source package tree there, with metadata gathered in a debian
directory.
Let’s check the top-level files.json
file that describes which files need to be signed and how:
{
"version": "2",
"packages": {
"grub-efi-amd64-bin": {
"trusted_certs": [],
"files": [
{"sig_type": "efi", "file": "usr/lib/grub/x86_64-efi/monolithic/gcdx64.efi"},
{"sig_type": "efi", "file": "usr/lib/grub/x86_64-efi/monolithic/grubnetx64.efi"},
{"sig_type": "efi", "file": "usr/lib/grub/x86_64-efi/monolithic/grubx64.efi"}
]
}
}
}
Let’s check what the debian/control
file looks like:
Source: grub-efi-amd64-signed
Section: admin
Priority: optional
Maintainer: GRUB Maintainers <REDACTED>
Uploaders: Individual Developers <REDACTED>
Standards-Version: 3.9.8
Build-Depends: debhelper (>= 10.1~),
sbsigntool [amd64 arm64 i386],
grub-efi-amd64-bin (= 2.02+dfsg1-17)
Rules-Requires-Root: no
Package: grub-efi-amd64-signed
Architecture: amd64
Depends: grub-common (= 2.02+dfsg1-17)
Recommends: shim-signed [amd64]
Built-Using: grub2 (= 2.02+dfsg1-17)
Description: GRand Unified Bootloader, version 2 (amd64 UEFI signed by Debian)
GRUB is a portable, powerful bootloader. This version of GRUB is based on a
cleaner design than its predecessors, and provides the following new features:
.
- Scripting in grub.cfg using BASH-like syntax.
- Support for modern partition maps such as GPT.
- Modular generation of grub.cfg via update-grub. Packages providing GRUB
add-ons can plug in their own script rules and trigger updates by invoking
update-grub.
.
This package contains the binaries signed by the Debian UEFI CA to be used by
shim-signed.
Let’s highlight a few things:
- The name is the same for both the source package and the binary package:
grub-efi-amd64-signed
. - The binary has a
Built-Using
field that ensures the archive retains the relevant version of the actual source package. - Besides the usual
debhelper
tool, the build dependencies are the binary package which ships the files that need to be signed (grub-efi-amd64-bin
), and the needed tool (sbsigntool
). The latter ships thesbattach
command. - The
shim-signed
package is listed inRecommends
.
The last point explains why the grub-installer
component of the Debian Installer doesn’t even need to list any *-signed
packages, the following chain of dependencies takes care of installing the needed packages for the initial chainloading: grub-efi
→ grub-efi-amd64
→ grub-efi-amd64-bin
, the last one recommending grub-efi-amd64-signed
, which in turns recommends shim-signed
.
Let’s check what the debian/rules
file looks like:
#!/usr/bin/make -f
SIG_DIR := debian/signatures/grub-efi-amd64-bin
%:
dh $@
override_dh_auto_install:
set -e ; \
find "$(SIG_DIR)" -name '*.sig' -printf '%P\n' | \
while read sig; do \
dst="debian/tmp/$${sig%/monolithic/*}-signed/$${sig##*/}ned" ; \
install -m 0755 -d "$${dst%/*}" ; \
install -m 0644 "/$${sig%.sig}" "$$dst" ; \
sbattach --attach "$(SIG_DIR)/$$sig" "$$dst" ; \
done
override_dh_install:
dh_install --sourcedir=debian/tmp .
As mentioned in the SecureBoot/Discussion page of the Debian Wiki, signatures are added to source-template/debian/signatures/<original-binary-package-name>/<complete-path-name>.sig
when the -signed-template
file is processed by the code signing service (which won’t be detailed in depth in this article): This explains why there is a loop in the dh_auto_install
override, to attach the generated signatures to the actual files that were installed because of the build dependencies on grub-efi-amd64-bin
.
This leads to the following contents for the resulting grub-efi-amd64-signed
binary package:
/usr/lib/grub/x86_64-efi-signed/gcdx64.efi.signed /usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed
which are a signed version of the following files in the grub-efi-amd64-bin
binary package:
/usr/lib/grub/x86_64-efi/monolithic/gcdx64.efi /usr/lib/grub/x86_64-efi/monolithic/grubnetx64.efi /usr/lib/grub/x86_64-efi/monolithic/grubx64.efi
(There’s also an extra file both in the source template and in the binary package to instruct reportbug
which package to file bug report against but that’s really anecdotal.)
Wrapping up for GRUB
- The
grub2
source package generates many binary packages. - Those named
*-signed-template
are post-processed on a specific code signing service, with their contents being used to generate a source package that builds the final binary packages containing signed files. - Those signed files are the combination of files shipped in other binary packages, with a digital signature appended.
Signatures for the Linux kernel
The same mechanism is used for the linux
source package. It is slightly different because of the amount of binary packages that are built from this source package: 1194 as of version 4.19.28-2
! There can be various flavours and patchsets involved, for each supported architecture; plus many udebs (components to be used in the Debian Installer), explaining this high number.
One might have noticed that the linux-image-<ABI>
packages are no longer built by the linux
source package though. Let’s focus on amd64
again:
- The
linux
source package builds alinux-image-amd64-signed-template
binary package. - Using this
*-signed-template
binary package, many signed packages are generated, which include the following, familiar one:linux-image-4.19.0-4-amd64
. Those don’t come with a-signed
suffix, probably because changing their names in all packages and scripts related to the Linux kernel would have meant huge work for little benefit.
Let’s look at the linux-image-amd64-signed-template
binary package as of version 4.19.28-2
:
/usr/share/code-signing/linux-image-amd64-signed-template /usr/share/code-signing/linux-image-amd64-signed-template/files.json /usr/share/code-signing/linux-image-amd64-signed-template/source-template /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/changelog /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/compat /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/control /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/copyright /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-amd64.postinst /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-amd64.postrm /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-amd64.preinst /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-amd64.prerm /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-cloud-amd64.postinst /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-cloud-amd64.postrm /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-cloud-amd64.preinst /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-cloud-amd64.prerm /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-rt-amd64.postinst /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-rt-amd64.postrm /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-rt-amd64.preinst /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/linux-image-4.19.0-4-rt-amd64.prerm /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/README.source /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/rules /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/rules.gen /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/rules.real /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/source /usr/share/code-signing/linux-image-amd64-signed-template/source-template/debian/source/format
Some differences compared to the previous grub-efi-amd64-signed-template
package:
- There are various
debian/rules*
files. This is likely due to the fact some of them are generated even for the regularlinux
source package, as version numbers are mentioned there. - There are some maintainer scripts (post-installation, post-removal, etc.) for some of the generated binary packages.
- There are many binary packages built from this single template.
Let’s check the Source
stanza of the debian/control
file, leaving the 55 Package
entries aside for a moment:
Source: linux-signed-amd64
Section: kernel
Priority: optional
Maintainer: Debian Kernel Team <REDACTED>
Uploaders: Ben Hutchings <REDACTED>
Standards-Version: 4.1.1
Build-Depends:
debhelper (>= 10.1~),
rsync,
sbsigntool,
kernel-wedge (>= 2.99~),
linux-kbuild-4.19,
linux-support-4.19.0-4 (= 4.19.28-2),
linux-image-4.19.0-4-amd64-unsigned (= 4.19.28-2),
linux-image-4.19.0-4-cloud-amd64-unsigned (= 4.19.28-2),
linux-image-4.19.0-4-rt-amd64-unsigned (= 4.19.28-2)
Homepage: https://www.kernel.org/
Rules-Requires-Root: no
Vcs-Browser: https://salsa.debian.org/kernel-team/linux
Vcs-Git: https://salsa.debian.org/kernel-team/linux.git
Glancing at the build dependencies, there are 3 linux-image-*
binary involved, containing files that will need signatures. Both linux-kbuild-4.19
and linux-support-4.19.0-4
are likely to be use to make the Makefile machinery work, while kernel-wedge
is used to dispatch kernel modules into various binary packages.
Instead of looking at all 55 binary packages, let’s concentrate on some of them. Only 3 of them are deb
packages, while all others (named *-amd64-di
) are udeb
packages, for use in the Debian Installer.
linux-image-4.19.0-4-amd64
: usual, default package foramd64
.linux-image-4.19.0-4-rt-amd64
: this package features the Real Time patchset.linux-image-4.19.0-4-cloud-amd64
: this package is optimized for use in “cloud environments”.kernel-image-4.19.0-4-amd64-di
: this is the main kernel package for the Debian Installer, it contains the main Linux image but it doesn’t ship all kernel modules. A selection is shipped in the otherudebs
.ata-modules-4.19.0-4-amd64-di
,ext4-modules-4.19.0-4-amd64-di
,nic-wireless-modules-4.19.0-4-amd64-di
,usb-modules-4.19.0-4-amd64-di
, etc. each contain different sets of kernel modules that can be loaded on demand by the Debian Installer.
Another huge difference compared to GRUB is the files.json
file. While the GRUB one was only used to list 3 files for a single package, the Linux kernel one is close to 1 MB in size!
It also comes with different kinds of signatures. On the GRUB side, the signature type was efi
; on the Linux side, that happens for some files as well:
linux-image-4.19.0-4-amd64-unsigned
:boot/vmlinuz-4.19.0-4-amd64
linux-image-4.19.0-4-rt-amd64-unsigned
:boot/vmlinuz-4.19.0-4-rt-amd64
linux-image-4.19.0-4-cloud-amd64-unsigned
:boot/vmlinuz-4.19.0-4-cloud-amd64
which is consistent with the GRUB to Linux chainloading. But all other files that need signatures are the kernel modules, which use the linux-module
signature type.
Signatures for other packages
This write-up is overly long already, so the following packages won’t be detailed, let’s just mention both the source and binary packages for each:
- The
shim
source package builds:shim-unsigned
,shim-helpers-amd64-signed-template
,shim-helpers-i386-signed-template
,shim-helpers-arm64-signed-template
. - The
fwupd
source package builds:fwupd
fwupd-amd64-signed-template
,fwupd-arm64-signed-template
,fwupd-armhf-signed-template
,fwupd-i386-signed-template
(and some other packages). - The
fwupdate
source package builds:fwupdate
fwupdate-amd64-signed-template
,fwupdate-arm64-signed-template
,fwupdate-armhf-signed-template
,fwupdate-i386-signed-template
(and some other packages).
What is the current status of Secure Boot in Debian?
Short version: Starting with the Debian Installer Buster RC 1 release, Secure Boot support should work out of the box on amd64!
Longer version: The Debian Installer Buster Alpha 5 release was released with initial support for Secure Boot. Initially, the signing service running on the Debian infrastructure was using a test key, and some manual enrollment was needed. Since the publication of this last alpha, the switch to the production key happened, and the following release (Debian Installer Buster RC 1) shipped with signatures performed with the production key.