Signing mcuboot, b0n and applications with custom private key on the nRF5340

Hi,

I used the default keys while developing my application. But for production, we want to use our proprietary key for the bootloader(s) and signing the application.

Our project uses mcuboot without TFM (CONFIG_BOOTLOADER_MCUBOOT=y, CONFIG_BUILD_WITH_TFM=n). We use both the APP core and the NET core (which runs b0n). All we want to do for now is sign our application binaries correctly.

I added the following configuration to my main project (prj.conf):

CONFIG_SB_SIGNING_KEY_FILE="C:/my_path/my_priv_key.pem"

I added the following code to child_image\mcuboot.conf:

CONFIG_BOOT_SIGNATURE_KEY_FILE="C:/my_path/my_priv_key.pem"
CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y

Question 1: CONFIG_SECURE_BOOT

When I add CONFIG_SB_SIGNING_KEY_FILE, I am forced to also add CONFIG_SECURE_BOOT=y. Why is this option needed? If I understand correctly, secure boot forces the application to be verified against its certificate each time before running. I didn't want to lose time during startup, I just need the image to be signed correctly for the flashing process. Or is CONFIG_SECURE_BOOT doing something else?

And just to be sure: Do I still only use mcuboot with the CONFIG_SECURE_BOOT option? No Secure Immutable Bootloader?

Question 2: Key error for hci_rpmsg

When the hci_rpmsg target for CPUNET is built, I get some strange error message which says I still use the wrong key:

      --------------------------------------------------------------
      --- WARNING: Using generated NSIB public/private key-pair. ---
      --- It should not be used for production.                  ---
      --- See CONFIG_SB_SIGNING_KEY_FILE                         ---
      --------------------------------------------------------------

It happens just after the b0n part within hci_rpmsg was built. Here is the complete log, for reference:

=== child image hci_rpmsg - CPUNET begin ===
loading initial cache file C:/my_proj_dir/build/hci_rpmsg/child_image_preload.cmake
Loading Zephyr default modules (Zephyr base).
-- Application: C:/ncs/v2.5.3/zephyr/samples/bluetooth/hci_rpmsg
-- CMake version: 3.20.5
-- Found Python3: C:/ncs/toolchains/c57af46cb7/opt/bin/python.exe (found suitable version "3.8.2", minimum required is "3.8") found components: Interpreter 
-- Cache files will be written to: C:/ncs/v2.5.3/zephyr/.cache
-- Zephyr version: 3.4.99 (C:/ncs/v2.5.3/zephyr)
-- Found west (found suitable version "1.1.0", minimum required is "0.14.0")
-- Board: nrf5340dk_nrf5340_cpunet
-- Found host-tools: zephyr 0.16.1 (C:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk)
-- Found toolchain: zephyr 0.16.1 (C:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk)
-- Found Dtc: C:/ncs/toolchains/c57af46cb7/opt/bin/dtc.exe (found suitable version "1.4.7", minimum required is "1.4.6") 
-- Found BOARD.dts: C:/ncs/v2.5.3/zephyr/boards/arm/nrf5340dk_nrf5340/nrf5340dk_nrf5340_cpunet.dts
-- Generated zephyr.dts: C:/my_proj_dir/build/hci_rpmsg/zephyr/zephyr.dts
-- Generated devicetree_generated.h: C:/my_proj_dir/build/hci_rpmsg/zephyr/include/generated/devicetree_generated.h
-- Including generated dts.cmake file: C:/my_proj_dir/build/hci_rpmsg/zephyr/dts.cmake
Parsing C:/ncs/v2.5.3/zephyr/Kconfig
Loaded configuration 'C:/ncs/v2.5.3/zephyr/boards/arm/nrf5340dk_nrf5340/nrf5340dk_nrf5340_cpunet_defconfig'
Merged configuration 'C:/ncs/v2.5.3/zephyr/samples/bluetooth/hci_rpmsg/prj.conf'
Merged configuration 'C:/ncs/v2.5.0/nrf/subsys/partition_manager/partition_manager_enabled.conf'
Merged configuration 'C:/ncs/v2.5.2/nrf/subsys/partition_manager/partition_manager_enabled.conf'
Merged configuration 'C:/ncs/v2.5.2/nrf/subsys/bootloader/image/secure_boot.conf'
Merged configuration 'C:/ncs/v2.5.3/nrf/subsys/bootloader/image/secure_boot.conf'
Merged configuration 'C:/ncs/v2.5.3/nrf/subsys/partition_manager/partition_manager_enabled.conf'
Merged configuration 'C:/my_proj_dir/build/hci_rpmsg/zephyr/misc/generated/extra_kconfig_options.conf'
Configuration saved to 'C:/my_proj_dir/build/hci_rpmsg/zephyr/.config'
Kconfig header saved to 'C:/my_proj_dir/build/hci_rpmsg/zephyr/include/generated/autoconf.h'
-- Found GnuLd: c:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd.exe (found version "2.38") 
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: C:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe

