Firmware updates

This user guide provides an overview of firmware updates with nRF Connect SDK.

Signature keys

You can find information on the key types supported in the nRF Connect SDK in the nRF Secure Immutable Bootloader and Image tool documentation.

Generating private keys

The nRF Connect SDK supports the following most common ways to generate private/public key pairs:

Using OpenSSL to generate keys

OpenSSL is installed by default with some packages and programs, such as Git. It supports many different types of keys, but not all of them are supported by nRF Secure Immutable Bootloader and MCUboot. To see a complete list of the key types supported by OpenSSL, call openssl help and openssl <key type> -help from a terminal.

For a complete list of the key types supported by each bootloader, see the following table:

Bootloader

Supported key types

nRF Secure Immutable Bootloader

ECDSA256

MCUboot

RSA-2048, RSA-3072, ECDSA-P256, ED25519

Examples of using OpenSSL to create some commonly used key types:

openssl ecparam -name prime256v1 -genkey -noout -out priv.pem

Note

priv.pem, priv_*.pem, and pub_*.pem are keys named arbitrarily and used as an example in this documentation. You can name private and public keys used with Zephyr and the nRF Connect SDK as you prefer, as long as the files are in the .pem format.

Using Imgtool to generate keys

Image tool is a Python tool maintained by MCUboot that handles public/private key pairs.

It is also available as a PyPI package that you can install using pip. However, when working within the nRF Connect SDK framework, it is recommended to use the script that is included in the fork of MCUboot used by the nRF Connect SDK.

Examples of imgtool used to create some commonly used key types:

python3 bootloader/mcuboot/scripts/imgtool.py keygen -t ecdsa-p256 -k priv.pem

For a full list of supported types, use the --help argument with the tool or any of its commands.

Using Python to generate keys

The nRF Connect SDK includes an internal script to interact with private and public keys with the nRF Secure Immutable Bootloader. This script is maintained by the nRF Connect SDK team and uses the ecdsa package available from PyPI. It is only valid for use with ECDSA keys.

An example of this internal Python script used to generate keys.

python3 nrf/scripts/bootloader/keygen.py --private -o priv.pem

Using development keys

When testing the bootloader chain, you can optionally generate and use custom signing keys. If you do not provide your own keys through Kconfig options, the build system automatically creates debug keys, depending on the bootloaders compiled into the application.

Caution

Keys that are automatically used or generated by bootloaders for image signature validation are intended for development or debug use only.

You should never send applications into production when they are not protected by secure keys. You must always create and store these keys in a safe location, not only to protect the security of the application but also to ensure that the hardware can receive firmware updates throughout the project lifecycle.

While the default keys for MCUboot are tracked in its repository and are therefore publicly visible, the development/debug keys autogenerated by nRF Secure Immutable Bootloader change whenever the build directory is removed and rebuilt from scratch. If you are not programming the nRF Secure Immutable Bootloader when this happens, relying on the default ECDSA key to sign an application or an upgradable second-stage bootloader image results in a failed boot chain validation.

You can avoid this issue by storing a custom private key outside of the build directory during development.

Revoking private keys

The nRF Secure Immutable Bootloader allows you to revoke public verification keys used to validate the next image in the secure boot chain. Key revocation can be a useful security measure for devices that have already been deployed to the field. If a private key has been compromised or lost, you can invalidate its public key by uploading a new firmware image signed by another key known to the bootloader.

These keys are kept internally by the bootloader, so the list of available public keys cannot change once it is deployed. See CONFIG_SB_PUBLIC_KEY_FILES for details on how this mechanism is implemented.

