CycloneBOOT 2.6.6
Secure Bootloader for ARM Cortex-M MCU
Loading...
Searching...
No Matches
Getting Started

The quickest way to use CycloneBOOT is to run a demo on a board, then copy its structure into your project. Every demo folder builds on its own and carries a README.md with the steps to build, flash and send an update.

Download

Every release is available from https://www.oryx-embedded.com/download/CycloneBOOT.html in two versions, each with the full source code, the demos and ImageBuilder:

  • CycloneBOOT Open, under the GNU General Public License v2.0 or later
  • CycloneBOOT Eval, under an evaluation licence, to evaluate CycloneBOOT before a commercial licence

A build stops with an error until the licence terms are accepted: define GPL_LICENSE_TERMS_ACCEPTED (Open) or EVAL_LICENSE_TERMS_ACCEPTED (Eval), for example in the os_port_config.h of each project.

Requirements

  • Arm GNU toolchain (arm-none-eabi-gcc), CMake 3.25 or newer, Ninja
  • STM32CubeProgrammer for ST boards, J-Link for the Microchip board
  • Python 3, for the update senders shipped with the demos
  • ImageBuilder, prebuilt in tools/ImageBuilder/bin/ (Linux and Windows); the demo builds call it

1. Run a Demo

Pick the update mode (see Secure Bootloader and Firmware Update), then a demo folder demo/<vendor>/<board>/<mode>/. The NUCLEO-G474RE has a demo for each of the three modes over UART/Y-Modem, through the ST-LINK virtual COM port, with no extra hardware.

Each project builds from its own folder:

cd demo/st/nucleo_g474re/hybrid_app_bl/app_uart_ymodem
cmake --preset Debug
cmake --build --preset Debug

Flash addresses, UART pins and programming commands are in the comment block at the top of each project's src/main.c. The update senders for Y-Modem, TFTP and HTTP are in demo/common/update_protocols/.

2. Build Images with ImageBuilder

ImageBuilder turns the application binary into a CycloneBOOT image: a 64-byte header, the firmware and a check value. It also generates signing keys:

image_builder_linux --keygen --key-type ecdsa --curve p256 --public-key pub.pem --private-key priv.pem

--key-type rsa --key-size 2048|3072|4096 generates an RSA key, --curve p256|p384|p521 selects the ECDSA curve.

The hybrid and standalone demos build two images of the same firmware:

# Initial image, flashed to the application slot
image_builder_linux --input app.bin --output app_bootable.img.bin --firmware-version 1.0.0 \
--runtime-sign-algo ecdsa-sha256 --runtime-sign-key priv.pem --vtor-align
# Update image, sent to the device: encrypted and signed
image_builder_linux --input app.bin --output app_update.img --firmware-version 1.0.0 \
--runtime-sign-algo ecdsa-sha256 --runtime-sign-key priv.pem --vtor-align \
--enc-algo aes-cbc --enc-key-hex <32, 48 or 64 hex digits for AES-128, -192 or -256> \
--sign-algo ecdsa-sha256 --sign-key priv.pem
  • --vtor-align places the firmware 1024 bytes after the start of the slot, so the application is linked at the slot address plus 0x400. It is required with a runtime signature or a runtime hash.
  • --runtime-sign-* embeds the signature the bootloader checks before every start. The bootloader holds the matching public key.
  • --sign-* signs the update image. The application (hybrid) or the bootloader (standalone) holds the matching public key. --integrity-algo or --auth-algo replace it with a hash or an HMAC; one method per image.
  • The dual-bank demos need neither --vtor-align nor --runtime-sign-*: the application is linked at the bank base.

image_builder_linux --help lists every option.

3. Integrate into a Project

Copy the source list, include paths and compile definitions from the cmake/CMakeLists.txt of the demo closest to your MCU and mode. Each project has a boot_config.h that enables the features, and a crypto_config.h for CycloneCRYPTO.

Dual-Bank

The application receives the update, writes it to the other bank and resets; the MCU starts from the new bank. Program the option bytes for dual-bank operation first (see the demo src/main.c).

boot_config.h: UPDATE_DUAL_BANK_SUPPORT, plus the verification and decryption switches the update images need (VERIFY_SIGNATURE_SUPPORT with VERIFY_RSA_SUPPORT or VERIFY_ECDSA_SUPPORT, CIPHER_SUPPORT, IMAGE_INPUT_ENCRYPTED).

updateGetDefaultSettings(&updateSettings);
//Public key and algorithm of the update image signature
updateSettings.imageInCrypto.verifySettings.verifyMethod = VERIFY_METHOD_SIGNATURE;
//...
//One flash memory, one slot per bank (SLOT_CONTENT_APP | SLOT_CONTENT_BACKUP)
updateSettings.memories[0].nbSlots = 2;
//...
updateInit(&updateContext, &updateSettings);
//For each block received by the transport
updateProcess(&updateContext, data, length);
//After the last block: check the image, then swap the banks and reset
updateFinalize(&updateContext);
updateReboot(&updateContext);
cboot_error_t updateInit(UpdateContext *context, UpdateSettings *settings)
Initialize IAP Application context.
Definition update.c:145
cboot_error_t updateReboot(UpdateContext *context)
Reboot the device by triggering a system reset. This reboot can only be performed if firmware validat...
Definition update.c:663
void updateGetDefaultSettings(UpdateSettings *settings)
Initialize settings with default values.
Definition update.c:83
cboot_error_t updateFinalize(UpdateContext *context)
Finalize firmware update. It performs :
Definition update.c:371
cboot_error_t updateProcess(UpdateContext *context, const void *data, size_t length)
Write receive firmware in the unused flash bank.
Definition update.c:289

