Commit d1a9a5c659e8cfdb7c680ab7e77ecf985274c246

Authored by tim
1 parent 8bcd5ce6

beta: Add support for secure-boot - see Readme.md

Initial BETA release for secure-boot.

secure-boot adds two new sub-directory to usbboot:

* secure-boot-recovery is used to create an EEPROM signed with
  the customer's RSA private key and configures the 2711 OTP
  settings permenantely require signed boot images.
  See secure-boot-recovery/README.md
* Once secure-boot is enabled the CM4 MSD mode firmware must
  also be signed with the customer's RSA private key.
  See secure-boot-msd/README.md

N.B The revoke_devkey and program_jtag_lock are NOT enabled in
this initial BETA release.
.gitignore
1 1 rpiboot
2 2 bin2c
  3 +*.exe
... ...
Readme.md
... ... @@ -50,7 +50,7 @@ standard firmware release then this will at the very least boot the linux kernel
50 50 you can build an initramfs into the kernel, add an initramfs to the boot directory or provide some
51 51 other interface to the filesystem.
52 52  
53   -```
  53 +```bash
54 54 sudo ./rpiboot -d boot
55 55 ```
56 56  
... ... @@ -60,6 +60,69 @@ This will serve the boot directory to the Raspberry Pi Device.
60 60 On Compute Module 4 EMMC-DISABLE / nRPIBOOT (GPIO 40) must be fitted to switch the ROM to usbboot mode.
61 61 Otherwise, the SPI EEPROM bootloader image will be loaded instead.
62 62  
  63 +<a name="secure-boot"></a>
  64 +## Secure Boot
  65 +TODO - Add link to whitepaper / user-guide
  66 +
  67 +### Host setup
  68 +Secure boot require a 2048 bit RSA asymettric keypair and the Python `pycrytodomex` module to sign the EEPROM config and boot image.
  69 +
  70 +#### Install Python Crypto support (the pycryptodomex module)
  71 +```bash
  72 +python3 -m pip install pycryptodomex
  73 +# or
  74 +pip install pycryptodomex
  75 +```
  76 +
  77 +#### Create an RSA key-pair using OpenSSL. Must be 2048 bits
  78 +```bash
  79 +cd $HOME
  80 +openssl genrsa 2048 > private.pem
  81 +```
  82 +
  83 +### Secure Boot - configuration
  84 +* Please see the [secure boot EEPROM guide](secure-boot-recovery/README.md) to enable via rpiboot `recovery.bin`.
  85 +* 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.
  86 +
  87 +## Secure Boot - image creation
  88 +Secure boot requires a boot.img FAT image to be created. This plus a signature file (boot.sig)
  89 +must be placed in the boot partition of the Raspberry Pi.
  90 +
  91 +The contents of the boot.img are the files normally present in the Raspberry Pi OS boot
  92 +partition i.e. firmware, DTBs and kernel image. However, in order to reduce boot time
  93 +it is advisible to remove unused files e.g. firmware or kernel images for Pi models.
  94 +
  95 +The firmware must be new enough to support secure boot. Either download the latest
  96 +Raspberry Pi OS Bullseye OS image or alternateively, download the files
  97 +for the `raspberrypi-bootloader` APT package directly from Github and use the files
  98 +in the `boot` directory.
  99 +
  100 +`git clone --depth 1 --branch stable https://github.com/raspberrypi/firmware`
  101 +
  102 +A helper script (`make-boot-image`) is provided to automate the image creation process. This
  103 +script depends upon the mkfs.fat and udisksctl tools and only runs on Linux.
  104 +
  105 +#### Clone the Raspberry Pi OS boot files
  106 +Copy the contents of `/boot` to a local directory called `secure-boot-files`
  107 +
  108 +#### Set the kernel root device
  109 +Verify that `cmdline.txt` in `secure-boot-files` points to the correct device for the root file-system.
  110 +e.g. `root=/dev/mmcblk0p2` for the normal partition on CM4 EMMC.
  111 +
  112 +#### Create the boot image
  113 +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.
  114 +```bash
  115 +../tools/make-boot-image -d secure-boot-files -o boot.img -p pi4
  116 +```
  117 +
  118 +#### Sign the boot image
  119 +```bash
  120 +../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
  121 +```
  122 +
  123 +#### Copy the secure boot image to the device boot filesystem
  124 +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).
  125 +
63 126 ### Raspberry Pi Imager - BETA
64 127 The Raspberry Pi Imager can be run natively on the CM4 providing a GUI for downloading and installing the operating system.
65 128  
... ... @@ -69,7 +132,7 @@ Beta notes:
69 132 * The HDMI display is limited to 1080p to avoid potential problems with cables etc if a 4K display is attached.
70 133  
71 134 Run Raspberry Pi Imager:
72   -```
  135 +```bash
