Skip to content

boot: Add support of xip encryption mode for swap#2685

Open
DOAR-Infineon wants to merge 1 commit intomcu-tools:mainfrom
DOAR-Infineon:xip-enc-swap
Open

boot: Add support of xip encryption mode for swap#2685
DOAR-Infineon wants to merge 1 commit intomcu-tools:mainfrom
DOAR-Infineon:xip-enc-swap

Conversation

@DOAR-Infineon
Copy link
Copy Markdown
Contributor

@DOAR-Infineon DOAR-Infineon commented Apr 2, 2026

Standard MCUboot encryption decrypts images during the swap process so that
the primary slot always contains plaintext. This works well for internal flash
but prevents execute-in-place (XIP) from external memory, because decrypted
data would be exposed on the external bus.

XIP encryption keeps images encrypted in all slots. A hardware crypto engine
(e.g., Infineon SMIF with on-the-fly AES decryption) provides transparent
decryption during code execution. The CPU fetches ciphertext from flash and the
hardware returns plaintext — no software decryption step is needed at runtime.

How XIP encryption for swap works

  1. Build-time encryption — Images are encrypted by imgtool (or
    edgeprotecttools) using AES-CTR before signing. The AES-CTR nonce
    follows the edgeprotecttools format:

    AES-CTR block input = counter_LE32 || nonce[0:12]
    

    where counter_LE32 is the flash-area-relative byte offset encoded as a
    little-endian 32-bit integer, and nonce[0:12] is the first 12 bytes of
    the HKDF-derived xip_iv. The counter is not slot-dependent; both the
    primary and secondary slots store identical ciphertext.

  2. Ciphertext signing — The SHA-256 hash and ECDSA signature are computed
    over the encrypted image (header + ciphertext + protected TLVs). The
    bootloader never performs software decryption during validation — the
    signature covers the exact bytes that reside in flash.

  3. Mandatory signature — Encrypted XIP images must be signed. The
    bootloader rejects unsigned encrypted images to prevent XOR attacks: an
    attacker who knows plaintext at a given offset could XOR it against the
    ciphertext to recover the keystream. A mandatory signature closes this
    attack vector.

  4. Swap copies raw bytes — During an upgrade, swap moves encrypted blocks
    between slots without any encryption or decryption. MCUBOOT_ENC_IMAGES
    is not defined; the data is byte-for-byte identical regardless of which
    slot it resides in.

  5. Post-boot hardware setup — After boot_go() returns, the application
    startup code configures hardware crypto regions for each image. The AES key
    and IV are available in the boot_rsp structure:

    struct boot_rsp rsp;
    /* ... boot_go(&rsp) ... */
    
    /* rsp.xip_key[4] -- 16-byte AES-128 key */
    /* rsp.xip_iv[4]  -- 16-byte IV/nonce     */

    Keys are cleared from library-internal storage before jumping to the
    application.

  6. Bootloader validation flow — The boot_image_check_hook() validates
    encrypted XIP images without software decryption:

    1. Verify image size fits within slot limits
    2. Compute SHA-256 over raw flash (header + ciphertext + protected TLVs)
    3. Verify hash against IMAGE_TLV_SHA256
    4. Verify mandatory ECDSA-P256 signature against IMAGE_TLV_ECDSA_SIG
    5. ECIES-P256 key unwrap from IMAGE_TLV_ENC_EC256 to extract AES key/IV

Extended ECIES TLV format

XIP images use an extended ECIES TLV (177 bytes) that adds a 32-byte HKDF
salt field beyond the standard ECIES-P256 layout:

[ ephemeral_pubkey (65 B) | hmac_tag (32 B) | enc_key (16 B) |
  hkdf_salt (32 B) | reserved (32 B) ]

The hkdf_salt is a per-image random value used as an HKDF diversifier,
ensuring that every image derives a unique AES key and IV — a critical
requirement for AES-CTR security. The final 32-byte field is reserved for
future use and must be zero.

Platform requirements

