Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/sim.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
- "sig-rsa validate-primary-slot ram-load multiimage"
- "sig-rsa validate-primary-slot direct-xip multiimage"
- "sig-ecdsa hw-rollback-protection multiimage"
- "sig-ed25519 sig-second-key"
- "sig-ecdsa-psa,sig-ecdsa-psa sig-p384,sig-ecdsa-psa swap-move bootstrap max-align-16"
- "sig-ecdsa-psa enc-ec256 max-align-16, sig-ecdsa-psa enc-ec256 swap-offset validate-primary-slot max-align-16"
- "ram-load enc-aes256-kw multiimage"
Expand Down
90 changes: 61 additions & 29 deletions boot/zephyr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -314,19 +314,11 @@ if(CONFIG_MCUBOOT_SERIAL)
endif()
endif()

set(mcuboot_sign_key_count_max 8)
target_compile_definitions(app PRIVATE MCUBOOT_SIGN_KEY_COUNT_MAX=${mcuboot_sign_key_count_max})

if(NOT CONFIG_BOOT_SIGNATURE_TYPE_NONE AND NOT CONFIG_BOOT_BUILTIN_KEY AND NOT
CONFIG_BOOT_SIGNATURE_KEY_FILE STREQUAL "")
set(key_file "${CONFIG_BOOT_SIGNATURE_KEY_FILE}")
string(CONFIGURE "${key_file}" key_file)

if(IS_ABSOLUTE ${key_file})
set(signing_key_file ${key_file})
elseif(EXISTS ${APPLICATION_CONFIG_DIR}/${key_file})
set(signing_key_file ${APPLICATION_CONFIG_DIR}/${key_file})
else()
set(signing_key_file ${MCUBOOT_DIR}/${key_file})
endif()
message("MCUBoot bootloader key file: ${signing_key_file}")

