diff --git a/boot/boot_serial/src/boot_serial_encryption.c b/boot/boot_serial/src/boot_serial_encryption.c index ba8ad31496..2cf22d93fe 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_en_take_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..9decaa4036 100644 --- a/boot/bootutil/src/loader.c +++ b/boot/bootutil/src/loader.c @@ -1006,9 +1006,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 +1134,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 +1162,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 +1203,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 +1215,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, diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt index 2053008559..3d95305e7d 100644 --- a/boot/zephyr/CMakeLists.txt +++ b/boot/zephyr/CMakeLists.txt @@ -383,45 +383,48 @@ if(NOT CONFIG_BOOT_SIGNATURE_TYPE_NONE AND NOT CONFIG_BOOT_SIGNATURE_KEY_FILE ST endif() if(CONFIG_BOOT_ENCRYPTION_KEY_FILE AND NOT CONFIG_BOOT_ENCRYPTION_KEY_FILE STREQUAL "") - set(key_file "${CONFIG_BOOT_ENCRYPTION_KEY_FILE}") - string(CONFIGURE "${key_file}" key_file) + if(CONFIG_BOOT_ENCRYPT_IMAGE_WITH_SHARED_KEY) + set(key_file "${CONFIG_BOOT_ENCRYPTION_KEY_FILE}") + string(CONFIGURE "${key_file}" key_file) + + if(IS_ABSOLUTE ${key_file}) + set(encryption_key_file ${key_file}) + elseif(EXISTS ${APPLICATION_CONFIG_DIR}/${key_file}) + set(encryption_key_file ${APPLICATION_CONFIG_DIR}/${key_file}) + else() + set(encryption_key_file ${MCUBOOT_DIR}/${key_file}) + endif() + message("MCUBoot bootloader encryption key file: ${encryption_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 + ) - if(IS_ABSOLUTE ${key_file}) - set(encryption_key_file ${key_file}) - elseif(EXISTS ${APPLICATION_CONFIG_DIR}/${key_file}) - set(encryption_key_file ${APPLICATION_CONFIG_DIR}/${key_file}) - else() - set(encryption_key_file ${MCUBOOT_DIR}/${key_file}) - endif() - message("MCUBoot bootloader encryption key file: ${encryption_key_file}") + if(${encryption_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() - # 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 - ) + 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 + ${encryption_key_file} + > ${GENERATED_ENCKEY} + DEPENDS ${encryption_key_file} + ) - if(${encryption_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!") + 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 - ${encryption_key_file} - > ${GENERATED_ENCKEY} - DEPENDS ${encryption_key_file} - ) - zephyr_library_sources(${GENERATED_ENCKEY}) endif() if(CONFIG_MCUBOOT_CLEANUP_ARM_CORE) @@ -709,3 +712,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 ${CONFIG_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 d9b80d570b..3eb78a251b 100644 --- a/boot/zephyr/Kconfig +++ b/boot/zephyr/Kconfig @@ -95,6 +95,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 @@ -112,6 +114,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 @@ -353,7 +357,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 @@ -609,7 +613,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 @@ -669,12 +674,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 fd49cabd88..49c56a84d5 100644 --- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h +++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h @@ -152,6 +152,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; +} diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index 04036b2137..8c7e032a28 100755 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -512,9 +512,9 @@ def ecies_hkdf(self, enckey, plainkey, hmac_sha_alg): format=PublicFormat.Raw) return cipherkey, ciphermac, pubk - def create(self, key, public_key_format, enckey, dependencies=None, + def create2(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_key=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): @@ -606,7 +606,7 @@ def create(self, key, public_key_format, enckey, dependencies=None, # # This adds the padding if image is not aligned to the 16 Bytes # in encrypted mode - if self.enckey is not None and dont_encrypt is False: + if aes_key is not None and dont_encrypt is False: pad_len = len(self.payload) % 16 if pad_len > 0: pad = bytes(16 - pad_len) @@ -621,10 +621,8 @@ 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) + aes_key_bits = 0 if aes_key is None else len(aes_key) * 8 + self.add_header(protected_tlv_size, compression_flags, aes_key_bits) prot_tlv = TLV(self.endian, TLV_PROT_INFO_MAGIC) @@ -743,12 +741,18 @@ def create(self, key, public_key_format, enckey, dependencies=None, if protected_tlv_off is not 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) + # When passed AES key and clear flag, then do not encrypt, because the key + # is only passed to be stored in encryption key TLV, the payload stays clear text. + if aes_key and not clear: + nonce = bytes([0] * 16) + cipher = Cipher(algorithms.AES(aes_key), modes.CTR(nonce), + backend=default_backend()) + encryptor = cipher.encryptor() + img = bytes(self.payload[self.header_size:]) + self.payload[self.header_size:] = \ + encryptor.update(img) + encryptor.finalize() + if enckey is not None and dont_encrypt is False: if not isinstance(enckey, rsa.RSAPublic): if hmac_sha == 'auto' or hmac_sha == '256': hmac_sha = '256' @@ -763,19 +767,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_key, 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_key, 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_key, hmac_sha_alg) enctlv = pubk + mac + cipherkey self.enctlv_len = len(enctlv) if (hmac_sha == '256'): @@ -783,20 +787,39 @@ def create(self, key, public_key_format, enckey, dependencies=None, else: tlv.add('ENCX25519_SHA512', enctlv) - if not clear: - nonce = bytes([0] * 16) - cipher = Cipher(algorithms.AES(plainkey), modes.CTR(nonce), - backend=default_backend()) - encryptor = cipher.encryptor() - img = bytes(self.payload[self.header_size:]) - self.payload[self.header_size:] = \ - encryptor.update(img) + encryptor.finalize() - self.payload += prot_tlv.get() self.payload += tlv.get() self.check_trailer() + 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): + + # With enckey None and dont_encrypt false we still get encrypted image generated + # but without TLV for sharing key; if we do not have enckey and dont_encrypt is + # true, we are not going to encrypt image at all, so it is pointless to generate + # AES key. + if not enckey and dont_encrypt: + plainkey = None + else: + 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 + plainkey = os.urandom(encrypt_keylen_bytes) + + return self.create2(key, public_key_format, enckey, dependencies, sw_type, + custom_tlvs, compression_tlvs, compression_type, + plainkey, clear, fixed_sig, pub_key, vector_to_sign, + user_sha, hmac_sha, is_pure, keep_comp_size, dont_encrypt) + + def get_struct_endian(self): return STRUCT_ENDIAN_DICT[self.endian] @@ -806,11 +829,11 @@ 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, protected_tlv_size, compression_flags, aes_length=0): """Install the image header.""" flags = 0 - if enckey is not None: + if aes_length != 0: if aes_length == 128: flags |= IMAGE_F['ENCRYPTED_AES128'] else: diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index 3074234e2f..2c915efb33 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 @@ -472,17 +485,23 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, non_bootable=non_bootable, vid=vid, cid=cid) 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: + print('--clear overrides raw AES key, image will not 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 +546,26 @@ 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.') + plainkey = None + if aes_key: + # Converting the command line provided raw AES key to byte array; + # this aray will be truncated to desired len. + plainkey = bytes.fromhex(aes_key) + plainkey_len = len(plainkey) + if plainkey_len not in (16, 32): + raise click.UsageError("Provided keylen, {int(plainkey_len)} in bytes, not supported") + elif 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 + plainkey = os.urandom(encrypt_keylen_bytes) + if compression in ["lzma2", "lzma2armthumb"]: - img.create(key, public_key_format, enckey, dependencies, boot_record, - custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, + img.create2(key, public_key_format, enckey, dependencies, boot_record, + custom_tlvs, compression_tlvs, None, None, 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), @@ -573,15 +609,15 @@ def sign(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, int(encrypt_keylen), clear, baked_signature, + compression, plainkey, 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, + img.create2(key, public_key_format, enckey, dependencies, boot_record, + custom_tlvs, compression_tlvs, None, plainkey, 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)