From e51f11b0d0a17d9157519cea075eb4d9355bbce1 Mon Sep 17 00:00:00 2001 From: Dominik Ermel Date: Thu, 16 Oct 2025 18:20:31 +0000 Subject: [PATCH 1/4] bootutil: Provide support for embedded AES keys Commit provides support for MCUBOOT_EMBEDDED_ENC_KEY config option, that allows to compile code with embedded key. When this option is enabled, compilation requires definition of boot_take_enc_key function to be provided by user; prototype for the function is provided. The boot_take_enc_key function is supposed to provide encryption AES key to be used for image encryption and decryption. Signed-off-by: Dominik Ermel --- boot/boot_serial/src/boot_serial_encryption.c | 8 ++++++ boot/bootutil/include/bootutil/enc_key.h | 3 +++ boot/bootutil/src/bootutil_loader.c | 4 +++ boot/bootutil/src/bootutil_misc.c | 2 +- boot/bootutil/src/encrypted.c | 4 +++ boot/bootutil/src/loader.c | 27 +++++++++++++++---- boot/mynewt/src/single_loader.c | 1 + 7 files changed, 43 insertions(+), 6 deletions(-) diff --git a/boot/boot_serial/src/boot_serial_encryption.c b/boot/boot_serial/src/boot_serial_encryption.c index ba8ad31496..9be5d0c574 100644 --- a/boot/boot_serial/src/boot_serial_encryption.c +++ b/boot/boot_serial/src/boot_serial_encryption.c @@ -31,7 +31,11 @@ boot_image_validate_encrypted(struct boot_loader_state *state, int rc; if (MUST_DECRYPT(fa_p, BOOT_CURR_IMG(state), hdr)) { +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[BOOT_SLOT_SECONDARY], BOOT_CUR_IMG(state), BOOT_SLOT_SECONDARY); +#else rc = boot_enc_load(state, BOOT_SLOT_SECONDARY, hdr, fa_p, bs); +#endif if (rc < 0) { FIH_RET(fih_rc); } @@ -232,7 +236,11 @@ decrypt_image_inplace(const struct flash_area *fa_p, } #endif /* Load the encryption keys into cache */ +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[BOOT_SLOT_PRIMARY], BOOT_CURR_IMG(state), BOOT_SLOT_PRIMARY); +#else rc = boot_enc_load(state, BOOT_SLOT_PRIMARY, hdr, fa_p, bs); +#endif if (rc < 0) { goto total_out; } diff --git a/boot/bootutil/include/bootutil/enc_key.h b/boot/bootutil/include/bootutil/enc_key.h index 6fa0db18e4..461d4c00af 100644 --- a/boot/bootutil/include/bootutil/enc_key.h +++ b/boot/bootutil/include/bootutil/enc_key.h @@ -75,6 +75,9 @@ void boot_enc_decrypt(struct enc_key_data *enc_state, /* Note that boot_enc_zeorize takes BOOT_CURR_ENC, not BOOT_CURR_ENC_SLOT */ void boot_enc_zeroize(struct enc_key_data *enc_state); +/* Retrieve key for a slot */ +int boot_take_enc_key(uint8_t *key, int image, int slot); + #ifdef __cplusplus } #endif diff --git a/boot/bootutil/src/bootutil_loader.c b/boot/bootutil/src/bootutil_loader.c index d3a3ffc855..eca87ef13c 100644 --- a/boot/bootutil/src/bootutil_loader.c +++ b/boot/bootutil/src/bootutil_loader.c @@ -179,7 +179,11 @@ boot_check_image(struct boot_loader_state *state, struct boot_status *bs, int sl */ #if defined(MCUBOOT_ENC_IMAGES) && !defined(MCUBOOT_RAM_LOAD) if (MUST_DECRYPT(fap, BOOT_CURR_IMG(state), hdr)) { +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[BOOT_SLOT_SECONDARY], BOOT_CURR_IMG(state), BOOT_SLOT_SECONDARY); +#else rc = boot_enc_load(state, BOOT_SLOT_SECONDARY, hdr, fap, bs); +#endif if (rc < 0) { FIH_RET(fih_rc); } diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c index 360ea71c0c..f1d0513783 100644 --- a/boot/bootutil/src/bootutil_misc.c +++ b/boot/bootutil/src/bootutil_misc.c @@ -239,7 +239,7 @@ boot_read_unprotected_tlv_sizes(const struct flash_area *fap, uint16_t *tlv_size } #endif -#ifdef MCUBOOT_ENC_IMAGES +#if defined(MCUBOOT_ENC_IMAGES) && !defined(MCUBOOT_EMBEDDED_ENC_KEY) int boot_read_enc_key(const struct flash_area *fap, uint8_t slot, struct boot_status *bs) { diff --git a/boot/bootutil/src/encrypted.c b/boot/bootutil/src/encrypted.c index 9f86bb457b..049bf316af 100644 --- a/boot/bootutil/src/encrypted.c +++ b/boot/bootutil/src/encrypted.c @@ -370,6 +370,7 @@ static int fake_rng(void *p_rng, unsigned char *output, size_t len) #endif /* (MCUBOOT_ENCRYPT_RSA && MCUBOOT_USE_MBED_TLS && !MCUBOOT_USE_PSA_CRYPTO) || (MCUBOOT_ENCRYPT_EC256 && MCUBOOT_USE_MBED_TLS) */ +#if !defined(MCUBOOT_EMBEDDED_ENC_KEY) /* * Decrypt an encryption key TLV. * @@ -564,7 +565,9 @@ boot_decrypt_key(const uint8_t *buf, uint8_t *enckey) return rc; } #endif /* CONFIG_BOOT_ED25519_PSA && CONFIG_BOOT_ECDSA_PSA */ +#endif /* defined(MCUBOOT_EMBEDDED_ENC_KEY) */ +#if !defined(MCUBOOT_EMBEDDED_ENC_KEY) /* * Load encryption key. */ @@ -625,6 +628,7 @@ boot_enc_load(struct boot_loader_state *state, int slot, return boot_decrypt_key(buf, bs->enckey[slot]); } +#endif /* defined(MCUBOOT_EMBEDDED_ENC_KEY */ int boot_enc_init(struct enc_key_data *enc_state) diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c index f3eb66297a..228bc4f11b 100644 --- a/boot/bootutil/src/loader.c +++ b/boot/bootutil/src/loader.c @@ -632,6 +632,7 @@ boot_validate_slot(struct boot_loader_state *state, int slot, } #endif if (!boot_check_header_valid(state, slot)) { + BOOT_LOG_DBG("boot_validate_slot: header validation failed %d", slot); fih_rc = FIH_FAILURE; } else { BOOT_HOOK_CALL_FIH(boot_image_check_hook, FIH_BOOT_HOOK_REGULAR, @@ -644,16 +645,16 @@ boot_validate_slot(struct boot_loader_state *state, int slot, check_validity: #endif if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { +#if !defined(__BOOTSIM__) + BOOT_LOG_ERR("Image in the %s slot is not valid!", + (slot == BOOT_SLOT_PRIMARY) ? "primary" : "secondary"); +#endif if ((slot != BOOT_SLOT_PRIMARY) || ARE_SLOTS_EQUIVALENT()) { boot_scramble_slot(fap, slot); /* Image is invalid, erase it to prevent further unnecessary * attempts to validate and boot it. */ } -#if !defined(__BOOTSIM__) - BOOT_LOG_ERR("Image in the %s slot is not valid!", - (slot == BOOT_SLOT_PRIMARY) ? "primary" : "secondary"); -#endif fih_rc = FIH_NO_BOOTABLE_IMAGE; goto out; } @@ -1006,9 +1007,13 @@ boot_copy_image(struct boot_loader_state *state, struct boot_status *bs) #ifdef MCUBOOT_ENC_IMAGES if (IS_ENCRYPTED(boot_img_hdr(state, BOOT_SLOT_SECONDARY))) { +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[BOOT_SLOT_SECONDARY], BOOT_CURR_IMG(state), BOOT_SLOT_SECONDARY); +#else rc = boot_enc_load(state, BOOT_SLOT_SECONDARY, boot_img_hdr(state, BOOT_SLOT_SECONDARY), fap_secondary_slot, bs); +#endif /* MCUBOOT_EMBEDDED_ENC_KEY */ if (rc < 0) { return BOOT_EBADIMAGE; @@ -1130,7 +1135,11 @@ boot_swap_image(struct boot_loader_state *state, struct boot_status *bs) #ifdef MCUBOOT_ENC_IMAGES if (IS_ENCRYPTED(hdr)) { fap = BOOT_IMG_AREA(state, BOOT_SLOT_PRIMARY); +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[BOOT_SLOT_PRIMARY], BOOT_CURR_IMG(state), BOOT_SLOT_PRIMARY); +#else rc = boot_enc_load(state, BOOT_SLOT_PRIMARY, hdr, fap, bs); +#endif /* MCUBOOT_EMBEDDED_ENC_KEY */ assert(rc >= 0); if (rc == 0) { @@ -1154,7 +1163,11 @@ boot_swap_image(struct boot_loader_state *state, struct boot_status *bs) hdr = boot_img_hdr(state, BOOT_SLOT_SECONDARY); if (IS_ENCRYPTED(hdr)) { fap = BOOT_IMG_AREA(state, BOOT_SLOT_SECONDARY); +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[BOOT_SLOT_SECONDARY], BOOT_CURR_IMG(state), BOOT_SLOT_SECONDARY); +#else rc = boot_enc_load(state, BOOT_SLOT_SECONDARY, hdr, fap, bs); +#endif /* MCUBOOT_EMBEDDED_ENC_KEY */ assert(rc >= 0); if (rc == 0) { @@ -1191,7 +1204,11 @@ boot_swap_image(struct boot_loader_state *state, struct boot_status *bs) boot_enc_init(BOOT_CURR_ENC_SLOT(state, slot)); +#ifdef MCUBOOT_EMBEDDED_ENC_KEY + rc = boot_take_enc_key(bs->enckey[slot], image_index, slot); +#else rc = boot_read_enc_key(fap, slot, bs); +#endif /* MCUBOOT_EMBEDDED_ENC_KEY */ if (rc) { BOOT_LOG_DBG("boot_swap_image: Failed loading key (%d, %d)", image_index, slot); @@ -1199,7 +1216,7 @@ boot_swap_image(struct boot_loader_state *state, struct boot_status *bs) boot_enc_set_key(BOOT_CURR_ENC_SLOT(state, slot), bs->enckey[slot]); } } -#endif +#endif /* MCUBOOT_ENC_IMAGES */ flash_area_close(fap); } diff --git a/boot/mynewt/src/single_loader.c b/boot/mynewt/src/single_loader.c index 394fc372ac..935d04881c 100644 --- a/boot/mynewt/src/single_loader.c +++ b/boot/mynewt/src/single_loader.c @@ -49,6 +49,7 @@ boot_image_validate(const struct flash_area *fa_p, * was performed. We will try to validate the image, and if still * encrypted the validation will fail, and go in panic mode */ + BOOT_LOG_DBG("boot_image_validate: clearing encryption flags"); hdr->ih_flags &= ~(ENCRYPTIONFLAGS); } FIH_CALL(bootutil_img_validate, fih_rc, NULL, hdr, fa_p, tmpbuf, From 483601b0833f9d7680a0078924723c56a6048455 Mon Sep 17 00:00:00 2001 From: Dominik Ermel Date: Tue, 21 Oct 2025 18:07:46 +0000 Subject: [PATCH 2/4] zephyr: Add support for embedded AES key The commit provides Kconfig options that allow to configure MCUboot to use embedded AES key. Primary option is CONFIG_BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY that allows to select usage of embedded key in the code. After it follow sets of Kconfigs: - CONFIG_BOOT_ENCRYPT_IMAGE_GENERATE_BASIC_KEY_PROVIDER - CONFIG_BOOT_ENCRYPT_IMAGE_USE_CUSTOM_KEY_PROVIDER The above set allows to select source of the key. The first option will choose to generate default key provider, with a single embedded key, where the key is provided as a string assigned to CONFIG_BOOOT_ENCRYPT_IMAGE_EMBEDDED_RAW_KEY. The second option selects user provided code as source of key(s). Signed-off-by: Dominik Ermel --- boot/zephyr/CMakeLists.txt | 109 ++++++++++-------- boot/zephyr/Kconfig | 65 ++++++++++- .../include/mcuboot_config/mcuboot_config.h | 5 + ...single_builtin_aes_key_provider.c.template | 26 +++++ 4 files changed, 154 insertions(+), 51 deletions(-) create mode 100644 boot/zephyr/templates/single_builtin_aes_key_provider.c.template diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt index 1813ea3217..295decea85 100644 --- a/boot/zephyr/CMakeLists.txt +++ b/boot/zephyr/CMakeLists.txt @@ -393,57 +393,59 @@ if(NOT CONFIG_BOOT_SIGNATURE_KEY_FILE STREQUAL "") endif() if(CONFIG_BOOT_ENCRYPTION_KEY_FILE AND NOT CONFIG_BOOT_ENCRYPTION_KEY_FILE STREQUAL "") - # CONF_FILE points to the KConfig configuration files of the bootloader. - unset(CONF_DIR) - foreach(filepath ${CONF_FILE}) - file(READ ${filepath} temp_text) - string(FIND "${temp_text}" ${CONFIG_BOOT_ENCRYPTION_KEY_FILE} match) - if(${match} GREATER_EQUAL 0) - if(NOT DEFINED CONF_DIR) - get_filename_component(CONF_DIR ${filepath} DIRECTORY) - else() - message(FATAL_ERROR "Encryption key file defined in multiple conf files") + if(CONFIG_BOOT_ENCRYPT_IMAGE_WITH_SHARED_KEY) + # CONF_FILE points to the KConfig configuration files of the bootloader. + unset(CONF_DIR) + foreach(filepath ${CONF_FILE}) + file(READ ${filepath} temp_text) + string(FIND "${temp_text}" ${CONFIG_BOOT_ENCRYPTION_KEY_FILE} match) + if(${match} GREATER_EQUAL 0) + if(NOT DEFINED CONF_DIR) + get_filename_component(CONF_DIR ${filepath} DIRECTORY) + else() + message(FATAL_ERROR "Encryption key file defined in multiple conf files") + endif() endif() - endif() - endforeach() + endforeach() - if(IS_ABSOLUTE ${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) - set(KEY_FILE ${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) - elseif((DEFINED CONF_DIR) AND - (EXISTS ${CONF_DIR}/${CONFIG_BOOT_ENCRYPTION_KEY_FILE})) - set(KEY_FILE ${CONF_DIR}/${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) - else() - set(KEY_FILE ${MCUBOOT_DIR}/${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) - endif() - message("MCUBoot bootloader encryption key file: ${KEY_FILE}") + if(IS_ABSOLUTE ${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) + set(KEY_FILE ${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) + elseif((DEFINED CONF_DIR) AND + (EXISTS ${CONF_DIR}/${CONFIG_BOOT_ENCRYPTION_KEY_FILE})) + set(KEY_FILE ${CONF_DIR}/${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) + else() + set(KEY_FILE ${MCUBOOT_DIR}/${CONFIG_BOOT_ENCRYPTION_KEY_FILE}) + endif() + message("MCUBoot bootloader encryption key file: ${KEY_FILE}") + + # Emit a warning if using one of the default MCUboot key files + set(mcuboot_default_encryption_files + ${MCUBOOT_DIR}/enc-ec256-priv.pem + ${MCUBOOT_DIR}/enc-ec256-pub.pem + ${MCUBOOT_DIR}/enc-rsa2048-priv.pem + ${MCUBOOT_DIR}/enc-rsa2048-pub.pem + ${MCUBOOT_DIR}/enc-x25519-priv.pem + ${MCUBOOT_DIR}/enc-x25519-pub.pem + ) - # Emit a warning if using one of the default MCUboot key files - set(mcuboot_default_encryption_files - ${MCUBOOT_DIR}/enc-ec256-priv.pem - ${MCUBOOT_DIR}/enc-ec256-pub.pem - ${MCUBOOT_DIR}/enc-rsa2048-priv.pem - ${MCUBOOT_DIR}/enc-rsa2048-pub.pem - ${MCUBOOT_DIR}/enc-x25519-priv.pem - ${MCUBOOT_DIR}/enc-x25519-pub.pem - ) + if(${KEY_FILE} IN_LIST mcuboot_default_encryption_files) + message(WARNING "WARNING: Using default MCUboot encryption key file, this file is for debug use only and is not secure!") + endif() - if(${KEY_FILE} IN_LIST mcuboot_default_encryption_files) - message(WARNING "WARNING: Using default MCUboot encryption key file, this file is for debug use only and is not secure!") + set(GENERATED_ENCKEY ${ZEPHYR_BINARY_DIR}/autogen-enckey.c) + add_custom_command( + OUTPUT ${GENERATED_ENCKEY} + COMMAND + ${PYTHON_EXECUTABLE} + ${MCUBOOT_DIR}/scripts/imgtool.py + getpriv + -k + ${KEY_FILE} + > ${GENERATED_ENCKEY} + DEPENDS ${KEY_FILE} + ) + zephyr_library_sources(${GENERATED_ENCKEY}) endif() - - set(GENERATED_ENCKEY ${ZEPHYR_BINARY_DIR}/autogen-enckey.c) - add_custom_command( - OUTPUT ${GENERATED_ENCKEY} - COMMAND - ${PYTHON_EXECUTABLE} - ${MCUBOOT_DIR}/scripts/imgtool.py - getpriv - -k - ${KEY_FILE} - > ${GENERATED_ENCKEY} - DEPENDS ${KEY_FILE} - ) - zephyr_library_sources(${GENERATED_ENCKEY}) endif() if(CONFIG_MCUBOOT_CLEANUP_ARM_CORE) @@ -731,3 +733,18 @@ if(SYSBUILD) set(mcuboot_image_footer_size ${required_size} CACHE INTERNAL "Estimated MCUboot image trailer size" FORCE) set(mcuboot_image_upgrade_footer_size ${required_upgrade_size} CACHE INTERNAL "Estimated MCUboot update image trailer size" FORCE) endif() + +if(${CONFIG_BOOT_ENCRYPT_IMAGE_GENERATE_BASIC_KEY_PROVIDER}) + # Need to generate single key provider source, from template. + # Take provided key, in form of a string and make it into C array, BOOT_AES_RAW_KEY_HEX_ARRAY, + # of byte size hex values. + set(BOOT_AES_RAW_KEY_HEX_STRING ${BOOT_ENCRYPT_IMAGE_EMBEDDED_RAW_KEY}) + string(REGEX REPLACE "(..)" "0x\\1, " BOOT_AES_RAW_KEY_HEX_ARRAY "${BOOT_AES_RAW_KEY_HEX_STRING}") + + # The tamplate references BOOT_AES_RAW_KEY_HEX_ARRAY where it expects the array to be substituted. + set(OUTPUT_BOOT_AES_RAW_KEY_SRC ${ZEPHYR_BINARY_DIR}/mcuboot_generated/builtin_aes_key_provider.c) + configure_file(templates/single_builtin_aes_key_provider.c.template ${OUTPUT_BOOT_AES_RAW_KEY_SRC} @ONLY) + + # Add generated source file to build + zephyr_library_sources(${OUTPUT_BOOT_AES_RAW_KEY_SRC}) +endif() diff --git a/boot/zephyr/Kconfig b/boot/zephyr/Kconfig index bb2c87ac2c..9ee501dd42 100644 --- a/boot/zephyr/Kconfig +++ b/boot/zephyr/Kconfig @@ -94,6 +94,8 @@ config BOOT_ED25519_PSA_DEPENDENCIES if BOOT_ENCRYPT_IMAGE +if !BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + config BOOT_X25519_PSA_DEPENDENCIES bool select PSA_WANT_ALG_ECDH @@ -111,6 +113,8 @@ config BOOT_X25519_PSA_DEPENDENCIES to use with it; the others are used for shared key decryption and derivation. +endif # !BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + endif # BOOT_ENCRYPT_IMAGE config BOOT_ECDSA_PSA_DEPENDENCIES @@ -359,7 +363,7 @@ config BOOT_ED25519_PSA select BOOT_IMG_HASH_ALG_SHA256_ALLOW select BOOT_IMG_HASH_ALG_SHA512_ALLOW select BOOT_ED25519_PSA_DEPENDENCIES - select BOOT_X25519_PSA_DEPENDENCIES if BOOT_ENCRYPT_IMAGE + select BOOT_X25519_PSA_DEPENDENCIES if BOOT_ENCRYPT_IMAGE_WITH_SHARED_KEY endchoice @@ -595,7 +599,8 @@ config BOOT_BOOTSTRAP config BOOT_SWAP_SAVE_ENCTLV bool "Save encrypted key TLVs instead of plaintext keys in swap metadata" - depends on BOOT_ENCRYPT_IMAGE + depends on BOOT_ENCRYPT_IMAGE_WITH_SHARED_KEY + depends on !BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY help If y, instead of saving the encrypted image keys in plaintext in the swap resume metadata, save the encrypted image TLVs. This should be used @@ -663,12 +668,62 @@ config BOOT_ENCRYPTION_SUPPORT help Hidden option used to check if image encryption is supported. -config BOOT_ENCRYPT_IMAGE - bool "Support for encrypted image updates" - depends on BOOT_ENCRYPTION_SUPPORT +config BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + bool "Use key that is already on board with MCUboot" + depends on BOOT_ENCRYPT_IMAGE + help + The key is supposed to be either compiled in or on board. + User is responsible for providing boot_enc_take_key + function that will be able to retrieve the key. + +if BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + +choice BOOT_ENCRYPT_IMAGE_EMBEDDED_KEY_PROVIDER + prompt "Embedded AES key provider" + default BOOT_ENCRYPT_IMAGE_GENERATE_BASIC_KEY_PROVIDER + +config BOOT_ENCRYPT_IMAGE_GENERATE_BASIC_KEY_PROVIDER + bool "Generate basic boot_enc_take_key" + depends on BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + help + Basic implementation of boot_enc_take_key will be implemented, + that will have single key built in, used for all images and + slots. + +config BOOT_ENCRYPT_IMAGE_USE_CUSTOM_KEY_PROVIDER + bool "User provides source code for key provider" + help + User is required to provide implementation for + the boot_enc_take_key function. + +endchoice # BOOT_ENCRYPT_IMAGE_EMBEDDED_KEY_PROVIDER + +config BOOT_ENCRYPT_IMAGE_EMBEDDED_RAW_KEY + string "Hexadecimal string representing AES key" + depends on BOOT_ENCRYPT_IMAGE_GENERATE_BASIC_KEY_PROVIDER + help + AES key in form of hexadecimal string that will be used to + generate boot_enc_take_key function, returning the key for + decryption and encryption of image. + The key character length should be the double of expected + AES key length in bytes. + +endif # BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + +config BOOT_ENCRYPT_IMAGE_WITH_SHARED_KEY + bool + default y if !BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY + depends on BOOT_ENCRYPT_IMAGE select BOOT_ENCRYPT_RSA if BOOT_SIGNATURE_TYPE_RSA select BOOT_ENCRYPT_EC256 if BOOT_SIGNATURE_TYPE_ECDSA_P256 select BOOT_ENCRYPT_X25519 if BOOT_SIGNATURE_TYPE_ED25519 + help + Hidden option for default behaviour where AES encryption key + is derived from Public Key Cryptography key exchange. + +config BOOT_ENCRYPT_IMAGE + bool "Support for encrypted image updates" + depends on BOOT_ENCRYPTION_SUPPORT depends on !SINGLE_APPLICATION_SLOT || MCUBOOT_SERIAL help If y, images in the secondary slot can be encrypted and are decrypted diff --git a/boot/zephyr/include/mcuboot_config/mcuboot_config.h b/boot/zephyr/include/mcuboot_config/mcuboot_config.h index 5dec8a2577..514ab24cf5 100644 --- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h +++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h @@ -151,6 +151,11 @@ #define MCUBOOT_USE_TLV_ALLOW_LIST 1 #endif +#ifdef CONFIG_BOOT_ENCRYPT_IMAGE_WITH_EMBEDDED_KEY +#define MCUBOOT_ENC_IMAGES +#define MCUBOOT_EMBEDDED_ENC_KEY +#endif + #ifdef CONFIG_BOOT_ENCRYPT_RSA #define MCUBOOT_ENC_IMAGES #define MCUBOOT_ENCRYPT_RSA diff --git a/boot/zephyr/templates/single_builtin_aes_key_provider.c.template b/boot/zephyr/templates/single_builtin_aes_key_provider.c.template new file mode 100644 index 0000000000..ac3f9a78d3 --- /dev/null +++ b/boot/zephyr/templates/single_builtin_aes_key_provider.c.template @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2025 Nordic Semiconductor ASA + * + */ + +#include +#include +#include +#include +#include + +#include "mcuboot_config/mcuboot_config.h" +#include "bootutil/enc_key.h" + +int boot_take_enc_key(uint8_t *key, int image, int slot) +{ + const unsigned char array[] = { + @BOOT_AES_RAW_KEY_HEX_ARRAY@ + }; + + memcpy(key, array, sizeof(array)); + + return 0; +} From f6076d38e4a1d8eccf2b4f33a459d3a8d4a159a9 Mon Sep 17 00:00:00 2001 From: Dominik Ermel Date: Thu, 16 Oct 2025 16:20:35 +0000 Subject: [PATCH 3/4] imgtool: Add support for encrypting image with raw AES key The change adds --aes-key option that allows to pass a key via command line. The key is used to encrypt the image and there is not key exchange TLV added to the image. The options is provided for encrypting images for devices that store AES key on them so they do not expect it to be passed with image, in encrypted form. Signed-off-by: Dominik Ermel --- scripts/imgtool/image.py | 33 +++++++++------------- scripts/imgtool/main.py | 61 ++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index 803aff69e0..392a9fac53 100755 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -513,11 +513,12 @@ def ecies_hkdf(self, enckey, plainkey, hmac_sha_alg): def create(self, key, public_key_format, enckey, dependencies=None, sw_type=None, custom_tlvs=None, compression_tlvs=None, - compression_type=None, encrypt_keylen=128, clear=False, + compression_type=None, aes_raw=None, clear=False, fixed_sig=None, pub_key=None, vector_to_sign=None, user_sha='auto', hmac_sha='auto', is_pure=False, keep_comp_size=False, dont_encrypt=False): self.enckey = enckey + encrypt_keylen = len(aes_raw) * 8 if aes_raw else 0 # key decides on sha, then pub_key; of both are none default is used check_key = key if key is not None else pub_key @@ -620,10 +621,7 @@ def create(self, key, public_key_format, enckey, dependencies=None, if compression_type == "lzma2armthumb": compression_flags |= IMAGE_F['COMPRESSED_ARM_THUMB'] # This adds the header to the payload as well - if encrypt_keylen == 256: - self.add_header(enckey, protected_tlv_size, compression_flags, 256) - else: - self.add_header(enckey, protected_tlv_size, compression_flags) + self.add_header(enckey, protected_tlv_size, compression_flags, encrypt_keylen) prot_tlv = TLV(self.endian, TLV_PROT_INFO_MAGIC) @@ -743,11 +741,6 @@ def create(self, key, public_key_format, enckey, dependencies=None, self.payload = self.payload[:protected_tlv_off] if enckey is not None and dont_encrypt is False: - if encrypt_keylen == 256: - plainkey = os.urandom(32) - else: - plainkey = os.urandom(16) - if not isinstance(enckey, rsa.RSAPublic): if hmac_sha == 'auto' or hmac_sha == '256': hmac_sha = '256' @@ -762,19 +755,19 @@ def create(self, key, public_key_format, enckey, dependencies=None, if isinstance(enckey, rsa.RSAPublic): cipherkey = enckey._get_public().encrypt( - plainkey, padding.OAEP( + aes_raw, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)) self.enctlv_len = len(cipherkey) tlv.add('ENCRSA2048', cipherkey) elif isinstance(enckey, ecdsa.ECDSA256P1Public): - cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg) + cipherkey, mac, pubk = self.ecies_hkdf(enckey, aes_raw, hmac_sha_alg) enctlv = pubk + mac + cipherkey self.enctlv_len = len(enctlv) tlv.add('ENCEC256', enctlv) elif isinstance(enckey, x25519.X25519Public): - cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey, hmac_sha_alg) + cipherkey, mac, pubk = self.ecies_hkdf(enckey, aes_raw, hmac_sha_alg) enctlv = pubk + mac + cipherkey self.enctlv_len = len(enctlv) if (hmac_sha == '256'): @@ -784,7 +777,7 @@ def create(self, key, public_key_format, enckey, dependencies=None, if not clear: nonce = bytes([0] * 16) - cipher = Cipher(algorithms.AES(plainkey), modes.CTR(nonce), + cipher = Cipher(algorithms.AES(aes_raw), modes.CTR(nonce), backend=default_backend()) encryptor = cipher.encryptor() img = bytes(self.payload[self.header_size:]) @@ -805,15 +798,15 @@ def get_signature(self): def get_infile_data(self): return self.infile_data - def add_header(self, enckey, protected_tlv_size, compression_flags, aes_length=128): + def add_header(self, enckey, protected_tlv_size, compression_flags, aes_length=0): """Install the image header.""" flags = 0 - if enckey is not None: - if aes_length == 128: - flags |= IMAGE_F['ENCRYPTED_AES128'] - else: - flags |= IMAGE_F['ENCRYPTED_AES256'] + if aes_length == 128: + flags |= IMAGE_F['ENCRYPTED_AES128'] + elif aes_length == 256: + flags |= IMAGE_F['ENCRYPTED_AES256'] + if self.load_addr != 0: # Indicates that this image should be loaded into RAM # instead of run directly from flash. diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index 3074234e2f..41be093702 100755 --- a/scripts/imgtool/main.py +++ b/scripts/imgtool/main.py @@ -20,6 +20,7 @@ import base64 import getpass import lzma +import os import re import struct import sys @@ -322,6 +323,14 @@ def create_lzma2_header(dictsize, pb, lc, lp): header.append( ( pb * 5 + lp) * 9 + lc) return header +def match_sig_enc_key(skey, ekey): + ok = ((isinstance(skey, keys.ECDSA256P1) and isinstance(ekey, keys.ECDSA256P1Public)) or + (isinstance(skey, keys.ECDSA384P1) and isinstance(ekey, keys.ECDSA384P1Public)) or + (isinstance(skey, keys.RSA) and isinstance(ekey, keys.RSAPublic)) + ) + + return ok + class BasedIntParamType(click.ParamType): name = 'integer' @@ -450,13 +459,17 @@ def convert(self, value, param, ctx): help='Unique vendor identifier, format: (|') @click.option('--cid', default=None, required=False, help='Unique image class identifier, format: (|)') -def sign(key, public_key_format, align, version, pad_sig, header_size, +@click.option('--aes-key', default=None, required=False, + help='String representing raw AES key, format: hex byte string of 32 or 64' + 'hexadecimal characters') +@click.pass_context +def sign(ctx, key, public_key_format, align, version, pad_sig, header_size, pad_header, slot_size, pad, confirm, test, max_sectors, overwrite_only, endian, encrypt_keylen, encrypt, compression, infile, outfile, dependencies, load_addr, hex_addr, erased_val, save_enctlv, security_counter, boot_record, custom_tlv, rom_fixed, max_align, clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, hmac_sha, is_pure, - vector_to_sign, non_bootable, vid, cid): + vector_to_sign, non_bootable, vid, cid, aes_key): if confirm or test: # Confirmed but non-padded images don't make much sense, because @@ -473,16 +486,23 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, compression_tlvs = {} img.load(infile) key = load_key(key) if key else None - enckey = load_key(encrypt) if encrypt else None - if enckey and key and ((isinstance(key, keys.ECDSA256P1) and - not isinstance(enckey, keys.ECDSA256P1Public)) - or (isinstance(key, keys.ECDSA384P1) and - not isinstance(enckey, keys.ECDSA384P1Public)) - or (isinstance(key, keys.RSA) and - not isinstance(enckey, keys.RSAPublic))): - # FIXME - raise click.UsageError("Signing and encryption must use the same " - "type of key") + enckey = None + if not aes_key: + enckey = load_key(encrypt) if encrypt else None + if enckey and not match_sig_enc_key(key, enckey): + # FIXME + raise click.UsageError("Signing and encryption must use the same " + "type of key") + else: + if encrypt: + encrypt = None + print('Raw AES key overrides --key, there will be no encrypted key added to the image') + if clear: + clear = False + print('Raw AES key overrides --clear, image will be encrypted') + if ctx.get_parameter_source('encrypt_keylen') != click.core.ParameterSource.DEFAULT: + print('Raw AES key len overrides --encrypt-keylen') + if pad_sig and hasattr(key, 'pad_sig'): key.pad_sig = True @@ -527,9 +547,20 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, 'Pure signatures, currently, enforces preferred hash algorithm, ' 'and forbids sha selection by user.') + aes_raw_key = None + if aes_key: + # Converting the command line provided raw AES key to byte array. + aes_raw_key = bytes.fromhex(aes_key) + aes_raw_key_len = len(aes_raw_key) + if aes_raw_key_len not in (16, 32): + raise click.UsageError("Provided keylen, {int(aes_raw_key_len)} in bytes, " + "not supported") + elif enckey: + aes_raw_key = os.urandom(int(int(encrypt_keylen) / 8)) + if compression in ["lzma2", "lzma2armthumb"]: img.create(key, public_key_format, enckey, dependencies, boot_record, - custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, + custom_tlvs, compression_tlvs, None, aes_raw_key, clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=False, dont_encrypt=True) compressed_img = image.Image(version=decode_version(version), @@ -575,13 +606,13 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, keep_comp_size = True compressed_img.create(key, public_key_format, enckey, dependencies, boot_record, custom_tlvs, compression_tlvs, - compression, int(encrypt_keylen), clear, baked_signature, + compression, aes_raw_key, clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=keep_comp_size) img = compressed_img else: img.create(key, public_key_format, enckey, dependencies, boot_record, - custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, + custom_tlvs, compression_tlvs, None, aes_raw_key, clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha, is_pure=is_pure) img.save(outfile, hex_addr) From 264ec55a1e4612f91361ec6bd0677d86da99c54f Mon Sep 17 00:00:00 2001 From: Dominik Ermel Date: Tue, 4 Nov 2025 14:52:21 +0000 Subject: [PATCH 4/4] imgtool: Temporary workaround for entanglement with TF-M. Once TF-M stops using internal imgtool APIs this commit should be reverted. Signed-off-by: Dominik Ermel --- scripts/imgtool/image.py | 28 ++++++++++++++++++++++++++++ scripts/imgtool/main.py | 6 +++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index 392a9fac53..10aaf59cca 100755 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -512,6 +512,34 @@ def ecies_hkdf(self, enckey, plainkey, hmac_sha_alg): return cipherkey, ciphermac, pubk def create(self, key, public_key_format, enckey, dependencies=None, + sw_type=None, custom_tlvs=None, compression_tlvs=None, + compression_type=None, encrypt_keylen=128, clear=False, + fixed_sig=None, pub_key=None, vector_to_sign=None, + user_sha='auto', hmac_sha='auto', is_pure=False, keep_comp_size=False, + dont_encrypt=False): + + # This is old logic of image creation where lack of enckey indicated + # lack of encryption. + # New create requires a key to be provided from outside. + if enckey: + if encrypt_keylen == 256: + encrypt_keylen_bytes = 32 + else: + encrypt_keylen_bytes = 16 + + # No AES plain key and there is request to encrypt, generate random AES key + raw_key = os.urandom(encrypt_keylen_bytes) + else: + raw_key = None + + self.create2(key, public_key_format, enckey, dependencies, + sw_type, custom_tlvs, compression_tlvs, + compression_type, raw_key, clear, + fixed_sig, pub_key, vector_to_sign, + user_sha, hmac_sha, is_pure, keep_comp_size, + dont_encrypt) + + def create2(self, key, public_key_format, enckey, dependencies=None, sw_type=None, custom_tlvs=None, compression_tlvs=None, compression_type=None, aes_raw=None, clear=False, fixed_sig=None, pub_key=None, vector_to_sign=None, diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index 41be093702..a2fd8c730a 100755 --- a/scripts/imgtool/main.py +++ b/scripts/imgtool/main.py @@ -559,7 +559,7 @@ def sign(ctx, key, public_key_format, align, version, pad_sig, header_size, aes_raw_key = os.urandom(int(int(encrypt_keylen) / 8)) if compression in ["lzma2", "lzma2armthumb"]: - img.create(key, public_key_format, enckey, dependencies, boot_record, + img.create2(key, public_key_format, enckey, dependencies, boot_record, custom_tlvs, compression_tlvs, None, aes_raw_key, clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=False, dont_encrypt=True) @@ -604,14 +604,14 @@ def sign(ctx, key, public_key_format, align, version, pad_sig, header_size, keep_comp_size = False if enckey: keep_comp_size = True - compressed_img.create(key, public_key_format, enckey, + compressed_img.create2(key, public_key_format, enckey, dependencies, boot_record, custom_tlvs, compression_tlvs, compression, aes_raw_key, clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha, is_pure=is_pure, keep_comp_size=keep_comp_size) img = compressed_img else: - img.create(key, public_key_format, enckey, dependencies, boot_record, + img.create2(key, public_key_format, enckey, dependencies, boot_record, custom_tlvs, compression_tlvs, None, aes_raw_key, clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, hmac_sha=hmac_sha, is_pure=is_pure)