The bootloader sample implements an immutable first stage bootloader that can verify and boot either a second stage bootloader or an application.
If the second stage bootloader is upgradable, it can reside in one of two slots. In this case, the sample chooses the second stage bootloader with the highest version number.
The bootloader sample provides both a simple Root of Trust (RoT) and support for an upgradable second stage bootloader.
This is accomplished as follows:
- Locking the flash memory.
To enable the RoT, the bootloader sample locks the flash memory that contains the sample bootloader and its configuration. Locking is done using the hardware that is available on the given architecture.
For additional details on locking, see the Hardware flash write protection driver.
- Selecting the next stage in the boot chain.
The next stage in the boot chain can either be another bootloader or an application.
When the bootloader sample is enabled and MCUboot is used as the second stage bootloader, there are two slots in which the second stage bootloader can reside. The second stage bootloader in each slot has a version number associated with it, and the bootloader sample selects the second stage bootloader that has the highest version number.
- Verifying the next stage in the boot chain.
After selecting the image to be booted next, the bootloader sample verifies the validity of the image using one of the provisioned public keys hashes.
The image for the next boot stage has a set of metadata associated with it. This metadata contains the full public key corresponding to the private key that was used to sign the firmware. The bootloader sample checks the public key against a set of provisioned keys.
To save space, only the hashes of the provisioned keys are stored, and only the hashes of the keys are compared.
If the public key in the metadata matches one of the valid provisioned public key hashes, the image is considered valid.
All public key hashes at lower indices than the matching hash are permanently invalidated at this point, which means that images can no longer be validated with those public keys. For example, if an image is successfully validated with the public key at index 2, the public keys 0 and 1 are invalidated. This mechanism can be used to decommission broken keys.
If the public key does not match any of the still valid provisioned hashes, validation fails.
- Booting the next stage in the boot chain.
After verifying the next boot stage, the bootloader sample uninitializes all the peripherals that it used and boots the next boot stage.
- Sharing the cryptographic library over EXT_API.
The bootloader sample shares some of its functionality through an external API (EXT_API).
For more information on the process, see the
bl_crypto.hfile. For more information on EXT_API, see External APIs.
The flash memory layout is defined by the
The bootloader sample defines four main areas:
B0 - Contains the bootloader sample.
Provision - Stores the provisioned data.
S0 - Defines one of the two potential storage areas for the second stage bootloader.
S1 - Defines the other one of the two potential storage areas for the second stage bootloader.
The public key hashes are not compiled with the source code of the bootloader sample. Instead, they must be stored in a separate memory region through a process called provisioning.
By default, the bootloader sample will automatically generate and provision public key hashes directly into the bootloader HEX file, based on the specified private key and additional public keys.
Alternatively, to facilitate the manufacturing process of a device with the bootloader sample, it is possible to decouple this process and program the sample HEX file and the HEX file containing the public key hashes separately.
If you choose to do so, use the Python scripts located in the
scripts/bootloader folder to create and provision the keys manually.
On some SoCs/SiPs, like the nRF5340 or the nRF9160, the provisioned data is held in the One-time programmable (OTP) region in the User Information Configuration Registers (UICR). For this reason, please note the following:
You must erase the UICR before programming the bootloader. On the nRF9160, the UICR can only be erased by erasing the entire chip:
On the command line, call
west flashwith the
--eraseoption to erase the chip, like in the following example:
:class: highlight west flash -d build_directory --erase This will erase the whole chip before programming the new image.
In SEGGER Embedded Studio, choose Target -> Connect J-Link and then Target -> Erase All to erase the whole chip.
The public key hash cannot contain half-words with the value
0xFFFF, because half-words are writeable when they are
0xFFFF, so such hashes cannot be guaranteed to be immutable. The bootloader will refuse to boot if any hash contains a half-word with the value
0xFFFF. If your public key hash is found to have
0xFFFF, please regenerate it or use another public key.
The bootloader uses the Bootloader storage library to access provisioned data.
The sample supports the following development kits:
This sample can be found under
samples/bootloader in the nRF Connect SDK folder structure.
Add it to any other sample, then build and program that sample as described in the following sections.
The most common use case for the bootloader sample is to be included as a child image in a multi-image build, rather than being built stand-alone. Complete the following steps to add the bootloader sample as a child image to your application:
Create a private key in PEM format by running the following command:
openssl ecparam -name prime256v1 -genkey -noout -out priv.pem
It will store your private key in a file named
priv.pemin the current folder. As OpenSSL is installed with GIT, it should be available in your GIT bash. See OpenSSL for more information.
This step is optional for testing the bootloader chain. If you do not provide your own keys, debug keys are created automatically. However, you should never go into production with an application that is not protected by secure keys.
Enable Secure Boot by running
menuconfigon your application:
Select Project -> Configure nRF Connect SDK project.
Go to Modules -> Nordic nRF Connect -> Bootloader and set Use Secure Bootloader to enable
Under Private key PEM file (
CONFIG_SB_SIGNING_KEY_FILE), enter the path to the private key that you created. If you choose to run the sample with default debug keys, you can skip this step.
There are additional configuration options that you can modify, but it is not recommended to do so. The default settings are suitable for most use cases.
Select Build -> Build Solution to compile your application. The build process creates two images, one for the bootloader and one for the application, and merges them together.
Select Build -> Build and Run to program the resulting image to your device.
You can easily add an immutable bootloader to most Zephyr or nRF Connect SDK samples by enabling
CONFIG_SECURE_BOOT in the sample’s
prj.conf file, or directly to the build command as follows:
west build -b nrf52840dk_nrf52840 zephyr/samples/hello_world -- -DCONFIG_SECURE_BOOT=y
For more information, see Building on the command line.
Overlays specific to bootloaders can be used to further modify the bootloader child image when compiling with an application:
west build -b nrf52840dk_nrf52840 zephyr/samples/hello_world -- -DCONFIG_SECURE_BOOT=y -Db0_OVERLAY_CONFIG=overlay-minimal-size.conf
The image-specific overlay will be grabbed from the child image’s respective source directory, such as
samples/bootloader in the nRF Connect SDK folder structure for
For more information, see Image-specific variables.
To test the bootloader sample, perform the following steps:
Connect to the kit with a terminal emulator (for example, PuTTY). See How to connect with PuTTY for the required settings.
Reset the development kit.
Observe that the development kit prints the following information (hash and boot address may vary):
Attempting to boot slot 0. Attempting to boot from address 0x9000. Verifying signature against key 0. Hash: 0x18...3f Firmware signature verified. Firmware version 1 Setting monotonic counter (version: 1, slot: 0)