diff --git a/boot/bootutil/CMakeLists.txt b/boot/bootutil/CMakeLists.txt index 3d1b32717e..9fae2ba850 100644 --- a/boot/bootutil/CMakeLists.txt +++ b/boot/bootutil/CMakeLists.txt @@ -14,28 +14,32 @@ target_include_directories(bootutil src ) -target_sources(bootutil - PRIVATE - src/boot_record.c - src/bootutil_misc.c - src/bootutil_public.c - src/caps.c - src/encrypted.c - src/fault_injection_hardening.c - src/fault_injection_hardening_delay_rng_mbedtls.c - src/image_ecdsa.c - src/image_ed25519.c - src/image_rsa.c - src/image_validate.c - src/loader.c - src/swap_misc.c - src/swap_move.c - src/swap_scratch.c - src/tlv.c +set(BOOTUTIL_SOURCES + src/boot_record.c + src/bootutil_misc.c + src/bootutil_public.c + src/caps.c + src/encrypted.c + src/fault_injection_hardening.c + src/fault_injection_hardening_delay_rng_mbedtls.c + src/image_ecdsa.c + src/image_ed25519.c + src/image_rsa.c + src/loader.c + src/swap_misc.c + src/swap_move.c + src/swap_scratch.c + src/tlv.c ) + if(CONFIG_BOOT_RAM_LOAD) - target_sources(bootutil - PRIVATE - src/ram_load.c - ) + list(APPEND BOOTUTIL_SOURCES src/ram_load.c) endif() + +if(MCUBOOT_IMAGE_MULTI_SIG_SUPPORT) + list(APPEND BOOTUTIL_SOURCES src/image_multi_sig.c) +else() + list(APPEND BOOTUTIL_SOURCES src/image_validate.c) +endif() + +target_sources(bootutil PRIVATE ${BOOTUTIL_SOURCES}) diff --git a/boot/bootutil/include/bootutil/crypto/ecdsa.h b/boot/bootutil/include/bootutil/crypto/ecdsa.h index 3b05410722..e0cf493ce1 100644 --- a/boot/bootutil/include/bootutil/crypto/ecdsa.h +++ b/boot/bootutil/include/bootutil/crypto/ecdsa.h @@ -392,11 +392,6 @@ static inline void bootutil_ecdsa_init(bootutil_ecdsa_context *ctx) ctx->required_algorithm = 0; #else /* !MCUBOOT_BUILTIN_KEY */ - /* The incoming key ID is equal to the image index. The key ID value must be - * shifted (by one in this case) because zero is reserved (PSA_KEY_ID_NULL) - * and considered invalid. - */ - ctx->key_id++; /* Make sure it is not equal to 0. */ #if defined(MCUBOOT_SIGN_EC256) ctx->curve_byte_count = 32; ctx->required_algorithm = PSA_ALG_SHA_256; diff --git a/boot/bootutil/include/bootutil/image.h b/boot/bootutil/include/bootutil/image.h index 15de3e01ac..cd3a8bf5b1 100644 --- a/boot/bootutil/include/bootutil/image.h +++ b/boot/bootutil/include/bootutil/image.h @@ -100,6 +100,7 @@ struct flash_area; */ #define IMAGE_TLV_KEYHASH 0x01 /* hash of the public key */ #define IMAGE_TLV_PUBKEY 0x02 /* public key */ +#define IMAGE_TLV_KEYID 0x03 /* Key ID */ #define IMAGE_TLV_SHA256 0x10 /* SHA256 of image hdr and body */ #define IMAGE_TLV_SHA384 0x11 /* SHA384 of image hdr and body */ #define IMAGE_TLV_SHA512 0x12 /* SHA512 of image hdr and body */ diff --git a/boot/bootutil/include/bootutil/sign_key.h b/boot/bootutil/include/bootutil/sign_key.h index a5e81d3506..46bed10fab 100644 --- a/boot/bootutil/include/bootutil/sign_key.h +++ b/boot/bootutil/include/bootutil/sign_key.h @@ -39,6 +39,17 @@ struct bootutil_key { }; extern const struct bootutil_key bootutil_keys[]; +#ifdef MCUBOOT_BUILTIN_KEY +/** + * Verify that the specified key ID is valid for authenticating the given image. + * + * @param[in] image_index Index of the image to be verified. + * @param[in] key_id Identifier of the key to be verified against the image. + * + * @return 0 if the key ID is valid for the image; nonzero on failure. + */ +int boot_verify_key_id_for_image(uint8_t image_index, int32_t key_id); +#endif /* MCUBOOT_BUILTIN_KEY */ #else struct bootutil_key { uint8_t *key; @@ -51,6 +62,7 @@ extern struct bootutil_key bootutil_keys[]; * Retrieve the hash of the corresponding public key for image authentication. * * @param[in] image_index Index of the image to be authenticated. + * @param[in] key_index Index of the key to be used. * @param[out] public_key_hash Buffer to store the key-hash in. * @param[in,out] key_hash_size As input the size of the buffer. As output * the actual key-hash length. @@ -58,8 +70,10 @@ extern struct bootutil_key bootutil_keys[]; * @return 0 on success; nonzero on failure. */ int boot_retrieve_public_key_hash(uint8_t image_index, + uint8_t key_index, uint8_t *public_key_hash, size_t *key_hash_size); + #endif /* !MCUBOOT_HW_KEY */ extern const int bootutil_key_cnt; diff --git a/boot/bootutil/src/image_multi_sig.c b/boot/bootutil/src/image_multi_sig.c new file mode 100644 index 0000000000..d284ee8a60 --- /dev/null +++ b/boot/bootutil/src/image_multi_sig.c @@ -0,0 +1,536 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2017-2019 Linaro LTD + * Copyright (c) 2016-2019 JUUL Labs + * Copyright (c) 2019-2024 Arm Limited + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * Original license: + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include + +#include + +#include "bootutil/bootutil_log.h" +#include "bootutil/crypto/ecdsa.h" +#include "bootutil/image.h" +#include "bootutil/crypto/sha.h" +#include "bootutil/sign_key.h" +#include "bootutil/security_cnt.h" +#include "bootutil/fault_injection_hardening.h" +#include "bootutil_priv.h" + +#include "mcuboot_config/mcuboot_config.h" + +#include "mbedtls/ecdsa.h" +#include "mbedtls/asn1.h" + +/* + * Compute SHA hash over the image. + * (SHA384 if ECDSA-P384 is being used, + * SHA256 otherwise). + */ +static int +bootutil_img_hash(struct boot_loader_state *state, + struct image_header *hdr, const struct flash_area *fap, + uint8_t *tmp_buf, uint32_t tmp_buf_sz, uint8_t *hash_result, + uint8_t *seed, int seed_len) +{ + bootutil_sha_context sha_ctx; + uint32_t size; + uint16_t hdr_size; + uint32_t blk_off; + uint32_t tlv_off; + int rc; + uint32_t off; + uint32_t blk_sz; + +#if (BOOT_IMAGE_NUMBER == 1) || defined(MCUBOOT_RAM_LOAD) + (void)state; + (void)hdr_size; + (void)blk_off; + (void)tlv_off; +#ifdef MCUBOOT_RAM_LOAD + (void)blk_sz; + (void)off; + (void)rc; + (void)fap; + (void)tmp_buf; + (void)tmp_buf_sz; +#endif +#endif + + bootutil_sha_init(&sha_ctx); + + /* in some cases (split image) the hash is seeded with data from + * the loader image */ + if (seed && (seed_len > 0)) { + bootutil_sha_update(&sha_ctx, seed, seed_len); + } + + /* Hash is computed over image header and image itself. */ + size = hdr_size = hdr->ih_hdr_size; + size += hdr->ih_img_size; + tlv_off = size; + + /* If protected TLVs are present they are also hashed. */ + size += hdr->ih_protect_tlv_size; + +#ifdef MCUBOOT_RAM_LOAD + bootutil_sha_update(&sha_ctx, + (void*)(IMAGE_RAM_BASE + hdr->ih_load_addr), + size); +#else + for (off = 0; off < size; off += blk_sz) { + blk_sz = size - off; + if (blk_sz > tmp_buf_sz) { + blk_sz = tmp_buf_sz; + } + rc = flash_area_read(fap, off, tmp_buf, blk_sz); + if (rc) { + bootutil_sha_drop(&sha_ctx); + return rc; + } + bootutil_sha_update(&sha_ctx, tmp_buf, blk_sz); + } +#endif /* MCUBOOT_RAM_LOAD */ + + bootutil_sha_finish(&sha_ctx, hash_result); + bootutil_sha_drop(&sha_ctx); + + return 0; +} + +#if defined(MCUBOOT_SIGN_EC256) || \ + defined(MCUBOOT_SIGN_EC384) || \ + defined(MCUBOOT_SIGN_EC) +# define EXPECTED_SIG_TLV IMAGE_TLV_ECDSA_SIG +# define SIG_BUF_SIZE 128 +# define EXPECTED_SIG_LEN(x) (1) /* always true, ASN.1 will validate */ +#else +#error "Unsupported signature algorithm for multi signature support" +#endif + +#if ((defined(MCUBOOT_HW_KEY) + defined(MCUBOOT_BUILTIN_KEY) > 1) || \ + (!defined(MCUBOOT_HW_KEY) && !defined(MCUBOOT_BUILTIN_KEY))) +#error "Please use either MCUBOOT_HW_KEY or the MCUBOOT_BUILTIN_KEY feature." +#endif + +#if defined(MCUBOOT_HW_KEY) +/* The key TLV contains the whole public key. + * Add a few extra bytes to the key buffer size for encoding and + * for public exponent. + */ +#define EXPECTED_KEY_TLV IMAGE_TLV_PUBKEY +#define KEY_BUF_SIZE (SIG_BUF_SIZE + 24) +extern unsigned int pub_key_len; +static int +bootutil_find_key(uint8_t image_index, uint8_t *key, uint16_t key_len) +{ + bootutil_sha_context sha_ctx; + uint8_t key_index = 0; + uint8_t hash[IMAGE_HASH_SIZE]; + uint8_t key_hash[IMAGE_HASH_SIZE]; + size_t key_hash_size = sizeof(key_hash); + int rc; + FIH_DECLARE(fih_rc, FIH_FAILURE); + + bootutil_sha_init(&sha_ctx); + bootutil_sha_update(&sha_ctx, key, key_len); + bootutil_sha_finish(&sha_ctx, hash); + bootutil_sha_drop(&sha_ctx); + + for(key_index = 0; key_index < 2; key_index++) { + rc = boot_retrieve_public_key_hash(image_index, key_index, key_hash, &key_hash_size); + if (rc) { + return -1; + } + + /* Adding hardening to avoid this potential attack: + * - Image is signed with an arbitrary key and the corresponding public + * key is added as a TLV field. + * - During public key validation (comparing against key-hash read from + * HW) a fault is injected to accept the public key as valid one. + */ + FIH_CALL(boot_fih_memequal, fih_rc, hash, key_hash, key_hash_size); + if (FIH_EQ(fih_rc, FIH_SUCCESS)) { + BOOT_LOG_INF("Key %d hash found for image %d", key_index, image_index); + bootutil_keys[0].key = key; + pub_key_len = key_len; + return 0; + } + } + + BOOT_LOG_ERR("Key hash not found for image %d", image_index); + return -1; +} + +#endif /* MCUBOOT_HW_KEY */ + + +#if defined(MCUBOOT_BUILTIN_KEY) +/* For MCUBOOT_BUILTIN_KEY, key id is passed */ +#define EXPECTED_KEY_TLV IMAGE_TLV_KEYID +#define KEY_BUF_SIZE sizeof(int) + +static int bootutil_find_key(uint8_t *key_id_buf, uint8_t key_id_buf_len) +{ + /* Key id is passed */ + assert(key_id_buf_len == sizeof(int32_t)); + return ((int32_t)key_id_buf[0] << 24) | + ((int32_t)key_id_buf[1] << 16) | + ((int32_t)key_id_buf[2] << 8) | + ((int32_t)key_id_buf[3]); +} +#endif /* !MCUBOOT_BUILTIN_KEY */ + +/** + * Reads the value of an image's security counter. + * + * @param state Pointer to the boot state object. + * @param slot Slot of the current image to get the security counter of. + * @param fap Pointer to a description structure of the image's + * flash area. + * @param security_cnt Pointer to store the security counter value. + * + * @return 0 on success; nonzero on failure. + */ +int32_t +bootutil_get_img_security_cnt(struct boot_loader_state *state, int slot, + const struct flash_area *fap, + uint32_t *img_security_cnt) +{ + struct image_tlv_iter it; + uint32_t off; + uint16_t len; + int32_t rc; + + if ((state == NULL) || + (boot_img_hdr(state, slot) == NULL) || + (fap == NULL) || + (img_security_cnt == NULL)) { + /* Invalid parameter. */ + return BOOT_EBADARGS; + } + + /* The security counter TLV is in the protected part of the TLV area. */ + if (boot_img_hdr(state, slot)->ih_protect_tlv_size == 0) { + return BOOT_EBADIMAGE; + } + + rc = bootutil_tlv_iter_begin(&it, boot_img_hdr(state, slot), fap, IMAGE_TLV_SEC_CNT, true); + if (rc) { + return rc; + } + + /* Traverse through the protected TLV area to find + * the security counter TLV. + */ + + rc = bootutil_tlv_iter_next(&it, &off, &len, NULL); + if (rc != 0) { + /* Security counter TLV has not been found. */ + return -1; + } + + if (len != sizeof(*img_security_cnt)) { + /* Security counter is not valid. */ + return BOOT_EBADIMAGE; + } + + rc = LOAD_IMAGE_DATA(boot_img_hdr(state, slot), fap, off, img_security_cnt, len); + if (rc != 0) { + return BOOT_EFLASH; + } + + return 0; +} + +#ifndef ALLOW_ROGUE_TLVS +/* + * The following list of TLVs are the only entries allowed in the unprotected + * TLV section. All other TLV entries must be in the protected section. + */ +static const uint16_t allowed_unprot_tlvs[] = { + IMAGE_TLV_KEYHASH, + IMAGE_TLV_PUBKEY, + IMAGE_TLV_KEYID, + IMAGE_TLV_SHA256, + IMAGE_TLV_SHA384, + IMAGE_TLV_SHA512, + IMAGE_TLV_RSA2048_PSS, + IMAGE_TLV_ECDSA224, + IMAGE_TLV_ECDSA_SIG, + IMAGE_TLV_RSA3072_PSS, + IMAGE_TLV_ED25519, + IMAGE_TLV_ENC_RSA2048, + IMAGE_TLV_ENC_KW, + IMAGE_TLV_ENC_EC256, + IMAGE_TLV_ENC_X25519, + /* Mark end with ANY. */ + IMAGE_TLV_ANY, +}; +#endif + +/* + * Verify the integrity of the image. + * Return non-zero if image could not be validated/does not validate. + */ +fih_ret +bootutil_img_validate(struct boot_loader_state *state, + struct image_header *hdr, const struct flash_area *fap, + uint8_t *tmp_buf, uint32_t tmp_buf_sz, uint8_t *seed, + int seed_len, uint8_t *out_hash) +{ +#if (defined(EXPECTED_KEY_TLV) && defined(MCUBOOT_HW_KEY)) || defined(MCUBOOT_HW_ROLLBACK_PROT) + int image_index = (state == NULL ? 0 : BOOT_CURR_IMG(state)); +#endif + uint32_t off; + uint16_t len; + uint16_t type; + FIH_DECLARE(valid_signature, FIH_FAILURE); + int key_id = -1; +#ifdef MCUBOOT_HW_KEY + uint8_t key_buf[KEY_BUF_SIZE]; +#endif + struct image_tlv_iter it; + uint8_t buf[SIG_BUF_SIZE]; +#if defined(EXPECTED_HASH_TLV) + int image_hash_valid = 0; + uint8_t hash[IMAGE_HASH_SIZE]; +#endif + int rc = 0; + FIH_DECLARE(fih_rc, FIH_FAILURE); +#ifdef MCUBOOT_HW_ROLLBACK_PROT + fih_int security_cnt = fih_int_encode(INT_MAX); + uint32_t img_security_cnt = 0; + FIH_DECLARE(security_counter_valid, FIH_FAILURE); +#endif + bool key_must_sign = true; + bool key_might_sign = false; + uint8_t key_must_sign_count = 0; + +#if defined(EXPECTED_HASH_TLV) + rc = bootutil_img_hash(state, hdr, fap, tmp_buf, tmp_buf_sz, hash, seed, seed_len); + if (rc) { + goto out; + } + + if (out_hash) { + memcpy(out_hash, hash, IMAGE_HASH_SIZE); + } +#endif /* defined(EXPECTED_HASH_TLV) */ + + rc = bootutil_tlv_iter_begin(&it, hdr, fap, IMAGE_TLV_ANY, false); + if (rc) { + goto out; + } + + if (it.tlv_end > bootutil_max_image_size(state, fap)) { + rc = -1; + goto out; + } + + /* + * Traverse through all of the TLVs, performing any checks we know + * and are able to do. + */ + while (true) { + rc = bootutil_tlv_iter_next(&it, &off, &len, &type); + if (rc < 0) { + goto out; + } else if (rc > 0) { + break; + } + +#ifndef ALLOW_ROGUE_TLVS + /* + * Ensure that the non-protected TLV only has entries necessary to hold + * the signature. We also allow encryption related keys to be in the + * unprotected area. + */ + if (!bootutil_tlv_iter_is_prot(&it, off)) { + bool found = false; + for (const uint16_t *p = allowed_unprot_tlvs; *p != IMAGE_TLV_ANY; p++) { + if (type == *p) { + found = true; + break; + } + } + if (!found) { + FIH_SET(fih_rc, FIH_FAILURE); + goto out; + } + } +#endif + switch(type) { +#if defined(EXPECTED_HASH_TLV) + case EXPECTED_HASH_TLV: + { + /* Verify the image hash. This must always be present. */ + if (len != sizeof(hash)) { + rc = -1; + goto out; + } + rc = LOAD_IMAGE_DATA(hdr, fap, off, buf, sizeof(hash)); + if (rc) { + goto out; + } + + FIH_CALL(boot_fih_memequal, fih_rc, hash, buf, sizeof(hash)); + if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { + FIH_SET(fih_rc, FIH_FAILURE); + goto out; + } + + image_hash_valid = 1; + break; + } +#endif /* defined(EXPECTED_HASH_TLV) */ + +#ifdef EXPECTED_KEY_TLV + case EXPECTED_KEY_TLV: + { + /* + * Determine which key we should be checking. + */ + if (len > KEY_BUF_SIZE) { + rc = -1; + goto out; + } +#ifndef MCUBOOT_HW_KEY + rc = LOAD_IMAGE_DATA(hdr, fap, off, buf, len); + if (rc) { + goto out; + } + key_id = bootutil_find_key(buf, len); +#else + rc = LOAD_IMAGE_DATA(hdr, fap, off, key_buf, len); + if (rc) { + goto out; + } + key_id = bootutil_find_key(image_index, key_buf, len); +#endif /* !MCUBOOT_HW_KEY */ + /* + * The key may not be found, which is acceptable. There + * can be multiple signatures, each preceded by a key. + */ + break; + } +#endif /* EXPECTED_KEY_TLV */ + case EXPECTED_SIG_TLV: + { + if (!EXPECTED_SIG_LEN(len) || len > sizeof(buf)) { + rc = -1; + goto out; + } + rc = LOAD_IMAGE_DATA(hdr, fap, off, buf, len); + if (rc) { + goto out; + } + + FIH_CALL(bootutil_verify_sig, valid_signature, hash, sizeof(hash), + buf, len, key_id); + + rc = boot_plat_check_key_policy((valid_signature == 0), key_id, + &key_might_sign, &key_must_sign, + &key_must_sign_count); + if (rc) { + goto out; + } + key_id = -1; + break; + } +#ifdef MCUBOOT_HW_ROLLBACK_PROT + case IMAGE_TLV_SEC_CNT: + { + /* + * Verify the image's security counter. + * This must always be present. + */ + if (len != sizeof(img_security_cnt)) { + /* Security counter is not valid. */ + rc = -1; + goto out; + } + + rc = LOAD_IMAGE_DATA(hdr, fap, off, &img_security_cnt, len); + if (rc) { + goto out; + } + + FIH_CALL(boot_nv_security_counter_get, fih_rc, image_index, + &security_cnt); + if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { + FIH_SET(fih_rc, FIH_FAILURE); + goto out; + } + + /* Compare the new image's security counter value against the + * stored security counter value. + */ + fih_rc = fih_ret_encode_zero_equality(img_security_cnt < + (uint32_t)fih_int_decode(security_cnt)); + if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { + FIH_SET(fih_rc, FIH_FAILURE); + goto out; + } + + /* The image's security counter has been successfully verified. */ + security_counter_valid = fih_rc; + break; + } +#endif /* MCUBOOT_HW_ROLLBACK_PROT */ + } + } + +#if defined(EXPECTED_HASH_TLV) + rc = !image_hash_valid; + if (rc) { + goto out; + } +#endif + if (FIH_NOT_EQ(key_must_sign, true) || FIH_NOT_EQ(key_might_sign, true) || + FIH_EQ(key_must_sign_count, 0)) { + FIH_RET(FIH_FAILURE); + FIH_SET(fih_rc, FIH_FAILURE); + } else { + FIH_RET(FIH_SUCCESS); + } +#ifdef MCUBOOT_HW_ROLLBACK_PROT + if (FIH_NOT_EQ(security_counter_valid, FIH_SUCCESS)) { + rc = -1; + goto out; + } +#endif + +out: + if (rc) { + FIH_SET(fih_rc, FIH_FAILURE); + } + + FIH_RET(fih_rc); +} diff --git a/boot/bootutil/src/image_validate.c b/boot/bootutil/src/image_validate.c index 61cbf4de04..5589912762 100644 --- a/boot/bootutil/src/image_validate.c +++ b/boot/bootutil/src/image_validate.c @@ -273,12 +273,12 @@ bootutil_img_hash(struct boot_loader_state *state, #if !defined(MCUBOOT_HW_KEY) static int -bootutil_find_key(uint8_t *keyhash, uint8_t keyhash_len) +bootutil_find_key(uint8_t image_index, uint8_t *keyhash, uint8_t keyhash_len) { bootutil_sha_context sha_ctx; int i; const struct bootutil_key *key; - uint8_t hash[IMAGE_HASH_SIZE]; + (void)image_index; if (keyhash_len > IMAGE_HASH_SIZE) { return -1; @@ -314,7 +314,7 @@ bootutil_find_key(uint8_t image_index, uint8_t *key, uint16_t key_len) bootutil_sha_finish(&sha_ctx, hash); bootutil_sha_drop(&sha_ctx); - rc = boot_retrieve_public_key_hash(image_index, key_hash, &key_hash_size); + rc = boot_retrieve_public_key_hash(image_index, 0, key_hash, &key_hash_size); if (rc) { return -1; } @@ -335,6 +335,32 @@ bootutil_find_key(uint8_t image_index, uint8_t *key, uint16_t key_len) return -1; } #endif /* !MCUBOOT_HW_KEY */ + +#else +/* For MCUBOOT_BUILTIN_KEY, key id is passed */ +#define EXPECTED_KEY_TLV IMAGE_TLV_KEYID +#define KEY_BUF_SIZE sizeof(int32_t) + +static int bootutil_find_key(uint8_t image_index, uint8_t *key_id_buf, uint8_t key_id_buf_len) +{ + int rc; + FIH_DECLARE(fih_rc, FIH_FAILURE); + + /* Key id is passed */ + assert(key_id_buf_len == sizeof(int32_t)); + int32_t key_id = (((int32_t)key_id_buf[0] << 24) | + ((int32_t)key_id_buf[1] << 16) | + ((int32_t)key_id_buf[2] << 8) | + ((int32_t)key_id_buf[3])); + + /* Check if key id is associated with the image */ + FIH_CALL(boot_verify_key_id_for_image, fih_rc, image_index, key_id); + if (FIH_EQ(fih_rc, FIH_SUCCESS)) { + return key_id; + } + + return -1; +} #endif /* !MCUBOOT_BUILTIN_KEY */ #endif /* EXPECTED_SIG_TLV */ @@ -450,6 +476,7 @@ static int bootutil_check_for_pure(const struct image_header *hdr, static const uint16_t allowed_unprot_tlvs[] = { IMAGE_TLV_KEYHASH, IMAGE_TLV_PUBKEY, + IMAGE_TLV_KEYID, IMAGE_TLV_SHA256, IMAGE_TLV_SHA384, IMAGE_TLV_SHA512, @@ -492,14 +519,7 @@ bootutil_img_validate(struct boot_loader_state *state, uint16_t type; #ifdef EXPECTED_SIG_TLV FIH_DECLARE(valid_signature, FIH_FAILURE); -#ifndef MCUBOOT_BUILTIN_KEY int key_id = -1; -#else - /* Pass a key ID equal to the image index, the underlying crypto library - * is responsible for mapping the image index to a builtin key ID. - */ - int key_id = image_index; -#endif /* !MCUBOOT_BUILTIN_KEY */ #ifdef MCUBOOT_HW_KEY uint8_t key_buf[KEY_BUF_SIZE]; #endif @@ -631,7 +651,7 @@ bootutil_img_validate(struct boot_loader_state *state, if (rc) { goto out; } - key_id = bootutil_find_key(buf, len); + key_id = bootutil_find_key(image_index, buf, len); #else rc = LOAD_IMAGE_DATA(hdr, fap, off, key_buf, len); if (rc) { diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index 3e2c11073f..4cc4851b99 100644 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -76,6 +76,7 @@ TLV_VALUES = { 'KEYHASH': 0x01, 'PUBKEY': 0x02, + 'KEYID': 0x03, 'SHA256': 0x10, 'SHA384': 0x11, 'SHA512': 0x12, @@ -135,13 +136,19 @@ def add(self, kind, payload): """ e = STRUCT_ENDIAN_DICT[self.endian] if isinstance(kind, int): - if not TLV_VENDOR_RES_MIN <= kind <= TLV_VENDOR_RES_MAX: + if kind in TLV_VALUES.values(): + buf = struct.pack(e + 'BBH', kind, 0, len(payload)) + elif TLV_VENDOR_RES_MIN <= kind <= TLV_VENDOR_RES_MAX: + # Custom vendor-reserved tag + buf = struct.pack(e + 'HH', kind, len(payload)) + else: msg = "Invalid custom TLV type value '0x{:04x}', allowed " \ "value should be between 0x{:04x} and 0x{:04x}".format( kind, TLV_VENDOR_RES_MIN, TLV_VENDOR_RES_MAX) raise click.UsageError(msg) - buf = struct.pack(e + 'HH', kind, len(payload)) else: + if kind not in TLV_VALUES: + raise click.UsageError(f"Unknown TLV type string: {kind}") buf = struct.pack(e + 'BBH', TLV_VALUES[kind], 0, len(payload)) self.buf += buf self.buf += payload @@ -290,6 +297,7 @@ def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE, self.enctlv_len = 0 self.max_align = max(DEFAULT_MAX_ALIGN, align) if max_align is None else int(max_align) self.non_bootable = non_bootable + self.psa_key_ids = None if self.max_align == DEFAULT_MAX_ALIGN: self.boot_magic = bytes([ @@ -457,32 +465,40 @@ def ecies_hkdf(self, enckey, plainkey): format=PublicFormat.Raw) return cipherkey, ciphermac, pubk - def create(self, key, public_key_format, enckey, dependencies=None, + def create(self, keys, 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', is_pure=False, keep_comp_size=False, dont_encrypt=False): self.enckey = enckey - # 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 + # key decides on sha, then pub_key; if both are none default is used + check_key = keys[0] if keys[0] is not None else pub_key hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha, is_pure) # Calculate the hash of the public key - if key is not None: - pub = key.get_public_bytes() - sha = hash_algorithm() - sha.update(pub) - pubbytes = sha.digest() - elif pub_key is not None: - if hasattr(pub_key, 'sign'): - print(os.path.basename(__file__) + ": sign the payload") - pub = pub_key.get_public_bytes() - sha = hash_algorithm() - sha.update(pub) - pubbytes = sha.digest() + pub_digests = [] + pub_list = [] + + if keys is None: + if pub_key is not None: + if hasattr(pub_key, 'sign'): + print(os.path.basename(__file__) + ": sign the payload") + pub = pub_key.get_public_bytes() + sha = hash_algorithm() + sha.update(pub) + pubbytes = sha.digest() + else: + pubbytes = bytes(hashlib.sha256().digest_size) else: - pubbytes = bytes(hashlib.sha256().digest_size) + for key in keys or []: + pub = key.get_public_bytes() + sha = hash_algorithm() + sha.update(pub) + pubbytes = sha.digest() + pub_digests.append(pubbytes) + pub_list.append(pub) + protected_tlv_size = 0 @@ -510,10 +526,14 @@ def create(self, key, public_key_format, enckey, dependencies=None, # value later. digest = bytes(hash_algorithm().digest_size) + if pub_digests: + boot_pub_digest = pub_digests[0] + else: + boot_pub_digest = pubbytes # Create CBOR encoded boot record boot_record = create_sw_component_data(sw_type, image_version, hash_tlv, digest, - pubbytes) + boot_pub_digest) protected_tlv_size += TLV_SIZE + len(boot_record) @@ -632,17 +652,30 @@ def create(self, key, public_key_format, enckey, dependencies=None, print(os.path.basename(__file__) + ': export digest') return - if key is not None or fixed_sig is not None: - if public_key_format == 'hash': - tlv.add('KEYHASH', pubbytes) - else: - tlv.add('PUBKEY', pub) + if fixed_sig is not None and keys is not None: + raise click.UsageError("Can not sign using key and provide fixed-signature at the same time") + + if fixed_sig is not None: + tlv.add(pub_key.sig_tlv(), fixed_sig['value']) + self.signatures[0] = fixed_sig['value'] + else: + # Multi-signature handling: iterate through each provided key and sign. + self.signatures = [] + for i, key in enumerate(keys): + # If key IDs are provided, and we have enough for this key, add it first. + if self.psa_key_ids is not None and len(self.psa_key_ids) > i: + # Convert key id (an integer) to 4-byte big-endian bytes. + kid_bytes = self.psa_key_ids[i].to_bytes(4, 'big') + tlv.add('KEYID', kid_bytes) # Using the TLV tag that corresponds to key IDs. + + if public_key_format == 'hash': + tlv.add('KEYHASH', pub_digests[i]) + else: + tlv.add('PUBKEY', pub_list[i]) - if key is not None and fixed_sig is None: # `sign` expects the full image payload (hashing done # internally), while `sign_digest` expects only the digest # of the payload - if hasattr(key, 'sign'): print(os.path.basename(__file__) + ": sign the payload") sig = key.sign(bytes(self.payload)) @@ -650,12 +683,8 @@ def create(self, key, public_key_format, enckey, dependencies=None, print(os.path.basename(__file__) + ": sign the digest") sig = key.sign_digest(message) tlv.add(key.sig_tlv(), sig) - self.signature = sig - elif fixed_sig is not None and key is None: - tlv.add(pub_key.sig_tlv(), fixed_sig['value']) - self.signature = fixed_sig['value'] - else: - raise click.UsageError("Can not sign using key and provide fixed-signature at the same time") + self.signatures.append(sig) + # At this point the image was hashed + signed, we can remove the # protected TLVs from the payload (will be re-added later) @@ -704,7 +733,7 @@ def get_struct_endian(self): return STRUCT_ENDIAN_DICT[self.endian] def get_signature(self): - return self.signature + return self.signatures def get_infile_data(self): return self.infile_data @@ -814,72 +843,106 @@ def verify(imgfile, key): if magic != IMAGE_MAGIC: return VerifyResult.INVALID_MAGIC, None, None, None + # Locate the first TLV info header tlv_off = header_size + img_size tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE] magic, tlv_tot = struct.unpack('HH', tlv_info) + + # If it's the protected-TLV block, skip it if magic == TLV_PROT_INFO_MAGIC: - tlv_off += tlv_tot + tlv_off += TLV_INFO_SIZE + tlv_tot tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE] magic, tlv_tot = struct.unpack('HH', tlv_info) if magic != TLV_INFO_MAGIC: return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None, None - # This is set by existence of TLV SIG_PURE - is_pure = False + # Define the unprotected-TLV window + unprot_off = tlv_off + TLV_INFO_SIZE + unprot_end = unprot_off + tlv_tot - prot_tlv_size = tlv_off - hash_region = b[:prot_tlv_size] - tlv_end = tlv_off + tlv_tot - tlv_off += TLV_INFO_SIZE # skip tlv info + # Region up to the start of unprotected TLVs is hashed + prot_tlv_end = unprot_off - TLV_INFO_SIZE + hash_region = b[:prot_tlv_end] - # First scan all TLVs in search of SIG_PURE - while tlv_off < tlv_end: - tlv = b[tlv_off:tlv_off + TLV_SIZE] + # This is set by existence of TLV SIG_PURE + is_pure = False + scan_off = unprot_off + while scan_off < unprot_end: + tlv = b[scan_off:scan_off + TLV_SIZE] tlv_type, _, tlv_len = struct.unpack('BBH', tlv) if tlv_type == TLV_VALUES['SIG_PURE']: is_pure = True break - tlv_off += TLV_SIZE + tlv_len + scan_off += TLV_SIZE + tlv_len + if key is not None and not isinstance(key, list): + key = [key] + + verify_results = [] + scan_off = unprot_off digest = None - tlv_off = header_size + img_size - tlv_end = tlv_off + tlv_tot - tlv_off += TLV_INFO_SIZE # skip tlv info - while tlv_off < tlv_end: - tlv = b[tlv_off:tlv_off + TLV_SIZE] + prot_tlv_size = unprot_off - TLV_INFO_SIZE + + # Verify hash and signatures + while scan_off < unprot_end: + tlv = b[scan_off:scan_off + TLV_SIZE] tlv_type, _, tlv_len = struct.unpack('BBH', tlv) if is_sha_tlv(tlv_type): - if not tlv_matches_key_type(tlv_type, key): + if not tlv_matches_key_type(tlv_type, key[0]): return VerifyResult.KEY_MISMATCH, None, None, None - off = tlv_off + TLV_SIZE + off = scan_off + TLV_SIZE digest = get_digest(tlv_type, hash_region) - if digest == b[off:off + tlv_len]: - if key is None: - return VerifyResult.OK, version, digest, None - else: - return VerifyResult.INVALID_HASH, None, None, None - elif not is_pure and key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]: - off = tlv_off + TLV_SIZE - tlv_sig = b[off:off + tlv_len] - payload = b[:prot_tlv_size] - try: - if hasattr(key, 'verify'): - key.verify(tlv_sig, payload) - else: - key.verify_digest(tlv_sig, digest) - return VerifyResult.OK, version, digest, None - except InvalidSignature: - # continue to next TLV - pass + if digest != b[off:off + tlv_len]: + verify_results.append(("Digest", "INVALID_HASH")) + + elif not is_pure and key is not None and tlv_type == TLV_VALUES[key[0].sig_tlv()]: + for idx, k in enumerate(key): + if tlv_type == TLV_VALUES[k.sig_tlv()]: + off = scan_off + TLV_SIZE + tlv_sig = b[off:off + tlv_len] + payload = b[:prot_tlv_size] + try: + if hasattr(k, 'verify'): + k.verify(tlv_sig, payload) + else: + k.verify_digest(tlv_sig, digest) + verify_results.append((f"Key {idx}", "OK")) + break + except InvalidSignature: + # continue to next TLV + verify_results.append((f"Key {idx}", "INVALID_SIGNATURE")) + continue + elif is_pure and key is not None and tlv_type in ALLOWED_PURE_SIG_TLVS: - off = tlv_off + TLV_SIZE + # pure signature verification + off = scan_off + TLV_SIZE tlv_sig = b[off:off + tlv_len] + k = key[0] try: - key.verify_digest(tlv_sig, hash_region) + k.verify_digest(tlv_sig, hash_region) return VerifyResult.OK, version, None, tlv_sig except InvalidSignature: - # continue to next TLV - pass - tlv_off += TLV_SIZE + tlv_len - return VerifyResult.INVALID_SIGNATURE, None, None, None + return VerifyResult.INVALID_SIGNATURE, None, None, None + + scan_off += TLV_SIZE + tlv_len + # Now print out the verification results: + for k, result in verify_results: + print(f"{k}: {result}") + + # Decide on a final return (for example, OK only if at least one signature is valid) + if any(result == "OK" for _, result in verify_results): + return VerifyResult.OK, version, digest, None + else: + return VerifyResult.INVALID_SIGNATURE, None, None, None + + + def set_key_ids(self, psa_key_ids): + """Set list of key IDs (integers) to be inserted before each signature.""" + self.psa_key_ids = psa_key_ids + + def _add_key_id_tlv_to_unprotected(self, tlv, key_id: int): + """Add a key ID TLV into the *unprotected* TLV area.""" + tag = TLV_VALUES['KEYID'] + value = key_id.to_bytes(4, self.endian) + tlv.add(tag, value) \ No newline at end of file diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index 1cdb792a54..6db456ce30 100755 --- a/scripts/imgtool/main.py +++ b/scripts/imgtool/main.py @@ -91,12 +91,14 @@ def load_signature(sigfile): signature = base64.b64decode(f.read()) return signature - -def save_signature(sigfile, sig): +def save_signatures(sigfile, sig): with open(sigfile, 'wb') as f: - signature = base64.b64encode(sig) - f.write(signature) - + if isinstance(sig, list): + for s in sig: + encoded = base64.b64encode(s) + f.write(encoded + b'\n') + else: + f.write(base64.b64encode(sig)) def load_key(keyfile): # TODO: better handling of invalid pass-phrase @@ -222,11 +224,14 @@ def getpriv(key, minimal, format): @click.argument('imgfile') -@click.option('-k', '--key', metavar='filename') +@click.option('-k', '--key', multiple=True, metavar='filename') @click.command(help="Check that signed image can be verified by given key") def verify(key, imgfile): - key = load_key(key) if key else None - ret, version, digest, signature = image.Image.verify(imgfile, key) + if key: + keys = [load_key(k) for k in key] + else: + keys = None + ret, version, digest, signature = image.Image.verify(imgfile, keys) if ret == image.VerifyResult.OK: print("Image was correctly validated") print("Image version: {}.{}.{}+{}".format(*version)) @@ -421,7 +426,7 @@ def convert(self, value, param, ctx): @click.option('--public-key-format', type=click.Choice(['hash', 'full']), default='hash', help='In what format to add the public key to ' 'the image manifest: full key or hash of the key.') -@click.option('-k', '--key', metavar='filename') +@click.option('-k', '--key', multiple=True, metavar='filename') @click.option('--fix-sig', metavar='filename', help='fixed signature for the image. It will be used instead of ' 'the signature calculated using the public key') @@ -441,6 +446,8 @@ def convert(self, value, param, ctx): help='send to OUTFILE the payload or payload''s digest instead ' 'of complied image. These data can be used for external image ' 'signing') +@click.option('--psa-key-ids', multiple=True, type=int, required=False, + help='List of integer key IDs for each signature.') @click.command(help='''Create a signed or unsigned image\n INFILE and OUTFILE are parsed as Intel HEX if the params have .hex extension, otherwise binary format is used''') @@ -450,7 +457,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, 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, is_pure, - vector_to_sign, non_bootable): + vector_to_sign, non_bootable, psa_key_ids): if confirm: # Confirmed but non-padded images don't make much sense, because @@ -466,21 +473,28 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, non_bootable=non_bootable) compression_tlvs = {} img.load(infile) - key = load_key(key) if key else None + if key: + loaded_keys = [load_key(k) for k in key] + else: + loaded_keys = None + enckey = load_key(encrypt) if encrypt else None if enckey and key: - if ((isinstance(key, keys.ECDSA256P1) and + first_key = loaded_keys[0] + if ((isinstance(first_key, keys.ECDSA256P1) and not isinstance(enckey, keys.ECDSA256P1Public)) - or (isinstance(key, keys.ECDSA384P1) and + or (isinstance(first_key, keys.ECDSA384P1) and not isinstance(enckey, keys.ECDSA384P1Public)) - or (isinstance(key, keys.RSA) and + or (isinstance(first_key, keys.RSA) and not isinstance(enckey, keys.RSAPublic))): # FIXME raise click.UsageError("Signing and encryption must use the same " "type of key") - if pad_sig and hasattr(key, 'pad_sig'): - key.pad_sig = True + if pad_sig and loaded_keys: + for k in loaded_keys: + if hasattr(k, 'pad_sig'): + k.pad_sig = True # Get list of custom protected TLVs from the command-line custom_tlvs = {} @@ -523,7 +537,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, 'and forbids sha selection by user.') if compression in ["lzma2", "lzma2armthumb"]: - img.create(key, public_key_format, enckey, dependencies, boot_record, + img.create(loaded_keys, public_key_format, enckey, dependencies, boot_record, custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, is_pure=is_pure, keep_comp_size=False, dont_encrypt=True) @@ -572,14 +586,14 @@ def sign(key, public_key_format, align, version, pad_sig, header_size, 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.create(loaded_keys, public_key_format, enckey, dependencies, boot_record, custom_tlvs, compression_tlvs, None, int(encrypt_keylen), clear, baked_signature, pub_key, vector_to_sign, user_sha=user_sha, is_pure=is_pure) img.save(outfile, hex_addr) if sig_out is not None: new_signature = img.get_signature() - save_signature(sig_out, new_signature) + save_signatures(sig_out, new_signature) class AliasesGroup(click.Group):