set(mcuboot_default_signature_files
${MCUBOOT_DIR}/root-ec-p256-pkcs8.pem
Expand All @@ -338,26 +330,66 @@ if(NOT CONFIG_BOOT_SIGNATURE_TYPE_NONE AND NOT CONFIG_BOOT_BUILTIN_KEY AND NOT
${MCUBOOT_DIR}/root-ec-p256.pem
)

# Emit a warning if using one of the default MCUboot key files
if(${signing_key_file} IN_LIST mcuboot_default_signature_files)
message(WARNING "WARNING: Using default MCUboot signing key file, this file is for debug use only and is not secure!")
set(mcuboot_key_files "${CONFIG_BOOT_SIGNATURE_KEY_FILE}")
list(LENGTH mcuboot_key_files mcuboot_key_count)
if(mcuboot_key_count GREATER ${mcuboot_sign_key_count_max})
message(FATAL_ERROR
"CONFIG_BOOT_SIGNATURE_KEY_FILE lists ${mcuboot_key_count} keys, but the "
"Zephyr port is capped at ${mcuboot_sign_key_count_max}. Raise mcuboot_sign_key_count_max "
"in boot/zephyr/CMakeLists.txt to extend it.")
endif()

set(generated_pubkey ${ZEPHYR_BINARY_DIR}/autogen-pubkey.c)
add_custom_command(
OUTPUT ${generated_pubkey}
COMMAND
${PYTHON_EXECUTABLE}
${MCUBOOT_DIR}/scripts/imgtool.py
getpub
-k
${signing_key_file}
> ${generated_pubkey}
DEPENDS ${signing_key_file}
)
target_sources(app PRIVATE
${generated_pubkey}
)
set(key_index 0)
foreach(raw_key_path IN LISTS mcuboot_key_files)
string(CONFIGURE "${raw_key_path}" key_path)

if(IS_ABSOLUTE ${key_path})
set(resolved_key_path ${key_path})
elseif(EXISTS ${APPLICATION_CONFIG_DIR}/${key_path})
set(resolved_key_path ${APPLICATION_CONFIG_DIR}/${key_path})
else()
set(resolved_key_path ${MCUBOOT_DIR}/${key_path})
endif()

if(key_index EQUAL 0)
set(signing_key_file ${resolved_key_path})
set(name_suffix_arg "")
set(generated_pubkey ${ZEPHYR_BINARY_DIR}/autogen-pubkey.c)
set(keyinfo_check_command "")
message("MCUBoot bootloader key file: ${resolved_key_path}")
if(${resolved_key_path} IN_LIST mcuboot_default_signature_files)
message(WARNING "WARNING: Using default MCUboot signing key file, this file is for debug use only and is not secure!")
endif()
else()
set(name_suffix_arg "--name-suffix" "_${key_index}")
set(generated_pubkey ${ZEPHYR_BINARY_DIR}/autogen-pubkey${key_index}.c)
set(keyinfo_check_command
COMMAND ${PYTHON_EXECUTABLE} ${MCUBOOT_DIR}/scripts/imgtool.py
keyinfo --key ${resolved_key_path} --require public
)
message("MCUBoot bootloader verification key file #${key_index}: ${resolved_key_path}")
if(${resolved_key_path} IN_LIST mcuboot_default_signature_files)
message(WARNING "WARNING: Using default MCUboot signing key file for verification key #${key_index}, this file is for debug use only and is not secure!")
endif()
target_compile_definitions(app PRIVATE MCUBOOT_SIGN_KEY_${key_index}=1)
endif()

add_custom_command(
OUTPUT ${generated_pubkey}
${keyinfo_check_command}
COMMAND
${PYTHON_EXECUTABLE}
${MCUBOOT_DIR}/scripts/imgtool.py
getpub
-k
${resolved_key_path}
${name_suffix_arg}
> ${generated_pubkey}
DEPENDS ${resolved_key_path}
)
target_sources(app PRIVATE ${generated_pubkey})
math(EXPR key_index "${key_index} + 1")
endforeach()
endif()

if(CONFIG_BOOT_ENCRYPTION_KEY_FILE AND NOT CONFIG_BOOT_ENCRYPTION_KEY_FILE STREQUAL "")
Expand Down
38 changes: 30 additions & 8 deletions boot/zephyr/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ config BOOT_BYPASS_KEY_MATCH
MCUboot code and boot time.

config BOOT_SIGNATURE_KEY_FILE
string "PEM key file"
string "PEM key file (or semicolon-separated list)"
depends on !BOOT_SIGNATURE_TYPE_NONE
default "root-ec-p256.pem" if BOOT_SIGNATURE_TYPE_ECDSA_P256
default "root-ed25519.pem" if BOOT_SIGNATURE_TYPE_ED25519
Expand All @@ -472,13 +472,35 @@ config BOOT_SIGNATURE_KEY_FILE
default ""
depends on !BOOT_BUILTIN_KEY
help
You can use either absolute or relative path.
In case relative path is used, the build system assumes that it starts
from the APPLICATION_CONFIG_DIR directory. If the key file is not there, the build
system uses relative path that starts from the MCUBoot repository root directory.
The key file will be parsed by imgtool's getpub command and a .c source
with the public key information will be written in a format expected by
MCUboot.
Path to a signing/verification key PEM, or a semicolon-separated
list of PEMs (e.g. "prod_pub.pem;dev_pub.pem") to embed multiple
verification keys in the bootloader. Only the public-key bytes are
ever embedded in the bootloader image regardless of which form is
passed in.

The first entry may be either a keypair PEM or a public-only PEM.
A keypair is required only if the same file is also used with
`imgtool sign`; a public-only PEM is sufficient (and preferred)
when image signing is performed elsewhere with the private half
held under separate custody. Embedded private material is
recoverable from flash, so a public-only first entry is the safer
default whenever the signing workflow allows.

Subsequent entries (positions past the first) must be public-only
PEMs; the build will fail at CMake time otherwise. This guards the
intended workflow: a development bootloader that accepts both
production-signed images (verified against the prod public key,
whose private half stays under release-team custody) and
development-signed images (verified against the dev public key).
All entries must use the same BOOT_SIGNATURE_TYPE. Multi-key mode
is mutually exclusive with BOOT_HW_KEY, BOOT_BUILTIN_KEY, and
BOOT_BYPASS_KEY_MATCH.

Each entry can be an absolute or relative path. Relative paths are
resolved first against APPLICATION_CONFIG_DIR, then against the
MCUboot repository root. Each file is parsed by imgtool's getpub
command and a .c source with the public key information is written
in a format expected by MCUboot.

Note: In configuration files, escaped CMake variables can be used to refer to paths
e.g. \${CMAKE_CURRENT_LIST_DIR} will allow referencing a file in that directory, these
Expand Down
49 changes: 32 additions & 17 deletions boot/zephyr/keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,42 @@
* provides via the compiler command line).
*/
#include <mcuboot_config/mcuboot_config.h>
#include <zephyr/sys/util.h>

#if !defined(MCUBOOT_HW_KEY)
#if defined(MCUBOOT_SIGN_RSA) || defined(MCUBOOT_SIGN_EC256) || defined(MCUBOOT_SIGN_ED25519)
#define HAVE_KEYS

#ifndef MCUBOOT_SIGN_KEY_COUNT_MAX
#error "MCUBOOT_SIGN_KEY_COUNT_MAX must be defined by the build system"
#endif

#define _BOOT_KEY_CAT(a, b) a##b
#define BOOT_KEY_CAT(a, b) _BOOT_KEY_CAT(a, b)

