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
36 changes: 36 additions & 0 deletions boot/zephyr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,42 @@ if(NOT CONFIG_BOOT_SIGNATURE_TYPE_NONE AND NOT CONFIG_BOOT_BUILTIN_KEY AND NOT
target_sources(app PRIVATE
${generated_pubkey}
)

if(NOT CONFIG_BOOT_SIGNATURE_KEY_FILE_2 STREQUAL "")
set(key_file_2 "${CONFIG_BOOT_SIGNATURE_KEY_FILE_2}")
string(CONFIGURE "${key_file_2}" key_file_2)

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

if(${signing_key_file_2} IN_LIST mcuboot_default_signature_files)
message(WARNING "WARNING: Using default MCUboot signing key file for the second key, this file is for debug use only and is not secure!")
endif()

set(generated_pubkey_2 ${ZEPHYR_BINARY_DIR}/autogen-pubkey2.c)
add_custom_command(
OUTPUT ${generated_pubkey_2}
COMMAND
${PYTHON_EXECUTABLE}
${MCUBOOT_DIR}/scripts/imgtool.py
getpub
-k
${signing_key_file_2}
--name-suffix _2
> ${generated_pubkey_2}
DEPENDS ${signing_key_file_2}
)
target_sources(app PRIVATE
${generated_pubkey_2}
)
target_compile_definitions(app PRIVATE MCUBOOT_SIGN_KEY_2)
endif()
endif()

if(CONFIG_BOOT_ENCRYPTION_KEY_FILE AND NOT CONFIG_BOOT_ENCRYPTION_KEY_FILE STREQUAL "")
Expand Down
21 changes: 21 additions & 0 deletions boot/zephyr/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,27 @@ config BOOT_SIGNATURE_KEY_FILE
e.g. \${CMAKE_CURRENT_LIST_DIR} will allow referencing a file in that directory, these
will be automatically configured by the build system.

config BOOT_SIGNATURE_KEY_FILE_2
string "Second PEM signing key file"
depends on !BOOT_SIGNATURE_TYPE_NONE
depends on !BOOT_HW_KEY
depends on !BOOT_BUILTIN_KEY
depends on !BOOT_BYPASS_KEY_MATCH
depends on BOOT_SIGNATURE_KEY_FILE != ""
default ""
help
Optional second signing key. When set, the bootloader accepts images
signed by either the primary signing key (BOOT_SIGNATURE_KEY_FILE) or
this secondary signing key.

Useful for development bootloaders that should boot both
production-signed and development-signed images, while production
bootloaders continue to accept only production-signed images.

Both keys must use the same BOOT_SIGNATURE_TYPE. Path resolution and
escaped-CMake-variable substitution are identical to
BOOT_SIGNATURE_KEY_FILE.

config MCUBOOT_CLEANUP_ARM_CORE
bool "Perform core cleanup before chain-load the application"
depends on CPU_CORTEX_M || ARMV7_R
Expand Down
28 changes: 27 additions & 1 deletion boot/zephyr/keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ extern unsigned int ecdsa_pub_key_len;
extern const unsigned char ed25519_pub_key[];
extern unsigned int ed25519_pub_key_len;
#endif
#if defined(MCUBOOT_SIGN_KEY_2)
#if defined(MCUBOOT_SIGN_RSA)
extern const unsigned char rsa_pub_key_2[];
extern unsigned int rsa_pub_key_2_len;
#elif defined(MCUBOOT_SIGN_EC256)
extern const unsigned char ecdsa_pub_key_2[];
extern unsigned int ecdsa_pub_key_2_len;
#elif defined(MCUBOOT_SIGN_ED25519)
extern const unsigned char ed25519_pub_key_2[];
extern unsigned int ed25519_pub_key_2_len;
#endif
#endif
#endif

/*
Expand All @@ -62,8 +74,22 @@ const struct bootutil_key bootutil_keys[] = {
.len = &ed25519_pub_key_len,
#endif
},
#if defined(MCUBOOT_SIGN_KEY_2)
{
#if defined(MCUBOOT_SIGN_RSA)
.key = rsa_pub_key_2,
.len = &rsa_pub_key_2_len,
#elif defined(MCUBOOT_SIGN_EC256)
.key = ecdsa_pub_key_2,
.len = &ecdsa_pub_key_2_len,
#elif defined(MCUBOOT_SIGN_ED25519)
.key = ed25519_pub_key_2,
.len = &ed25519_pub_key_2_len,
#endif
},
#endif
};
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_2="root-ed25519-2.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
11 changes: 11 additions & 0 deletions docs/imgtool.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ output it as a C data structure. You can replace or insert this 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 `CONFIG_BOOT_SIGNATURE_KEY_FILE_2`),
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.

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

Image signing takes an image in binary or Intel Hex format intended for the
Expand Down
12 changes: 10 additions & 2 deletions docs/readme-zephyr.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,16 @@ the public key in a format usable by the C compiler.
The generated public key is saved in `build/zephyr/autogen-pubkey.h`, which is included
by the `boot/zephyr/keys.c`.

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 up to two signing-verification keys built into the
bootloader. The primary key is always `CONFIG_BOOT_SIGNATURE_KEY_FILE`; an
optional second key can be supplied via `CONFIG_BOOT_SIGNATURE_KEY_FILE_2`,
in which case the bootloader accepts images signed by either key. This is
useful, for example, when development bootloaders must boot both
production-signed and development-signed images while production
bootloaders continue to accept only production-signed images. Both keys
must use the same `BOOT_SIGNATURE_TYPE`, and the second-key option is
mutually exclusive with `BOOT_HW_KEY`, `BOOT_BUILTIN_KEY`, and
`BOOT_BYPASS_KEY_MATCH`.

Once MCUboot is built, this new keypair file (`mykey.pem` in this
example) can be used to sign images.
Expand Down
4 changes: 4 additions & 0 deletions docs/signed_images.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ 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, use `CONFIG_BOOT_SIGNATURE_KEY_FILE` for the primary key and
`CONFIG_BOOT_SIGNATURE_KEY_FILE_2` for an optional second key; see
[readme-zephyr.md](readme-zephyr.md) for details.

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.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-----
18 changes: 9 additions & 9 deletions scripts/imgtool/keys/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,28 @@ def _emit_raw(self, encoded_bytes, file):
# raw binary data, can be for example io.BytesIO
file.write(encoded_bytes)

def emit_c_public(self, file=sys.stdout):
def emit_c_public(self, file=sys.stdout, name_suffix: str = ""):
self._emit(
header=f"const unsigned char {self.shortname()}_pub_key[] = {{"
header=f"const unsigned char {self.shortname()}_pub_key{name_suffix}[] = {{"
,
trailer="};",
encoded_bytes=self.get_public_bytes(),
indent=" ",
len_format=f"const unsigned int {self.shortname()}_pub_key_len = {{}};"
len_format=f"const unsigned int {self.shortname()}_pub_key{name_suffix}_len = {{}};"
,
file=file)

def emit_c_public_hash(self, file=sys.stdout):
def emit_c_public_hash(self, file=sys.stdout, name_suffix: str = ""):
digest = Hash(SHA256())
digest.update(self.get_public_bytes())
self._emit(
header=f"const unsigned char {self.shortname()}_pub_key_hash[] = {{"
header=f"const unsigned char {self.shortname()}_pub_key_hash{name_suffix}[] = {{"
,
trailer="};",
encoded_bytes=digest.finalize(),
indent=" ",
len_format=f"const unsigned int {self.shortname()}_pub_key_hash_len = {{}};"
,
len_format=("const unsigned int "
f"{self.shortname()}_pub_key_hash{name_suffix}_len = {{}};"),
file=file)

def emit_raw_public(self, file=sys.stdout):
Expand All @@ -91,9 +91,9 @@ def emit_raw_public_hash(self, file=sys.stdout):
digest.update(self.get_public_bytes())
self._emit_raw(digest.finalize(), file=file)

def emit_rust_public(self, file=sys.stdout):
def emit_rust_public(self, file=sys.stdout, name_suffix: str = ""):
self._emit(
header=f"static {self.shortname().upper()}_PUB_KEY: &[u8] = &["
header=f"static {self.shortname().upper()}_PUB_KEY{name_suffix.upper()}: &[u8] = &["
,
trailer="];",
encoded_bytes=self.get_public_bytes(),
Expand Down
25 changes: 20 additions & 5 deletions scripts/imgtool/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,28 +141,37 @@ def keygen(type, key, password):
@click.option('-e', '--encoding', metavar='encoding',
type=click.Choice(valid_encodings),
help='Valid encodings: {}'.format(', '.join(valid_encodings)))
@click.option('--name-suffix', 'name_suffix', metavar='SUFFIX', default='',
help='Append SUFFIX to the emitted C/Rust symbol names '
'(e.g. `--name-suffix _2` emits `rsa_pub_key_2` / '
'`rsa_pub_key_2_len`). Useful when embedding multiple '
'signing keys in the same image. Ignored for PEM/raw '
'encodings (those emit no identifiers).')
Comment on lines +148 to +149
@click.option('-k', '--key', metavar='filename', required=True)
@click.option('-o', '--output', metavar='output', required=False,
help='Specify the output file\'s name. \
The stdout is used if it is not provided.')
@click.command(help='Dump public key from keypair')
def getpub(key, encoding, lang, output):
def getpub(key, encoding, lang, output, name_suffix):
if encoding and lang:
raise click.UsageError('Please use only one of `--encoding/-e` or `--lang/-l`')
elif not encoding and not lang:
# Preserve old behavior defaulting to `c`. If `lang` is removed,
# `default=valid_encodings[0]` should be added to `-e` param.
lang = valid_langs[0]
if name_suffix and (encoding in ('pem', 'raw')):
raise click.UsageError(
'`--name-suffix` is only meaningful for lang-c / lang-rust encodings')
key = load_key(key)

if not output:
output = sys.stdout
if key is None:
print("Invalid passphrase")
elif lang == 'c' or encoding == 'lang-c':
key.emit_c_public(file=output)
key.emit_c_public(file=output, name_suffix=name_suffix)
elif lang == 'rust' or encoding == 'lang-rust':
key.emit_rust_public(file=output)
key.emit_rust_public(file=output, name_suffix=name_suffix)
elif encoding == 'pem':
key.emit_public_pem(file=output)
elif encoding == 'raw':
Expand All @@ -177,22 +186,28 @@ def getpub(key, encoding, lang, output):
'Default value is {}.'
.format(', '.join(valid_hash_encodings),
valid_hash_encodings[0]))
@click.option('--name-suffix', 'name_suffix', metavar='SUFFIX', default='',
help='Append SUFFIX to the emitted C symbol names (lang-c '
'encoding only). Ignored for raw encoding.')
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

@click.option('-k', '--key', metavar='filename', required=True)
@click.option('-o', '--output', metavar='output', required=False,
help='Specify the output file\'s name. \
The stdout is used if it is not provided.')
@click.command(help='Dump the SHA256 hash of the public key')
def getpubhash(key, output, encoding):
def getpubhash(key, output, encoding, name_suffix):
if not encoding:
encoding = valid_hash_encodings[0]
if name_suffix and encoding == 'raw':
raise click.UsageError(
'`--name-suffix` is only meaningful for the lang-c encoding')
key = load_key(key)

if not output:
output = sys.stdout
if key is None:
print("Invalid passphrase")
elif encoding == 'lang-c':
key.emit_c_public_hash(file=output)
key.emit_c_public_hash(file=output, name_suffix=name_suffix)
elif encoding == 'raw':
key.emit_raw_public_hash(file=output)
else:
Expand Down
Loading
Loading