A platform using XIP encryption must provide or override:

  • boot_image_check_hook(state, img_index, slot) — Performs complete image
    validation including hash and signature verification over ciphertext and
    ECIES key unwrap. A default (weak) implementation is provided by the XIP
    encryption library (xip_enc_validate.c). The state pointer provides
    access to boot loader context (slot flash areas, secondary offset for
    swap-offset mode, max image size); it may be NULL when called outside the
    normal boot flow (e.g. serial recovery).

    fih_ret boot_image_check_hook(struct boot_loader_state *state,
                                  int img_index, int slot);
  • boot_xip_populate_rsp(img_index, rsp) — Called after successful
    validation to copy the extracted AES key and IV into the boot_rsp
    structure. The application uses these values to configure hardware crypto
    regions.

    void boot_xip_populate_rsp(int img_index, struct boot_rsp *rsp);
  • Hardware crypto setup — After boot_go() returns, the application
    must configure the hardware crypto engine (e.g., SMIF crypto regions) using
    the key material from boot_rsp before jumping to the application image.

Copy link
Copy Markdown
Member

@d3zd3z d3zd3z left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of specific comments, notably that I believe we should change to signing the ciphertext and not the plaintext, which avoids having to decrypt the image in software.

A few other things, Zephyr seems to be missing CMake changes to allow this code to be compiled.

It is important to explain the the IV calculation, and for example that either the key itself must be unique per image, or the extended TLV must be used to ensure that the IV is different for each image encrypted.

There are some memory issues, memset on stack variables is generally optimized away, and some uses of memcmp instead of a constant time comparison.

Also, it isn't clear to me what the 32 bytes of zero padding are after the salt.

I think the missing calls to zeroize concern prk and T? But, other memset calls need to zeroize. And we need a platform-specific correct zeroize (which can be rather difficult to write).

And, the Zephyr kconfig could have a depends on !BOOT_ENCRYPT_IMAGE to detect the misconfiguration early, rather than only after a compile failure.

Comment thread docs/encrypted_images.md
XIP encryption keeps images encrypted in **all** slots. A hardware crypto engine
(e.g., Infineon SMIF with on-the-fly AES decryption) provides transparent
decryption during code execution. The CPU fetches ciphertext from flash and the
hardware returns plaintext -- no software decryption step is needed at runtime.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of expansion here I think will help with a security analysis:

  • It's probably best to state plainly that in this mode, software never decrypts or encrypts.
  • Signatures are over the encrypted image.
  • It is critical that every image uses a completely different encryption key. This is a requirement for AES-CTR.

There is a tendency, in the security world, to "mandate" authenticated encryption. I have only partially made an argument with various people at the IETF that this is mostly unworkable for embedded use, and especially XIP.

In this case, it is unlikely we even have control over the particular encryption used. So, key and counter hygeine is very important.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment thread docs/encrypted_images.md Outdated
computed as:

