D-FENS
D-FENS

Reputation: 1521

Unlocking multiple LUKS-encrypted volumes with a single password at boot (Gentoo, encrypted ZFS root, genkernel initramfs)

With this post I am sharing a solution with the community.

I have a Gentoo system installed on a ZFS pool consisting of multiple encrypted devices. It is normally decrypted at boot as described in this post. In the file /etc/default/grub I add to the kernel line:

GRUB_CMDLINE_LINUX="dozfs crypt_roots=UUID=aaaaaaaa crypt_roots=UUID=bbbbbbbb ..."

where aaaaaaaa and bbbbbbbb stand for the UUID-s of the encrypted volumes as listed in /dev/disk/by-uuid, but there is an inconvenience: A password must be entered once for each volume. I use full disk encryption and 8 encrypted volumes, which would require 9 password entries on each boot, even though I use the same password for all the volumes. Quite a hassle!

Is it possible to decrypt all volumes with a single password entry?

My initramfs is created via sys-kernel/genkernel-next-69.

Upvotes: 1

Views: 1735

Answers (1)

D-FENS
D-FENS

Reputation: 1521

I have come up with a patch for genkernel that allows for decrypting multiple volumes with a single password (save the file as /tmp/genkernel_multicrypt.patch):

diff --git a/defaults/initrd.d/00-crypt-multicrypt.sh b/defaults/initrd.d/00-crypt-multicrypt.sh
new file mode 100755
index 0000000..848f06a
--- /dev/null
+++ b/defaults/initrd.d/00-crypt-multicrypt.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+MULTICRYPT_MAX_TRIES=4
+
+multicrypt_all_volumes_mapped()
+{
+    local k=0
+
+    while true; do
+        eval local voltype='"${MULTICRYPT_type'${k}'}"'
+        k=$(( k+1 ))
+
+        [ -n "$voltype" ] || return 0
+
+        local m=0
+        while true; do
+            eval local vol='"${MULTICRYPT_'${voltype}${m}'}"'
+            [ -n "$vol" ] || break
+            [ -e "/dev/mapper/${voltype}${m}" ] || return 1
+            m=$(( m+1 ))
+        done
+    done
+
+    return 0
+}
+
+multicrypt_open_volumes()
+{
+    local pass="$1"; shift
+
+    local k=0
+
+    while true; do
+        eval local voltype='"${MULTICRYPT_type'${k}'}"'
+        k=$(( k+1 ))
+
+        [ -n "$voltype" ] || return 0
+
+        local m=0
+        while true; do
+            local volname="${voltype}${m}"
+
+            eval local vol='"${MULTICRYPT_'${volname}'}"'
+            [ -n "$vol" ] || break
+
+            echo Decrypting vol ${volname} ${vol} ...
+            echo -n "$pass" | ${CRYPTSETUP_BIN} luksOpen /dev/disk/by-uuid/${vol} "${volname}" -d - || return 1
+            
+            m=$(( m+1 ))
+        done
+    done
+}
+
+multicrypt_execute() {
+
+    [ -z "${MULTICRYPT_type0}" ] && return 1   # multicrypt not requested
+
+    local root_or_swap=
+    if [ -n "${CRYPT_ROOTS}" ] || [ -n "${CRYPT_SWAPS}" ]; then
+        root_or_swap=1
+    fi
+
+    if ! multicrypt_all_volumes_mapped; then
+        echo "Opening multiple encrypted partitions..."
+    fi
+
+    local try=0
+    while ! multicrypt_all_volumes_mapped && [ "$try" -lt "$MULTICRYPT_MAX_TRIES" ]; do
+        try=$(( try+1 ))
+
+        echo -n "Password (try $try of $MULTICRYPT_MAX_TRIES): "
+        read -s pass
+        echo ""
+
+        multicrypt_open_volumes "$pass"
+
+        if [ $? -ne 0 ]; then
+            echo "ERROR: Decrypting one or more volumes failed."
+        fi
+    done
+
+    if [ -n "${root_or_swap}" ]; then
+        # We postponed the initialization of raid devices
+        # in order to avoid to assemble possibly degraded
+        # arrays.
+        start_volumes
+    fi
+}
diff --git a/defaults/initrd.d/00-crypt.sh b/defaults/initrd.d/00-crypt.sh
index 0e7c863..0515446 100755
--- a/defaults/initrd.d/00-crypt.sh
+++ b/defaults/initrd.d/00-crypt.sh
@@ -4,6 +4,7 @@
 . /etc/initrd.d/00-devmgr.sh
 . /etc/initrd.d/00-splash.sh
 . /etc/initrd.d/00-fsdev.sh