You can add this feature to your own project and check its functionality as follows:

  1. Use the bootloader Python script to generate two or more private keys for the application and extract a public key for each one:

    python3 nrf/scripts/bootloader/keygen.py --public -i priv.pem -o pub.pem
    
  2. Compile the application and bootloader with the relevant configurations, using only absolute paths:

    CONFIG_SECURE_BOOT=y
    CONFIG_SB_SIGNING_KEY_FILE="/path/to/priv_a.pem"
    CONFIG_SB_PUBLIC_KEY_FILES="/path/to/pub_b.pem,/path/to/pub_c.pem"
    

    Caution

    The public key associated with the original private signing key must not be included in the public key list.

  3. Program the application to the target development kit and check its console output. With the first firmware version, priv_a.pem and pub_a.pem are used for signing and validating the image.

    *** Booting Zephyr OS build ...  ***
    Attempting to boot slot 0.
    Attempting to boot from address 0x9000.
    Verifying signature against key 0.
    Hash: 0xda...4f
    Firmware signature verified.
    Firmware version 1
    *** Booting Zephyr OS build ...  ***
    ...
    
  4. To revoke keys, rebuild the application modifying the configuration setting to use the private key associated with a key listed after the currently used key in the list.

    CONFIG_BUILD_S1_VARIANT=y
    CONFIG_SB_SIGNING_KEY_FILE="/path/to/priv_c.pem"
    CONFIG_FW_INFO_FIRMWARE_VERSION=2
    

    In this example, when compiling with the priv_c.pem key, images signed with priv_a.pem or priv_b.pem no longer boot when uploaded into an image slot. Additionally, a firmware version higher than the previous one has been set.

  5. Deploy the firmware update.

  6. Observe the bootloader checking the hashes of the public keys against the new image, then invalidating the earlier keys:

    *** Booting Zephyr OS build ...  ***
    Attempting to boot slot 1.
    Attempting to boot from address 0x84800.
    Verifying signature against key 0.
    Hash: 0xda...4f
    Public key didn't match, try next.
    Verifying signature against key 1.
    Hash: 0x5c...f5
    Public key didn't match, try next.
    Verifying signature against key 2.
    Hash: 0x19...73
    Invalidating key 0.
    Invalidating key 1.
    Firmware signature verified.
    Firmware version 2
    Setting monotonic counter (version: 2, slot: 1)
    *** Booting Zephyr OS build ...  ***
    ...
    

To test that the bootloader no longer boots images signed with the earlier keys, upload an image signed with one of them.

  1. Recompile the application with the following options:

    CONFIG_SB_SIGNING_KEY_FILE="/path/to/priv_b.pem"
    CONFIG_FW_INFO_FIRMWARE_VERSION=3
    
  2. To facilitate testing, use nrfjprog to program this image directly into a slot:

    nrfjprog -f nRF52 -r --verify --program build/zephyr/signed_by_b0_s0_image.hex --sectorerase
    
  3. Observe the bootloader skipping the invalid image and booting the valid image in the other slot:

    *** Booting Zephyr OS build ...  ***
    Attempting to boot slot 0.
    Attempting to boot from address 0x9000.
    Key 0 has been invalidated, try next.
    Key 1 has been invalidated, try next.
    Verifying signature against key 2.
    Hash: 0x19...73
    Public key didn't match, try next.
    Failed to validate signature.
    Failed to validate, permanently invalidating!
    Attempting to boot slot 1.
    Attempting to boot from address 0x84800.
    Key 0 has been invalidated, try next.
    Key 1 has been invalidated, try next.
    Verifying signature against key 2.
    Hash: 0x19...73
    Invalidating key 0.
    Invalidating key 1.
    Firmware signature verified.
    Firmware version 2
    *** Booting Zephyr OS build ...  ***
    ...
    

Recompile with priv_c.pem and the incremented firmware version to correctly boot the new image.

Image versions

Each release of an application must come with a specific version. The version can have the form of a single number. Alternatively, it can be based on a more advanced versioning scheme. For example, the semantic versioning scheme uses three numbers to denote major, minor, and patch versions, respectively.

The choice of the versioning scheme depends on the selected bootloader and the configuration of the bootloader. The nRF Connect SDK build system can automatically handle building of the bootloader together with the application. The build system can also sign the application images and provide versioning information for the images.

See the following sections for details related to application versioning for bootloaders supported in the nRF Connect SDK.

Configuring image version with nRF Secure Immutable Bootloader

