diff --git a/host-support/config b/host-support/config index 7cae227..0bdaa8e 100644 --- a/host-support/config +++ b/host-support/config @@ -8,3 +8,5 @@ RPI_DEVICE_EEPROM_WP_SET= DEVICE_SERIAL_STORE=/usr/local/etc/rpi-sb-provisioner/seen DEMO_MODE_ONLY= RPI_SB_WORKDIR= +BOOT_IMAGE_VENDOR= +BOOT_IMAGE_MAINTAINER= diff --git a/host-support/ramdisk_cmdline.txt b/host-support/ramdisk_cmdline.txt new file mode 100644 index 0000000..6314b8c --- /dev/null +++ b/host-support/ramdisk_cmdline.txt @@ -0,0 +1 @@ +rootwait console=tty0 console=serial0,115200 root=/dev/ram0 diff --git a/host-support/ramdisk_internal_config.txt b/host-support/ramdisk_internal_config.txt new file mode 100644 index 0000000..f77af30 --- /dev/null +++ b/host-support/ramdisk_internal_config.txt @@ -0,0 +1,13 @@ +[all] +kernel=zImage +arm_64bit=1 +initramfs rootfs.cpio.zst +enable_uart=1 +uart_2ndstage=1 +disable_overscan=1 +cmdline=cmdline.txt + +[cm4] +dtoverlay=dwc2,dr_mode=host + +[none] diff --git a/host-support/terminal-functions.sh b/host-support/terminal-functions.sh index dbee795..e40bed3 100644 --- a/host-support/terminal-functions.sh +++ b/host-support/terminal-functions.sh @@ -130,4 +130,20 @@ get_fastboot_config_file() { else echo "/var/lib/rpi-sb-provisioner/boot_ramdisk_config.txt" fi -} \ No newline at end of file +} + +get_internal_config_file() { + if [ -f /etc/rpi-sb-provisioner/ramdisk_internal_config.txt ]; then + echo "/etc/rpi-sb-provisioner/ramdisk_internal_config.txt" + else + echo "/var/lib/rpi-sb-provisioner/ramdisk_internal_config.txt" + fi +} + +get_ramdisk_cmdline_file() { + if [ -f /etc/rpi-sb-provisioner/ramdisk_cmdline.txt ]; then + echo "/etc/rpi-sb-provisioner/ramdisk_cmdline.txt" + else + echo "/var/lib/rpi-sb-provisioner/ramdisk_cmdline.txt" + fi +} diff --git a/make-boot-image/README.md b/make-boot-image/README.md new file mode 100644 index 0000000..596a0f8 --- /dev/null +++ b/make-boot-image/README.md @@ -0,0 +1,35 @@ +# make-boot-image +A oneshot service to download the specified Raspberry Pi linux-image- and +create a replacement boot-image- package. This replacement package contains a +signed boot.img with a cryptroot-enabled initramfs. The kernel modules are +retained in the replacement package. Necessary firmware file are inserted into +the signed boot.img where appropriate (via raspi-firmware package). + +> [!CAUTION] +> Support only exists for v8 kernels at this time. + +## Configuration +- VENDOR +- OPENSSL +- CUSTOMER\_KEY\_FILE\_PEM + +## Usage +To create a replacement boot-image- package for linux-image-6.6.31+rpt-rpi-v8 +``` +systemctl start make-boot-image@$(systemd-escape 6.6.31+rpt-rpi-v8).service +``` + +To determine the latest v8 linux image (in order to run the service as +suggested above) +``` +META_PKG=linux-image-rpi-v8 +SRV=rpi-package-download@$(systemd-escape $META_PKG).service +systemctl start --wait $SRV \ + && grep-dctrl -F Package -X $META_PKG -n -s Depends /var/cache/$SRV/latest/Packages \ + | grep -o '^[[:graph:]]*' +``` + +The service makes use of systemd's CacheDirectory during execution. The boot-image- package created by the example given above would typically be found at: +``` +/var/cache/make-boot-image@6.6.31\x2brpt\x2drpi\x2dv8.service/boot-image--6.6.31+rpt-rpi-v8_6.6.31-1+rpt1_arm64.deb +``` diff --git a/make-boot-image/make-boot-image-from-kernel b/make-boot-image/make-boot-image-from-kernel new file mode 100755 index 0000000..4116e6f --- /dev/null +++ b/make-boot-image/make-boot-image-from-kernel @@ -0,0 +1,229 @@ +#!/usr/bin/env bash + +set -e + +# deps: +# - dpkg (dpkg-deb) +# - openssl +# - zstd +# - cpio + +. /usr/local/bin/terminal-functions.sh + +read_config + +TMPDIR="${TMPDIR:=/tmp}" + +if [[ -z "${1}" ]]; then + >&2 echo "No linux image specified" + exit 1 +fi + +if [[ -z "${RPI_DEVICE_FAMILY}" ]]; then + >&2 echo "'RPI_DEVICE_FAMILY' not specified" + exit 1 +fi + +if [[ -z "${BOOT_IMAGE_VENDOR}" ]]; then + >&2 echo "'BOOT_IMAGE_VENDOR' not specified" + exit 1 +fi + +if [[ -z "${BOOT_IMAGE_MAINTAINER}" ]]; then + >&2 echo "'BOOT_IMAGE_MAINTAINER' not specified" + exit 1 +fi + +if [[ -z "${OPENSSL}" || ! -f "${OPENSSL}" ]]; then + >&2 echo "'OPENSSL' not set or binary does not exist" + exit 1 +fi + +if [[ -z "${CUSTOMER_KEY_FILE_PEM}" || ! -f "${CUSTOMER_KEY_FILE_PEM}" ]]; then + >&2 echo "'CUSTOMER_KEY_FILE_PEM' not set or file does not exist" + exit 1 +fi + +LINUX_IMAGE="${1}" + +# Should be set by systemd +SERVICE_NAME="make-boot-image@$(systemd-escape $LINUX_IMAGE).service" +CACHE_DIRECTORY="${CACHE_DIRECTORY:=/var/cache/${SERVICE_NAME}}" +RUNTUME_DIRECTORY="${RUNTIME_DIRECTORY:=/run/${SERVICE_NAME}}" + +# TODO: Might be interesting to start rpi-package-download with --no-block to +# allow multiple simultaneous downloads. +function download_package() { + systemctl start \ + --wait \ + rpi-package-download@$(systemd-escape ${1}).service +} + +KERNEL_2711="linux-image-${LINUX_IMAGE}" +>&2 echo "Downloading ${KERNEL_2711}" +download_package "$KERNEL_2711" + +PACKAGE_NAME="boot-image-${BOOT_IMAGE_VENDOR}-${LINUX_IMAGE}" + +# Temp directory cleanup +TEMP_DIRS=() +function remove_temp_dirs() { + >&2 echo "Removing temporary directories" + for dir in "${TEMP_DIRS[@]}" + do + rm -rf "$dir" + done +} +trap remove_temp_dirs EXIT + +>&2 echo -n "Creating filesystem hierarchy for deb package: " +DEB_HIER="$(mktemp --directory --tmpdir debhier.XXX)" +TEMP_DIRS+=("${DEB_HIER}") +>&2 echo "${DEB_HIER}" + +>&2 echo -n "Create rootfs working directory: " +WORK_DIR="$(mktemp --directory --tmpdir boot-image-rootfs.XXX)" +TEMP_DIRS+=("${WORK_DIR}") +>&2 echo "${WORK_DIR}" + +function latest_pkg_dir() { + echo "/var/cache/rpi-package-download@$(systemd-escape ${1}).service/latest" +} + +>&2 echo "Extracting package contents" +dpkg-deb --raw-extract "$(latest_pkg_dir $KERNEL_2711)/package.deb" "${WORK_DIR}" + +function get_dctrl_field() { + grep-dctrl \ + --field=Package \ + --exact-match "${2}" \ + --no-field-names \ + --show-field="${3}" \ + "${1}" +} + +# Determine package version for later reuse +KERNEL_2711_VERSION="$(get_dctrl_field ${WORK_DIR}/DEBIAN/control ${KERNEL_2711} Version)" +>&2 echo "Extracted ${KERNEL_2711}, version ${KERNEL_2711_VERSION}" + +# rootfs kernel modules +>&2 echo "Copy kernel modules into deb package" +mkdir -p "${DEB_HIER}/lib/modules" +rsync -crt "${WORK_DIR}/lib/modules/"* "${DEB_HIER}/lib/modules" + +>&2 echo -n "Create ramdisk working directory: " +BFS_DIR="$(mktemp --directory --tmpdir boot-image-bootfs.XXX)" +TEMP_DIRS+=("${BFS_DIR}") +>&2 echo "${BFS_DIR}" + +# Kernel Images +>&2 echo "Copy kernel to ramdisk" +cp "${WORK_DIR}/boot/vmlinuz-${LINUX_IMAGE}" "${BFS_DIR}/zImage" + +# Overlays +>&2 echo "Copy overlays to ramdisk" +OVERLAY_PATH="${WORK_DIR}/usr/lib/${KERNEL_2711}/overlays" +rsync -crt "${OVERLAY_PATH}"/*.dtb* "${OVERLAY_PATH}/README" "${BFS_DIR}/overlays" + +# DTBs +>&2 echo "Copy DTBs to ramdisk" +DTB_PATH="${WORK_DIR}/usr/lib/${KERNEL_2711}/broadcom" +rsync -crt "${DTB_PATH}"/bcm27*.dtb "${BFS_DIR}" + +# Insert an initramfs +>&2 echo "Add cryptoot initramfs to ramdisk (with necessary kernel modules)" +INITRAMFS_EXTRACT="$(mktemp --directory --tmpdir initramfs-extract.XXX)" +TEMP_DIRS+=("${INITRAMFS_EXTRACT}") +zstd -q -d "$(get_cryptroot)" -o "${INITRAMFS_EXTRACT}/initramfs.cpio" +mkdir -p "${INITRAMFS_EXTRACT}/initramfs" +pushd "${INITRAMFS_EXTRACT}/initramfs" > /dev/null +cpio --quiet -id < ../initramfs.cpio > /dev/null +rm ../initramfs.cpio +pushd "${WORK_DIR}" > /dev/null +find lib/modules \ + \( \ + -name 'dm-mod.*' \ + -o \ + -name 'dm-crypt.*' \ + -o \ + -name 'af_alg.*' \ + -o \ + -name 'algif_skcipher.*' \ + -o \ + -name 'libaes.*' \ + -o \ + -name 'aes_generic.*' \ + -o \ + -name 'aes-arm64.*' \ + \) \ + -exec cp -r --parents "{}" "${INITRAMFS_EXTRACT}/initramfs/usr/" \; +popd > /dev/null +find . -print0 | cpio --quiet --null -ov --format=newc > ../initramfs.cpio 2> /dev/null +popd > /dev/null +zstd -q -6 "${INITRAMFS_EXTRACT}/initramfs.cpio" -o "${BFS_DIR}/rootfs.cpio.zst" + +# raspi-firmware +>&2 echo "Downloading raspi-firmware" +download_package raspi-firmware + +>&2 echo -n "Create temp directory to extract firmware: " +FW_EXTRACT_DIR="$(mktemp --directory --tmpdir boot-image-firmware.XXX)" +TEMP_DIRS+=("${FW_EXTRACT_DIR}") +>&2 echo "${FW_EXTRACT_DIR}" + +>&2 echo "Extracting firmware package contents" +dpkg-deb --raw-extract "$(latest_pkg_dir raspi-firmware)/package.deb" "${FW_EXTRACT_DIR}" + +>&2 echo "Add firmware to ramdisk" +rsync -v -crt "${FW_EXTRACT_DIR}/usr/lib/raspi-firmware/" "${BFS_DIR}" + +# cmdline.txt +>&2 echo "Add cmdline.txt to ramdisk" +cp "$(get_ramdisk_cmdline_file)" "${BFS_DIR}/cmdline.txt" + +# Inner config.txt +>&2 echo "Add config.txt to ramdisk" +cp "$(get_internal_config_file)" "${BFS_DIR}/config.txt" + +# Invoke make-boot-image +>&2 echo "Finalise ramdisk in deb package (boot.img)" +mkdir -p "${DEB_HIER}/boot/firmware" +make-boot-image \ + -b pi${RPI_DEVICE_FAMILY} \ + -d "${BFS_DIR}" \ + -o "${DEB_HIER}/boot/firmware/boot.img" > /dev/null + +# Outer config.txt +>&2 echo "Add config.txt to deb package (ensure boot.img is used)" +cp "$(get_fastboot_config_file)" "${DEB_HIER}/boot/firmware/config.txt" + +# boot.sig generation +>&2 echo "Signing ramdisk image" +sha256sum "${DEB_HIER}/boot/firmware/boot.img" | awk '{print $1}' > "${DEB_HIER}/boot/firmware/boot.sig" +echo -n "rsa2048: " >> "${DEB_HIER}/boot/firmware/boot.sig" +${OPENSSL} dgst \ + -sign "${CUSTOMER_KEY_FILE_PEM}" \ + -keyform PEM \ + -sha256 \ + "${DEB_HIER}/boot/firmware/boot.img" \ + | xxd -c 4096 -p >> "${DEB_HIER}/boot/firmware/boot.sig" + +# Insert control file +mkdir "${DEB_HIER}/DEBIAN" +echo \ +"Package: ${PACKAGE_NAME} +Source: linux +Version: ${KERNEL_2711_VERSION} +Architecture: arm64 +Maintainer: ${BOOT_IMAGE_MAINTAINER} +Section: kernel +Priority: optional +Homepage: https://github.com/raspberrypi/linux/ +Provides: ${KERNEL_2711} +Conflicts: ${KERNEL_2711} +Replaces: ${KERNEL_2711} +Description: Repackaged ${KERNEL_2711} for signed/cryptroot boot" \ +> "${DEB_HIER}/DEBIAN/control" + +# Create Debian package +dpkg-deb --build "${DEB_HIER}" "${CACHE_DIRECTORY}" diff --git a/make-boot-image/make-boot-image.service b/make-boot-image/make-boot-image.service new file mode 100644 index 0000000..09d1cb4 --- /dev/null +++ b/make-boot-image/make-boot-image.service @@ -0,0 +1,10 @@ +[Unit] +Description=Creates a signed boot image using a Raspberry Pi OS kernel / bootloader + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/make-boot-image-from-kernel "%I" +CacheDirectory=%n + +[Install] +WantedBy=multi-user.target diff --git a/nfpm.yaml b/nfpm.yaml index f6c6219..acf9219 100755 --- a/nfpm.yaml +++ b/nfpm.yaml @@ -144,6 +144,8 @@ depends: - diffutils - findutils - python3-textual + - dpkg + - zstd # Recommended packages. (overridable) # This will expand any env var you set in the field, e.g. ${RECOMMENDS_BLA} @@ -201,6 +203,12 @@ contents: - src: host-support/fastboot-gadget.img dst: /var/lib/rpi-sb-provisioner/fastboot-gadget.img + - src: host-support/ramdisk_internal_config.txt + dst: /var/lib/rpi-sb-provisioner/ramdisk_internal_config.txt + + - src: host-support/ramdisk_cmdline.txt + dst: /var/lib/rpi-sb-provisioner/ramdisk_cmdline.txt + - src: host-support/make-boot-image dst: /usr/local/bin/make-boot-image @@ -225,6 +233,12 @@ contents: - src: rpi-package-download/rpi-package-download dst: /usr/local/bin/rpi-package-download + - src: make-boot-image/make-boot-image.service + dst: /usr/local/lib/systemd/system/make-boot-image@.service + + - src: make-boot-image/make-boot-image-from-kernel + dst: /usr/local/bin/make-boot-image-from-kernel + - src: monitor/monitor.sh dst: /usr/local/bin/monitor.sh