diff --git a/.gitignore b/.gitignore
index 995fa2c..23f4150 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
rpiboot
bin2c
+*.exe
diff --git a/Readme.md b/Readme.md
index 7a4397a..e247a37 100644
--- a/Readme.md
+++ b/Readme.md
@@ -50,7 +50,7 @@ standard firmware release then this will at the very least boot the linux kernel
you can build an initramfs into the kernel, add an initramfs to the boot directory or provide some
other interface to the filesystem.
-```
+```bash
sudo ./rpiboot -d boot
```
@@ -60,6 +60,69 @@ This will serve the boot directory to the Raspberry Pi Device.
On Compute Module 4 EMMC-DISABLE / nRPIBOOT (GPIO 40) must be fitted to switch the ROM to usbboot mode.
Otherwise, the SPI EEPROM bootloader image will be loaded instead.
+
+## Secure Boot
+TODO - Add link to whitepaper / user-guide
+
+### Host setup
+Secure boot require a 2048 bit RSA asymettric keypair and the Python `pycrytodomex` module to sign the EEPROM config and boot image.
+
+#### Install Python Crypto support (the pycryptodomex module)
+```bash
+python3 -m pip install pycryptodomex
+# or
+pip install pycryptodomex
+```
+
+#### Create an RSA key-pair using OpenSSL. Must be 2048 bits
+```bash
+cd $HOME
+openssl genrsa 2048 > private.pem
+```
+
+### Secure Boot - configuration
+* Please see the [secure boot EEPROM guide](secure-boot-recovery/README.md) to enable via rpiboot `recovery.bin`.
+* Please see the [secure boot MSD guide](secure-boot-msd/README.md) for instructions about to mount the EMMC via USB mass-storage once secure-boot has been enabled.
+
+## Secure Boot - image creation
+Secure boot requires a boot.img FAT image to be created. This plus a signature file (boot.sig)
+must be placed in the boot partition of the Raspberry Pi.
+
+The contents of the boot.img are the files normally present in the Raspberry Pi OS boot
+partition i.e. firmware, DTBs and kernel image. However, in order to reduce boot time
+it is advisible to remove unused files e.g. firmware or kernel images for Pi models.
+
+The firmware must be new enough to support secure boot. Either download the latest
+Raspberry Pi OS Bullseye OS image or alternateively, download the files
+for the `raspberrypi-bootloader` APT package directly from Github and use the files
+in the `boot` directory.
+
+`git clone --depth 1 --branch stable https://github.com/raspberrypi/firmware`
+
+A helper script (`make-boot-image`) is provided to automate the image creation process. This
+script depends upon the mkfs.fat and udisksctl tools and only runs on Linux.
+
+#### Clone the Raspberry Pi OS boot files
+Copy the contents of `/boot` to a local directory called `secure-boot-files`
+
+#### Set the kernel root device
+Verify that `cmdline.txt` in `secure-boot-files` points to the correct device for the root file-system.
+e.g. `root=/dev/mmcblk0p2` for the normal partition on CM4 EMMC.
+
+#### Create the boot image
+The `-p` product argument (pi4,pi400,cm4) tells the script to discard files which are not required by that product. This makes the image smaller and reduces the time taken to calculate the hash of the image file thereby reducing the boot time.
+```bash
+../tools/make-boot-image -d secure-boot-files -o boot.img -p pi4
+```
+
+#### Sign the boot image
+```bash
+../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
+```
+
+#### Copy the secure boot image to the device boot filesystem
+Copy `boot.img` and `boot.sig` to the chosen boot filesystem. Secure boot images can be loaded from any of the normal boot devices (e.g. SD, USB, Network).
+
### Raspberry Pi Imager - BETA
The Raspberry Pi Imager can be run natively on the CM4 providing a GUI for downloading and installing the operating system.
@@ -69,7 +132,7 @@ Beta notes:
* The HDMI display is limited to 1080p to avoid potential problems with cables etc if a 4K display is attached.
Run Raspberry Pi Imager:
-```
+```bash
sudo ./rpiboot -d imager
```
diff --git a/debian/rpiboot.install b/debian/rpiboot.install
index 671ac52..3091bb7 100644
--- a/debian/rpiboot.install
+++ b/debian/rpiboot.install
@@ -4,4 +4,13 @@ msd/*.bin usr/share/rpiboot/msd/
recovery/*.bin usr/share/rpiboot/recovery/
recovery/*.sig usr/share/rpiboot/recovery/
recovery/*.txt usr/share/rpiboot/recovery/
+secure-boot-recovery/*.txt usr/share/rpiboot/secure-boot-recovery/
+secure-boot-recovery/*.conf usr/share/rpiboot/secure-boot-recovery/
+secure-boot-recovery/*.md usr/share/rpiboot/secure-boot-recovery/
+secure-boot-recovery/*.bin usr/share/rpiboot/secure-boot-recovery/
+secure-boot-msd/*.bin usr/share/rpiboot/secure-boot-msd/
+secure-boot-msd/*.img usr/share/rpiboot/secure-boot-msd/
+secure-boot-msd/*.msd usr/share/rpiboot/secure-boot-msd/
+secure-boot-msd/*.md usr/share/rpiboot/secure-boot-msd/
+tools/* usr/share/rpiboot/tools/
debian/99-rpiboot.rules /lib/udev/rules.d
diff --git a/imager/README.md b/imager/README.md
new file mode 100644
index 0000000..564de8c
--- /dev/null
+++ b/imager/README.md
@@ -0,0 +1,17 @@
+# Signing the Raspberry Pi Imager for secure boot
+
+If secure-boot has been enabled then this image must be signed with
+the customer's RSA private key. Otherwise, the SPI EEPROM bootloader
+will refused to load this image.
+
+To do this run:
+
+```bash
+KEY_FILE=$HOME/private.pem
+../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
+```
+
+To run load the USB MSD device drivers via RPIBOOT run
+```bash
+../rpiboot -d .
+```
diff --git a/imager/bootcode4.bin b/imager/bootcode4.bin
index 17f66d0..5753561 100644
--- a/imager/bootcode4.bin
+++ b/imager/bootcode4.bin
diff --git a/recovery/update-pieeprom.sh b/recovery/update-pieeprom.sh
index ab56464..85af81f 100755
--- a/recovery/update-pieeprom.sh
+++ b/recovery/update-pieeprom.sh
@@ -3,13 +3,11 @@
# Utility to update the EEPROM image (pieeprom.bin) and signature
# (pieeprom.sig) with a new EEPROM config.
#
+# This script is now a thin wrapper for the new version in ../tools
+#
# pieeprom.original.bin - The source EEPROM from rpi-eeprom repo
# boot.conf - The bootloader config file to apply.
set -e
-script_dir="$(cd "$(dirname "$0")" && pwd)"
-
-${script_dir}/rpi-eeprom-config --config ${script_dir}/boot.conf --out ${script_dir}/pieeprom.bin ${script_dir}/pieeprom.original.bin
-sha256sum ${script_dir}/pieeprom.bin | awk '{print $1}' > ${script_dir}/pieeprom.sig
-echo "ts: $(date -u +%s)" >> "${script_dir}/pieeprom.sig"
+../tools/update-pieeprom.sh "$@"
diff --git a/secure-boot-msd/.gitignore b/secure-boot-msd/.gitignore
new file mode 100644
index 0000000..6edb4c7
--- /dev/null
+++ b/secure-boot-msd/.gitignore
@@ -0,0 +1,2 @@
+*.h
+boot.sig
diff --git a/secure-boot-msd/README.md b/secure-boot-msd/README.md
new file mode 100644
index 0000000..3480db8
--- /dev/null
+++ b/secure-boot-msd/README.md
@@ -0,0 +1,17 @@
+# USB MSD device mode drivers for signed-boot
+
+If secure-boot has been enabled then this image must be signed with
+the customer's RSA private key. Otherwise, the SPI EEPROM bootloader
+will refused to load this image.
+
+To do this run:
+
+```bash
+KEY_FILE=$HOME/private.pem
+../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
+```
+
+To run load the USB MSD device drivers via RPIBOOT run
+```bash
+../rpiboot -d .
+```
diff --git a/secure-boot-msd/boot.img b/secure-boot-msd/boot.img
new file mode 100644
index 0000000..4fe3adf
--- /dev/null
+++ b/secure-boot-msd/boot.img
diff --git a/secure-boot-msd/bootcode4.bin b/secure-boot-msd/bootcode4.bin
new file mode 100644
index 0000000..5753561
--- /dev/null
+++ b/secure-boot-msd/bootcode4.bin
diff --git a/secure-boot-msd/config.txt b/secure-boot-msd/config.txt
new file mode 100644
index 0000000..f6c35d5
--- /dev/null
+++ b/secure-boot-msd/config.txt
@@ -0,0 +1,5 @@
+# Load boot.img which contains usb.elf
+# In signed-boot or secure-boot mode the bootloader checks the
+# RSA signature of the ramdisk. The signature is located in boot.sig
+boot_ramdisk=1
+uart_2ndstage=1
diff --git a/secure-boot-recovery/.gitignore b/secure-boot-recovery/.gitignore
new file mode 100644
index 0000000..7bfd25b
--- /dev/null
+++ b/secure-boot-recovery/.gitignore
@@ -0,0 +1,2 @@
+pieeprom.bin
+pieeprom.sig
diff --git a/secure-boot-recovery/README.md b/secure-boot-recovery/README.md
new file mode 100644
index 0000000..2f57335
--- /dev/null
+++ b/secure-boot-recovery/README.md
@@ -0,0 +1,96 @@
+# Raspberry Pi 4 - secure boot
+
+This directory contains the beta bootcode4.bin (recovery.bin) and pieeprom-2021-05-19.bin
+bootloader release. Older bootloader and recovery.bin releases do not support secure boot.
+
+Steps for enabling secure boot:
+
+## Extra steps for Raspberry Pi 4B & Pi 400
+Raspberry Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO
+must be used to enable RPIBOOT if pulled low. The available GPIOs are 2,4,5,6,7,8
+since these are high by default.
+
+### Step 1 - Erase the EEPROM
+In order to avoid this OTP configuration being accidently set on Pi 4B / Pi 400
+this option can only be set via RPIBOOT. To force RPIBOOT on a Pi 4B / Pi 400
+erase the SPI EEPROM.
+
+Copy recovery.bin to a blank FAT32 formatted SD card with the following `config.txt` file.
+Then insert the SD card and boot the Pi and wait at least 10 seconds for the green
+LED to flash rapidly.
+```
+erase_eeprom=1
+```
+
+### Step 2 - Select the nRPIBOOT GPIO
+Then use rpiboot config.txt specify the GPIO to use for nRPIBOOT. For example:
+```
+program_rpiboot_gpio=8
+```
+
+The OTP setting for nRPIBOOT will then be set in the next steps when the
+EEPROM / secure-boot configuration is programmed.
+
+## Optional. Specify the private key file in an environment variable.
+Alternatively, specify the path when invoking the helper scripts.
+```bash
+export KEY_FILE="${HOME}/private.pem"
+```
+
+## Optional. Customize the EEPROM config.
+Custom with the desired bootloader settings.
+See: [Bootloader configuration](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md)
+
+Setting `SIGNED_BOOT=1` enables signed-boot mode so that the bootloader will only
+boot.img files signed with the specified RSA key. Since this is an EEPROM config
+option secure-boot can be tested and reverted via `RPIBOOT` at this stage.
+
+## Generate the signed bootloader image
+```bash
+cd secure-boot-recovery
+../tools/update-pieeprom.sh -k "${KEY_FILE}"
+```
+
+`pieeprom.bin` can then be flashed to the bootloader EEPROM via rpiboot.
+
+## Program the EEPROM image using rpiboot
+* Power off CM4
+* Set nRPIBOOT jumper and remove EEPROM WP protection
+```bash
+cd secure-boot-recovery
+../rpiboot -d .
+```
+* Power ON CM4
+
+## Locking secure-boot mode
+After verifying that the signed OS image boots successfully the system
+can be locked into secure-boot mode. This writes the hash of the
+customer public key to "one time programmable" (OTP) bits. From then
+onwards:
+
+* The bootloader will only load OS images signed with the customer private key.
+* The EEPROM configuration file must be signed with the customer private key.
+* It is not possible to install an old version of the bootloader that does
+ support secure boot.
+* **It is NOT possible to use a different private key to signed the OS images**
+
+**WARNING: THESE OPTIONS PERMANENTLY THE BCM2711 CHIP AND ARE IRREVERSIBLE.**
+
+To enable this edit the `config.txt` file in this directory and set
+`program_pubkey=1`
+
+* `program_pubkey` - If 1, write the hash of the customer's public key to OTP.
+* `revoke_devkey` - If 1, revoke the ROM bootloader development key which
+ requires secure-boot mode and prevents downgrades to bootloader versions that
+ don't support secure boot.
+
+ ** DO NOT SET THIS `revoke_devkey` UNTIL THE BOOTLOADER IS SIGNED WITH THE SECURE
+BOOT KEY. IT WILL PREVENT THE PI FROM BOOTING.**
+
+## Disabling VideoCore JTAG
+
+VideoCore JTAG may be permentantly disabled by setting `program_jtag_lock` in
+`config.txt`. This option has no effect unless `revoke_revkey=1` is set and
+the EEPROM and customer OTP key were programmed successfully.
+
+See [config.txt](config.txt)
diff --git a/secure-boot-recovery/boot.conf b/secure-boot-recovery/boot.conf
new file mode 100644
index 0000000..09575e9
--- /dev/null
+++ b/secure-boot-recovery/boot.conf
@@ -0,0 +1,17 @@
+[all]
+BOOT_UART=1
+WAKE_ON_GPIO=0
+POWER_OFF_ON_HALT=1
+HDMI_DELAY=0
+
+# SD, USB-MSD, BCM-USB-MSD, Network
+BOOT_ORDER=0xf2541
+
+# Disable self-update mode
+ENABLE_SELF_UPDATE=0
+
+# Select signed-boot mode in the EEPROM. This can be used to during development
+# to test the signed boot image. Once secure boot is enabled via OTP this setting
+# has no effect i.e. it is always 1.
+SIGNED_BOOT=1
+
diff --git a/secure-boot-recovery/bootcode4.bin b/secure-boot-recovery/bootcode4.bin
new file mode 100644
index 0000000..f9b1617
--- /dev/null
+++ b/secure-boot-recovery/bootcode4.bin
diff --git a/secure-boot-recovery/config.txt b/secure-boot-recovery/config.txt
new file mode 100644
index 0000000..85b14aa
--- /dev/null
+++ b/secure-boot-recovery/config.txt
@@ -0,0 +1,39 @@
+uart_2ndstage=1
+
+# Mark the EEPROM as write protected when the EEPROM /WIP pin is pulled low.
+# See https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md#eeprom_write_protect
+
+eeprom_write_protect=1
+
+# Uncomment to write to enable secure-boot by writing. This
+# locks the device to the public key in the EEPROM by storing the
+# sha256 hash of the public key in OTP.
+#
+# This option also prevents the ROM from loading recovery.bin from SD/EMMC
+# which means that the bootloader can only be updated via RPIBOOT or self-update.
+#
+# Uncomment program_pubkey=1 to enable this
+# WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
+
+#program_pubkey=1
+
+# Uncomment to revoke the ROM development key via OTP preventing older
+# bootloader or recovery.bin releases from running on this Pi
+# WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
+#
+# DO NOT SET THIS OPTION UNTIL THE BOOTLOADER IS SIGNED WITH THE SECURE
+# BOOT KEY. IT WILL PREVENT THE PI FROM BOOTING.
+#revoke_devkey=1
+
+# Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO
+# must be used to enable RPIBOOT if pulled low. The options are 2,4,5,6,7,8.
+#
+# This option has no effect on CM4.
+
+# WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
+#program_rpiboot_gpio=8
+
+# Permanently disable VideoCore JTAG access.
+# Warning: This option limits the ability to do failure analysis on
+# boards returned to resellers or Raspberry Pi Trading Ltd.
+#program_jtag_lock=1
diff --git a/secure-boot-recovery/pieeprom.original.bin b/secure-boot-recovery/pieeprom.original.bin
new file mode 100644
index 0000000..26cb552
--- /dev/null
+++ b/secure-boot-recovery/pieeprom.original.bin
diff --git a/tools/example-private.pem b/tools/example-private.pem
new file mode 100644
index 0000000..3f88c5c
--- /dev/null
+++ b/tools/example-private.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA+l3E+h/QNjrIR1cG6NpzP0fBwp2UDpuQAafXDS5yryrfCPDY
+TO9DvzAfOk9Dz/putDfHV0RTOFXv1tmc4nqOgU6nKx7tTdsjTiY4CgG3vXRMuAmD
+GX5ssJFCVmljGuILt1INlCmtun7Ow35VTxOcRDDfrBDKnSitzOTf6KTR7xJhqFFh
+dMpIg8hW4bDBKMavyt38pRvDaO1o01qaQT/GgAPmJm27y5RKNAe6iVTqsm4TMAhK
+C6P4XyRAbe6OMdFZyEWEk7Asexuc7uZlVHsUI6pebSW/07O+5l/U7/3k6r//hO/H
+DFOBUUW55EjzzC1BhTlWHWfZNI+5+NdN8o323QIDAQABAoIBAByQGZKSkhG5w5MV
+++ERWQARaurNyPAgsb1qnUdw8t8GlFLkDT07t74mWo2vsNQXpU0Upv6O+jKNZVMc
+2P/ijQL2Cu7JtLeC5mR6Sj7kAscPr1f4p9b+/B3puIh8tfSBcOY9a3Spi5sg7+xQ
+K6HdoiCKdd4evUrQMwHS47OaKCQuuibm46LWbXO1nk9QkymUy6zyaT5IuNpfKYKD
+UdFqV1FNwZ9A2Yb89rweBgU4DWdbjgVqBc23vS9l913rqd2LHN/4+XDBOGrovu5r
+mJy4WsyXuT0twuqi7FzhtbCdN/zhLo2od1XK6uA65EKdA9rrRMkNeGvxts6q3fPE
+i6tj7OECgYEA/YbIR8n8Vvb5XPAav/aAon4qjXyhkUTjnJfVT0yA+6T1AJwvQ+O4
+AhYgN4ld7msKRDJLcJs0EU8CmWUKJRt5Ai+JsOCbPuBNo+VGEFSsdG0mrSjFZf2e
+Bjm41lnvAEWReGwr9MVIf/prDE2/3aUl9irkNdu5q6NpG9M0N7AhzGECgYEA/M8Y
+Ew9Nv+XqEVKvOzxKRZBa6yzlOUj5PQ3cD7jl1aUNK4rTucvr3sJZAsgm5j+0XG99
+AJ447zdDEdcQbsOSaBR69pccdHYEaRSiIxWaCAir2BBS5DxYtgB6BLrIfBd1cKHv
+qB6u4M6FRJ5BcQa6VYlizAfG2yXoJv0xFrlQ2/0CgYEAwq0Alb+QOOckzCzDHayX
+Ui83VbXiCr6vWMtuTJoeYR1l1LYZxTPTVCbRTlP5AN7I310PeMR00uWsxUVE6QGT
+hg4i2ONf0oRCmhuwFVIvqqc2D7lC+vIoqfcg69fbIoZJEgNeLXJgHYWZNbVuIzBx
+WfnNi13R0O6GA4vGiQyCp4ECgYB1ZTG3wBaJsxlDnBLVPgT7UrJ1nO6A8HsUt/fl
+sSXBVRjNjHUPRTutwLAW050EtLZrajYw8EheBVp20VjHJrg47rG/CqLjDd60cSlt
+g114t5YdCk+DvuYu9f+zbI0m2rnlaL1iY4UvzZcjKx4Wf1pN2DNxrXbRU0P/vvlp
+pPqAfQKBgDZnxWuvRsT9rztGrEottifchfrStZx7u/2+iBtjFeFXr7L4MI14fNm2
+HkoThCpfFXCJFpRxy+kYi6xbPK/Om/hFNs3J5xqheTW8hFx7KN/zPg7jc0MlZ2R/
+uuOgZU9kkzLOamDyP85Doah7kAyA2PnLUno2k4IirbNVoH3aV++G
+-----END RSA PRIVATE KEY-----
diff --git a/tools/make-boot-image b/tools/make-boot-image
new file mode 100755
index 0000000..8d7badc
--- /dev/null
+++ b/tools/make-boot-image
@@ -0,0 +1,219 @@
+#!/bin/sh
+
+set -e
+
+TMP_DIR=""
+TMP_IMAGE=""
+IMAGE_SIZE=0
+MEGABYTE=$((1024 * 1024))
+BOOT_MOUNT=""
+LOOP=""
+
+# Define these environment variables to override mkfs options
+SECTOR_SIZE=${SECTOR_SIZE:-512}
+ROOT_DIR_ENTRIES=${ROOT_DIR_ENTRIES:-256}
+
+# Add 64k to the size calculation to reserve some space for the FAT,
+# directory entries and rounding up files to cluster sizes.
+FAT_OVERHEAD=${FAT_OVERHEAD:-64}
+
+cleanup() {
+ unmount_image
+
+ [ -z "${TMP_DIR}" ] && return
+ if [ -d "${TMP_DIR}" ]; then
+ rm -rf "${TMP_DIR}"
+ fi
+}
+
+die() {
+ echo "$@" >&2
+ exit 1
+}
+
+createfs() {
+ size_mb="$1"
+ image="$2"
+
+ volume_label="BOOT"
+ if [ -n "${SECTORS_PER_CLUSTER}" ]; then
+ SECTORS_PER_CLUSTER="-s ${SECTORS_PER_CLUSTER}"
+ fi
+
+ if [ -n "${FAT_SIZE}" ]; then
+ fat_size="-F ${FAT_SIZE}"
+ fi
+
+ sectors=$((size_mb * MEGABYTE / SECTOR_SIZE))
+ sectors=$((sectors / 2))
+ /sbin/mkfs.fat -C -f 1 \
+ ${SECTORS_PER_CLUSTER} -n "${volume_label}" \
+ ${fat_size} \
+ -S "${SECTOR_SIZE}" -r "${ROOT_DIR_ENTRIES}" "${image}" ${sectors} || \
+ die "Failed to create FAT filesystem"
+}
+
+mountfs() {
+ image="$1"
+
+ LOOP=$(udisksctl loop-setup -f "${image}" \
+ | grep loop \
+ | sed 's/.*\/dev\/loop\([0-9]*\).*/\/dev\/loop\1/')
+ [ -e "${LOOP}" ] || die "Failed to create loop device"
+
+ BOOT_MOUNT=$(udisksctl mount --options rw -b "${LOOP}" | sed 's/.*Mounted \/dev\/.* at \(.*\)\.$/\1/')
+ [ -d "${BOOT_MOUNT}" ] || die "Failed to mount bootfs @ ${BOOT_MOUNT}"
+
+ echo "Mounted ${LOOP} @ ${BOOT_MOUNT}"
+}
+
+unmount_image() {
+ if [ -d "${BOOT_MOUNT}" ]; then
+ udisksctl unmount -b "${LOOP}" > /dev/null || true
+ BOOT_MOUNT=""
+ fi
+ if [ -e "${LOOP}" ];then
+ udisksctl loop-delete -b "${LOOP}"
+ LOOP=""
+ fi
+}
+
+
+createstaging() {
+ source_dir="$1"
+ staging="$2"
+ board="$3"
+
+ mkdir -p "${staging}" || die "Failed to create ${staging}"
+ cp -a "${source_dir}/"* "${staging}"
+
+ # Remove files for previous hardware version
+ if [ "${board}" = "pi4" ] || [ "${board}" = "pi400" ] || [ "${board}" = "cm4" ]; then
+ (
+ cd "${staging}"
+ rm -f kernel.img kernel7.img bootcode.bin
+ rm -f start.elf fixup.dat start_cd.elf fixup_cd.dat start_db.elf fixup_db.dat start_x.elf fixup_x.dat
+ rm -f start4cd.elf fixup4cd.dat
+ rm -f start4db.elf fixup4db.dat
+ rm -f bcm2708* bcm2709* bcm2710*
+ rm -f bootcode.bin
+ )
+ fi
+
+ if [ "${ARCH}" = 32 ]; then
+ rm -f "${staging}/kernel8.img"
+ elif [ "${ARCH}" = 64 ]; then
+ rm -f "${staging}/kernel7l.img"
+ fi
+
+ if [ "${board}" = pi400 ]; then
+ rm -f "${staging}/start4x.elf"
+ rm -f "${staging}/fixup4x.dat"
+ fi
+ # Estimate the size of the image in KBs
+ content="${TMP_DIR}/content.tar"
+ echo "$(cd "${staging}"; ls -R)"
+ tar -cf "${content}" "${staging}" > /dev/null 2>&1
+ IMAGE_SIZE=$(stat --printf "%s" "${content}")
+ IMAGE_SIZE=$(((IMAGE_SIZE + 1023) / 1024))
+ rm -f "${content}"
+
+ # Add a little padding for FAT etc and convert to megabytes
+ IMAGE_SIZE=$((IMAGE_SIZE + FAT_OVERHEAD))
+ IMAGE_SIZE=$(((IMAGE_SIZE + 1023) / 1024))
+
+ echo "Using IMAGE_SIZE of ${IMAGE_SIZE}"
+
+ if [ "${IMAGE_SIZE}" -gt 20 ]; then
+ echo "Warning: Large image size detected. Try removing unused files."
+ fi
+}
+
+checkDependencies() {
+ if [ ! -f /sbin/mkfs.fat ]; then
+ die "mkfs.fat is requried. Run this script on Linux"
+ fi
+
+ if ! command -v udisksctl; then
+ die "udisksctl ot found. Try installing the udisks2 package."
+ fi
+}
+
+usage() {
+cat <LL', self._bytes, offset)
+ if magic == 0x0 or magic == 0xffffffff:
+ break # EOF
+ elif (magic & MAGIC_MASK) != MAGIC:
+ raise Exception('EEPROM is corrupted %x %x %x' % (magic, magic & MAGIC_MASK, MAGIC))
+
+ filename = ''
+ if magic == FILE_MAGIC: # Found a file
+ # Discard trailing null characters used to pad filename
+ filename = self._bytes[offset + 8: offset + FILE_HDR_LEN].decode('utf-8').replace('\0', '')
+ self._sections.append(ImageSection(magic, offset, length, filename))
+
+ offset += 8 + length # length + type
+ offset = (offset + 7) & ~7
+
+ def find_file(self, filename):
+ """
+ Returns the offset, length and whether this is the last section in the
+ EEPROM for a modifiable file within the image.
+ """
+ ret = (-1, -1, False)
+ for i in range(0, len(self._sections)):
+ s = self._sections[i]
+ if s.magic == FILE_MAGIC and s.filename == filename:
+ is_last = (i == len(self._sections) - 1)
+ ret = (s.offset, s.length, is_last)
+ break
+ debug('%s offset %d length %d last %s' % (filename, ret[0], ret[1], ret[2]))
+ return ret
+
+ def update(self, src_bytes, dst_filename):
+ """
+ Replaces a modifiable file with specified byte array.
+ """
+ hdr_offset, length, is_last = self.find_file(dst_filename)
+ if hdr_offset < 0:
+ raise Exception('Update target %s not found' % dst_filename)
+
+ if hdr_offset + len(src_bytes) + FILE_HDR_LEN > IMAGE_SIZE:
+ raise Exception('EEPROM image size exceeded')
+
+ new_len = len(src_bytes) + FILENAME_LEN + 4
+ struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len)
+ struct.pack_into(("%ds" % len(src_bytes)), self._bytes,
+ hdr_offset + 4 + FILE_HDR_LEN, src_bytes)
+
+ # If the new file is smaller than the old file then set any old
+ # data which is now unused to all ones (erase value)
+ pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes)
+
+ # Add padding up to 8-byte boundary
+ while pad_start % 8 != 0:
+ struct.pack_into('B', self._bytes, pad_start, 0xff)
+ pad_start += 1
+
+ # Create a padding section unless the padding size is smaller than the
+ # size of a section head. Padding is allowed in the last section but
+ # by convention bootconf.txt is the last section and there's no need to
+ # pad to the end of the sector. This also ensures that the loopback
+ # config read/write tests produce identical binaries.
+ pad_bytes = ALIGN_SIZE - (pad_start % ALIGN_SIZE)
+ if pad_bytes > 8 and not is_last:
+ pad_bytes -= 8
+ struct.pack_into('>i', self._bytes, pad_start, PAD_MAGIC)
+ pad_start += 4
+ struct.pack_into('>i', self._bytes, pad_start, pad_bytes)
+ pad_start += 4
+
+ debug("pad %d" % pad_bytes)
+ pad = 0
+ while pad < pad_bytes:
+ struct.pack_into('B', self._bytes, pad_start + pad, 0xff)
+ pad = pad + 1
+
+ def update_key(self, src_pem, dst_filename):
+ """
+ Replaces the specified public key entry with the public key values extracted
+ from the source PEM file.
+ """
+ pubkey_bytes = pemtobin(src_pem)
+ self.update(pubkey_bytes, dst_filename)
+
+ def update_file(self, src_filename, dst_filename):
+ """
+ Replaces the contents of dst_filename in the EEPROM with the contents of src_file.
+ """
+ src_bytes = open(src_filename, 'rb').read()
+ if len(src_bytes) > MAX_FILE_SIZE:
+ raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes."
+ % (src_filename, len(src_bytes), MAX_FILE_SIZE))
+ self.update(src_bytes, dst_filename)
+
+ def write(self):
+ """
+ Writes the updated EEPROM image to stdout or the specified output file.
+ """
+ if self._out is not None:
+ self._out.write(self._bytes)
+ self._out.close()
+ else:
+ if hasattr(sys.stdout, 'buffer'):
+ sys.stdout.buffer.write(self._bytes)
+ else:
+ sys.stdout.write(self._bytes)
+
+ def get_file(self, filename):
+ hdr_offset, length, is_last = self.find_file(filename)
+ offset = hdr_offset + 4 + FILE_HDR_LEN
+ config_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4]
+ return config_bytes
+
+ def read(self):
+ config_bytes = self.get_file('bootconf.txt')
+ if self._out is not None:
+ self._out.write(config_bytes)
+ self._out.close()
+ else:
+ if hasattr(sys.stdout, 'buffer'):
+ sys.stdout.buffer.write(config_bytes)
+ else:
+ sys.stdout.write(config_bytes)
+
+def main():
+ """
+ Utility for reading and writing the configuration file in the
+ Raspberry Pi 4 bootloader EEPROM image.
+ """
+ description = """\
+Bootloader EEPROM configuration tool for the Raspberry Pi 4.
+Operating modes:
+
+1. Outputs the current bootloader configuration to STDOUT if no arguments are
+ specified OR the given output file if --out is specified.
+
+ rpi-eeprom-config [--out boot.conf]
+
+2. Extracts the configuration file from the given 'eeprom' file and outputs
+ the result to STDOUT or the output file if --output is specified.
+
+ rpi-eeprom-config pieeprom.bin [--out boot.conf]
+
+3. Writes a new EEPROM image replacing the configuration file with the contents
+ of the file specified by --config.
+
+ rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin
+
+ The new image file can be installed via rpi-eeprom-update
+ rpi-eeprom-update -d -f newimage.bin
+
+4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update
+ to schedule an update of the bootloader when the system is rebooted.
+
+ Since this command launches rpi-eeprom-update to schedule the EEPROM update
+ it must be run as root.
+
+ sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin]
+
+ If the 'eeprom' argument is not specified then the latest available image
+ is selected by calling 'rpi-eeprom-update -l'.
+
+5. The '--edit' parameter behaves the same as '--apply' except that instead of
+ applying a predefined configuration file a text editor is launched with the
+ contents of the current EEPROM configuration.
+
+ Since this command launches rpi-eeprom-update to schedule the EEPROM update
+ it must be run as root.
+
+ The configuration file will be taken from:
+ * The blconfig reserved memory nvmem device
+ * The cached bootloader configuration 'vcgencmd bootloader_config'
+ * The current pending update - typically /boot/pieeprom.upd
+
+ sudo -E rpi-eeprom-config --edit [pieeprom.bin]
+
+ To cancel the pending update run 'sudo rpi-eeprom-update -r'
+
+ The default text editor is nano and may be overridden by setting the 'EDITOR'
+ environment variable and passing '-E' to 'sudo' to preserve the environment.
+
+6. Signing the bootloader config file.
+ Updates an EEPROM binary with a signed config file (created by rpi-eeprom-digest) plus
+ the corresponding RSA public key.
+
+ Requires Python Cryptodomex libraries and OpenSSL. To install on Raspberry Pi OS run:-
+ sudo apt install openssl python-pip
+ sudo python3 -m pip install cryptodomex
+
+ rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
+ rpi-eeprom-config --config bootconf.txt --digest bootconf.sig --pubkey public.pem --out pieeprom-signed.bin pieeprom.bin
+
+ Currently, the signing process is a separate step so can't be used with the --edit or --apply modes.
+
+
+See 'rpi-eeprom-update -h' for more information about the available EEPROM images.
+"""
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=description)
+
+ parser.add_argument('-a', '--apply', required=False,
+ help='Updates the bootloader to the given config plus latest available EEPROM release.')
+ parser.add_argument('-c', '--config', help='Name of bootloader configuration file', required=False)
+ parser.add_argument('-e', '--edit', action='store_true', default=False, help='Edit the current EEPROM config')
+ parser.add_argument('-o', '--out', help='Name of output file', required=False)
+ parser.add_argument('-d', '--digest', help='Signed boot only. The name of the .sig file generated by rpi-eeprom-dgst for config.txt ', required=False)
+ parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False)
+ parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input')
+ args = parser.parse_args()
+
+ if (args.edit or args.apply is not None) and os.getuid() != 0:
+ exit_error("--edit/--apply must be run as root")
+
+ if (args.edit or args.apply is not None) and not rpi4():
+ exit_error("--edit/--apply must run on a Raspberry Pi 4")
+
+ if args.edit:
+ edit_config(args.eeprom)
+ elif args.apply is not None:
+ if not os.path.exists(args.apply):
+ exit_error("config file '%s' not found" % args.apply)
+ apply_update(args.apply, args.eeprom, args.apply)
+ elif args.eeprom is not None:
+ image = BootloaderImage(args.eeprom, args.out)
+ if args.config is not None:
+ if not os.path.exists(args.config):
+ exit_error("config file '%s' not found" % args.config)
+ image.update_file(args.config, BOOTCONF_TXT)
+ if args.digest is not None:
+ image.update_file(args.digest, BOOTCONF_SIG)
+ if args.pubkey is not None:
+ image.update_key(args.pubkey, PUBKEY_BIN)
+ image.write()
+ else:
+ image.read()
+ elif args.config is None and args.eeprom is None:
+ current_config, config_src = read_current_config()
+ if args.out is not None:
+ open(args.out, 'w').write(current_config)
+ else:
+ sys.stdout.write(current_config)
+
+if __name__ == '__main__':
+ atexit.register(exit_handler)
+ main()
diff --git a/tools/rpi-eeprom-digest b/tools/rpi-eeprom-digest
new file mode 100755
index 0000000..58b4843
--- /dev/null
+++ b/tools/rpi-eeprom-digest
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+# Helper script to generate .sig files for use with the Raspberry Pi bootloader.
+
+# This has been implemented in a separate script in order to have avoid having
+# a hard dependency on OpenSSL.
+
+set -e
+
+OPENSSL=${OPENSSl:-openssl}
+
+die() {
+ echo "$@" >&2
+ exit 1
+}
+
+TMP_DIR=""
+cleanup() {
+ if [ -f "${TMP_DIR}" ]; then
+ rm -rf "${TMP_DIR}"
+ fi
+}
+
+checkDependencies() {
+ if ! command -v sha256sum > /dev/null; then
+ die "sha256sum not found. Try installing the coreutilities package."
+ fi
+
+ if ! command -v openssl > /dev/null; then
+ die "openssl not found. Try installing the openssl package."
+ fi
+
+ if ! command -v xxd > /dev/null; then
+ die "xxd not found. Try installing the xxd package."
+ fi
+}
+
+usage() {
+cat < "${OUTPUT}"
+
+# Include the update-timestamp
+echo "ts: $(date -u +%s)" >> "${OUTPUT}"
+
+if [ -n "${KEY}" ]; then
+ [ -f "${KEY}" ] || die "RSA private \"${KEY}\" not found"
+
+ "${OPENSSL}" dgst -sign "${KEY}" -keyform PEM -sha256 -out "${SIG_TMP}" "${IMAGE}"
+ echo "rsa2048: $(xxd -c 4096 -p < "${SIG_TMP}")" >> "${OUTPUT}"
+fi
diff --git a/tools/update-pieeprom.sh b/tools/update-pieeprom.sh
new file mode 100755
index 0000000..bc4146d
--- /dev/null
+++ b/tools/update-pieeprom.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+
+# Utility to update the EEPROM image (pieeprom.bin) and signature
+# (pieeprom.sig) with a new EEPROM config.
+#
+# pieeprom.original.bin - The source EEPROM from rpi-eeprom repo
+# boot.conf - The bootloader config file to apply.
+
+set -e
+
+script_dir="$(cd "$(dirname "$0")" && pwd)"
+
+# Minimum version for secure-boot support
+BOOTLOADER_SECURE_BOOT_MIN_VERSION=1632136573
+SRC_IMAGE="pieeprom.original.bin"
+CONFIG="boot.conf"
+DST_IMAGE="pieeprom.bin"
+PEM_FILE=""
+TMP_CONFIG_SIG=""
+
+die() {
+ echo "$@" >&2
+ exit ${EXIT_FAILED}
+}
+
+cleanup() {
+ [ -f "${TMP_CONFIG}" ] && rm -f "${TMP_CONFIG}"
+}
+
+usage() {
+cat <