Hybrid

The application receives the update and stores it in an update slot. On the next reset the bootloader verifies it, decrypts it, installs it over the application, and checks the application before every start.

Bootloader, linked at the flash base:

bootGetDefaultSettings(&bootSettings);
//Public key of the runtime signature (verified boot)
bootSettings.verifySettings.signKey = bootRuntimePublicKey;
//...
//Slot 0: application (SLOT_CONTENT_BINARY); other slots: SLOT_CONTENT_UPDATE | SLOT_CONTENT_BACKUP
bootSettings.memories[0].nbSlots = 3;
//...
bootInit(&bootContext, &bootSettings);
while(bootFsm(&bootContext) == CBOOT_NO_ERROR)
{
}
cboot_error_t bootFsm(BootContext *context)
Bootloader Task routine.
Definition boot.c:233
cboot_error_t bootInit(BootContext *context, BootSettings *settings)
Initialize the first-stage bootloader context from user settings.
Definition boot.c:131
void bootGetDefaultSettings(BootSettings *settings)
Initialize first-stage bootloader settings with default values.
Definition boot.c:55

boot_config.h of the bootloader: BOOT_RUNTIME_SIGNATURE_CHECK_SUPPORT with BOOT_RUNTIME_SIGNATURE_RSA or BOOT_RUNTIME_SIGNATURE_ECDSA for verified boot (or BOOT_RUNTIME_INTEGRITY_CHECK_SUPPORT for a hash only), BOOT_FALLBACK_SUPPORT to return to the previous firmware, BOOT_COUNTER_SUPPORT to fall back after repeated failed starts.

Application, linked at the application slot plus 0x400: the same update calls as in dual-bank mode, with UPDATE_SINGLE_BANK_SUPPORT and the same slot table as the bootloader (slot 0 SLOT_CONTENT_APP). Once the new firmware runs correctly, it confirms itself so that no fallback occurs:

void mailBoxClearBootCounter(void)
Clears the current value of the boot counter in the mailbox.
Definition mailbox.c:343
void mailBoxSetUpdateConfirmed(bool_t state)
Posts UPDATE_REQUESTED message to the mailbox.
Definition mailbox.c:218

An optional first-stage bootloader, in hybrid_app_bl/1st_stage_bl on the STM32H573I-DK and the NUCLEO-L476RG, sits in front of the bootloader so that the bootloader itself can be updated.

Standalone

The bootloader receives the update itself. The application only asks for an update:

//In the application
NVIC_SystemReset();
void mailBoxSetUpdateRequested(bool_t state)
Posts UPDATE_REQUESTED message to the mailbox.
Definition mailbox.c:186

Bootloader, linked at the flash base:

bootGetDefaultSettings(&bootSettings);
//Update protocol: Y-Modem, TFTP or HTTP from demo/common/update_protocols
bootSettings.transport = &ymodemTransport;
//Verified boot key, update image verification and decryption, slots
//...
bootInit(&bootContext, &bootSettings);
while(bootFsm(&bootContext) == CBOOT_NO_ERROR)
{
//Service the transport if it needs it, e.g. netTask()
}

boot_config.h: at least one update trigger (BOOT_TRIGGER_GPIO, BOOT_TRIGGER_MAILBOX, BOOT_TRIGGER_WINDOW, BOOT_TRIGGER_NO_APP, BOOT_TRIGGER_RESET_CAUSE), and optionally BOOT_RECOVERY_SUPPORT with BOOT_FACTORY_SLOT_SUPPORT to restore a factory image. A new protocol is an UpdateTransport (update/update_transport.h): four callbacks, init, poll, read and close.

For execute-in-place, slot 0 points to the memory-mapped address of the external flash (for example 0x90000000) and its flash driver implements flashActivateXiPMode. See demo/st/stm32h750b_discovery/standalone_bl.

4. Configuration Notes

  • The public keys, the algorithms and the cipher key must match on both sides: ImageBuilder signs and encrypts, the bootloader and the application verify and decrypt.
  • ECDSA update images need VERIFY_ECDSA_SUPPORT in the boot_config.h of the project that verifies them. Without it, updateInit() fails.
  • RSA-4096 signature verification needs about 5 KB of heap; ECDSA needs about 2 KB on any curve.
  • Encrypting images written to external memory (IMAGE_OUTPUT_ENCRYPTED) needs a unique initialization vector per image: implement cipherGenerateIv(), for example with the true random number generator of the MCU.
  • Traces are enabled with CBOOT_TRACE_LEVEL and CBOOT_DRIVER_TRACE_LEVEL in boot_config.h, for example TRACE_LEVEL_INFO.
  • A candidate second-stage bootloader is checked for integrity only before the first stage installs it; its signature is checked before it runs. Sign every bootloader update with the key the first stage holds.