1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
|
#+title: Secure Booting (Continued)
#+date: 2026-03-10 Mon
#+author: W. Kosior
#+email: wkosior@agh.edu.pl
#+HTML_HEAD: <link rel="stylesheet" href="../org-dark-styles.css">
In these exercises we are going to replace the Secure Boot key hierarchy in a
virtualized environment. Later on, we are going to compile the Linux kernel by
ourself, sign the new kernel image and boot the OS with it.
To be graded, send the required screenshots to
@@html:<a href="email:wkosior@agh.edu.pl">wkosior@agh.edu.pl</a>@@ along with
your names. You can work in teams of up to three people.
The [[#recompiling-the-kernel]["Recompiling the Kernel"]] step in a later part of these instructions is
likely to take a non-negligible amount of time. You might want to launch the
compilation process in the background now, before starting the other exrecises.
* Our Setup
We shall continue using the tools from [[../01-secure-booting.org][the previous topic]]. To start quickly,
download and extract the [[https://pluton.kt.agh.edu.pl/~wokosior/bso-files/debian-13-qemu-uefi-sb.tar.xz][VM image and QEMU firmware pack]] that contains files
resulting from completing our earlier exercies:
- a MoK key and certificate,
- =t.efi= and =t2.efi= along with their signed variants,
- UEFI firmware code / variables files with the MoK certificate imported, and
- a VM image with compiled "Hello World!" EFI applications on the ESP.
Verify that the VM boots properly and that you can log in with the
=root=/=security= credentials.
#+begin_src shell-script
qemu-system-x86_64 \
-m 1G \
-enable-kvm \
-machine q35 \
-drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE_4M.ms.fd \
-drive if=pflash,format=raw,file=./OVMF_VARS_4M.ms.fd \
-hda ./debian-13-nocloud-amd64.qcow2 \
-net nic -net user,hostfwd=tcp::30022-:22 \
-nographic # replace with `-vga virtio' to get a QEMU GUI
#+end_src
Also make sure that the =efitools= package is installed on the host.
#+begin_src shell-script
sudo apt install efitools
#+end_src
* Backing up Old Keys
We shall now replace PK, KEK and db. We are going to compile our own shim, sign
it and use it with the usual Debian bootloader and kernel.
First, in the guest, backup the certificates from EFI variables (we omit dbx as
the blacklisted hash in it can as well remain there).
#+begin_src shell-script
for VAR in PK KEK db; do
efi-readvar -v $VAR -o $VAR-old.esl
done
#+end_src
Note that we are also able to extract individual certificates from the EFI
signature list (=.esl=) files.
#+begin_src shell-script
sig-list-to-certs KEK-old.esl /tmp/KEK-old
openssl x509 -inform DER -in /tmp/KEK-old-0.der -text -noout
# Just to see the contents:
openssl x509 -inform DER -in /tmp/KEK-old-1.der -text -noout
#+end_src
Also export the keys from the MokList.
#+begin_src shell-script
mokutil --export
# Just to see the contents of the exported cert(s):
openssl x509 -inform DER -in MOK-0001.der -text -noout
#+end_src
Copy the EFI signature list files and =MOK-000?.der= files out of the VM.
*Prepare screenshot 1: part of the output of the =openssl= command above, with
at least the CN (Common Name) of the certificate subject visible.*
* Building shim
We'll now build our own shim binary to use instead of the distro-shipped one.
This repository uses a submodule (the gnu-efi project we have already used),
hence we need to clone recursively to obtain it.
#+begin_src shell-script
git clone https://github.com/rhboot/shim/ --recursive
#+end_src
We could pass a trusted cert to the shim's build system, but we don't need to.
shim is able to use the certs in db variable together with those from MokList.
If we wanted to, at this point we could also specify another bootloader filename
than grubx64.efi (which is by default hardcoded in shim).
We build the shim with a simple =make= invokation. The resulting binary is
called =shim/shimx64.efi= (inside the project directory).
*Prepare screenshot 2: the final part of the build process output.*
* Keys Generation
We shall now generate the keys.
#+begin_src shell-script
# Other lengths than 2048 might not be supported by some UEFI firmwares :(
openssl req -new -newkey rsa:2048 -days $((365 * 10)) -noenc -x509 \
-keyout PK.key -out PK.pem -subj "/CN=My PK 2026"
#+end_src
Repeat this operation to also generate your own KEK and db keys.
* shim Signing
Sign shim. Use =sbsign= command as in the previous topic, but this time use the
db key.
#+begin_src shell-script
sbsign --cert db.pem --key db.key --output ./my-shim-signed.efi \
shim/shimx64.efi
#+end_src
We could've also signed =mmx64.efi= (the Mok Manger) and =fbx64.efi= (shim's
fallback bootloader) that have been built. They're not, however, needed in our
scenario.
*Prepare screenshot 3: the output of =sbverify --list= when passed the signed
shim.*
* Preparing the Keys for Loading
Generate new EFI signature lists and their respective =.auth= files for loading
certificates into secure UEFI variables. A GUID needs to be supplied, but is
not very interesting to us (it identifies the owner of the entry). We can use
different GUIDs or a single one for different signature lists in subsequent
steps.
#+begin_src shell-script
uuidgen
uuidgen # Just to see how it works.
MY_GUID="$(uuidgen)"
# Cert in PEM format must be converted to EFI signature list.
cert-to-efi-sig-list -g $MY_GUID PK.pem PK.esl # Creates `PK.esl'.
sign-efi-sig-list -k PK.key -c PK.pem PK PK.esl PK.auth # Creates `PK.auth'.
#+end_src
The signed =.auth= file shall be used with UEFI to replace the contents of PK.
We also need =KEK.auth= and =db.auth=.
#+begin_src shell-script
cert-to-efi-sig-list -g $MY_GUID KEK.pem KEK.esl # Analogically.
# The following is not completely analogical, we now sign KEK with sth other
# than KEK itself.
sign-efi-sig-list -k PK.key -c PK.pem KEK KEK.esl KEK.auth
#+end_src
In the db variable, we shall store our cert together with the distribution cert.
The latter is useful to be able to boot the kernels installed with APT.
Fortunately, we have backed up the Debian certificate from the MokList and we
can use it now. Before creating the =db.auth= file, create two EFI signature
lists, one for each of the certificates.
#+begin_src shell-script
cert-to-efi-sig-list -g $MY_GUID db.pem db.esl # Analogically, but…
# MOK-0001.der is likely the Debian cert and 0002 is likely our MOK cert from
# the earlier exercise. Use grep to confirm. We only need the former cert now
# (also, the latter has been made by us with larger RSA key than supported in
# sig list).
openssl x509 -inform DER -in MOK-0001.der -text -noout | grep Debian
openssl x509 -inform DER -in MOK-0001.der > MOK-0001.pem # To text format.
cert-to-efi-sig-list -g $MY_GUID MOK-0001.pem debian.esl
grep Debian debian.esl # If there's no match, it means we screwed up ;)
#+end_src
*Prepare screenshot 4: the output of =grep Debian debian.esl=, with a match.*
The signature lists can be simply concatenated! Create one including both certs
and sign it with KEK.
#+begin_src shell-script
cat debian.esl >> db.esl
sign-efi-sig-list -k KEK.key -c KEK.pem db db.esl db.auth
#+end_src
Now, copy all =.auth= files and shim to the guest.
* Re-Populating Secure Boot Variables
In the UEFI firmware settings, go to:
- Device Manager
- Secure Boot Configuration
Set "Secure Boot Mode" to "Custom Mode".
In "Custom Mode Boot Options" select
- PK Options
- Delete PK
*Prepare screenshot 4: the "Delete PK" option shown.*
You can (but do not need to) do analogically with KEK and db. Deleting them now
merely saves us one step a moment later. Reboot to the OS when you're done.
#+begin_src shell-script
mokutil --sb-state # Should report "Setup Mode".
#+end_src
When the system is in "Setup Mode" (as is the case after deleting PK), it means
PK, KEK, db and dbx are writeable by the OS! It shall automatically exit Setup
Mode when we write the PK.
Enroll the keys.
#+begin_src shell-script
efi-updatevar -f PK.auth PK
# Do not turn off the VM now or you might have to delete the PK again!
mokutil --sb-state # should no longer report Setup Mode :)
efi-updatevar -f KEK.auth KEK
efi-updatevar -f db.auth db
cp my-shim-signed.efi /boot/efi/EFI/BOOT/
# Now, change the boot order so that the firmware tries to load our new shim
# rather than the old one.
efibootmgr # lists current boot entries
efibootmgr -c --part 15 --label "My Own Shim" --disk /dev/sda \
--loader '\EFI\BOOT\my-shim-signed.efi'
# You can reboot the VM now.
#+end_src
If you haven't deleted KEK and db from the UEFI firmware interface, you'll
likely encounter permission issues. See the [[#appending-certs]["Appending Certs"]] section for a
solution.
*Prepare screenshot 5: the output of =efibootmgr= _after_ adding the new entry.*
After rebooting, verify that secure boot is on.
#+begin_src shell-script
mokutil --sb-state
#+end_src
We have now somewhat decreased the attack surface — only code from Debian (&
code signed by ourselves) can run in ring 0. All at the cost of having to
maintain our own signed shim binaries.
Try rebooting and selecting the "QEMU HARDDISK" option in the UEFI boot menu.
It tries to load the default bootx64.efi binary (the old shim from Debian,
signed by Microsoft) and fails due to secure boot. In case of OVMF, the failure
is silent (no error message, we just return to the Boot Manager Menu).
* Recompiling the Kernel
:PROPERTIES:
:CUSTOM_ID: recompiling-the-kernel
:END:
In certain situations it is useful to be able to use a customized Linux kernel
build. With our own certificate in either db or MokList, we are able to sign
our own kernel images and boot them with Secure Boot enabled.
# /Note: if you have little time for the exercise, you can omit this step and use
# [[https://pluton.kt.agh.edu.pl/~wokosior/bso-files/linux-6.19.6-built.tar.xz][a packed source tree with kernel already compiled]] and ready for installation./
First, let's install the tools necessary for the compilation.
#+begin_src shell-script
sudo apt install fakeroot build-essential libncurses-dev xz-utils libssl-dev \
flex libelf-dev bison
#+end_src
Then download and unpack your kernel sources pack of choice, for example [[https://linux-libre.fsfla.org/pub/linux-libre/releases/LATEST-6.19.N/linux-libre-6.19.6-gnu.tar.xz][the
Linux-Libre kernel sources]].
#+begin_src shell-script
wget "$LINUX_URL" -O - | tar -xJf - # `-J' decompresses using XZ
cd linux-6.19.6
#+end_src
Before compiling the kernel, configure it according to our needs.
#+begin_src shell-script
make defconfig
make menuconfig
#+end_src
Use the default configuration for x86_64 as a base. We want the kernel to
*automatically sign its module files*. We can use our own key or have the build
system generete one. To achieve the latter, check the following options in the
=menuconfig= dialog.
#+begin_example
Enable loadable module support
---> [*] Module signature verification
[*] Automatically sign all modules
#+end_example
Then, compile.
#+begin_src shell-script
make all -j $(nproc)
#+end_src
*Prepare screenshot 6: the final lines of compilation output.*
* Signing and Distributing the Kernel
In our scenario, the kernel is being compiled and signed on a different system
from the one it is going to be used on. For this reason we want to use the
=install= and =modules_install= Makefile targets with environment variables
telling the build system to install to a different place than =/=.
#+begin_src shell-script
mkdir -p ../kernel-install-root/boot
export INSTALL_PATH=../kernel-install-root/boot
export INSTALL_MOD_PATH=../kernel-install-root/ \
make install modules_install
(cd ../kernel-install-root/ && find) # See installed files.
#+end_src
The modules are automatically signed during =modules_install= invokation, the
kernel image itself is not. Let's use our db key to sign it. Keep the unsigned
copy under a different name.
#+begin_src shell-script
VMLINUZ=kernel-install-root/boot/vmlinuz-6.19.6-gnu
mv "$VMLINUZ" "$VMLINUZ".unsigned
sbsign --cert db.pem --key db.key --output "$VMLINUZ" "$VMLINUZ".unsigned
#+end_src
Transfer the files to the VM. Use =tar=, =ssh=, =qemu-nbd= or whatever
you find suitable. Beware, however, that installed module directories shall
contain a symlink back to kernel sources. Some of our tools follow these by
default and you might end up transferring the kernel sources to the VM and
filling up guest's disk space.
* Installing the Kernel
Inside the VM, change owner / group owner of all the files to root and put
everything under =/=. Once done, you should, among others, have
=/boot/vmlinuz-6.19.6-gnu= and =/lib/modules/6.19.6-gnu/modules.dep= present in
the filesystem.
The OS requires an initial RAM filesystem image to be generated for each kernel
installed. We also want to prepare dedicated bootloader menu entries for
booting with our kernels. The =update-initramfs= and =update-grub= commands
handle these tasks.
#+begin_src shell-script
update-initramfs -k 6.19.6-gnu -c # or `-k all'
update-grub # updates config, adds boot en
#+end_src
*Prepare screenshot 7: the output of =update-grub=.*
* Booting with the New Kernel
Reboot the VM. In GRUB, select "Advanced options for Debian GNU/Linux". First,
choose to boot with the unsigned kernel — you should encounter the "bad shim
signature" error. Then, choose to boot with the signed one — this should work.
Once booted and logged in, verify that you are running the desired kernel.
#+begin_src shell-script
uname -a # reports the new, self-compiled kernel
mokutil --sb-state
#+end_src
*Prepare screenshot 8: the =uname= and =mokutil= commands from the code block
above, along with their output.*
* Appending Certs
:PROPERTIES:
:CUSTOM_ID: appending-certs
:END:
If for some reason we need to allow Microsoft-signed software (e.g., other
distros' shims), we can re-add the Microsoft key. The =-a= flag to
=sign-efi-sig-list= allows us to create a =.auth= file that will cause entries
to be appended to secure variable list instead of completely replacing the
variable contents.
#+begin_src shell-script
sign-efi-sig-list -a -k KEK.key -c KEK.pem db db-old.esl db-old-readd.auth
#+end_src
After transmitting the .auth file to the guest, we can try updating the
variable.
#+begin_src shell-script
# Note the `-a' switch needed for *appending*.
sudo efi-updatevar -a -f db-old-readd.auth db
#+end_src
If there are permission errors, it might be because the OS protection of EFI
variables from accidental modification is on — files under
=/sys/firmware/efi/efivars/= have the "i" attribute which prevents their
modification and deletion.
#+begin_src shell-script
lsattr /sys/firmware/efi/efivars/db*
#+end_src
See =man chattr= to find out how to remove this attribute from
=/sys/firmware/efi/efivars/db-GUID_GOES_HERE=. Once this is done, the
=efi-updatevar= command should be successful.
*Prepare screenshot 9: the output of a successful =efi-updatevar=.*
Reboot. The old shim from Debian (and others signed by Microsoft) should now
work as well.
|