forked from zephyrproject-rtos/zephyr
-
Notifications
You must be signed in to change notification settings - Fork 0
mcuboot: support multiple signing keys #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JPHutchins
wants to merge
1
commit into
main
Choose a base branch
from
mcuboot/multiple-signing-keys
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # 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) | ||
|
|
||
| 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 | ||
|
JPHutchins marked this conversation as resolved.
|
||
| --version=${CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION} | ||
| --header-size=${CONFIG_ROM_START_OFFSET} | ||
| --slot-size=${slot_size} | ||
| --overwrite-only | ||
| --align=1 | ||
| ) | ||
|
|
||
| # Match the bootloader's hash/signature scheme (as cmake/mcuboot.cmake does). | ||
| if(CONFIG_MCUBOOT_BOOTLOADER_SIGNATURE_TYPE_PURE) | ||
| list(APPEND imgtool_sign --pure) | ||
| elseif(CONFIG_MCUBOOT_BOOTLOADER_USES_SHA512) | ||
| list(APPEND imgtool_sign --sha 512) | ||
| endif() | ||
|
|
||
| # ${image}.hex is a POST_BUILD byproduct of the Zephyr ELF target; depend on | ||
| # the target so the hex exists on every generator, not just Ninja. | ||
| 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 ${logical_target_for_zephyr_elf} ${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() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| .. 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 a common 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 used here in place of a 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] <inf> app: Address of sample 0xc000 | ||
| [00:00:00.000,000] <inf> 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_<board_target>.hex`. See | ||
| :ref:`sysbuild_merged_hex_files`. | ||
|
|
||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| 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 <your dev key>.pem # -> private | ||
|
|
||
| Attempting to ``imgtool sign`` with the sample's | ||
| :file:`prod_pubkey.pem` fails (as it should — signing requires a | ||
| private key). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
|
Check warning on line 1 in samples/sysbuild/mcuboot_signing_keys/keys/prod_pubkey.pem
|
||
| MCowBQYDK2VwAyEAw7zfMHZOH120DYkuDQ6rBJwwBk55qO2293kuRpom2nc= | ||
| -----END PUBLIC KEY----- | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Enable logging so the application's banner reaches the console. | ||
| CONFIG_LOG=y |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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!(.*)" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| * Copyright (c) 2026 Intercreate, Inc. | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #include <zephyr/kernel.h> | ||
| #include <zephyr/linker/linker-defs.h> | ||
| #include <zephyr/logging/log.h> | ||
|
|
||
| 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; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.