User:Aruslantsev/Laptop secure setup

From Gentoo Wiki
Jump to:navigation Jump to:search

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.

Warning
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.
Warning
Be careful, you may lose all your data.
Warning
Author is not responsible for all possible data loss and hardware damage.

Prerequisites

Partitioning

Warning
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  |                                          |
+--------------------+------------------------------------------+
Note
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
Note
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.

Warning
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

Note
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

Note
This part can be used without secureboot

Create key:

user $gpg --full-generate-key --expert

Export key:

user $gpg --export > gpg.key

Install grub

Important
It was mentioned above, that our EFI partition mountpoint is /boot
Important
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
FILE grub.cfg
set superusers="root"
password_pbkdf2 root grub.pbkdf2.sha512.10000.YOURHASHHERE

...

menuentry 'Gentoo GNU/Linux' --unrestricted
{
	...
}
Note
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

Warning
Preinstalled by the motherboard manufacturer keys will be lost!
Note
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.

CODE
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.

Warning
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

FILE /boot/grub/grub.cfg
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
}
Warning
grub.cfg should be signed using gpg after every change
Note
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
Warning
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
and check presented sections

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:

FILE /boot/grub/grub.cfg
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
}
Warning
grub.cfg should be signed using gpg after every change
Note
After every update kernel should be signed with secureboot key and gpg key, and initramfs with gpg key only.