You can set the image version for your application using the CONFIG_FW_INFO_FIRMWARE_VERSION Kconfig option. This option can refer to two different things:

  • If NSIB directly boots your application image, the Kconfig option denotes the application image version.

  • If NSIB boots a secondary-stage bootloader, the Kconfig option denotes the version of the secondary-stage bootloader. In such case, the application is booted by the secondary-state bootloader and the application image version is configured using the versioning scheme of the secondary-stage bootloader. For example, if you opted for Adding MCUboot as an upgradable bootloader, the application image versioning configuration is described in Configuring image version with MCUboot.

Configuring image version with MCUboot

To assign a semantic version number to your application when you have opted for booting application directly by the MCUboot bootloader (that is, if you have opted for either Adding MCUboot as an immutable bootloader or Adding MCUboot as an upgradable bootloader), add the version string to the CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION option for the application:

CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.1.2+3"

See the Semantic versioning webpage or Image tool for details on version syntax.

Downgrade protection

The downgrade protection feature blocks downgrading the firmware version on the device. This can be useful for security reasons, because firmware downgrade can allow attackers to bring back security vulnerabilities that were fixed by a particular firmware update.

As part of this process, the bootloader compares the new firmware image version with the version of the currently booted firmware. The bootloader rejects the update if the new image version is lower than the version of the currently booted firmware.

The implementation is related to the Image versions and also depends on the type of bootloader used:

  • Software-based downgrade protection is supported only by MCUboot. In this kind of protection, the current firmware version is encoded into the currently active image.

  • Hardware-based downgrade protection is supported by both MCUboot and nRF Secure Immutable Bootloader. In this kind of protection, the current version is encoded into a non-volatile storage partition outside of any image.

Software-based downgrade protection

The nRF Connect SDK supports MCUboot’s software-based downgrade prevention for application images, using semantic versioning. This feature offers protection against any outdated firmware that is uploaded to a device.

To enable this feature, set the MCUboot-specific configuration options CONFIG_MCUBOOT_DOWNGRADE_PREVENTION and CONFIG_BOOT_UPGRADE_ONLY for the MCUboot image.

Caution

Enabling CONFIG_BOOT_UPGRADE_ONLY prevents the fallback recovery of application images. Consult its Kconfig description and the MCUboot Design documentation for more information on how to use it.

You can compile your application with this feature as follows:

west build -b board application -- \
-DCONFIG_BOOTLOADER_MCUBOOT=y \
-DCONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION=\"0.1.2\+3\" \
-Dmcuboot_CONFIG_MCUBOOT_DOWNGRADE_PREVENTION=y \
-Dmcuboot_CONFIG_BOOT_UPGRADE_ONLY=y

See Configuring your application for information on how to set the required configuration options temporarily or permanently.

After you upload a new image and reset the development kit, MCUboot attempts to boot the secondary image. If this image has, in order of precedence, a major, minor, or revision value that is lower than the primary application image, it is considered invalid and the existing primary application boots instead.

Note

The optional label or build number specified after the + character is ignored when evaluating the version. For example, an existing application image with version 0.1.2+3 can be overwritten by an uploaded image with 0.1.2+2, but not by one with 0.1.1+2.

Hardware-based downgrade protection

You can implement hardware-based downgrade protection using a non-volatile monotonic counter.

Counter updates are written to slots in the Provision area, with each new counter update occupying a new slot. For this reason, the number of counter updates, and therefore firmware version updates, is limited. The Provision is a partition in non-volatile memory, and its location can be found using Partition Manager report.

Using a counter is optional and can be configured for the application using configuration options. You can also configure the supported number of updates, but the number is limited by the size of the Provision area and how much of that area is taken up by other features, like public key hashes. In addition, you can configure what firmware version of the image you want to boot.

Downgrade protection using nRF Secure Immutable Bootloader

To enable anti-rollback protection with monotonic counter for nRF Secure Immutable Bootloader, set the following configurations in the application (parent image):

Special handling is needed when updating the S1 variant of an image when Adding an upgradable bootloader. See Generating pre-signed variants for details.

To set options for child images, see the Image-specific variables section.

Downgrade protection using MCUboot

To enable anti-rollback protection with monotonic counter for MCUboot, set the following configurations in the application (parent image):

To set options for child images, see the Image-specific variables section.

MCUboot output build files

Read the MCUboot output build files page for the list of all the FOTA upgrade files that are automatically generated when using MCUboot.