=== child image b0n - CPUNET (inherited) begin ===
loading initial cache file C:/my_proj_dir/build/hci_rpmsg/b0n/child_image_preload.cmake
Loading Zephyr default modules (Zephyr base).
-- Application: C:/ncs/v2.5.3/nrf/samples/nrf5340/netboot
-- CMake version: 3.20.5
-- Found Python3: C:/ncs/toolchains/c57af46cb7/opt/bin/python.exe (found suitable version "3.8.2", minimum required is "3.8") found components: Interpreter 
-- Cache files will be written to: C:/ncs/v2.5.3/zephyr/.cache
-- Zephyr version: 3.4.99 (C:/ncs/v2.5.3/zephyr)
-- Found west (found suitable version "1.1.0", minimum required is "0.14.0")
-- Board: nrf5340dk_nrf5340_cpunet
-- Found host-tools: zephyr 0.16.1 (C:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk)
-- Found toolchain: zephyr 0.16.1 (C:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk)
-- Found Dtc: C:/ncs/toolchains/c57af46cb7/opt/bin/dtc.exe (found suitable version "1.4.7", minimum required is "1.4.6") 
-- Found BOARD.dts: C:/ncs/v2.5.3/zephyr/boards/arm/nrf5340dk_nrf5340/nrf5340dk_nrf5340_cpunet.dts
-- Generated zephyr.dts: C:/my_proj_dir/build/hci_rpmsg/b0n/zephyr/zephyr.dts
-- Generated devicetree_generated.h: C:/my_proj_dir/build/hci_rpmsg/b0n/zephyr/include/generated/devicetree_generated.h
-- Including generated dts.cmake file: C:/my_proj_dir/build/hci_rpmsg/b0n/zephyr/dts.cmake
Parsing C:/ncs/v2.5.3/nrf/samples/nrf5340/netboot/Kconfig
Loaded configuration 'C:/ncs/v2.5.3/zephyr/boards/arm/nrf5340dk_nrf5340/nrf5340dk_nrf5340_cpunet_defconfig'
Merged configuration 'C:/ncs/v2.5.3/nrf/samples/nrf5340/netboot/prj.conf'
Merged configuration 'C:/ncs/v2.5.3/nrf/subsys/partition_manager/partition_manager_enabled.conf'
Configuration saved to 'C:/my_proj_dir/build/hci_rpmsg/b0n/zephyr/.config'
Kconfig header saved to 'C:/my_proj_dir/build/hci_rpmsg/b0n/zephyr/include/generated/autoconf.h'
-- Found GnuLd: c:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd.exe (found version "2.38") 
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: C:/ncs/toolchains/c57af46cb7/opt/zephyr-sdk/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe
CMake Warning at C:/ncs/v2.5.3/zephyr/CMakeLists.txt:1958 (message):
  __ASSERT() statements are globally ENABLED


-- Configuring done
-- Generating done
-- Build files have been written to: C:/my_proj_dir/build/hci_rpmsg/b0n
=== child image b0n - CPUNET (inherited) end ===

CMake Warning at C:/ncs/v2.5.3/nrf/subsys/bootloader/cmake/debug_keys.cmake:36 (message):
  

      --------------------------------------------------------------
      --- WARNING: Using generated NSIB public/private key-pair. ---
      --- It should not be used for production.                  ---
      --- See CONFIG_SB_SIGNING_KEY_FILE                         ---
      --------------------------------------------------------------
      

