Deniable encryption and shared boot partition

You must use a USB stick to store the detached boot partition while deploying deniable encryption. However, what if you have multiple computers? Do you need to bring tons of USB sticks every day?

YOU MUST KNOW HOW TO DEPLOY SINGLE-INSTANCE PLAIN ENCRYPTION BEFORE READING THIS DOC! https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#Plain_dm-crypt

I'm using archlinux, so I'm sharing my solution for Arch Linux and Manjaro Linux. I don't want to take many many USB sticks with me, which is suspicious. I also don't want to create many boot partitions, which forces me to carefully choose between many indistinguishable boot options on every boot. So the solution is:

Multiple linux installation sharing the same boot partition

The following problems need to get resolved:

  1. pacman -S linux always creates /boot/vmlinuz-linux, which shares the same filename. Every linux installation must use its own kernel, instead of a shared one.
  2. mkinitcpio always read /boot/vmlinuz-linux and creates initramfs with the same filename. This causes another conflict.
  3. Every installation has different encryption key, and has different rootfs device name. So each of them must have different kernel paramter set.
  4. All installations shares the same grub installation and the same grub.cfg. We need to make grub-mkconfig correctly generates boot entry for every installation.

NOTICE: THIS GUIDE IS OUTDATED. IT HAS BEEN MOVED TO https://git.recolic.net/root/shared-bootdir-helper

Let's do the following steps on every machine, with their own name.

1 - Name every machine

For example, I have 3 machines with deniable encryption enabled: RECOLICPC, RECOLICMPC, RECOLICMSMPC. We use this name across the whole configuration process.

2 - Create different filename for vmlinuz

That's easy. Since vmlinuz-linux are created by pacman -S linux (or linux-lts, or other kernels), we just add a post-transaction hook for the pacman package.

Create and customize /usr/share/libalpm/hooks/89-kernel-rename.hook:

[Trigger]
Type = Package
Operation = Install
Operation = Upgrade
# Set it to linux-lts, linux-zen, linux-surface or anything you need. 
Target = linux

[Action]
Description = Renaming vmlinuz to allow multiple installation shares the same /boot...
When = PostTransaction
Exec = /usr/bin/mv /boot/vmlinuz-linux /boot/vmlinuz-linux-recolicpc

This hook should be executed before 90-mkinitcpio-install.hook.

3 - Create different filename for initramfs (mkinitcpio)

You just need to manually rename them in /etc/mkinitcpio.d/linux.preset. Your distribution may use other presets, and you should know how to figure them out.

# mkinitcpio preset file for the 'linux' package

ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux-recolicpc"

PRESETS=('default' 'fallback')

#default_config="/etc/mkinitcpio.conf"
default_image="/boot/initramfs-linux-recolicpc.img"
#default_options=""

#fallback_config="/etc/mkinitcpio.conf"
fallback_image="/boot/initramfs-linux-recolicpc-fallback.img"
fallback_options="-S autodetect"

grub-mkconfig will be able to find them by prefix.

4 - Different kernel parameter for every installation

Usually, you set different GRUB_CMDLINE_LINUX_DEFAULT for every machine in /etc/default/grub. However, grub on one machine doesn't know the existence of others, so it generates grub.cfg with the same kernel parameter set for every kernel image.

Since I don't know any elegant solution, I created a script shared-boot-grub-mkconfig-helper.sh to solve this problem. And this script should be shared between all systems.

#!/bin/bash
#
# For shared boot partition between multiple installations, 
#   each kernel image may need different boot parameter, and 
#   it's not a good idea to manage them manually in grub.d. 
# So we set GRUB_CMDLINE_LINUX_DEFAULT to 
#    __KERNEL_PARAMETER_MANAGED_BY_HELPER, 
# and this script is intended to run after `grub-mkconfig`, 
#   which alters all `__KERNEL_PARAMETER_MANAGED_BY_HELPER`
#   to correct kernel parameters. 
#
# Usage: ./this.sh /boot/grub/grub.cfg

placeholder="__KERNEL_PARAMETER_MANAGED_BY_HELPER"
declare -A map_kimage_to_kparam=(
    # Parameters can not contain `|` character, which will crash this naive script. 
    ["vmlinuz-linux-recolicpc"]="quiet amdgpu.ppfeaturemask=0xffffffff cryptdevice=/dev/disk/by-id/nvme-SAMSUNG_MZVLW256HEHP-xxxxxxxxxxxx:cryptlvm:allow-discards cryptkey=/dev/disk/by-partlabel/xxxxxxxx:0:64 crypto=:aes-xts-plain64:512:0:"
    ["vmlinuz-4.19-x86_64"]="quiet cryptdevice=/dev/disk/by-id/ata-SAMSUNG_MZNTY128HDHP-000xxxxxxxxxx:cryptlvm:allow-discards cryptkey=/dev/disk/by-partlabel/xxxxxxx:0:64 crypto=:aes-xts-plain64:512:0: resume=/dev/RecolicmpcVolGroup/swap"
    ["vmlinuz-linux-recolicmsmpc"]="quiet cryptdevice=/dev/disk/by-id/xxxxxxxxxxxxxxxxxx:cryptlvm cryptkey=/dev/disk/by-partlabel/xxxxxxxx:0:64 crypto=xxxxxxxxxxxxxxxxxxxxxx"
)

########### implementation begin ##############
tmpfile="$(mktemp)"
inputfile="$1"
[[ "$inputfile" = "" ]] && echo "Usage: $0 /boot/grub/grub.cfg" && exit 1

while IFS= read -r line; do
    matched=0
    if [[ "$line" == *"$placeholder"* ]]; then
        for kimg_fname in "${!map_kimage_to_kparam[@]}"; do
            [[ "$line" == *"/$kimg_fname "* ]] &&
                echo "$line" | sed "s|$placeholder|${map_kimage_to_kparam[$kimg_fname]}|g" >> "$tmpfile" &&
                matched=1 && 
                break
        done
    fi
    [[ $matched == 0 ]] && echo "$line" >> "$tmpfile"
done < "$inputfile" || exit $?

mv "$tmpfile" "$inputfile" || exit $?

grep "$placeholder" "$inputfile" &&
    echo "Warning: placeholder '$placeholder' still exists in processed grub.cfg. Have you correctly set the 'map_kimage_to_kparam' of $0? Please double-check! " &&
    exit 2

exit 0

You need to run this script after grub-mkconfig -o /boot/grub/grub.cfg, which finalizes the generation of grub.cfg.