Support for CUSTOM_ENCRYPT_KEY that allows customer to supply their own implementation

pull/662/head
David Garske 2025-12-30 16:47:02 -08:00 committed by Daniele Lacamera
parent 5a4862525d
commit cf08819d8e
7 changed files with 229 additions and 56 deletions

View File

@ -1047,23 +1047,20 @@ int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce);
int wolfBoot_erase_encrypt_key(void); /* called automatically by wolfBoot_success() */
```
To use your own implementation for getting the encryption key use `CUSTOM_ENCRYPT_KEY` and `OBJS_EXTRA=src/my_custom_encrypt_key.o`. Then provide your own implementation of `int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce);`
To sign and encrypt an image, create a key file with the concatenated key and nonce, then use the sign tool:
```sh
# Create key file (32-byte key + 16-byte IV for AES-256)
echo -n "0123456789abcdef0123456789abcdef0123456789abcdef" > enc_key.der
# Create key file (32-byte key + 16-byte nonce for AES-256)
printf "0123456789abcdef0123456789abcdef0123456789abcdef" > /tmp/enc_key.der
# Sign and encrypt
./tools/keytools/sign --ecc384 --sha384 --aes256 --encrypt enc_key.der \
./tools/keytools/sign --ecc384 --sha384 --aes256 --encrypt /tmp/enc_key.der \
fitImage wolfboot_signing_private_key.der 1
```
In your application, set the encryption key before triggering an update:
```c
wolfBoot_set_encrypt_key(enc_key, enc_iv);
wolfBoot_update_trigger();
```
The result is `fitImage_v1_signed_and_encrypted.bin`, which gets placed into your OFP_A or OFP_B partitions.
During boot, wolfBoot decrypts the image headers from disk to select the best candidate, loads and decrypts the full image to RAM, then verifies integrity and authenticity before booting. On successful boot, `wolfBoot_success()` clears the key from RAM.

View File

@ -36,6 +36,55 @@ wolfBoot upon next boot.
Aside from setting the temporary key, the update mechanism remains the same for distributing, uploading and
installing firmware updates through wolfBoot.
### Custom encryption key storage
You can use the `CUSTOM_ENCRYPT_KEY` option to implement your own functions for:
`wolfBoot_get_encrypt_key`, `wolfBoot_set_encrypt_key` and
`wolfBoot_erase_encrypt_key`.
To enable:
1) Add `CUSTOM_ENCRYPT_KEY=1` to your `.config`
2) Add your own .c file using `OBJS_EXTRA`. For example, for your own
`src/custom_encrypt_key.c` add this to your `.config`:
`OBJS_EXTRA=src/custom_encrypt_key.o`
Your custom implementation must provide these functions:
```c
int wolfBoot_set_encrypt_key(const uint8_t *key, const uint8_t *nonce);
int wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce);
int wolfBoot_erase_encrypt_key(void);
```
Example custom function for testing:
```c
#include "wolfboot/wolfboot.h"
#include "image.h"
int RAMFUNCTION wolfBoot_get_encrypt_key(uint8_t *key, uint8_t *nonce)
{
int i;
/* Test key: "0123456789abcdef0123456789abcdef" (32 bytes for AES-256) */
const char test_key[] = "0123456789abcdef0123456789abcdef";
/* Test nonce: "0123456789abcdef" (16 bytes) */
const char test_nonce[] = "0123456789abcdef";
for (i = 0; i < ENCRYPT_KEY_SIZE && i < (int)sizeof(test_key); i++) {
key[i] = (uint8_t)test_key[i];
}
for (i = 0; i < ENCRYPT_NONCE_SIZE && i < (int)sizeof(test_nonce); i++) {
nonce[i] = (uint8_t)test_nonce[i];
}
return 0;
}
```
Note: On platforms that use the src/update_disk.c loader it only reads from a
GPT partition and with ENCRYPT=1 it only needs `wolfBoot_get_encrypt_key` implemented.
### Libwolfboot API
The API to communicate with the bootloader from the application is expanded when this feature is enabled,

View File

@ -1605,7 +1605,7 @@ int disk_read(int drv, uint64_t start, uint32_t count, uint8_t *buf)
uint32_t start_offset = (start % EMMC_SD_BLOCK_SIZE);
(void)drv; /* only one drive supported */
#if 1 //def DEBUG_MMC
#ifdef DEBUG_MMC
wolfBoot_printf("disk_read: drv:%d, start:%llu, count:%d, dst:%p\n",
drv, start, count, buf);
#endif
@ -1657,7 +1657,7 @@ int disk_write(int drv, uint64_t start, uint32_t count, const uint8_t *buf)
uint32_t start_offset = (start % EMMC_SD_BLOCK_SIZE);
(void)drv; /* only one drive supported */
#if 1 //def DEBUG_MMC
#ifdef DEBUG_MMC
wolfBoot_printf("disk_write: drv:%d, start:%llu, count:%d, src:%p\n",
drv, start, count, buf);
#endif

View File

@ -563,6 +563,9 @@ ifeq ($(ENCRYPT),1)
endif
endif
endif
ifeq ($(CUSTOM_ENCRYPT_KEY),1)
CFLAGS+=-D"CUSTOM_ENCRYPT_KEY"
endif
endif
ifeq ($(EXT_FLASH),1)

View File

@ -1491,6 +1491,7 @@ static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce)
return ret;
#endif
}
#ifndef CUSTOM_ENCRYPT_KEY
/**
* @brief Set the encryption key.
*
@ -1545,7 +1546,8 @@ int RAMFUNCTION wolfBoot_get_encrypt_key(uint8_t *k, uint8_t *nonce)
#endif
return 0;
}
#endif
#endif /* UNIT_TEST */
/**
* @brief Erase the encryption key.
*
@ -1575,6 +1577,7 @@ int RAMFUNCTION wolfBoot_erase_encrypt_key(void)
#endif
return 0;
}
#endif /* !CUSTOM_ENCRYPT_KEY */
#if defined(__WOLFBOOT) || defined(UNIT_TEST)
@ -1585,20 +1588,31 @@ ChaCha chacha;
int RAMFUNCTION chacha_init(void)
{
#if defined(MMU) || defined(UNIT_TEST)
const uint8_t *key = ENCRYPT_KEY;
#ifdef CUSTOM_ENCRYPT_KEY
uint8_t stored_nonce[ENCRYPT_NONCE_SIZE];
uint8_t key[ENCRYPT_KEY_SIZE];
#else
const uint8_t *key = (uint8_t *)(WOLFBOOT_PARTITION_BOOT_ADDRESS +
ENCRYPT_TMP_SECRET_OFFSET);
const uint8_t* stored_nonce;
uint8_t *key;
#endif
uint8_t ff[ENCRYPT_KEY_SIZE];
const uint8_t* stored_nonce;
#ifdef NVM_FLASH_WRITEONCE
key -= WOLFBOOT_SECTOR_SIZE * nvm_select_fresh_sector(PART_BOOT);
#endif
#ifdef CUSTOM_ENCRYPT_KEY
int ret = wolfBoot_get_encrypt_key(key, stored_nonce);
if (ret != 0)
return ret;
#else
#if defined(MMU) || defined(UNIT_TEST)
key = ENCRYPT_KEY;
#else
key = (uint8_t *)(WOLFBOOT_PARTITION_BOOT_ADDRESS +
ENCRYPT_TMP_SECRET_OFFSET);
#endif
#ifdef NVM_FLASH_WRITEONCE
key -= WOLFBOOT_SECTOR_SIZE * nvm_select_fresh_sector(PART_BOOT);
#endif
stored_nonce = key + ENCRYPT_KEY_SIZE;
#endif
XMEMSET(&chacha, 0, sizeof(chacha));
@ -1632,9 +1646,14 @@ Aes aes_dec, aes_enc;
*/
int aes_init(void)
{
int devId;
int devId = INVALID_DEVID;
#if defined(CUSTOM_ENCRYPT_KEY) && !defined(WOLFBOOT_RENESAS_TSIP)
uint8_t stored_nonce[ENCRYPT_NONCE_SIZE];
uint8_t key[ENCRYPT_KEY_SIZE];
#else
uint8_t *stored_nonce;
uint8_t *key;
#endif
uint8_t ff[ENCRYPT_KEY_SIZE];
#ifdef WOLFBOOT_RENESAS_TSIP
@ -1645,19 +1664,20 @@ int aes_init(void)
key = enc_key->encrypted_user_key;
stored_nonce = enc_key->initial_vector;
wolfCrypt_Init(); /* required to setup the crypto callback defaults */
#else /* non TSIP */
devId = INVALID_DEVID;
#if defined(MMU) || defined(UNIT_TEST)
key = ENCRYPT_KEY;
#elif defined(CUSTOM_ENCRYPT_KEY)
wolfBoot_get_encrypt_key(key, stored_nonce);
#else
key = (uint8_t*)(WOLFBOOT_PARTITION_BOOT_ADDRESS +
ENCRYPT_TMP_SECRET_OFFSET);
#endif
#ifdef NVM_FLASH_WRITEONCE
key -= WOLFBOOT_SECTOR_SIZE * nvm_select_fresh_sector(PART_BOOT);
#endif
stored_nonce = key + ENCRYPT_KEY_SIZE;
#endif /* WOLFBOOT_RENESAS_TSIP */
#if defined(MMU) || defined(UNIT_TEST)
key = ENCRYPT_KEY;
#else
key = (uint8_t*)(WOLFBOOT_PARTITION_BOOT_ADDRESS +
ENCRYPT_TMP_SECRET_OFFSET);
#endif
#ifdef NVM_FLASH_WRITEONCE
key -= WOLFBOOT_SECTOR_SIZE * nvm_select_fresh_sector(PART_BOOT);
#endif
stored_nonce = key + ENCRYPT_KEY_SIZE;
#endif /* non TSIP */
XMEMSET(&aes_enc, 0, sizeof(aes_enc));
XMEMSET(&aes_dec, 0, sizeof(aes_dec));

View File

@ -52,6 +52,9 @@
defined(ENCRYPT_WITH_CHACHA)
#define DISK_ENCRYPT
#include "encrypt.h"
/* Module-level storage for encryption nonce */
static uint8_t disk_encrypt_nonce[ENCRYPT_NONCE_SIZE];
#endif
#include <stdint.h>
@ -91,6 +94,92 @@
#endif
#ifdef DISK_ENCRYPT
/* Module-level storage for encryption key */
static uint8_t disk_encrypt_key[ENCRYPT_KEY_SIZE];
/**
* @brief Get the version from an already-decrypted header.
*
* This function extracts the version from a decrypted header blob
* without calling wolfBoot_get_blob_version, which might try to
* decrypt again if EXT_ENCRYPTED && MMU is defined.
*
* @param hdr Pointer to the decrypted header.
*
* @return The version number, or 0 if not found.
*/
static uint32_t get_decrypted_blob_version(uint8_t *hdr)
{
uint32_t *magic = (uint32_t *)hdr;
uint16_t tlv_type, tlv_len;
uint8_t *p = hdr + IMAGE_HEADER_OFFSET;
uint8_t *max_p = hdr + IMAGE_HEADER_SIZE;
if (*magic != WOLFBOOT_MAGIC)
return 0;
/* Search for version TLV */
while (p + 4 < max_p) {
tlv_type = *((uint16_t*)p);
tlv_len = *((uint16_t*)(p + 2));
if (tlv_type == 0 || tlv_type == 0xFFFF)
break;
/* Skip padding bytes */
if ((p[0] == 0xFF) || ((((uintptr_t)p) & 0x01) != 0)) {
p++;
continue;
}
if (tlv_type == HDR_VERSION && tlv_len == 4) {
uint32_t ver = *((uint32_t*)(p + 4));
return ver;
}
p += 4 + tlv_len;
}
return 0;
}
/**
* @brief Set up decryption context with IV at specified block offset.
*
* This function sets up the AES/ChaCha context with the IV positioned
* at the specified block offset. It matches how sign.c sets up encryption.
*
* @param block_offset Block offset for IV counter (0 = start of image).
*/
static void disk_crypto_set_iv(uint32_t block_offset)
{
#if defined(ENCRYPT_WITH_CHACHA)
wc_Chacha_SetIV(&chacha, disk_encrypt_nonce, block_offset);
#elif defined(ENCRYPT_WITH_AES128) || defined(ENCRYPT_WITH_AES256)
/* For AES CTR, we need to construct the IV with the counter.
* The sign tool uses the IV directly without byte-reversal,
* so we must match that behavior here. */
uint8_t iv[ENCRYPT_BLOCK_SIZE];
uint32_t ctr;
/* Copy nonce/IV (first 12 bytes for CTR nonce, last 4 for counter) */
memcpy(iv, disk_encrypt_nonce, ENCRYPT_NONCE_SIZE);
/* Add block offset to the counter portion (last 4 bytes, big-endian) */
/* The IV from sign.c is already in the correct format, we just need
* to add the block offset to the counter portion */
ctr = ((uint32_t)iv[12] << 24) | ((uint32_t)iv[13] << 16) |
((uint32_t)iv[14] << 8) | (uint32_t)iv[15];
ctr += block_offset;
iv[12] = (uint8_t)(ctr >> 24);
iv[13] = (uint8_t)(ctr >> 16);
iv[14] = (uint8_t)(ctr >> 8);
iv[15] = (uint8_t)(ctr);
wc_AesSetIV(&aes_dec, iv);
#endif
}
/**
* @brief Decrypt an image header in RAM.
*
@ -104,13 +193,14 @@
*/
static int decrypt_header(const uint8_t *src, uint8_t *dst)
{
uint32_t i;
uint32_t magic;
for (i = 0; i < IMAGE_HEADER_SIZE; i += ENCRYPT_BLOCK_SIZE) {
wolfBoot_crypto_set_iv(NULL, i / ENCRYPT_BLOCK_SIZE);
crypto_decrypt(dst + i, src + i, ENCRYPT_BLOCK_SIZE);
}
/* Reset IV to start of image (block 0) */
disk_crypto_set_iv(0);
/* Decrypt header - CTR mode handles counter increment internally */
crypto_decrypt(dst, src, IMAGE_HEADER_SIZE);
magic = *((uint32_t*)dst);
if (magic != WOLFBOOT_MAGIC)
return -1;
@ -130,22 +220,12 @@ static int decrypt_header(const uint8_t *src, uint8_t *dst)
*/
static int decrypt_image(uint8_t *data, uint32_t size)
{
uint32_t iv_counter = 0;
uint32_t offset = 0;
uint8_t dec_block[ENCRYPT_BLOCK_SIZE];
/* Reset IV to start of image (block 0) */
disk_crypto_set_iv(0);
while (offset < size) {
uint32_t chunk = size - offset;
if (chunk > ENCRYPT_BLOCK_SIZE)
chunk = ENCRYPT_BLOCK_SIZE;
/* Decrypt entire image - CTR mode handles counter increment internally */
crypto_decrypt(data, data, size);
wolfBoot_crypto_set_iv(NULL, iv_counter);
crypto_decrypt(dec_block, data + offset, chunk);
memcpy(data + offset, dec_block, chunk);
offset += ENCRYPT_BLOCK_SIZE;
iv_counter++;
}
return 0;
}
#endif /* DISK_ENCRYPT */
@ -189,11 +269,17 @@ void RAMFUNCTION wolfBoot_start(void)
uint64_t start_us, elapsed_ms;
#ifdef DISK_ENCRYPT
/* Initialize encryption */
/* Initialize encryption - this sets up the cipher with key from storage */
if (wolfBoot_initialize_encryption() != 0) {
wolfBoot_printf("Error initializing encryption\r\n");
wolfBoot_panic();
}
/* Retrieve encryption key and nonce for disk decryption */
if (wolfBoot_get_encrypt_key(disk_encrypt_key, disk_encrypt_nonce) != 0) {
wolfBoot_printf("Error getting encryption key\r\n");
wolfBoot_panic();
}
wolfBoot_printf("Disk encryption enabled\r\n");
#endif
ret = disk_init(BOOT_DISK);
@ -212,7 +298,9 @@ void RAMFUNCTION wolfBoot_start(void)
== IMAGE_HEADER_SIZE) {
#ifdef DISK_ENCRYPT
if (decrypt_header(p_hdr, dec_hdr) == 0) {
pA_ver = wolfBoot_get_blob_version(dec_hdr);
/* Use local version parser to avoid double-decryption issue
* when EXT_ENCRYPTED && MMU is also defined */
pA_ver = get_decrypted_blob_version(dec_hdr);
}
#else
pA_ver = wolfBoot_get_blob_version((uint8_t*)p_hdr);
@ -225,7 +313,9 @@ void RAMFUNCTION wolfBoot_start(void)
== IMAGE_HEADER_SIZE) {
#ifdef DISK_ENCRYPT
if (decrypt_header(p_hdr, dec_hdr) == 0) {
pB_ver = wolfBoot_get_blob_version(dec_hdr);
/* Use local version parser to avoid double-decryption issue
* when EXT_ENCRYPTED && MMU is also defined */
pB_ver = get_decrypted_blob_version(dec_hdr);
}
#else
pB_ver = wolfBoot_get_blob_version((uint8_t*)p_hdr);
@ -275,8 +365,19 @@ void RAMFUNCTION wolfBoot_start(void)
continue;
}
#ifdef DISK_ENCRYPT
/* Decrypt header to parse image size */
if (decrypt_header(p_hdr, dec_hdr) != 0) {
wolfBoot_printf("Error decrypting header for %s\r\n", part_name);
selected ^= 1;
continue;
}
memset(&os_image, 0, sizeof(os_image));
ret = wolfBoot_open_image_address(&os_image, (void*)dec_hdr);
#else
memset(&os_image, 0, sizeof(os_image));
ret = wolfBoot_open_image_address(&os_image, (void*)p_hdr);
#endif
if (ret < 0) {
wolfBoot_printf("Error parsing loaded image\r\n");
selected ^= 1;

View File

@ -1808,6 +1808,8 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
fsize = ftell(f);
fseek(f, 0, SEEK_SET); /* restart the _signed file from 0 */
printf("Encrypting %u bytes...\n", fsize);
if (CMD.encrypt == ENC_CHACHA) {
ChaCha cha;
#ifndef HAVE_CHACHA
@ -1845,6 +1847,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
}
}
fclose(fef);
printf("Encryption complete.\n");
}
printf("Output image(s) successfully created.\n");
ret = 0;