Call Stack (most recent call first):
  C:/ncs/v2.5.3/nrf/subsys/bootloader/cmake/provision_hex.cmake:47 (include)
  C:/ncs/v2.5.3/nrf/subsys/CMakeLists.txt:20 (include)


-- libmetal version: 1.4.0 (C:/ncs/v2.5.3/zephyr/samples/bluetooth/hci_rpmsg)
-- Build type:  
-- Host:    Windows/AMD64
-- Target:  Generic/arm
-- Machine: arm
-- Looking for include file stdatomic.h
-- Looking for include file stdatomic.h - found
-- open-amp version: 1.4.0 (C:/ncs/v2.5.3/modules/lib/open-amp/open-amp)
-- Host:    Windows/AMD64
-- Target:  Generic/arm
-- Machine: arm
-- C_FLAGS :  -Wall -Wextra
-- Looking for include file fcntl.h
-- Looking for include file fcntl.h - found
CMake Warning at C:/ncs/v2.5.3/nrf/cmake/partition_manager.cmake:79 (message):
  

          ---------------------------------------------------------------------
          --- WARNING: Using a bootloader without pm_static.yml.            ---
          --- There are cases where a deployed product can consist of       ---
          --- multiple images, and only a subset of these images can be     ---
          --- upgraded through a firmware update mechanism. In such cases,  ---
          --- the upgradable images must have partitions that are static    ---
          --- and are matching the partition map used by the bootloader     ---
          --- programmed onto the device.                                   ---
          ---------------------------------------------------------------------
          

Call Stack (most recent call first):
  C:/ncs/v2.5.3/zephyr/cmake/modules/kernel.cmake:247 (include)
  C:/ncs/v2.5.3/zephyr/cmake/modules/zephyr_default.cmake:138 (include)
  C:/ncs/v2.5.3/zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake:66 (include)
  C:/ncs/v2.5.3/zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake:92 (include_boilerplate)
  CMakeLists.txt:5 (find_package)


-- Configuring done
-- Generating done
-- Build files have been written to: C:/my_proj_dir/build/hci_rpmsg
=== child image hci_rpmsg - CPUNET end ===

