rustBoot
by Nihal Pasham
rustBoot is a standalone bootloader, written entirely in Rust
, designed to run on anything from a microcontroller to a system on chip. It can be used to boot into bare-metal firmware or Linux.
Why rustBoot?
rustBoot prioritizes the following above all else.
- keep only the bare-essentials
- secure by default
- low-integration complexity
rustBoot
does the bare minimum needed to securely boot bare-metal firmware (or Linux) i.e. it has a really small trusted computing base
. It is secure by default i.e. it does not boot digitally unsigned firmware
and uses memory-safe implementations
(for crypto and boot-logic) as the default. It also attempts to eliminate the high degree of integration-complexity involved in rolling a production-grade bootloader by adopting a batteries-included
approach.
- For example, we include
flash device drivers for all supported boards
, written in safe Rust.
Why prioritize the above?
Trusted Computing Base:
Open-source bootloaders have a large trusted computing base i.e. they (pretty much) resemble a mini operating system with
- a complete networking stack
- a collection of device drivers and device-tree blob(s)
- integrated debug and command shells
- support for every possible filesystem you can think of.
- and more stuff.
Note: This includes
U-boot
, the de-facto standard in the embedded-systems world.DepthCharge
is an example of a U-Boot hacking toolkit for security researchers and tinkerers, designed to exploit U-boot's large attack surface.
Memory safety:
A large TCB inevitably equates to a large attack surface. The vast majority of them are written in C or some combination of C and Assembly. A quick analysis of CVEs
reported over the last 2 years (in u-boot, bare-box and other open-source ones) show that the bulk of them fall into the memory-safety category.
Note:
addressable attack surface
is much larger, the aboveattack surface
is only compounded when we add boot-time driver vulnerabilities.
Complexity & boot-time:
Custom secure boot implementations can get quite complex and add latency
with
- redundant hierarchical digital signature verification trust chains or
- elaborate parsing of custom header or container formats.
Vendor dependencies:
Vendor-specific or custom chain of trust dependencies make it difficult to port bootloader implementations across boards. This is in-part attributable to non-standards based solutions
.
Goals
Contrary to prevailing wisdom, writing your own secure-bootloader is a complex project. The effort involved in developing/integrating one can be overwhelming. For example, we'll need to address a plethora of accompanying tasks such as key-management, signing infrastructure, code-safety, trust-chains, reliable back-ups etc. before we even get to the actual booting logic
.
rustBoot's purpose is to help simplify the entire process. Its primary goals are
- complies with key requirements of the IETF-SUIT standard i.e.
- one of
SUIT's requirements
- transferring or downloading anupdate
should be delegated to the firmware/OS to avoidsize or computational
limitations. In other words, the bootloader should NOT be required to download and install an update. This removes the need for a networking stack and provides for a drastic reduction in the bootloader's attack surface. - SUIT also does not mandate the use of specific protocols or data link interfaces to transfer
updates
to a device. - rustBoot fully complies with this requirement.
- one of
- reliable updates:
- reliable updates in rustBoot will take the form of
flash swap operations
for microcontroller based systems. We'll use theboot/update based multi-slot partitioning method
to replace currently active firmware with a newly received update and at the same time store a back-up copy of it in a (passive) secondary partition.ram swap operations
for more powerful system-on-chip boards which can boot Linux.
- reliable updates in rustBoot will take the form of
- predictability over performance:
- one of rustBoot's core design objectives is to keep it simple and avoid complexity. So, there will be little to no application of meta or async programming constructs.
Note: We don't actually need the extra performance. rustBoot can hit sub-second
secure boot-times
as we've stripped it down to the bare-essentials. This assumes flash load times are fast enough and a firmware binary-blob size of < 1MB. - zero-dynamic memory allocation:
- to make it highly portable, apart from its modular design, rustBoot relies on a zero dynamic memory allocation architecture i.e. no heap required.
- memory safety & type-state programming:
- the entire bootloader is written in rust's safe-fragment with a limited set of well-defined api(s) for unsafe HW access.
- as a consequence, it makes rustBoot immune to a whole host of memory safety bugs. ex: things like parsing image-headers (i.e. container-formats) in rustBoot is much safer.
- rustBoot takes advantage of rust's powerful type-system to make
invalid boot-states, unrepresentable at compile time
and along with constructs such as sealed states, global singletons, it improves the overall security of the entire code-base.
There is a plan to further add to rustBoot's high levels of assurance by leveraging
formal methods
such as
property-based testing via symbolic execution:
to formally verify rustBoot's parser.deductive verification:
for critical sections of code (ex: swapping contents of boot and update partitions).
Features currently supported:
-
support for
ARM Cortex-M, Cortex-A
micro-architectures -
support for multi-slot partitioning of microcontroller flash memory. This allows us to implement the
boot/update
approach for bare-metalfirmware updates
. -
support for
Aarch64 linux
booting -
elliptic curve cryptography for integrity and authenticity verification using
RustCrypto
crates - a tiny hardware abstraction layer for non-volatile memory (i.e. flash) access.
- anti-rollback protection via version numbering.
- a fully memory safe core-bootloader implementation with safe parsers and firmware-update logic.
- power-interruptible firmware updates along with the assurance of fall-back availability.
-
a
signing utility
to sign bare-metal firmware and fit-image(s), written in pure rust.
Features planned:
- support for external flash devices (ex: SPI flash) and serial/console logging interfaces.
-
support for
ARM TrustZone-M and A
and certifiedsecure hardware elements
- microchip ATECC608a, NXP SE050, STSAFE-100 -
support for a highly secure and efficient
firmware transport
method over end-end mutually authenticated and encrypted channels via ockam-networking-libraries.
Design
rustBoot
aims to offer an OS and micro-architecture agnostic (i.e. highly portable) standards-based secure bootloader that's easy to integrate into existing embedded software projects. Its architecture is designed around a simple idea – a bootloader handles ONLY the bare-essentials and offloads the rest to systems that are better suited for the job.
What does a bootloader actually do?
- it initializes (the bare-minimum) requisite hardware. Ex: cpu-core, flash, gpios, uart, (RAM and hardware secure elements if needed).
- digitally verifies or authenticates firmware.
- boots or passes control over to firmware or an OS i.e.
linux or RTOS or bare-metal
and - if an update is available, it validates and applies the update before performing a re-boot.
In this chapter, we'll talk about rustBoot's design and its core components.
Components of rustBoot
At its core, rustBoot
is comprised of 4 components
- the core bootloader
- a minimal hardware abstraction layer
- fast and safe crypto drivers
- rustBoot firmware interface
The core bootloader
- has a tiny trusted computing base i.e. its less than
32KB in size
when compiled to an executable. - this includes signature-based authentication, reliable firmware updates with rollbacks and protections against downgrades attacks.
A minimal hardware abstraction layer
rustBoot provides abstractions for the following hardware classes i.e. it exposes a tiny API for you to easily integrate the following types of hardware.
- flash memory controllers: NVMC, SPI-flash, EMMC block devices etc.
- TrustZone: Cortex-M or Cortex-A
- serial interfaces: UART(s), GPIO(s)
Note: To minimize integrational complexity and enhance security, we already provide a number of different hardware drivers written in safe-rust. So, you can use
your own drivers using rust-ffi
or use existing ones from the repo.
Fast and safe crypto drivers
hardware secure elements or accelerators:
again, rustBoot offers drivers for crypto hardware or you can use your own.- examples of supported vendor-specific crypto modules include
ATECC608a
.
- examples of supported vendor-specific crypto modules include
software implementations of crypto-libraries:
rustBoot uses theRustCrypto
project as its software crypto provider.- This includes all crates in the rustcrypto project - hashing, signing, verification, encryption etc.
rustBoot firmware interface
- rustBoot complies with a key requirement of the IETF-SUIT standard and does not include a networking stack, instead networking is offloaded to the underlying firmware/OS.
- Firmware updates are downloaded and stored in non-volatile storage.
- In order to trigger the update, rustBoot provides a simple API that can be called from within bare-metal firmware or linux.
Note: In the above context, firmware refers to either linux or bare-metal firmware.
High Level Overview
rustBoot's architecture reflects its focus on simplicity and security
, above everything else.
For a high-level overview, you can think of rustBoot as operating in 2 independent stages.
- Pre-handover stage: post power-on, the BootROM (or some other intermediate-stage bootloader) executes and hands control over to
rustBoot
. This is a stage whererustBoot
has fullexecution control
. - Post-handover stage: firmware has begun executing and has complete
execution control
. Firmware uses a couplerustBoot
dependencies to trigger and confirm updates.
Pre-handover stage:
- rustBoot provides a minimal hardware abstraction layer for a wide range of ARM microcontrollers (STM32, Nordic, Microchip etc.) and microprocessors (rpi4, NXP etc.). The HAL allows peripherals drivers to initialize requisite hardware such as flash memories, UART controllers, GPIO pins etc.
- an optional software-based crypto library in-case you don't need (or use) dedicated crypto hardware.
- rustBoot's core-bootloader houses all of the
actual boot-logic
such as- firmware image
integrity and authenticity verification
via digital signatures - power-interruptible firmware updates along with the assurance of fall-back availability.
FIT-Image and device tree
parsing while booting linux.- multi-slot partitioning of microcontroller flash memory
anti-rollback protection
via version numbering.
- firmware image
Post-handover stage:
- At this stage, control has been handed over to firmware (or linux).
- rustBoot
does not
have a networking stack. The job of downloading and installing an update is offloaded to firmware or linux (drastically reducing the TCB
) - Firmware can trigger and confirm updates by setting the state of the
update
partition via a rustBoot api. This removes the need for a filesystem (again smaller TCB
).- However, not all systems can boot without a file-system.
- If you need one, rustBoot offers a FAT 16/32 implementation, written in safe rust.
- Once an update is triggered, the device is reset (i.e. restarted). rustBoot takes over and attempts to verify the update. If everything checks out, it boots the updated firmware.
Notes:
- rustBoot
can replace U-boot
in a trust-chain i.e. it can easily be integrated into an existing trust-chain, wherever U-boot is used.- As it has a very small hardware abstraction layer, it is highly portable across Cortex-M and Cortex-A architectures.
- Public-key hashes or trust anchors can be stored in secure hardware or embedded in software.
- Hardware drivers for different types of secure-hardware (ex: crypto elements) will be made available via the HAL.
rustBoot Images
rustBoot supports 2 types of firmware image formats, depending on the underlying device. It could either be an
mcu-image:
a simple 256-byte firmware image format for microcontrollers or afit-image:
the flattened-image-tree format for systems capable of booting linux.
MCU image format
rustBoot mcu-images comprise of a 256-byte header
pre-pended to a firmware binary and are deliberately designed to be as simple as possible.
- it does not rely on the use of complex digital certificate formats which keeps the
TCB
small and avoids unnecessary code-complexity
rustBoot image header layout:
The header always starts with a 4-byte magic number, followed by a 4-byte field indicating the size of the firmware image (excluding the header). All header contents are stored in little-endian format.
The 2 (magic and size
) fixed fields are followed by one or more TLV(s) or Type, Length, Value
tags. A TLV has the following layout
- Type: 2 bytes to indicate the
Type
of the tag - Length: 2 bytes to indicate the
length in bytes
of the tag (excluding the type and size bytes). - Value: N bytes of tag content
Padding and End of header bytes:
- An
0xFF
byte in theType
field indicates a padding byte. A 'padding' byte does NOT have a size field, and the next byte is interpreted asType
again. - A 2 byte value of
0x0000
signals the end of the rustBoot header.
Tags:
Each tag represents some information about the firmware. rustBoot
requires the following Tags
for firmware validation:
- The
version
tag provides firmware version number information.- Type:
0x0001
- Length: 4 bytes
- Type:
- The
timestamp
tag provides the timestamp in unix seconds for when therustBoot image
was created.- Type:
0x0002
- Length: 8 bytes
- Type:
- The
auth type
tag identifies the type of the authentication mechanism in use. Ex: which ECC curve are we using and what's the key strength etc.- Type:
0x0030
- Length: 2 bytes
- Type:
- The
sha256 digest
tag contains aSHA2 hash
of the firmware and is used to check firmware integrity.- Type:
0x0003
- Length:
32 bytes
- Type:
- The
firmware signature
tag contains theECC signature
and is used to verify firmware against a known public key.- Type:
0x0020
- Length: 64 bytes
- Type:
Optional tags:
- Pubkey Hint: A
pubkey hint digest
tag can be included in the header.- Type:
0x1000
- Length: 32 bytes
- This tag contains the SHA256 digest of the public key of the corresponding private-key used by the signing tool. The bootloader may use this field to locate the correct public key in case multiple keys are available.
- Type:
MCU defaults:
- By default, a valid rustBoot image is always signed.
- It relies on the 256-byte header for firmware validation.
- It will fail to boot an image
- if it does not possess a
valid rustBoot header
or- if it isn't signed or if it cannot be verified using the included the
authentication-type
.
FIT-image format
rustBoot leverages Uboot's flattened-uImage-tree
format to boot the linux kernel.
The FIT format is essentially an extension of the device-tree
format. FIT allows us to combine multiple binaries such as the kernel, ramdisk, device-tree-blob etc. into a single image.
A typical rustBoot fit-image contains 4 items in the following order
- kernel
- fdt
- initrd
- rbconfig
An example fit-image source file:
It is also referred to as an image-tree
source file or .its
file.
/dts-v1/;
/ {
description = "rustBoot FIT Image";
#address-cells = <1>;
images {
kernel {
description = "Kernel";
data = /incbin/("vmlinuz");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "none";
load = <0x40480000>;
entry = <0x40480000>;
hash {
algo = "sha256";
};
};
fdt {
description = "DTB";
data = /incbin/("unpatched-bcm2711-rpi-4-b.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x43000000>;
entry = <0x43000000>;
hash {
algo = "sha256";
};
};
initrd {
description = "Initrd";
data = /incbin/("initramfs");
type = "ramdisk";
arch = "arm64";
os = "linux";
compression = "none";
hash {
algo = "sha256";
};
};
rbconfig {
description = "rustBoot Config";
data = /incbin/("rbconfig.txt");
type = "rustBoot cmdline config";
arch = "none";
os = "linux";
compression = "none";
hash {
algo = "sha256";
};
};
};
configurations {
default = "bootconfig";
bootconfig {
description = "Boot Config";
kernel = "kernel";
fdt = "fdt";
ramdisk = "initrd";
rbconfig = "rbconfig";
signature@1 {
algo = "sha256,ecdsa256,nistp256";
key-name-hint = "dev";
signed-images = "fdt", "kernel", "ramdisk", "rbconfig";
value = "";
};
};
};
};
The default configuration of an .its
file determines which kernel, initrd, fdt and rbconfig is to be used for booting. In the above example, bootconfig
is our default configuration.
rustBoot's FIT parser will select the corresponding kernel, fdt, initrd and rbconfig associated with
bootconfig
for booting
Building a rustBoot compliant fit-image:
As shown in the example above, a rustBoot compliant fit-image contains 4 items -
kernel
- the linux kernelfdt
- the flattened device tree or device tree blobramdisk
- a root filesystem that is embedded into the kernel and loaded at an early stage of the boot process. It is the successor of initrd. It can do things the kernel can't easily do by itself during the boot process. For example: customize the boot process (e.g., print a welcome message)rbconfig
- this is rustBoot's kernel configuration. A simpletxt
file to add kernel command-line arguments.
You can retrieve the first 3 (i.e. kernel, fdt, ramdisk) from a pre-built OS image:
- Maintainers of a linux distribution provide pre-built OS images. These images usually contain several partitions such as -
boot:
contains the bootloader, kernel, dtb, ramdisk and other stuffsystem:
contains the root file systemothers:
may contain other partitions for things such as storage etc.
- simply download an OS image or a pre-built linux distribution from the maintainers website.
- in this example, I'll be using the
apertis
distribution.
- in this example, I'll be using the
- it’s usually a compressed (zImage) format, decompress it using a tool like unarchiver to get a disk image.
- use
partx --show
to list all partitions
$ partx --show __linux_image_filepath__
NR START END SECTORS SIZE NAME UUID
1 8192 532479 524288 256M 9730496b-01
2 532480 3661823 3129344 1.5G 9730496b-02
In the above case, the first partition with a size of 256MB contains the boot-files. It's usually named boot
. We can calculate the offset to the boot
volume/partition with the following command
$ bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
> 8192 * 512
> 4194304
> quit
512
is the sector-size. We multiply sector-size with the sector offset to get the actual starting (byte) location ofboot
.
mount the partition as an ext4
file-system (or fat
file-system, whichever)
$ sudo mkdir /mnt/other
$ sudo mount -v -o offset=4194304 -t ext4 /_path_to_file_image/__filename__.img /mnt/other
mount: /dev/loop0 mounted on /mnt/other.
Check mounted image
$ ls /mnt/other
Copy the dtb
, ramdisk
and vmlinuz
image (i.e. kernel) from the mounted partition to a new folder. You can give it any name you want. I'll use pkg
for this example.
vmlinuz is a PE (portable executable) i.e. we can jump to it and it will in-turn jump to the kernel's entry point.
rbconfig:
Lastly, create a file named rbconfig.txt
in the pkg folder. This file will be used by rustBoot to pass command-line parameters to the linux kernel.
Here's an example of the rbconfig.txt
file -
bootargs="root=UUID=64bc182a-ca9d-4aa1-8936-d2919863c22a rootwait ro plymouth.ignore-serial-consoles fsck.mode=auto fsck.repair=yes cma=128M"
When you have added all 4 items to the pkg
folder, you can build a fit-image by running the following commands.
On a mac:
brew install u-boot-tools
On a linux machine:
sudo apt install u-boot-tools
and then run
mkimage -f rpi4-apertis.its rpi4-test-apertis.itb
- the input to
mkimage
is an.its
file.- and
.itb
filename we've specified is the name given to the generated fit-image (that's stored in thepkg
folder).- you can copy the contents of the example
fit-image
file above into a new.its
file namedrpi4-apertis.its
and add it to the pkg folder.
Output:
rpi4-apertis.its:65.37-70.6: Warning (unit_address_vs_reg): /configurations/bootconfig/signature@1: node has a unit name, but no reg or ranges property
Image contains unit addresses @, this will break signing
FIT description: rustBoot FIT Image
Created: Sat Jun 4 13:18:45 2022
Image 0 (kernel)
Description: Kernel
Created: Sat Jun 4 13:18:45 2022
Type: Kernel Image
Compression: uncompressed
Data Size: 29272576 Bytes = 28586.50 KiB = 27.92 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x40480000
Entry Point: 0x40480000
Hash algo: sha256
Hash value: 97dcbff24ad0a60514e31a7a6b34a765681fea81f8dd11e4644f3ec81e1044fb
Image 1 (fdt)
Description: DTB
Created: Sat Jun 4 13:18:45 2022
Type: Flat Device Tree
Compression: uncompressed
Data Size: 25713 Bytes = 25.11 KiB = 0.02 MiB
Architecture: AArch64
Load Address: 0x43000000
Hash algo: sha256
Hash value: 3572783be74511b710ed7fca9b3131e97fd8073c620a94269a4e4ce79d331540
Image 2 (initrd)
Description: Initrd
Created: Sat Jun 4 13:18:45 2022
Type: RAMDisk Image
Compression: uncompressed
Data Size: 32901194 Bytes = 32130.07 KiB = 31.38 MiB
Architecture: AArch64
OS: Linux
Load Address: unavailable
Entry Point: unavailable
Hash algo: sha256
Hash value: f1290587e2155e3a5c2c870fa1d6e3e2252fb0dddf74992113d2ed86bc67f37c
Image 3 (rbconfig)
Description: rustBoot Config
Created: Sat Jun 4 13:18:45 2022
Type: Unknown Image
Compression: uncompressed
Data Size: 141 Bytes = 0.14 KiB = 0.00 MiB
Hash algo: sha256
Hash value: b16d058c4f09abdb8da98561f3a15d06ff271c38a4655c2be11dec23567fd519
Default Configuration: 'bootconfig'
Configuration 0 (bootconfig)
Description: Boot Config
Kernel: kernel
Init Ramdisk: initrd
FDT: fdt
Sign algo: sha256,ecdsa256,nistp256:dev
Sign value: 00
Timestamp: unavailable
This .itb
file is our fit-image. It does not contain a signature yet i.e. it is not signed - notice the sign-value
field is empty.
Signing fit-images
rustBoot fit-images are signed with ecdsa256
. The signature includes the kernel, fdt, initrd and rbconfig.
Signing a rustBoot fit-image involves 2 steps:
- Building a fit-image: As explained in preceding section, FIT images can be built using
mkimage
- a command-line utility from theuboot-tools
package i.e. you can pass an.its
file to the mkimage tool and mkimage will produce an.itb
blob or a image-tree blob. - signing the fit-image: once you've built your fit-image, you can pass the it along with a signing key to rustBoot's
rbsigner
utility to generate a signed fit-image.
FIT-image defaults:
- By default, valid rustBoot images are always signed.
- It will fail to boot an image
- if the image fails fit-validation i.e. if its not a properly formatted fit-image or if the fit-parser cant find the specified default config or its components.
- if it isn't signed or if it cannot be verified using the specified algo.
- rustBoot's fit parser currently supports the following architectures
Aarch64
rustBoot Partitions
rustBoot
has 2 distinct partitioning schemes, depending on the type of the underlying system.
- micro-controller partitions: uses the concept of
swappable flash partitions
to update micro-controller firmware.This usually means bare-metal firmware but it is also applicable to
RTOS(s)
. - linux system partitions: uses a single fat32 partition to host the
rustBoot-bootloader
and (boot/update) fit-images. This method uses arustBoot-state
file to determine which image is to be booted.
Micro-controller Partitions:
Note:
BOOT
,UPDATE
andSWAP
partitions need NOT be consecutively laid out in flash memory. The above diagram only serves as a visual aid.
rustBoot requires an mcu's
non-volatile memory (or flash storage
) to be divided into (at-least) 4 non-overlapping memory regions (i.e. partitions).
rustBoot
: contains the bootloader. This usually starts ataddress 0x0
in flash-memory.BOOT:
contains boot firmware.rustBoot
always boots from this partition address.UPDATE:
contains update firmware i.e. downloaded update firmware is placed in this partition.SWAP:
is an empty partition that is used to swap contents ofBOOT
andUPDATE
, one sector at a time.
All 3 partition boundaries must be aligned to a physical sector as rustBoot
erases all flash sectors prior to storing a new firmware image, and swaps the contents of the two partitions, one sector at a time.
To ensure that a partition's sector alignments are maintained, the following points must be considered:
BOOT and UPDATE
partition must be of the same size.SWAP
partitionmust be larger than or equal to the largest sector
in eitherBOOT
orUPDATE
partition.
MCU flash memory is partitioned as follows:
- rustBoot partition starts at
address 0x0
in flash memory. It should be at least 32KB in size. BOOT
partition starts at a pre-defined address -BOOT_PARTITION_ADDRESS
UPDATE
partition starts at a pre-defined address -UPDATE_PARTITION_ADDRESS
- both partitions must be of the same size, defined by
PARTITION_SIZE
- both partitions must be of the same size, defined by
SWAP
partition starts at a predefined address -SWAP_PARTITION_ADDRESS
- swap-space size is defined by
SECTOR_SIZE
and must be larger than the largest sector in eitherBOOT
orUPDATE
partition.
- swap-space size is defined by
BOOT, UPDATE, SWAP addresses and SECTOR_SIZE, PARTITION_SIZE values can be set via source files - constants.rs
.
MCU defaults:
- By default, public keys used for firmware validation are embedded in
rustBoot-firmware
during a factory image-burn. However, rustBoot also supports the option to retrieve them from secure-hardware (ex: crypto-elements).- The
BOOT
partition is the only partition from which we can boot a firmware image. The firmware image must be linked so that its entry-point is at address256 + BOOT_PARTITION_ADDRESS
.BOOT
firmware is responsible for downloading a new firmware image via a secure channel and installing it in theUPDATE
partition.- To trigger an update, the
BOOT
firmware updates thestatus byte
of theUPDATE
partition and performs a reboot. This will allow the bootloader toswap the contents
ofBOOT
partition with that of theUPDATE
partition.
Linux system partitions:
To boot into a linux system, rustBoot includes support for the fat32 file-system.
Boot-storage media must contain a fat32 partition
- of at least 150 MiB to accommodate the bootloader, boot + update fit-images and other vendor-specific boot files and
- to add rustBoot support for your board, you can either implement the
BlockDevice
trait for your board's boot-storage mediacontroller
or simply use an existing implementation from the repo.
Note: rustBoot comes with batteries-included. It provides
rusty
implementations for basic peripherals such as flash, uart, crypto, gpio (out of the box) along with the necessary arch-specific initialization routines.
- for example: the rustBoot implementation for
rpi4
includes bare-metal drivers for the on-board emmc controller, gpio and uart peripherals.
Secure Boot & Update
rustBoot supports the following boot and update
schemes, depending on the underlying device-type.
mcu updates:
to boot and update bare-metal mcu-firmware using a simplepartitioning scheme
linux-system updates:
to boot and update a linux system.
mcu updates:
rustBoot implements a small state machine leveraging rust's type-system to enforce compile-time state transition checks. Additionally, it uses the sealed-trait
pattern to declare and seal (all possible) valid states
and partitions
.
The image below captures rustBoot's state transitions.
Upon supplying power to a system, execution usually starts in an mcu's BootROM. The BootROM is a piece of immutable code, also referred to as the root of trust
. BootROM(s) are programmed into an mcu's ROM in the factory and may support secure-boot i.e. the ability to cryptographically verify (and pass control to) a 2nd stage executable/binary.
In our case, rustBoot
is the 2nd stage. It checks the status of BOOT
and UPDATE
partitions and drives the state machine forward, as shown in the image below.
Note:
- there may be many intermediate stages/executables between
BootROM
andrustBoot
.- rustBoot assumes that the target device contains a
root of trust
with support for digitally verifying cryptographic signatures.- rustBoot will check for firmware integrity and authenticity at appropriate stages during boot/update/rollback.
- booting, updating and rolling back are determined by the the state machine.
partition status:
rustBoot partitions
use a status byte to track the status of firmware in each partition. The status byte is a 1-byte field stored at the end of each partition.
Possible states are:
STATE_NEW (0xFF):
The image was never staged for boot, or triggered for an update. If an image is present, no flags are active.STATE_UPDATING (0x70):
Only valid in theUPDATE
partition. The image is marked for update and should replace the current image inBOOT
.STATE_TESTING (0x10):
Only valid in theBOOT
partition. The image has been just updated, and never completed its boot. If present after reboot, it means that the updated image failed to boot, despite being correctly verified. This particular situation triggers a rollback.STATE_SUCCESS (0x00):
Only valid in theBOOT
partition. The image stored inBOOT
has been successfully staged at least once, and the update is now complete.
partition sector flags:
- When an update is triggered, the contents of
UPDATE
andBOOT
are swapped one sector at a time.SWAP
is used to temporarily hold a sector during the swap. This ensures that we always store a backup of the original firmware. - rustBoot keeps track of the state of each sector (during a swap), using 4 bits per sector at the end of the
UPDATE
partition. - Each
sector-swap
operation corresponds to a different flag-value for a sector in the sector flags area. This means if a swap operation is interrupted, it can be resumed upon reboot.
linux-system updates:
linux-system updates rely on an updt.txt
file. updt.txt
as the name suggests is an update-configuration file containing active and passive image components.
- active component: refers to the rustBoot compliant fit-image that's currently in-use. This also implies that the active image has been successfully verified and booted at least once.
- passive component: refers to the rustBoot compliant fit-image that's been marked
ready-for-update
i.e. the active image has downloaded an update and set the corresponding passive component field inupdt.txt
. Passive component(s) have an additional field calledupdate_status
and can be assigned one of the following values.- updating: The image is marked for update and should replace the current
active-image
. - testing: The image has been just updated, and never completed its boot. If present after reboot, it means that the updated image failed to boot, despite being correctly verified. This particular situation triggers a rollback.
- success: The update has been successfully staged at least once, and the update is now complete.
- updating: The image is marked for update and should replace the current
Notes:
- A valid
updt.txt
file must (always) contain an active and a passive component.- The active component must contain the fields -
image_name
andimage_version
.- The passive component may contain optional fields such as
image_name
,image_version
andupdate_status
- Example: for what constitutes a
valid config file
, please see theupdt.txt
in the rpi4 example.
Here's how it works:
- rustBoot loads the
updt.txt
file from the root directory, parses it and retrieves the active and passive components. - it then performs the following checks, assuming an update is available
- does the active component include valid
image_name
andimage_version
fields? - has the passive component's
ready_for_update
field been set to true? - if yes, have the
passive_name
,passive_version
fields been set and haspassive_status
been set to eitherupdating
orsuccess
? i.e. update has been marked as ready (on the next reboot). - is the passive_version field greater than the active? A valid update must have a version greater than the active version.
- does the active component include valid
- if all the above checks pass, we attempt to load the update (fit-image) and if any one of above checks fail, we simply load the currently active-image.
- lastly, rustBoot verifies the loaded fit-image's cryptographic digital signature and additionally checks whether the
version-number
fromupdt.txt
matches the fit-image's timestamp (to prevent rollback or downgrade attacks). The fit's version number is retrieved from rustBoot'supdt.txt
file.- if a version mismatch arises in the last step, rustBoot attempts to rollback to the currently active-image
A note on valid updt.txt
configs. They must contain
- an image name that ends with
.itb
as its file extension - a valid unix epoch timestamp as its version number and it must start with the
ts_
prefix. - config keys that are recognized by rustBoot -
[active]
,[passive]
- field names that are recognized by rustBoot. -
image_name
,image_version
,ready_for_update_flag
,update_status
Signing Utilities
As rustBoot supports 2 types of firmware image formats, depending on the underlying device i.e. either an
mcu-image:
a simple 256-byte firmware image format for microcontrollers or afit-image:
the flattened-image-tree format for systems capable of booting linux.
rustBoot rbsigner
utility can produce 2 different types signed images.
Signing mcu-images:
To sign a mcu-image, rustBoot's image signing utility takes 3 inputs
- an unsigned mcu-image.
- a raw signing-key or ecdsa private key.
- the ecdsa curve-type - (nistp256 only for now).
There are 2 ways to sign a mcu-image
- First we build the image and then sign it using the following commands.
cargo [board-name] build pkgs-for
cargo [board-name] sign pkgs-for [boot-version] [update-version]
Note : Here stm32f411 is used as example board for signing.
output:
command : cargo stm32f411 build pkgs-for
yashwanthsingh@Yashwanths-MBP rustBoot % cargo stm32f411 build pkgs-for
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/xtask stm32f411 build pkgs-for`
$ cargo build --release
warning: unused config key `build.runner` in `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/firmware/stm32f411/boot_fw_blinky_green/.cargo/config.toml`
Finished release [optimized] target(s) in 0.08s
$ cargo build --release
warning: unused config key `build.runner` in `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/firmware/stm32f411/updt_fw_blinky_red/.cargo/config.toml`
Finished release [optimized] target(s) in 0.08s
$ cargo build --release
Finished release [optimized] target(s) in 0.07s
output:
command : cargo stm32f411 sign pkgs-for 1234 1235
yashwanthsingh@Yashwanths-MBP rustBoot % cargo stm32f411 sign pkgs-for 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/xtask stm32f411 sign pkgs-for 1234 1235`
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_bootfw -O binary stm32f411_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_updtfw -O binary stm32f411_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f411_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f411_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: stm32f411_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f411_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1908 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f411_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f411_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: stm32f411_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f411_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1996 bytes.
- Single command to build ,sign and flash.
cargo [board-name] build-sign-flash rustBoot [boot-ver] [updt-ver]
output :
command : cargo stm32f411 build-sign-flash rustBoot 1234 1235
yashwanthsingh@Yashwanths-MacBook-Pro rustBoot % cargo stm32f411 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/xtask stm32f411 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
warning: unused config key `build.runner` in `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/firmware/stm32f411/boot_fw_blinky_green/.cargo/config.toml`
Finished release [optimized] target(s) in 0.10s
$ cargo build --release
warning: unused config key `build.runner` in `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/firmware/stm32f411/updt_fw_blinky_red/.cargo/config.toml`
Finished release [optimized] target(s) in 0.10s
$ cargo build --release
Finished release [optimized] target(s) in 0.11s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_bootfw -O binary stm32f411_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_updtfw -O binary stm32f411_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f411_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f411_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: stm32f411_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f411_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1908 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f411_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f411_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: stm32f411_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f411_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1996 bytes.
$ probe-rs-cli erase --chip stm32f411vetx
$ probe-rs-cli download --format Bin --base-address 0x8020000 --chip stm32f411vetx stm32f411_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:01] [############################] 128.00KiB/128.00KiB @ 65.02KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 2.00KiB/ 2.00KiB @ 677B/s (eta 0s )
Finished in 2.057s
$ probe-rs-cli download --format Bin --base-address 0x8040000 --chip stm32f411vetx stm32f411_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:01] [############################] 128.00KiB/128.00KiB @ 65.15KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 2.00KiB/ 2.00KiB @ 679B/s (eta 0s )
Finished in 2.052s
$ cargo flash --chip stm32f411vetx --release
Finished release [optimized] target(s) in 0.08s
Flashing /Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/target/thumbv7em-none-eabihf/release/stm32f411
Erasing sectors ✔ [00:00:01] [##############################] 48.00KiB/48.00KiB @ 40.79KiB/s (eta 0s )
Programming pages ✔ [00:00:01] [##############################] 43.00KiB/43.00KiB @ 17.31KiB/s (eta 0s )
Finished in 2.267s
yashwanthsingh@Yashwanths-MacBook-Pro rustBoot %
Signing fit-images:
To sign a fit-image, rustBoot's image signing utility takes 3 inputs
- an unsigned fit-image in the above format
- a raw signing-key or ecdsa private key
- the ecdsa curve-type - (nistp256 only for now).
Simply run the following command from root directory of the rustBoot project.
cargo run ../boards/bootloaders/rpi4/apertis/rpi4-test-apertis.itb ../boards/rbSigner/keygen/ecc256.der nistp256
In the above example:
../boards/bootloaders/rpi4/apertis/rpi4-test-apertis.itb
is the path to my fit-image../boards/rbSigner/keygen/ecc256.der
is the path to mytest
signing-keynistp256
is the type ecdsa curve I'd like to use. Its the only one supported for now.
Output:
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/target/debug/rbsigner ../boards/bootloaders/rpi4/apertis/rpi4-test-apertis.itb ../boards/rbSigner/keygen/ecc256.der nistp256`
signature: ecdsa::Signature<NistP256>([64, 147, 93, 99, 241, 5, 118, 167, 156, 150, 203, 234, 74, 207, 182, 243, 129, 143, 38, 2, 107, 85, 114, 145, 178, 163, 33, 153, 2, 100, 0, 114, 135, 18, 174, 183, 194, 110, 24, 186, 33, 36, 39, 105, 116, 74, 8, 118, 171, 237, 30, 108, 64, 205, 206, 14, 110, 226, 43, 143, 180, 193, 19, 33])
bytes_written: 62202019
In the above example, the signed fit-image
will be stored at the following path - ../boards/bootloaders/rpi4/apertis/signed-rpi4-apertis.itb
Build & Flash
This section will detail the steps involved in building and flashing rustBoot
onto a specific board. More precisely, it will cover the following topics.
- Partitioning:
- rustBoot offers 2 different partitioning schemes, depending on the target device
- Building:
- a rustBoot build usually involves
- compiling firmware i.e. boot and update firmware.
- signing the compiled firmware. We have 2 different signing schemes, depending on the target device.
- compiling rustBoot i.e. the bootloader
- a rustBoot build usually involves
- Programming:
- After building the required artefacts, the next step is to program the board's non-volatile storage memory.
- Again, depending on the target device, we employ different loading/programming strategies.
- mcu(s): typically, rustBoot will use the
probe-run
utility for programming (and debugging). - sbc(s): this will depending the on type of single-board computer. For example: the raspberry-pi uses a sd card for storage and booting.
- mcu(s): typically, rustBoot will use the
- Verifying:
- To verify that everything works as expected, rustBoot outputs boot-logs.
- mcu(s): we use a combination of boot-logs and blinking-leds to verify that
secure boot and update
works as expected. For specifics, please refer to theusage
page for the board. - sbc(s): rustBoot simply outputs logs to a UART-terminal. For specifics, please refer to
usage
page for the board.
- mcu(s): we use a combination of boot-logs and blinking-leds to verify that
- Among other things, rustBoot logs will indicate
image-authentication
status.
- To verify that everything works as expected, rustBoot outputs boot-logs.
Note: drivers for peripherals such as flash-memory, uart, gpio etc. are included for each board.
nrf52840
The nrf52840
example uses a maker-diary board. It has a custom led configuration.
Note:
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
Partitioning:
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the nrf52840
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "nrf52840")] pub const SECTOR_SIZE: usize = 0x1000; #[cfg(feature = "nrf52840")] pub const PARTITION_SIZE: usize = 0x28000; #[cfg(feature = "nrf52840")] pub const BOOT_PARTITION_ADDRESS: usize = 0x2f000; #[cfg(feature = "nrf52840")] pub const SWAP_PARTITION_ADDRESS: usize = 0x57000; #[cfg(feature = "nrf52840")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x58000; }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address0x0
.BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the nrf52840
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo nrf52840 build-sign-flash rustBoot
Note:
- The
updt-ver
number must be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command line output that should be produced.
PS C:\Users\Nil\devspace\rust\projects\rb> cargo nrf52840 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/xtask nrf52840 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Compiling rand_core v0.6.3
Compiling subtle v2.4.1
Compiling nb v1.0.0
Compiling cfg-if v1.0.0
...
...
Compiling rustBoot-hal v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/hal)
Compiling rustBoot-update v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/update)
Compiling nrf52840_bootfw v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/firmware/nrf52840/boot_fw_blinky_green)
Finished release [optimized] target(s) in 10.60s
$ cargo build --release
Compiling nrf52840_updtfw v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/firmware/nrf52840/updt_fw_blinky_red)
Finished release [optimized] target(s) in 0.43s
$ cargo build --release
Compiling rand_core v0.6.3
Compiling subtle v2.4.1
Compiling cfg-if v1.0.0
Compiling nb v1.0.0
...
...
Compiling rustBoot-hal v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/hal)
Compiling rustBoot-update v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/update)
Compiling nrf52840 v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/bootloaders/nrf52840)
Finished release [optimized] target(s) in 10.96s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/nrf52840_bootfw -O binary nrf52840_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/nrf52840_updtfw -O binary nrf52840_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/nrf52840_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/nrf52840_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Image type: mcu-image
Curve type: nistp256
Input image: nrf52840_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: nrf52840_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1696 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/nrf52840_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/nrf52840_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Image type: mcu-image
Curve type: nistp256
Input image: nrf52840_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: nrf52840_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1724 bytes.
$ probe-rs-cli erase --chip nRF52840_xxAA
$ probe-rs-cli download --format Bin --base-address 0x2f000 --chip nRF52840_xxAA nrf52840_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:00] [##########] 4.00KiB/ 4.00KiB @ 15.97KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##########] 4.00KiB/ 4.00KiB @ 6.77KiB/s (eta 0s )
Finished in 0.73s
$ probe-rs-cli download --format Bin --base-address 0x58000 --chip nRF52840_xxAA nrf52840_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:00] [##########] 4.00KiB/ 4.00KiB @ 16.34KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##########] 4.00KiB/ 4.00KiB @ 6.86KiB/s (eta 0s )
Finished in 0.713s
$ cargo flash --chip nRF52840_xxAA --release
Finished release [optimized] target(s) in 0.06s
Flashing /Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/target/thumbv7em-none-eabihf/release/nrf52840
Erasing sectors ✔ [00:00:01] [##########] 44.00KiB/44.00KiB @ 25.20KiB/s (eta 0s )
Programming pages ✔ [00:00:03] [##########] 44.00KiB/44.00KiB @ 5.62KiB/s (eta 0s )
Finished in 4.808s
Verifying:
blinky leds
are used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
validates
the firmware image stored in theBOOT
partition i.e.- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
green-led
for a few seconds,- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
swaps
the contents of theBOOT
and theUPDATE
partitions- marks the new firmware in the
BOOT
partition as in stateSTATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- it will attempt to blink a
red-led
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
red-led
.
- it will attempt to blink a
rp2040
The rp2040
example uses a Raspberry Pi Pico Board. The board has 1 user LED which will be blinking at different frequencies depending on whether we're executing boot-firmware or update-firmware.
Raspberry Pi Pico's Boot Sequence
Raspberry Pi Pico board has an external QSPI Flash chip W25Q080
with support for cached execute-in-place.
It has a first stage bootloader baked into ROM. This is always the very first thing that runs when the RP2040 starts up.
The built-in bootloader has a fixed sequence, it checks if the BOOTSEL button is pressed, and if so, enters the USB mass storage mode for code upload.
If the BOOTSEL button is not pressed, and it looks like the flash contains a valid program, then it starts executing the “program” from flash.
In flash, first 256 bytes are second-stage bootloader which are required to configure external QSPI flash for high speed and efficient code access.
Note: For this example we are using two Pico boards, one board will be used as debug probe and other will be a target board. You can learn more about this here
Partitioning
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the Pico
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "rp2040")] pub const SECTOR_SIZE: usize = 0x1000; #[cfg(feature = "rp2040")] pub const PARTITION_SIZE: usize = 0x20000; #[cfg(feature = "rp2040")] pub const BOOT_PARTITION_ADDRESS: usize = 0x10020000; #[cfg(feature = "rp2040")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x10040000; #[cfg(feature = "rp2040")] pub const SWAP_PARTITION_ADDRESS: usize = 0x10060000; }
Note: Choose the number of sectors based on your boot and update firmware sizes.
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address 0x10000100
.
BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the Pico
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f334 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command and its output that should be produced.
cargo rp2040 build-sign-flash rustBoot 1234 1235
C:\Users\ABHISHEK\Documents\rustBoot> cargo rp2040 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target\debug\xtask.exe rp2040 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Finished release [optimized] target(s) in 0.21s
$ cargo build --release
Finished release [optimized] target(s) in 0.16s
$ cargo build --release
Finished release [optimized] target(s) in 0.26s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv6m-none-eabi/release/rp2040_bootfw -O binary rp2040_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv6m-none-eabi/release/rp2040_updtfw -O binary rp2040_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/rp2040_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `C:\Users\ABHISHEK\Documents\rustBoot\target\debug\rbsigner.exe mcu-image ../boards/sign_images/signed_images/rp2040_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: rp2040_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: rp2040_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1644 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/rp2040_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `C:\Users\ABHISHEK\Documents\rustBoot\target\debug\rbsigner.exe mcu-image ../boards/sign_images/signed_images/rp2040_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: rp2040_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: rp2040_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1612 bytes.
$ probe-rs-cli download --format Bin --base-address 0x10020000 --chip RP2040 rp2040_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 23.50KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 10.21KiB/s (eta 0s )
Finished in 0.494s
$ probe-rs-cli download --format Bin --base-address 0x10040000 --chip RP2040 rp2040_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 23.77KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [################################################################] 4.00KiB/ 4.00KiB @ 10.27KiB/s (eta 0s )
Finished in 0.495s
$ cargo flash --chip RP2040 --release
Finished release [optimized] target(s) in 0.18s
Flashing C:\Users\ABHISHEK\Documents\rustBoot\boards\target\thumbv6m-none-eabi\release\rp2040
Erasing sectors ✔ [00:00:00] [################################################################] 48.00KiB/48.00KiB @ 45.42KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [################################################################] 48.00KiB/48.00KiB @ 9.73KiB/s (eta 0s )
Finished in 3.174s
Verifying:
user led
is used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
user led
for a few seconds, at an interval of1 second
five times.- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
user led
at an interval of0.125 second
continuously.
- and set a
raspberry-pi 4
Table of contents:
-
raspberry-pi 4 boot-sequence:
🥧 -
rustBoot execution-sequence:
🦀 -
Booting from an SD card:
💾 -
Compiling rustBoot:
⌛ -
Adding a root file system:
💼 -
UART communication:
🚌 -
Power-on and test:
🧪
🥧 raspberry-pi 4 boot-sequence:
rpi4 has an unconventional boot process
- Upon initial power-on, the
bcm2711
SoC (CPU is offline but GPU is powered on) executes from the onboard bootROM i.e.1st stage bootloader
Note: the GPU contains a tiny risc core that executes the
bootROM
.
bootROM
checks an onboard SPI-EEPROM for a 2nd stage bootloader- This
2nd stage bootloader
is loaded into the GPU's L2 cache for the GPU to execute.- It initializes system clocks and SDRAM
- loads GPU firmware (start4.elf) into RAM
- the
GPU firmware
performs RAM allocations i.e.- RAM is shared between CPU and GPU
- enables BCM7211 CPU.
- loads
rustBoot-bootloader
from the SD card into CPU-assigned RAM and passes control of the ARMv8 core to rustBoot
Note: At this point, rustBoot has complete control over the CPU.
🦀 rustBoot execution-sequence:
- By default, rpi4 will always start executing in EL2. Since we are booting a traditional Kernel (i.e. linux), we have to transition into the more appropriate EL1.
Note: EL1 and EL2 are abbreviations for ARMv8-A exception levels
So, rustBoot checks the following
- is the core executing in
EL2
? - are we executing on the
boot-core
i.e. is it core 0? - if the answer to any of the above questions is
no
, then we park the core i.e. go into an infinite wait state. - If yes, then we initialize
DRAM
,zero out bss
, transition to EL1 and finally jump to an early initialization routine called kernel_init. kernel_init
is an early initialization routine. It takes care of the following -- enables exception handling
- enables the MMU along with instruction + data caching
- initializes a small set of peripheral drivers i.e EMMC controller, UART, GPIO
- and passes control to the core bootloader routine called kernel_main.
kernel_main
takes care of loading, verifying and booting fit-images.- it uses rustBoot's (fat32) file-system to retrieve the first partition (or volume).
Note: rustBoot does not support GUID Partition Table disks.
- If the first volume is a
valid fat32
partition, it loads the supplied fit-image into RAM and attempts to verify its signature using the ecdsa algorithm. - If the fit-image is authentic i.e. the signature check passes, it relocates the following components to an appropriate location in memory.
linux kernel
fdt or dtb
ramdisk or initramfs
- additionally, it will patch the dtb with any supplied (boot-time) kernel command-line arguments.
Note: kernel cmd-line arguments are set at package-build time i.e. when building the fit-image and cannot be interactively set at run time.
- Finally, it disables the MMU and boots the linux kernel by jumping to its (relocated) entry point.
💾 Booting from an SD card:
Raspberry Pi computers use a micro SD card to store a bootable image.
SD card preparation:
- make 2 partitions
- the first one must be a
fat32
partition namedfirmware
. Thefat
partition needs to be (at-least) 150MB(s). But to keep things simple, you can use a 256MB partition. - the second one can be
ext2/3/4
partition. This is used to host the root file system.
- the first one must be a
FAT32 partition contents:
- Create a file named
config.txt
with the following contents in the fat partition.
arm_64bit=1
enable_uart=1
init_uart_clock=4000000
kernel=rustBoot.bin
- Copy the following files from the Raspberry Pi firmware repo onto the fat partition:
Note: Should it not work on your rpi4, try renaming start4.elf to start.elf (without the 4) on the SD card.
⌛ Compiling rustBoot:
You must have rust installed. You can install rust by following the installation instructions here. After installing rust, you'll need to switch to rust's nightly
toolchain and add the aarch64
compilation-target. This will allow us to compile code for the rpi4
rustup default nightly
rustup target add aarch64-unknown-none-softfloat
Additionally you'll need a few binary utilities to extract the bootloader as a .bin
file. They can be installed via the following commands.
cargo install cargo-binutils
rustup component add llvm-tools-preview
To verify that you have the pre-requisites installed correctly, run the following command
rustup show
In my case, rustup show
returns the following output.
rustup show
Default host: aarch64-apple-darwin
rustup home: /Users/nihal.pasham/.rustup
installed toolchains
--------------------
stable-aarch64-apple-darwin
nightly-aarch64-apple-darwin (default)
installed targets for active toolchain
--------------------------------------
aarch64-apple-darwin
aarch64-unknown-none-softfloat
thumbv7em-none-eabihf
active toolchain
----------------
nightly-aarch64-apple-darwin (default)
rustc 1.63.0-nightly (ee160f2f5 2022-05-23)
You should be able to see aarch64-unknown-none-softfloat
as one of the installed targets. To compile the rustBoot-bootloader, simply clone the rustBoot repo
and run the following command
cargo rpi4 build rustBoot-only
The above command should output the following (output will be longer when compiling for the first time) logs, produce an executable bootloader named rustBoot.bin
and store it in the following path ./rustBoot/boards/bootloaders/rpi4
Output:
Compiling xtask v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/xtask)
Finished dev [unoptimized + debuginfo] target(s) in 0.64s
Running `target/debug/xtask rpi4 build rustBoot-only`
$ cargo build --release
Compiling rustBoot v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/rustBoot)
Compiling rustBoot-hal v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/hal)
Compiling rpi4 v0.1.0 (/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/boards/bootloaders/rpi4)
Finished release [optimized] target(s) in 4.77s
$ rust-objcopy --strip-all -O binary ../../target/aarch64-unknown-none-softfloat/release/kernel rustBoot.bin
If you run into any linker issues during the compilation process in a windows environment, please ensure you have C++ build tools installed on your machine. You can download and install the visual studio's build tools from the microsoft website.
After compiling rustBoot, copy rustBoot.bin
file onto the sd card's fat32 partition.
The last step in preparing a bootable SD card is to copy the rustBoot fit-image that you'd like to boot onto the sd card's fat32 partition.
Note:
- to build a rustBoot fit-image, you can follow these instructions and
- to sign a fit-image, you can follow these instructions.
Finally, once you've added the above mentioned files to your sd card. The fat32 partition should contain the following files:
- config.txt
- fixup4.dat
- start4.elf
- bcm2711-rpi-4-b.dtb
- rustBoot.bin
- signed-example-image.itb
💼 Adding a root file system:
There are many ways to add a root file-system to the second ext2/3/4 partition. One way is to copy a root file system to an empty ext2/3/4 drive:
- Maintainers of a linux distribution provide pre-built OS images. These images usually contain several partitions such as -
boot:
contains the bootloader, kernel, dtb, ramdisk and other stuffsystem:
contains the root file systemothers:
may contain other partitions for things such as storage etc.
- simply download an OS image or a pre-built linux distribution from the maintainers website.
- in this example, I'll be using the
apertis
distribution.
- in this example, I'll be using the
- it’s usually in a compressed (zImage) format, decompress it using a tool like unarchiver to get a disk image.
- use
partx --show
to list all partitions
$ partx --show __linux_image_filepath__
NR START END SECTORS SIZE NAME UUID
1 8192 532479 524288 256M 9730496b-01
2 532480 3661823 3129344 1.5G 9730496b-02
- in the above example, partition 2 with a size of 1.5G contains the root file system. It's usually named
system
. - calculate the offset to the
system
volume/partition. You can do this with thebc
command.
512
is the sector-size. We multiply sector-size with the sector offset to get the actual starting (byte) location ofsystem
.
$ bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
> 532480 * 512
> 272629760
> quit
- mount the partition as an
ext4 filesystem
$ sudo mkdir /mnt/other
$ sudo mount -v -o offset=272629760 -t ext4 /_path_to_file_image/__filename__.img /mnt/other
mount: /dev/loop0 mounted on /mnt/other.
Check mounted image
$ ls /mnt/other
- copy all of (system partition's) contents to sd card's ext2/3/4 partition using the
cp
command.
Note:
- this method only works on linux or macOS and does not work on WSL2.
- all symbolic links need to be copied. If required you can create symbolic links using
ln
command. Here' an example that creates a symbolic link calledsbin
tousr/bin
ln -s usr/sbin sbin
🚌 UART communication:
rustBoot will output boot-logs via the raspberry-pi 4's UART interface. These logs can be sent to a host computer (i.e. laptop/desktop).
We'll need extra hardware for this:
- a usb-to-serial ttl converter: is a tiny piece of hardware that allows us to send serial data from the rpi4's uart interface to the host
Connect the USB-serial converter to your host computer as shown in the wiring diagram .
- make sure that you DO NOT connect the power pin of the USB serial. Only RX/TX and GND.
- connect the rpi4 to the (USB) power cable and observe the output:
Serial console: To view rpi4's output on a host machine, you'll need a tool/app/console that handles sending and receiving of serial data. There are a number of ways to interact with a serial console. I'll be using
minicom
on linuxscreen
on the macterminal-s
on windows
❗ NOTE: Depending on your host operating system, the device name might differ. For example, on macOS, it might be something like /dev/tty.usbserial-0001. In this case, please give the name explicitly:
- Using minicom:
- install minicom with
sudo apt-get install minicom
, so you can emulate a terminal connected over serial. - and run
minicom -b 115200 -D /dev/tty.usbserial-0001
- boot the Pi.
- within a few seconds, you should see data in your session.
- install minicom with
- Using screen:
- on a mac, run
screen /dev/tty.usbserial-0001 115200
- boot the Pi.
- within a few seconds, you should see data in your session.
- Using terminal-s:
- on a windows machine, install terminal-s (a python-based serial terminal) with
pip install terminal-s
- and run
terminal-s
- no need to provide a baud-rate. It will auto-detect the port and baud-rate (assuming its lower than 115200).
- on a windows machine, install terminal-s (a python-based serial terminal) with
Note:
- To exit the screen session, press Ctrl-A, then Ctrl-K, and confirm you want to exit when using minicom or screen
- To exit terminal-s, press Ctrl-]
🧪 Power-on and test:
Now that you have a fully bootable SD card containing
- a
fat32 formatted
boot partition populated with the relevant boot files and - a
ext2/3/4 formatted
root-file-system
and have your uart-usb interface set-up, you are now ready to flip the switch i.e.
- insert the sd card into the pi's sd slot and
- supply power to your pi.
Your serial console should now start receiving boot-logs from the rpi4
stm32f411
The stm32f411
example uses a STM32F411EDISCOVERY BOARD.The board has four LEDs of which two LEDs are used in this example.
Note:
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
Partitioning:
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the stm32f411
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "stm32f411")] pub const SECTOR_SIZE: usize = 0x20000; #[cfg(feature = "stm32f411")] pub const PARTITION_SIZE: usize = 0x20000; #[cfg(feature = "stm32f411")] pub const BOOT_PARTITION_ADDRESS: usize = 0x08020000; #[cfg(feature = "stm32f411")] pub const SWAP_PARTITION_ADDRESS: usize = 0x08060000; #[cfg(feature = "stm32f411")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x08040000; }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address0x0800_0000
.BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the stm32f411
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f411 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command line output that should be produced.
yashwanthsingh@Yashwanths-MacBook-Pro rustBoot % cargo stm32f411 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/xtask stm32f411 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
warning: unused config key `build.runner` in `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/firmware/stm32f411/boot_fw_blinky_green/.cargo/config.toml`
Finished release [optimized] target(s) in 0.10s
$ cargo build --release
warning: unused config key `build.runner` in `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/firmware/stm32f411/updt_fw_blinky_red/.cargo/config.toml`
Finished release [optimized] target(s) in 0.10s
$ cargo build --release
Finished release [optimized] target(s) in 0.11s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_bootfw -O binary stm32f411_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_updtfw -O binary stm32f411_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f411_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f411_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: stm32f411_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f411_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1908 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f411_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `/Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f411_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: stm32f411_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f411_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1996 bytes.
$ probe-rs-cli erase --chip stm32f411vetx
$ probe-rs-cli download --format Bin --base-address 0x8020000 --chip stm32f411vetx stm32f411_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:01] [############################] 128.00KiB/128.00KiB @ 65.02KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 2.00KiB/ 2.00KiB @ 677B/s (eta 0s )
Finished in 2.057s
$ probe-rs-cli download --format Bin --base-address 0x8040000 --chip stm32f411vetx stm32f411_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:01] [############################] 128.00KiB/128.00KiB @ 65.15KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 2.00KiB/ 2.00KiB @ 679B/s (eta 0s )
Finished in 2.052s
$ cargo flash --chip stm32f411vetx --release
Finished release [optimized] target(s) in 0.08s
Flashing /Users/yashwanthsingh/Yash/Projects/git_rustBoot_mcusigner/rustBoot/boards/target/thumbv7em-none-eabihf/release/stm32f411
Erasing sectors ✔ [00:00:01] [##############################] 48.00KiB/48.00KiB @ 40.79KiB/s (eta 0s )
Programming pages ✔ [00:00:01] [##############################] 43.00KiB/43.00KiB @ 17.31KiB/s (eta 0s )
Finished in 2.267s
yashwanthsingh@Yashwanths-MacBook-Pro rustBoot %
Verifying:
blinky leds
are used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
green-led
for a few seconds,- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- it will attempt to blink a
red-led
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
red-led
.
- it will attempt to blink a
stm32f446 Nucleo
The stm32f446
example uses a STM32F446 Nucleo Board. The board has 1 user LED which will be blinking at different frequencies depending on whether we're executing boot-firmware or update-firmware.
Note
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
Partitioning
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the stm32f446
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "stm32f446")] pub const SECTOR_SIZE: usize = 0x20000; #[cfg(feature = "stm32f446")] pub const PARTITION_SIZE: usize = 0x20000; #[cfg(feature = "stm32f446")] pub const BOOT_PARTITION_ADDRESS: usize = 0x08020000; #[cfg(feature = "stm32f446")] pub const SWAP_PARTITION_ADDRESS: usize = 0x08060000; #[cfg(feature = "stm32f446")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x08040000; }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address 0x0800_0000
.
BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the stm32f446 Nucleo's
on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f446 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board. With the respective firmware versions.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command line output that should be produced.
anand@anand-VirtualBox:~/Desktop/dev_space/Prod/rustBoot_mcusigner$ cargo stm32f446 build-sign-flash rustBoot 1234 1235
Compiling version_check v0.9.4
Compiling typenum v1.15.0
Compiling subtle v2.4.1
Compiling rand_core v0.6.3
Compiling const-oid v0.7.1
Compiling zeroize v1.4.3
Compiling memchr v2.5.0
Compiling cfg-if v1.0.0
Compiling base16ct v0.1.1
Compiling log v0.4.17
Compiling opaque-debug v0.3.0
Compiling cpufeatures v0.2.2
Compiling anyhow v1.0.58
Compiling minimal-lexical v0.2.1
Compiling stable_deref_trait v1.2.0
Compiling byteorder v1.4.3
Compiling xshell-macros v0.1.17
Compiling der v0.5.1
Compiling ff v0.11.1
Compiling as-slice v0.2.1
Compiling group v0.11.0
...
...
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/xtask stm32f446 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Finished release [optimized] target(s) in 0.03s
$ cargo build --release
Finished release [optimized] target(s) in 0.03s
$ cargo build --release
Finished release [optimized] target(s) in 0.03s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f446_bootfw -O binary stm32f446_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f446_updtfw -O binary stm32f446_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f446_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Compiling libc v0.2.126
Compiling rustBoot v0.1.0 (/home/anand/Desktop/dev_space/Prod/rustBoot_mcusigner/rustBoot)
Compiling filetime v0.2.17
Compiling rbsigner v0.1.0 (/home/anand/Desktop/dev_space/Prod/rustBoot_mcusigner/rbsigner)
Finished dev [unoptimized + debuginfo] target(s) in 1.18s
Running `/home/anand/Desktop/dev_space/Prod/rustBoot_mcusigner/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f446_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: stm32f446_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f446_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 1948 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f446_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `/home/anand/Desktop/dev_space/Prod/rustBoot_mcusigner/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f446_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: stm32f446_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f446_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2092 bytes.
$ probe-rs-cli erase --chip stm32f446retx
$ probe-rs-cli download --format Bin --base-address 0x8020000 --chip stm32f446retx stm32f446_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:03] [##############################################################################################################] 128.00KiB/128.00KiB @ 37.61KiB/s (eta 0s )
Programming pages ✔ [00:00:01] [################################################################################################################] 2.00KiB/ 2.00KiB @ 296B/s (eta 0s )
Finished in 5.804s
$ probe-rs-cli download --format Bin --base-address 0x8040000 --chip stm32f446retx stm32f446_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:03] [##############################################################################################################] 128.00KiB/128.00KiB @ 37.75KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [################################################################################################################] 3.00KiB/ 3.00KiB @ 353B/s (eta 0s )
Finished in 6.273s
$ cargo flash --chip stm32f446vetx --release
Finished release [optimized] target(s) in 0.03s
Flashing /home/anand/Desktop/dev_space/Prod/rustBoot_mcusigner/boards/target/thumbv7em-none-eabihf/release/stm32f446
Erasing sectors ✔ [00:00:03] [################################################################################################################] 48.00KiB/48.00KiB @ 11.88KiB/s (eta 0s )
Programming pages ✔ [00:00:21] [################################################################################################################] 43.00KiB/43.00KiB @ 1.69KiB/s (eta 0s )
Finished in 25.449s
Verifying:
user led
is used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
user led
for a few seconds, at an interval of0.5 seconds
.- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- it will attempt to blink a
user led
at an interval of1 second
. - and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
user led
at an interval of1 second
.
- it will attempt to blink a
stm32f469
The stm32f469
example uses a STM32F469-IDISCOVERY BOARD. The board has four front LEDs, three of which are used in this example.
Note:
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
- On the discovery board, an external 1MB N25Q128A flash is mounted (in addition to the STM32 internal 1MB flash), but we will only use the internal flash in this example.
Partitioning:
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the stm32f469
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "stm32f469")] pub const SECTOR_SIZE: usize = 0x20000; #[cfg(feature = "stm32f469")] pub const PARTITION_SIZE: usize = 0x60000; #[cfg(feature = "stm32f469")] pub const BOOT_PARTITION_ADDRESS: usize = 0x08020000; #[cfg(feature = "stm32f469")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x08080000; #[cfg(feature = "stm32f469")] pub const SWAP_PARTITION_ADDRESS: usize = 0x080e0000; }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address0x0800_0000
.BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the stm32f469
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f469 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command line output that should be produced.
lionel@saturn:/tmp/rustBoot$ cargo stm32f469 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/xtask stm32f469 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
warning: unused config key `build.runner` in `/tmp/rustBoot/boards/firmware/stm32f469/boot_fw_blinky_green/.cargo/config.toml`
Finished release [optimized] target(s) in 0.05s
$ cargo build --release
warning: unused config key `build.runner` in `/tmp/rustBoot/boards/firmware/stm32f469/updt_fw_blinky_red/.cargo/config.toml`
Finished release [optimized] target(s) in 0.04s
$ cargo build --release
Finished release [optimized] target(s) in 0.04s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f469_bootfw -O binary stm32f469_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f469_updtfw -O binary stm32f469_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f469_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `/tmp/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f469_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Image type: mcu-image
Curve type: nistp256
Input image: stm32f469_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f469_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2004 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f469_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `/tmp/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f469_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Image type: mcu-image
Curve type: nistp256
Input image: stm32f469_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f469_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2232 bytes.
$ probe-rs-cli erase --chip STM32F469NIHx
$ probe-rs-cli download --format Bin --base-address 0x8020000 --chip STM32F469NIHx stm32f469_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:02] [##############################################################################################################] 128.00KiB/128.00KiB @ 60.69KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [################################################################################################################] 2.00KiB/ 2.00KiB @ 630B/s (eta 0s )
Finished in 2.249s
$ probe-rs-cli download --format Bin --base-address 0x8080000 --chip STM32F469NIHx stm32f469_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:02] [##############################################################################################################] 128.00KiB/128.00KiB @ 60.92KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [################################################################################################################] 3.00KiB/ 3.00KiB @ 772B/s (eta 0s )
Finished in 2.244s
$ cargo flash --chip STM32F469NIHx --release
Finished release [optimized] target(s) in 0.07s
Flashing /tmp/rustBoot/boards/target/thumbv7em-none-eabihf/release/stm32f469
Erasing sectors ✔ [00:00:01] [################################################################################################################] 48.00KiB/48.00KiB @ 39.74KiB/s (eta 0s )
Programming pages ✔ [00:00:01] [################################################################################################################] 40.00KiB/40.00KiB @ 14.95KiB/s (eta 0s )
Finished in 2.443s
lionel@saturn:/tmp/rustBoot$
Verifying:
blinky leds
are used to confirm that rustBoot works as expected. On the STM32F469-IDISCOVERY board, the LEDs we are referring to below correspond to the LEDs next to the LCD screen. Here's the flow
- After flashing the demo, upon supplying power to the board or pressing the black (reset) button, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
green-led
for a few seconds,- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- it will attempt to blink a
red-led
- and set a
confirmation flag
to indicate that the update was successful. - post which, it will also turn on a
blue-led
and continue blinking ared-led
.
- it will attempt to blink a
stm32h723
The stm32h723
example uses a Nucleo-h723zg board. The board has three LEDs
of which two LEDs
are used in this example.
Note:
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
Partitioning:
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the stm32h723
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
Note:
- Error correction code memory is a type of flash memory data storage that uses error correction code to detect and correct n-bit data corruption that occurs in memory.
- Since the STM32H7 series devices have a 10-bit
ECC
function, it is not possible to write partition status/sector flags more than once, so use a biggerPARTITION_SIZE
. The last128KB
sector in each partition will be reserved for bootloader flags or partition status.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "stm32h723")] pub const SECTOR_SIZE: usize = 0x20000; #[cfg(feature = "stm32h723")] pub const PARTITION_SIZE: usize = 0x40000; #[cfg(feature = "stm32h723")] pub const BOOT_PARTITION_ADDRESS: usize = 0x08020000; #[cfg(feature = "stm32h723")] pub const SWAP_PARTITION_ADDRESS: usize = 0x080A0000; #[cfg(feature = "stm32h723")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x08060000; }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address0x0
.BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the stm32h723
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32h723 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites - probe-rs-cli, cargo-binutils and cargo-flash.
$ cargo install probe-rs-cli
$ cargo install cargo-binutils
$ cargo install cargo-flash
Here's the command line output that should be produced.
$ cargo stm32h723 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/xtask stm32h723 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Compiling version_check v0.9.4
Compiling typenum v1.15.0
...
...
Compiling rustBoot-update v0.1.0 (/Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/boards/update)
Finished release [optimized] target(s) in 53.55s
$ cargo build --release
Compiling stm32h723_updtfw v0.1.0 (/Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/boards/firmware/stm32h723/updt_fw_blinky_red)
Finished release [optimized] target(s) in 1.54s
$ cargo build --release
Compiling stm32h723 v0.1.0 (/Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/boards/bootloaders/stm32h723)
Finished release [optimized] target(s) in 2.25s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32h723_bootfw -O binary stm32h723_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32h723_updtfw -O binary stm32h723_updtfw.bin
$ cargo run mcu-image ../boards/rbSigner/signed_images/stm32h723_bootfw.bin nistp256 ../boards/rbSigner/keygen/ecc256.der 1234
Compiling rbsigner v0.1.0 (/Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/rbsigner)
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
Running `/Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/rbSigner/signed_images/stm32h723_bootfw.bin nistp256 ../boards/rbSigner/keygen/ecc256.der 1234`
Update type :Firmware
Curve type :nistp256
Input image :stm32h723_bootfw.bin
Public key :ecc256.der
Image version :1234
Output image :stm32h723_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 4608 bytes.
$ cargo run mcu-image ../boards/rbSigner/signed_images/stm32h723_updtfw.bin nistp256 ../boards/rbSigner/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `/Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/target/debug/rbsigner mcu-image ../boards/rbSigner/signed_images/stm32h723_updtfw.bin nistp256 ../boards/rbSigner/keygen/ecc256.der 1235`
Update type :Firmware
Curve type :nistp256
Input image :stm32h723_updtfw.bin
Public key :ecc256.der
Image version :1235
Output image :stm32h723_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 4624 bytes.
$ probe-rs-cli erase --chip stm32h723ZGTx
$ probe-rs-cli download --format Bin --base-address 0x8020000 --chip STM32H723ZGTx stm32h723_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:02] [############################] 128.00KiB/128.00KiB @ 63.21KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 5.00KiB/ 5.00KiB @ 1.05KiB/s (eta 0s )
Finished in 2.165s
$ probe-rs-cli download --format Bin --base-address 0x8060000 --chip STM32H723ZGTx stm32h723_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:01] [############################] 128.00KiB/128.00KiB @ 64.68KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 5.00KiB/ 5.00KiB @ 1.08KiB/s (eta 0s )
Finished in 2.117s
$ cargo flash --chip stm32h723ZGTx --release
Finished release [optimized] target(s) in 0.08s
Flashing /Users/imrankhaleelsab/Imran/Boschspace/RB_workspace/rustBoot-mcusigner/rustBoot/boards/target/thumbv7em-none-eabihf/release/stm32h723
WARN probe_rs::config::target > Using custom sequence for STM32H7
Erasing sectors ✔ [00:00:01] [############################] 128.00KiB/128.00KiB @ 66.35KiB/s (eta 0s )
Programming pages ✔ [00:00:00] [##############################] 44.00KiB/44.00KiB @ 13.45KiB/s (eta 0s )
Finished in 2.878s
Verifying:
blinky leds
are used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
green-led
for a few seconds,- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- it will attempt to blink a
red-led
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
red-led
.
- it will attempt to blink a
stm32f746
The stm32f746
example uses a Nucleo-f746zg board. The board has three LEDs
of which two LEDs
are used in this example.
Note:
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
Partitioning:
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the stm32f746
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "stm32f746")] pub const SECTOR_SIZE: usize = 0x40000; #[cfg(feature = "stm32f746")] pub const PARTITION_SIZE: usize = 0x40000; #[cfg(feature = "stm32f746")] pub const BOOT_PARTITION_ADDRESS: usize = 0x08040000; #[cfg(feature = "stm32f746")] pub const SWAP_PARTITION_ADDRESS: usize = 0x080C0000; #[cfg(feature = "stm32f746")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x08080000; }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address0x0
.BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the stm32f746
's on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f746 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
cargo install cargo-binutils
Here's the command line output that should be produced.
udayakumar@udayakumar-VirtualBox:~/devspace/rustBoot$ cargo stm32f746 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/xtask stm32f746 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Compiling stm32f746_bootfw v0.1.0 (/home/udayakumar/devspace/rustBoot/boards/firmware/stm32f746/boot_fw_blinky_green)
Finished release [optimized] target(s) in 0.99s
$ cargo build --release
Compiling stm32f746_updtfw v0.1.0 (/home/udayakumar/devspace/rustBoot/boards/firmware/stm32f746/updt_fw_blinky_red)
Finished release [optimized] target(s) in 0.95s
$ cargo build --release
Finished release [optimized] target(s) in 0.09s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f746_bootfw -O binary stm32f746_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f746_updtfw -O binary stm32f746_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f746_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Compiling libc v0.2.126
Compiling rustBoot v0.1.0 (/home/udayakumar/devspace/rustBoot/rustBoot)
Compiling filetime v0.2.17
Compiling rbsigner v0.1.0 (/home/udayakumar/devspace/rustBoot/rbsigner)
Finished dev [unoptimized + debuginfo] target(s) in 2.17s
Running `/home/udayakumar/devspace/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f746_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: stm32f746_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f746_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2780 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f746_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `/home/udayakumar/devspace/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f746_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: stm32f746_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f746_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2748 bytes.
$ probe-rs-cli erase --chip stm32f746zgtx
$ probe-rs-cli download --format Bin --base-address 0x8040000 --chip stm32f746zgtx stm32f746_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:03] [################################################################################################################] 256.00KiB/256.00KiB @ 72.27KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [##################################################################################################################] 3.00KiB/ 3.00KiB @ 353B/s (eta 0s )
Finished in 6.153s
$ probe-rs-cli download --format Bin --base-address 0x8080000 --chip stm32f746zgtx stm32f746_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:03] [################################################################################################################] 256.00KiB/256.00KiB @ 72.63KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [##################################################################################################################] 3.00KiB/ 3.00KiB @ 351B/s (eta 0s )
Finished in 6.171s
$ cargo flash --chip stm32f746zgtx --release
Finished release [optimized] target(s) in 0.08s
Flashing /home/udayakumar/devspace/rustBoot/boards/target/thumbv7em-none-eabihf/release/stm32f746
Erasing sectors ✔ [00:00:02] [##################################################################################################################] 64.00KiB/64.00KiB @ 22.76KiB/s (eta 0s )
Programming pages ✔ [00:00:20] [##################################################################################################################] 43.00KiB/43.00KiB @ 1.88KiB/s (eta 0s )
Finished in 22.968s
udayakumar@udayakumar-VirtualBox:~/devspace/rustBoot$
Verifying:
blinky leds
are used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
green-led
for a few seconds,- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- it will attempt to blink a
red-led
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
red-led
.
- it will attempt to blink a
stm32f334
The stm32f334
example uses a STM32F334 Nucleo-64 Board. The board has 1 user LED which will be blinking at different frequencies depending on whether we're executing boot-firmware or update-firmware.
Note
- If you're using a different version of the board, you'll probably need to edit your firmware's
partition-addresses
to accommodate for differences.- Just make sure you don't change the names of files or the folder structure, as cargo xtask looks for these file/folder names.
Partitioning
The first step in integrating rustBoot is flash-memory partitioning
i.e. we divide the stm32f334
's flash-memory into 4 partitions, taking into account the geometry of the flash memory.
You can read more about
mcu
partitioning here
In this example, we'll be using the following partitioning scheme. You can locate these constants in the constants module
#![allow(unused)] fn main() { #[cfg(feature = "stm32f334")] pub const SECTOR_SIZE: usize = 0x1800; #[cfg(feature = "stm32f334")] pub const PARTITION_SIZE: usize = 0x1800; #[cfg(feature = "stm32f334")] pub const BOOT_PARTITION_ADDRESS: usize = 0x0800b800; //its is of 3 pages starting from this address as the boot firmware is 2.47KiB #[cfg(feature = "stm32f334")] pub const SWAP_PARTITION_ADDRESS: usize = 0x0800e800; //its is of 3 pages starting from this address #[cfg(feature = "stm32f334")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x0800d000; //its is of 3 pages starting from this address as the update firmware is 2.50KiB > Note: Choose the number of pages based on your boot and update firmware sizes. }
RUSTBOOT partition:
contains the bootloader (its code and data) and a (test) public-key embedded as part of the bootloader image, starts at address 0x0800_0000
.
BOOT partition:
contains boot firmware, starts at addressPARTITION_BOOT_ADDRESS
.UPDATE partition:
contains update firmware, starts at addressUPDATE_PARTITION_ADDRESS
. The boot firmware is responsible for downloading and installing the update firmware into this partition via a secure channel.SWAP partition:
is the temporary swap space, starts at addressSWAP_PARTITION_ADDRESS
.
Compiling, Signing and Programming:
Now that we have properly partitioned the stm32f334 Nucleo's
on-board flash-memory, the next step is - compiling, signing and programming
We will compile the following
- bootloader
- boot and update firmware
sign both pieces of firmware with a (test) private-key and finally create valid rustBoot mcu-images
i.e. signed boot and update firmware images.
Note:
- the
ecc256.der
file contains a public-key and a private-key, the first 64 bytes being the public-key and remaining 32 bytes make up the private-key.- This is a test key file and is to be used for testing purposes only.
Compiling, signing and programming can be performed via a single command
cargo stm32f334 build-sign-flash rustBoot [boot-ver] [updt-ver]
Note:
- The
updt-ver
number should be greater thanboot-ver
.
This will build, sign and flash all 3 packages (i.e. bootloader + boot-fw + update-fw) onto the board.
Note:
- The corresponding public-key is embedded in the bootloader's source.
- In order to test this example, you'll have to install a couple of pre-requisites as it uses probe-run to flash the binary.
cargo install probe-rs-cli
cargo install cargo-flash
Here's the command line output that should be produced.
sarathk@sarath:~/stm32f/pull_req/rustBoot$ cargo stm32f334 build-sign-flash rustBoot 1234 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/xtask stm32f334 build-sign-flash rustBoot 1234 1235`
$ cargo build --release
Compiling rustBoot-hal v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/boards/hal)
Compiling rustBoot-update v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/boards/update)
Compiling stm32f334_bootfw v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/boards/firmware/stm32f334/boot_fw_blinky_green)
Finished release [optimized] target(s) in 1.60s
$ cargo build --release
Compiling stm32f334_updtfw v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/boards/firmware/stm32f334/updt_fw_blinky_red)
Finished release [optimized] target(s) in 1.17s
$ cargo build --release
Compiling defmt v0.3.2
Compiling defmt-macros v0.3.2
Compiling critical-section v0.2.7
Compiling defmt-parser v0.3.1
Compiling bare-metal v1.0.0
Compiling bitflags v1.3.2
Compiling defmt-rtt v0.3.2
Compiling stm32f334 v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/boards/bootloaders/stm32f334)
Compiling proc-macro-error-attr v1.0.4
Compiling proc-macro-error v1.0.4
Finished release [optimized] target(s) in 9.29s
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f334_bootfw -O binary stm32f334_bootfw.bin
$ rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f334_updtfw -O binary stm32f334_updtfw.bin
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f334_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234
Compiling libc v0.2.126
Compiling filetime v0.2.17
Compiling rustBoot v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/rustBoot)
Compiling rbsigner v0.1.0 (/home/sarathk/stm32f/pull_req/rustBoot/rbsigner)
Finished dev [unoptimized + debuginfo] target(s) in 3.89s
Running `/home/sarathk/stm32f/pull_req/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f334_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1234`
Update type: Firmware
Curve type: nistp256
Input image: stm32f334_bootfw.bin
Public key: ecc256.der
Image version: 1234
Output image: stm32f334_bootfw_v1234_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2508 bytes.
$ cargo run mcu-image ../boards/sign_images/signed_images/stm32f334_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `/home/sarathk/stm32f/pull_req/rustBoot/target/debug/rbsigner mcu-image ../boards/sign_images/signed_images/stm32f334_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der 1235`
Update type: Firmware
Curve type: nistp256
Input image: stm32f334_updtfw.bin
Public key: ecc256.der
Image version: 1235
Output image: stm32f334_updtfw_v1235_signed.bin
Calculating sha256 digest...
Signing the firmware...
Done.
Output image successfully created with 2484 bytes.
$ probe-rs-cli erase --chip stm32f334r8tx
$ probe-rs-cli download --format Bin --base-address 0x800b800 --chip stm32f334r8tx stm32f334_bootfw_v1234_signed.bin
Erasing sectors ✔ [00:00:01] [################################################################################################] 4.00KiB/ 4.00KiB @ 1.81KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [################################################################################################] 3.00KiB/ 3.00KiB @ 557B/s (eta 0s )
Finished in 4.483s
$ probe-rs-cli download --format Bin --base-address 0x800d000 --chip stm32f334r8tx stm32f334_updtfw_v1235_signed.bin
Erasing sectors ✔ [00:00:01] [################################################################################################] 4.00KiB/ 4.00KiB @ 1.85KiB/s (eta 0s )
Programming pages ✔ [00:00:02] [################################################################################################] 3.00KiB/ 3.00KiB @ 554B/s (eta 0s )
Finished in 4.491s
$ cargo flash --chip stm32f334r8tx --release
Finished release [optimized] target(s) in 0.08s
Flashing /home/sarathk/stm32f/pull_req/rustBoot/boards/target/thumbv7em-none-eabihf/release/stm32f334
Erasing sectors ✔ [00:00:10] [################################################################################################] 44.00KiB/44.00KiB @ 4.03KiB/s (eta 0s )
Programming pages ✔ [00:00:20] [################################################################################################] 43.00KiB/43.00KiB @ 1.31KiB/s (eta 0s )
Finished in 31.232s
Verifying:
user led
is used to confirm that rustBoot works as expected. Here's the flow
- Upon supplying power to the board, rustBoot takes over
- validates the firmware image stored in the BOOT partition
- verifies the signature attached against a known public key stored in the rustBoot image.
- If the signature checks out, rustBoot boots into the bootfw and blinks a
user led
for a few seconds, at an interval of1 second
three times.- post which, the boot firmware triggers the update and performs a system reset.
- Upon reset, the rustBoot again takes over
- validates the firmware image stored in the UPDATE partition
- swaps the contents of the BOOT and the UPDATE partitions
- marks the new firmware in the BOOT partition as in state STATE_TESTING
- boots into the UPDATE'd firmware
- Now that execution-control has been transferred to the UPDATE'd firmware
- and set a
confirmation flag
to indicate that the update was successful. - post which, it continuously blinks a
user led
at an interval of0.25 second
continuously.
- and set a
Secure Coding guidelines
Testing
Continuous Integration
For Developers
Reference Guide
Contributors
Here is a list of the contributors who have helped improve rustBoot
. Big shout-out to them!
If you feel you're missing from this list, feel free to add yourself in a PR.