Fedora 31 Self-Contained Change proposal: Include several modules in the EFI build of Grub2 for security use-cases

<a href="" title=""></a>

== Summary ==
Include Grub's "verify," "cryptodisk" and "luks" modules in
grubx64.efi of the 'grub2-efi-x64' package.

Note: Although the build process will automatically include explicit
dependencies ("mpi," "gcry_sha1," "procfs" and "archelp"), the
following implicit ones must also be included: "gcry_sha256,"
"gcry_rsa," "gcry_rijndael," "gcry_serpent," gcry_twofish" and

== Owner ==
* Name: [[User:benjamind|Benjamin Doron]]
* Email: <a href="mailto:benjamin. ... at gmail dot com">benjamin. ... at gmail dot com</a>
* Name: [[User:pjones|Peter Jones]]
* Email: <a href="mailto: ... at redhat dot com"> ... at redhat dot com</a>
* Name: [[User:javierm|Javier Martinez Canillas]]
* Email: <a href="mailto: ... at redhat dot com"> ... at redhat dot com</a>

== Detailed Description ==
Users utilising secure boot functionality on the UEFI platform cannot
insert grub modules. Paradoxically, this means that security-conscious
users cannot use grub's verify module for signature verification, or
employ (near) full disk encryption using cryptodisk and luks.

The security benefits of signature verification would reach more users
if Fedora automated it by incorporating the process into
`grub2-mkconfig`. Similarly, it would be easier to use cryptodisk
functionality if it were configurable by anaconda. This can be
considered for future releases, after allowing time to straighten out
the details of the implementation and analyse the benefits/costs.

For the long-term, to grant flexibility with grub2 modules on secure
boot instances in general, it may be advisable to consider signing the
.mod files in the 'grub2-efi-x64-modules' package, allow inserting of
signed modules in secure boot instances and then modify
`grub2-mkconfig` to copy the necessary modules into the EFI partition
when required by the user's configuration.

== Benefit to Fedora ==
This change will allow users to gain trust in the integrity of
early-launch code either through verification of signatures
(particularly useful for the initramfs, which is particularly
vulnerable to possible offline modification) or encryption of the boot

== Scope ==
* Proposal owners: Modify grub.macros file to include the
above-mentioned modules in the GRUB_MODULES variable.
* Other developers: N/A (not a System Wide Change)
* Policies and guidelines: N/A (not a System Wide Change)
* Trademark approval: N/A (not needed for this Change)

== Upgrade/compatibility impact ==
Change only adds modules, so existing users should have no problems.

== How To Test ==
<b>Disclaimer: Commands assume that tester is root. Secure Boot must
be disabled for testing purposes (`insmod` is forbidden under Secure
Boot, for now. The description details a tentative plan to get us to a
place where that can be changed). Ensure that the package
'grub2-efi-x64-modules' is installed, and for testing purposes, copy
the contents to the EFI partition with `cp -r
/usr/lib/grub/x86_64-efi/ /boot/efi/EFI/fedora/`</b>

<b>For "verify":</b>

1. Generate an RSA signing key with `gpg --full-generate-key` (option
4), then export it with `gpg --export > pubkey` and move it to the EFI
partition with `mv pubkey /boot/efi/EFI/fedora`. You can also export
the private key (`gpg --export-secret-keys > seckey`), but the signing
process doesn't require it as gpg will get the key from its own

2. Add "insmod verify," "insmod gcry_sha256," "insmod gcry_rsa,"
"trust (hd0,gpt1)/efi/fedora/pubkey" (change this based on your
environment) and "set check_signatures=enforce" to

3. Run `grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg`

4. Run `export GPG_TTY=$(tty)`. Don't ask, apparently something
changed in gpg with a recent update.