(By the way, I already know about the pm_static issue for the NET core from my other ticket, I'm going to fix this later.)

So, what is the key related error message telling me? Do I have to add a key for signing the NET core separately to the configuration? How/where do I add it correctly? Maybe some additional configuration file in the child_image directory (e.g. hci_rpmsg.conf)? I couldn't find this in the documentation yet.

Question 3: Understanding key usage

Please help me understand the process of deployment. As far as I know:

  • mcuboot on the APP core needs the private key to recognize correctly signed images.
  • mcuboot checks both the app_update.bin and net_core_app_update.bin for correct signatures
  • both APP core and NET core binaries must be signed at the end of the build process. I assumed that CONFIG_SB_SIGNING_KEY_FILE in my prj.con would be used by the build system to sign both images, as signing is a separate action at the end of the build process and not during child image building. Is my assumption here wrong?
  • Big question: Is the signature for firmware updates checked exclusively by mcuboot, and b0n is not aware of the signature? Or will I have to add the private key information also to b0n when I use dual core MCU's like the nRF5340? (If so, how do I do it correctly?)

Question 4: Multiple keys for mcuboot

Is my assumption correct that mcuboot only supports one key? No multiple keys with key revoking possible like with the NSIB?

Question 5: Handling of invalid images by mcuboot

What does mcuboot do when I try to flash a wrongly signed image? I tried it out and it appeared that our system was not running for around 20 seconds (similar to the time it needs to flash a new firmware). But the firmware running afterwards was still the old firmware, and the secondary slot was empty after the old firmware started again. My assumption would be that mcuboot just erased all secondary slot pages (containing the invalid image). Is this correct? I was expecting to find some flags set by mcuboot if the new image was invalid, which would be simple feedback for a flashing application.

Finally, it would be really helpful if you could add the information how to set customer-specific key files correctly for multicore systems to the NCS documentation, e.g. here:
https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/config_and_build/bootloaders/bootloader_signature_keys.html

Best regards,
Michael

  • One more question... if I configure my project as described above, it compiles without any problem, but I cannot flash merged_domains.hex anymore. It tries to access an invalid memory location. Any ideas?

    -- runners.nrfjprog: Flashing file: c:\svn\TecnoBasicDigital\Prototyp\Source\_trunk_\TecnoBasicDigital\build\zephyr\merged_domains.hex
    -- runners.nrfjprog: c:\svn\TecnoBasicDigital\Prototyp\Source\_trunk_\TecnoBasicDigital\build\zephyr\merged_domains.hex targets both nRF53 coprocessors; splitting it into: c:\svn\TecnoBasicDigital\Prototyp\Source\_trunk_\TecnoBasicDigital\build\zephyr\GENERATED_CP_NETWORK_merged_domains.hex and c:\svn\TecnoBasicDigital\Prototyp\Source\_trunk_\TecnoBasicDigital\build\zephyr\GENERATED_CP_APPLICATION_merged_domains.hex
    [ #################### ]  12.468s | Erase file - Done erasing                                                          
    [ #################### ]   1.302s | Program file - Done programming                                                    
    [ #################### ]   1.305s | Verify file - Done verifying                                                       
    [error] [ Client] - Encountered error -173: Command erase_file executed for 35 milliseconds with result -173
    [error] [ Worker] - Address 0x00FF8424 does not map to a known memory.                                                 
    ERROR: The file specified is not a valid hex file, has data outside valid areas
    ERROR: or does not have data in valid areas.

    This strange section at 0xFF8424 (just before the NET core flash) is added to merged_domains.hex now...

    :10752000BEA6FC690552B186829B9B8651604FCBFB
    :107530007DEC913192C0EE23707C7B70C1DBDB204F
    :0C7540006681C60F3265076BE652A17F22
    :0200000400FFFB
    :10842400000202000041050001000000FFFFFFFF01
    :10843400C7770375F2E3FC2E99D83C4E6840DE936F
    :108444000100010001001400FFFFFFFFFFFFFFFF19
    :10845400FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28
    :10846400FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18
    :020000040100F9
    :10000000A00D0021E116000187420001B516000194
    :10001000B5160001B5160001B5160001000000007C

    Update 1: Found it in the Memory report, is's a provision partition...:

    Instead of only mcuboot, the build system now also includes b0, s0 and s1 (including image slots) in my primary APP space. This was not epected/intendet, I already created my static oatririon file with mcuboot only... I guess it's the CONFIG_SECURE_BOOT option.

    Update 2: Today, I could strangely build with CONFIG_SECURE_BOOT disabled...

  • Hello,

    Question 1: CONFIG_SECURE_BOOT

    With this symbol selected, the nRF Secure Immutable Bootloader will become included in the build and causing MCUBoot to be included as a second-stage bootloader on the application core, see Secure bootloader chain. Enabling secure boot is optional and is not needed for supporting DFU of the main app.

    The CONFIG_SB_SIGNING_KEY_FILE symbol specifies the signing key to be used by the nRF Secure Immutable Bootloader to validate the mcuboot image, which will be the next image in the bootchain, see Adding a custom signature key file.

    Question 2: Key error for hci_rpmsg

     The Network core bootloader should also be provisioned with an unique key. This is done with the same CONFIG_SB_SIGNING_KEY_FILE symbol. This thread disucusses how you can configure this symbol for you hci_rpmsg child image:

     How to use CONFIG_SB_SIGNING_KEY_FILE as a relative path  

    Question 3: Understanding key usage

    Please help me understand the process of deployment. As far as I know:

    • mcuboot on the APP core needs the private key to recognize correctly signed images.
    • mcuboot checks both the app_update.bin and net_core_app_update.bin for correct signatures
    • both APP core and NET core binaries must be signed at the end of the build process. I assumed that CONFIG_SB_SIGNING_KEY_FILE in my prj.con would be used by the build system to sign both images, as signing is a separate action at the end of the build process and not during child image building. Is my assumption here wrong?
    • Big question: Is the signature for firmware updates checked exclusively by mcuboot, and b0n is not aware of the signature? Or will I have to add the private key information also to b0n when I use dual core MCU's like the nRF5340? (If so, how do I do it correctly?)

    The public key from your private/public keypair is included the bootloader and used to verify that the app was signed with the corresponding private key. The private is not distributed with the FW. 

    MCUBOOT will validate both netcore and app core images during DFU. But the netcore bootloader (B0n) will be responsible for doing subsequent boot validation of the netcore image after the DFU has been completed with the CONFIG_SB_SIGNING_KEY_FILE key provided to the hci_rpmsg build. You may use the same CONFIG_SB_SIGNING_KEY_FILE key for the application core and network core.

    b0n is not aware of the MCUBoot signature that was verified during the DFU process. 

    Question 4: Multiple keys for mcuboot

    This is correct. To revoke an old key, you would need to update to a new mcuboot image with a different key-pair.

    Question 5: Handling of invalid images by mcuboot

    The correct behaviour is that it falls back to the previous version if the validation failed. The long processing time suggests that the swap alghoritm is enabled in MCUBOOT. I.e., BOOT_UPGRADE_ONLY is not enabled in build/mcuboot/zephyr/.config. 

    puz_md said:
    One more question... if I configure my project as described above, it compiles without any problem, but I cannot flash merged_domains.hex anymore. It tries to access an invalid memory location. Any ideas?

    I have not seen this error before. But the failing address is in the UICR.KEYSLOT.CONFIG range for the application core. Do you see the same error if you try to program the merged_domains.hex with nrfjprog directly (note: this requires an up to date version of nrfjprog installed)?

    nrfjprog --program merged_domains.hex --chiperase --verify --fast -r
     

    Best regards,

    Vidar

  • Hi Vidar,

    thanks for the details. Please let me ask one final question:

    Assuming that I do not use the nRF Secure Immutable Bootloader (CONFIG_SECURE_BOOT=n). Just mcuboot (and b0n on the NET core which appears to be mandatory). Which options in which (child and main) projects do I need to replace all the default keys from the nRF Connect SDK? Just a simple list of configuration files and the CONFIG_* options I have to set in them would be nice..

    Best regards,
    Michael

    PS: I edited my first comment with two updates, the problem was caused by enabling secure boot with my pm_static.yml which did not leave room for the new components.

  • Hi,

    I tend to set the CONFIG_SB_SIGNING_KEY_FILE and CONFIG_BOOT_SIGNATURE_KEY_FILE symbols in the project's CMakeLists.txt file rather than using Kconfig fragments. It's more convenient if you want to reference the key files relative to the project source directory instead of using an absolute path.

    Below is an example where I have selected a custom signing key for each bootloader. Note: The variables must be set right after the cmake_minimum_required() line to ensure they are applied early enough in the build process.

    cmake_minimum_required(VERSION 3.20.0)
    
    # Set signature key for network core bootloader (B0n).
    set(hci_rpmsg_CONFIG_SB_SIGNING_KEY_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/keys/nsib.pem\")
    
    # Set signature key for MCUBoot
    set(mcuboot_CONFIG_BOOT_SIGNATURE_KEY_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/keys/mcuboot.pem\")
    
    ...

    <project directory>/keys
    ├── mcuboot.pem
    └── nsib.pem

  • Hi Vidar,

    thanks for the information. From your reply, I understand that I need the following configuration artefacts for using mcuboot without the secure immutable bootloader on the APP core, and the automatically generated hci_rpmsg  with b0n for Bluetooth on the NET core:

    • CONFIG_SB_SIGNING_KEY_FILE for the hci_rpmsg child project
    • CONFIG_BOOT_SIGNATURE_KEY_FILE for the mcuboot child project
    • No key information is needed in the main application
    • The NET core checks its application's signature during each startup because it's an automatic feature from b0n which cannot be turned off. This is what SB_SIGNING_KEY_FILE is for.

    Can you please confirm?

    And if you use two different files for signing, does this mean that the NET core app is signed twice (one time with the SB_SIGNING_KEY for b0n and one time with the mcuboot signing key)?

    Also thanks for the hint with CMakeLists.txt. I didn't know you could configure settings for child projects there. It seems like it does the same as creating chld_image/mcuboot.conf and hci_rpmsg.conf with the mentioned information.

    Best regards,
    Michael

Related