Add bootutil support for encrypted images
This allows storing encrypted images in slot1, that are automatically
decrypted when copying to slot0 and re-encrypted when copying from slot0
to slot1.
The encryption works by applying AES-CTR-128 on the image blocks
(excluding the header and TLVs) using a random key. This random key
is itself encrypted using either RSA-OAEP-2048 or AES-KW-128 (AES keywrap
as defined by RFC3394), and appended to the image as newly defined TLVs.
AES-CTR-128 was chosen primarily for having stream cipher proporties,
which basically means that any block being encrypted/decrypted does not
depend on any other previous blocks results.
The TLV adds about 256 bytes to the image in RSA-OAEP-2048 mode, and 24
bytes in AES-KW-128 mode. Resulting sizes for a Mynewt generated mcuboot
(frdm-k64f):
- swap mode and no signing: 12KB
- adding encryption with RSA-OAEP-2048: 28KB
- adding encryption with AES-KW-128: 20KB
Some extra comments:
- AES-KW-128 requires a fairly new mbedtls with nist_kw support.
- An alternative methods which could be added later are ECIES.
- Key-wrapping seems easy enough to implement using just standard
AES-ECB mode that it should be straight-forward to also add support to
tinycrypt.
Signed-off-by: Fabio Utzig <utzig@apache.org>
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index f61a29f..e7c8d73 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -34,6 +34,10 @@
#include "bootutil_priv.h"
#include "bootutil/bootutil_log.h"
+#ifdef MCUBOOT_ENC_IMAGES
+#include "bootutil/enc_key.h"
+#endif
+
#include "mcuboot_config/mcuboot_config.h"
static struct boot_loader_state boot_data;
@@ -572,9 +576,26 @@
* Validate image hash/signature in a slot.
*/
static int
-boot_image_check(struct image_header *hdr, const struct flash_area *fap)
+boot_image_check(struct image_header *hdr, const struct flash_area *fap,
+ struct boot_status *bs)
{
static uint8_t tmpbuf[BOOT_TMPBUF_SZ];
+ int rc;
+
+#ifndef MCUBOOT_ENC_IMAGES
+ (void)bs;
+ (void)rc;
+#else
+ if (fap->fa_id == FLASH_AREA_IMAGE_1 && hdr->ih_flags & IMAGE_F_ENCRYPTED) {
+ rc = boot_enc_load(hdr, fap, bs->enckey[1]);
+ if (rc < 0) {
+ return BOOT_EBADIMAGE;
+ }
+ if (rc == 0 && boot_enc_set_key(1, bs->enckey[1])) {
+ return BOOT_EBADIMAGE;
+ }
+ }
+#endif
if (bootutil_img_validate(hdr, fap, tmpbuf, BOOT_TMPBUF_SZ,
NULL, 0, NULL)) {
@@ -625,7 +646,7 @@
}
static int
-boot_validate_slot(int slot)
+boot_validate_slot(int slot, struct boot_status *bs)
{
const struct flash_area *fap;
struct image_header *hdr;
@@ -643,7 +664,7 @@
return -1;
}
- if ((hdr->ih_magic != IMAGE_MAGIC || boot_image_check(hdr, fap) != 0)) {
+ if ((hdr->ih_magic != IMAGE_MAGIC || boot_image_check(hdr, fap, bs) != 0)) {
if (slot != 0) {
flash_area_erase(fap, 0, fap->fa_size);
/* Image in slot 1 is invalid. Erase the image and
@@ -669,7 +690,7 @@
* @return The type of swap to perform (BOOT_SWAP_TYPE...)
*/
static int
-boot_validated_swap_type(void)
+boot_validated_swap_type(struct boot_status *bs)
{
int swap_type;
@@ -679,7 +700,7 @@
case BOOT_SWAP_TYPE_PERM:
case BOOT_SWAP_TYPE_REVERT:
/* Boot loader wants to switch to slot 1. Ensure image is valid. */
- if (boot_validate_slot(1) != 0) {
+ if (boot_validate_slot(1, bs) != 0) {
swap_type = BOOT_SWAP_TYPE_FAIL;
}
}
@@ -731,37 +752,17 @@
/**
* Erases a region of flash.
*
- * @param flash_area_idx The ID of the flash area containing the region
- * to erase.
+ * @param flash_area The flash_area containing the region to erase.
* @param off The offset within the flash area to start the
* erase.
* @param sz The number of bytes to erase.
*
* @return 0 on success; nonzero on failure.
*/
-static int
-boot_erase_sector(int flash_area_id, uint32_t off, uint32_t sz)
+static inline int
+boot_erase_sector(const struct flash_area *fap, uint32_t off, uint32_t sz)
{
- const struct flash_area *fap;
- int rc;
-
- rc = flash_area_open(flash_area_id, &fap);
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
- rc = flash_area_erase(fap, off, sz);
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
- rc = 0;
-
-done:
- flash_area_close(fap);
- return rc;
+ return flash_area_erase(fap, off, sz);
}
/**
@@ -779,32 +780,23 @@
* @return 0 on success; nonzero on failure.
*/
static int
-boot_copy_sector(int flash_area_id_src, int flash_area_id_dst,
+boot_copy_sector(const struct flash_area *fap_src,
+ const struct flash_area *fap_dst,
uint32_t off_src, uint32_t off_dst, uint32_t sz)
{
- const struct flash_area *fap_src;
- const struct flash_area *fap_dst;
uint32_t bytes_copied;
int chunk_sz;
int rc;
+#ifdef MCUBOOT_ENC_IMAGES
+ uint32_t off;
+ size_t blk_off;
+ struct image_header *hdr;
+ uint16_t idx;
+ uint32_t blk_sz;
+#endif
static uint8_t buf[1024];
- fap_src = NULL;
- fap_dst = NULL;
-
- rc = flash_area_open(flash_area_id_src, &fap_src);
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
- rc = flash_area_open(flash_area_id_dst, &fap_dst);
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
bytes_copied = 0;
while (bytes_copied < sz) {
if (sz - bytes_copied > sizeof buf) {
@@ -815,42 +807,63 @@
rc = flash_area_read(fap_src, off_src + bytes_copied, buf, chunk_sz);
if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
+ return BOOT_EFLASH;
}
+#ifdef MCUBOOT_ENC_IMAGES
+ if (fap_src->fa_id == FLASH_AREA_IMAGE_1 ||
+ fap_dst->fa_id == FLASH_AREA_IMAGE_1) {
+ /* assume slot1 as src, needs decryption */
+ hdr = boot_img_hdr(&boot_data, 1);
+ off = off_src;
+ if (fap_dst->fa_id == FLASH_AREA_IMAGE_1) {
+ /* might need encryption (metadata from slot0) */
+ hdr = boot_img_hdr(&boot_data, 0);
+ off = off_dst;
+ }
+ if (hdr->ih_flags & IMAGE_F_ENCRYPTED) {
+ blk_sz = chunk_sz;
+ idx = 0;
+ if (off + bytes_copied < hdr->ih_hdr_size) {
+ /* do not decrypt header */
+ blk_off = 0;
+ blk_sz = chunk_sz - hdr->ih_hdr_size;
+ idx = hdr->ih_hdr_size;
+ } else {
+ blk_off = ((off + bytes_copied) - hdr->ih_hdr_size) & 0xf;
+ }
+ if (off + bytes_copied + chunk_sz > hdr->ih_hdr_size + hdr->ih_img_size) {
+ /* do not decrypt TLVs */
+ if (off + bytes_copied >= hdr->ih_hdr_size + hdr->ih_img_size) {
+ blk_sz = 0;
+ } else {
+ blk_sz = (hdr->ih_hdr_size + hdr->ih_img_size) - (off + bytes_copied);
+ }
+ }
+ boot_encrypt(fap_src, (off + bytes_copied + idx) - hdr->ih_hdr_size,
+ blk_sz, blk_off, &buf[idx]);
+ }
+ }
+#endif
+
rc = flash_area_write(fap_dst, off_dst + bytes_copied, buf, chunk_sz);
if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
+ return BOOT_EFLASH;
}
bytes_copied += chunk_sz;
}
- rc = 0;
-
-done:
- if (fap_src) {
- flash_area_close(fap_src);
- }
- if (fap_dst) {
- flash_area_close(fap_dst);
- }
- return rc;
+ return 0;
}
#ifndef MCUBOOT_OVERWRITE_ONLY
static inline int
-boot_status_init_by_id(int flash_area_id, const struct boot_status *bs)
+boot_status_init(const struct flash_area *fap, const struct boot_status *bs)
{
- const struct flash_area *fap;
struct boot_swap_state swap_state;
int rc;
- rc = flash_area_open(flash_area_id, &fap);
- assert(rc == 0);
-
rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_1, &swap_state);
assert(rc == 0);
@@ -862,10 +875,16 @@
rc = boot_write_swap_size(fap, bs->swap_size);
assert(rc == 0);
- rc = boot_write_magic(fap);
+#ifdef MCUBOOT_ENC_IMAGES
+ rc = boot_write_enc_key(fap, 0, bs->enckey[0]);
assert(rc == 0);
- flash_area_close(fap);
+ rc = boot_write_enc_key(fap, 1, bs->enckey[1]);
+ assert(rc == 0);
+#endif
+
+ rc = boot_write_magic(fap);
+ assert(rc == 0);
return 0;
}
@@ -873,13 +892,13 @@
#ifndef MCUBOOT_OVERWRITE_ONLY
static int
-boot_erase_last_sector_by_id(int flash_area_id)
+boot_erase_last_sector(const struct flash_area *fap)
{
uint8_t slot;
uint32_t last_sector;
int rc;
- switch (flash_area_id) {
+ switch (fap->fa_id) {
case FLASH_AREA_IMAGE_0:
slot = 0;
break;
@@ -891,7 +910,7 @@
}
last_sector = boot_img_num_sectors(&boot_data, slot) - 1;
- rc = boot_erase_sector(flash_area_id,
+ rc = boot_erase_sector(fap,
boot_img_sector_off(&boot_data, slot, last_sector),
boot_img_sector_size(&boot_data, slot, last_sector));
assert(rc == 0);
@@ -915,7 +934,9 @@
static void
boot_swap_sectors(int idx, uint32_t sz, struct boot_status *bs)
{
- const struct flash_area *fap;
+ const struct flash_area *fap_slot0;
+ const struct flash_area *fap_slot1;
+ const struct flash_area *fap_scratch;
uint32_t copy_sz;
uint32_t trailer_sz;
uint32_t img_off;
@@ -946,26 +967,34 @@
bs->use_scratch = (bs->idx == BOOT_STATUS_IDX_0 && copy_sz != sz);
+ rc = flash_area_open(FLASH_AREA_IMAGE_0, &fap_slot0);
+ assert (rc == 0);
+
+ rc = flash_area_open(FLASH_AREA_IMAGE_1, &fap_slot1);
+ assert (rc == 0);
+
+ rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH, &fap_scratch);
+ assert (rc == 0);
+
if (bs->state == BOOT_STATUS_STATE_0) {
- rc = boot_erase_sector(FLASH_AREA_IMAGE_SCRATCH, 0, sz);
+ rc = boot_erase_sector(fap_scratch, 0, sz);
assert(rc == 0);
- rc = boot_copy_sector(FLASH_AREA_IMAGE_1, FLASH_AREA_IMAGE_SCRATCH,
- img_off, 0, copy_sz);
+ rc = boot_copy_sector(fap_slot1, fap_scratch, img_off, 0, copy_sz);
assert(rc == 0);
if (bs->idx == BOOT_STATUS_IDX_0) {
if (bs->use_scratch) {
- boot_status_init_by_id(FLASH_AREA_IMAGE_SCRATCH, bs);
+ boot_status_init(fap_scratch, bs);
} else {
/* Prepare the status area... here it is known that the
* last sector is not being used by the image data so it's
* safe to erase.
*/
- rc = boot_erase_last_sector_by_id(FLASH_AREA_IMAGE_0);
+ rc = boot_erase_last_sector(fap_slot0);
assert(rc == 0);
- boot_status_init_by_id(FLASH_AREA_IMAGE_0, bs);
+ boot_status_init(fap_slot0, bs);
}
}
@@ -975,18 +1004,17 @@
}
if (bs->state == BOOT_STATUS_STATE_1) {
- rc = boot_erase_sector(FLASH_AREA_IMAGE_1, img_off, sz);
+ rc = boot_erase_sector(fap_slot1, img_off, sz);
assert(rc == 0);
- rc = boot_copy_sector(FLASH_AREA_IMAGE_0, FLASH_AREA_IMAGE_1,
- img_off, img_off, copy_sz);
+ rc = boot_copy_sector(fap_slot0, fap_slot1, img_off, img_off, copy_sz);
assert(rc == 0);
if (bs->idx == BOOT_STATUS_IDX_0 && !bs->use_scratch) {
/* If not all sectors of the slot are being swapped,
* guarantee here that only slot0 will have the state.
*/
- rc = boot_erase_last_sector_by_id(FLASH_AREA_IMAGE_1);
+ rc = boot_erase_last_sector(fap_slot1);
assert(rc == 0);
}
@@ -996,28 +1024,18 @@
}
if (bs->state == BOOT_STATUS_STATE_2) {
- rc = boot_erase_sector(FLASH_AREA_IMAGE_0, img_off, sz);
+ rc = boot_erase_sector(fap_slot0, img_off, sz);
assert(rc == 0);
/* NOTE: also copy trailer from scratch (has status info) */
- rc = boot_copy_sector(FLASH_AREA_IMAGE_SCRATCH, FLASH_AREA_IMAGE_0,
- 0, img_off, copy_sz);
+ rc = boot_copy_sector(fap_scratch, fap_slot0, 0, img_off, copy_sz);
assert(rc == 0);
if (bs->use_scratch) {
- rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH, &fap);
- assert(rc == 0);
-
- scratch_trailer_off = boot_status_off(fap);
-
- flash_area_close(fap);
-
- rc = flash_area_open(FLASH_AREA_IMAGE_0, &fap);
- assert(rc == 0);
+ scratch_trailer_off = boot_status_off(fap_scratch);
/* copy current status that is being maintained in scratch */
- rc = boot_copy_sector(FLASH_AREA_IMAGE_SCRATCH, FLASH_AREA_IMAGE_0,
- scratch_trailer_off,
+ rc = boot_copy_sector(fap_scratch, fap_slot0, scratch_trailer_off,
img_off + copy_sz,
BOOT_STATUS_STATE_COUNT * BOOT_WRITE_SZ(&boot_data));
BOOT_STATUS_ASSERT(rc == 0);
@@ -1027,17 +1045,23 @@
assert(rc == 0);
if (swap_state.image_ok == BOOT_FLAG_SET) {
- rc = boot_write_image_ok(fap);
+ rc = boot_write_image_ok(fap_slot0);
assert(rc == 0);
}
- rc = boot_write_swap_size(fap, bs->swap_size);
+ rc = boot_write_swap_size(fap_slot0, bs->swap_size);
assert(rc == 0);
- rc = boot_write_magic(fap);
+#ifdef MCUBOOT_ENC_IMAGES
+ rc = boot_write_enc_key(fap_slot0, 0, bs->enckey[0]);
assert(rc == 0);
- flash_area_close(fap);
+ rc = boot_write_enc_key(fap_slot0, 1, bs->enckey[1]);
+ assert(rc == 0);
+#endif
+
+ rc = boot_write_magic(fap_slot0);
+ assert(rc == 0);
}
bs->idx++;
@@ -1046,12 +1070,17 @@
rc = boot_write_status(bs);
BOOT_STATUS_ASSERT(rc == 0);
}
+
+ flash_area_close(fap_slot0);
+ flash_area_close(fap_slot1);
+ flash_area_close(fap_scratch);
}
#endif /* !MCUBOOT_OVERWRITE_ONLY */
/**
- * Swaps the two images in flash. If a prior copy operation was interrupted
- * by a system reset, this function completes that operation.
+ * Overwrite slot 0 with the image contained in slot 1. If a prior copy
+ * operation was interrupted by a system reset, this function redos the
+ * copy.
*
* @param bs The current boot status. This function reads
* this struct to determine if it is resuming
@@ -1071,6 +1100,11 @@
size_t size;
size_t this_size;
size_t last_sector;
+ const struct flash_area *fap_slot0;
+ const struct flash_area *fap_slot1;
+#ifdef MCUBOOT_ENC_IMAGES
+ uint8_t enckey[BOOT_ENC_KEY_SIZE];
+#endif
(void)bs;
@@ -1083,12 +1117,16 @@
BOOT_LOG_INF("Image upgrade slot1 -> slot0");
BOOT_LOG_INF("Erasing slot0");
+ rc = flash_area_open(FLASH_AREA_IMAGE_0, &fap_slot0);
+ assert (rc == 0);
+
+ rc = flash_area_open(FLASH_AREA_IMAGE_1, &fap_slot1);
+ assert (rc == 0);
+
sect_count = boot_img_num_sectors(&boot_data, 0);
for (sect = 0, size = 0; sect < sect_count; sect++) {
this_size = boot_img_sector_size(&boot_data, 0, sect);
- rc = boot_erase_sector(FLASH_AREA_IMAGE_0,
- size,
- this_size);
+ rc = boot_erase_sector(fap_slot0, size, this_size);
assert(rc == 0);
size += this_size;
@@ -1100,30 +1138,58 @@
#endif
}
+#ifdef MCUBOOT_ENC_IMAGES
+ if (boot_img_hdr(&boot_data, 1)->ih_flags & IMAGE_F_ENCRYPTED) {
+ rc = boot_enc_load(boot_img_hdr(&boot_data, 1), fap_slot1, enckey);
+ if (rc < 0) {
+ return BOOT_EBADIMAGE;
+ }
+ if (rc == 0 && boot_enc_set_key(1, enckey)) {
+ return BOOT_EBADIMAGE;
+ }
+ }
+#endif
+
BOOT_LOG_INF("Copying slot 1 to slot 0: 0x%lx bytes", size);
- rc = boot_copy_sector(FLASH_AREA_IMAGE_1, FLASH_AREA_IMAGE_0,
- 0, 0, size);
+ rc = boot_copy_sector(fap_slot1, fap_slot0, 0, 0, size);
/*
* Erases header and trailer. The trailer is erased because when a new
* image is written without a trailer as is the case when using newt, the
* trailer that was left might trigger a new upgrade.
*/
- rc = boot_erase_sector(FLASH_AREA_IMAGE_1,
+ rc = boot_erase_sector(fap_slot1,
boot_img_sector_off(&boot_data, 1, 0),
boot_img_sector_size(&boot_data, 1, 0));
assert(rc == 0);
last_sector = boot_img_num_sectors(&boot_data, 1) - 1;
- rc = boot_erase_sector(FLASH_AREA_IMAGE_1,
+ rc = boot_erase_sector(fap_slot1,
boot_img_sector_off(&boot_data, 1, last_sector),
boot_img_sector_size(&boot_data, 1, last_sector));
assert(rc == 0);
+ flash_area_close(fap_slot0);
+ flash_area_close(fap_slot1);
+
/* TODO: Perhaps verify slot 0's signature again? */
return 0;
}
+
#else
+
+/**
+ * Swaps the two images in flash. If a prior copy operation was interrupted
+ * by a system reset, this function completes that operation.
+ *
+ * @param bs The current boot status. This function reads
+ * this struct to determine if it is resuming
+ * an interrupted swap operation. This
+ * function writes the updated status to this
+ * function on return.
+ *
+ * @return 0 on success; nonzero on failure.
+ */
static int
boot_copy_image(struct boot_status *bs)
{
@@ -1132,6 +1198,12 @@
int last_sector_idx;
uint32_t swap_idx;
struct image_header *hdr;
+#ifdef MCUBOOT_ENC_IMAGES
+ const struct flash_area *fap;
+ uint8_t slot;
+ uint8_t i;
+ uint8_t enckey[BOOT_ENC_KEY_SIZE];
+#endif
uint32_t size;
uint32_t copy_size;
int rc;
@@ -1151,17 +1223,53 @@
assert(rc == 0);
}
+#ifdef MCUBOOT_ENC_IMAGES
+ if (hdr->ih_flags & IMAGE_F_ENCRYPTED) {
+ fap = BOOT_IMG_AREA(&boot_data, 0);
+ rc = boot_enc_load(hdr, fap, bs->enckey[0]);
+ assert(rc >= 0);
+
+ if (rc == 0) {
+ rc = boot_enc_set_key(0, bs->enckey[0]);
+ assert(rc == 0);
+ } else {
+ rc = 0;
+ }
+ } else {
+ memset(bs->enckey[0], 0xff, BOOT_ENC_KEY_SIZE);
+ }
+#endif
+
hdr = boot_img_hdr(&boot_data, 1);
if (hdr->ih_magic == IMAGE_MAGIC) {
rc = boot_read_image_size(1, hdr, &size);
assert(rc == 0);
}
+#ifdef MCUBOOT_ENC_IMAGES
+ hdr = boot_img_hdr(&boot_data, 1);
+ if (hdr->ih_flags & IMAGE_F_ENCRYPTED) {
+ fap = BOOT_IMG_AREA(&boot_data, 1);
+ rc = boot_enc_load(hdr, fap, bs->enckey[1]);
+ assert(rc >= 0);
+
+ if (rc == 0) {
+ rc = boot_enc_set_key(1, bs->enckey[1]);
+ assert(rc == 0);
+ } else {
+ rc = 0;
+ }
+ } else {
+ memset(bs->enckey[1], 0xff, BOOT_ENC_KEY_SIZE);
+ }
+#endif
+
if (size > copy_size) {
copy_size = size;
}
bs->swap_size = copy_size;
+
} else {
/*
* If a swap was under way, the swap_size should already be present
@@ -1171,6 +1279,23 @@
assert(rc == 0);
copy_size = bs->swap_size;
+
+#ifdef MCUBOOT_ENC_IMAGES
+ for (slot = 0; slot <= 1; slot++) {
+ rc = boot_read_enc_key(slot, bs->enckey[slot]);
+ assert(rc == 0);
+
+ for (i = 0; i < BOOT_ENC_KEY_SIZE; i++) {
+ if (enckey[i] != 0xff) {
+ break;
+ }
+ }
+
+ if (i != BOOT_ENC_KEY_SIZE) {
+ boot_enc_set_key(slot, bs->enckey[slot]);
+ }
+ }
+#endif
}
size = 0;
@@ -1303,7 +1428,7 @@
*/
swap_type = boot_previous_swap_type();
} else {
- swap_type = boot_validated_swap_type();
+ swap_type = boot_validated_swap_type(&bs);
switch (swap_type) {
case BOOT_SWAP_TYPE_TEST:
case BOOT_SWAP_TYPE_PERM:
@@ -1345,6 +1470,11 @@
boot_data.imgs[0].sectors = slot0_sectors;
boot_data.imgs[1].sectors = slot1_sectors;
+#ifdef MCUBOOT_ENC_IMAGES
+ /* FIXME: remove this after RAM is cleared by sim */
+ boot_enc_zeroize();
+#endif
+
/* Open boot_data image areas for the duration of this call. */
for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
fa_id = flash_area_id_from_image_slot(slot);
@@ -1448,7 +1578,7 @@
}
#ifdef MCUBOOT_VALIDATE_SLOT0
- rc = boot_validate_slot(0);
+ rc = boot_validate_slot(0, NULL);
ASSERT(rc == 0);
if (rc != 0) {
rc = BOOT_EBADIMAGE;