#if defined(MCUBOOT_SIGN_RSA)
extern const unsigned char rsa_pub_key[];
extern unsigned int rsa_pub_key_len;
# define BOOT_KEY_PRIMARY rsa_pub_key
#elif defined(MCUBOOT_SIGN_EC256)
extern const unsigned char ecdsa_pub_key[];
extern unsigned int ecdsa_pub_key_len;
# define BOOT_KEY_PRIMARY ecdsa_pub_key
#elif defined(MCUBOOT_SIGN_ED25519)
extern const unsigned char ed25519_pub_key[];
extern unsigned int ed25519_pub_key_len;
# define BOOT_KEY_PRIMARY ed25519_pub_key
#endif

#define BOOT_KEY_NAME(N) BOOT_KEY_CAT(BOOT_KEY_PRIMARY, BOOT_KEY_CAT(_, N))

#define BOOT_KEY_DECL_AT(N, _) \
IF_ENABLED(MCUBOOT_SIGN_KEY_##N, \
(extern const unsigned char BOOT_KEY_NAME(N)[]; \
extern unsigned int BOOT_KEY_CAT(BOOT_KEY_NAME(N), _len);))

#define BOOT_KEY_ENTRY_AT(N, _) \
IF_ENABLED(MCUBOOT_SIGN_KEY_##N, \
({ .key = BOOT_KEY_NAME(N), \
.len = &BOOT_KEY_CAT(BOOT_KEY_NAME(N), _len) },))

extern const unsigned char BOOT_KEY_PRIMARY[];
extern unsigned int BOOT_KEY_CAT(BOOT_KEY_PRIMARY, _len);
LISTIFY(UTIL_INC(MCUBOOT_SIGN_KEY_COUNT_MAX), BOOT_KEY_DECL_AT, ())
#endif

/*
Expand All @@ -51,19 +73,12 @@ extern unsigned int ed25519_pub_key_len;
#if defined(HAVE_KEYS)
const struct bootutil_key bootutil_keys[] = {
{
#if defined(MCUBOOT_SIGN_RSA)
.key = rsa_pub_key,
.len = &rsa_pub_key_len,
#elif defined(MCUBOOT_SIGN_EC256)
.key = ecdsa_pub_key,
.len = &ecdsa_pub_key_len,
#elif defined(MCUBOOT_SIGN_ED25519)
.key = ed25519_pub_key,
.len = &ed25519_pub_key_len,
#endif
.key = BOOT_KEY_PRIMARY,
.len = &BOOT_KEY_CAT(BOOT_KEY_PRIMARY, _len),
},
LISTIFY(UTIL_INC(MCUBOOT_SIGN_KEY_COUNT_MAX), BOOT_KEY_ENTRY_AT, ())
};
const int bootutil_key_cnt = 1;
const int bootutil_key_cnt = sizeof(bootutil_keys) / sizeof(bootutil_keys[0]);
#endif /* HAVE_KEYS */
#else
unsigned int pub_key_len;
Expand Down
8 changes: 8 additions & 0 deletions boot/zephyr/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ tests:
integration_platforms:
- nrf52840dk/nrf52840
tags: bootloader_mcuboot
sample.bootloader.mcuboot.two_signing_keys:
extra_configs:
- CONFIG_BOOT_SIGNATURE_TYPE_ED25519=y
- CONFIG_BOOT_SIGNATURE_KEY_FILE="root-ed25519.pem;root-ed25519-2-pub.pem"
platform_allow: nrf52840dk/nrf52840
integration_platforms:
- nrf52840dk/nrf52840
tags: bootloader_mcuboot
sample.bootloader.mcuboot.runtime_source.hooks:
extra_args: EXTRA_CONF_FILE=../../samples/runtime-source/zephyr/sample.conf
TEST_RUNTIME_SOURCE_HOOKS=y
Expand Down
24 changes: 24 additions & 0 deletions docs/imgtool.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ exported public-key PEM. You can replace or insert the emitted code
into the key file. However, when the `MCUBOOT_HW_KEY` config option
is enabled, this last step is unnecessary and can be skipped.

When embedding more than one signing-verification key in the same image
(for example, a Zephyr build with a multi-key
`CONFIG_BOOT_SIGNATURE_KEY_FILE` list), pass `--name-suffix` to
distinguish the emitted symbol names:

./scripts/imgtool.py getpub -k dev-key.pem --name-suffix _2

emits `<shortname>_pub_key_2[]` and `<shortname>_pub_key_2_len` (the
same suffix is applied by `getpubhash` for the lang-c encoding). The
option is accepted only for the `lang-c` / `lang-rust` encodings; using
it with `--encoding pem` or `--encoding raw` is rejected.

## [Inspecting key kind](#inspecting-key-kind)