73 136 sudo ./rpiboot -d imager
74 137 ```
75 138  
... ...
debian/rpiboot.install
... ... @@ -4,4 +4,13 @@ msd/*.bin usr/share/rpiboot/msd/
4 4 recovery/*.bin usr/share/rpiboot/recovery/
5 5 recovery/*.sig usr/share/rpiboot/recovery/
6 6 recovery/*.txt usr/share/rpiboot/recovery/
  7 +secure-boot-recovery/*.txt usr/share/rpiboot/secure-boot-recovery/
  8 +secure-boot-recovery/*.conf usr/share/rpiboot/secure-boot-recovery/
  9 +secure-boot-recovery/*.md usr/share/rpiboot/secure-boot-recovery/
  10 +secure-boot-recovery/*.bin usr/share/rpiboot/secure-boot-recovery/
  11 +secure-boot-msd/*.bin usr/share/rpiboot/secure-boot-msd/
  12 +secure-boot-msd/*.img usr/share/rpiboot/secure-boot-msd/
  13 +secure-boot-msd/*.msd usr/share/rpiboot/secure-boot-msd/
  14 +secure-boot-msd/*.md usr/share/rpiboot/secure-boot-msd/
  15 +tools/* usr/share/rpiboot/tools/
7 16 debian/99-rpiboot.rules /lib/udev/rules.d
... ...
imager/README.md 0 โ†’ 100644
  1 +# Signing the Raspberry Pi Imager for secure boot
  2 +
  3 +If secure-boot has been enabled then this image must be signed with
  4 +the customer's RSA private key. Otherwise, the SPI EEPROM bootloader
  5 +will refused to load this image.
  6 +
  7 +To do this run:
  8 +
  9 +```bash
  10 +KEY_FILE=$HOME/private.pem
  11 +../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
  12 +```
  13 +
  14 +To run load the USB MSD device drivers via RPIBOOT run
  15 +```bash
  16 +../rpiboot -d .
  17 +```
... ...
imager/bootcode4.bin
No preview for this file type
recovery/update-pieeprom.sh
... ... @@ -3,13 +3,11 @@
3 3 # Utility to update the EEPROM image (pieeprom.bin) and signature
4 4 # (pieeprom.sig) with a new EEPROM config.
5 5 #
  6 +# This script is now a thin wrapper for the new version in ../tools
  7 +#
6 8 # pieeprom.original.bin - The source EEPROM from rpi-eeprom repo
7 9 # boot.conf - The bootloader config file to apply.
8 10  
9 11 set -e
10 12  
11   -script_dir="$(cd "$(dirname "$0")" && pwd)"
12   -
13   -${script_dir}/rpi-eeprom-config --config ${script_dir}/boot.conf --out ${script_dir}/pieeprom.bin ${script_dir}/pieeprom.original.bin
14   -sha256sum ${script_dir}/pieeprom.bin | awk '{print $1}' > ${script_dir}/pieeprom.sig
15   -echo "ts: $(date -u +%s)" >> "${script_dir}/pieeprom.sig"
  13 +../tools/update-pieeprom.sh "$@"
... ...
secure-boot-msd/.gitignore 0 โ†’ 100644
  1 +*.h
  2 +boot.sig
... ...
secure-boot-msd/README.md 0 โ†’ 100644
  1 +# USB MSD device mode drivers for signed-boot
  2 +
  3 +If secure-boot has been enabled then this image must be signed with
  4 +the customer's RSA private key. Otherwise, the SPI EEPROM bootloader
  5 +will refused to load this image.
  6 +
  7 +To do this run:
  8 +
  9 +```bash
  10 +KEY_FILE=$HOME/private.pem
  11 +../tools/rpi-eeprom-digest -i boot.img -o boot.sig -k "${KEY_FILE}"
  12 +```
  13 +
  14 +To run load the USB MSD device drivers via RPIBOOT run
  15 +```bash
  16 +../rpiboot -d .
  17 +```
... ...
secure-boot-msd/boot.img 0 โ†’ 100644
No preview for this file type
secure-boot-msd/bootcode4.bin 0 โ†’ 100644
No preview for this file type
secure-boot-msd/config.txt 0 โ†’ 100644
  1 +# Load boot.img which contains usb.elf
  2 +# In signed-boot or secure-boot mode the bootloader checks the
  3 +# RSA signature of the ramdisk. The signature is located in boot.sig
  4 +boot_ramdisk=1
  5 +uart_2ndstage=1
... ...
secure-boot-recovery/.gitignore 0 โ†’ 100644
  1 +pieeprom.bin
  2 +pieeprom.sig
... ...
secure-boot-recovery/README.md 0 โ†’ 100644
  1 +# Raspberry Pi 4 - secure boot
  2 +
  3 +This directory contains the beta bootcode4.bin (recovery.bin) and pieeprom-2021-05-19.bin
  4 +bootloader release. Older bootloader and recovery.bin releases do not support secure boot.
  5 +
  6 +Steps for enabling secure boot:
  7 +
  8 +## Extra steps for Raspberry Pi 4B & Pi 400
  9 +Raspberry Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO
  10 +must be used to enable RPIBOOT if pulled low. The available GPIOs are 2,4,5,6,7,8
  11 +since these are high by default.
  12 +
  13 +### Step 1 - Erase the EEPROM
  14 +In order to avoid this OTP configuration being accidently set on Pi 4B / Pi 400
  15 +this option can only be set via RPIBOOT. To force RPIBOOT on a Pi 4B / Pi 400
  16 +erase the SPI EEPROM.
  17 +
  18 +Copy recovery.bin to a blank FAT32 formatted SD card with the following `config.txt` file.
  19 +Then insert the SD card and boot the Pi and wait at least 10 seconds for the green
  20 +LED to flash rapidly.
  21 +```
  22 +erase_eeprom=1
  23 +```
  24 +
  25 +### Step 2 - Select the nRPIBOOT GPIO
  26 +Then use rpiboot config.txt specify the GPIO to use for nRPIBOOT. For example:
  27 +```
  28 +program_rpiboot_gpio=8
  29 +```
  30 +
  31 +The OTP setting for nRPIBOOT will then be set in the next steps when the
  32 +EEPROM / secure-boot configuration is programmed.
  33 +
  34 +## Optional. Specify the private key file in an environment variable.
  35 +Alternatively, specify the path when invoking the helper scripts.
  36 +```bash
  37 +export KEY_FILE="${HOME}/private.pem"
  38 +```
  39 +
  40 +## Optional. Customize the EEPROM config.
  41 +Custom with the desired bootloader settings.
  42 +See: [Bootloader configuration](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md)
  43 +
  44 +Setting `SIGNED_BOOT=1` enables signed-boot mode so that the bootloader will only
  45 +boot.img files signed with the specified RSA key. Since this is an EEPROM config
  46 +option secure-boot can be tested and reverted via `RPIBOOT` at this stage.
  47 +
  48 +## Generate the signed bootloader image
  49 +```bash
  50 +cd secure-boot-recovery
  51 +../tools/update-pieeprom.sh -k "${KEY_FILE}"
  52 +```
  53 +
  54 +`pieeprom.bin` can then be flashed to the bootloader EEPROM via rpiboot.
  55 +
  56 +## Program the EEPROM image using rpiboot
  57 +* Power off CM4
  58 +* Set nRPIBOOT jumper and remove EEPROM WP protection
  59 +```bash
  60 +cd secure-boot-recovery
  61 +../rpiboot -d .
  62 +```
  63 +* Power ON CM4
  64 +
  65 +## Locking secure-boot mode
  66 +After verifying that the signed OS image boots successfully the system
  67 +can be locked into secure-boot mode. This writes the hash of the
  68 +customer public key to "one time programmable" (OTP) bits. From then
  69 +onwards:
  70 +
  71 +* The bootloader will only load OS images signed with the customer private key.
  72 +* The EEPROM configuration file must be signed with the customer private key.
  73 +* It is not possible to install an old version of the bootloader that does
  74 + support secure boot.
  75 +* **It is NOT possible to use a different private key to signed the OS images**
  76 +
  77 +**WARNING: THESE OPTIONS PERMANENTLY THE BCM2711 CHIP AND ARE IRREVERSIBLE.**
  78 +
  79 +To enable this edit the `config.txt` file in this directory and set
  80 +`program_pubkey=1`
  81 +
  82 +* `program_pubkey` - If 1, write the hash of the customer's public key to OTP.
  83 +* `revoke_devkey` - If 1, revoke the ROM bootloader development key which
  84 + requires secure-boot mode and prevents downgrades to bootloader versions that
  85 + don't support secure boot.
  86 +
  87 + ** DO NOT SET THIS `revoke_devkey` UNTIL THE BOOTLOADER IS SIGNED WITH THE SECURE
  88 +BOOT KEY. IT WILL PREVENT THE PI FROM BOOTING.**
  89 +
  90 +## Disabling VideoCore JTAG
  91 +
  92 +VideoCore JTAG may be permentantly disabled by setting `program_jtag_lock` in
  93 +`config.txt`. This option has no effect unless `revoke_revkey=1` is set and
  94 +the EEPROM and customer OTP key were programmed successfully.
  95 +
  96 +See [config.txt](config.txt)
... ...
secure-boot-recovery/boot.conf 0 โ†’ 100644
  1 +[all]
  2 +BOOT_UART=1
  3 +WAKE_ON_GPIO=0
  4 +POWER_OFF_ON_HALT=1
  5 +HDMI_DELAY=0
  6 +
  7 +# SD, USB-MSD, BCM-USB-MSD, Network
  8 +BOOT_ORDER=0xf2541
  9 +
  10 +# Disable self-update mode
  11 +ENABLE_SELF_UPDATE=0
  12 +
  13 +# Select signed-boot mode in the EEPROM. This can be used to during development
  14 +# to test the signed boot image. Once secure boot is enabled via OTP this setting
  15 +# has no effect i.e. it is always 1.
  16 +SIGNED_BOOT=1
  17 +
... ...
secure-boot-recovery/bootcode4.bin 0 โ†’ 100644
No preview for this file type
secure-boot-recovery/config.txt 0 โ†’ 100644
  1 +uart_2ndstage=1
  2 +
  3 +# Mark the EEPROM as write protected when the EEPROM /WIP pin is pulled low.
  4 +# See https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md#eeprom_write_protect
  5 +
  6 +eeprom_write_protect=1
  7 +
  8 +# Uncomment to write to enable secure-boot by writing. This
  9 +# locks the device to the public key in the EEPROM by storing the
  10 +# sha256 hash of the public key in OTP.
  11 +#
  12 +# This option also prevents the ROM from loading recovery.bin from SD/EMMC
  13 +# which means that the bootloader can only be updated via RPIBOOT or self-update.
  14 +#
  15 +# Uncomment program_pubkey=1 to enable this
  16 +# WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
  17 +
  18 +#program_pubkey=1
  19 +
  20 +# Uncomment to revoke the ROM development key via OTP preventing older
  21 +# bootloader or recovery.bin releases from running on this Pi
  22 +# WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
  23 +#
  24 +# DO NOT SET THIS OPTION UNTIL THE BOOTLOADER IS SIGNED WITH THE SECURE
  25 +# BOOT KEY. IT WILL PREVENT THE PI FROM BOOTING.
  26 +#revoke_devkey=1
  27 +
  28 +# Pi 4B and Pi400 do not have a dedicated RPIBOOT jumper so a different GPIO
  29 +# must be used to enable RPIBOOT if pulled low. The options are 2,4,5,6,7,8.
  30 +#
  31 +# This option has no effect on CM4.
  32 +
  33 +# WARNING: THIS OPTION MODIFIES THE BCM2711 CHIP AND IS IRREVERSIBLE.
  34 +#program_rpiboot_gpio=8
  35 +
  36 +# Permanently disable VideoCore JTAG access.
  37 +# Warning: This option limits the ability to do failure analysis on
  38 +# boards returned to resellers or Raspberry Pi Trading Ltd.
  39 +#program_jtag_lock=1
... ...
secure-boot-recovery/pieeprom.original.bin 0 โ†’ 100644
No preview for this file type
tools/example-private.pem 0 โ†’ 100644
  1 +-----BEGIN RSA PRIVATE KEY-----
  2 +MIIEowIBAAKCAQEA+l3E+h/QNjrIR1cG6NpzP0fBwp2UDpuQAafXDS5yryrfCPDY
  3 +TO9DvzAfOk9Dz/putDfHV0RTOFXv1tmc4nqOgU6nKx7tTdsjTiY4CgG3vXRMuAmD
  4 +GX5ssJFCVmljGuILt1INlCmtun7Ow35VTxOcRDDfrBDKnSitzOTf6KTR7xJhqFFh
  5 +dMpIg8hW4bDBKMavyt38pRvDaO1o01qaQT/GgAPmJm27y5RKNAe6iVTqsm4TMAhK
  6 +C6P4XyRAbe6OMdFZyEWEk7Asexuc7uZlVHsUI6pebSW/07O+5l/U7/3k6r//hO/H
  7 +DFOBUUW55EjzzC1BhTlWHWfZNI+5+NdN8o323QIDAQABAoIBAByQGZKSkhG5w5MV
  8 +++ERWQARaurNyPAgsb1qnUdw8t8GlFLkDT07t74mWo2vsNQXpU0Upv6O+jKNZVMc
  9 +2P/ijQL2Cu7JtLeC5mR6Sj7kAscPr1f4p9b+/B3puIh8tfSBcOY9a3Spi5sg7+xQ
  10 +K6HdoiCKdd4evUrQMwHS47OaKCQuuibm46LWbXO1nk9QkymUy6zyaT5IuNpfKYKD
  11 +UdFqV1FNwZ9A2Yb89rweBgU4DWdbjgVqBc23vS9l913rqd2LHN/4+XDBOGrovu5r
  12 +mJy4WsyXuT0twuqi7FzhtbCdN/zhLo2od1XK6uA65EKdA9rrRMkNeGvxts6q3fPE
  13 +i6tj7OECgYEA/YbIR8n8Vvb5XPAav/aAon4qjXyhkUTjnJfVT0yA+6T1AJwvQ+O4
  14 +AhYgN4ld7msKRDJLcJs0EU8CmWUKJRt5Ai+JsOCbPuBNo+VGEFSsdG0mrSjFZf2e
  15 +Bjm41lnvAEWReGwr9MVIf/prDE2/3aUl9irkNdu5q6NpG9M0N7AhzGECgYEA/M8Y
  16 +Ew9Nv+XqEVKvOzxKRZBa6yzlOUj5PQ3cD7jl1aUNK4rTucvr3sJZAsgm5j+0XG99
  17 +AJ447zdDEdcQbsOSaBR69pccdHYEaRSiIxWaCAir2BBS5DxYtgB6BLrIfBd1cKHv
  18 +qB6u4M6FRJ5BcQa6VYlizAfG2yXoJv0xFrlQ2/0CgYEAwq0Alb+QOOckzCzDHayX
  19 +Ui83VbXiCr6vWMtuTJoeYR1l1LYZxTPTVCbRTlP5AN7I310PeMR00uWsxUVE6QGT
  20 +hg4i2ONf0oRCmhuwFVIvqqc2D7lC+vIoqfcg69fbIoZJEgNeLXJgHYWZNbVuIzBx
  21 +WfnNi13R0O6GA4vGiQyCp4ECgYB1ZTG3wBaJsxlDnBLVPgT7UrJ1nO6A8HsUt/fl
  22 +sSXBVRjNjHUPRTutwLAW050EtLZrajYw8EheBVp20VjHJrg47rG/CqLjDd60cSlt
  23 +g114t5YdCk+DvuYu9f+zbI0m2rnlaL1iY4UvzZcjKx4Wf1pN2DNxrXbRU0P/vvlp
  24 +pPqAfQKBgDZnxWuvRsT9rztGrEottifchfrStZx7u/2+iBtjFeFXr7L4MI14fNm2
  25 +HkoThCpfFXCJFpRxy+kYi6xbPK/Om/hFNs3J5xqheTW8hFx7KN/zPg7jc0MlZ2R/
  26 +uuOgZU9kkzLOamDyP85Doah7kAyA2PnLUno2k4IirbNVoH3aV++G
  27 +-----END RSA PRIVATE KEY-----
... ...
tools/make-boot-image 0 โ†’ 100755
  1 +#!/bin/sh
  2 +
  3 +set -e
  4 +
  5 +TMP_DIR=""
  6 +TMP_IMAGE=""
  7 +IMAGE_SIZE=0
  8 +MEGABYTE=$((1024 * 1024))
  9 +BOOT_MOUNT=""
  10 +LOOP=""
  11 +
  12 +# Define these environment variables to override mkfs options
  13 +SECTOR_SIZE=${SECTOR_SIZE:-512}
  14 +ROOT_DIR_ENTRIES=${ROOT_DIR_ENTRIES:-256}
  15 +
  16 +# Add 64k to the size calculation to reserve some space for the FAT,
  17 +# directory entries and rounding up files to cluster sizes.
  18 +FAT_OVERHEAD=${FAT_OVERHEAD:-64}
  19 +
  20 +cleanup() {
  21 + unmount_image
  22 +
  23 + [ -z "${TMP_DIR}" ] && return
  24 + if [ -d "${TMP_DIR}" ]; then
  25 + rm -rf "${TMP_DIR}"
  26 + fi
  27 +}
  28 +
  29 +die() {
  30 + echo "$@" >&2
  31 + exit 1
  32 +}
  33 +
  34 +createfs() {
  35 + size_mb="$1"
  36 + image="$2"
  37 +
  38 + volume_label="BOOT"
  39 + if [ -n "${SECTORS_PER_CLUSTER}" ]; then
  40 + SECTORS_PER_CLUSTER="-s ${SECTORS_PER_CLUSTER}"
  41 + fi
  42 +
  43 + if [ -n "${FAT_SIZE}" ]; then
  44 + fat_size="-F ${FAT_SIZE}"
  45 + fi
  46 +
  47 + sectors=$((size_mb * MEGABYTE / SECTOR_SIZE))
  48 + sectors=$((sectors / 2))
  49 + /sbin/mkfs.fat -C -f 1 \
  50 + ${SECTORS_PER_CLUSTER} -n "${volume_label}" \
  51 + ${fat_size} \
  52 + -S "${SECTOR_SIZE}" -r "${ROOT_DIR_ENTRIES}" "${image}" ${sectors} || \
  53 + die "Failed to create FAT filesystem"
  54 +}
  55 +
  56 +mountfs() {
  57 + image="$1"
  58 +
  59 + LOOP=$(udisksctl loop-setup -f "${image}" \
  60 + | grep loop \
  61 + | sed 's/.*\/dev\/loop\([0-9]*\).*/\/dev\/loop\1/')
  62 + [ -e "${LOOP}" ] || die "Failed to create loop device"
  63 +
  64 + BOOT_MOUNT=$(udisksctl mount --options rw -b "${LOOP}" | sed 's/.*Mounted \/dev\/.* at \(.*\)\.$/\1/')
  65 + [ -d "${BOOT_MOUNT}" ] || die "Failed to mount bootfs @ ${BOOT_MOUNT}"
  66 +
  67 + echo "Mounted ${LOOP} @ ${BOOT_MOUNT}"
  68 +}
  69 +
  70 +unmount_image() {
  71 + if [ -d "${BOOT_MOUNT}" ]; then
  72 + udisksctl unmount -b "${LOOP}" > /dev/null || true
  73 + BOOT_MOUNT=""
  74 + fi
  75 + if [ -e "${LOOP}" ];then
  76 + udisksctl loop-delete -b "${LOOP}"
  77 + LOOP=""
  78 + fi
  79 +}
  80 +
  81 +
  82 +createstaging() {
  83 + source_dir="$1"
  84 + staging="$2"
  85 + board="$3"
  86 +
  87 + mkdir -p "${staging}" || die "Failed to create ${staging}"
  88 + cp -a "${source_dir}/"* "${staging}"
  89 +
  90 + # Remove files for previous hardware version
  91 + if [ "${board}" = "pi4" ] || [ "${board}" = "pi400" ] || [ "${board}" = "cm4" ]; then
  92 + (
  93 + cd "${staging}"
  94 + rm -f kernel.img kernel7.img bootcode.bin
  95 + rm -f start.elf fixup.dat start_cd.elf fixup_cd.dat start_db.elf fixup_db.dat start_x.elf fixup_x.dat
  96 + rm -f start4cd.elf fixup4cd.dat
  97 + rm -f start4db.elf fixup4db.dat
  98 + rm -f bcm2708* bcm2709* bcm2710*
  99 + rm -f bootcode.bin
  100 + )
  101 + fi
  102 +
  103 + if [ "${ARCH}" = 32 ]; then
  104 + rm -f "${staging}/kernel8.img"
  105 + elif [ "${ARCH}" = 64 ]; then
  106 + rm -f "${staging}/kernel7l.img"
  107 + fi
  108 +
  109 + if [ "${board}" = pi400 ]; then
  110 + rm -f "${staging}/start4x.elf"
  111 + rm -f "${staging}/fixup4x.dat"
  112 + fi
  113 + # Estimate the size of the image in KBs
  114 + content="${TMP_DIR}/content.tar"
  115 + echo "$(cd "${staging}"; ls -R)"
  116 + tar -cf "${content}" "${staging}" > /dev/null 2>&1
  117 + IMAGE_SIZE=$(stat --printf "%s" "${content}")
  118 + IMAGE_SIZE=$(((IMAGE_SIZE + 1023) / 1024))
  119 + rm -f "${content}"
  120 +
  121 + # Add a little padding for FAT etc and convert to megabytes
  122 + IMAGE_SIZE=$((IMAGE_SIZE + FAT_OVERHEAD))
  123 + IMAGE_SIZE=$(((IMAGE_SIZE + 1023) / 1024))
  124 +
  125 + echo "Using IMAGE_SIZE of ${IMAGE_SIZE}"
  126 +
  127 + if [ "${IMAGE_SIZE}" -gt 20 ]; then
  128 + echo "Warning: Large image size detected. Try removing unused files."
  129 + fi
  130 +}
  131 +
  132 +checkDependencies() {
  133 + if [ ! -f /sbin/mkfs.fat ]; then
  134 + die "mkfs.fat is requried. Run this script on Linux"
  135 + fi
  136 +
  137 + if ! command -v udisksctl; then
  138 + die "udisksctl ot found. Try installing the udisks2 package."
  139 + fi
  140 +}
  141 +
  142 +usage() {
  143 +cat <<EOF
  144 +make-boot-image -d SOURCE_DIR -o OUTPUT
  145 +Options:
  146 + -a Select 32 or 64 bit kernel
  147 + -b Optionally prune the files to those required for the given board type.
  148 + -d The directory containing the files to include in the boot image.
  149 + -o The filename for the boot image. -h Display help text and exit
  150 +
  151 +Examples:
  152 +# Include all files in bootfs/
  153 +make-boot-image -d bootfs/ -o boot.img
  154 +
  155 +# Include only the files from bootfs/ required by Pi 4B
  156 +make-boot-image -b pi4 -d bootfs/ -o boot.img
  157 +
  158 +Environment variables:
  159 +The following environment variables may be specified to optionally override mkfs.vfat
  160 +arguments to help minimise the size of the boot image.
  161 +
  162 +
  163 +Name mkfs.vfat parameter
  164 +SECTOR_SIZE -S
  165 +ROOT_DIR_ENTRIES -r
  166 +AFT_SIZE -F
  167 +
  168 +EOF
  169 +exit 0
  170 +}
  171 +
  172 +SOURCE_DIR=""
  173 +OUTPUT=""
  174 +ARCH=32
  175 +while getopts a:b:d:ho: option; do
  176 + case "${option}" in
  177 + a) ARCH="${OPTARG}"
  178 + ;;
  179 + b) BOARD="${OPTARG}"
  180 + ;;
  181 + d) SOURCE_DIR="${OPTARG}"
  182 + ;;
  183 + o) OUTPUT="${OPTARG}"
  184 + ;;
  185 + h) usage
  186 + ;;
  187 + *) echo "Unknown argument \"${option}\""
  188 + usage
  189 + ;;
  190 + esac
  191 +done
  192 +
  193 +checkDependencies
  194 +
  195 +[ -d "${SOURCE_DIR}" ] || usage
  196 +[ -n "${OUTPUT}" ] || usage
  197 +
  198 +trap cleanup EXIT
  199 +TMP_DIR="$(mktemp -d)"
  200 +STAGING="${TMP_DIR}/staging"
  201 +
  202 +echo "Processing source files"
  203 +createstaging "${SOURCE_DIR}" "${STAGING}" "${BOARD}"
  204 +
  205 +echo "Creating FAT file system"
  206 +TMP_IMAGE="${TMP_DIR}/boot.img"
  207 +createfs ${IMAGE_SIZE} "${TMP_IMAGE}"
  208 +
  209 +echo "Copying files"
  210 +mountfs "${TMP_IMAGE}"
  211 +cp -a "${staging}"/* "${BOOT_MOUNT}"
  212 +
  213 +echo "Sync"
  214 +unmount_image
  215 +cp -f "${TMP_IMAGE}" "${OUTPUT}"
  216 +
  217 +echo "Created image"
  218 +file "${OUTPUT}"
  219 +ls -l "${OUTPUT}"
... ...
tools/rpi-eeprom-config 0 โ†’ 100755
  1 +#!/usr/bin/env python3
  2 +
  3 +"""
  4 +rpi-eeprom-config
  5 +"""
  6 +
  7 +import argparse
  8 +import atexit
  9 +import os
  10 +import subprocess
  11 +import string
  12 +import struct
  13 +import sys
  14 +import tempfile
  15 +import time
  16 +
  17 +IMAGE_SIZE = 512 * 1024
  18 +
  19 +# Larger files won't with with "vcgencmd bootloader_config"
  20 +MAX_FILE_SIZE = 2024
  21 +ALIGN_SIZE = 4096
  22 +BOOTCONF_TXT = 'bootconf.txt'
  23 +BOOTCONF_SIG = 'bootconf.sig'
  24 +PUBKEY_BIN = 'pubkey.bin'
  25 +
  26 +# Each section starts with a magic number followed by a 32 bit offset to the
  27 +# next section (big-endian).
  28 +# The number, order and size of the sections depends on the bootloader version
  29 +# but the following mask can be used to test for section headers and skip
  30 +# unknown data.
  31 +#
  32 +# The last 4KB of the EEPROM image is reserved for internal use by the
  33 +# bootloader and may be overwritten during the update process.
  34 +MAGIC = 0x55aaf00f
  35 +PAD_MAGIC = 0x55aafeef
  36 +MAGIC_MASK = 0xfffff00f
  37 +FILE_MAGIC = 0x55aaf11f # id for modifiable files
  38 +FILE_HDR_LEN = 20
  39 +FILENAME_LEN = 12
  40 +TEMP_DIR = None
  41 +
  42 +DEBUG = False
  43 +def debug(s):
  44 + if DEBUG:
  45 + sys.stderr.write(s + '\n')
  46 +
  47 +def rpi4():
  48 + compatible_path = "/sys/firmware/devicetree/base/compatible"
  49 + if os.path.exists(compatible_path):
  50 + with open(compatible_path, "rb") as f:
  51 + compatible = f.read().decode('utf-8')
  52 + if "bcm2711" in compatible:
  53 + return True
  54 + return False
  55 +
  56 +def exit_handler():
  57 + """
  58 + Delete any temporary files.
  59 + """
  60 + if TEMP_DIR is not None and os.path.exists(TEMP_DIR):
  61 + tmp_image = os.path.join(TEMP_DIR, 'pieeprom.upd')
  62 + if os.path.exists(tmp_image):
  63 + os.remove(tmp_image)
  64 + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf')
  65 + if os.path.exists(tmp_conf):
  66 + os.remove(tmp_conf)
  67 + os.rmdir(TEMP_DIR)
  68 +
  69 +def create_tempdir():
  70 + global TEMP_DIR
  71 + if TEMP_DIR is None:
  72 + TEMP_DIR = tempfile.mkdtemp()
  73 +
  74 +def pemtobin(infile):
  75 + """
  76 + Converts an RSA public key into the format expected by the bootloader.
  77 + """
  78 + # Import the package here to make this a weak dependency.
  79 + from Cryptodome.PublicKey import RSA
  80 +
  81 + arr = bytearray()
  82 + f = open(infile,'r')
  83 + key = RSA.importKey(f.read())
  84 +
  85 + if key.size_in_bits() != 2048:
  86 + raise Exception("RSA key size must be 2048")
  87 +
  88 + # Export N and E in little endian format
  89 + arr.extend(key.n.to_bytes(256, byteorder='little'))
  90 + arr.extend(key.e.to_bytes(8, byteorder='little'))
  91 + return arr
  92 +
  93 +def exit_error(msg):
  94 + """
  95 + Trapped a fatal error, output message to stderr and exit with non-zero
  96 + return code.
  97 + """
  98 + sys.stderr.write("ERROR: %s\n" % msg)
  99 + sys.exit(1)
  100 +
  101 +def shell_cmd(args):
  102 + """
  103 + Executes a shell command waits for completion returning STDOUT. If an
  104 + error occurs then exit and output the subprocess stdout, stderr messages
  105 + for debug.
  106 + """
  107 + start = time.time()
  108 + arg_str = ' '.join(args)
  109 + result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  110 +
  111 + while time.time() - start < 5:
  112 + if result.poll() is not None:
  113 + break
  114 +
  115 + if result.poll() is None:
  116 + exit_error("%s timeout" % arg_str)
  117 +
  118 + if result.returncode != 0:
  119 + exit_error("%s failed: %d\n %s\n %s\n" %
  120 + (arg_str, result.returncode, result.stdout.read(), result.stderr.read()))
  121 + else:
  122 + return result.stdout.read().decode('utf-8')
  123 +
  124 +def get_latest_eeprom():
  125 + """
  126 + Returns the path of the latest EEPROM image file if it exists.
  127 + """
  128 + latest = shell_cmd(['rpi-eeprom-update', '-l']).rstrip()
  129 + if not os.path.exists(latest):
  130 + exit_error("EEPROM image '%s' not found" % latest)
  131 + return latest
  132 +
  133 +def apply_update(config, eeprom=None, config_src=None):
  134 + """
  135 + Applies the config file to the latest available EEPROM image and spawns
  136 + rpi-eeprom-update to schedule the update at the next reboot.
  137 + """
  138 + if eeprom is not None:
  139 + eeprom_image = eeprom
  140 + else:
  141 + eeprom_image = get_latest_eeprom()
  142 + create_tempdir()
  143 + tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd')
  144 + image = BootloaderImage(eeprom_image, tmp_update)
  145 + image.write(config)
  146 + config_str = open(config).read()
  147 + if config_src is None:
  148 + config_src = ''
  149 + sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig_src: %s\nconfig: %s\n%s\n%s\n%s\n" %
  150 + (eeprom_image, config_src, config, '#' * 80, config_str, '#' * 80))
  151 +
  152 + sys.stdout.write("\n*** To cancel this update run 'sudo rpi-eeprom-update -r' ***\n\n")
  153 +
  154 + # Ignore APT package checksums so that this doesn't fail when used
  155 + # with EEPROMs with configs delivered outside of APT.
  156 + # The checksums are really just a safety check for automatic updates.
  157 + args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update]
  158 + resp = shell_cmd(args)
  159 + sys.stdout.write(resp)
  160 +
  161 +def edit_config(eeprom=None):
  162 + """
  163 + Implements something like 'git commit' for editing EEPROM configs.
  164 + """
  165 + # Default to nano if $EDITOR is not defined.
  166 + editor = 'nano'
  167 + if 'EDITOR' in os.environ:
  168 + editor = os.environ['EDITOR']
  169 +
  170 + config_src = ''
  171 + # If there is a pending update then use the configuration from
  172 + # that in order to support incremental updates. Otherwise,
  173 + # use the current EEPROM configuration.
  174 + bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip()
  175 + pending = os.path.join(bootfs, 'pieeprom.upd')
  176 + if os.path.exists(pending):
  177 + config_src = pending
  178 + image = BootloaderImage(pending)
  179 + current_config = image.get_config().decode('utf-8')
  180 + else:
  181 + current_config, config_src = read_current_config()
  182 +
  183 + create_tempdir()
  184 + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf')
  185 + out = open(tmp_conf, 'w')
  186 + out.write(current_config)
  187 + out.close()
  188 + cmd = "\'%s\' \'%s\'" % (editor, tmp_conf)
  189 + result = os.system(cmd)
  190 + if result != 0:
  191 + exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result))
  192 +
  193 + new_config = open(tmp_conf, 'r').read()
  194 + if len(new_config.splitlines()) < 2:
  195 + exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf)
  196 + apply_update(tmp_conf, eeprom, config_src)
  197 +
  198 +def read_current_config():
  199 + """
  200 + Reads the configuration used by the current bootloader.
  201 + """
  202 + fw_base = "/sys/firmware/devicetree/base/"
  203 + nvmem_base = "/sys/bus/nvmem/devices/"
  204 +
  205 + if os.path.exists(fw_base + "/aliases/blconfig"):
  206 + with open(fw_base + "/aliases/blconfig", "rb") as f:
  207 + nvmem_ofnode_path = fw_base + f.read().decode('utf-8')
  208 + for d in os.listdir(nvmem_base):
  209 + if os.path.realpath(nvmem_base + d + "/of_node") in os.path.normpath(nvmem_ofnode_path):
  210 + return (open(nvmem_base + d + "/nvmem", "rb").read().decode('utf-8'), "blconfig device")
  211 +
  212 + return (shell_cmd(['vcgencmd', 'bootloader_config']), "vcgencmd bootloader_config")
  213 +
  214 +class ImageSection:
  215 + def __init__(self, magic, offset, length, filename=''):
  216 + self.magic = magic
  217 + self.offset = offset
  218 + self.length = length
  219 + self.filename = filename
  220 + debug("ImageSection %x %x %x %s" % (magic, offset, length, filename))
  221 +
  222 +class BootloaderImage(object):
  223 + def __init__(self, filename, output=None):
  224 + """
  225 + Instantiates a Bootloader image writer with a source eeprom (filename)
  226 + and optionally an output filename.
  227 + """
  228 + self._filename = filename
  229 + self._sections = []
  230 + try:
  231 + self._bytes = bytearray(open(filename, 'rb').read())
  232 + except IOError as err:
  233 + exit_error("Failed to read \'%s\'\n%s\n" % (filename, str(err)))
  234 + self._out = None
  235 + if output is not None:
  236 + self._out = open(output, 'wb')
  237 +
  238 + if len(self._bytes) != IMAGE_SIZE:
  239 + exit_error("%s: Expected size %d bytes actual size %d bytes" %
  240 + (filename, IMAGE_SIZE, len(self._bytes)))
  241 + self.parse()
  242 +
  243 + def parse(self):
  244 + """
  245 + Builds a table of offsets to the different sections in the EEPROM.
  246 + """
  247 + offset = 0
  248 + magic = 0
  249 + found = False
  250 + while offset < IMAGE_SIZE:
  251 + magic, length = struct.unpack_from('>LL', self._bytes, offset)
  252 + if magic == 0x0 or magic == 0xffffffff:
  253 + break # EOF
  254 + elif (magic & MAGIC_MASK) != MAGIC:
  255 + raise Exception('EEPROM is corrupted %x %x %x' % (magic, magic & MAGIC_MASK, MAGIC))
  256 +
  257 + filename = ''
  258 + if magic == FILE_MAGIC: # Found a file
  259 + # Discard trailing null characters used to pad filename
  260 + filename = self._bytes[offset + 8: offset + FILE_HDR_LEN].decode('utf-8').replace('\0', '')
  261 + self._sections.append(ImageSection(magic, offset, length, filename))
  262 +
  263 + offset += 8 + length # length + type
  264 + offset = (offset + 7) & ~7
  265 +
  266 + def find_file(self, filename):
  267 + """
  268 + Returns the offset, length and whether this is the last section in the
  269 + EEPROM for a modifiable file within the image.
  270 + """
  271 + ret = (-1, -1, False)
  272 + for i in range(0, len(self._sections)):
  273 + s = self._sections[i]
  274 + if s.magic == FILE_MAGIC and s.filename == filename:
  275 + is_last = (i == len(self._sections) - 1)
  276 + ret = (s.offset, s.length, is_last)
  277 + break
  278 + debug('%s offset %d length %d last %s' % (filename, ret[0], ret[1], ret[2]))
  279 + return ret
  280 +
  281 + def update(self, src_bytes, dst_filename):
  282 + """
  283 + Replaces a modifiable file with specified byte array.
  284 + """
  285 + hdr_offset, length, is_last = self.find_file(dst_filename)
  286 + if hdr_offset < 0:
  287 + raise Exception('Update target %s not found' % dst_filename)
  288 +
  289 + if hdr_offset + len(src_bytes) + FILE_HDR_LEN > IMAGE_SIZE:
  290 + raise Exception('EEPROM image size exceeded')
  291 +
  292 + new_len = len(src_bytes) + FILENAME_LEN + 4
  293 + struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len)
  294 + struct.pack_into(("%ds" % len(src_bytes)), self._bytes,
  295 + hdr_offset + 4 + FILE_HDR_LEN, src_bytes)
  296 +
  297 + # If the new file is smaller than the old file then set any old
  298 + # data which is now unused to all ones (erase value)
  299 + pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes)
  300 +
  301 + # Add padding up to 8-byte boundary
  302 + while pad_start % 8 != 0:
  303 + struct.pack_into('B', self._bytes, pad_start, 0xff)
  304 + pad_start += 1
  305 +
  306 + # Create a padding section unless the padding size is smaller than the
  307 + # size of a section head. Padding is allowed in the last section but
  308 + # by convention bootconf.txt is the last section and there's no need to
  309 + # pad to the end of the sector. This also ensures that the loopback
  310 + # config read/write tests produce identical binaries.
  311 + pad_bytes = ALIGN_SIZE - (pad_start % ALIGN_SIZE)
  312 + if pad_bytes > 8 and not is_last:
  313 + pad_bytes -= 8
  314 + struct.pack_into('>i', self._bytes, pad_start, PAD_MAGIC)
  315 + pad_start += 4
  316 + struct.pack_into('>i', self._bytes, pad_start, pad_bytes)
  317 + pad_start += 4
  318 +
  319 + debug("pad %d" % pad_bytes)
  320 + pad = 0
  321 + while pad < pad_bytes:
  322 + struct.pack_into('B', self._bytes, pad_start + pad, 0xff)
  323 + pad = pad + 1
  324 +
  325 + def update_key(self, src_pem, dst_filename):
  326 + """
  327 + Replaces the specified public key entry with the public key values extracted
  328 + from the source PEM file.
  329 + """
  330 + pubkey_bytes = pemtobin(src_pem)
  331 + self.update(pubkey_bytes, dst_filename)
  332 +
  333 + def update_file(self, src_filename, dst_filename):
  334 + """
  335 + Replaces the contents of dst_filename in the EEPROM with the contents of src_file.
  336 + """
  337 + src_bytes = open(src_filename, 'rb').read()
  338 + if len(src_bytes) > MAX_FILE_SIZE:
  339 + raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes."
  340 + % (src_filename, len(src_bytes), MAX_FILE_SIZE))
  341 + self.update(src_bytes, dst_filename)
  342 +
  343 + def write(self):
  344 + """
  345 + Writes the updated EEPROM image to stdout or the specified output file.
  346 + """
  347 + if self._out is not None:
  348 + self._out.write(self._bytes)
  349 + self._out.close()
  350 + else:
  351 + if hasattr(sys.stdout, 'buffer'):
  352 + sys.stdout.buffer.write(self._bytes)
  353 + else:
  354 + sys.stdout.write(self._bytes)
  355 +
  356 + def get_file(self, filename):
  357 + hdr_offset, length, is_last = self.find_file(filename)
  358 + offset = hdr_offset + 4 + FILE_HDR_LEN
  359 + config_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4]
  360 + return config_bytes
  361 +
  362 + def read(self):
  363 + config_bytes = self.get_file('bootconf.txt')
  364 + if self._out is not None:
  365 + self._out.write(config_bytes)
  366 + self._out.close()
  367 + else:
  368 + if hasattr(sys.stdout, 'buffer'):
  369 + sys.stdout.buffer.write(config_bytes)
  370 + else:
  371 + sys.stdout.write(config_bytes)
  372 +
  373 +def main():
  374 + """
  375 + Utility for reading and writing the configuration file in the
  376 + Raspberry Pi 4 bootloader EEPROM image.
  377 + """
  378 + description = """\
  379 +Bootloader EEPROM configuration tool for the Raspberry Pi 4.
  380 +Operating modes:
  381 +
  382 +1. Outputs the current bootloader configuration to STDOUT if no arguments are
  383 + specified OR the given output file if --out is specified.
  384 +
  385 + rpi-eeprom-config [--out boot.conf]
  386 +
  387 +2. Extracts the configuration file from the given 'eeprom' file and outputs
  388 + the result to STDOUT or the output file if --output is specified.
  389 +
  390 + rpi-eeprom-config pieeprom.bin [--out boot.conf]
  391 +
  392 +3. Writes a new EEPROM image replacing the configuration file with the contents
  393 + of the file specified by --config.
  394 +
  395 + rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin
  396 +
  397 + The new image file can be installed via rpi-eeprom-update
  398 + rpi-eeprom-update -d -f newimage.bin
  399 +
  400 +4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update
  401 + to schedule an update of the bootloader when the system is rebooted.
  402 +
  403 + Since this command launches rpi-eeprom-update to schedule the EEPROM update
  404 + it must be run as root.
  405 +
  406 + sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin]
  407 +
  408 + If the 'eeprom' argument is not specified then the latest available image
  409 + is selected by calling 'rpi-eeprom-update -l'.
  410 +
  411 +5. The '--edit' parameter behaves the same as '--apply' except that instead of
  412 + applying a predefined configuration file a text editor is launched with the
  413 + contents of the current EEPROM configuration.
  414 +
  415 + Since this command launches rpi-eeprom-update to schedule the EEPROM update
  416 + it must be run as root.
  417 +
  418 + The configuration file will be taken from:
  419 + * The blconfig reserved memory nvmem device
  420 + * The cached bootloader configuration 'vcgencmd bootloader_config'
  421 + * The current pending update - typically /boot/pieeprom.upd
  422 +
  423 + sudo -E rpi-eeprom-config --edit [pieeprom.bin]
  424 +
  425 + To cancel the pending update run 'sudo rpi-eeprom-update -r'
  426 +
  427 + The default text editor is nano and may be overridden by setting the 'EDITOR'
  428 + environment variable and passing '-E' to 'sudo' to preserve the environment.
  429 +
  430 +6. Signing the bootloader config file.
  431 + Updates an EEPROM binary with a signed config file (created by rpi-eeprom-digest) plus
  432 + the corresponding RSA public key.
  433 +
  434 + Requires Python Cryptodomex libraries and OpenSSL. To install on Raspberry Pi OS run:-
  435 + sudo apt install openssl python-pip
  436 + sudo python3 -m pip install cryptodomex
  437 +
  438 + rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig
  439 + rpi-eeprom-config --config bootconf.txt --digest bootconf.sig --pubkey public.pem --out pieeprom-signed.bin pieeprom.bin
  440 +
  441 + Currently, the signing process is a separate step so can't be used with the --edit or --apply modes.
  442 +
  443 +
  444 +See 'rpi-eeprom-update -h' for more information about the available EEPROM images.
  445 +"""
  446 + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
  447 + description=description)
  448 +
  449 + parser.add_argument('-a', '--apply', required=False,
  450 + help='Updates the bootloader to the given config plus latest available EEPROM release.')
  451 + parser.add_argument('-c', '--config', help='Name of bootloader configuration file', required=False)
  452 + parser.add_argument('-e', '--edit', action='store_true', default=False, help='Edit the current EEPROM config')
  453 + parser.add_argument('-o', '--out', help='Name of output file', required=False)
  454 + 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)
  455 + parser.add_argument('-p', '--pubkey', help='Signed boot only. The name of the RSA public key file to store in the EEPROM', required=False)
  456 + parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input')
  457 + args = parser.parse_args()
  458 +
  459 + if (args.edit or args.apply is not None) and os.getuid() != 0:
  460 + exit_error("--edit/--apply must be run as root")
  461 +
  462 + if (args.edit or args.apply is not None) and not rpi4():
  463 + exit_error("--edit/--apply must run on a Raspberry Pi 4")
  464 +
  465 + if args.edit:
  466 + edit_config(args.eeprom)
  467 + elif args.apply is not None:
  468 + if not os.path.exists(args.apply):
  469 + exit_error("config file '%s' not found" % args.apply)
  470 + apply_update(args.apply, args.eeprom, args.apply)
  471 + elif args.eeprom is not None:
  472 + image = BootloaderImage(args.eeprom, args.out)
  473 + if args.config is not None:
  474 + if not os.path.exists(args.config):
  475 + exit_error("config file '%s' not found" % args.config)
  476 + image.update_file(args.config, BOOTCONF_TXT)
  477 + if args.digest is not None:
  478 + image.update_file(args.digest, BOOTCONF_SIG)
  479 + if args.pubkey is not None:
  480 + image.update_key(args.pubkey, PUBKEY_BIN)
  481 + image.write()
  482 + else:
  483 + image.read()
  484 + elif args.config is None and args.eeprom is None:
  485 + current_config, config_src = read_current_config()
  486 + if args.out is not None:
  487 + open(args.out, 'w').write(current_config)
  488 + else:
  489 + sys.stdout.write(current_config)
  490 +
  491 +if __name__ == '__main__':
  492 + atexit.register(exit_handler)
  493 + main()
... ...
tools/rpi-eeprom-digest 0 โ†’ 100755
  1 +#!/bin/sh
  2 +
  3 +# Helper script to generate .sig files for use with the Raspberry Pi bootloader.
  4 +
  5 +# This has been implemented in a separate script in order to have avoid having
  6 +# a hard dependency on OpenSSL.
  7 +
  8 +set -e
  9 +
  10 +OPENSSL=${OPENSSl:-openssl}
  11 +
  12 +die() {
  13 + echo "$@" >&2
  14 + exit 1
  15 +}
  16 +
  17 +TMP_DIR=""
  18 +cleanup() {
  19 + if [ -f "${TMP_DIR}" ]; then
  20 + rm -rf "${TMP_DIR}"
  21 + fi
  22 +}
  23 +
  24 +checkDependencies() {
  25 + if ! command -v sha256sum > /dev/null; then
  26 + die "sha256sum not found. Try installing the coreutilities package."
  27 + fi
  28 +
  29 + if ! command -v openssl > /dev/null; then
  30 + die "openssl not found. Try installing the openssl package."
  31 + fi
  32 +
  33 + if ! command -v xxd > /dev/null; then
  34 + die "xxd not found. Try installing the xxd package."
  35 + fi
  36 +}
  37 +
  38 +usage() {
  39 +cat <<EOF
  40 +rpi-eeprom-digest [-k RSA_KEY] -i IMAGE -o OUTPUT
  41 +
  42 +Creates a .sig file containing the sha256 digest of the IMAGE and an optional
  43 +RSA signature of that hash.
  44 +
  45 +Options:
  46 + -i The source image.
  47 + -o The name of the digest/signature file.
  48 + -k Optional RSA private key.
  49 +
  50 +RSA signing
  51 +If a private key in PEM format is supplied then the RSA signature of the
  52 +sha256 digest is included in the .sig file. Currently, the bootloader only
  53 +supports sha256 digests signed with a 2048bit RSA key.
  54 +The bootloader only verifies RSA signatures in signed boot mode
  55 +(not available yet) and only for the EEPROM config file and the signed image.
  56 +
  57 +Examples:
  58 +
  59 +# Generate RSA signature for the EEPROM config file.
  60 +rpi-eeprom-digest -k key.pem -i bootconf.txt -o bootconf.sig
  61 +
  62 +# Generate the normal sha256 hash to guard against file-system corruption
  63 +rpi-eeprom-digest -i pieeprom.bin -o pieeprom.sig
  64 +rpi-eeprom-digest -i vl805.bin -o vl805.sig
  65 +
  66 +EOF
  67 +exit 0
  68 +}
  69 +
  70 +OUTPUT=""
  71 +while getopts i:k:ho: option; do
  72 + case "${option}" in
  73 + i) IMAGE="${OPTARG}"
  74 + ;;
  75 + k) KEY="${OPTARG}"
  76 + ;;
  77 + o) OUTPUT="${OPTARG}"
  78 + ;;
  79 + h) usage
  80 + ;;
  81 + *) echo "Unknown argument \"${option}\""
  82 + usage
  83 + ;;
  84 + esac
  85 +done
  86 +
  87 +[ -n "${IMAGE}" ] || usage
  88 +[ -n "${OUTPUT}" ] || usage
  89 +
  90 +trap cleanup EXIT
  91 +
  92 +checkDependencies
  93 +
  94 +[ -f "${IMAGE}" ] || die "Source image \"${IMAGE}\" not found"
  95 +
  96 +TMP_DIR=$(mktemp -d)
  97 +SIG_TMP="${TMP_DIR}/tmp.sig"
  98 +sha256sum "${IMAGE}" | awk '{print $1}' > "${OUTPUT}"
  99 +
  100 +# Include the update-timestamp
  101 +echo "ts: $(date -u +%s)" >> "${OUTPUT}"
  102 +
  103 +if [ -n "${KEY}" ]; then
  104 + [ -f "${KEY}" ] || die "RSA private \"${KEY}\" not found"
  105 +
  106 + "${OPENSSL}" dgst -sign "${KEY}" -keyform PEM -sha256 -out "${SIG_TMP}" "${IMAGE}"
  107 + echo "rsa2048: $(xxd -c 4096 -p < "${SIG_TMP}")" >> "${OUTPUT}"
  108 +fi
... ...
tools/update-pieeprom.sh 0 โ†’ 100755
  1 +#!/bin/sh
  2 +
  3 +# Utility to update the EEPROM image (pieeprom.bin) and signature
  4 +# (pieeprom.sig) with a new EEPROM config.
  5 +#
  6 +# pieeprom.original.bin - The source EEPROM from rpi-eeprom repo
  7 +# boot.conf - The bootloader config file to apply.
  8 +
  9 +set -e
  10 +
  11 +script_dir="$(cd "$(dirname "$0")" && pwd)"
  12 +
  13 +# Minimum version for secure-boot support
  14 +BOOTLOADER_SECURE_BOOT_MIN_VERSION=1632136573
  15 +SRC_IMAGE="pieeprom.original.bin"
  16 +CONFIG="boot.conf"
  17 +DST_IMAGE="pieeprom.bin"
  18 +PEM_FILE=""
  19 +TMP_CONFIG_SIG=""
  20 +
  21 +die() {
  22 + echo "$@" >&2
  23 + exit ${EXIT_FAILED}
  24 +}
  25 +
  26 +cleanup() {
  27 + [ -f "${TMP_CONFIG}" ] && rm -f "${TMP_CONFIG}"
  28 +}
  29 +
  30 +usage() {
  31 +cat <<EOF
  32 + update-pieeprom.sh [key]
  33 +
  34 + Updates and EEPROM image by replacing the configuration file and generates
  35 + the new pieeprom.sig file.
  36 +
  37 + The bootloader configuration may also be signed by specifying the name
  38 + of an .pem with containing a 2048bit RSA public/private key pair.
  39 +
  40 + RSA signature support requires the Python Crypto module. To install:
  41 + python3 -m pip install Crypto
  42 +
  43 + -c Bootloader config file - default: "${SRC_IMAGE}"
  44 + -i Source EEPROM image - default: "${CONFIG}"
  45 + -o Output EEPROM image - default: "${DST_IMAGE}"
  46 + -k Optional RSA private PEM file - default: "${PEM_FILE}"
  47 +
  48 +The -k argument signs the EEPROM configuration using the specified RSA 2048
  49 +bit private key in PEM format. It also embeds the public portion of the RSA
  50 +key pair in the EEPROM image so that the bootloader can verify the signed OS
  51 +image.
  52 +EOF
  53 +}
  54 +
  55 +update_eeprom() {
  56 + src_image="$1"
  57 + config="$2"
  58 + dst_image="$3"
  59 + pem_file="$4"
  60 + sign_args=""
  61 +
  62 + if [ -n "${pem_file}" ]; then
  63 + if ! grep -q "SIGNED_BOOT=1" "${CONFIG}"; then
  64 + # If the OTP bit to require secure boot are set then then
  65 + # SIGNED_BOOT=1 is implicitly set in the EEPROM config.
  66 + # For debug in signed-boot mode it's normally useful to set this
  67 + echo "Warning: SIGNED_BOOT=1 not found in \"${CONFIG}\""
  68 + fi
  69 + update_version=$(strings "${src_image}" | grep BUILD_TIMESTAMP | sed 's/.*=//g')
  70 + if [ "${BOOTLOADER_SECURE_BOOT_MIN_VERSION}" -gt "${update_version}" ]; then
  71 + die "Source bootloader image ${src_image} does not support secure-boot. Please use a newer verison."
  72 + fi
  73 +
  74 + TMP_CONFIG_SIG="$(mktemp)"
  75 + echo "Signing bootloader config"
  76 + "${script_dir}/rpi-eeprom-digest" \
  77 + -i "${config}" -o "${TMP_CONFIG_SIG}" \
  78 + -k "${pem_file}" || die "Failed to sign EEPROM config"
  79 +
  80 + cat "${TMP_CONFIG_SIG}"
  81 +
  82 + # rpi-eeprom-config extracts the public key args from the specified
  83 + # PEM file. It will also accept just the public key so it's possible
  84 + # to tweak this script so that rpi-eeprom-config never sees the private
  85 + # key.
  86 + sign_args="-d ${TMP_CONFIG_SIG} -p ${pem_file}"
  87 + fi
  88 +
  89 + rm -f "${dst_image}"
  90 + ${script_dir}/rpi-eeprom-config \
  91 + --config "${config}" \
  92 + --out "${dst_image}" ${sign_args} \
  93 + "${src_image}" || die "Failed to update EEPROM image"
  94 +
  95 +cat <<EOF
  96 +new-image: ${dst_image}
  97 +source-image: ${src_image}
  98 +config: ${config}
  99 +EOF
  100 +}
  101 +
  102 +image_digest() {
  103 + "${script_dir}/rpi-eeprom-digest" \
  104 + -i "${1}" -o "${2}"
  105 +}
  106 +
  107 +trap cleanup EXIT
  108 +
  109 +while getopts "c:hi:o:k:" option; do
  110 + case "${option}" in
  111 + c) CONFIG="${OPTARG}"
  112 + ;;
  113 + i) SRC_IMAGE="${OPTARG}"
  114 + ;;
  115 + o) DST_IMAGE="${OPTARG}"
  116 + ;;
  117 + k) PEM_FILE="${OPTARG}"
  118 + ;;
  119 + h) usage
  120 + ;;
  121 + *) echo "Unknown argument \"${option}\""
  122 + usage
  123 + ;;
  124 + esac
  125 +done
  126 +
  127 +[ -f "${SRC_IMAGE}" ] || die "Source image \"${SRC_IMAGE}\" not found"
  128 +[ -f "${CONFIG}" ] || die "Bootloader config file \"${CONFIG}\" not found"
  129 +if [ -n "${PEM_FILE}" ]; then
  130 + [ -f "${PEM_FILE}" ] || die "RSA key file \"${PEM_FILE}\" not found"
  131 +fi
  132 +
  133 +DST_IMAGE_SIG="$(echo "${DST_IMAGE}" | sed 's/\..*//').sig"
  134 +
  135 +update_eeprom "${SRC_IMAGE}" "${CONFIG}" "${DST_IMAGE}" "${PEM_FILE}"
  136 +image_digest "${DST_IMAGE}" "${DST_IMAGE_SIG}"
  137 +
... ...
win32/install_script.nsi
... ... @@ -96,6 +96,9 @@ Section &quot;Raspberry Pi USB Boot&quot; Sec_rpiboot
96 96 File /r redist
97 97 File /r ..\msd
98 98 File /r ..\recovery
  99 + File /r ..\secure-boot-recovery
  100 + File /r ..\secure-boot-msd
  101 + File /r ..\tools
99 102  
100 103 DetailPrint "Installing BCM2708 driver..."
101 104 ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2763 -t 0' $0
... ...