diff --git a/doc/build/signing/index.rst b/doc/build/signing/index.rst index 0870add5768c8..ff5481f29e13e 100644 --- a/doc/build/signing/index.rst +++ b/doc/build/signing/index.rst @@ -37,8 +37,13 @@ Notes on the above commands: For more information on these and other related configuration options, see: - ``SB_CONFIG_BOOTLOADER_MCUBOOT``: build the application for loading by MCUboot -- ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE``: the key file to use when signing images. If you have - your own key, change this appropriately +- ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE``: the key file, or a comma-separated list of key + files, to use when signing images. If you have your own key, change this appropriately. + When a list is given, MCUboot embeds the public half of every key and accepts an image + signed with any of them; the first entry is also the key the application is signed with, + and every entry past the first must be a public-only PEM (all keys must use the same + signature type). Typical use: development bootloaders that should boot both production- + and development-signed images, while production bootloaders embed only the production key - :kconfig:option:`CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS`: optional additional command line arguments for ``imgtool`` - :kconfig:option:`CONFIG_MCUBOOT_GENERATE_CONFIRMED_IMAGE`: also generate a confirmed image, @@ -46,6 +51,11 @@ For more information on these and other related configuration options, see: - On Windows, if you get "Access denied" issues, the recommended fix is to run ``pip3 install imgtool``, then retry with a pristine build directory. +For a worked example of embedding more than one verification key — a development +bootloader that boots both development- and production-signed images while never +holding the production private key — see the +:zephyr:code-sample:`mcuboot_signing_keys` sample. + If your ``west flash`` :ref:`runner ` uses an image format supported by imgtool, you should see something like this on your device's serial console when you run ``west flash -d build-hello-signed``: diff --git a/doc/releases/release-notes-4.5.rst b/doc/releases/release-notes-4.5.rst index 888548fe850f4..c1a86682da660 100644 --- a/doc/releases/release-notes-4.5.rst +++ b/doc/releases/release-notes-4.5.rst @@ -216,6 +216,15 @@ Other notable changes * Removed the ``samples/net/wifi/test_certs/rsa2k`` enterprise test certificates (DES-encrypted private keys). Use ``rsa2k_no_des`` instead. +* MCUboot + + * ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE`` now accepts a comma-separated list of + key files, embedding the public half of each in the MCUboot bootloader. When more + than one key is given, MCUboot accepts an image signed with any of them; the first + entry is the key the application is signed with and the rest are verification-only + public keys. See :ref:`build-signing` and the + :zephyr:code-sample:`mcuboot_signing_keys` sample. + .. Any more descriptive subsystem or driver changes. Do you really want to write a paragraph or is it enough to link to the api/driver/Kconfig/board page above? diff --git a/modules/Kconfig.mcuboot b/modules/Kconfig.mcuboot index c83b2ef9d1553..33a30f957134a 100644 --- a/modules/Kconfig.mcuboot +++ b/modules/Kconfig.mcuboot @@ -52,7 +52,7 @@ config MCUBOOT_SIGNATURE_KEY_FILE The existence of bin and hex files depends on CONFIG_BUILD_OUTPUT_BIN and CONFIG_BUILD_OUTPUT_HEX. - This option should contain a path to the same file as the + This option should contain a path to the same file as the (first) BOOT_SIGNATURE_KEY_FILE option in your MCUboot .config. The path may be absolute or relative to the west workspace topdir. (The MCUboot config option is used for the MCUboot bootloader image; this option is @@ -60,6 +60,10 @@ config MCUBOOT_SIGNATURE_KEY_FILE config option can be a relative path from the MCUboot repository root.) + The application is always signed with a single key. When the MCUboot + bootloader embeds several verification keys (BOOT_SIGNATURE_KEY_FILE is + a comma-separated list), this is the first entry of that list. + If left empty and MCUBOOT_GENERATE_UNSIGNED_IMAGE is not set, you must sign and prepare the Zephyr binaries manually to be bootable from MCUboot. diff --git a/samples/sysbuild/mcuboot_signing_keys/CMakeLists.txt b/samples/sysbuild/mcuboot_signing_keys/CMakeLists.txt new file mode 100644 index 0000000000000..695a09dbe2eb0 --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/CMakeLists.txt @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mcuboot_signing_keys) + +test_sysbuild() + +target_sources(app PRIVATE src/main.c) + +# Demonstration aid: re-sign the built image with extra keys so multi-key +# acceptance can be checked by flashing a prebuilt hex instead of re-signing by +# hand. The signing parameters mirror the build's own overwrite-only ED25519 +# signing (see cmake/mcuboot.cmake). +if(CONFIG_BOOTLOADER_MCUBOOT AND NOT CONFIG_MCUBOOT_GENERATE_UNSIGNED_IMAGE) + message(WARNING + "mcuboot_signing_keys is a demonstration sample: it also signs the " + "application with the production and an unknown key (zephyr.signed.prod.hex " + "and zephyr.signed.unknown.hex) using MCUboot's public TEST keys. A real " + "build must never hold the production private key -- do not copy this " + "signing step into a production application." + ) + + dt_nodelabel(slot1 NODELABEL "slot1_partition" REQUIRED) + dt_prop(slot_size PATH "${slot1}" PROPERTY "reg" INDEX 1 REQUIRED) + + set(image ${ZEPHYR_BINARY_DIR}/${KERNEL_NAME}) + set(imgtool_sign ${PYTHON_EXECUTABLE} ${ZEPHYR_MCUBOOT_MODULE_DIR}/scripts/imgtool.py sign + --version=${CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION} + --header-size=${CONFIG_ROM_START_OFFSET} + --slot-size=${slot_size} + --overwrite-only + --align=1 + ) + + # Re-sign the built image (a declared build byproduct) with each demo key. + add_custom_command( + OUTPUT ${image}.signed.prod.hex ${image}.signed.unknown.hex + COMMAND ${imgtool_sign} + --key=${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519-2.pem + ${image}.hex ${image}.signed.prod.hex + COMMAND ${imgtool_sign} + --key=${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519-unknown.pem + ${image}.hex ${image}.signed.unknown.hex + DEPENDS ${image}.hex + COMMENT "Signing demo images with the production and unknown keys" + ) + add_custom_target(mcuboot_signing_keys_demo_images ALL + DEPENDS ${image}.signed.prod.hex ${image}.signed.unknown.hex + ) +endif() diff --git a/samples/sysbuild/mcuboot_signing_keys/README.rst b/samples/sysbuild/mcuboot_signing_keys/README.rst new file mode 100644 index 0000000000000..8c6218191ab88 --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/README.rst @@ -0,0 +1,246 @@ +.. zephyr:code-sample:: mcuboot_signing_keys + :name: MCUboot multiple signing keys with sysbuild + + Build a development bootloader that boots images signed by either a + local development key or a remote production key. + +Overview +******** + +This sample demonstrates the canonical real-world use of a +multi-key ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE``: a **development +bootloader** that accepts images signed with either the team's +development key or the production key, while production bootloaders +(built separately) accept only the production key. + +``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE`` takes a single PEM path *or* a +comma-separated list of PEMs. MCUboot embeds the public half of +every key in the list and, at boot, accepts an image signed by any one +of them. There is no fixed limit on the number of keys; this sample +uses two because the development/production split is the most common +case. + +The security-critical property modelled here is that the development +workstation **never holds the production private key**. The production +team signs release images on a locked-down build server and +distributes only the public half of their signing key to development +workstations. That public half is embedded into development +bootloaders as a list entry — no private material is required or +present. + +Keys used by this sample +======================== + +``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE`` is set to a two-entry list: + +- **First entry** — ``${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519.pem``, + a publicly-known MCUboot test key standing in for the developer's own + signing key. The first entry is also the key the **application** is + signed with, so it must be a key pair. +- **Second entry** — :file:`keys/prod_pubkey.pem`, a PEM file + containing **only the public half** of a keypair. It was extracted + from MCUboot's ``root-ed25519-2.pem`` test key with + ``imgtool getpub --encoding pem``, simulating what a dev workstation + would receive from a production team. Every list entry past the first + is verification-only and **must** be public-only; the build enforces + this with ``imgtool keyinfo --require public``. + +:file:`sysbuild.conf` references the first key from the MCUboot module +directory with ``${ZEPHYR_MCUBOOT_MODULE_DIR}`` and the second with a +plain relative path. Sysbuild expands CMake variable references and +resolves relative signing-key paths against the application source +directory before forwarding the list to the MCUboot child image. + +All keys here derive from MCUboot's publicly-available test keys and +are insecure. Substitute your own ED25519 keys when adopting this +pattern for production. + +Files +===== + +- :file:`sysbuild.conf` — enables MCUboot with ED25519 signing and sets + ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE`` to the ``dev,prod`` key list. +- :file:`keys/prod_pubkey.pem` — public-only PEM shipped with the + sample. +- :file:`sysbuild/mcuboot.conf` — raises the MCUboot log level to debug + so the signature verification is visible at boot. +- :file:`prj.conf` — enables the Zephyr logging subsystem in the + application. +- :file:`CMakeLists.txt` — builds the application and, as a + demonstration aid, emits the production- and unknown-key signed images + used below. + +Building and running +******************** + +.. zephyr-app-commands:: + :tool: west + :zephyr-app: samples/sysbuild/mcuboot_signing_keys + :board: nrf52840dk/nrf52840 + :goals: build flash + :west-args: --sysbuild + :compact: + +The default build signs the application with the first (development) +key. With MCUboot debug logging enabled, the console shows MCUboot +selecting an embedded key and validating the image before chain-loading +it. Expected (abridged) output: + +.. code-block:: console + + *** Booting MCUboot *** + I: Starting bootloader + ... + D: bootutil_img_validate: EXPECTED_KEY_TLV == 1 + D: bootutil_find_key + D: bootutil_img_validate: EXPECTED_SIG_TLV == 36 + D: bootutil_verify_sig: ED25519 key_id 0 + ... + I: Jumping to the first image slot + *** Booting Zephyr OS *** + [00:00:00.000,000] app: Address of sample 0xc000 + [00:00:00.000,000] app: Hello mcuboot signing keys! nrf52840dk + +The ``key_id`` on the ``bootutil_verify_sig`` line is the runtime proof of +*which* embedded key was used. ``bootutil_find_key`` hashes the image's key +TLV against each embedded key and returns its index, then the signature is +verified with that key. Here ``key_id 0`` is the first list entry +(``root-ed25519.pem``, emitted as ``autogen-pubkey.c``); an image signed +with the second key validates against ``key_id 1`` +(:file:`keys/prod_pubkey.pem`, emitted as ``autogen-pubkey-1.c``). + +What the build produces +*********************** + +After ``west build``, the bootloader and three signed application +images exist side by side: + +- :file:`build/mcuboot/zephyr/zephyr.hex` — the MCUboot bootloader + with the public half of **every key in the list embedded** (the first + from ``root-ed25519.pem`` and the second from + :file:`keys/prod_pubkey.pem`). Inspect + :file:`build/mcuboot/zephyr/autogen-pubkey.c` (the first key) and + :file:`autogen-pubkey-1.c` (the second; the trailing index disambiguates + the emitted C symbol names) to see the embedded byte arrays. Neither + file contains private key material — both are pure public-key data. +- :file:`build/mcuboot_signing_keys/zephyr/zephyr.signed.hex` — the + application signed with the **first (development)** key. This is the + image ``west flash`` programs by default. +- :file:`build/mcuboot_signing_keys/zephyr/zephyr.signed.prod.hex` and + :file:`build/mcuboot_signing_keys/zephyr/zephyr.signed.unknown.hex` — + the same application re-signed with the production key and with an + unrecognized key. The sample's :file:`CMakeLists.txt` emits these so + multi-key acceptance can be checked by flashing a prebuilt image. They + are a **demonstration shortcut** that signs with MCUboot's public test + keys; see the note under *Verifying acceptance*. + +``west flash`` programs the bootloader and the default (development- +signed) application automatically (the flash order is recorded in +:file:`build/domains.yaml`); the production- and unknown-signed copies +are flashed on demand, as shown under *Verifying acceptance*. For a single +combined hex, enable :kconfig:option:`SB_CONFIG_MERGED_HEX_FILES` in +:file:`sysbuild.conf` and sysbuild will emit +:file:`build/merged_.hex`. See +:ref:`sysbuild_merged_hex_files`. + +The security property this build demonstrates +============================================= + +A developer with only :file:`keys/prod_pubkey.pem` (no production +private key) can produce a bootloader that accepts production-signed +images. They cannot themselves produce such an image — only the +production team, who holds the private half, can. This is the +separation of duties a multi-key bootloader is designed to enforce. + +Which key signs the application? +******************************** + +The application's default image (:file:`zephyr.signed.hex`) is signed +at build time by imgtool using the **first** list entry (forwarded to +``CONFIG_MCUBOOT_SIGNATURE_KEY_FILE``). The bootloader uses the +remaining embedded keys only for verification — a development +workstation should only hold the private key it is authorized to sign +with. (This sample additionally re-signs demo copies with the other +keys, as described under *Verifying acceptance*, purely to make the +multi-key behavior easy to observe.) + +To change which key signs the application, make the desired private key +the first entry of ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE`` and rebuild — +the local development key on a developer's machine, or the production +key on the release build server. + +The two bootloaders are deliberately asymmetric: a **development** +bootloader also lists the production public key, so it accepts both +development- and production-signed images, whereas a **production** +bootloader lists only the production key and so rejects +development-signed images. That asymmetry is the point — production +units run only production firmware. + +Verifying acceptance of the second key +************************************** + +The build already produced +:file:`build/mcuboot_signing_keys/zephyr/zephyr.signed.prod.hex` — the +application signed with the production key. Flash it in place of the +default image to confirm the bootloader accepts the **second** embedded +key. ``--domain`` reflashes only the application slot, leaving the +bootloader in place: + +.. code-block:: console + + west flash -d build --domain mcuboot_signing_keys \ + --hex-file build/mcuboot_signing_keys/zephyr/zephyr.signed.prod.hex + +The bootloader verifies and chain-loads it identically, but the debug +log now reports ``bootutil_verify_sig: ED25519 key_id 1`` — the +*second* embedded key — instead of ``key_id 0``. Seeing the ``key_id`` +change from 0 to 1 between the development- and production-signed +images is the runtime confirmation that both keys are embedded and that +MCUboot selects the matching one per image. + +.. important:: + + Producing a production-signed image requires the production *private* + key, which a development workstation must not have. This sample can + emit :file:`zephyr.signed.prod.hex` only because it uses MCUboot's + publicly-available test keys — it is a demonstration shortcut. In a + real deployment the release team signs production images on their own + build server and hands the development team the finished binary; the + production private key never reaches the developer's machine. + +As a negative check, flash +:file:`build/mcuboot_signing_keys/zephyr/zephyr.signed.unknown.hex`, +signed with a key the bootloader does not embed: + +.. code-block:: console + + west flash -d build --domain mcuboot_signing_keys \ + --hex-file build/mcuboot_signing_keys/zephyr/zephyr.signed.unknown.hex + +MCUboot should reject it. With the debug logging this sample enables, +``bootutil_find_key`` matches none of the embedded keys, so **no** +``bootutil_verify_sig: key_id`` line is printed at all; the rejection +is reported as ``Image in the primary slot is not valid!`` and the +application is not booted. Re-flash the development image with +``west flash -d build`` to return to a bootable state. + +How the sample proves the public-only property +********************************************** + +After a default build, inspect :file:`build/mcuboot/zephyr/.config` +and :file:`build/mcuboot/zephyr/autogen-pubkey-1.c`: + +- ``CONFIG_BOOT_SIGNATURE_KEY_FILE`` ends with + :file:`keys/prod_pubkey.pem` — a PEM file whose only content is + ``-----BEGIN PUBLIC KEY-----``. +- ``autogen-pubkey-1.c`` contains a valid ED25519 public key byte + array. No private-key material was involved at any point. + +You can confirm the custody roles of any PEM directly:: + + imgtool keyinfo -k keys/prod_pubkey.pem # -> public + imgtool keyinfo -k .pem # -> private + +Attempting to ``imgtool sign`` with the sample's +:file:`prod_pubkey.pem` fails (as it should — signing requires a +private key). diff --git a/samples/sysbuild/mcuboot_signing_keys/keys/prod_pubkey.pem b/samples/sysbuild/mcuboot_signing_keys/keys/prod_pubkey.pem new file mode 100644 index 0000000000000..683505fe90c26 --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/keys/prod_pubkey.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAw7zfMHZOH120DYkuDQ6rBJwwBk55qO2293kuRpom2nc= +-----END PUBLIC KEY----- diff --git a/samples/sysbuild/mcuboot_signing_keys/prj.conf b/samples/sysbuild/mcuboot_signing_keys/prj.conf new file mode 100644 index 0000000000000..847620f44df2d --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/prj.conf @@ -0,0 +1,5 @@ +# Enable the Zephyr logging subsystem in the application so its boot banner is +# emitted through the same console as MCUboot's verification output. Immediate +# mode keeps the application's log lines ordered with the bootloader's. +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y diff --git a/samples/sysbuild/mcuboot_signing_keys/sample.yaml b/samples/sysbuild/mcuboot_signing_keys/sample.yaml new file mode 100644 index 0000000000000..2d762ba60295d --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/sample.yaml @@ -0,0 +1,18 @@ +sample: + description: Sample demonstrating MCUboot with multiple accepted signing keys + name: mcuboot signing keys +tests: + sample.sysbuild.mcuboot_signing_keys: + sysbuild: true + platform_allow: + - nrf52840dk/nrf52840 + - frdm_k64f + integration_platforms: + - nrf52840dk/nrf52840 + tags: mcuboot + harness: console + harness_config: + type: multi_line + regex: + - "Address of sample(.*)" + - "Hello mcuboot signing keys!(.*)" diff --git a/samples/sysbuild/mcuboot_signing_keys/src/main.c b/samples/sysbuild/mcuboot_signing_keys/src/main.c new file mode 100644 index 0000000000000..3662fdb3954e7 --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/src/main.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2026 Intercreate, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(app, LOG_LEVEL_INF); + +int main(void) +{ + LOG_INF("Address of sample %p", (void *)__rom_region_start); + LOG_INF("Hello mcuboot signing keys! %s", CONFIG_BOARD); + return 0; +} diff --git a/samples/sysbuild/mcuboot_signing_keys/sysbuild.conf b/samples/sysbuild/mcuboot_signing_keys/sysbuild.conf new file mode 100644 index 0000000000000..f77ef0732b706 --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/sysbuild.conf @@ -0,0 +1,22 @@ +# Sysbuild configuration for the MCUboot multiple-signing-keys sample. +# +# Models a development bootloader that accepts images signed with either a +# local development key or a remote production public key. The public half of +# both keys is embedded into the bootloader; only the development private key +# ever lives on this workstation. +# +# CONFIG_BOOT_SIGNATURE_KEY_FILE is a comma-separated list: +# 1. the development signing key pair (MCUboot's `root-ed25519.pem`, +# referenced from the module directory) — this is also the key the +# application is signed with; +# 2. a PUBLIC-ONLY PEM shipped in this sample's `keys/` directory, embedded +# for verification only. +# +# `${ZEPHYR_MCUBOOT_MODULE_DIR}` is expanded and the relative `keys/...` path is +# resolved against the application source directory by sysbuild before the list +# is forwarded to the MCUboot image. + +SB_CONFIG_BOOTLOADER_MCUBOOT=y +SB_CONFIG_MCUBOOT_MODE_OVERWRITE_ONLY=y +SB_CONFIG_BOOT_SIGNATURE_TYPE_ED25519=y +SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519.pem,keys/prod_pubkey.pem" diff --git a/samples/sysbuild/mcuboot_signing_keys/sysbuild/mcuboot.conf b/samples/sysbuild/mcuboot_signing_keys/sysbuild/mcuboot.conf new file mode 100644 index 0000000000000..f1d90d797ff9c --- /dev/null +++ b/samples/sysbuild/mcuboot_signing_keys/sysbuild/mcuboot.conf @@ -0,0 +1,8 @@ +# MCUboot-specific Kconfig fragment for the multiple-signing-keys sample. +# +# MCUboot already enables CONFIG_LOG by default. Raise its log level to debug +# so the per-slot image validation and the KEY/SIG TLV processing that backs +# multi-key verification are visible on the console at boot, letting users +# confirm an image signed with any embedded key is accepted (and one signed +# with an unknown key is rejected with "Image in the ... slot is not valid!"). +CONFIG_MCUBOOT_LOG_LEVEL_DBG=y diff --git a/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake b/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake index c4edc7e3daf56..69e54f24be095 100644 --- a/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake +++ b/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake @@ -6,8 +6,15 @@ # on a firmware updater image. set_config_bool(${ZCMAKE_APPLICATION} CONFIG_BOOTLOADER_MCUBOOT "${SB_CONFIG_BOOTLOADER_MCUBOOT}") + +# The application is signed with a single key: the first entry of the (possibly +# multi-key, comma-separated) SB_CONFIG_BOOT_SIGNATURE_KEY_FILE list. +string(REPLACE "," ";" application_signature_key_file "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}") +if(application_signature_key_file) + list(GET application_signature_key_file 0 application_signature_key_file) +endif() set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_SIGNATURE_KEY_FILE - "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}" + "${application_signature_key_file}" ) set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_ENCRYPTION_KEY_FILE "${SB_CONFIG_BOOT_ENCRYPTION_KEY_FILE}" diff --git a/share/sysbuild/image_configurations/MAIN_image_default.cmake b/share/sysbuild/image_configurations/MAIN_image_default.cmake index 6f5604d777753..a8c4a719c3ced 100644 --- a/share/sysbuild/image_configurations/MAIN_image_default.cmake +++ b/share/sysbuild/image_configurations/MAIN_image_default.cmake @@ -6,8 +6,15 @@ # on the main Zephyr image. set_config_bool(${ZCMAKE_APPLICATION} CONFIG_BOOTLOADER_MCUBOOT "${SB_CONFIG_BOOTLOADER_MCUBOOT}") + +# The application is signed with a single key: the first entry of the (possibly +# multi-key, comma-separated) SB_CONFIG_BOOT_SIGNATURE_KEY_FILE list. +string(REPLACE "," ";" application_signature_key_file "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}") +if(application_signature_key_file) + list(GET application_signature_key_file 0 application_signature_key_file) +endif() set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_SIGNATURE_KEY_FILE - "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}" + "${application_signature_key_file}" ) set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_ENCRYPTION_KEY_FILE "${SB_CONFIG_BOOT_ENCRYPTION_KEY_FILE}" diff --git a/share/sysbuild/images/bootloader/CMakeLists.txt b/share/sysbuild/images/bootloader/CMakeLists.txt index e7c81321f15d6..ea449b010bd65 100644 --- a/share/sysbuild/images/bootloader/CMakeLists.txt +++ b/share/sysbuild/images/bootloader/CMakeLists.txt @@ -17,7 +17,24 @@ if(SB_CONFIG_BOOTLOADER_MCUBOOT) # Link the footer size output of this MCUboot build with the main sysbuild image set_target_properties(${image} PROPERTIES MCUBOOT_FOOTER_UPDATE_IMAGES ${DEFAULT_IMAGE}) - set_config_string(${image} CONFIG_BOOT_SIGNATURE_KEY_FILE "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}") + # CONFIG_BOOT_SIGNATURE_KEY_FILE may be a single path or a comma-separated + # list. Expand CMake references (e.g. ${ZEPHYR_MCUBOOT_MODULE_DIR}) in each entry + # and resolve relative entries against the main application directory, so an + # application can ship keys under its own source tree and reference them by + # relative path -- the same resolution cmake/mcuboot.cmake performs app-side. + # Commas (rather than semicolons) separate the entries so the value survives + # set_config_string() and -D overrides, which treat ';' as a CMake list. + string(REPLACE "," ";" signature_key_files "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}") + set(resolved_key_files) + foreach(key_path ${signature_key_files}) + string(CONFIGURE "${key_path}" key_path) + if(key_path AND NOT IS_ABSOLUTE "${key_path}") + cmake_path(ABSOLUTE_PATH key_path BASE_DIRECTORY "${APP_DIR}" NORMALIZE) + endif() + list(APPEND resolved_key_files "${key_path}") + endforeach() + string(REPLACE ";" "," resolved_key_files "${resolved_key_files}") + set_config_string(${image} CONFIG_BOOT_SIGNATURE_KEY_FILE "${resolved_key_files}") if(SB_CONFIG_MCUBOOT_DIRECT_XIP_GENERATE_VARIANT) ExternalZephyrVariantProject_Add( diff --git a/share/sysbuild/images/bootloader/Kconfig b/share/sysbuild/images/bootloader/Kconfig index 40790490cda61..10cf26a2172dd 100644 --- a/share/sysbuild/images/bootloader/Kconfig +++ b/share/sysbuild/images/bootloader/Kconfig @@ -195,13 +195,33 @@ config BOOT_SIGNATURE_TYPE_ED25519 endchoice config BOOT_SIGNATURE_KEY_FILE - string "Signing PEM key file" if !BOOT_SIGNATURE_TYPE_NONE + string "Signing PEM key file (or comma-separated list)" if !BOOT_SIGNATURE_TYPE_NONE default "$(ZEPHYR_MCUBOOT_MODULE_DIR)/root-ec-p256.pem" if BOOT_SIGNATURE_TYPE_ECDSA_P256 default "$(ZEPHYR_MCUBOOT_MODULE_DIR)/root-ed25519.pem" if BOOT_SIGNATURE_TYPE_ED25519 default "$(ZEPHYR_MCUBOOT_MODULE_DIR)/root-rsa-2048.pem" if BOOT_SIGNATURE_TYPE_RSA default "" help - Absolute path to signing key file to use with MCUBoot. + Path to the signing key file used by MCUboot, or a comma-separated + list of paths to embed more than one verification key in the bootloader. + + When a list is given, the MCUboot bootloader embeds the public half of + every key and accepts images signed with any of them (via + bootutil_find_key). All keys must use the same BOOT_SIGNATURE_TYPE. + + The first entry is also the key the application is signed with, so it + must be a key pair (or a public-only PEM whose private half signs the + application elsewhere). Every entry past the first is verification-only + and must be a PUBLIC-only PEM; this is enforced at build time by + 'imgtool keyinfo --require public'. The typical use is a development + bootloader that boots both production- and development-signed images, + while production bootloaders embed only the production key. + + Each entry may be absolute or relative; relative paths are resolved + against the application source directory, and CMake variable references + such as ${ZEPHYR_MCUBOOT_MODULE_DIR} are expanded. A single entry + produces a bootloader bit-identical to one built without a list. + Multi-key mode is mutually exclusive with MCUBOOT_HW_KEY / + MCUBOOT_BUILTIN_KEY (enforced at the MCUboot Kconfig level). config SUPPORT_BOOT_ENCRYPTION bool diff --git a/tests/boot/test_mcuboot/testcase.yaml b/tests/boot/test_mcuboot/testcase.yaml index ddeac3705c1ea..add14358b46f9 100644 --- a/tests/boot/test_mcuboot/testcase.yaml +++ b/tests/boot/test_mcuboot/testcase.yaml @@ -94,3 +94,12 @@ tests: extra_args: - SB_CONFIG_MCUBOOT_MODE_SWAP_USING_MOVE=y - swapped_app_CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_MOVE=y + bootloader.mcuboot.signing_keys: + platform_allow: + - nrf52840dk/nrf52840 + - frdm_k64f + integration_platforms: + - nrf52840dk/nrf52840 + extra_args: + - SB_CONFIG_BOOT_SIGNATURE_TYPE_ED25519=y + - SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519.pem,${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519-2-pub.pem" diff --git a/west.yml b/west.yml index a9a693b4b812c..aec96d9a55ece 100644 --- a/west.yml +++ b/west.yml @@ -329,7 +329,8 @@ manifest: groups: - crypto - name: mcuboot - revision: 511dc9e28f223d6b158740b66a702209531361ff + url: https://github.com/intercreate/mcuboot + revision: feature-zephyr-multiple-signing-keys path: bootloader/mcuboot groups: - bootloader