diff --git a/doc/build/signing/index.rst b/doc/build/signing/index.rst index 0870add5768c8..4741da4934b2a 100644 --- a/doc/build/signing/index.rst +++ b/doc/build/signing/index.rst @@ -39,6 +39,12 @@ 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_2``: optional second key that MCUboot will accept for + image verification in addition to the primary key. Both keys must use the same signature + type. Typical use: development bootloaders that should boot both production and + development images, while production bootloaders remain locked to the production key + alone. Leave unset (the default) to produce a bootloader identical to one built without + this option - :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, diff --git a/doc/releases/release-notes-4.5.rst b/doc/releases/release-notes-4.5.rst index 0f5d6563f4b5b..fcd1c4ce91f7a 100644 --- a/doc/releases/release-notes-4.5.rst +++ b/doc/releases/release-notes-4.5.rst @@ -108,6 +108,12 @@ Libraries / Subsystems Other notable changes ********************* +* MCUboot sysbuild gained ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2`` for optionally + embedding a second verification key in the bootloader. When set, MCUboot + accepts images signed with either the primary or the second key. See + :ref:`build-signing` and the + :zephyr:code-sample:`mcuboot_dual_key` 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..8fa0d47682f15 100644 --- a/modules/Kconfig.mcuboot +++ b/modules/Kconfig.mcuboot @@ -64,6 +64,23 @@ config MCUBOOT_SIGNATURE_KEY_FILE must sign and prepare the Zephyr binaries manually to be bootable from MCUboot. +config MCUBOOT_SIGNATURE_KEY_FILE_2 + string "Path to a second mcuboot signing key file (optional)" + default "" + help + Path to an optional second signing key known to the application + build. Corresponds to the bootloader's BOOT_SIGNATURE_KEY_FILE_2; + see SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2 for end-to-end semantics. + + The default Zephyr imgtool signing flow signs with + MCUBOOT_SIGNATURE_KEY_FILE only; this option does not cause + imgtool to dual-sign. It exposes the secondary key path to + custom signing scripts and pipelines that may produce a + separately-signed artifact (for example, a dev-signed variant + alongside a prod-signed default). + + Leave empty (the default) to disable. + config MCUBOOT_ENCRYPTION_KEY_FILE string "Path to the mcuboot encryption key file" default "" diff --git a/samples/sysbuild/mcuboot_dual_key/CMakeLists.txt b/samples/sysbuild/mcuboot_dual_key/CMakeLists.txt new file mode 100644 index 0000000000000..b3f5493e48b70 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mcuboot_dual_key) + +test_sysbuild() + +target_sources(app PRIVATE src/main.c) diff --git a/samples/sysbuild/mcuboot_dual_key/README.rst b/samples/sysbuild/mcuboot_dual_key/README.rst new file mode 100644 index 0000000000000..73c28a0e8fe14 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/README.rst @@ -0,0 +1,173 @@ +.. zephyr:code-sample:: mcuboot_dual_key + :name: MCUboot dual signing key with sysbuild + + Build a development bootloader that accepts images signed with + either a local development key or a remote production public key. + +Overview +******** + +This sample demonstrates the canonical real-world use of +``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2``: 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. + +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 via ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2`` — no private +material is required or present. + +Keys used by this sample +======================== + +- **Primary key** — represents the developer's own signing key. This + sample inherits the ED25519 Kconfig default + (``$ZEPHYR_MCUBOOT_MODULE_DIR/root-ed25519.pem``), a publicly-known + MCUboot test key standing in for a private dev key. +- **Second key** — :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. + +:file:`sysbuild.conf` references :file:`keys/prod_pubkey.pem` with a +plain relative path. Sysbuild resolves application-owned signing-key +paths against the application source directory before forwarding them +to the MCUboot child image. + +All keys here derive from MCUboot's publicly-available test keys and +are insecure. Substitute your own ED25519 keypair when adopting this +pattern for production. + +Files +===== + +- :file:`sysbuild.conf` — enables MCUboot with ED25519 signing, + inherits the primary key from the Kconfig default, and points the + second key at the sample-shipped public-only PEM. +- :file:`keys/prod_pubkey.pem` — public-only PEM shipped with the + sample. +- :file:`sysbuild/mcuboot.conf` — MCUboot-specific Kconfig + adjustments. + +Building and running +******************** + +.. zephyr-app-commands:: + :tool: west + :zephyr-app: samples/sysbuild/mcuboot_dual_key + :board: nrf52840dk/nrf52840 + :goals: build flash + :west-args: --sysbuild + :compact: + +The default build signs the application with the primary (development) +key. Expected console output: + +.. code-block:: console + + *** Booting MCUboot *** + *** Booting Zephyr OS *** + Address of sample 0xc000 + Hello mcuboot dual key! nrf52840dk/nrf52840 + +What the build produces +*********************** + +After ``west build``, two flashable artifacts exist side by side: + +- :file:`build/mcuboot/zephyr/zephyr.hex` — the MCUboot bootloader + with **both public keys embedded** (the primary from + ``root-ed25519.pem`` and the secondary from + :file:`keys/prod_pubkey.pem`). Inspect + :file:`build/mcuboot/zephyr/autogen-pubkey.c` and + :file:`autogen-pubkey2.c` to see the embedded byte arrays. Neither + file contains private key material — both are pure public-key data. +- :file:`build/mcuboot_dual_key/zephyr/zephyr.signed.hex` — the + application, signed by imgtool with the **primary (development) + private key only**. The production private key is not on this + workstation and was not used. + +``west flash`` chains both to the target automatically (the flash +order is recorded in :file:`build/domains.yaml`). 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 dual-key bootloader is designed to enforce. + +Which key signs the application? +******************************** + +The application is signed at build time by imgtool, using the +**primary** key (``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE``). The second +key is never used for signing — it exists only for bootloader-side +verification. This matches the security model: a development +workstation should only hold the private key it is authorized to +sign with. + +To change which key signs the application, point +``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE`` in :file:`sysbuild.conf` at the +desired private key and rebuild. On a development workstation that is +typically the local dev key; on a production build server it would be +the production private key. Both types of workstation would use the +same ``SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2`` value (embedding the +*other* role's public key), producing bootloader binaries that +accept either side's signed images. + +Verifying second-key acceptance +******************************* + +To confirm the bootloader accepts images signed with the production +key, re-sign the application slot with MCUboot's +``root-ed25519-2.pem`` — the *private* half of the same keypair whose +public half ships as :file:`keys/prod_pubkey.pem` — and reflash only +the application: + +.. code-block:: console + + west sign -t imgtool -d build/mcuboot_dual_key \ + -- --key ${ZEPHYR_MCUBOOT_MODULE_DIR}/root-ed25519-2.pem + +The bootloader should verify and chain-load the image identically. +This simulates a release-signed image arriving at a development +workstation for testing — the workstation runs it without ever +possessing the production signing key. + +In practice the release team would sign production images on their own +build server and hand the dev team a signed binary; the +``root-ed25519-2.pem`` invocation above only stands in for "here is +what a production-signed image looks like" so the full loop can be +demonstrated from one machine. + +As a negative check, signing with ``root-ed25519-unknown.pem`` (also +shipped in MCUboot for testing) should cause MCUboot to reject the +image at boot. + +How the sample proves the public-only property +********************************************** + +After a default build, inspect :file:`build/mcuboot/zephyr/.config` +and :file:`build/mcuboot/zephyr/autogen-pubkey2.c`: + +- ``CONFIG_BOOT_SIGNATURE_KEY_FILE_2`` points at + :file:`keys/prod_pubkey.pem` — a PEM file whose only content is + ``-----BEGIN PUBLIC KEY-----``. +- ``autogen-pubkey2.c`` contains a valid ED25519 public key byte + array. No private-key material was involved at any point. + +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_dual_key/keys/prod_pubkey.pem b/samples/sysbuild/mcuboot_dual_key/keys/prod_pubkey.pem new file mode 100644 index 0000000000000..683505fe90c26 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/keys/prod_pubkey.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAw7zfMHZOH120DYkuDQ6rBJwwBk55qO2293kuRpom2nc= +-----END PUBLIC KEY----- diff --git a/samples/sysbuild/mcuboot_dual_key/prj.conf b/samples/sysbuild/mcuboot_dual_key/prj.conf new file mode 100644 index 0000000000000..932b79829cf36 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/prj.conf @@ -0,0 +1 @@ +# Empty file diff --git a/samples/sysbuild/mcuboot_dual_key/sample.yaml b/samples/sysbuild/mcuboot_dual_key/sample.yaml new file mode 100644 index 0000000000000..d68c1d012cdab --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/sample.yaml @@ -0,0 +1,18 @@ +sample: + description: Sample demonstrating MCUboot with two accepted signing keys + name: mcuboot dual key +tests: + sample.sysbuild.mcuboot_dual_key: + 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 dual key!(.*)" diff --git a/samples/sysbuild/mcuboot_dual_key/src/main.c b/samples/sysbuild/mcuboot_dual_key/src/main.c new file mode 100644 index 0000000000000..dfb62157373b3 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/src/main.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2026 Intercreate, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int main(void) +{ + printk("Address of sample %p\n", (void *)__rom_region_start); + printk("Hello mcuboot dual key! %s\n", CONFIG_BOARD); + return 0; +} diff --git a/samples/sysbuild/mcuboot_dual_key/sysbuild.conf b/samples/sysbuild/mcuboot_dual_key/sysbuild.conf new file mode 100644 index 0000000000000..5e95f7af0f879 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/sysbuild.conf @@ -0,0 +1,17 @@ +# Sysbuild configuration for the dual-key MCUboot sample. +# +# Models a development bootloader that accepts images signed with either +# a local development key or a remote production public key. Both public +# keys are embedded into the bootloader; only the development private key +# lives on this workstation. +# +# The primary key inherits from the ED25519 Kconfig default (MCUboot's +# `root-ed25519.pem`), representing a local dev signing key. The second +# key points at a PUBLIC-ONLY PEM that ships in this sample's `keys/` +# directory. The relative path is resolved against the application +# source directory by sysbuild. + +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_2="keys/prod_pubkey.pem" diff --git a/samples/sysbuild/mcuboot_dual_key/sysbuild/mcuboot.conf b/samples/sysbuild/mcuboot_dual_key/sysbuild/mcuboot.conf new file mode 100644 index 0000000000000..bd95d88d06009 --- /dev/null +++ b/samples/sysbuild/mcuboot_dual_key/sysbuild/mcuboot.conf @@ -0,0 +1,2 @@ +# MCUboot-specific Kconfig fragment for the dual-key sample. +CONFIG_MCUBOOT_LOG_LEVEL_INF=y diff --git a/scripts/ci/check_compliance.py b/scripts/ci/check_compliance.py index 9c3aa721b8302..1fa8307e3ea60 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -1535,6 +1535,7 @@ def check_no_undef_outside_kconfig(self, kconf): "BOOT_SHARE_DATA", # Used in Kconfig text "BOOT_SHARE_DATA_BOOTINFO", # Used in (sysbuild-based) test "BOOT_SIGNATURE_KEY_FILE", # MCUboot setting used by sysbuild + "BOOT_SIGNATURE_KEY_FILE_2", # MCUboot setting used by sysbuild "BOOT_SIGNATURE_TYPE_ECDSA_P256", # MCUboot setting used by sysbuild "BOOT_SIGNATURE_TYPE_ED25519", # MCUboot setting used by sysbuild "BOOT_SIGNATURE_TYPE_NONE", # MCUboot setting used by sysbuild diff --git a/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake b/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake index c4edc7e3daf56..2e183f1641ef7 100644 --- a/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake +++ b/share/sysbuild/image_configurations/FIRMWARE_LOADER_image_default.cmake @@ -9,6 +9,9 @@ set_config_bool(${ZCMAKE_APPLICATION} CONFIG_BOOTLOADER_MCUBOOT "${SB_CONFIG_BOO set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_SIGNATURE_KEY_FILE "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}" ) +set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_SIGNATURE_KEY_FILE_2 + "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2}" +) 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..1233e2e62642e 100644 --- a/share/sysbuild/image_configurations/MAIN_image_default.cmake +++ b/share/sysbuild/image_configurations/MAIN_image_default.cmake @@ -9,6 +9,9 @@ set_config_bool(${ZCMAKE_APPLICATION} CONFIG_BOOTLOADER_MCUBOOT "${SB_CONFIG_BOO set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_SIGNATURE_KEY_FILE "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}" ) +set_config_string(${ZCMAKE_APPLICATION} CONFIG_MCUBOOT_SIGNATURE_KEY_FILE_2 + "${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2}" +) 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..1030a72de9d82 100644 --- a/share/sysbuild/images/bootloader/CMakeLists.txt +++ b/share/sysbuild/images/bootloader/CMakeLists.txt @@ -17,7 +17,19 @@ 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}") + # Normalize relative signing-key paths to absolute, resolved against the + # main application directory. This lets an application ship keys under its + # own source tree and reference them with a simple relative path in + # `sysbuild.conf` (e.g. `SB_CONFIG_BOOT_SIGNATURE_KEY_FILE_2="keys/mykey.pem"`), + # matching the resolution the app-side signing flow in `cmake/mcuboot.cmake` + # already performs against APPLICATION_CONFIG_DIR. + foreach(sb_sym BOOT_SIGNATURE_KEY_FILE BOOT_SIGNATURE_KEY_FILE_2) + set(key_path "${SB_CONFIG_${sb_sym}}") + if(key_path AND NOT IS_ABSOLUTE "${key_path}") + cmake_path(ABSOLUTE_PATH key_path BASE_DIRECTORY "${APP_DIR}" NORMALIZE) + endif() + set_config_string(${image} CONFIG_${sb_sym} "${key_path}") + endforeach() 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..dcdf766726e8c 100644 --- a/share/sysbuild/images/bootloader/Kconfig +++ b/share/sysbuild/images/bootloader/Kconfig @@ -201,7 +201,28 @@ config BOOT_SIGNATURE_KEY_FILE 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. May be absolute or + relative; relative paths are resolved against the application + source directory. + +config BOOT_SIGNATURE_KEY_FILE_2 + string "Second signing PEM key file (optional)" if !BOOT_SIGNATURE_TYPE_NONE + default "" + help + Optional second signing key. When non-empty, the MCUboot bootloader + embeds both public keys and accepts images signed with either + BOOT_SIGNATURE_KEY_FILE or this key (via bootutil_find_key). + Both keys must use the same BOOT_SIGNATURE_TYPE. Typical use: + development bootloaders that should boot both production and + development images, while production bootloaders accept only the + production key. May be absolute or relative; relative paths are + resolved against the application source directory. A PUBLIC-only + PEM is sufficient here — the bootloader needs only the public + half of this key to verify signatures. Leave empty (the default) + to disable; when empty, the compiled bootloader is bit-identical + to one built without this option. 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/root-ed25519-2.pem b/tests/boot/test_mcuboot/root-ed25519-2.pem new file mode 100644 index 0000000000000..79976b9d43142 --- /dev/null +++ b/tests/boot/test_mcuboot/root-ed25519-2.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEICZVk44tV7KC3eJ+Qokha0aULNUVqDp9iR0cKjpqcO4D +-----END PRIVATE KEY----- diff --git a/tests/boot/test_mcuboot/testcase.yaml b/tests/boot/test_mcuboot/testcase.yaml index ddeac3705c1ea..0ce20ebee4e6d 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.dual_key: + 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_2="root-ed25519-2.pem" diff --git a/west.yml b/west.yml index 7df37e5cec0b1..f5eadf069109c 100644 --- a/west.yml +++ b/west.yml @@ -335,7 +335,8 @@ manifest: groups: - crypto - name: mcuboot - revision: ee39e2d694bd827ffd1bebbce2f571a9154e6ec2 + url: https://github.com/intercreate/mcuboot + revision: feature-zephyr-multiple-signing-keys path: bootloader/mcuboot groups: - bootloader