```
counter = (encryption_address + offset) / 16
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear what these two values are. Below, offset is described as the byte offset within the image. But "encryption address" is not explained. Is this the base address of the first slot? Please make sure to explain it, especially that it is not a property of which slot the image resides in.

* Compute SHA-256 hash over header + decrypted payload + protected TLVs.
*
* Mirrors bootutil_img_hash() logic but uses boot_decrypt_xip() for
* payload decryption instead of boot_enc_decrypt().
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, I think is a bit of a fundamental problem here. With swap-based software decryption, the image signature is over the plaintext. This is because for the normal run case, the ciphertext is not available. For XIP, I think it makes much more sense to perform the signature over the ciphertext. This will allow the image to be verified without ever needing to do software decryption (with a goal that boot_decrypt_xip() is not present, and that the key is only used to give to the hardware and forgotten).

* a strong override of this function.
*/
__attribute__((weak))
fih_ret boot_image_check_hook(int img_index, int slot)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, I think this verification will make more sense if we change XIP encryption to sign the ciphertext instead of the plaintext.

But, it is also important to ensure that with encryption, signature verification is mandatory. It shouldn't be possible to build a configuration that has encryption validates the entire ciphertext before loading the encryption key into the hardware.

Without the signature verification, an attacker can trivially XOR parts of the image, which will apply as a XOR of the plaintext as well. Well known areas, such as vectors can easily be manipulated to refer to non-checked code as an attack.


void xip_enc_zeroize(void)
{
(void)memset(xip_keys, 0, sizeof(xip_keys));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some various zeroize implementations in the tree, and perhaps this needs to be generalized more. However, beyond this, there doesn't seem to be anything that ever calls this function.

bootutil_aes_ctr_drop(&aes_ctr);

/* Zeroize key from stack */
(void)memset(key, 0, sizeof(key));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use a platform provided zeroize, as compilers are free to optimize away a memset of a stack variable. This applies in all instances throughout the code.

return -1;
}

if ((tlv_len < ECIES_STD_TLV_SIZE) || (tlv_len > ECIES_EXT_TLV_SIZE)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check will allow a tlv with length anywhere between STD and EXT TLV size. However, the code below will only check if the value is greater than STD, and if so, assume that the extended data is available. If the tlv_len is between these values, this will result in that code reading from uninitialized memory.

This check should require the tlv_len to be either the STD or the EXT sizes, and not in between.

@stindaNXP
Copy link
Copy Markdown

stindaNXP commented Apr 7, 2026

Hi,

This PR gives the impression that it provides generic support for encrypted XIP for other vendors. However, not all implementations are based on AES-CTR, so the image has to be re-encrypted using the platform’s encryption scheme.
Are you aware of that?

In addition, some implementations cannot set the initial AES-CTR offset value (base address), as it is hardwired to be computed from the beginning of the flash memory. This could be fixed on imgtool side which could provide an argument for initial offset, so the computed cipher could be readable for the implementation.

@DOAR-Infineon
Copy link
Copy Markdown
Contributor Author

Hi,

This PR gives the impression that it provides generic support for encrypted XIP for other vendors. However, not all implementations are based on AES-CTR, so the image has to be re-encrypted using the platform’s encryption scheme. Are you aware of that?

In addition, some implementations cannot set the initial AES-CTR offset value (base address), as it is hardwired to be computed from the beginning of the flash memory. This could be fixed on imgtool side which could provide an argument for initial offset, so the computed cipher could be readable for the implementation.

Hi, thanks for the review.

  1. This implementation is based on AES-CTR, which is the most common cipher used by XIP-capable flash controllers. The
    design is generic within the AES-CTR family — the nonce format (counter_LE32 || iv[0:12]) and per-image key/IV
    derivation via HKDF are not tied to any specific vendor. The architecture is designed to be overridable for platforms with different encryption schemes: boot_image_check_hook(), boot_decrypt_xip(), and boot_xip_populate_rsp() are all __weak functions that platforms can replace with their own validation and hardware setup logic. The AES-CTR-specific code is confined to the xip_enc/ library, which serves as a default implementation.

  2. The bootloader-side AES-CTR nonce format follows the Infineon edgeprotecttools convention:
    AES-CTR block = counter_LE32 || nonce[0:12] where the counter is the absolute byte address and the 12-byte nonce is derived via HKDF from the ECIES TLV. The HKDF salt in the extended TLV also embeds the image base address (last 4 bytes), binding key derivation to the target slot — again matching edgeprotecttools. I'm currently working on aligning imgtool --encrypt-xip with edgeprotecttools as well.

@DOAR-Infineon DOAR-Infineon requested a review from d3zd3z April 7, 2026 18:28
@DOAR-Infineon DOAR-Infineon marked this pull request as draft April 8, 2026 09:28
@DOAR-Infineon DOAR-Infineon force-pushed the xip-enc-swap branch 5 times, most recently from fbac3e1 to c0c7885 Compare April 8, 2026 18:35
@DOAR-Infineon DOAR-Infineon marked this pull request as ready for review April 8, 2026 20:58
Signed-off-by: INFINEON\DovhalA <artem.dovhal@infineon.com>

Security properties doc. update
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants