Commit d1a9a5c659e8cfdb7c680ab7e77ecf985274c246
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.
Showing
23 changed files
with
1260 additions
and
7 deletions
Readme.md
| @@ -50,7 +50,7 @@ standard firmware release then this will at the very least boot the linux kernel | @@ -50,7 +50,7 @@ standard firmware release then this will at the very least boot the linux kernel | ||
| 50 | you can build an initramfs into the kernel, add an initramfs to the boot directory or provide some | 50 | you can build an initramfs into the kernel, add an initramfs to the boot directory or provide some |
| 51 | other interface to the filesystem. | 51 | other interface to the filesystem. |
| 52 | 52 | ||
| 53 | -``` | 53 | +```bash |
| 54 | sudo ./rpiboot -d boot | 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,6 +60,69 @@ This will serve the boot directory to the Raspberry Pi Device. | ||
| 60 | On Compute Module 4 EMMC-DISABLE / nRPIBOOT (GPIO 40) must be fitted to switch the ROM to usbboot mode. | 60 | On Compute Module 4 EMMC-DISABLE / nRPIBOOT (GPIO 40) must be fitted to switch the ROM to usbboot mode. |
| 61 | Otherwise, the SPI EEPROM bootloader image will be loaded instead. | 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 | ### Raspberry Pi Imager - BETA | 126 | ### Raspberry Pi Imager - BETA |
| 64 | The Raspberry Pi Imager can be run natively on the CM4 providing a GUI for downloading and installing the operating system. | 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,7 +132,7 @@ Beta notes: | ||
| 69 | * The HDMI display is limited to 1080p to avoid potential problems with cables etc if a 4K display is attached. | 132 | * The HDMI display is limited to 1080p to avoid potential problems with cables etc if a 4K display is attached. |
| 70 | 133 | ||
| 71 | Run Raspberry Pi Imager: | 134 | Run Raspberry Pi Imager: |
| 72 | -``` | 135 | +```bash |
| 73 | sudo ./rpiboot -d imager | 136 | sudo ./rpiboot -d imager |
| 74 | ``` | 137 | ``` |
| 75 | 138 |
debian/rpiboot.install
| @@ -4,4 +4,13 @@ msd/*.bin usr/share/rpiboot/msd/ | @@ -4,4 +4,13 @@ msd/*.bin usr/share/rpiboot/msd/ | ||
| 4 | recovery/*.bin usr/share/rpiboot/recovery/ | 4 | recovery/*.bin usr/share/rpiboot/recovery/ |
| 5 | recovery/*.sig usr/share/rpiboot/recovery/ | 5 | recovery/*.sig usr/share/rpiboot/recovery/ |
| 6 | recovery/*.txt usr/share/rpiboot/recovery/ | 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 | debian/99-rpiboot.rules /lib/udev/rules.d | 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,13 +3,11 @@ | ||
| 3 | # Utility to update the EEPROM image (pieeprom.bin) and signature | 3 | # Utility to update the EEPROM image (pieeprom.bin) and signature |
| 4 | # (pieeprom.sig) with a new EEPROM config. | 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 | # pieeprom.original.bin - The source EEPROM from rpi-eeprom repo | 8 | # pieeprom.original.bin - The source EEPROM from rpi-eeprom repo |
| 7 | # boot.conf - The bootloader config file to apply. | 9 | # boot.conf - The bootloader config file to apply. |
| 8 | 10 | ||
| 9 | set -e | 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
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
secure-boot-recovery/.gitignore
0 โ 100644
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 "Raspberry Pi USB Boot" Sec_rpiboot | @@ -96,6 +96,9 @@ Section "Raspberry Pi USB Boot" Sec_rpiboot | ||
| 96 | File /r redist | 96 | File /r redist |
| 97 | File /r ..\msd | 97 | File /r ..\msd |
| 98 | File /r ..\recovery | 98 | File /r ..\recovery |
| 99 | + File /r ..\secure-boot-recovery | ||
| 100 | + File /r ..\secure-boot-msd | ||
| 101 | + File /r ..\tools | ||
| 99 | 102 | ||
| 100 | DetailPrint "Installing BCM2708 driver..." | 103 | DetailPrint "Installing BCM2708 driver..." |
| 101 | ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2763 -t 0' $0 | 104 | ExecWait '"$INSTDIR\redist\wdi-simple.exe" -n "Raspberry Pi USB boot" -v 0x0a5c -p 0x2763 -t 0' $0 |