User:Aruslantsev/Laptop secure setup
Introduction
I like some ideas implemented in Apple computers, but I don’t like so much Mac OS itself. The thing that I really like is that the hardware is locked and the data is encrypted until you enter the user password. If the laptop is stolen it become useless. The system can’t be used, the hardware is locked too.
So, I wanted to implement a setup, where computer requires the password to change the UEFI settings and checks the integrity of the bootloader and EFI partition contents, bootloader asks the password to change the boot options and loads only trusted kernel and initramfs, and system requires the password to decrypt the disk. To implement this I will replaced secureboot keys by my own, signed bootloader and kernel by keys and encrypted the disk.
If somebody gets your laptop and wants to install new system, he can’t change the boot device and disable Secureboot because of UEFI password, if he replaces your system with preinstalled one, it will not boot because of enabled Secureboot with custom keys. And the information from the NVMe drive can’t be easily restored because of disk encryption.
I am sure that for many people this problem does not exist at all. And for many people it can be solved by using PAM, ZFS encryption, systemd-homed etc. But solutions with PAM don’t look reliable for me and I don’t use systemd.
This article consists of two mostly independent parts. The first one is dedicated to disk encryption, the second one - to Secure Boot setup.
This is not the official guide and never will be the official one. This notes can be used only as the addition and modification of the Handbook.
Be careful, you may lose all your data.
Author is not responsible for all possible data loss and hardware damage.
Prerequisites
- Modern Laptop or PC with UEFI and SecureBoot
- You don’t need to have Windows in dual boot
- Basic knowledge of Gentoo Linux
- Installed sys-fs/cryptsetup, sys-kernel/dracut, app-crypt/gnupg, app-crypt/efitools, app-crypt/sbsigntools
- Bravery and courage
Partitioning
Be sure to make a backup of your data before you begin. You may lose all your data.
The only thing that UEFI needs to boot the system is FAT32 UEFI partition. All other partitions can be encrypted. UEFI checks for the integrity of the data, located at UEFI boot partition.
So, the proposed partitioning scheme is:
+--------------------+------------------------------------------+ | Boot partition | | | (FAT32 EFI) | LVM on LUKS (root + home + swap + ...) | | Moutpoint: /boot | | +--------------------+------------------------------------------+
LVM is not obligatory in this scheme. Btrfs, ext4, etc. can be used. It is just convenient for me to use this scheme
The UEFI boot partition has the mountpoint /boot and contains all GRUB stuff, kernel and initramfs. The size of my UEFI boot partition is 1GiB (with two kernels installed 700+MiB are free), usually 256MiB is enough. It should be formatted by command
root #
mkfs.vfat -F32 /dev/nvme0n1p1
Kernel does not consider swap on lvm on luks as encrypted partition, so suspend to disk without disabled lockdown is not possible
Encryption
See Dm-crypt for more information.
How to choose the fastest algorithm
To determine the fastest algorithm it is needed to run:
root #
cryptsetup benchmark
# Tests are approximate using memory only (no storage IO). PBKDF2-sha1 789590 iterations per second for 256-bit key PBKDF2-sha256 1349518 iterations per second for 256-bit key PBKDF2-sha512 563145 iterations per second for 256-bit key PBKDF2-ripemd160 340446 iterations per second for 256-bit key PBKDF2-whirlpool 237449 iterations per second for 256-bit key argon2i 7 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time) argon2id 7 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time) # Algorithm | Key | Encryption | Decryption aes-cbc 128b 525.0 MiB/s 1830.0 MiB/s serpent-cbc 128b 36.2 MiB/s 234.6 MiB/s twofish-cbc 128b 85.1 MiB/s 151.9 MiB/s aes-cbc 256b 398.6 MiB/s 1493.2 MiB/s serpent-cbc 256b 36.2 MiB/s 230.3 MiB/s twofish-cbc 256b 84.3 MiB/s 149.1 MiB/s aes-xts 256b 1467.5 MiB/s 1441.0 MiB/s serpent-xts 256b 220.7 MiB/s 215.2 MiB/s twofish-xts 256b 142.8 MiB/s 145.4 MiB/s aes-xts 512b 1349.6 MiB/s 1340.1 MiB/s serpent-xts 512b 225.7 MiB/s 214.8 MiB/s twofish-xts 512b 142.3 MiB/s 145.4 MiB/s
On modern CPUs AES is hardware accelerated, that's why AES-XTS is fastest. AES-XTS with 512b key is chosen.
The data will be lost during the next step
root #
cryptsetup -v -c aes-xts-plain64 -s 512 --hash sha512 --iter-time 5000 --use-random --type luks2 luksFormat /dev/nvme0n1p2
The passphrase will be asked. It should be strong enough.
If the device that supports TRIM command (modern SSD, NVMe) is used, it may be a good idea to enable trim for your partition. But it may be a bit less safe.
root #
cryptsetup --allow-discards --persistent open /dev/nvme0n1p2 cryptlvm
Or if partition was opened:
root #
cryptsetup luksOpen /dev/nvme0n1p2 cryptlvm
root #
cryptsetup --allow-discards --persistent refresh cryptlvm
For one-time trim support may be used the following command:
root #
cryptsetup luksOpen --allow-discards /dev/nvme0n1p2 cryptlvm
Now create LVM on LUKS-encrypted partition can be created. In this article partitions are created for /, /home, /var/cache/distfiles and swap. Also /tmp, /var/tmp and some more partitions are mounted as tmpfs. It is assumed that volume group has name data
.
It is a good practice to backup the luks header:
root #
cryptsetup luksHeaderBackup /dev/nvme0n1p2 --header-backup-file crypt_headers.img
Bootloader options and dracut
It is needed to add some boot parameters for kernel.
First, the uuid of LUKS encrypted partition should be determined:
root #
lsblk -o name,uuid
NAME UUID nvme0n1 ├─nvme0n1p1 1E86-3A24 └─nvme0n1p2 c4d6e4ac-57f7-4af1-ba98-e06669a57887 └─luks-c4d6e4ac-57f7-4af1-ba98-e06669a57887 ... ├─data-root ... ├─data-swap ... ├─data-distfiles ... └─data-home ...
The UUID of nvme0n2 partition is needed. It is c4d6e4ac-57f7-4af1-ba98-e06669a57887
in this example.
It is needed to add to the kernel cmdline following string:
rd.luks.uuid=c4d6e4ac-57f7-4af1-ba98-e06669a57887 rd.luks.allow-discards rd.lvm.vg=data root=/dev/mapper/data-root
Also I add issue_discards = 1
to /etc/lvm/lvm.conf.
Dracut does not need any changed to generate the initramfs. To generate initramfs it is needed to run:
root #
dracut --force --kver <your kernel version>
.
Check if TRIM works
This part can be safely skipped. It is only the test that trim is working
Create file with pattern
root #
yes | dd iflag=fullblock bs=1M count=1 of=trim.test
Get the address of the file
root #
filefrag -s -v trim.test
Filesystem type is: ef53 File size of trim.test is 1048576 (256 blocks of 4096 bytes) ext: logical_offset: physical_offset: length: expected: flags: 0: 0.. 255: 1608448.. 1608703: 256: last,eof trim.test: 1 extent found
Get the device
root #
df trim.test
Filesystem Size Used Avail Use% Mounted on /dev/mapper/data-root 20G 10G 10G 50% /
Get contents of the file
root #
dd bs=4096 skip=1608448 count=256 if=/dev/mapper/data-root | hexdump -C
00000000 79 0a 79 0a 79 0a 79 0a 79 0a 79 0a 79 0a 79 0a |y.y.y.y.y.y.y.y.| *
Remove file and sync filesystem
root #
rm trim.test
root #
sync
root #
fstrim -a -v
root #
echo 1 > /proc/sys/vm/drop_caches
Get contents of the file again. This command should output zeros on non-encrypted.
root #
dd bs=4096 skip=1608448 count=256 if=/dev/mapper/data-root | hexdump -C
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00100000
If encryption is involved, a random pattern will be presented:
root #
dd bs=4096 skip=1608448 count=256 if=/dev/mapper/data-root | hexdump -C
00000000 1f c9 55 7d 07 15 00 d1 4a 1c 41 1a 43 84 15 c0 |..U}....J.A.C...| 00000010 24 35 37 fe 05 f7 43 93 1e f4 3c cc d8 83 44 ad |$57...C...<...D.| 00000020 46 80 c2 26 13 06 dc 20 7e 22 e4 94 21 7c 8b 2c |F..&... ~"..!|.,|
If yes-pattern still observed, trim does not work.
Bootloader
It is possible to create Unified kernel image and embed cmdline in it, then UKI should be signed using secureboot keys. But it is not possible to change cmdline of UKI, if it is loaded without external bootloader.
That's GRUB is used as the bootloader in this guide. With secureboot enabled GRUB loads only signed by secureboot keys kernel, but it does not check grub.cfg and initramfs. To protect this files, gpg key can be embedded into GRUB EFI app. After this GRUB will use only sigmed config, modules, kernels and initramfs images.
Digital signature
This part can be used without secureboot
Create key:
user $
gpg --full-generate-key --expert
Export key:
user $
gpg --export > gpg.key
Install grub
It was mentioned above, that our EFI partition mountpoint is /boot
It is needed to embed modules gcry_sha256, gcry_sha512, gcry_dsa, gcry_rsa in GRUB EFI app. GRUB needs this modules to check signatures.
root #
grub-install --pubkey gpg.key --no-nvram --removable --modules="gcry_sha256 gcry_sha512 gcry_dsa gcry_rsa" --efi-directory=/boot
Now Grub is installed and public key is embedded in it. Now we need to sign config, modules, kernel and initramfs:
root #
for i in `find /boot -name "*.cfg" -or -name "*.lst" -or -name "*.mod" -or -name "vmlinuz*" -or -name "initr*" -or -name "grubenv"`; do gpg --batch --detach-sign $i; done
Set password
user $
grub-mkpasswd-pbkdf2 # create hash here
set superusers="root"
password_pbkdf2 root grub.pbkdf2.sha512.10000.YOURHASHHERE
...
menuentry 'Gentoo GNU/Linux' --unrestricted
{
...
}
If you don't want to use secureboot, you can stop here. You will need to sign vmlinuz, initramfs and grub.cfg after every kernel update
secureboot
Put secure boot into setup mode.
Save old keys.
root #
efi-readvar -v PK -o old_PK.esl
root #
efi-readvar -v KEK -o old_KEK.esl
root #
efi-readvar -v db -o old_db.esl
root #
efi-readvar -v dbx -o old_dbx.esl
Create new keys
root #
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=PK/" -keyout PK.key -out PK.crt -days 7300 -nodes -sha256
root #
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=KEK/" -keyout KEK.key -out KEK.crt -days 7300 -nodes -sha256
root #
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=db/" -keyout db.key -out db.crt -days 7300 -nodes -sha256
root #
cert-to-efi-sig-list PK.crt PK.esl
root #
sign-efi-sig-list -k PK.key -c PK.crt PK PK.esl PK.auth
root #
cert-to-efi-sig-list KEK.crt KEK.esl
root #
sign-efi-sig-list -k PK.key -c PK.crt KEK KEK.esl KEK.auth
root #
cert-to-efi-sig-list db.crt db.esl
root #
sign-efi-sig-list -k KEK.key -c KEK.crt db db.esl db.auth
Install keys
Preinstalled by the motherboard manufacturer keys will be lost!
If you want to save preinstalled keys and add yours as additional, please, follow another manuals
root #
efi-updatevar -f db.auth db
root #
efi-updatevar -f KEK.auth KEK
root #
efi-updatevar -f PK.auth PK
Use efi-readvar
to check if right keys are saved
Install GRUB. When secureboot is enabled, GRUB can't load modules.
grub-install \
--target x86_64-efi \
--efi-directory /boot \
--pubkey /root/gpg/gpg.key \
--no-nvram \
--removable \
--disable-shim-lock \
--modules="\
acpi all_video boot bufio cat chain configfile cpuid \
crypto datetime disk echo efi_gop efi_uga efifwsetup \
efinet ext2 extcmd fat font fshelp gcry_crc gcry_dsa \
gcry_rsa gcry_sha256 gcry_sha512 gettext gfxmenu gfxterm \
gfxterm_background gzio halt jpeg keystatus loopback \
lsefi lsefimmap lsefisystab help linux loadenv ls memdisk \
minicmd mmap net normal part_gpt part_msdos password_pbkdf2 \
play png priority_queue probe reboot regexp relocator search \
search_fs_file search_fs_uuid search_label sleep terminal tpm \
video video_bochs video_cirrus video_colors video_fb zstd"
Sign GRUB
root #
sbsign -key ~/secureboot/db.key --cert ~/secureboot/db.crt --output BOOTX64.EFI BOOTX64.EFI
Remove old signatures and sign GRUB config and modules:
root #
for i in `find /boot -name "*.cfg" -or -name "*.lst" -or -name "*.mod" -or -name "grubenv"`; do gpg --batch --detach-sign $i; done
It is possible to use kernel and initramfs together or only UKI. Both ways are described below.
Do not forget to set UEFI password. Otherwise Secureboot can be easily disabled
Kernel and initramfs
Sign kernel with Secureboot keys
root #
sbsign --key ~/secureboot/db.key --cert ~/secureboot/db.crt --output vmlinuz vmlinuz
Sign kernel and initramfs with gpg key
root #
gpg --batch --detach-sign vmlinuz
root #
gpg --batch --detach-sign initramfs.img
Edit grub.cfg and remove modules loading
set default=0
set check_signatures=enforce
export chech_signatures
set superusers="root"
password_pbkdf2 root grub.pbkdf2.sha512.10000.YOURHASHHERE
loadfont unicode
set gfxmode=auto
set lang=C
terminal_output gfxterm
set timeout_style=menu
set timeout=3
menuentry 'Gentoo GNU/Linux' --unrestricted {
set gfxpayload=keep
linux /vmlinuz rd.luks.uuid=c4d6e4ac-57f7-4af1-ba98-e06669a57887 rd.luks.allow-discards rd.lvm.vg=data root=/dev/mapper/data-root resume=/dev/mapper/data-swap
initrd /initramfs.img
}
grub.cfg should be signed using gpg after every change
After every update kernel should be signed with secureboot key and gpg key, and initramfs with gpg key only.
UKI
Unified kernel image can be used. GRUB can pass parameters to UKI, so it is still possible to change boot parameters of the kernel.
Generate UKI:
root #
dracut --uefi --uefi-stub linuxx64.efi.stub
Cmdline should not be specified because it can't be overrided in grub.cfg. To check that UKI does not contain cmdline, run
root #
objdump -x linux.efi
Move UKI to /boot and sign it with secureboot key and gpg:
root #
sbsign --key ~/secureboot/db.key --cert ~/secureboot/db.crt --output linux.efi linux.efi
root #
gpg --batch --detach-sign linux.efi
Edit grub.cfg and remove modules loading:
set default=0
set check_signatures=enforce
export chech_signatures
set superusers="root"
password_pbkdf2 root grub.pbkdf2.sha512.10000.YOURHASHHERE
loadfont unicode
set gfxmode=auto
set lang=C
terminal_output gfxterm
set timeout_style=menu
set timeout=3
menuentry 'Gentoo GNU/Linux' --unrestricted {
set gfxpayload=keep
chainloader /linux.efi rd.luks.uuid=c4d6e4ac-57f7-4af1-ba98-e06669a57887 rd.luks.allow-discards rd.lvm.vg=data root=/dev/mapper/data-root resume=/dev/mapper/data-swap
}
grub.cfg should be signed using gpg after every change
After every update kernel should be signed with secureboot key and gpg key, and initramfs with gpg key only.