For build-system use, `imgtool keyinfo` reports whether a PEM contains
private material (`private`) or only public material (`public`):

./scripts/imgtool.py keyinfo -k some-key.pem

Pair with `--require private` or `--require public` to exit non-zero
when the kind does not match. The Zephyr port uses this to enforce that
every verification-only key passed via `CONFIG_BOOT_SIGNATURE_KEY_FILE`
past the first entry is a public-only PEM.

## [Signing images](#signing-images)

Image signing takes an image in binary or Intel Hex format intended for the
Expand Down
42 changes: 40 additions & 2 deletions docs/readme-zephyr.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,46 @@ by a release team and only an exported public key is provided to
bootloader builders. Signing images (`imgtool sign`) requires the
private key.

Currently, the Zephyr RTOS port limits its support to one keypair at the time,
although MCUboot's key management infrastructure supports multiple keypairs.
The Zephyr port supports embedding multiple verification keys in the
bootloader. `CONFIG_BOOT_SIGNATURE_KEY_FILE` accepts a single PEM path or
a semicolon-separated list, e.g.
`"prod_pubkey.pem;dev_pubkey.pem"` or
`"\${CMAKE_CURRENT_LIST_DIR}/prod_pubkey.pem;\${CMAKE_CURRENT_LIST_DIR}/dev_pubkey.pem"`.
Only the public-key bytes are ever embedded in the bootloader image,
regardless of which form is passed in. Up to eight total keys are
supported by the Zephyr port. All entries must use the same
`BOOT_SIGNATURE_TYPE`, and multi-key mode is mutually exclusive with
`BOOT_HW_KEY`, `BOOT_BUILTIN_KEY`, and `BOOT_BYPASS_KEY_MATCH`.

The first entry **may** be a keypair PEM or a public-only PEM. A
keypair is required only if the same file is also used with
`imgtool sign`; otherwise a public-only PEM is sufficient — and
preferred, since private material embedded in the bootloader image is
recoverable from flash. Subsequent entries (positions past the first)
**must** be public-only PEMs; the build is rejected at CMake time
otherwise (via `imgtool keyinfo --require public`).

### Custody model

The feature is for separating signing custody from verification custody.

| Key | Custody | Distribution | Blast radius if lost |
| ------------------------- | ------------------------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------- |
| Production private | HSM, signing ceremony, release team only | Never leaves the HSM | Catastrophic: attacker can sign images that boot on the prod fleet |
| Production public | N/A (public material) | Embedded in dev bootloaders so dev units can verify prod images | None: public by design |
| Development private | Loosely held by engineers | On dev workstations / dev signing infra | Bounded: only authorizes firmware on non-deployed dev hardware |

Production bootloaders should embed only the production public key, so
that production units boot only production-signed images. Development
bootloaders embed both the production public key and the development
public key, so a dev unit can boot a production-signed image (verified
against the prod public key) without re-signing, while still allowing
engineers to flash development-signed images.

The bootloader's existing key-matching logic (`bootutil_find_key()`)
hashes the image's KEYHASH TLV against every embedded key and accepts
the first match. There is no per-key behaviour: multi-key mode is purely
about accepting more than one valid signer.

Once MCUboot is built, this new keypair file (`mykey.pem` in this
example) can be used to sign images.
Expand Down
12 changes: 12 additions & 0 deletions docs/signed_images.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ be useful when you want to prevent production units from booting
development images, but want development units to be able to boot
both production images and development images.

On Zephyr, `CONFIG_BOOT_SIGNATURE_KEY_FILE` accepts a semicolon-separated
list of PEMs. Only the public-key bytes are embedded regardless of which
form is passed in. The first entry may be a keypair PEM (needed only if
the same file is also fed to `imgtool sign`) or a public-only PEM
(preferred when signing happens elsewhere); subsequent entries must be
public-only. The intended use is to separate signing custody (a
production private key, held only by a release team) from verification
custody (the production public key, embedded in development bootloaders
so dev units can boot prod-signed images). See
[readme-zephyr.md](readme-zephyr.md) for the threat-model table and a
worked example.

For an alternative solution when the public key(s) doesn't need to be
included in the bootloader, see the [design](design.md) document.

Expand Down
3 changes: 3 additions & 0 deletions root-ed25519-2-pub.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAw7zfMHZOH120DYkuDQ6rBJwwBk55qO2293kuRpom2nc=
-----END PUBLIC KEY-----
3 changes: 3 additions & 0 deletions root-ed25519-2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICZVk44tV7KC3eJ+Qokha0aULNUVqDp9iR0cKjpqcO4D
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions root-ed25519-unknown.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIGHiQXOA1EyKdOzovW9M2d5tP/fDC0i1ByV80WJGMrqN
-----END PRIVATE KEY-----
Loading
Loading