4. Create a file, /dev/shm/pass, with the key's password and execute:
`for x in $(find /boot -name "*.cfg" -or -name "*.lst" -or -name
"*.mod" -or -name "vmlinuz*" -or -name "initramfs*" -or -name
"grubenv"); do gpg --batch --pinentry-mode loopback --detach-sign
--passphrase-fd 0 $x < /dev/shm/pass; done`. Then, `shred

5. Reboot. If the system starts, backup and remove .sig files. If the
system does not start this time, change is successful

(To recover from a now non-booting system, open the grub terminal and
execute `set check_signatures=no`. The system should then boot, and
.sig files can be replaced or regenerated.)

<b>For cryptodisk functionality:</b>

1. Backup the files in your boot partition

2. Run `cryptsetup luksFormat /dev/sda2 --type luks1` (change this
based on your environment to /boot's block device)

Note: If filesystem root is also encrypted, it is recommended that the
same password be used for boot as for root to decrease the amount of
engagement required at start-up. Consider using --iter-time with low
time in milliseconds (default is 2000ms)

3. Open luks container with `cryptsetup luksOpen /dev/sda2 --type
luks1 crypt_boot` (change this to match your environment) and run
`mkfs.ext4 /dev/mapper/crypt_boot`, then mount and restore backup

4. Add "GRUB_ENABLE_CRYPTODISK=y" to /etc/default/grub

5. Correct /etc/fstab to the correct UUID for /boot

6. Add an entry for the boot container to /etc/crypttab (i.e.
"luks-<your luks UUID> UUID=<your luks UUID> none discard"), then run
`dracut -vf --regenerate-all`

7. Run `grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg`

8. Reboot. Grub should ask for the password created in step 2. If the
system then starts, change is successful

(If filesystem root is also encrypted, the user will be asked for a
password twice. This can be mitigated with a keyfile for filesystem
root, or use of the clevis package and likely, a tpm.)

== User Experience ==
Users may optionally elect to guarantee the integrity of boot code
either through verification of digital signatures or encryption of the
boot partition.

== Dependencies ==
'Grub2-efi-x64-modules' and 'grub2-tools-*' depend upon/are a part of
this package, but require no change.

== Contingency Plan ==
* Contingency mechanism: Revert the shipped configuration
* Contingency deadline: Beta freeze
* Blocks release? N/A (not a System Wide Change)
* Blocks product? No

== Documentation ==
<a href="" title=""></a>

== Release Notes ==
Fedora now supports Grub's detached verify and cryptodisk
functionality natively, even on secure boot systems.


Re: Fedora 31 Self-Contained Change proposal: Include several mo

By Benjamin Doron at 06/26/2019 - 12:48

Hi all,
I've written some scripts to help with the signature verification aspect of this change. I've attempted to have them seamlessly handle different environments, but please let me know if you observe any misbehaviour. I'd particularly like to get input on the second script. The first can setup a system for signature verification only if the relevant modules are made available separately. Also, compare the steps the script takes with the wiki page. The script assumes that the modules are loaded by default (they are once they're included in the build) too. Check steps 2 and 3 in the "verify" portion of the How To Test section for what is missing (So, this is definitely useful after F31's release, but can be convenient now too).



## This, for now, is a holistic script. It assumes that we've either configured signature verification, or not.
## This will need to be changed. Individual scripts should check for their files, and call on a central script (or
## function therein) to configure things otherwise.

if [[ $(id -u) != 0 ]]; then
echo "You must run this script as root"
exit 1

sata_or_nvme=$(if [[ $(mount | grep "/boot/efi" | cut -d " " -f 1) =~ (/dev/nvme*|/dev/mmcblk*) ]]; then echo 3; else echo 2; fi)
drive_num=$(lsblk | grep /boot/efi | cut -d " " -f 1 | cut -c 3- | rev | cut -c $sata_or_nvme- | rev)
part_type=$(fdisk /dev/$drive_num -l | grep "Disklabel type" | cut -d " " -f 3)
ESP_partnum=$(lsblk | grep /boot/efi | cut -c 6)
#export GPG_TTY=$(tty)

function firstrun {
touch /var/tmp/grub_verify-pgp_pass
chmod 600 /var/tmp/grub_verify-pgp_pass
gpg --gen-random --armor 0 24 > /var/tmp/grub_verify-pgp_pass
gpg --pinentry-mode loopback --batch --quick-generate-key --passphrase-file /var/tmp/grub_verify-pgp_pass "Grub_verify testing key" rsa sign never
gpg --export "Grub_verify testing key" > /boot/efi/EFI/fedora/pubkey
echo "
trust (hd0,$part_type$ESP_partnum)/efi/fedora/pubkey --skip-sig
set check_signatures=enforce" >> /etc/grub.d/40_custom
grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg

function resign {
for x in $(find /boot -name "*.cfg.sig" -or -name "*.lst.sig" -or -name "*.mod.sig" -or -name "vmlinuz*.sig" -or -name "initramfs*.sig" -or -name "grubenv.sig"); do rm -f "$x"; done
for x in $(find /boot -name "*.cfg" -or -name "*.lst" -or -name "*.mod" -or -name "vmlinuz*" -or -name "initramfs*" -or -name "grubenv"); do gpg --batch --detach-sign -u "Grub_verify testing key" --pinentry-mode loopback --passphrase-fd 0 "$x" < /var/tmp/grub_verify-pgp_pass; done

if [ ! -f /boot/efi/EFI/fedora/grub.cfg.sig ]; then
exit 0
exit 0


## This is only going to address the kernel and initramfs (we're tacking on grubenv, as it is edited concurrently with
## kernel upgrades. However, "savedentry" might only change after a reboot. This requires further testing). While these
## are the most frequently modified, those with certain configurations will need to keep an eye on things.
## Thankfully, the new BootLoaderSpec format ensures that grub.cfg is rarely modified. The large majority of users don't
## use custom.cfg and user.cfg is generally on written once. An initial round of signing should cover all of this.
## grubenv will be resigned by this version of the script, but requires further testing.
## The default configuration doesn't allow for inserting modules, so we don't need to resign any of them. While this can
## hopefully be changed with Grub's 2.04 release, by that time additional module loading can be automated
## per-environment with patches to grub2-mkconfig.
## Once/if we turn on signature verification by default, all of the above will be handled with patches to the relevant
## scripts.

## It's unlikely that we'll hit this, but we need to be sure in case we're run directly.
if [[ $(id -u) != 0 ]]; then
echo "You must run this script as root"
exit 1

old_sigs=$(for x in $(find /boot -name "vmlinuz*.sig" -or -name "initramfs*.sig" | grep -v rescue | sed 's/.sig//'); do if [[ "$x" != "$(rpm -ql kernel-core | grep -e /boot/vmlinuz -e /boot/initramfs | grep "$x")" ]]; then echo "$x"; fi; done)
new_uname_r=$(rpm -qa --last kernel | head -n 1 | cut -d " " -f 1 | sed 's/kernel-//')

for x in $old_sigs; do rm -f "$x.sig"; done
for x in $(find /boot -name "grubenv.sig"); do rm -f "$x"; done
for x in $(find /boot -name "vmlinuz-$new_uname_r" -or -name "initramfs-$new_uname_r.img" -or -name "grubenv"); do gpg --batch --detach-sign -u "Grub_verify testing key" --pinentry-mode loopback --passphrase-fd 0 "$x" < /var/tmp/grub_verify-pgp_pass; done
exit 0