+. /etc/initrd.d/00-crypt-multicrypt.sh
 
 CRYPTSETUP_BIN="/sbin/cryptsetup"
 KEY_MNT="/mnt/key"
@@ -285,6 +286,8 @@ _open_luks() {
 
 start_luks() {
 
+    multicrypt_execute && return 0
+
     local root_or_swap=
     if [ -n "${CRYPT_ROOTS}" ] || [ -n "${CRYPT_SWAPS}" ]; then
         root_or_swap=1

The patch must be applied to genkernel as root in directory /usr/share/genkernel:

su

# Make a backup of genkernel if something goes wrong.
cp -r /usr/share/genkernel /root/genkernel-backup

cd /usr/share/genkernel
patch -p1 < /tmp/genkernel_multicrypt.patch

If everything goes right, the patch will be applied and you will not see any error messages.

Usage

First make sure your disks can be decrypted using the same password!!!

The patch modifies genkernel's initial ramdisks to support the following additional input parameters:

MULTICRYPT_type0=boot
MULTICRYPT_type1=root
MULTICRYPT_type2=tank

MULTICRYPT_boot0=aaaaaaaaa

MULTICRYPT_root0=bbbbbbbbb
MULTICRYPT_root1=ccccccccc

MULTICRYPT_tank0=ddddddddd
MULTICRYPT_tank1=eeeeeeeee
...

In the above example you can freely choose how many types you want to have, I used 3: boot, root, tank. For each type you can have one or more volume UUIDs as shown above.

Make sure the UUIDs are present in /dev/disk/by-uuid. Make sure the UUIDs are exact! Typos can lead to an unbootable system!

The devices will be added to the device mapper as boot0, root0, root1, etc.

Now, the kernel line must be modified as follows (simply append all parameters mentioned above to the kernel line along with your regular ones, for example dolvm, dozfs, etc.).

Do not delete your original kernel line yet, simply comment it out in case you want to revert.

GRUB_CMDLINE_LINUX="MULTICRYPT_type0=boot MULTICRYPT_type1=root MULTICRYPT_type2=tank MULTICRYPT_boot0=aaaaaaaaa MULTICRYPT_root0=bbbbbbbbb MULTICRYPT_root1=ccccccccc MULTICRYPT_tank0=ddddddddd MULTICRYPT_tank1=eeeeeeeee ..."

# As per standard grub configuration, we need to load the necessary modules and enable encryption:
GRUB_PRELOAD_MODULES="part_gpt search_fs_uuid cryptodisk luks linux"  # zfs lvm etc. if necessary
GRUB_ENABLE_CRYPTODISK="y"

When this chore is done, we can call genkernel do generate the new initramfs. Make sure you make a backup copy of your old one to enable easy recovery if something goes wrong. Compiling kernel and modules is not necessary.

Also, write down your original kernel line on a piece of paper, so that you can type it in Grub in case the system can't boot properly.

su
cp /boot/<YOUR-INITRAMFS> /boot/<YOUR-INITRAMFS>--copy

# This uses 8 threads for compilation, adjust accordingly.
# Add "--zfs" if your root is on ZFS.
genkernel --makeopts=-j8 --udev --luks --busybox --bootloader=grub2 --menuconfig --oldconfig --install initramfs

Your shiny new initramfs is installed in /boot. Now for the final touch - GRUB's configuration file must be generated to activate the new parameters:

grub-mkconfig -o /boot/grub/grub.cfg

At the time of writing grub-mkconfig generates an incorrect file that needs to be patched manually if your root is on ZFS. In the file /boot/grub/grub.cfg my root dataset is incorrectly specified as /ROOT/gentoo instead of zroot/ROOT/gentoo so I need to insert zroot in multiple locations. This should not be necessary once that particular bug is fixed.

Done! After rebooting the following prompt will be shown:

Opening multiple encrypted partitions...
Password (try 1 of 4):

Enter your password and (fingers crossed) all volumes should decrypt.

Troubleshooting

If your system becomes unbootable, you can use your old initramfs that we saved. When the Grub menu is shown, hit E and retype your original kernel line, appending "--saved" to your initramfs filename. This should boot your system as it was before.

If you are not satisfied with the patch, restore your original genkernel from /root/genkernel-backup to /usr/share/genkernel, then restore your original kernel line in /etc/default/grub and call genkernel and grub-mkconfig again:

genkernel --makeopts=-j8 --udev --luks --busybox --bootloader=grub2 --menuconfig --oldconfig --install initramfs
grub-mkconfig -o /boot/grub/grub.cfg

Caveat

For I use a full disk encryption, I still need to enter my password twice at boot. First, to decrypt /boot and load the kernel, then a second time to decrypt all other volumes. I am not aware of any solution for this.

State 17.02.2022

Upvotes: 1

Related Questions