diff --git a/README.md b/README.md index 70fb540..0540df6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,94 @@ +## Coreboot+Grub2 for Purism Librem 14 +### Purposes +Currently [Purism](https://puri.sm) offers two possible boot options, coreboot+SeaBIOS, which is comparable to a standard BIOS legacy boot process and coreboot+HEADS, modified and renamed as [PureBoot](https://docs.puri.sm/PureBoot.html) which attempts to build a trust chain for the boot process and detect tampering if used in addition with the Librem Key. -sudo qubes-dom0-update python3-dnf-plugin-post-transaction-actions \ No newline at end of file +Although PureBoot has evident benefits, it has also a lot of added complexity, still needs a `/boot` partition unencrypted (even if it not expected to contain sensitive data) and to be fully secure it needs external hardware, the [Librem Key](https://puri.sm/products/librem-key/). + +Before getting the Librem 14 I used to run [Qubes OS](https://qubes-os.org) on an old [Thinkpad X220 with coreboot and with Grub2 as a payload](https://git.lsd.cat/g/thinkpad-coreboot-qubes). The idea of using Grub2 directly in order to gain real full disk encryption comes mostly from the [libreboot](https://libreboot.org) project. + +### Security considerations +The Librem 14 ships with a "write protection" screw for the coreboot flash chip on the mainboard. Assuming that it can be trusted, and that additional anti tampering measures are in place once the coreboot+Grub2 firmware has been written, it should be unmodifiable. The `/boot` partition, which is normally unencrypted, is now inside the same LUKS volume as the `root` volume and thus has the same properties and is secure as your passphrase and LUKS can be. + +The only physical attack vector, except for attack vectors such as a `bootrom` exploit, is the disassembly of the laptop, removal of the "write protection" and flash of a malicious firmware. In this scenario we using glitter on the screws to detect disassembly. PureBoot uses instead the Librem Key and that is definitely a more robust solution, as would be a TPM TOTP anti evil maid mechanism, which would be really a nice addition to Grub itself but is currently not supported. + +By default, Grub2 will boot the default option (encrypted Qubes). Since sometimes updates may fail and the system can fail to boot and recovery would otherwise require either changes to the firmware or extracting the storage to mount the system to repair, USB boot is supported but protected by password. Any Grub2 console access, manual modification or action which is not the default boot option in its state also requires a password. The password is set in the `grub.cfg` hardcoded in the CBFS. + +### How does it work? +Coreboot is compiled as normally for the Librem 14, with Grub2 as a payload. The `grub.conf` file is modified prior to flashing and is generic enough not to need any edits under normal circumstances. Since the Qubes installer does not support partitioning encrypting `/boot`, it is necessary to manually edit the partition scheme after installation. + +#### Pros + * Faster boot time + * Almost comparable security with Pureboot + * Encrypted `boot` + * Does not normally need `grub.cfg` updates + +#### Cons + * Anti evil maid relies on glitter (lol) + * Need to unscrew the "write protection" to update firmware + * LUKS password has to be entered twice or a keyfile has to be added + +### Installation instructions +#### Warning +Data loss is extremely probable and firmware corruption is rare but not impossible. The first can be avoided doing a full `dd` backup of the involved disk or performing the operation on a new laptop/disk. The latter may result in having the laptop in a bricked state and recovery could require hardware flashing. Do not attempt the procedure if you have no mean of hardware unbricking. This setup is unsupported by both me and Purism. Use at your own risk. + +#### Prerequisites + * Purism Librem 14 + * USB drive for Qubes installation + * USB drive for live system (Fedora/PureOS) + * Screwdriver kit + * Nail polish glitter + +### Procedure +#### Install Qubes (Step 1) + + * Install QubesOS from the USB drive on the target storage with LUKS and LVM + * Note down the target drive, `/dev/sdX` + +#### Boot live distro (Step 2) + + * Reboot into a Debian or Fedora based distro, such as PureOS or Fedora + * Connect to the internet + * Clone this repository + * Write down the Librem 14 serial in `resources/serial-number.txt` + * Run `mkpassword.sh` to generate a Grub2 password + * If desired, change the background image for grub in `resources/background.jpg`. It must be a `1920*1080` JPG of size below 300KB. + +### Build coreboot+Grub2 (Step 3) + + * Run `build.sh` as root and check that it completes successfully + +*If `build.sh` fails, stop here. It does not make any sense to proceed without a valid firmware image* + +### Encrypt /boot (Step 4) + + * Run `encryptboot.sh /dev/sdX` as root. The parameter indicates the target device noted down at Step 1. + +*If `encryptboot.sh` fails stop here. It does not make any sense to flash coreboot+Grub2 if it is unable to properly boot.* + +### Flash the firmware (Step 5) + + * Run `flash.sh` as root. + +*If `flash.sh` fails, do not reboot or turn off your computer. If you believe the failure left your firmware in an unknown state, run `recover.sh`* + +### Boot into Qubes and set update hooks (Step 6) + + * Run the following command in `dom0` + + `sudo qubes-dom0-update python3-dnf-plugin-post-transaction-actions` + + * Copy `resources/00-kernel-xen-symlink.conf` from this repository to `dom0` `/etc/dnf/plugins/post-transaction-actions.d/00-kernel-xen-symlink.conf` + +Since editing `grub.cfg` requires a re flash, that we would like to avoid, the supplied configuration files boots qubes using some symlinks: + * /boot/vmlinuz-latest + * /boot/initramfs-latest + * /boot/xen-latest-latest + +Since those names are not standard, in order to be able to update Qubes Linux Kernel and Xen without manual intervention, the aforementioned procedure creates a post transaction hook for `dnf` that automatically updates those symlinks. + +### Tun on hardware write protection (Step 7) +### Add glitter to screws and joints (Step 8) + +### Community + +### References \ No newline at end of file diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 439c163..8006696 --- a/build.sh +++ b/build.sh @@ -20,13 +20,14 @@ DEPS_GRUB2_FEDORA=(autoconf automake gettext-devel freetype-devel unifont-fonts) #local dirs LOCAL_TOOLS_PATH="./tools" -LOCAL_FIRMWARE_PATH="./firmware" LOCAL_TEMP_DIR="$(mktemp -d)" # locally compiled coreboot utils CBFSTOOL_CB="./coreboot/util/cbfstool/cbfstool" IFDTOOL_CB="./coreboot/util/ifdtool/ifdtool" +BACKUP_DIR="./backups" + # functions, functions, functions check_root() { @@ -37,18 +38,9 @@ check_root() { print_info () { - clear - echo "################################################" - echo "## Purism Librem coreboot Utility " - echo "################################################" - if [[ "${LIBREM_MODEL^^}" != "UNKNOWN" ]]; then - model=${LIBREM_MODEL^} - echo "# Device: Librem ${model//_/ }" - echo "# Serial: ${DMIDECODE_SERIAL_NUMBER}" - echo "# Firmware: ${FW_TYPE_STRING}" - echo "# Version: ${CURRENT_FW_VER} ${CURRENT_FW_DATE}" - echo "################################################" - fi + echo "################################" + echo "Librem 14 coreboot+Grub2 builder " + echo "################################" } @@ -333,7 +325,7 @@ set_serial_number () { # inject serial into coreboot image echo "Injecting serial number into firmware image" $CBFSTOOL_BIN "$1" remove -n serial_number -r COREBOOT >/dev/null 2>&1 - $CBFSTOOL_BIN "$1" add -n serial_number -t raw -f ${LOCAL_TEMP_DIR}/serial_number.txt -r COREBOOT >/dev/null 2>&1 + $CBFSTOOL_BIN "$1" add -n serial_number -t raw -f resources/serial_number.txt -r COREBOOT >/dev/null 2>&1 [ $? -ne 0 ] && die "Error adding serial number to file ${1}" fi } @@ -608,8 +600,6 @@ check_dependencies() { echo "One or more required dependencies are missing: ${needed[@]}" echo "The script will now attempt to install them for you" echo "" - read -rp "Press [Enter] to continue " discard - echo "" if [ $use_dnf = true ]; then if ! sudo dnf -y install "${needed[@]}" ; then die "Some required dependencies could not be installed automatically" diff --git a/encryptboot.sh b/encryptboot.sh new file mode 100755 index 0000000..cbfde0e --- /dev/null +++ b/encryptboot.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash + +DEVICE="${1}" +BACKUP_DIR="./backups" +DATE_FIX=$(date '+%Y%m%d-%H%M%S') +DD_OPTS="bs=512 iflag=fullblock conv=notrunc" +TARGET_BOOT="qubes_dom0-boot" + +welcome() { + echo "################################" + echo "This script will encrypt an unencrypted /boot partition" + echo "Confirmation will be asked before writing" + echo "################################" +} + +warning() { + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "The following steps may corrupt and lose your data, continue at your own risk" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + read -r +} + +adios() { + echo "[+] Procedure completed!" +} + +panic() { + echo "[*] Something went wrong in a write operation, system may be in a corrupted state. Attempting recovery" + restore + exit 1 +} + +restore() { + echo "[*] Attempting to restore original partition scheme" + dd if=${BACKUP_DIR}/mbr-${DATE_FIX}.img of=${DEVICE} bs=512 iflag=fullblock conv=notrunc status=progress + if [[ "${?}" -ne 0 ]]; then + echo "[-] Something went wrong restoring, hope you made a backup as advised ☠" + fi +} + +check_params() { + if [[ "${1}" -ne 1 ]]; then + echo "Usage: ./encryptboot.sh " + echo "Example: ./encryptboot.sh /dev/sda" + exit + fi +} + +check_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "[-] This script must be run as root; re-run prefixed with sudo" + exit 1 + fi +} + +check_device() { + if [[ ! -b "${DEVICE}" ]]; then + echo "[-] Device ${DEVICE} does not exists" + exit 1 + fi +} + +backup_boot() { + echo "[+] Backing up boot device" + mkdir -p "${BACKUP_DIR}" + dd if=${DEVICE}1 of=${BACKUP_DIR}/boot-${DATE_FIX}.img ${DD_OPTS} status=progress + if [[ "${?}" -ne 0 ]]; then + echo "[-] Something went wrong backing up boot partition, exiting" + exit 1 + fi + BOOT_HASH=$(sha256sum ${DEVICE}1 | cut -d ' ' -f 1) + BOOT_BACKUP_HASH=$(sha256sum ${BACKUP_DIR}/boot-${DATE_FIX}.img | cut -d ' ' -f 1) + if [[ ${BOOT_HASH} != ${BOOT_BACKUP_HASH} ]]; then + echo "[-] Backup ${BACKUP_DIR}/boot-${DATE_FIX}.img hash is not equal to ${DEVICE}1 hash, exiting" + exit 1 + fi + echo "[+] Backup successful" +} + +backup_partition_table() { + echo "[+] Backing up partition table" + mkdir -p "${BACKUP_DIR}" + dd if=${DEVICE} of=${BACKUP_DIR}/mbr-${DATE_FIX}.img ${DD_OPTS} count=1 + if [[ "${?}" -ne 0 ]]; then + echo "[-] Something went wrong backing up partition table, exiting" + exit 1 + fi +} + +check_headers() { + BOOT_HEADER=$(dd if=${DEVICE}1 ${DD_OPTS} count=16 2>/dev/null | file -s -) + LUKS_HEADER=$(dd if=${DEVICE}2 ${DD_OPTS} count=16 2>/dev/null | file -s -) + if [[ "${BOOT_HEADER}" != *"ext4"* ]]; then + echo "[-] ${DEVICE}1 is not an ext4 filesystem" + exit 1 + fi + if [[ "${LUKS_HEADER}" != *"LUKS"* ]]; then + echo "[-] ${DEVICE}2 is not a LUKS container" + exit + fi + echo "[+] Headers check completed" +} + +get_offsets() { + echo "[+] Getting boot partition offsets" + START_OFFSET=$(parted -s ${DEVICE} unit s print | grep boot | tr -s ' ' | cut -d ' ' -f 3 | tr -d 's') + END_OFFSET=$(parted -s ${DEVICE} unit s print | grep boot | tr -s ' ' | cut -d ' ' -f 4 | tr -d 's') + if [[ "${START_OFFSET}" -le 0 ]] || [[ "${END_OFFSET}" -le 0 ]] || [[ "${END_OFFSET}" -le ${START_OFFSET} ]]; then + echo "[-] Error parsing boot partition get_offsets" + exit 1 + fi + #OFFSET=$((${END_OFFSET}-${START_OFFSET})) + OFFSET=$((${END_OFFSET}+1)) +} + +delete_partitions() { + echo "[+] Deleting old partition scheme" + parted "${DEVICE}" rm 1 + if [[ "${?}" -ne 0 ]]; then + echo "[-] Something went wrong deleting boot partition" + panic + fi + parted "${DEVICE}" rm 2 + if [[ "${?}" -ne 0 ]]; then + echo "[-] Something went wrong deleting LUKS partition" + panic + fi +} + +create_partition() { + echo "[+] Creating new full disk partition" + parted -s ${DEVICE} mkpart primary luks 0% 100% + if [[ "${?}" -ne 0 ]]; then + echo "[-] Something went wrong creatig the new partition" + panic + fi +} + +check_offsets() { + echo ${START_OFFSET} + echo ${END_OFFSET} + echo ${OFFSET} + LUKS_HEADER=$(dd if=${DEVICE}1 ${DD_OPTS} skip=${OFFSET} seek=0 count=16 2>/dev/null | file -s -) + if [[ "${LUKS_HEADER}" != *"LUKS"* ]]; then + echo "[-] Luks header not found at given offset " + exit + fi + +} + +move_data() { + dd if=${DEVICE}1 of=${DEVICE}1 ${DD_OPTS} skip=${OFFSET} seek=0 status=progress + if [[ "${?}" -ne 0 ]]; then + echo "[-] Failed moving data backwards, hope you had backups because this is most likely total corruption. MBR and boot.img backups are in ${BACKUP_DIR}" + exit + fi +} + +config_luks_lvm() { + echo "[+] Extending LVM pool" + cryptsetup luksOpen ${DEVICE}1 qubespv + pvresize qubespv + echo "[+] Creating LVM boot partition" + lvcreate -n boot -l100%FREE ${TARGET_BOOT} +} + +restore_boot() { + echo "[+] Copying old boot image in new encrypted LVM volume " + dd if=${BACKUP_DIR}/boot-${DATE_FIX}.img of=/dev/mapper/${TARGET_BOOT} ${DD_OPTS} status=progress + if [[ "${?}" -ne 0 ]]; then + echo "[-] Failed to copy back boot.img to LVM, probably a recoverable state but needs manual intervention" + exit + fi + LVM_BOOT_HASH=$(sha256sum ${TARGET_BOOT} | cut -d ' ' -f 1) + if [[ ${BOOT_HASH} != ${LVM_BOOT_HASH} ]]; then + echo "[-] " + exit 1 + fi + echo "[+] Boot partition written back successfully" +} + +check_params "${#}" +welcome +check_root +check_device +backup_partition_table +backup_boot +check_headers +get_offsets +check_offsets +warning +#delete_partitions +#create_partition +#move_data +#config_luks_lvm +#restore_boot +adios diff --git a/resources/serial-number.txt b/resources/serial-number.txt new file mode 100644 index 0000000..e69de29