What changed, and why it matters
This is a large firmware commit that adds a new two-stage bootloader update mechanism for the BitBox02 hardware wallet. It replaces the old single bootloader with a small, fixed 'stage0' plus a separately signed 'stage1', and ships a special firmware image that can install both stages onto existing devices. The change also updates how firmware signatures are computed so that the product type is included in the hash, which is a security improvement. Because the commit is a major architectural rewrite and includes prebuilt binary blobs whose source is not shown, it carries normal supply-chain and implementation risks, but no specific vulnerability is visible in the diff.
Treat this as a high-risk, security-sensitive release. Verify the prebuilt stage0/stage1 binaries against reproducible builds and audit the new stage0 C code, stage1 signature verification, and firmware installer for memory-safety, downgrade, and rollback issues before shipping. Re-enable the commented-out CI targets only after the stage0 root keys used to sign stage1 are confirmed embedded in the branch.
Security signals we found
Bootloader architecture changed from monolithic to two-stage (stage0 + signed stage1).
Firmware signature hash now includes a 16-bit product_id, binding firmware to product variant.
Root public keys were rotated/replaced with a single set across all products.
New stage0 locks debug access (DSU hard lock, security bit) in production builds.
Development stage0 skips signature verification and shows a visible cross overlay.
Prebuilt stage0/stage1 binary blobs are added to the repository (supply-chain concern).
Changelog explicitly calls out 'Security improvements' and a bugfix for full-sized firmware upgrades.
Evidence from the diff
The commit re-architects the BitBox02 bootloader into stage0 (8 KB immutable root of trust at 0x00000000) and stage1 (signed, updatable bootloader at 0x00002000). It adds linker scripts, CMake targets, Python tooling (scripts/bootloader_update.py) for preparing signed stage1 payloads and validating stage0 descriptors, and a new ‘firmware-blupgrade’ image that embeds prebuilt stage0/stage1 binaries and installs them. The firmware signature scheme is changed from double-SHA256 over [version|firmware] to single SHA256 over [product_id_le16|version|firmware]; the bootloader’s PUKCC ECDSA verification still hashes that prehash once more, so signatures now cover product identity. Root public keys in bootloader.c are replaced with a single product-agnostic set. A new boot_args.h defines a RAM-based boot-args structure for passing commands such as ‘enter bootloader’ and screen orientation from firmware to stage0. The commit also notes ‘Security improvements’ and a bugfix for full-sized firmware upgrades in the changelog.
Changed components
BitBox02 / BitBox02 Nova bootloaderFirmware upgrade/installer logicFirmware signature verification (PUKCC/SHA256)Build system (CMake, linker scripts, Makefile)Python release tooling and host-side bootloader scriptsPrebuilt bootloader binary assetsInspect captured patch +5003 / −523
diff --git a/.ci/check-hashes b/.ci/check-hashes
index 19951f43..ee48a689 100755
--- a/.ci/check-hashes
+++ b/.ci/check-hashes
@@ -4,3 +4,8 @@ set -e
set -x
sha256sum --check bitbox-da14531-firmware.bin.sha256
+
+(
+ cd src/bootloader_upgrade/bin
+ sha256sum --check *.sha256
+)
diff --git a/.ci/check-tidy b/.ci/check-tidy
index 0bb9d114..32de9846 100755
--- a/.ci/check-tidy
+++ b/.ci/check-tidy
@@ -41,7 +41,8 @@ for dir in build build-build; do
# Only check files if they are in the compile_commands.json file
SOURCES=""
for SOURCE in ${SOURCES1}; do
- if grep -q ${SOURCE} ${dir}/compile_commands.json; then
+ SOURCE_ABS="${PWD}/${SOURCE}"
+ if grep -Fq "\"file\": \"${SOURCE_ABS}\"" ${dir}/compile_commands.json; then
SOURCES+=" $SOURCE"
fi
done
diff --git a/.ci/check-unwanted-symbols b/.ci/check-unwanted-symbols
index 7a1650b5..7a8ba1d0 100755
--- a/.ci/check-unwanted-symbols
+++ b/.ci/check-unwanted-symbols
@@ -99,7 +99,7 @@ if [[ -f "$firmware_elf" ]]; then
fi
shopt -s nullglob
-for bootloader_elf in build/bin/bb02-bl-*.elf build/bin/bb02p-bl-*.elf; do
+for bootloader_elf in build/bin/bootloader-stage0-*.elf build/bin/bootloader-stage1-*.elf; do
check_bootloader "$bootloader_elf"
done
shopt -u nullglob
diff --git a/.github/workflows/ci-common.yml b/.github/workflows/ci-common.yml
index 07b214b0..068189a6 100644
--- a/.github/workflows/ci-common.yml
+++ b/.github/workflows/ci-common.yml
@@ -199,21 +199,12 @@ jobs:
strategy:
matrix:
target:
- - bootloader
- - bootloader-development
- - bootloader-development-locked
- - bootloader-production
- - bootloader-debug
- - bootloader-btc
- - bootloader-btc-development
- - bootloader-btc-production
- - bootloader-plus
- - bootloader-plus-development
- - bootloader-plus-production
- - bootloader-plus-debug
- - bootloader-plus-btc
- - bootloader-plus-btc-development
- - bootloader-plus-btc-production
+ - bootloader-stage0
+ - bootloader-stage1
+ # Re-enable after the stage1 upgrade binaries are signed with the
+ # stage0 root keys embedded in this branch.
+ # - bootloader-upgrade-assets
+ # - bootloader-upgrade-assets-development
- firmware
- firmware-btc
- factory-setup
@@ -242,7 +233,7 @@ jobs:
run: make -j$(($(nproc)+1)) ${{ matrix.target }}
- name: Check unwanted symbols
- if: (matrix.target == 'firmware' || (startsWith(matrix.target, 'bootloader') && !endsWith(matrix.target, 'debug'))) && !cancelled()
+ if: (matrix.target == 'firmware' || startsWith(matrix.target, 'bootloader-stage')) && !cancelled()
run: ./.ci/check-unwanted-symbols
- name: Print hashes
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9d19df7..9d718a48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
### [Unreleased]
- Fixed a crash when listing many backups over Bluetooth
+### v9.26.2
+- Intermediate release to update the bootloader to v1.2.0
+
### v9.26.1
- Fix a payment request validation issue
@@ -199,6 +202,11 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
## Bootloader
+### v1.2.0
+- Convert to stage1
+- Security improvements
+- Bugfix to allow full-sized firmware upgrades
+
### v1.1.2
- BitBox02 Nova: correctly orient bootloader screen
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 434a34d3..7fb69f99 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -104,6 +104,7 @@ set(VERSION_MANIFEST ${CMAKE_SOURCE_DIR}/versions.json)
set(VERSION_GENERATOR ${CMAKE_SOURCE_DIR}/scripts/generate_version_headers.py)
set(VERSION_TEMPLATE ${CMAKE_SOURCE_DIR}/src/version.h.tmpl)
set(BOOTLOADER_VERSION_TEMPLATE ${CMAKE_SOURCE_DIR}/src/bootloader/bootloader_version.h.tmpl)
+set(STAGE0_VERSION_TEMPLATE ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0_version.h.tmpl)
set(GENERATED_VERSION_HEADERS_DIR ${CMAKE_BINARY_DIR}/src)
set(GENERATED_VERSION_CMAKE ${CMAKE_BINARY_DIR}/generated_versions.cmake)
set_property(
@@ -114,6 +115,7 @@ set_property(
${VERSION_GENERATOR}
${VERSION_TEMPLATE}
${BOOTLOADER_VERSION_TEMPLATE}
+ ${STAGE0_VERSION_TEMPLATE}
)
execute_process(
COMMAND
diff --git a/Makefile b/Makefile
index 252e778c..c4dc3c25 100644
--- a/Makefile
+++ b/Makefile
@@ -57,39 +57,78 @@ firmware-btc: | build
firmware-debug: | build-debug
$(MAKE) -C build-debug firmware.elf
-bootloader: | build
- $(MAKE) -C build bb02-bl-multi.elf
-bootloader-development: | build
- $(MAKE) -C build bb02-bl-multi-development.elf
-bootloader-development-locked: | build
- $(MAKE) -C build bb02-bl-multi-development-locked.elf
-bootloader-production: | build
- $(MAKE) -C build bb02-bl-multi-production.elf
-bootloader-debug: | build-debug
- $(MAKE) -C build-debug bb02-bl-multi-development.elf
-
-bootloader-btc: | build
- $(MAKE) -C build bb02-bl-btconly.elf
-bootloader-btc-development: | build
- $(MAKE) -C build bb02-bl-btconly-development.elf
-bootloader-btc-production: | build
- $(MAKE) -C build bb02-bl-btconly-production.elf
-
-bootloader-plus: | build
- $(MAKE) -C build bb02p-bl-multi.elf
-bootloader-plus-development: | build
- $(MAKE) -C build bb02p-bl-multi-development.elf
-bootloader-plus-production: | build
- $(MAKE) -C build bb02p-bl-multi-production.elf
-bootloader-plus-debug: | build-debug
- $(MAKE) -C build-debug bb02p-bl-multi-development.elf
-
-bootloader-plus-btc: | build
- $(MAKE) -C build bb02p-bl-btconly.elf
-bootloader-plus-btc-development: | build
- $(MAKE) -C build bb02p-bl-btconly-development.elf
-bootloader-plus-btc-production: | build
- $(MAKE) -C build bb02p-bl-btconly-production.elf
+firmware-blupgrade-bitbox02-btconly: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02-btconly.elf
+firmware-blupgrade-bitbox02-multi: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02-multi.elf
+firmware-blupgrade-bitbox02nova-btconly: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02nova-btconly.elf
+firmware-blupgrade-bitbox02nova-multi: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02nova-multi.elf
+firmware-blupgrade-bitbox02-btconly-development: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02-btconly-development.elf
+firmware-blupgrade-bitbox02-multi-development: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02-multi-development.elf
+firmware-blupgrade-bitbox02nova-btconly-development: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02nova-btconly-development.elf
+firmware-blupgrade-bitbox02nova-multi-development: | build
+ $(MAKE) -C build firmware-blupgrade-bitbox02nova-multi-development.elf
+
+# Stage0 aggregate targets build all production/development variants.
+bootloader-stage0: | build
+ $(MAKE) -C build bootloader-stage0
+bootloader-stage0-production: | build
+ $(MAKE) -C build bootloader-stage0-production
+bootloader-stage0-development: | build
+ $(MAKE) -C build bootloader-stage0-development
+
+# Per-product stage0 targets build their matching ELF/bin.
+bootloader-stage0-bitbox02-btconly-development: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02-btconly-development.elf
+bootloader-stage0-bitbox02-btconly-production: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02-btconly-production.elf
+bootloader-stage0-bitbox02-multi-development: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02-multi-development.elf
+bootloader-stage0-bitbox02-multi-production: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02-multi-production.elf
+bootloader-stage0-bitbox02nova-btconly-development: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02nova-btconly-development.elf
+bootloader-stage0-bitbox02nova-btconly-production: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02nova-btconly-production.elf
+bootloader-stage0-bitbox02nova-multi-development: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02nova-multi-development.elf
+bootloader-stage0-bitbox02nova-multi-production: | build
+ $(MAKE) -C build bootloader-stage0-bitbox02nova-multi-production.elf
+
+# Stage1 aggregate targets build all production/development variants.
+# The per-product stage1 targets build their matching ELF/bin.
+bootloader-stage1: | build
+ $(MAKE) -C build bootloader-stage1
+bootloader-stage1-production: | build
+ $(MAKE) -C build bootloader-stage1-production
+bootloader-stage1-development: | build
+ $(MAKE) -C build bootloader-stage1-development
+bootloader-stage1-bitbox02-btconly-development: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02-btconly-development.elf
+bootloader-stage1-bitbox02-btconly-production: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02-btconly-production.elf
+bootloader-stage1-bitbox02-multi-development: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02-multi-development.elf
+bootloader-stage1-bitbox02-multi-production: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02-multi-production.elf
+bootloader-stage1-bitbox02nova-btconly-development: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02nova-btconly-development.elf
+bootloader-stage1-bitbox02nova-btconly-production: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02nova-btconly-production.elf
+bootloader-stage1-bitbox02nova-multi-development: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02nova-multi-development.elf
+bootloader-stage1-bitbox02nova-multi-production: | build
+ $(MAKE) -C build bootloader-stage1-bitbox02nova-multi-production.elf
+
+bootloader-upgrade-assets: | build
+ $(MAKE) -C build bootloader-upgrade-assets
+bootloader-upgrade-assets-development: | build
+ $(MAKE) -C build bootloader-upgrade-assets-development
factory-setup: | build
$(MAKE) -C build factory-setup.elf
@@ -129,20 +168,25 @@ run-valgrind-on-unit-tests:
bash -ec 'for exe in build-build/bin/test_*; do valgrind --leak-check=yes --track-origins=yes --error-exitcode=1 --exit-on-first-error=yes $$exe; done'
flash-dev-firmware:
./py/load_firmware.py build/bin/firmware.bin --debug
-jlink-flash-bootloader-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02-bl-multi-development.jlink
-jlink-flash-bootloader-plus-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02p-bl-multi-development.jlink
-jlink-flash-bootloader-btc-plus-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02p-bl-btconly-development.jlink
-jlink-flash-bootloader-development-locked: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02-bl-multi-development-locked.jlink
-jlink-flash-bootloader: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02-bl-multi.jlink
-jlink-flash-bootloader-btc-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02-bl-btconly-development.jlink
-jlink-flash-bootloader-btc: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bb02-bl-btc.jlink
+
+# Per-product development stage0/stage1 J-Link wrappers flash already-built images.
+jlink-flash-bootloader-stage0-bitbox02-btconly-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02-btconly-development.jlink
+jlink-flash-bootloader-stage0-bitbox02-multi-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02-multi-development.jlink
+jlink-flash-bootloader-stage0-bitbox02nova-btconly-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02nova-btconly-development.jlink
+jlink-flash-bootloader-stage0-bitbox02nova-multi-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02nova-multi-development.jlink
+jlink-flash-bootloader-stage1-bitbox02-btconly-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02-btconly-development.jlink
+jlink-flash-bootloader-stage1-bitbox02-multi-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02-multi-development.jlink
+jlink-flash-bootloader-stage1-bitbox02nova-btconly-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02nova-btconly-development.jlink
+jlink-flash-bootloader-stage1-bitbox02nova-multi-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02nova-multi-development.jlink
+
jlink-flash-firmware: | build
JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware.jlink
jlink-flash-firmware-btc: | build
@@ -151,6 +195,16 @@ jlink-flash-factory-setup: | build
JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/factory-setup.jlink
jlink-flash-firmware-debug: | build
JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build-debug/scripts/firmware.jlink
+
+jlink-flash-firmware-blupgrade-bitbox02-btconly-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02-btconly-development.jlink
+jlink-flash-firmware-blupgrade-bitbox02-multi-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02-multi-development.jlink
+jlink-flash-firmware-blupgrade-bitbox02nova-btconly-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02nova-btconly-development.jlink
+jlink-flash-firmware-blupgrade-bitbox02nova-multi-development: | build
+ JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02nova-multi-development.jlink
+
jlink-flash-set-new-screen:
JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/set-new-screen.jlink
jlink-flash-set-original-screen:
@@ -171,8 +225,10 @@ rtt-client:
telnet localhost 19021
run-debug:
arm-none-eabi-gdb -x scripts/jlink.gdb build-debug/bin/firmware.elf
-run-bootloader:
- arm-none-eabi-gdb -x scripts/jlink-bootloader.gdb build/bin/bb02p-bl-multi-development.elf
+run-bootloader-stage0:
+ arm-none-eabi-gdb -x scripts/jlink-bootloader-stage0.gdb build/bin/bootloader-stage0-bitbox02-multi-development.elf
+run-bootloader-stage1:
+ arm-none-eabi-gdb -x scripts/jlink-bootloader-stage1.gdb build/bin/bootloader-stage1-bitbox02-multi-development.elf
run-factory-setup-debug:
arm-none-eabi-gdb -x scripts/jlink.gdb build-debug/bin/factory-setup.elf
dockerinit:
diff --git a/bootloader-stage0.ld b/bootloader-stage0.ld
new file mode 100644
index 00000000..61b178a0
--- /dev/null
+++ b/bootloader-stage0.ld
@@ -0,0 +1,122 @@
+OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
+OUTPUT_ARCH(arm)
+SEARCH_DIR(.)
+
+STAGE0_IMAGE_ORIGIN = 0x00000000;
+STAGE0_IMAGE_LEN = 0x00002000;
+STAGE0_DESCRIPTOR_LEN = 0x0000000C;
+STAGE0_DESCRIPTOR_ADDR = STAGE0_IMAGE_ORIGIN + STAGE0_IMAGE_LEN - STAGE0_DESCRIPTOR_LEN;
+
+MEMORY
+{
+ rom (rx) : ORIGIN = STAGE0_IMAGE_ORIGIN, LENGTH = STAGE0_IMAGE_LEN - STAGE0_DESCRIPTOR_LEN
+ stage0_descriptor (rx) : ORIGIN = STAGE0_DESCRIPTOR_ADDR, LENGTH = STAGE0_DESCRIPTOR_LEN
+ ram (rwx) : ORIGIN = 0x20000200, LENGTH = 0x0003FE00
+}
+
+STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : 0x1000;
+HEAP_SIZE = DEFINED(HEAP_SIZE) ? HEAP_SIZE : 0x0;
+
+SECTIONS
+{
+ .text :
+ {
+ . = ALIGN(4);
+ _sfixed = .;
+ __stage0_vectors_start = .;
+ KEEP(*(.vectors .vectors.*))
+ __stage0_vectors_end = .;
+ *(.text .text.* .gnu.linkonce.t.*)
+ *(.glue_7t) *(.glue_7)
+ *(.rodata .rodata* .gnu.linkonce.r.*)
+ *(.ARM.extab* .gnu.linkonce.armextab.*)
+
+ . = ALIGN(4);
+ KEEP(*(.init))
+ . = ALIGN(4);
+ __preinit_array_start = .;
+ KEEP (*(.preinit_array))
+ __preinit_array_end = .;
+ . = ALIGN(4);
+ __init_array_start = .;
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array))
+ __init_array_end = .;
+ . = ALIGN(4);
+ KEEP(*(.fini))
+ . = ALIGN(4);
+ __fini_array_start = .;
+ KEEP (*(.fini_array))
+ KEEP (*(SORT(.fini_array.*)))
+ __fini_array_end = .;
+ . = ALIGN(4);
+ _efixed = .;
+ } > rom
+
+ PROVIDE_HIDDEN (__exidx_start = .);
+ .ARM.exidx :
+ {
+ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+ } > rom
+ PROVIDE_HIDDEN (__exidx_end = .);
+
+ . = ALIGN(4);
+ _etext = .;
+
+ .relocate :
+ {
+ . = ALIGN(4);
+ _srelocate = .;
+ *(.ramfunc .ramfunc.*);
+ *(.data .data.*);
+ . = ALIGN(4);
+ _erelocate = .;
+ } > ram AT> rom
+
+ .stage0_descriptor ORIGIN(stage0_descriptor) :
+ {
+ . = ALIGN(4);
+ KEEP(*(.stage0_descriptor))
+ } > stage0_descriptor
+
+ .bss (NOLOAD) :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ _szero = .;
+ *(.bss .bss.*)
+ *(COMMON)
+ . = ALIGN(4);
+ _ebss = .;
+ _ezero = .;
+ } > ram
+
+ .stack (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sstack = .;
+ . = . + STACK_SIZE;
+ . = ALIGN(8);
+ _estack = .;
+ } > ram
+
+ . = ALIGN(4);
+ _end = .;
+
+ .heap (NOLOAD):
+ {
+ . = ALIGN(8);
+ _heap_start = .;
+ . = . + HEAP_SIZE;
+ . = ALIGN(8);
+ _heap_end = .;
+ } > ram
+}
+
+ASSERT(__stage0_vectors_start == STAGE0_IMAGE_ORIGIN, "stage0 vector table offset changed")
+ASSERT(ADDR(.stage0_descriptor) == STAGE0_DESCRIPTOR_ADDR, "stage0 descriptor offset changed")
+ASSERT(bb02_stage0_descriptor == STAGE0_DESCRIPTOR_ADDR, "stage0 descriptor symbol changed")
+ASSERT(SIZEOF(.stage0_descriptor) == STAGE0_DESCRIPTOR_LEN, "stage0 descriptor size changed")
+ASSERT(_etext <= STAGE0_DESCRIPTOR_ADDR, "stage0 text overlaps descriptor")
+ASSERT(LOADADDR(.relocate) + SIZEOF(.relocate) <= STAGE0_DESCRIPTOR_ADDR, "stage0 data load overlaps descriptor")
+ASSERT(ADDR(.stage0_descriptor) + SIZEOF(.stage0_descriptor) == STAGE0_IMAGE_ORIGIN + STAGE0_IMAGE_LEN, "stage0 descriptor end changed")
diff --git a/bootloader-stage1.ld b/bootloader-stage1.ld
new file mode 100644
index 00000000..6772f1f9
--- /dev/null
+++ b/bootloader-stage1.ld
@@ -0,0 +1,159 @@
+OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
+OUTPUT_ARCH(arm)
+SEARCH_DIR(.)
+
+MEMORY
+{
+ /* Stage1 lives after the 8kB stage0 block and before the factory randomness. */
+ rom (rx) : ORIGIN = 0x00002000, LENGTH = 0x0000BFE0
+ ram (rwx) : ORIGIN = 0x20000200, LENGTH = 0x0003FE00
+ bkupram (rwx) : ORIGIN = 0x47000000, LENGTH = 0x00002000
+ qspi (rwx) : ORIGIN = 0x04000000, LENGTH = 0x01000000
+}
+
+STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : DEFINED(__stack_size__) ? __stack_size__ : 0x10000;
+HEAP_SIZE = DEFINED(HEAP_SIZE) ? HEAP_SIZE : DEFINED(__heap_size__) ? __heap_size__ : 0x10000;
+
+SECTIONS
+{
+ .stage1_header ORIGIN(rom) :
+ {
+ KEEP(*(.stage1_header))
+ } > rom
+
+ .vectors ORIGIN(rom) + 0x400 :
+ {
+ . = ALIGN(4);
+ _sfixed = .;
+ KEEP(*(.vectors .vectors.*))
+ } > rom
+
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text .text.* .gnu.linkonce.t.*)
+ *(.glue_7t) *(.glue_7)
+ *(.rodata .rodata* .gnu.linkonce.r.*)
+ *(.ARM.extab* .gnu.linkonce.armextab.*)
+
+ . = ALIGN(4);
+ KEEP(*(.init))
+ . = ALIGN(4);
+ __preinit_array_start = .;
+ KEEP (*(.preinit_array))
+ __preinit_array_end = .;
+
+ . = ALIGN(4);
+ __init_array_start = .;
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array))
+ __init_array_end = .;
+
+ . = ALIGN(4);
+ KEEP (*crtbegin.o(.ctors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+ KEEP (*(SORT(.ctors.*)))
+ KEEP (*crtend.o(.ctors))
+
+ . = ALIGN(4);
+ KEEP(*(.fini))
+
+ . = ALIGN(4);
+ __fini_array_start = .;
+ KEEP (*(.fini_array))
+ KEEP (*(SORT(.fini_array.*)))
+ __fini_array_end = .;
+
+ KEEP (*crtbegin.o(.dtors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+ KEEP (*(SORT(.dtors.*)))
+ KEEP (*crtend.o(.dtors))
+
+ . = ALIGN(4);
+ _efixed = .;
+ } > rom
+
+ PROVIDE_HIDDEN (__exidx_start = .);
+ .ARM.exidx :
+ {
+ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+ } > rom
+ PROVIDE_HIDDEN (__exidx_end = .);
+
+ . = ALIGN(4);
+ _etext = .;
+
+ .rtt (NOLOAD) :
+ {
+ . = ALIGN(4);
+ _srtt = .;
+ *(.segger_rtt);
+ *(.segger_rtt_buf);
+ _ertt = .;
+ } > ram
+
+ .relocate :
+ {
+ . = ALIGN(4);
+ _srelocate = .;
+ *(.ramfunc .ramfunc.*);
+ *(.data .data.*);
+ . = ALIGN(4);
+ _erelocate = .;
+ } > ram AT> rom
+
+ .bkupram (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sbkupram = .;
+ *(.bkupram .bkupram.*);
+ . = ALIGN(8);
+ _ebkupram = .;
+ } > bkupram
+
+ .qspi (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sqspi = .;
+ *(.qspi .qspi.*);
+ . = ALIGN(8);
+ _eqspi = .;
+ } > qspi
+
+ .bss (NOLOAD) :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ _szero = .;
+ *(.bss .bss.*)
+ *(COMMON)
+ . = ALIGN(4);
+ _ebss = .;
+ _ezero = .;
+ } > ram
+
+ .stack (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sstack = .;
+ . = . + STACK_SIZE;
+ . = ALIGN(8);
+ _estack = .;
+ } > ram
+
+ . = ALIGN(4);
+ _end = .;
+
+ .heap (NOLOAD):
+ {
+ . = ALIGN(8);
+ _heap_start = .;
+ . = . + HEAP_SIZE;
+ . = ALIGN(8);
+ _heap_end = .;
+ } > ram
+}
+
+ASSERT(SIZEOF(.stage1_header) == 0x400, "stage1 header size changed")
+ASSERT(ADDR(.vectors) == ORIGIN(rom) + 0x400, "stage1 vector table offset changed")
+ASSERT(_etext <= ORIGIN(rom) + LENGTH(rom), "stage1 overlaps factory randomness")
diff --git a/bootloader.ld b/bootloader.ld
deleted file mode 100644
index 1e9d5faa..00000000
--- a/bootloader.ld
+++ /dev/null
@@ -1,187 +0,0 @@
-/**
- * \file
- *
- * \brief Linker script for running in internal FLASH on the SAMD51J20A
- *
- * Copyright (c) 2017 Microchip Technology Inc.
- *
- * \asf_license_start
- *
- * \page License
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the Licence at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an AS IS BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * \asf_license_stop
- *
- */
-
-
-OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
-OUTPUT_ARCH(arm)
-SEARCH_DIR(.)
-
-/* Memory Spaces Definitions */
-MEMORY
-{
- /* Reserve 32 bytes of space for factory install random bytes */
- rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x0000DFE0
- ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000
- bkupram (rwx) : ORIGIN = 0x47000000, LENGTH = 0x00002000
- qspi (rwx) : ORIGIN = 0x04000000, LENGTH = 0x01000000
-}
-
-/* The stack size used by the application. NOTE: Stack and heap sizes should be set in
- * CMakeLists.txt */
-STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : DEFINED(__stack_size__) ? __stack_size__ : 0x10000;
-HEAP_SIZE = DEFINED(HEAP_SIZE) ? HEAP_SIZE : DEFINED(__heap_size__) ? __heap_size__ : 0x10000;
-
-/* Section Definitions */
-SECTIONS
-{
- .text :
- {
- . = ALIGN(4);
- _sfixed = .;
- KEEP(*(.vectors .vectors.*))
- *(.text .text.* .gnu.linkonce.t.*)
- *(.glue_7t) *(.glue_7)
- *(.rodata .rodata* .gnu.linkonce.r.*)
- *(.ARM.extab* .gnu.linkonce.armextab.*)
-
- /* Support C constructors, and C destructors in both user code
- and the C library. This also provides support for C++ code. */
- . = ALIGN(4);
- KEEP(*(.init))
- . = ALIGN(4);
- __preinit_array_start = .;
- KEEP (*(.preinit_array))
- __preinit_array_end = .;
-
- . = ALIGN(4);
- __init_array_start = .;
- KEEP (*(SORT(.init_array.*)))
- KEEP (*(.init_array))
- __init_array_end = .;
-
- . = ALIGN(4);
- KEEP (*crtbegin.o(.ctors))
- KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
- KEEP (*(SORT(.ctors.*)))
- KEEP (*crtend.o(.ctors))
-
- . = ALIGN(4);
- KEEP(*(.fini))
-
- . = ALIGN(4);
- __fini_array_start = .;
- KEEP (*(.fini_array))
- KEEP (*(SORT(.fini_array.*)))
- __fini_array_end = .;
-
- KEEP (*crtbegin.o(.dtors))
- KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
- KEEP (*(SORT(.dtors.*)))
- KEEP (*crtend.o(.dtors))
-
- . = ALIGN(4);
- _efixed = .; /* End of text section */
- } > rom
-
- /* .ARM.exidx is sorted, so has to go in its own output section. */
- PROVIDE_HIDDEN (__exidx_start = .);
- .ARM.exidx :
- {
- *(.ARM.exidx* .gnu.linkonce.armexidx.*)
- } > rom
- PROVIDE_HIDDEN (__exidx_end = .);
-
- . = ALIGN(4);
- _etext = .;
-
- /* Place RTT allocations first in RAM in debug builds, so that they are
- * aligned between bootloader and firmware */
- .rtt (NOLOAD) :
- {
- . = ALIGN(4);
- _srtt = .;
- *(.segger_rtt);
- *(.segger_rtt_buf);
- _ertt = .;
- } > ram
-
- .relocate :
- {
- . = ALIGN(4);
- _srelocate = .;
- *(.ramfunc .ramfunc.*);
- *(.data .data.*);
- . = ALIGN(4);
- _erelocate = .;
- } > ram AT> rom
-
- .bkupram (NOLOAD):
- {
- . = ALIGN(8);
- _sbkupram = .;
- *(.bkupram .bkupram.*);
- . = ALIGN(8);
- _ebkupram = .;
- } > bkupram
-
- .qspi (NOLOAD):
- {
- . = ALIGN(8);
- _sqspi = .;
- *(.qspi .qspi.*);
- . = ALIGN(8);
- _eqspi = .;
- } > qspi
-
- /* .bss section which is used for uninitialized data */
- .bss (NOLOAD) :
- {
- . = ALIGN(4);
- _sbss = . ;
- _szero = .;
- *(.bss .bss.*)
- *(COMMON)
- . = ALIGN(4);
- _ebss = . ;
- _ezero = .;
- } > ram
-
- /* stack section */
- .stack (NOLOAD):
- {
- . = ALIGN(8);
- _sstack = .;
- . = . + STACK_SIZE;
- . = ALIGN(8);
- _estack = .;
- } > ram
-
- . = ALIGN(4);
- _end = . ;
-
- /* heap section */
- .heap (NOLOAD):
- {
- . = ALIGN(8);
- _heap_start = .;
- . = . + HEAP_SIZE;
- . = ALIGN(8);
- _heap_end = .;
- } > ram
-}
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 178d2013..a5ffa653 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -35,6 +35,67 @@ if(CMAKE_CROSSCOMPILING)
PUBLIC
CMSIS
)
+ target_compile_options(samd51a-ds PRIVATE
+ -flto
+ -ffat-lto-objects
+ )
+
+ # Advanced Software Framework Drivers
+ # Only drivers needed for stage0.
+ add_library(asf4-drivers-stage0 STATIC
+ asf4-drivers/hal/src/hal_delay.c
+ asf4-drivers/hal/src/hal_flash.c
+ asf4-drivers/hal/src/hal_pac.c
+ asf4-drivers/hal/src/hal_rand_sync.c
+ asf4-drivers/hal/src/hal_sha_sync.c
+ asf4-drivers/hpl/gclk/hpl_gclk.c
+ asf4-drivers/hpl/oscctrl/hpl_oscctrl.c
+ asf4-drivers/hpl/mclk/hpl_mclk.c
+ asf4-drivers/hpl/osc32kctrl/hpl_osc32kctrl.c
+ asf4-drivers/hpl/core/hpl_init.c
+ asf4-drivers/hpl/core/hpl_core_m4.c
+ asf4-drivers/hpl/systick/hpl_systick.c
+ asf4-drivers/hpl/nvmctrl/hpl_nvmctrl.c
+ asf4-drivers/hpl/pac/hpl_pac.c
+ asf4-drivers/hpl/icm/hpl_icm.c
+ asf4-drivers/hpl/trng/hpl_trng.c
+ asf4-drivers/hpl/spi/spi_lite.c
+ )
+
+ target_compile_options(asf4-drivers-stage0 PRIVATE
+ -flto
+ -ffat-lto-objects
+ -Wno-cast-qual
+ -Wno-unused-parameter
+ -Wno-missing-prototypes
+ -Wno-missing-declarations
+ -Wno-bad-function-cast
+ -Wno-strict-prototypes
+ -Wno-old-style-definition
+ -Wno-cast-align
+ )
+
+ if (CMAKE_BUILD_TYPE STREQUAL "DEBUG")
+ target_compile_definitions(asf4-drivers-stage0 PUBLIC DEBUG USE_SIMPLE_ASSERT)
+ endif()
+
+ target_link_libraries(asf4-drivers-stage0 samd51a-ds)
+ set_property(TARGET asf4-drivers-stage0 PROPERTY INTERFACE_LINK_LIBRARIES "")
+
+ target_include_directories(asf4-drivers-stage0 SYSTEM
+ PUBLIC
+ asf4-drivers
+ asf4-drivers/Config
+ asf4-drivers/hal/include
+ asf4-drivers/hal/utils/include
+ asf4-drivers/hpl/core
+ asf4-drivers/hpl/gclk
+ asf4-drivers/hpl/port
+ asf4-drivers/hpl/pukcc
+ asf4-drivers/hpl/rtc
+ asf4-drivers/hpl/spi
+ asf4-drivers/hri
+ )
# Advanced Software Framework Drivers
# Only drivers needed for bootloader (minimal set)
diff --git a/firmware-blupgrade.ld b/firmware-blupgrade.ld
new file mode 100644
index 00000000..cbd3c202
--- /dev/null
+++ b/firmware-blupgrade.ld
@@ -0,0 +1,155 @@
+OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
+OUTPUT_ARCH(arm)
+SEARCH_DIR(.)
+
+MEMORY
+{
+ rom (rx) : ORIGIN = 0x00010000, LENGTH = 0x000C7000
+ bootloader_update (rx) : ORIGIN = 0x000DB000, LENGTH = 0x0000C000
+ ram (rwx) : ORIGIN = 0x20000200, LENGTH = 0x0003FE00
+ bkupram (rwx) : ORIGIN = 0x47000000, LENGTH = 0x00002000
+ qspi (rwx) : ORIGIN = 0x04000000, LENGTH = 0x01000000
+}
+
+ASSERT(ORIGIN(rom) + LENGTH(rom) <= 0x000D8000, "blupgrade rom overlaps factory randomness backup slot")
+ASSERT(ORIGIN(bootloader_update) >= 0x000DA000, "bootloader update slot overlaps factory randomness backup slot")
+
+STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : DEFINED(__stack_size__) ? __stack_size__ : 0x10000;
+HEAP_SIZE = DEFINED(HEAP_SIZE) ? HEAP_SIZE : DEFINED(__heap_size__) ? __heap_size__ : 0x10000;
+
+SECTIONS
+{
+ .text :
+ {
+ . = ALIGN(4);
+ _sfixed = .;
+ KEEP(*(.vectors .vectors.*))
+ *(.text .text.* .gnu.linkonce.t.*)
+ *(.glue_7t) *(.glue_7)
+ *(.rodata .rodata* .gnu.linkonce.r.*)
+ *(.ARM.extab* .gnu.linkonce.armextab.*)
+
+ . = ALIGN(4);
+ KEEP(*(.init))
+ . = ALIGN(4);
+ __preinit_array_start = .;
+ KEEP (*(.preinit_array))
+ __preinit_array_end = .;
+
+ . = ALIGN(4);
+ __init_array_start = .;
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array))
+ __init_array_end = .;
+
+ . = ALIGN(4);
+ KEEP (*crtbegin.o(.ctors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+ KEEP (*(SORT(.ctors.*)))
+ KEEP (*crtend.o(.ctors))
+
+ . = ALIGN(4);
+ KEEP(*(.fini))
+
+ . = ALIGN(4);
+ __fini_array_start = .;
+ KEEP (*(.fini_array))
+ KEEP (*(SORT(.fini_array.*)))
+ __fini_array_end = .;
+
+ KEEP (*crtbegin.o(.dtors))
+ KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+ KEEP (*(SORT(.dtors.*)))
+ KEEP (*crtend.o(.dtors))
+
+ . = ALIGN(4);
+ _efixed = .;
+ } > rom
+
+ PROVIDE_HIDDEN (__exidx_start = .);
+ .ARM.exidx :
+ {
+ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+ } > rom
+ PROVIDE_HIDDEN (__exidx_end = .);
+
+ . = ALIGN(4);
+ _etext = .;
+
+ .rtt (NOLOAD) :
+ {
+ . = ALIGN(4);
+ _srtt = .;
+ *(.segger_rtt);
+ *(.segger_rtt_buf);
+ _ertt = .;
+ } > ram
+
+ .relocate :
+ {
+ . = ALIGN(4);
+ _srelocate = .;
+ *(.ramfunc .ramfunc.*);
+ *(.data .data.*);
+ . = ALIGN(4);
+ _erelocate = .;
+ } > ram AT> rom
+
+ .bkupram (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sbkupram = .;
+ *(.bkupram .bkupram.*);
+ . = ALIGN(8);
+ _ebkupram = .;
+ } > bkupram
+
+ .qspi (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sqspi = .;
+ *(.qspi .qspi.*);
+ . = ALIGN(8);
+ _eqspi = .;
+ } > qspi
+
+ .bss (NOLOAD) :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ _szero = .;
+ *(.bss .bss.*)
+ *(COMMON)
+ . = ALIGN(4);
+ _ebss = .;
+ _ezero = .;
+ } > ram
+
+ .stack (NOLOAD):
+ {
+ . = ALIGN(8);
+ _sstack = .;
+ . = . + STACK_SIZE;
+ . = ALIGN(8);
+ _estack = .;
+ } > ram
+
+ . = ALIGN(4);
+ _end = .;
+
+ .heap (NOLOAD):
+ {
+ . = ALIGN(8);
+ _heap_start = .;
+ . = . + HEAP_SIZE;
+ . = ALIGN(8);
+ _heap_end = .;
+ } > ram
+
+ .bootloader_update ORIGIN(bootloader_update) :
+ {
+ FILL(0xff)
+ KEEP(*(.bootloader_update))
+ . = ORIGIN(bootloader_update) + LENGTH(bootloader_update);
+ } > bootloader_update =0xff
+}
diff --git a/firmware.ld b/firmware.ld
index f06a9d78..101225e6 100644
--- a/firmware.ld
+++ b/firmware.ld
@@ -36,7 +36,7 @@ SEARCH_DIR(.)
MEMORY
{
rom (rx) : ORIGIN = 0x00010000, LENGTH = 0x000D8000
- ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000
+ ram (rwx) : ORIGIN = 0x20000200, LENGTH = 0x0003FE00
bkupram (rwx) : ORIGIN = 0x47000000, LENGTH = 0x00002000
qspi (rwx) : ORIGIN = 0x04000000, LENGTH = 0x01000000
}
diff --git a/py/bitbox02/bitbox02/bitbox02/bootloader.py b/py/bitbox02/bitbox02/bitbox02/bootloader.py
index 5834bd36..640503d7 100644
--- a/py/bitbox02/bitbox02/bitbox02/bootloader.py
+++ b/py/bitbox02/bitbox02/bitbox02/bootloader.py
@@ -10,6 +10,8 @@ import hashlib
import enum
from typing import TypedDict
+import semver
+
from bitbox02.communication import TransportLayer
from bitbox02.communication.devices import (
@@ -24,6 +26,7 @@ from bitbox02.communication.devices import (
BOOTLOADER_CMD = 0x80 + 0x40 + 0x03
NUM_ROOT_KEYS = 3
NUM_SIGNING_KEYS = 3
+BOOTLOADER_NEW_SIGHASH_VERSION = semver.VersionInfo(1, 2, 0)
MAX_FIRMWARE_SIZE = 884736 # 928kB - 64kB
CHUNK_SIZE = 4096
@@ -36,6 +39,11 @@ SIGDATA_MAGIC_BITBOX02_BTCONLY = struct.pack(">I", 0x11233B0B)
SIGDATA_MAGIC_BITBOX02PLUS_MULTI = struct.pack(">I", 0x5B648CEB)
SIGDATA_MAGIC_BITBOX02PLUS_BTCONLY = struct.pack(">I", 0x48714774)
+PRODUCT_ID_BITBOX02_MULTI = 1
+PRODUCT_ID_BITBOX02_BTCONLY = 2
+PRODUCT_ID_BITBOX02PLUS_MULTI = 3
+PRODUCT_ID_BITBOX02PLUS_BTCONLY = 4
+
MAGIC_LEN = 4
VERSION_LEN = 4
@@ -90,11 +98,18 @@ class Bootloader:
BITBOX02PLUS_MULTI_BOOTLOADER: SIGDATA_MAGIC_BITBOX02PLUS_MULTI,
BITBOX02PLUS_BTC_BOOTLOADER: SIGDATA_MAGIC_BITBOX02PLUS_BTCONLY,
}.get(device_info["product_string"])
+ self.product_id = {
+ BB02MULTI_BOOTLOADER: PRODUCT_ID_BITBOX02_MULTI,
+ BB02BTC_BOOTLOADER: PRODUCT_ID_BITBOX02_BTCONLY,
+ BITBOX02PLUS_MULTI_BOOTLOADER: PRODUCT_ID_BITBOX02PLUS_MULTI,
+ BITBOX02PLUS_BTC_BOOTLOADER: PRODUCT_ID_BITBOX02PLUS_BTCONLY,
+ }.get(device_info["product_string"])
self.version = parse_device_version(device_info["serial_number"])
# Delete the prelease part, as it messes with the comparison (e.g. 3.0.0-pre < 3.0.0 is
# True, but the 3.0.0-pre has already the same API breaking changes like 3.0.0...).
self.version = self.version.replace(prerelease=None)
assert self.expected_magic
+ assert self.product_id
def _query(self, msg: bytes) -> bytes:
cid = self._transport.generate_cid()
@@ -215,6 +230,12 @@ class Bootloader:
"""
self._erase(0)
+ def _empty_firmware_hash(self, firmware_v: int) -> bytes:
+ empty_firmware = struct.pack("<I", firmware_v) + b"\xff" * MAX_FIRMWARE_SIZE
+ if self.version >= BOOTLOADER_NEW_SIGHASH_VERSION:
+ return hashlib.sha256(struct.pack("<H", self.product_id) + empty_firmware).digest()
+ return hashlib.sha256(hashlib.sha256(empty_firmware).digest()).digest()
+
def erased(self) -> bool:
"""
Returns True if the the device contains no firmware.
@@ -222,8 +243,7 @@ class Bootloader:
# We check by comparing the device reported firmware hash.
# If erased, the firmware is all '\xFF'.
firmware_v, _ = self.versions()
- empty_firmware = struct.pack("<I", firmware_v) + b"\xff" * MAX_FIRMWARE_SIZE
- empty_firmware_hash = hashlib.sha256(hashlib.sha256(empty_firmware).digest()).digest()
+ empty_firmware_hash = self._empty_firmware_hash(firmware_v)
reported_firmware_hash, _ = self.get_hashes()
return empty_firmware_hash == reported_firmware_hash
diff --git a/releases/describe_signed_firmware.py b/releases/describe_signed_firmware.py
index e024992d..3e7b5831 100755
--- a/releases/describe_signed_firmware.py
+++ b/releases/describe_signed_firmware.py
@@ -17,6 +17,11 @@ MAGIC_BTCONLY = struct.pack(">I", 0x11233B0B)
MAGIC_BITBOX02PLUS_MULTI = struct.pack(">I", 0x5B648CEB)
MAGIC_BITBOX02PLUS_BTCONLY = struct.pack(">I", 0x48714774)
+PRODUCT_ID_BITBOX02_MULTI = 1
+PRODUCT_ID_BITBOX02_BTCONLY = 2
+PRODUCT_ID_BITBOX02PLUS_MULTI = 3
+PRODUCT_ID_BITBOX02PLUS_BTCONLY = 4
+
MAX_FIRMWARE_SIZE = 884736
NUM_ROOT_KEYS = 3
NUM_SIGNING_KEYS = 3
@@ -24,6 +29,7 @@ VERSION_LEN = 4
SIGNING_PUBKEYS_DATA_LEN = VERSION_LEN + NUM_SIGNING_KEYS * 64 + NUM_ROOT_KEYS * 64
FIRMWARE_DATA_LEN = VERSION_LEN + NUM_SIGNING_KEYS * 64
SIGDATA_LEN = SIGNING_PUBKEYS_DATA_LEN + FIRMWARE_DATA_LEN
+NEW_SIGHASH_VERSION_CUTOFF = 50
def main() -> int:
@@ -47,12 +53,16 @@ def main() -> int:
if magic == MAGIC_MULTI:
print("This is a BitBox02 Multi firmware.")
+ product_id = PRODUCT_ID_BITBOX02_MULTI
elif magic == MAGIC_BTCONLY:
print("This is a BitBox02 Bitcoin-only firmware.")
+ product_id = PRODUCT_ID_BITBOX02_BTCONLY
elif magic == MAGIC_BITBOX02PLUS_MULTI:
print("This is a BitBox02 Nova Multi firmware")
+ product_id = PRODUCT_ID_BITBOX02PLUS_MULTI
elif magic == MAGIC_BITBOX02PLUS_BTCONLY:
print("This is a BitBox02 Nova Bitcoin-only firmware.")
+ product_id = PRODUCT_ID_BITBOX02PLUS_BTCONLY
else:
print(
f"Unrecognized firmware edition; magic = f{magic.hex()}. Maybe you have accidentally invoked this script on an unsigned binary. Make sure to use a signed firmware binary."
@@ -68,9 +78,13 @@ def main() -> int:
print("The hash of the unsigned firmware binary is (compare with reproducible build):")
print(hashlib.sha256(firmware).hexdigest())
version = sigdata[SIGNING_PUBKEYS_DATA_LEN:][:VERSION_LEN]
- print("The monotonic firmware version is:", struct.unpack("<I", version)[0])
- print("The hash of the firmware as verified/shown by the bootloader is:")
- print(hashlib.sha256(hashlib.sha256(version + firmware_padded).digest()).hexdigest())
+ monotonic_version = struct.unpack("<I", version)[0]
+ print("The monotonic firmware version is:", monotonic_version)
+ print("The firmware sighash as verified/shown by the bootloader is:")
+ if monotonic_version >= NEW_SIGHASH_VERSION_CUTOFF:
+ print(hashlib.sha256(struct.pack("<H", product_id) + version + firmware_padded).hexdigest())
+ else:
+ print(hashlib.sha256(hashlib.sha256(version + firmware_padded).digest()).hexdigest())
return 0
diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt
index ab8e6672..3cc6f815 100644
--- a/scripts/CMakeLists.txt
+++ b/scripts/CMakeLists.txt
@@ -8,11 +8,23 @@ if(PYTHONINTERP_FOUND)
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
endforeach()
+ foreach(target ${BB02_BLUPD_FIRMWARE_TARGETS})
+ execute_process(
+ COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-firmware.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ endforeach()
+
+ foreach(target ${BB02_STAGE0_TARGETS})
+ execute_process(
+ COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-stage0.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ endforeach()
- # This template flashes without an offset
- foreach(target ${BOOTLOADERS})
+ foreach(target ${BB02_STAGE1_TARGETS})
execute_process(
- COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-bootloader.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
+ COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-stage1.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
endforeach()
diff --git a/scripts/bootloader_update.py b/scripts/bootloader_update.py
new file mode 100644
index 00000000..ca8202bf
--- /dev/null
+++ b/scripts/bootloader_update.py
@@ -0,0 +1,362 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0
+
+import argparse
+import hashlib
+import struct
+from pathlib import Path
+
+import ecdsa
+
+
+STAGE1_HEADER_MAGIC = 0x31534242
+STAGE0_DESCRIPTOR_MAGIC = 0x30534242
+STAGE1_HEADER_FORMAT_VERSION = 1
+STAGE1_HEADER_LEN = 1024
+STAGE1_HEADER_ALIGNMENT = 1024
+BOOTLOADER_UPGRADE_STAGE0_LEN = 0x2000
+STAGE0_DESCRIPTOR_LEN = 12
+STAGE0_DESCRIPTOR_OFFSET = BOOTLOADER_UPGRADE_STAGE0_LEN - STAGE0_DESCRIPTOR_LEN
+STAGE0_DESCRIPTOR_FORMAT = "<HHII"
+STAGE1_VECTOR_OFFSET = 0x400
+STAGE1_MAX_LEN = 0xBFE0
+BOOTLOADER_UPGRADE_PAYLOAD_LEN = 0xC000
+STAGE1_ROOT_KEY_COUNT = 3
+STAGE1_SIGNATURE_THRESHOLD = 2
+STAGE1_SIGNATURE_LEN = 64
+STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN = 37
+STAGE0_DESCRIPTOR_FLAG_DEVELOPMENT = 1 << 0
+STAGE1_HEADER_FLAG_DEVELOPMENT = 1 << 0
+STAGE1_HEADER_ALLOWED_FLAGS = STAGE1_HEADER_FLAG_DEVELOPMENT
+STAGE1_HEADER_RESERVED_LEN = 768
+STAGE1_HEADER_PREFIX_FORMAT = (
+ f"<IIHHIQHB{STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN}s{STAGE1_HEADER_RESERVED_LEN}s"
+)
+STAGE1_HEADER_SIGNED_LEN = struct.calcsize(STAGE1_HEADER_PREFIX_FORMAT)
+STAGE1_HEADER_SIGNATURES_LEN = STAGE1_ROOT_KEY_COUNT * STAGE1_SIGNATURE_LEN
+PRODUCT_IDS = {
+ "bitbox-multi": 1,
+ "bitbox-btconly": 2,
+ "bitbox-plus-multi": 3,
+ "bitbox-plus-btconly": 4,
+}
+# Keep in sync with src/bootloader_upgrade/stage1_pubkeys.c.
+STAGE1_ROOT_PUBKEYS = (
+ bytes.fromhex(
+ "3a2d538f0e6db286287f5dfbf3046c2b436ead5f0153b0becb4561956016220e"
+ "750e49a7a4ba412ecace07f286c0b34f6a0eb2d952e396a3ebabda4355d8e677"
+ ),
+ bytes.fromhex(
+ "499370daa90cb008804237c62c7db4cb54eefed0430a3dcde7de57a61ae64ad3"
+ "bb163a031ab2cc5647aa74e261c023effede98e64bbe58b019fb4f7180f6872f"
+ ),
+ bytes.fromhex(
+ "4861aeb6b10526b73e97c6807918e9de8b99d498844c544cf22a6449a2120cf2"
+ "9011f7eecc147f56f64dfae32e963bebd3408ee5120cd87123cf4db96e936c04"
+ ),
+)
+STAGE1_ROOT_VERIFYING_KEYS = [
+ ecdsa.VerifyingKey.from_string(pubkey, curve=ecdsa.NIST256p) for pubkey in STAGE1_ROOT_PUBKEYS
+]
+
+assert STAGE1_HEADER_SIGNED_LEN == 832
+assert STAGE1_HEADER_SIGNED_LEN + STAGE1_HEADER_SIGNATURES_LEN == STAGE1_HEADER_LEN
+assert STAGE1_VECTOR_OFFSET == 1024
+assert STAGE1_VECTOR_OFFSET == STAGE1_HEADER_LEN
+assert struct.calcsize(STAGE0_DESCRIPTOR_FORMAT) == STAGE0_DESCRIPTOR_LEN
+assert len(STAGE1_ROOT_VERIFYING_KEYS) == STAGE1_ROOT_KEY_COUNT
+assert all(len(pubkey) == 64 for pubkey in STAGE1_ROOT_PUBKEYS)
+
+
+def _parse_product_id(value: str) -> int:
+ if value in PRODUCT_IDS:
+ return PRODUCT_IDS[value]
+ product_id = int(value, 0)
+ if product_id < 0 or product_id > 0xFFFF:
+ raise argparse.ArgumentTypeError("product id must fit in uint16_t")
+ return product_id
+
+
+def _decode_stage1_marketing_version(
+ stage1_marketing_version_len: int, stage1_marketing_version_field: bytes
+) -> str:
+ if (
+ stage1_marketing_version_len == 0
+ or stage1_marketing_version_len > STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN
+ ):
+ raise RuntimeError("invalid stage1 marketing version length")
+ version = stage1_marketing_version_field[:stage1_marketing_version_len]
+ padding = stage1_marketing_version_field[stage1_marketing_version_len:]
+ if any(padding):
+ raise RuntimeError("stage1 marketing version padding is not zero")
+ if any(ch < 0x21 or ch > 0x7E for ch in version):
+ raise RuntimeError("stage1 marketing version contains non-printable bytes")
+ return version.decode("ascii")
+
+
+def _unpack_header(header: bytes) -> dict:
+ if len(header) != STAGE1_HEADER_LEN:
+ raise RuntimeError("invalid header length")
+ values = struct.unpack(STAGE1_HEADER_PREFIX_FORMAT, header[:STAGE1_HEADER_SIGNED_LEN])
+ sigs = []
+ for i in range(STAGE1_ROOT_KEY_COUNT):
+ start = STAGE1_HEADER_SIGNED_LEN + i * STAGE1_SIGNATURE_LEN
+ sigs.append(header[start : start + STAGE1_SIGNATURE_LEN])
+ return {
+ "prefix": header[:STAGE1_HEADER_SIGNED_LEN],
+ "magic": values[0],
+ "flags": values[1],
+ "header_version": values[2],
+ "product_id": values[3],
+ "header_len": values[4],
+ "image_len": values[5],
+ "monotonic_version": values[6],
+ "stage1_marketing_version_len": values[7],
+ "stage1_marketing_version_field": values[8],
+ "stage1_marketing_version": _decode_stage1_marketing_version(values[7], values[8]),
+ "reserved": values[9],
+ "signatures": sigs,
+ }
+
+
+def _pack_prefix(header: dict) -> bytes:
+ return struct.pack(
+ STAGE1_HEADER_PREFIX_FORMAT,
+ STAGE1_HEADER_MAGIC,
+ header["flags"],
+ STAGE1_HEADER_FORMAT_VERSION,
+ header["product_id"],
+ header["header_len"],
+ header["image_len"],
+ header["monotonic_version"],
+ header["stage1_marketing_version_len"],
+ header["stage1_marketing_version_field"],
+ header["reserved"],
+ )
+
+
+def _pack_header(prefix: bytes, sigs: list[bytes]) -> bytes:
+ if len(prefix) != STAGE1_HEADER_SIGNED_LEN:
+ raise RuntimeError("invalid signed header prefix length")
+ if len(sigs) != STAGE1_ROOT_KEY_COUNT or any(len(sig) != STAGE1_SIGNATURE_LEN for sig in sigs):
+ raise RuntimeError("invalid header signatures")
+ return prefix + b"".join(sigs)
+
+
+def _stage1_signed_digest(image: bytes) -> bytes:
+ header = _unpack_header(image[:STAGE1_HEADER_LEN])
+ if len(image) <= header["header_len"]:
+ raise RuntimeError("stage1 image does not contain a vector table")
+ signed_header_len = header["header_len"] - STAGE1_HEADER_SIGNATURES_LEN
+ hasher = hashlib.sha256()
+ hasher.update(image[:signed_header_len])
+ hasher.update(image[header["header_len"] :])
+ return hasher.digest()
+
+
+def _signatures_are_zero(header: dict) -> bool:
+ return not any(byte for signature in header["signatures"] for byte in signature)
+
+
+def _verify_header_signatures(header: dict, image: bytes) -> None:
+ digest = _stage1_signed_digest(image)
+ valid = 0
+ for verifying_key, signature in zip(STAGE1_ROOT_VERIFYING_KEYS, header["signatures"]):
+ try:
+ if verifying_key.verify(
+ signature,
+ digest,
+ hashfunc=hashlib.sha256,
+ sigdecode=ecdsa.util.sigdecode_string,
+ ):
+ valid += 1
+ except ecdsa.BadSignatureError:
+ pass
+ if valid < STAGE1_SIGNATURE_THRESHOLD:
+ raise RuntimeError("stage1 header signatures do not verify")
+
+
+def _validate_fixed_fields(header: dict, expected_product_id: int | None = None) -> None:
+ if header["magic"] != STAGE1_HEADER_MAGIC:
+ raise RuntimeError("invalid header magic")
+ if header["header_version"] != STAGE1_HEADER_FORMAT_VERSION:
+ raise RuntimeError("invalid header version")
+ if header["product_id"] not in PRODUCT_IDS.values():
+ raise RuntimeError("invalid product id")
+ if expected_product_id is not None and header["product_id"] != expected_product_id:
+ raise RuntimeError("product id mismatch")
+ if header["flags"] & ~STAGE1_HEADER_ALLOWED_FLAGS:
+ raise RuntimeError("invalid stage1 header flags")
+ if header["header_len"] != STAGE1_HEADER_LEN:
+ raise RuntimeError("invalid stage1 header length")
+ if header["header_len"] % STAGE1_HEADER_ALIGNMENT != 0:
+ raise RuntimeError("invalid stage1 header alignment")
+ if any(header["reserved"]):
+ raise RuntimeError("reserved header bytes are not zero")
+
+
+def _validate_raw_stage1(image: bytes) -> dict:
+ header = _unpack_header(image[:STAGE1_HEADER_LEN])
+ _validate_fixed_fields(header)
+ if header["image_len"] != 0:
+ raise RuntimeError("raw stage1 image length field is not zero")
+ if not _signatures_are_zero(header):
+ raise RuntimeError("raw stage1 signatures are not zero")
+ if len(image) <= header["header_len"] or len(image) > STAGE1_MAX_LEN:
+ raise RuntimeError("raw stage1 image length is invalid")
+ return header
+
+
+def _validate_complete_stage1(
+ image: bytes,
+ expected_product_id: int | None,
+ require_signatures: bool,
+) -> dict:
+ header = _unpack_header(image[:STAGE1_HEADER_LEN])
+ _validate_fixed_fields(header, expected_product_id)
+ if header["image_len"] != len(image):
+ raise RuntimeError("image length does not match header")
+ if header["image_len"] <= header["header_len"] or header["image_len"] > STAGE1_MAX_LEN:
+ raise RuntimeError("stage1 image length is invalid")
+ if require_signatures:
+ _verify_header_signatures(header, image)
+ elif not _signatures_are_zero(header):
+ raise RuntimeError("unsigned stage1 signatures are not zero")
+ return header
+
+
+def _write_if_changed(path: Path, data: bytes) -> None:
+ if path.exists() and path.read_bytes() == data:
+ return
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_bytes(data)
+
+
+def prepare_stage1_unsigned(args) -> None:
+ image = Path(args.raw_bin).read_bytes()
+ header = _validate_raw_stage1(image)
+ header["image_len"] = len(image)
+ prefix = _pack_prefix(header)
+ unsigned_stage1 = (
+ _pack_header(prefix, [b"\x00" * STAGE1_SIGNATURE_LEN] * STAGE1_ROOT_KEY_COUNT)
+ + image[STAGE1_HEADER_LEN:]
+ )
+ _validate_complete_stage1(unsigned_stage1, None, require_signatures=False)
+ _write_if_changed(Path(args.unsigned_bin), unsigned_stage1)
+
+
+def _stage1_expected_flags(development: bool) -> int:
+ return STAGE1_HEADER_FLAG_DEVELOPMENT if development else 0
+
+
+def _stage0_expected_flags(development: bool) -> int:
+ return STAGE0_DESCRIPTOR_FLAG_DEVELOPMENT if development else 0
+
+
+def _update_payload(signed_stage1: bytes, product_id: int, development: bool) -> bytes:
+ header = _validate_complete_stage1(signed_stage1, product_id, require_signatures=True)
+ if header["flags"] != _stage1_expected_flags(development):
+ raise RuntimeError("unexpected stage1 update payload flags")
+ if len(signed_stage1) > BOOTLOADER_UPGRADE_PAYLOAD_LEN:
+ raise RuntimeError(
+ f"stage1 update payload is {len(signed_stage1)} bytes, max is {BOOTLOADER_UPGRADE_PAYLOAD_LEN}"
+ )
+ return signed_stage1 + b"\xff" * (BOOTLOADER_UPGRADE_PAYLOAD_LEN - len(signed_stage1))
+
+
+def create_stage1_fw_embedding(args) -> None:
+ payload = _update_payload(Path(args.signed_bin).read_bytes(), args.product_id, args.development)
+ _write_if_changed(Path(args.out_bin), payload)
+
+
+def _validate_stage0(stage0: bytes, product_id: int, development: bool) -> bytes:
+ if len(stage0) > BOOTLOADER_UPGRADE_STAGE0_LEN:
+ raise RuntimeError(
+ f"stage0 image is {len(stage0)} bytes, max is {BOOTLOADER_UPGRADE_STAGE0_LEN}"
+ )
+ stage0 = stage0 + b"\xff" * (BOOTLOADER_UPGRADE_STAGE0_LEN - len(stage0))
+ _descriptor_version, descriptor_product_id, flags, magic = struct.unpack_from(
+ STAGE0_DESCRIPTOR_FORMAT, stage0, STAGE0_DESCRIPTOR_OFFSET
+ )
+ if magic != STAGE0_DESCRIPTOR_MAGIC:
+ raise RuntimeError("invalid stage0 descriptor magic")
+ if descriptor_product_id != product_id:
+ raise RuntimeError("stage0 descriptor product id mismatch")
+ if flags != _stage0_expected_flags(development):
+ raise RuntimeError("stage0 descriptor flags mismatch")
+ return stage0
+
+
+def create_stage0_fw_embedding(args) -> None:
+ stage0 = _validate_stage0(
+ Path(args.stage0_bin).read_bytes(),
+ args.product_id,
+ args.development,
+ )
+ _write_if_changed(Path(args.out_bin), stage0)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description=(
+ "Prepare and validate stage0/stage1 bootloader-upgrade binaries. "
+ "The firmware embedding commands create binary inputs for CMake/objcopy; "
+ "they do not modify or link firmware images themselves."
+ )
+ )
+ subparsers = parser.add_subparsers()
+
+ prepare_parser = subparsers.add_parser(
+ "prepare-stage1-unsigned",
+ help="create an unsigned stage1 image from a raw linked stage1 binary",
+ description=(
+ "Validate the raw stage1 binary produced by objcopy, fill the stage1 "
+ "image_len header field with the actual image length, keep the signature "
+ "array zeroed, and write the canonical unsigned stage1 image that is "
+ "ready to be signed."
+ ),
+ )
+ prepare_parser.add_argument("--raw-bin", required=True)
+ prepare_parser.add_argument("--unsigned-bin", required=True)
+ prepare_parser.set_defaults(func=prepare_stage1_unsigned)
+
+ update_parser = subparsers.add_parser(
+ "create-stage1-fw-embedding",
+ help="create the stage1 update payload consumed by blupgrade firmware",
+ description=(
+ "Validate a signed stage1 image for the requested product and production "
+ "or development mode, then pad it with erased-flash bytes to the fixed "
+ "bootloader update slot size. The output is an intermediate binary that "
+ "CMake converts into a firmware object file to be linked into the upgrader firmware."
+ ),
+ )
+ update_parser.add_argument("--signed-bin", required=True)
+ update_parser.add_argument("--out-bin", required=True)
+ update_parser.add_argument("--product-id", type=_parse_product_id, required=True)
+ update_parser.add_argument("--development", action="store_true")
+ update_parser.set_defaults(func=create_stage1_fw_embedding)
+
+ embed_parser = subparsers.add_parser(
+ "create-stage0-fw-embedding",
+ help="create the stage0 image blob consumed by blupgrade firmware",
+ description=(
+ "Validate a prebuilt stage0 image descriptor for the requested product "
+ "and production or development mode, then pad the image to the fixed "
+ "stage0 size. The output is an intermediate binary that CMake converts "
+ "into a firmware object file to be linked into the upgrader firmware."
+ ),
+ )
+ embed_parser.add_argument("--stage0-bin", required=True)
+ embed_parser.add_argument("--out-bin", required=True)
+ embed_parser.add_argument("--product-id", type=_parse_product_id, required=True)
+ embed_parser.add_argument("--development", action="store_true")
+ embed_parser.set_defaults(func=create_stage0_fw_embedding)
+
+ args = parser.parse_args()
+ if not hasattr(args, "func"):
+ parser.error("missing command")
+ args.func(args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/format-python b/scripts/format-python
index 1daf9a8b..9551aa07 100755
--- a/scripts/format-python
+++ b/scripts/format-python
@@ -16,7 +16,7 @@ ARGS=("$@")
if [ $# -eq 0 ] ; then
# Store default files as array in ARGS
- ARGS=($(find py -name '*.py' | grep -v -e generated -e old))
+ ARGS=($(git ls-files '*.py' | grep -v -e "old/" -e "generated/" -e "rust/vendor/" -e "external/"))
fi
LC_ALL=C.UTF-8 ${BLACK} --config py/pyproject.toml --fast "${ARGS[@]}"
diff --git a/scripts/generate_version_headers.py b/scripts/generate_version_headers.py
index 1702f55e..96e2c22c 100755
--- a/scripts/generate_version_headers.py
+++ b/scripts/generate_version_headers.py
@@ -193,6 +193,9 @@ def load_manifest(manifest_path):
value = manifest.get(key)
if not isinstance(value, str) or not RELEASE_VERSION_RE.match(value):
raise ValueError("Manifest entry '{}' must be a semver string like v1.2.3".format(key))
+ stage0 = manifest.get("stage0")
+ if not isinstance(stage0, int) or isinstance(stage0, bool) or stage0 < 0 or stage0 > 0xFFFF:
+ raise ValueError("Manifest entry 'stage0' must be an integer in the uint16_t range")
return manifest
@@ -295,6 +298,7 @@ def generate_headers(repo_root, output_dir, cmake_vars_out=None, manifest_path=N
"BOOTLOADER_VERSION_FULL": bootloader_info["full"],
"BOOTLOADER_VERSION_FULL_W16": bootloader_info["full_w16"],
"BOOTLOADER_VERSION_FULL_LEN": str(bootloader_info["full_len"]),
+ "STAGE0_IMAGE_VERSION": str(manifest["stage0"]),
}
write_file(
@@ -311,6 +315,13 @@ def generate_headers(repo_root, output_dir, cmake_vars_out=None, manifest_path=N
substitutions,
),
)
+ write_file(
+ os.path.join(output_dir, "bootloader", "stage0", "stage0_version.h"),
+ render_template(
+ os.path.join(repo_root, "src", "bootloader", "stage0", "stage0_version.h.tmpl"),
+ substitutions,
+ ),
+ )
if cmake_vars_out is not None:
write_cmake_vars(
cmake_vars_out,
diff --git a/scripts/jlink-bootloader-stage0.gdb b/scripts/jlink-bootloader-stage0.gdb
new file mode 100644
index 00000000..514c3e41
--- /dev/null
+++ b/scripts/jlink-bootloader-stage0.gdb
@@ -0,0 +1,21 @@
+# Connect to jlink gdb server
+target extended-remote :2331
+
+# It seems more reliable to reset the chip before loading the new firmware. It
+# is also how they do it in the example in the wiki:
+# https://kb.segger.com/J-Link_GDB_Server#Console
+
+# Reset the CPU
+monitor reset
+
+# load the firmware into ROM
+load
+
+#break Reset_Handler
+#break HardFault_Handler
+#break NMI_Handler
+#break MemManage_Handler
+
+# start running
+# change `continue` to `stepi` to stop execution at the start if you want to set breakpoints etc.
+continue
diff --git a/scripts/jlink-bootloader-stage1.gdb b/scripts/jlink-bootloader-stage1.gdb
new file mode 100644
index 00000000..d877eba7
--- /dev/null
+++ b/scripts/jlink-bootloader-stage1.gdb
@@ -0,0 +1,32 @@
+# Connect to jlink gdb server
+target extended-remote :2331
+
+# It seems more reliable to reset the chip before loading the new bootloader
+# stage. It is also how they do it in the example in the wiki:
+# https://kb.segger.com/J-Link_GDB_Server#Console
+
+# Reset the CPU
+monitor reset
+
+# load the bootloader stage into ROM
+load
+
+define bootload
+ monitor reset
+ # Set VTOR (Vector Table Offset Register) to where stage1 vectors are located.
+ set *(uint32_t*)0xE000ED08=0x2400
+ # Set stack pointer to initial stack pointer according to exception table.
+ set $sp = *(uint32_t*)0x2400
+ # Set the program counter to the reset handler (second item in exception table)
+ set $pc = *(uint32_t*)0x2404
+end
+bootload
+
+#break Reset_Handler
+#break HardFault_Handler
+#break NMI_Handler
+#break MemManage_Handler
+
+# start running
+# change `continue` to `stepi` to stop execution at the start if you want to set breakpoints etc.
+continue
diff --git a/scripts/jlink-bootloader.gdb b/scripts/jlink-bootloader.gdb
deleted file mode 100644
index 514c3e41..00000000
--- a/scripts/jlink-bootloader.gdb
+++ /dev/null
@@ -1,21 +0,0 @@
-# Connect to jlink gdb server
-target extended-remote :2331
-
-# It seems more reliable to reset the chip before loading the new firmware. It
-# is also how they do it in the example in the wiki:
-# https://kb.segger.com/J-Link_GDB_Server#Console
-
-# Reset the CPU
-monitor reset
-
-# load the firmware into ROM
-load
-
-#break Reset_Handler
-#break HardFault_Handler
-#break NMI_Handler
-#break MemManage_Handler
-
-# start running
-# change `continue` to `stepi` to stop execution at the start if you want to set breakpoints etc.
-continue
diff --git a/scripts/template-bootloader.jlink b/scripts/template-bootloader.jlink
deleted file mode 100644
index 143b1b7e..00000000
--- a/scripts/template-bootloader.jlink
+++ /dev/null
@@ -1,3 +0,0 @@
-loadfile $file
-r
-q
diff --git a/scripts/template-stage0.jlink b/scripts/template-stage0.jlink
new file mode 100644
index 00000000..149f372d
--- /dev/null
+++ b/scripts/template-stage0.jlink
@@ -0,0 +1,3 @@
+loadbin $file 0x00000000
+r
+q
diff --git a/scripts/template-stage1.jlink b/scripts/template-stage1.jlink
new file mode 100644
index 00000000..75e6a556
--- /dev/null
+++ b/scripts/template-stage1.jlink
@@ -0,0 +1,3 @@
+loadbin $file 0x00002000
+r
+q
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8626a981..23e803a3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -286,36 +286,16 @@ if(NOT CMAKE_CROSSCOMPILING)
add_dependencies(rust-clippy rust-cbindgen)
endif()
-# If a bootloader that locks the bootloader is flashed the bootloader area is permanently read-only.
-set(BOOTLOADERS-BITBOX02
- bb02-bl-multi # Runs signed firmware
- bb02-bl-multi-development # Runs signed/unsigned firmware and has bootloader menu
- bb02-bl-multi-development-locked # Runs signed/unsigned firmware, has bootloader menu and bootloader is locked
- bb02-bl-multi-production # Runs signed firmware and bootloader is locked
- bb02-bl-btconly # Runs signed (btc-only keys) firmware
- bb02-bl-btconly-development # Runs signed/unsigned firmware and has bootloader menu
- bb02-bl-btconly-production # Runs signed (btc-only keys) firmware and bootloader is locked
- )
-set(BOOTLOADERS-BITBOX02PLUS
- bb02p-bl-multi # Runs signed firmware
- bb02p-bl-multi-development # Runs signed/unsigned firmware and has bootloader menu
- bb02p-bl-multi-development-locked # Runs signed/unsigned firmware, has bootloader menu and bootloader is locked
- bb02p-bl-multi-production # Runs signed firmware and bootloader is locked
- bb02p-bl-btconly # Runs signed (btc-only keys) firmware
- bb02p-bl-btconly-development # Runs signed/unsigned firmware and has bootloader menu
- bb02p-bl-btconly-production # Runs signed (btc-only keys) firmware and bootloader is locked
-)
-set(BOOTLOADERS
- ${BOOTLOADERS-BITBOX02}
- ${BOOTLOADERS-BITBOX02PLUS}
- )
-set(BOOTLOADERS ${BOOTLOADERS} PARENT_SCOPE)
-
-# Used to add QTouch to development bootloaders of bb02 bootloaders
-set(DEVDEVICE-BOOTLOADERS
- bb02-bl-multi-development
- bb02-bl-multi-development-locked
+# Stage1 uses the legacy bootloader Rust feature names internally.
+set(BB02_STAGE1_RUST_LIBS
bb02-bl-btconly-development
+ bb02-bl-btconly-production
+ bb02-bl-multi-development
+ bb02-bl-multi-production
+ bb02p-bl-btconly-development
+ bb02p-bl-btconly-production
+ bb02p-bl-multi-development
+ bb02p-bl-multi-production
)
set(FIRMWARES
@@ -326,7 +306,7 @@ set(FIRMWARES
set(FIRMWARES ${FIRMWARES} PARENT_SCOPE)
if(CMAKE_CROSSCOMPILING)
- set(RUST_LIBS ${FIRMWARES} ${BOOTLOADERS})
+ set(RUST_LIBS ${FIRMWARES} ${BB02_STAGE1_RUST_LIBS})
else()
set(RUST_LIBS c-unit-tests)
endif()
@@ -416,74 +396,6 @@ if(CMAKE_CROSSCOMPILING)
set(HEAP_SIZE "0x18000" CACHE STRING "Specify heap size for bootloader/firmware")
set(HEAP_SIZE ${HEAP_SIZE} PARENT_SCOPE)
- foreach(bootloader ${BOOTLOADERS})
- set(elf ${bootloader}.elf)
- add_executable(${elf} ${BOOTLOADER-SOURCES} ${PLATFORM-BITBOX02-SOURCES})
- target_compile_options(${elf} PRIVATE -fno-lto)
- target_link_libraries(${elf} PRIVATE c asf4-drivers-min samd51a-ds -Wl,-u,exception_table)
- target_include_directories(${elf} PRIVATE ${INCLUDES})
- target_compile_definitions(${elf} PRIVATE BOOTLOADER "APP_U2F=0")
- # needed to find version.h
- target_include_directories(${elf} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
- # needed to find bootloader_version.h
- target_include_directories(${elf} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/bootloader)
- target_link_libraries(${elf} PRIVATE "-Wl,-Map=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${bootloader}.map\" -T\"${CMAKE_SOURCE_DIR}/bootloader.ld\"")
- target_link_libraries(${elf} PRIVATE -Wl,--defsym=STACK_SIZE=${STACK_SIZE} -Wl,-defsym=HEAP_SIZE=${HEAP_SIZE})
-
- target_link_libraries(${elf} PRIVATE ${bootloader}_rust_c)
-
- # Select the smaller version of libc called nano.
- target_compile_options(${elf} PRIVATE --specs=nano.specs)
- target_link_libraries(${elf} PRIVATE --specs=nano.specs)
- target_compile_options(${elf} PRIVATE --specs=nosys.specs)
- target_link_libraries(${elf} PRIVATE --specs=nosys.specs)
- target_link_libraries(${elf} PRIVATE -Wl,--print-memory-usage)
- endforeach(bootloader)
-
- foreach(bootloader ${DEVDEVICE-BOOTLOADERS} ${BOOTLOADERS-BITBOX02PLUS})
- set(elf ${bootloader}.elf)
- target_link_libraries(${elf} PRIVATE ${QTOUCHLIB_A} ${QTOUCHLIB_B} ${QTOUCHLIB_T})
- target_sources(${elf} PRIVATE ${QTOUCH-SOURCES})
- endforeach(bootloader)
-
- foreach(bootloader ${BOOTLOADERS-BITBOX02PLUS})
- set(elf ${bootloader}.elf)
- target_sources(${elf} PRIVATE ${PLATFORM-BITBOX02-PLUS-SOURCES})
- target_link_libraries(${bootloader}.elf PRIVATE embedded-swd)
- endforeach(bootloader)
-
- # BB02 definitions
-
- target_compile_definitions(bb02-bl-multi.elf PRIVATE PRODUCT_BITBOX_MULTI)
- target_compile_definitions(bb02-bl-multi-development.elf PRIVATE PRODUCT_BITBOX_MULTI BOOTLOADER_DEVDEVICE)
- target_compile_definitions(bb02-bl-multi-development-locked.elf PRIVATE PRODUCT_BITBOX_MULTI BOOTLOADER_DEVDEVICE BOOTLOADER_PRODUCTION)
- set_property(TARGET bb02-bl-multi-development-locked.elf PROPERTY EXCLUDE_FROM_ALL ON)
-
-
- target_compile_definitions(bb02-bl-multi-production.elf PRIVATE PRODUCT_BITBOX_MULTI BOOTLOADER_PRODUCTION)
- set_property(TARGET bb02-bl-multi-production.elf PROPERTY EXCLUDE_FROM_ALL ON)
-
- target_compile_definitions(bb02-bl-btconly.elf PRIVATE PRODUCT_BITBOX_BTCONLY)
- target_compile_definitions(bb02-bl-btconly-development.elf PRIVATE PRODUCT_BITBOX_BTCONLY BOOTLOADER_DEVDEVICE)
- target_compile_definitions(bb02-bl-btconly-production.elf PRIVATE PRODUCT_BITBOX_BTCONLY BOOTLOADER_PRODUCTION)
- set_property(TARGET bb02-bl-btconly-production.elf PROPERTY EXCLUDE_FROM_ALL ON)
-
- # BB02PLUS definitions
-
- target_compile_definitions(bb02p-bl-multi.elf PRIVATE PRODUCT_BITBOX_PLUS_MULTI)
- target_compile_definitions(bb02p-bl-multi-development.elf PRIVATE PRODUCT_BITBOX_PLUS_MULTI BOOTLOADER_DEVDEVICE)
- target_compile_definitions(bb02p-bl-multi-development-locked.elf PRIVATE PRODUCT_BITBOX_PLUS_MULTI BOOTLOADER_DEVDEVICE BOOTLOADER_PRODUCTION)
- set_property(TARGET bb02p-bl-multi-development-locked.elf PROPERTY EXCLUDE_FROM_ALL ON)
-
-
- target_compile_definitions(bb02p-bl-multi-production.elf PRIVATE PRODUCT_BITBOX_PLUS_MULTI BOOTLOADER_PRODUCTION)
- set_property(TARGET bb02p-bl-multi-production.elf PROPERTY EXCLUDE_FROM_ALL ON)
-
- target_compile_definitions(bb02p-bl-btconly.elf PRIVATE PRODUCT_BITBOX_PLUS_BTCONLY)
- target_compile_definitions(bb02p-bl-btconly-development.elf PRIVATE PRODUCT_BITBOX_PLUS_BTCONLY BOOTLOADER_DEVDEVICE)
- target_compile_definitions(bb02p-bl-btconly-production.elf PRIVATE PRODUCT_BITBOX_PLUS_BTCONLY BOOTLOADER_PRODUCTION)
- set_property(TARGET bb02p-bl-btconly-production.elf PROPERTY EXCLUDE_FROM_ALL ON)
-
foreach(firmware ${FIRMWARES})
set(elf ${firmware}.elf)
add_executable(${elf} ${FIRMWARE-SOURCES})
@@ -541,6 +453,630 @@ if(CMAKE_CROSSCOMPILING)
target_compile_definitions(factory-setup.elf PRIVATE PRODUCT_BITBOX02_FACTORYSETUP "APP_U2F=0")
target_sources(factory-setup.elf PRIVATE ${PLATFORM-BITBOX02-SOURCES})
+ set(BB02_BLUPD_BIN_DIR ${CMAKE_SOURCE_DIR}/src/bootloader_upgrade/bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02_BTCONLY_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02-btconly-production.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02_MULTI_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02-multi-production.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02NOVA_BTCONLY_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02nova-btconly-production.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02NOVA_MULTI_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02nova-multi-production.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02_BTCONLY_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02-btconly-development.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02_MULTI_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02-multi-development.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02NOVA_BTCONLY_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02nova-btconly-development.v1.bin)
+ set(BB02_BLUPD_STAGE0_BITBOX02NOVA_MULTI_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage0-bitbox02nova-multi-development.v1.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02_BTCONLY_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02-btconly-production.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02_MULTI_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02-multi-production.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02NOVA_BTCONLY_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02nova-btconly-production.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02NOVA_MULTI_PRODUCTION_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02nova-multi-production.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02_BTCONLY_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02_MULTI_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02NOVA_BTCONLY_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin)
+ set(BB02_BLUPD_STAGE1_BITBOX02NOVA_MULTI_DEVELOPMENT_BIN ${BB02_BLUPD_BIN_DIR}/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin)
+
+ set(BB02_BLUPD_FIRMWARE_TARGETS
+ firmware-blupgrade-bitbox02-btconly
+ firmware-blupgrade-bitbox02-multi
+ firmware-blupgrade-bitbox02nova-btconly
+ firmware-blupgrade-bitbox02nova-multi
+ firmware-blupgrade-bitbox02-btconly-development
+ firmware-blupgrade-bitbox02-multi-development
+ firmware-blupgrade-bitbox02nova-btconly-development
+ firmware-blupgrade-bitbox02nova-multi-development
+ )
+ set(BB02_BLUPD_FIRMWARE_TARGETS ${BB02_BLUPD_FIRMWARE_TARGETS} PARENT_SCOPE)
+
+ function(add_bb02_stage1_target target rustlib product bootloader_type is_plus)
+ add_executable(${target}.elf
+ ${BOOTLOADER-SOURCES}
+ ${PLATFORM-BITBOX02-SOURCES}
+ ${QTOUCH-SOURCES}
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage1_header.c
+ )
+ target_compile_options(${target}.elf PRIVATE -fno-lto)
+ target_link_libraries(${target}.elf PRIVATE
+ c
+ asf4-drivers-min
+ samd51a-ds
+ ${QTOUCHLIB_A}
+ ${QTOUCHLIB_B}
+ ${QTOUCHLIB_T}
+ -Wl,-u,exception_table
+ "-Wl,-Map=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.map\" -T\"${CMAKE_SOURCE_DIR}/bootloader-stage1.ld\""
+ -Wl,--defsym=STACK_SIZE=${STACK_SIZE}
+ -Wl,-defsym=HEAP_SIZE=${HEAP_SIZE}
+ ${rustlib}
+ --specs=nano.specs
+ --specs=nosys.specs
+ -Wl,--print-memory-usage
+ )
+ target_include_directories(${target}.elf PRIVATE ${INCLUDES})
+ target_include_directories(${target}.elf PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+ target_include_directories(${target}.elf PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/bootloader)
+ target_compile_definitions(${target}.elf PRIVATE
+ BOOTLOADER
+ ${bootloader_type}
+ ${product}
+ "APP_U2F=0"
+ )
+ target_compile_options(${target}.elf PRIVATE --specs=nano.specs --specs=nosys.specs)
+ if(is_plus)
+ target_sources(${target}.elf PRIVATE ${PLATFORM-BITBOX02-PLUS-SOURCES})
+ target_link_libraries(${target}.elf PRIVATE embedded-swd)
+ endif()
+ add_custom_command(
+ TARGET ${target}.elf POST_BUILD
+ COMMAND ${CMAKE_SIZE} ${target}.elf
+ COMMAND ${CMAKE_OBJCOPY} -O binary ${target}.elf ${target}.raw.bin
+ COMMAND
+ ${PYTHON_EXECUTABLE}
+ ${CMAKE_SOURCE_DIR}/scripts/bootloader_update.py
+ prepare-stage1-unsigned
+ --raw-bin ${target}.raw.bin
+ --unsigned-bin ${target}.bin
+ WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
+ COMMENT "\nGenerating binary ${target}.bin"
+ )
+ set_property(TARGET ${target}.elf PROPERTY EXCLUDE_FROM_ALL ON)
+ endfunction()
+
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02-btconly-development
+ bb02-bl-btconly-development_rust_c
+ PRODUCT_BITBOX_BTCONLY
+ BOOTLOADER_DEVDEVICE
+ FALSE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02-btconly-production
+ bb02-bl-btconly-production_rust_c
+ PRODUCT_BITBOX_BTCONLY
+ BOOTLOADER_PRODUCTION
+ FALSE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02-multi-development
+ bb02-bl-multi-development_rust_c
+ PRODUCT_BITBOX_MULTI
+ BOOTLOADER_DEVDEVICE
+ FALSE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02-multi-production
+ bb02-bl-multi-production_rust_c
+ PRODUCT_BITBOX_MULTI
+ BOOTLOADER_PRODUCTION
+ FALSE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02nova-btconly-development
+ bb02p-bl-btconly-development_rust_c
+ PRODUCT_BITBOX_PLUS_BTCONLY
+ BOOTLOADER_DEVDEVICE
+ TRUE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02nova-btconly-production
+ bb02p-bl-btconly-production_rust_c
+ PRODUCT_BITBOX_PLUS_BTCONLY
+ BOOTLOADER_PRODUCTION
+ TRUE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02nova-multi-development
+ bb02p-bl-multi-development_rust_c
+ PRODUCT_BITBOX_PLUS_MULTI
+ BOOTLOADER_DEVDEVICE
+ TRUE)
+ add_bb02_stage1_target(
+ bootloader-stage1-bitbox02nova-multi-production
+ bb02p-bl-multi-production_rust_c
+ PRODUCT_BITBOX_PLUS_MULTI
+ BOOTLOADER_PRODUCTION
+ TRUE)
+
+ # Bare stage1 target names are exported for J-Link script generation.
+ set(BB02_STAGE1_TARGETS
+ bootloader-stage1-bitbox02-btconly-production
+ bootloader-stage1-bitbox02-multi-production
+ bootloader-stage1-bitbox02nova-btconly-production
+ bootloader-stage1-bitbox02nova-multi-production
+ bootloader-stage1-bitbox02-btconly-development
+ bootloader-stage1-bitbox02-multi-development
+ bootloader-stage1-bitbox02nova-btconly-development
+ bootloader-stage1-bitbox02nova-multi-development
+ )
+ set(BB02_STAGE1_TARGETS ${BB02_STAGE1_TARGETS} PARENT_SCOPE)
+
+ # Builds one stage0 ELF/bin for a product. Production stage0 includes stage1 signature
+ # verification; development stage0 skips that path and shows a visible development marker.
+ function(add_bb02_stage0_target stage0_target product target_product_id development)
+ set(stage0_sources
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0_descriptor.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0_runtime.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0_startup.c
+ ${CMAKE_SOURCE_DIR}/src/platform/driver_init.c
+ ${CMAKE_SOURCE_DIR}/src/ui/oled/oled.c
+ ${CMAKE_SOURCE_DIR}/src/ui/oled/oled_writer.c
+ ${CMAKE_SOURCE_DIR}/src/ui/oled/sh1107.c
+ ${CMAKE_SOURCE_DIR}/src/ui/oled/ssd1312.c
+ )
+ set_source_files_properties(
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0_startup.c
+ PROPERTIES COMPILE_OPTIONS -fno-stack-protector
+ )
+ set(stage0_compile_definitions)
+ if(development)
+ list(APPEND stage0_compile_definitions BB02_STAGE0_DEVELOPMENT)
+ else()
+ list(APPEND stage0_sources
+ ${CMAKE_SOURCE_DIR}/src/bootloader_upgrade/stage1_pubkeys.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage1_sigcheck.c
+ ${CMAKE_SOURCE_DIR}/src/pukcc/pukcc.c
+ ${CMAKE_SOURCE_DIR}/src/pukcc/curve_p256.c
+ )
+ endif()
+
+ add_executable(${stage0_target}.elf ${stage0_sources})
+ target_compile_options(${stage0_target}.elf PRIVATE -flto -ffat-lto-objects)
+ target_include_directories(${stage0_target}.elf PRIVATE ${INCLUDES})
+ target_include_directories(${stage0_target}.elf PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+ target_compile_definitions(${stage0_target}.elf PRIVATE
+ ${product}
+ BB02_STAGE1_TARGET_PRODUCT_ID=${target_product_id}
+ ${stage0_compile_definitions}
+ )
+ target_link_libraries(${stage0_target}.elf
+ PRIVATE
+ -flto
+ -nostdlib
+ samd51a-ds
+ asf4-drivers-stage0
+ -Wl,-u,exception_table
+ "-Wl,-Map=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${stage0_target}.map\" -T\"${CMAKE_SOURCE_DIR}/bootloader-stage0.ld\""
+ -Wl,--defsym=STACK_SIZE=0x1000
+ -Wl,-defsym=HEAP_SIZE=0
+ -Wl,--print-memory-usage
+ )
+ set_property(TARGET ${stage0_target}.elf PROPERTY EXCLUDE_FROM_ALL ON)
+
+ add_custom_command(
+ TARGET ${stage0_target}.elf POST_BUILD
+ COMMAND ${CMAKE_SIZE} ${stage0_target}.elf
+ COMMAND ${CMAKE_OBJCOPY} --gap-fill 0xff -O binary ${stage0_target}.elf ${stage0_target}.bin
+ WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
+ COMMENT "\nGenerating binary ${stage0_target}.bin"
+ )
+ endfunction()
+
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02-btconly-production
+ PRODUCT_BITBOX_BTCONLY
+ BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY
+ FALSE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02-multi-production
+ PRODUCT_BITBOX_MULTI
+ BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI
+ FALSE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02nova-btconly-production
+ PRODUCT_BITBOX_PLUS_BTCONLY
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY
+ FALSE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02nova-multi-production
+ PRODUCT_BITBOX_PLUS_MULTI
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI
+ FALSE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02-btconly-development
+ PRODUCT_BITBOX_BTCONLY
+ BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY
+ TRUE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02-multi-development
+ PRODUCT_BITBOX_MULTI
+ BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI
+ TRUE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02nova-btconly-development
+ PRODUCT_BITBOX_PLUS_BTCONLY
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY
+ TRUE)
+ add_bb02_stage0_target(
+ bootloader-stage0-bitbox02nova-multi-development
+ PRODUCT_BITBOX_PLUS_MULTI
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI
+ TRUE)
+
+ # Bare stage0 target names are exported for J-Link script generation.
+ set(BB02_STAGE0_TARGETS
+ bootloader-stage0-bitbox02-btconly-production
+ bootloader-stage0-bitbox02-multi-production
+ bootloader-stage0-bitbox02nova-btconly-production
+ bootloader-stage0-bitbox02nova-multi-production
+ bootloader-stage0-bitbox02-btconly-development
+ bootloader-stage0-bitbox02-multi-development
+ bootloader-stage0-bitbox02nova-btconly-development
+ bootloader-stage0-bitbox02nova-multi-development
+ )
+ set(BB02_STAGE0_TARGETS ${BB02_STAGE0_TARGETS} PARENT_SCOPE)
+
+ # Creates the embedded prebuilt stage0 image object and stage1 update payload object used by one
+ # bootloader-upgrade firmware target.
+ function(add_bb02_blupgrade_assets
+ name
+ out_prefix
+ product_id
+ stage0_bin
+ stage1_signed_bin
+ development)
+ set(asset_dir ${CMAKE_CURRENT_BINARY_DIR}/bootloader_upgrade_${name})
+ set(stage0_image_bin ${asset_dir}/bootloader_upgrade_stage0_image.bin)
+ set(stage0_image_obj ${asset_dir}/bootloader_upgrade_stage0_image.o)
+ set(payload_bin ${asset_dir}/bootloader_upgrade_payload.bin)
+ set(payload_obj ${asset_dir}/bootloader_upgrade_payload.o)
+ set(development_arg)
+ if(development)
+ set(development_arg --development)
+ endif()
+
+ add_custom_command(
+ OUTPUT ${stage0_image_bin}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${asset_dir}
+ COMMAND
+ ${PYTHON_EXECUTABLE}
+ ${CMAKE_SOURCE_DIR}/scripts/bootloader_update.py
+ create-stage0-fw-embedding
+ --stage0-bin ${stage0_bin}
+ --out-bin ${stage0_image_bin}
+ --product-id ${product_id}
+ ${development_arg}
+ DEPENDS ${stage0_bin} ${CMAKE_SOURCE_DIR}/scripts/bootloader_update.py
+ COMMENT "Creating stage0 firmware embedding input from ${stage0_bin}"
+ )
+
+ add_custom_command(
+ OUTPUT ${stage0_image_obj}
+ COMMAND
+ ${CMAKE_OBJCOPY}
+ -Ibinary
+ -Oelf32-littlearm
+ --rename-section .data=.rodata,alloc,load,readonly,data,contents
+ --set-section-alignment .rodata=512
+ bootloader_upgrade_stage0_image.bin
+ bootloader_upgrade_stage0_image.o
+ DEPENDS ${stage0_image_bin}
+ WORKING_DIRECTORY ${asset_dir}
+ COMMENT "Convert stage0 upgrade image to object file using ${CMAKE_OBJCOPY}"
+ )
+
+ add_custom_command(
+ OUTPUT ${payload_bin}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${asset_dir}
+ COMMAND
+ ${PYTHON_EXECUTABLE}
+ ${CMAKE_SOURCE_DIR}/scripts/bootloader_update.py
+ create-stage1-fw-embedding
+ --signed-bin ${stage1_signed_bin}
+ --out-bin ${payload_bin}
+ --product-id ${product_id}
+ ${development_arg}
+ DEPENDS ${stage1_signed_bin} ${CMAKE_SOURCE_DIR}/scripts/bootloader_update.py
+ COMMENT "Creating stage1 firmware embedding input from ${stage1_signed_bin}"
+ )
+
+ add_custom_command(
+ OUTPUT ${payload_obj}
+ COMMAND
+ ${CMAKE_OBJCOPY}
+ -Ibinary
+ -Oelf32-littlearm
+ --rename-section .data=.bootloader_update,alloc,load,readonly,data,contents
+ --set-section-alignment .bootloader_update=512
+ bootloader_upgrade_payload.bin
+ bootloader_upgrade_payload.o
+ DEPENDS ${payload_bin}
+ WORKING_DIRECTORY ${asset_dir}
+ COMMENT "Convert stage1 upgrade payload to object file using ${CMAKE_OBJCOPY}"
+ )
+ set_source_files_properties(
+ ${stage0_image_obj}
+ ${payload_obj}
+ PROPERTIES GENERATED TRUE EXTERNAL_OBJECT TRUE)
+ add_custom_target(
+ bootloader-upgrade-assets-${name}
+ DEPENDS
+ ${stage0_image_bin}
+ ${stage0_image_obj}
+ ${payload_bin}
+ ${payload_obj}
+ )
+ set(${out_prefix}_STAGE0_IMAGE_OBJ ${stage0_image_obj} PARENT_SCOPE)
+ set(${out_prefix}_PAYLOAD_OBJ ${payload_obj} PARENT_SCOPE)
+ set(${out_prefix}_ASSETS_TARGET bootloader-upgrade-assets-${name} PARENT_SCOPE)
+ endfunction()
+
+ add_bb02_blupgrade_assets(
+ bitbox02-btconly
+ BB02_BLUPD_BITBOX02_BTCONLY
+ bitbox-btconly
+ ${BB02_BLUPD_STAGE0_BITBOX02_BTCONLY_PRODUCTION_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02_BTCONLY_PRODUCTION_BIN}
+ FALSE)
+ add_bb02_blupgrade_assets(
+ bitbox02-multi
+ BB02_BLUPD_BITBOX02_MULTI
+ bitbox-multi
+ ${BB02_BLUPD_STAGE0_BITBOX02_MULTI_PRODUCTION_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02_MULTI_PRODUCTION_BIN}
+ FALSE)
+ add_bb02_blupgrade_assets(
+ bitbox02nova-btconly
+ BB02_BLUPD_BITBOX02NOVA_BTCONLY
+ bitbox-plus-btconly
+ ${BB02_BLUPD_STAGE0_BITBOX02NOVA_BTCONLY_PRODUCTION_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02NOVA_BTCONLY_PRODUCTION_BIN}
+ FALSE)
+ add_bb02_blupgrade_assets(
+ bitbox02nova-multi
+ BB02_BLUPD_BITBOX02NOVA_MULTI
+ bitbox-plus-multi
+ ${BB02_BLUPD_STAGE0_BITBOX02NOVA_MULTI_PRODUCTION_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02NOVA_MULTI_PRODUCTION_BIN}
+ FALSE)
+
+ add_bb02_blupgrade_assets(
+ bitbox02-btconly-development
+ BB02_BLUPD_BITBOX02_BTCONLY_DEVELOPMENT
+ bitbox-btconly
+ ${BB02_BLUPD_STAGE0_BITBOX02_BTCONLY_DEVELOPMENT_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02_BTCONLY_DEVELOPMENT_BIN}
+ TRUE)
+ add_bb02_blupgrade_assets(
+ bitbox02-multi-development
+ BB02_BLUPD_BITBOX02_MULTI_DEVELOPMENT
+ bitbox-multi
+ ${BB02_BLUPD_STAGE0_BITBOX02_MULTI_DEVELOPMENT_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02_MULTI_DEVELOPMENT_BIN}
+ TRUE)
+ add_bb02_blupgrade_assets(
+ bitbox02nova-btconly-development
+ BB02_BLUPD_BITBOX02NOVA_BTCONLY_DEVELOPMENT
+ bitbox-plus-btconly
+ ${BB02_BLUPD_STAGE0_BITBOX02NOVA_BTCONLY_DEVELOPMENT_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02NOVA_BTCONLY_DEVELOPMENT_BIN}
+ TRUE)
+ add_bb02_blupgrade_assets(
+ bitbox02nova-multi-development
+ BB02_BLUPD_BITBOX02NOVA_MULTI_DEVELOPMENT
+ bitbox-plus-multi
+ ${BB02_BLUPD_STAGE0_BITBOX02NOVA_MULTI_DEVELOPMENT_BIN}
+ ${BB02_BLUPD_STAGE1_BITBOX02NOVA_MULTI_DEVELOPMENT_BIN}
+ TRUE)
+
+ # Aggregate targets for building all production/development stage1 variants.
+ add_custom_target(
+ bootloader-stage1-production
+ DEPENDS
+ bootloader-stage1-bitbox02-btconly-production.elf
+ bootloader-stage1-bitbox02-multi-production.elf
+ bootloader-stage1-bitbox02nova-btconly-production.elf
+ bootloader-stage1-bitbox02nova-multi-production.elf
+ )
+
+ add_custom_target(
+ bootloader-stage1-development
+ DEPENDS
+ bootloader-stage1-bitbox02-btconly-development.elf
+ bootloader-stage1-bitbox02-multi-development.elf
+ bootloader-stage1-bitbox02nova-btconly-development.elf
+ bootloader-stage1-bitbox02nova-multi-development.elf
+ )
+
+ add_custom_target(
+ bootloader-stage1
+ DEPENDS
+ bootloader-stage1-production
+ bootloader-stage1-development
+ )
+
+ # Aggregate targets for building all production/development stage0 variants.
+ add_custom_target(
+ bootloader-stage0-production
+ DEPENDS
+ bootloader-stage0-bitbox02-btconly-production.elf
+ bootloader-stage0-bitbox02-multi-production.elf
+ bootloader-stage0-bitbox02nova-btconly-production.elf
+ bootloader-stage0-bitbox02nova-multi-production.elf
+ )
+
+ add_custom_target(
+ bootloader-stage0-development
+ DEPENDS
+ bootloader-stage0-bitbox02-btconly-development.elf
+ bootloader-stage0-bitbox02-multi-development.elf
+ bootloader-stage0-bitbox02nova-btconly-development.elf
+ bootloader-stage0-bitbox02nova-multi-development.elf
+ )
+
+ add_custom_target(
+ bootloader-stage0
+ DEPENDS
+ bootloader-stage0-production
+ bootloader-stage0-development
+ )
+
+ add_custom_target(
+ bootloader-upgrade-assets
+ DEPENDS
+ ${BB02_BLUPD_BITBOX02_BTCONLY_ASSETS_TARGET}
+ ${BB02_BLUPD_BITBOX02_MULTI_ASSETS_TARGET}
+ ${BB02_BLUPD_BITBOX02NOVA_BTCONLY_ASSETS_TARGET}
+ ${BB02_BLUPD_BITBOX02NOVA_MULTI_ASSETS_TARGET}
+ )
+
+ add_custom_target(
+ bootloader-upgrade-assets-development
+ DEPENDS
+ ${BB02_BLUPD_BITBOX02_BTCONLY_DEVELOPMENT_ASSETS_TARGET}
+ ${BB02_BLUPD_BITBOX02_MULTI_DEVELOPMENT_ASSETS_TARGET}
+ ${BB02_BLUPD_BITBOX02NOVA_BTCONLY_DEVELOPMENT_ASSETS_TARGET}
+ ${BB02_BLUPD_BITBOX02NOVA_MULTI_DEVELOPMENT_ASSETS_TARGET}
+ )
+
+ function(add_bb02_blupgrade_firmware target rustlib product app_u2f target_product_id stage0_image_obj payload_obj development)
+ add_executable(${target}.elf
+ ${FIRMWARE-SOURCES}
+ ${CMAKE_SOURCE_DIR}/src/firmware.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader_upgrade/firmware_installer_check.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader_upgrade/firmware_installer.c
+ ${stage0_image_obj}
+ ${payload_obj}
+ )
+ target_compile_options(${target}.elf PRIVATE -flto -ffat-lto-objects)
+ target_link_libraries(${target}.elf PRIVATE
+ optiga
+ embedded-swd
+ cryptoauthlib
+ c
+ ${rustlib}
+ samd51a-ds
+ asf4-drivers-min
+ asf4-drivers
+ -Wl,-u,exception_table
+ -flto
+ "-Wl,-Map=\"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.map\" -T\"${CMAKE_SOURCE_DIR}/firmware-blupgrade.ld\""
+ -Wl,--defsym=STACK_SIZE=${STACK_SIZE}
+ -Wl,-defsym=HEAP_SIZE=${HEAP_SIZE}
+ ${QTOUCHLIB_A}
+ ${QTOUCHLIB_B}
+ ${QTOUCHLIB_T}
+ --specs=nano.specs
+ --specs=nosys.specs
+ -Wl,--print-memory-usage
+ )
+ target_compile_options(${target}.elf PRIVATE --specs=nano.specs --specs=nosys.specs)
+ target_include_directories(${target}.elf PRIVATE ${INCLUDES})
+ target_include_directories(${target}.elf PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+ set(blupgrade_compile_definitions BOOTLOADER_UPGRADE)
+ if(development)
+ list(APPEND blupgrade_compile_definitions BOOTLOADER_UPGRADE_DEVELOPMENT)
+ endif()
+ target_compile_definitions(${target}.elf PRIVATE
+ ${product}
+ "APP_U2F=${app_u2f}"
+ BB02_STAGE1_TARGET_PRODUCT_ID=${target_product_id}
+ ${blupgrade_compile_definitions}
+ OPTIGA_LIB_EXTERNAL="optiga_config.h"
+ )
+ target_sources(${target}.elf PRIVATE ${PLATFORM-BITBOX02-SOURCES})
+ if(${app_u2f} EQUAL 1)
+ target_sources(${target}.elf PRIVATE ${FIRMWARE-U2F-SOURCES} ${FIRMWARE-U2F-DRIVER-SOURCES})
+ endif()
+ if(NOT development)
+ target_sources(${target}.elf PRIVATE
+ ${CMAKE_SOURCE_DIR}/src/bootloader_upgrade/stage1_pubkeys.c
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage1_sigcheck.c
+ ${CMAKE_SOURCE_DIR}/src/pukcc/pukcc.c
+ ${CMAKE_SOURCE_DIR}/src/pukcc/curve_p256.c
+ )
+ endif()
+ add_custom_command(
+ TARGET ${target}.elf POST_BUILD
+ COMMAND ${CMAKE_SIZE} ${target}.elf
+ COMMAND ${CMAKE_OBJCOPY} -O binary ${target}.elf ${target}.bin
+ WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
+ COMMENT "\nGenerating binary ${target}.bin"
+ )
+ endfunction()
+
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02-btconly
+ firmware-btc_rust_c
+ PRODUCT_BITBOX_BTCONLY
+ 0
+ BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY
+ ${BB02_BLUPD_BITBOX02_BTCONLY_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02_BTCONLY_PAYLOAD_OBJ}
+ FALSE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02-multi
+ firmware_rust_c
+ PRODUCT_BITBOX_MULTI
+ 1
+ BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI
+ ${BB02_BLUPD_BITBOX02_MULTI_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02_MULTI_PAYLOAD_OBJ}
+ FALSE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02nova-btconly
+ firmware-btc_rust_c
+ PRODUCT_BITBOX_BTCONLY
+ 0
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY
+ ${BB02_BLUPD_BITBOX02NOVA_BTCONLY_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02NOVA_BTCONLY_PAYLOAD_OBJ}
+ FALSE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02nova-multi
+ firmware_rust_c
+ PRODUCT_BITBOX_MULTI
+ 1
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI
+ ${BB02_BLUPD_BITBOX02NOVA_MULTI_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02NOVA_MULTI_PAYLOAD_OBJ}
+ FALSE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02-btconly-development
+ firmware-btc_rust_c
+ PRODUCT_BITBOX_BTCONLY
+ 0
+ BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY
+ ${BB02_BLUPD_BITBOX02_BTCONLY_DEVELOPMENT_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02_BTCONLY_DEVELOPMENT_PAYLOAD_OBJ}
+ TRUE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02-multi-development
+ firmware_rust_c
+ PRODUCT_BITBOX_MULTI
+ 1
+ BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI
+ ${BB02_BLUPD_BITBOX02_MULTI_DEVELOPMENT_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02_MULTI_DEVELOPMENT_PAYLOAD_OBJ}
+ TRUE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02nova-btconly-development
+ firmware-btc_rust_c
+ PRODUCT_BITBOX_BTCONLY
+ 0
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY
+ ${BB02_BLUPD_BITBOX02NOVA_BTCONLY_DEVELOPMENT_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02NOVA_BTCONLY_DEVELOPMENT_PAYLOAD_OBJ}
+ TRUE)
+ add_bb02_blupgrade_firmware(
+ firmware-blupgrade-bitbox02nova-multi-development
+ firmware_rust_c
+ PRODUCT_BITBOX_MULTI
+ 1
+ BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI
+ ${BB02_BLUPD_BITBOX02NOVA_MULTI_DEVELOPMENT_STAGE0_IMAGE_OBJ}
+ ${BB02_BLUPD_BITBOX02NOVA_MULTI_DEVELOPMENT_PAYLOAD_OBJ}
+ TRUE)
+
# Copy the binary file to the output directory because the symbol names in
# the object file depend on the path to the binary file
add_custom_command(
@@ -559,7 +1095,7 @@ if(CMAKE_CROSSCOMPILING)
target_link_libraries(factory-setup.elf PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/bitbox-da14531-firmware.o)
- foreach(name ${BOOTLOADERS} ${FIRMWARES})
+ foreach(name ${FIRMWARES})
add_custom_command(
TARGET ${name}.elf POST_BUILD
COMMAND ${CMAKE_SIZE} ${name}.elf
diff --git a/src/bootloader/boot_args.h b/src/bootloader/boot_args.h
new file mode 100644
index 00000000..d934df64
--- /dev/null
+++ b/src/bootloader/boot_args.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef BOOT_ARGS_H
+#define BOOT_ARGS_H
+
+#include "util.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define BOOT_ARGS_ADDR (0x20000000U)
+#define BOOT_ARGS_LEN (512U)
+#define BOOT_ARGS_MAGIC (0xB007A265U)
+#define BOOTCMD_BOOTLOADER_WAIT (1)
+#define BOOT_ARGS_FLAG_UPSIDE_DOWN (1U << 0)
+
+typedef union {
+ uint8_t raw[BOOT_ARGS_LEN - 3U * sizeof(uint32_t)];
+} boot_args_command_args_t;
+
+typedef struct {
+ uint32_t magic;
+ uint32_t command;
+ uint32_t flags;
+ boot_args_command_args_t command_args;
+} boot_args_t;
+
+_Static_assert(sizeof(boot_args_t) == BOOT_ARGS_LEN, "boot_args_t must occupy boot_args area");
+_Static_assert(offsetof(boot_args_t, magic) == 0, "boot_args magic offset changed");
+_Static_assert(offsetof(boot_args_t, command) == 4, "boot_args command offset changed");
+_Static_assert(offsetof(boot_args_t, flags) == 8, "boot_args flags offset changed");
+_Static_assert(offsetof(boot_args_t, command_args) == 12, "boot_args command_args offset changed");
+
+static inline volatile boot_args_t* boot_args_ram(void)
+{
+ return (volatile boot_args_t*)BOOT_ARGS_ADDR;
+}
+
+static inline bool boot_args_is_valid(void)
+{
+ return boot_args_ram()->magic == BOOT_ARGS_MAGIC;
+}
+
+static inline bool boot_args_is_bootloader_wait(void)
+{
+ volatile boot_args_t* args = boot_args_ram();
+ return boot_args_is_valid() && args->command == BOOTCMD_BOOTLOADER_WAIT;
+}
+
+static inline bool boot_args_is_upside_down(void)
+{
+ return (boot_args_ram()->flags & BOOT_ARGS_FLAG_UPSIDE_DOWN) != 0;
+}
+
+static inline void boot_args_write_bootloader_wait(bool upside_down)
+{
+ volatile boot_args_t* args = boot_args_ram();
+ args->command = 0;
+ args->magic = BOOT_ARGS_MAGIC;
+ args->flags = upside_down ? BOOT_ARGS_FLAG_UPSIDE_DOWN : 0;
+ args->command = BOOTCMD_BOOTLOADER_WAIT;
+}
+
+static inline void boot_args_clear_command(void)
+{
+ boot_args_ram()->command = 0;
+}
+
+#endif
diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c
index 569f647a..69743f4e 100644
--- a/src/bootloader/bootloader.c
+++ b/src/bootloader/bootloader.c
@@ -6,6 +6,8 @@
#include "mpu_regions.h"
#include "pac_ext.h"
+#include <bootloader/boot_args.h>
+#include <bootloader/bootloader_product.h>
#include <driver_init.h>
#include <flags.h>
#include <memory/memory.h>
@@ -118,9 +120,9 @@ static uint8_t _firmware_num_chunks = 0;
// The value is computed at bootloader enter.
static bool _is_app_flash_empty = false;
-// A "bare" firmware hash where all app flash sections are empty.
-// Bare meaning the hash is computed in the same way as _firmware_hash except
-// the firmware version is omitted.
+// A "bare" app-flash hash where all app flash sections are empty. This is only used to decide
+// whether the bootloader should show empty-device UI; it is independent of the signed firmware
+// prehash below.
// If FLASH_APP_LEN is changed, recompute with either a shell command:
// printf '%884736s' | tr ' ' '\377' | openssl sha256 -binary | openssl sha256 -hex
// or python:
@@ -138,93 +140,26 @@ extern struct RustByteQueue* uart_write_queue;
#endif
// clang-format off
-#if PRODUCT_BITBOX_BTCONLY == 1
static const uint8_t _root_pubkeys[BOOT_NUM_ROOT_SIGNING_KEYS][BOOT_PUBKEY_LEN] = { // order is important
{
- 0x56, 0x82, 0xcc, 0xed, 0x54, 0x4e, 0xa6, 0xa1, 0x8f, 0x9e, 0x7c, 0x48, 0x40, 0xb8, 0x6d, 0x3d,
- 0x51, 0x4e, 0x49, 0x4a, 0x9f, 0x20, 0xde, 0xe7, 0x6b, 0x5a, 0x99, 0x2c, 0xe1, 0x3e, 0x77, 0xa9,
- 0x8a, 0x61, 0xe2, 0x34, 0x3e, 0x1f, 0x9e, 0xc7, 0x27, 0x7f, 0xf7, 0x50, 0xf2, 0x07, 0x09, 0x3a,
- 0xa0, 0xba, 0x36, 0x31, 0xa4, 0x0f, 0xcd, 0x5a, 0xd6, 0xd0, 0xaf, 0x38, 0x44, 0x19, 0xc8, 0x86,
+ 0x08, 0x16, 0xb6, 0x5a, 0xde, 0x0b, 0x75, 0x0f, 0xe2, 0xa5, 0xdf, 0x76, 0xb8, 0x89, 0x54, 0xdb,
+ 0x13, 0x62, 0xc4, 0x5f, 0xbf, 0x4d, 0xe2, 0xb8, 0x37, 0x60, 0x08, 0xff, 0x08, 0xbe, 0xad, 0xeb,
+ 0xf3, 0x3e, 0xda, 0xa8, 0xbd, 0x44, 0x4b, 0xcd, 0x7a, 0xc4, 0xbf, 0x61, 0x8c, 0x54, 0xfe, 0x54,
+ 0xcd, 0x3b, 0x3f, 0x82, 0x81, 0x70, 0xb0, 0xb4, 0x54, 0x61, 0xb7, 0xfa, 0x54, 0x5f, 0x0b, 0xb3,
},
{
- 0x3b, 0x13, 0x86, 0x55, 0x8f, 0xc8, 0x31, 0xd6, 0x3a, 0x30, 0x2b, 0x30, 0x84, 0xbf, 0x0a, 0xde,
- 0x07, 0x8b, 0xf6, 0x08, 0xec, 0x15, 0x20, 0x8c, 0x0f, 0xb9, 0x51, 0x7d, 0xbc, 0xb4, 0x48, 0xe6,
- 0x33, 0xb6, 0x40, 0xf3, 0xb6, 0x19, 0xbe, 0x9b, 0x94, 0x94, 0x4b, 0x80, 0x4f, 0xf6, 0x12, 0x1a,
- 0xcd, 0x0a, 0x4d, 0xe7, 0x6b, 0x60, 0x12, 0x64, 0x2f, 0x7a, 0x62, 0x65, 0x2e, 0xc0, 0x44, 0x65,
+ 0xf5, 0x9d, 0x95, 0x5d, 0x26, 0x12, 0x30, 0x9f, 0xe4, 0x66, 0x11, 0x1d, 0x91, 0x8f, 0x03, 0x26,
+ 0xd8, 0x4f, 0xf2, 0xc1, 0x52, 0x2b, 0xce, 0x59, 0x3f, 0x6c, 0x41, 0xf8, 0xbb, 0xcc, 0x30, 0xcb,
+ 0x5d, 0xae, 0x60, 0x13, 0x73, 0xf7, 0x79, 0xc3, 0x05, 0x2a, 0xbc, 0x2c, 0x82, 0xf9, 0xfe, 0x67,
+ 0x3a, 0xb8, 0xbc, 0x6d, 0xf0, 0x1b, 0xcd, 0xeb, 0xa2, 0x69, 0xdd, 0x56, 0xaf, 0x60, 0xa1, 0xa6,
},
{
- 0x93, 0x13, 0x34, 0xe4, 0x43, 0x6e, 0x4d, 0x41, 0xfe, 0x2a, 0xee, 0xbd, 0xf1, 0x25, 0x1f, 0x08,
- 0x35, 0xb2, 0xca, 0x5a, 0x9b, 0xc5, 0xca, 0x5b, 0x12, 0xcb, 0x72, 0xf9, 0xf7, 0xbf, 0xb4, 0x6f,
- 0x73, 0xf5, 0xe2, 0x3e, 0x93, 0x45, 0x50, 0x2c, 0xe0, 0xaf, 0xce, 0x7b, 0xd4, 0x12, 0x56, 0xa2,
- 0xde, 0x34, 0x43, 0x8e, 0x71, 0xdf, 0x99, 0xeb, 0x59, 0xb4, 0x1e, 0xb1, 0x32, 0x17, 0xda, 0x8a,
+ 0x5b, 0xa3, 0xa4, 0x2e, 0x8d, 0xd0, 0x59, 0xe0, 0x62, 0x08, 0xfb, 0x46, 0xfe, 0x8b, 0xd5, 0x2e,
+ 0x5e, 0xbd, 0xd5, 0x72, 0xf4, 0x4c, 0xb0, 0x43, 0xd9, 0x19, 0xbd, 0xf4, 0x7b, 0xf0, 0x51, 0x9a,
+ 0xff, 0xfd, 0xd9, 0x6b, 0x98, 0x4d, 0x61, 0x9f, 0xb4, 0x68, 0x4a, 0x95, 0x95, 0xab, 0x60, 0xe8,
+ 0xf9, 0x1f, 0xbf, 0x9c, 0x79, 0x9e, 0xef, 0x45, 0xbb, 0xac, 0x96, 0xa7, 0xe0, 0x84, 0xca, 0x1d,
},
};
-#elif PRODUCT_BITBOX_MULTI == 1
-static const uint8_t _root_pubkeys[BOOT_NUM_ROOT_SIGNING_KEYS][BOOT_PUBKEY_LEN] = { // order is important
- {
- 0x08, 0xa6, 0xdc, 0x5f, 0x9b, 0x9e, 0x0c, 0x74, 0x25, 0x06, 0x3d, 0x00, 0x77, 0x66, 0xe1, 0x69,
- 0x0a, 0x57, 0xe7, 0x2d, 0xdb, 0xab, 0xa6, 0x4e, 0x3d, 0x88, 0x75, 0x41, 0x6d, 0xd1, 0x86, 0x37,
- 0x9e, 0x01, 0x8c, 0x2a, 0xd1, 0xcf, 0x01, 0xf7, 0x0f, 0x92, 0x5c, 0x18, 0x4f, 0x64, 0x36, 0xa9,
- 0xc3, 0xf8, 0x9a, 0x9c, 0x75, 0x9c, 0x92, 0xdb, 0x6a, 0x1a, 0x75, 0xcb, 0x00, 0xb0, 0x26, 0x88,
- },
- {
- 0xf5, 0xb9, 0xd3, 0xa8, 0x43, 0x99, 0x2c, 0xb2, 0x5a, 0xcc, 0xd4, 0x20, 0xb8, 0x24, 0x65, 0x46,
- 0x77, 0xa2, 0x03, 0xb0, 0x11, 0x68, 0xdb, 0x97, 0x26, 0x8d, 0xe4, 0xd5, 0xd1, 0x94, 0x28, 0x95,
- 0x09, 0x3d, 0x22, 0x7e, 0x57, 0x8f, 0x19, 0x4f, 0x2c, 0xd8, 0x45, 0x05, 0x83, 0xdf, 0xe8, 0xfe,
- 0xfd, 0x41, 0xdd, 0xb6, 0x7b, 0x05, 0xfe, 0xc1, 0x32, 0xfa, 0xc1, 0x51, 0xe1, 0xbb, 0x44, 0xc7,
- },
- {
- 0xa9, 0x1a, 0x8e, 0xc6, 0x46, 0xfc, 0x37, 0x41, 0x64, 0xb5, 0xdc, 0xbf, 0x29, 0x80, 0xfd, 0xbf,
- 0xbc, 0xd1, 0x2b, 0x57, 0xaf, 0xa0, 0x29, 0xa4, 0x05, 0x5d, 0x7f, 0x9a, 0x81, 0x75, 0x0f, 0x18,
- 0xfc, 0x13, 0x48, 0xdc, 0xda, 0xbd, 0x6e, 0x33, 0x25, 0x5b, 0x29, 0xa5, 0xb7, 0x51, 0x16, 0xbf,
- 0xf0, 0xca, 0xde, 0x45, 0xd6, 0x1c, 0x51, 0x4d, 0x86, 0x09, 0xfc, 0xa7, 0x64, 0x1c, 0x9e, 0xe2,
- }
-};
-#elif PRODUCT_BITBOX_PLUS_BTCONLY == 1
-static const uint8_t _root_pubkeys[BOOT_NUM_ROOT_SIGNING_KEYS][BOOT_PUBKEY_LEN] = { // order is important
- {
- 0x42, 0xeb, 0x2f, 0xfa, 0x68, 0xd8, 0xc4, 0x62, 0x5a, 0x01, 0x2b, 0x46, 0x7f, 0x04, 0x4a, 0xfc,
- 0x2c, 0x38, 0x1b, 0x89, 0x4a, 0x61, 0x29, 0xea, 0x4c, 0x94, 0xd7, 0xbd, 0x97, 0x19, 0x83, 0x75,
- 0xe9, 0x85, 0x96, 0xcf, 0xff, 0x40, 0xec, 0x7c, 0xa7, 0xbc, 0x7a, 0x0d, 0x04, 0x0b, 0xb3, 0x46,
- 0x95, 0x92, 0x04, 0x56, 0x18, 0x81, 0x2d, 0x1a, 0x56, 0xa9, 0x47, 0x82, 0xfa, 0x2d, 0x90, 0xd4,
- },
- {
- 0x76, 0x79, 0x4b, 0x9e, 0xff, 0x0d, 0x32, 0x14, 0xd3, 0x56, 0x7a, 0xc0, 0x13, 0x17, 0xc4, 0xcd,
- 0x6f, 0x9b, 0x7d, 0x66, 0xb8, 0x9a, 0xfe, 0x58, 0xf3, 0xd0, 0x39, 0x32, 0x3d, 0x12, 0xb0, 0xc5,
- 0xc8, 0x08, 0xfc, 0xd7, 0x57, 0x51, 0x4c, 0x9d, 0xf3, 0xed, 0x75, 0xcb, 0xba, 0x80, 0x07, 0x27,
- 0xb9, 0x8a, 0x13, 0x5a, 0x86, 0xbc, 0xb7, 0xcf, 0x87, 0x2a, 0x41, 0x09, 0x8d, 0x02, 0x36, 0x32,
- },
- {
- 0x3d, 0x67, 0x3b, 0x5b, 0x4a, 0x6e, 0xdb, 0x33, 0xe0, 0x2d, 0x2b, 0xe7, 0xe4, 0x1d, 0xf5, 0x74,
- 0x33, 0x1d, 0x66, 0xf0, 0xdf, 0xfe, 0x44, 0x8f, 0xd3, 0x52, 0x50, 0x3c, 0x3b, 0xe3, 0x91, 0xfc,
- 0x70, 0xd0, 0xd8, 0xa5, 0xed, 0x72, 0x4c, 0xda, 0xbd, 0x86, 0xe6, 0x3e, 0xdb, 0x1c, 0x28, 0xae,
- 0x1d, 0xc3, 0xd6, 0x6b, 0xc5, 0x51, 0x54, 0x67, 0xba, 0xb1, 0xc1, 0xcb, 0x24, 0x48, 0xa8, 0x7a,
- }
-};
-#elif PRODUCT_BITBOX_PLUS_MULTI == 1
-static const uint8_t _root_pubkeys[BOOT_NUM_ROOT_SIGNING_KEYS][BOOT_PUBKEY_LEN] = { // order is important
- {
- 0x5e, 0x1b, 0x09, 0x1c, 0x8f, 0x71, 0x15, 0xaf, 0xd3, 0x3c, 0x0b, 0x72, 0xe4, 0x4b, 0x3e, 0xd0,
- 0xe1, 0x7a, 0x3c, 0xc4, 0xff, 0x99, 0xf5, 0x65, 0x31, 0xda, 0x11, 0x29, 0x30, 0xb9, 0xf6, 0x70,
- 0x0e, 0x96, 0xd9, 0xb0, 0x15, 0x70, 0xb7, 0x7a, 0x56, 0xc9, 0x8d, 0x75, 0x15, 0x43, 0xbc, 0x36,
- 0xc6, 0xee, 0xe2, 0xbc, 0x9b, 0xfe, 0xce, 0xf7, 0x39, 0xe2, 0xe5, 0xf4, 0xb2, 0xba, 0xdf, 0x4e,
- },
- {
- 0xab, 0xf3, 0x5f, 0x53, 0x84, 0xa0, 0x3f, 0x01, 0x91, 0x5c, 0x68, 0xa9, 0xca, 0xb2, 0x53, 0xe8,
- 0xbb, 0xc9, 0x8d, 0x88, 0x7a, 0x72, 0x2a, 0x82, 0xa6, 0x2e, 0x44, 0x1b, 0xb4, 0xd2, 0x2c, 0xdb,
- 0x87, 0x0f, 0x89, 0xdb, 0x22, 0xcf, 0xfd, 0x8a, 0xc2, 0xac, 0xc4, 0x0a, 0x7e, 0xc0, 0x69, 0x89,
- 0x76, 0xb9, 0xa3, 0x6c, 0xee, 0x74, 0xd7, 0x2b, 0xd3, 0x54, 0xe9, 0x0e, 0x59, 0x77, 0x78, 0x47,
- },
- {
- 0xe2, 0xd2, 0x04, 0x13, 0x74, 0x8e, 0xfa, 0xdc, 0x81, 0xb2, 0x66, 0x79, 0x06, 0x1c, 0x7c, 0x97,
- 0x0f, 0x47, 0x74, 0xcc, 0x2a, 0xb0, 0x54, 0xa1, 0x46, 0x83, 0x75, 0xff, 0x37, 0xf1, 0x9a, 0x61,
- 0x0c, 0x23, 0x29, 0xc4, 0xe9, 0xed, 0x85, 0xac, 0x3a, 0x68, 0x4c, 0xf4, 0x3e, 0x89, 0x81, 0x1d,
- 0x58, 0xa9, 0xec, 0x5b, 0x6f, 0x40, 0xa3, 0xdd, 0x6a, 0xde, 0x28, 0x79, 0xcb, 0x21, 0x74, 0xcc,
- }
-};
-#else
-#error "unknown product"
-#endif
// clang-format on
const uint8_t _empty_sig[BOOT_SIG_LEN] = {0};
@@ -551,18 +486,24 @@ static inline version_t _parse_version(const uint8_t* start)
return version;
}
-static void _double_hash(const uint8_t* data, uint32_t len, uint8_t* hash)
+static void _hash_product_id(void)
{
- pukcc_sha256_compute(data, len, hash);
- pukcc_sha256_compute(hash, SHA256_DIGEST_LENGTH, hash);
+ const uint16_t product_id = BB02_STAGE1_PRODUCT_ID;
+ sha_sync_sha256_update(&HASH_ALGORITHM_0, (const uint8_t*)&product_id, sizeof(product_id));
}
+// Computes sha256(product_id_le16 | signing_pubkeys_version | signing_pubkeys).
+// pukcc_ecdsa_verify() hashes this prehash once more, so signatures cover:
+// sha256(sha256(product_id_le16 | signing_pubkeys_version | signing_pubkeys)).
static void _hash_signing_keys(const boot_data_t* data, uint8_t* hash_out)
{
- _double_hash(
+ sha_sync_sha256_start(&HASH_ALGORITHM_0, &_pukcc_sha256_context, false);
+ _hash_product_id();
+ sha_sync_sha256_update(
+ &HASH_ALGORITHM_0,
(const uint8_t*)(&data->fields.signing_pubkeys_version),
- sizeof(data->fields.signing_pubkeys_version) + sizeof(data->fields.signing_pubkeys),
- hash_out);
+ sizeof(data->fields.signing_pubkeys_version) + sizeof(data->fields.signing_pubkeys));
+ sha_sync_sha256_finish(&HASH_ALGORITHM_0, hash_out);
}
static secbool_u32 _pubkeys_verified(const boot_data_t* data)
@@ -595,17 +536,19 @@ static secbool_u32 _pubkeys_verified(const boot_data_t* data)
return secfalse_u32;
}
-// double hashes firmware version | firmware
+// Computes sha256(product_id_le16 | firmware_version | firmware).
+// pukcc_ecdsa_verify() hashes this prehash once more, so signatures cover:
+// sha256(sha256(product_id_le16 | firmware_version | firmware)).
static void _firmware_hash(const boot_data_t* data, uint8_t* hash_out)
{
sha_sync_sha256_start(&HASH_ALGORITHM_0, &_pukcc_sha256_context, false);
+ _hash_product_id();
sha_sync_sha256_update(
&HASH_ALGORITHM_0,
(const uint8_t*)&data->fields.firmware_version,
sizeof(data->fields.firmware_version));
sha_sync_sha256_update(&HASH_ALGORITHM_0, (const uint8_t*)FLASH_APP_START, FLASH_APP_LEN);
sha_sync_sha256_finish(&HASH_ALGORITHM_0, hash_out);
- pukcc_sha256_compute(hash_out, SHA256_DIGEST_LENGTH, hash_out);
}
static void _maybe_show_hash(void)
@@ -691,7 +634,8 @@ static uint8_t _write_chunk(uint32_t address, const uint8_t* data)
}
/*
- * input: firmware version | i signatures of the double hash of the [version | firmware app]
+ * input: firmware version | i signatures of the double hash of
+ * [product id | version | firmware app]
*/
static uint8_t _set_firmware_data(boot_data_t* data, const uint8_t* input)
{
@@ -715,8 +659,8 @@ static uint8_t _set_firmware_data(boot_data_t* data, const uint8_t* input)
}
/*
- * input: signing pubkeys version | i signing pubkeys | j signatures of the double hash of [version
- * | signing pubkeys]
+ * input: signing pubkeys version | i signing pubkeys | j signatures of the double hash of
+ * [product id | version | signing pubkeys]
*/
static uint8_t _set_signing_pubkey_data(boot_data_t* data, const uint8_t* input)
{
@@ -764,7 +708,7 @@ static size_t _api_write_sig_data(const uint8_t* input, uint8_t* output)
}
/*
- * output filled with double hash of firmware app | double hash of signing key data
+ * output filled with the firmware and signing-key prehashes passed to PUKCC verification.
*/
static size_t _api_get_hashes(const uint8_t* input, uint8_t* output)
{
@@ -1050,13 +994,23 @@ void bootloader_jump(void)
_check_init(&bootdata);
- if (shared_data.fields.upside_down) {
+ const bool boot_args_valid = boot_args_is_valid();
+ bool upside_down = shared_data.fields.upside_down != 0;
+ if (boot_args_valid) {
+ upside_down = boot_args_is_upside_down();
+ }
+ if (upside_down) {
screen_rotate();
}
+ const bool bootloader_wait = boot_args_is_bootloader_wait();
+ if (bootloader_wait) {
+ boot_args_clear_command();
+ }
+
UG_FontSelect(&font_font_a_9X9);
- if (shared_data.fields.auto_enter != sectrue_u8) {
+ if (!bootloader_wait && shared_data.fields.auto_enter != sectrue_u8) {
#ifdef BOOTLOADER_DEVDEVICE
if (!_devdevice_enter(_firmware_verified_jump(&bootdata, secfalse_u32))) {
_binary_exec();
@@ -1064,7 +1018,6 @@ void bootloader_jump(void)
}
#else
_firmware_verified_jump(&bootdata, sectrue_u32); // no return if firmware is valid
- _render_message("Firmware\ninvalid\n \nEntering bootloader", 3000);
#endif
}
diff --git a/src/bootloader/bootloader_product.h b/src/bootloader/bootloader_product.h
new file mode 100644
index 00000000..733eb9e0
--- /dev/null
+++ b/src/bootloader/bootloader_product.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _BOOTLOADER_PRODUCT_H_
+#define _BOOTLOADER_PRODUCT_H_
+
+#include <platform_config.h>
+#include <stdint.h>
+
+#define BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI (1u)
+#define BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY (2u)
+#define BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI (3u)
+#define BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY (4u)
+
+#if defined(BB02_STAGE1_TARGET_PRODUCT_ID)
+ #if BB02_STAGE1_TARGET_PRODUCT_ID != BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI && \
+ BB02_STAGE1_TARGET_PRODUCT_ID != BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY && \
+ BB02_STAGE1_TARGET_PRODUCT_ID != BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI && \
+ BB02_STAGE1_TARGET_PRODUCT_ID != BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY
+ #error "invalid bootloader target product"
+ #endif
+ #define BB02_STAGE1_PRODUCT_ID BB02_STAGE1_TARGET_PRODUCT_ID
+#elif PRODUCT_BITBOX_MULTI == 1
+ #define BB02_STAGE1_PRODUCT_ID BB02_STAGE1_PRODUCT_ID_BITBOX_MULTI
+#elif PRODUCT_BITBOX_BTCONLY == 1
+ #define BB02_STAGE1_PRODUCT_ID BB02_STAGE1_PRODUCT_ID_BITBOX_BTCONLY
+#elif PRODUCT_BITBOX_PLUS_MULTI == 1
+ #define BB02_STAGE1_PRODUCT_ID BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_MULTI
+#elif PRODUCT_BITBOX_PLUS_BTCONLY == 1
+ #define BB02_STAGE1_PRODUCT_ID BB02_STAGE1_PRODUCT_ID_BITBOX_PLUS_BTCONLY
+#else
+ #error "unknown product"
+#endif
+
+_Static_assert(BB02_STAGE1_PRODUCT_ID <= UINT16_MAX, "stage1 product id too large");
+
+#endif
diff --git a/src/bootloader/stage0/stage0.c b/src/bootloader/stage0/stage0.c
new file mode 100644
index 00000000..91c65e91
--- /dev/null
+++ b/src/bootloader/stage0/stage0.c
@@ -0,0 +1,664 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "bootloader/boot_args.h"
+#include "bootloader_upgrade/bootloader_upgrade.h"
+#ifndef BB02_STAGE0_DEVELOPMENT
+ #include "bootloader_upgrade/stage1_pubkeys.h"
+#endif
+#include "driver_init.h"
+#include "memory/memory_shared.h"
+#include "pac_ext.h"
+#include "stage0_flash.h"
+#include "ui/oled/oled.h"
+#include "util.h"
+#ifndef BB02_STAGE0_DEVELOPMENT
+ #include "pukcc/curve_p256.h"
+ #include "pukcc/pukcc.h"
+ #include "stage1_sigcheck.h"
+#endif
+#include <err_codes.h>
+#include <hal_flash.h>
+#include <hal_sha_sync.h>
+#include <hpl_cmcc_config.h>
+#include <hpl_dmac_config.h>
+#include <hpl_port_config.h>
+#include <hpl_reset.h>
+#include <hri_nvmctrl_d51.h>
+#include <sam.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#if CONF_DMAC_ENABLE
+ #error "stage0 _init_chip() must not initialize DMAC"
+#endif
+
+#if CONF_CMCC_ENABLE
+ #error "stage0 _init_chip() must not initialize CMCC"
+#endif
+
+#if CONF_PORT_EVCTRL_PORT_0 || CONF_PORT_EVCTRL_PORT_1 || CONF_PORT_EVCTRL_PORT_2 || \
+ CONF_PORT_EVCTRL_PORT_3
+ #error "stage0 _init_chip() must not initialize PORT events"
+#endif
+
+#define OLED_WIDTH (128)
+#define OLED_HEIGHT (64)
+#define NVMCTRL_STAGE0_ERROR_FLAGS \
+ (NVMCTRL_INTFLAG_ADDRE | NVMCTRL_INTFLAG_PROGE | NVMCTRL_INTFLAG_LOCKE | NVMCTRL_INTFLAG_NVME)
+static bool _oled_initialized = false;
+
+__attribute__((aligned(128))) static struct sha_context _sha_context;
+
+// GCC LTO needs externally_visible; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void __attribute__((noreturn, used, externally_visible)) __stack_chk_fail(void);
+
+static void _oled_render_error(void);
+
+static void __attribute__((noreturn)) _halt(void)
+{
+ _oled_render_error();
+ while (1) {
+ __WFI();
+ }
+}
+
+static void __attribute__((noreturn)) _reset(void)
+{
+ _reset_mcu();
+ _halt();
+}
+
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+uintptr_t __attribute__((used, externally_visible)) __stack_chk_guard = 0;
+
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void __attribute__((noreturn, used, externally_visible)) __stack_chk_fail(void)
+{
+ _halt();
+}
+
+static secbool_u32 _memeq(const uint8_t* a, const uint8_t* b, size_t len)
+{
+ uint8_t diff = 0;
+ for (size_t i = 0; i < len; i++) {
+ diff |= a[i] ^ b[i];
+ }
+ if (diff == 0) {
+ return sectrue_u32;
+ }
+ return secfalse_u32;
+}
+
+static const chunk_shared_t* _shared_data(void)
+{
+ return (const chunk_shared_t*)FLASH_SHARED_DATA_START;
+}
+
+uint8_t memory_get_screen_type(void)
+{
+ const uint8_t screen_type = _shared_data()->fields.screen_type;
+ if (screen_type == MEMORY_SCREEN_TYPE_SSD1312) {
+ return MEMORY_SCREEN_TYPE_SSD1312;
+ }
+ return MEMORY_SCREEN_TYPE_SH1107;
+}
+
+static bool _oled_upside_down(void)
+{
+ const bool flash_upside_down = _shared_data()->fields.upside_down != 0;
+ if (boot_args_is_valid()) {
+ return boot_args_is_upside_down();
+ }
+ return flash_upside_down;
+}
+
+#ifdef BB02_STAGE0_DEVELOPMENT
+static void _oled_development_cross_overlay(void)
+{
+ for (int16_t x = 0; x < OLED_WIDTH; x++) {
+ const int16_t y = (int16_t)(((int32_t)x * (OLED_HEIGHT - 1)) / (OLED_WIDTH - 1));
+ oled_set_pixel(x, y, true);
+ oled_set_pixel(x, y + 1, true);
+ oled_set_pixel(x, (OLED_HEIGHT - 1) - y, true);
+ oled_set_pixel(x, (OLED_HEIGHT - 2) - y, true);
+ }
+}
+
+static void _oled_development_cross(void)
+{
+ oled_clear_buffer();
+ _oled_development_cross_overlay();
+ oled_send_buffer();
+}
+#endif
+
+static void _oled_progress(uint8_t done, uint8_t total)
+{
+ oled_clear_buffer();
+ const int16_t width = 100;
+ const int16_t height = 10;
+ const int16_t border_width = 1;
+ const int16_t x0 = (int16_t)((OLED_WIDTH - width) / 2);
+ const int16_t y0 = (int16_t)((OLED_HEIGHT - height) / 2);
+ const int16_t fill = (int16_t)(((uint32_t)(width - 2 * border_width) * done) / total);
+ for (int16_t x = x0; x < x0 + width; x++) {
+ oled_set_pixel(x, y0, true);
+ oled_set_pixel(x, y0 + height - 1, true);
+ }
+ for (int16_t y = y0; y < y0 + height; y++) {
+ oled_set_pixel(x0, y, true);
+ oled_set_pixel(x0 + width - 1, y, true);
+ }
+ const int16_t y_fill_end = (int16_t)(y0 + height - border_width);
+ const int16_t x_fill_end = (int16_t)(x0 + border_width + fill);
+ for (int16_t y = (int16_t)(y0 + border_width); y < y_fill_end; y++) {
+ for (int16_t x = (int16_t)(x0 + border_width); x < x_fill_end; x++) {
+ oled_set_pixel(x, y, true);
+ }
+ }
+#ifdef BB02_STAGE0_DEVELOPMENT
+ _oled_development_cross_overlay();
+#endif
+ oled_send_buffer();
+}
+
+static void _oled_init(void)
+{
+ oled_init();
+ oled_mirror(_oled_upside_down());
+ _oled_initialized = true;
+}
+
+static void _oled_draw_error_pixel(int16_t x, int16_t y)
+{
+ const int16_t scale = 2;
+ for (int16_t dy = 0; dy < scale; dy++) {
+ for (int16_t dx = 0; dx < scale; dx++) {
+ oled_set_pixel((int16_t)(x + dx), (int16_t)(y + dy), true);
+ }
+ }
+}
+
+// Render a centered fixed "Error" bitmap. The framebuffer is cleared first,
+// then only the bitmap's set pixels are drawn, scaled up 2x.
+static void _oled_render_error(void)
+{
+ static const uint32_t error_bitmap[7] = {
+ 0x1f000000U,
+ 0x10000000U,
+ 0x10596396U,
+ 0x1e659459U,
+ 0x10410450U,
+ 0x10410450U,
+ 0x1f410390U,
+ };
+ const int16_t bitmap_width = 29;
+ const int16_t bitmap_height = 7;
+ const int16_t scale = 2;
+ const int16_t text_width = (int16_t)(bitmap_width * scale);
+ const int16_t text_height = (int16_t)(bitmap_height * scale);
+ const int16_t x0 = (int16_t)((OLED_WIDTH - text_width) / 2);
+ const int16_t y0 = (int16_t)((OLED_HEIGHT - text_height) / 2);
+
+ if (!_oled_initialized) {
+ _oled_init();
+ }
+ oled_clear_buffer();
+ for (int16_t row = 0; row < bitmap_height; row++) {
+ const uint32_t bits = error_bitmap[row];
+ for (int16_t col = 0; col < bitmap_width; col++) {
+ if ((bits & (1UL << (bitmap_width - 1 - col))) == 0) {
+ continue;
+ }
+ _oled_draw_error_pixel((int16_t)(x0 + col * scale), (int16_t)(y0 + row * scale));
+ }
+ }
+ oled_send_buffer();
+}
+
+static void _nvm_wait(void)
+{
+ while (!hri_nvmctrl_get_STATUS_READY_bit(NVMCTRL)) {
+ }
+}
+
+static void _nvm_clear_errors(void)
+{
+ hri_nvmctrl_clear_INTFLAG_reg(NVMCTRL, NVMCTRL_STAGE0_ERROR_FLAGS);
+}
+
+static void _nvm_check_errors(void)
+{
+ if ((hri_nvmctrl_read_INTFLAG_reg(NVMCTRL) & NVMCTRL_STAGE0_ERROR_FLAGS) != 0) {
+ _halt();
+ }
+}
+
+#ifndef BB02_STAGE0_DEVELOPMENT
+static void _lock_debug_access(void)
+{
+ // Hard lock the Device Service Unit, i.e., set the PAC write-protection bit,
+ // which can only be cleared by a hardware reset. Because the DSU is soft
+ // locked by default on reset, an unlock is required before a hard lock.
+ periph_unlock(DSU);
+ periph_lock_hard(DSU);
+
+ // Set the security bit to disable hardware debug access if not already set.
+ // The security bit is persistent and erased on chip erase. A chip erase is
+ // disabled by hard locking the DSU.
+ if (!DSU->STATUSB.bit.PROT) {
+ _nvm_wait();
+ do {
+ NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_SSB | NVMCTRL_CTRLB_CMDEX_KEY;
+ while (NVMCTRL->INTFLAG.bit.DONE == 0 || NVMCTRL->STATUS.bit.READY == 0) {
+ }
+ } while (NVMCTRL->INTFLAG.bit.PROGE); // Program Error flag
+ // Software reset the NVMCTRL peripheral to have correct NVM state output
+ NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_SWRST | NVMCTRL_CTRLB_CMDEX_KEY;
+ _nvm_wait();
+ }
+}
+#endif
+
+// GCC needs noclone; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+static void __attribute__((noinline, noclone)) _require_auth(const volatile secbool_u32* auth)
+{
+ if (*auth != sectrue_u32) {
+ _halt();
+ }
+}
+
+static void _flash_unlock_region(uint32_t addr, const volatile secbool_u32* install_auth)
+{
+ const uint32_t region_addr = addr & ~(STAGE0_FLASH_REGION_SIZE_BYTES - 1U);
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_clear_errors();
+ if (flash_unlock(&FLASH_0, region_addr, FLASH_REGION_PAGE_NUM) != FLASH_REGION_PAGE_NUM) {
+ _halt();
+ }
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_check_errors();
+}
+
+static void _nvm_disable_bootprot(const volatile secbool_u32* install_auth)
+{
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_clear_errors();
+ hri_nvmctrl_write_CTRLB_reg(NVMCTRL, NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_SBPDIS);
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_check_errors();
+}
+
+static void _flash_erase_block(uint32_t addr, const volatile secbool_u32* install_auth)
+{
+ if (stage0_flash_block_addr_ok(addr) != sectrue_u32) {
+ _halt();
+ }
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_clear_errors();
+ if (flash_erase(&FLASH_0, addr, STAGE0_FLASH_BLOCK_SIZE_BYTES / STAGE0_FLASH_PAGE_SIZE_BYTES) !=
+ ERR_NONE) {
+ _halt();
+ }
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_check_errors();
+}
+
+static void _flash_write_page(
+ uint32_t addr,
+ uint32_t* page_words,
+ const volatile secbool_u32* install_auth)
+{
+ if (stage0_flash_page_addr_ok(addr) != sectrue_u32) {
+ _halt();
+ }
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_clear_errors();
+ if (flash_append(&FLASH_0, addr, (uint8_t*)page_words, STAGE0_FLASH_PAGE_SIZE_BYTES) !=
+ ERR_NONE) {
+ _halt();
+ }
+ _require_auth(install_auth);
+ _nvm_wait();
+ _nvm_check_errors();
+}
+
+static secbool_u32 _flash_block_erased(uint32_t addr)
+{
+ const uint32_t* words = (const uint32_t*)addr;
+ for (uint32_t i = 0; i < STAGE0_FLASH_BLOCK_SIZE_BYTES / sizeof(uint32_t); i++) {
+ if (words[i] != UINT32_MAX) {
+ return secfalse_u32;
+ }
+ }
+ return sectrue_u32;
+}
+
+static secbool_u32 _factory_random_backup_valid(
+ uint8_t factory_random_out[BB02_STAGE1_FACTORY_RANDOM_LEN])
+{
+ return stage0_factory_random_backup_valid(
+ (const stage0_factory_random_backup_t*)BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR,
+ factory_random_out);
+}
+
+static void _factory_random_backup_create(
+ const uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN],
+ const volatile secbool_u32* install_auth)
+{
+ if (_flash_block_erased(BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR) != sectrue_u32) {
+ _flash_erase_block(BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR, install_auth);
+ }
+
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ stage0_factory_random_backup_make_data_page(page, factory_random);
+ _flash_write_page(BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR, page, install_auth);
+
+ stage0_factory_random_backup_make_commit_page(page);
+ _flash_write_page(BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR, page, install_auth);
+
+ uint8_t verify[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ if (_factory_random_backup_valid(verify) != sectrue_u32 ||
+ _memeq(verify, factory_random, sizeof(verify)) != sectrue_u32) {
+ _halt();
+ }
+}
+
+static void _factory_random_load_or_create_backup(
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN],
+ secbool_u32 installed_header_ok,
+ const volatile secbool_u32* install_auth)
+{
+ uint8_t current[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ memcpy(current, (const void*)BB02_STAGE1_FACTORY_RANDOM_ADDR, BB02_STAGE1_FACTORY_RANDOM_LEN);
+
+ uint8_t backup[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ const secbool_u32 backup_valid = _factory_random_backup_valid(backup);
+ const secbool_u32 current_matches_backup =
+ backup_valid == sectrue_u32 ? _memeq(current, backup, sizeof(current)) : secfalse_u32;
+
+ if (stage0_factory_random_source(installed_header_ok, backup_valid, current_matches_backup) ==
+ STAGE0_FACTORY_RANDOM_SOURCE_BACKUP) {
+ memcpy(factory_random, backup, sizeof(backup));
+ return;
+ }
+
+ memcpy(factory_random, current, sizeof(current));
+ _factory_random_backup_create(factory_random, install_auth);
+}
+
+static secbool_u32 _stage1_marketing_version_len_ok(const bb02_stage1_header_t* header)
+{
+ if (header->stage1_marketing_version_len >
+ BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN) {
+ return secfalse_u32;
+ }
+ return sectrue_u32;
+}
+
+static secbool_u32 _stage1_header_len_ok(uint32_t header_len)
+{
+ if (header_len < BB02_STAGE1_HEADER_LEN || header_len > BB02_STAGE1_MAX_LEN) {
+ return secfalse_u32;
+ }
+ if ((header_len % BB02_STAGE1_HEADER_ALIGNMENT) != 0) {
+ return secfalse_u32;
+ }
+ return sectrue_u32;
+}
+
+static uint32_t _stage1_vector_addr(const bb02_stage1_header_t* header)
+{
+ return BB02_STAGE1_ADDR + (uint32_t)header->header_len;
+}
+
+static secbool_u32 _stage1_flags_ok(const bb02_stage1_header_t* header)
+{
+#ifndef BB02_STAGE0_DEVELOPMENT
+ if ((header->flags & BB02_STAGE1_FLAG_DEVELOPMENT) != 0) {
+ return secfalse_u32;
+ }
+#else
+ if ((header->flags & BB02_STAGE1_FLAG_DEVELOPMENT) == 0) {
+ return secfalse_u32;
+ }
+#endif
+ return sectrue_u32;
+}
+
+static secbool_u32 _header_basic_ok(const bb02_stage1_header_t* header)
+{
+ const uint32_t header_len = (uint32_t)header->header_len;
+ if (header->magic != BB02_STAGE1_HEADER_MAGIC ||
+ _stage1_header_len_ok(header_len) != sectrue_u32 || header->image_len <= header_len ||
+ header->image_len > BB02_STAGE1_MAX_LEN || header->product_id != BB02_STAGE1_PRODUCT_ID ||
+ _stage1_flags_ok(header) != sectrue_u32 ||
+ _stage1_marketing_version_len_ok(header) != sectrue_u32) {
+ return secfalse_u32;
+ }
+ return sectrue_u32;
+}
+
+static void _stage1_hash(const bb02_stage1_header_t* header, uint8_t hash_out[32])
+{
+ const uint32_t image_len = (uint32_t)header->image_len;
+ sha_sync_sha256_start(&HASH_ALGORITHM_0, &_sha_context, false);
+ sha_sync_sha256_update(&HASH_ALGORITHM_0, (const uint8_t*)header, image_len);
+ sha_sync_sha256_finish(&HASH_ALGORITHM_0, hash_out);
+}
+
+#ifndef BB02_STAGE0_DEVELOPMENT
+static secbool_u32 _header_signatures_ok(const bb02_stage1_header_t* header)
+{
+ return stage1_sigcheck_image_ok(header, bb02_stage1_pubkeys);
+}
+#endif
+
+static secbool_u32 _stage1_image_ok(const bb02_stage1_header_t* header, uint8_t hash_out[32])
+{
+ if (_header_basic_ok(header) != sectrue_u32) {
+ return secfalse_u32;
+ }
+
+ _stage1_hash(header, hash_out);
+#ifdef BB02_STAGE0_DEVELOPMENT
+ return sectrue_u32;
+#else
+ return _header_signatures_ok(header);
+#endif
+}
+
+static void _install_stage1(
+ const bb02_stage1_header_t* update,
+ secbool_u32 installed_header_ok,
+ const volatile secbool_u32* install_auth)
+{
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ const uint32_t header_len = (uint32_t)update->header_len;
+ const uint32_t image_len = (uint32_t)update->image_len;
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+
+ _nvm_disable_bootprot(install_auth);
+ _flash_unlock_region(BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR, install_auth);
+ _factory_random_load_or_create_backup(factory_random, installed_header_ok, install_auth);
+ _flash_unlock_region(0x00000000U, install_auth);
+ _flash_unlock_region(0x00008000U, install_auth);
+
+ for (uint32_t block = BB02_STAGE1_ADDR; block < BB02_STAGE1_FACTORY_RANDOM_ADDR;
+ block += STAGE0_FLASH_BLOCK_SIZE_BYTES) {
+ _flash_erase_block(block, install_auth);
+ if (block <= STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR &&
+ block + STAGE0_FLASH_BLOCK_SIZE_BYTES > STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR) {
+ stage0_flash_make_stage1_page(
+ page,
+ (const uint8_t*)update,
+ image_len,
+ STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR,
+ factory_random);
+ _flash_write_page(STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR, page, install_auth);
+ // Check that the write succeeded.
+ if (_memeq(
+ (const uint8_t*)BB02_STAGE1_FACTORY_RANDOM_ADDR,
+ factory_random,
+ BB02_STAGE1_FACTORY_RANDOM_LEN) != sectrue_u32) {
+ _halt();
+ }
+ }
+ for (uint32_t page_offset = 0; page_offset < STAGE0_FLASH_BLOCK_SIZE_BYTES;
+ page_offset += STAGE0_FLASH_PAGE_SIZE_BYTES) {
+ const uint32_t dst_addr = block + page_offset;
+ if ((dst_addr >= BB02_STAGE1_HEADER_ADDR &&
+ dst_addr < BB02_STAGE1_HEADER_ADDR + header_len) ||
+ dst_addr == STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR) {
+ continue;
+ }
+ stage0_flash_make_stage1_page(
+ page, (const uint8_t*)update, image_len, dst_addr, factory_random);
+ _flash_write_page(dst_addr, page, install_auth);
+ }
+ _oled_progress(
+ (uint8_t)((block - BB02_STAGE1_ADDR) / STAGE0_FLASH_BLOCK_SIZE_BYTES + 1), 6);
+ }
+ for (uint32_t header_offset = 0; header_offset < header_len;
+ header_offset += STAGE0_FLASH_PAGE_SIZE_BYTES) {
+ const uint32_t dst_addr = BB02_STAGE1_HEADER_ADDR + header_offset;
+ stage0_flash_make_stage1_page(page, (const uint8_t*)update, image_len, dst_addr, NULL);
+ _flash_write_page(dst_addr, page, install_auth);
+ }
+}
+
+static void _invalidate_stage1_header(const volatile secbool_u32* install_auth)
+{
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ stage0_flash_make_invalid_header_page(page);
+ _flash_write_page(BB02_STAGE1_HEADER_ADDR, page, install_auth);
+}
+
+static secbool_u32 _valid_stage1_vector_table(const bb02_stage1_header_t* stage1)
+{
+ const uint32_t header_len = (uint32_t)stage1->header_len;
+ if (_stage1_header_len_ok(header_len) != sectrue_u32 || stage1->image_len <= header_len ||
+ stage1->image_len > BB02_STAGE1_MAX_LEN) {
+ return secfalse_u32;
+ }
+ const uint32_t vector_addr = _stage1_vector_addr(stage1);
+ const uint32_t* vectors = (const uint32_t*)vector_addr;
+ const uint32_t sp = vectors[0];
+ const uint32_t pc = vectors[1];
+ const uint32_t pc_addr = pc & ~1U;
+ const uint32_t image_end = BB02_STAGE1_ADDR + (uint32_t)stage1->image_len;
+ if (sp < BOOT_ARGS_ADDR + BOOT_ARGS_LEN || sp > 0x20040000U || (pc & 1U) == 0 ||
+ pc_addr < vector_addr || pc_addr >= image_end) {
+ return secfalse_u32;
+ }
+ return sectrue_u32;
+}
+
+static void _stage1_exec(const void* l_code_addr) __attribute__((noreturn));
+static void _stage1_exec(const void* l_code_addr)
+{
+ __asm__ volatile(
+ "ldr r1, [%[vectors], #4] \n"
+ "ldr sp, [%[vectors]] \n"
+ "blx r1 \n"
+ :
+ : [vectors] "r"(l_code_addr)
+ : "r1", "memory");
+ __builtin_unreachable();
+}
+
+static void _boot_stage1(const bb02_stage1_header_t* stage1, const volatile secbool_u32* boot_auth)
+{
+ _require_auth(boot_auth);
+ if (_valid_stage1_vector_table(stage1) != sectrue_u32) {
+ _halt();
+ }
+ const uint32_t vector_addr = _stage1_vector_addr(stage1);
+ const void* vectors = (const void*)vector_addr;
+ _require_auth(boot_auth);
+ stage0_deinit();
+ __disable_irq();
+ for (uint32_t i = 0; i < 8; i++) {
+ NVIC->ICER[i] = 0xFFFFFFFF;
+ }
+ for (uint32_t i = 0; i < 8; i++) {
+ NVIC->ICPR[i] = 0xFFFFFFFF;
+ }
+ __DSB();
+ __ISB();
+ SCB->VTOR = vector_addr;
+ __DSB();
+ __ISB();
+ _stage1_exec(vectors);
+}
+
+static void __attribute__((noreturn, noinline)) _stage0_main(void)
+{
+#ifdef BB02_STAGE0_DEVELOPMENT
+ _oled_init();
+ _oled_development_cross();
+#else
+ _lock_debug_access();
+#endif
+
+ uint8_t installed_hash[32];
+ uint8_t update_hash[32];
+ const bb02_stage1_header_t* installed = bb02_stage1_installed_header();
+ const bb02_stage1_header_t* update = bb02_stage1_update_header();
+
+ const secbool_u32 installed_header_ok = _header_basic_ok(installed);
+ const secbool_u32 installed_ok = installed_header_ok == sectrue_u32
+ ? _stage1_image_ok(installed, installed_hash)
+ : secfalse_u32;
+ const secbool_u32 update_ok = _stage1_image_ok(update, update_hash);
+ volatile secbool_u32 install_auth = update_ok;
+ volatile secbool_u32 boot_auth = installed_ok;
+
+ if (update_ok == sectrue_u32) {
+ if (installed_ok == sectrue_u32) {
+ if (_memeq(installed_hash, update_hash, sizeof(installed_hash)) == sectrue_u32) {
+ _boot_stage1(installed, &boot_auth);
+ }
+ if (update->monotonic_version < installed->monotonic_version) {
+ _boot_stage1(installed, &boot_auth);
+ }
+ }
+#ifndef BB02_STAGE0_DEVELOPMENT
+ _oled_init();
+#endif
+ _oled_progress(0, 6);
+ _install_stage1(update, installed_header_ok, &install_auth);
+ installed = bb02_stage1_installed_header();
+ if (_stage1_image_ok(installed, installed_hash) == sectrue_u32) {
+ _oled_progress(6, 6);
+ _reset();
+ }
+ _invalidate_stage1_header(&install_auth);
+ _halt();
+ }
+
+ if (installed_ok == sectrue_u32) {
+ _boot_stage1(installed, &boot_auth);
+ }
+
+ _halt();
+}
+
+int __attribute__((noreturn, noinline, no_stack_protector)) main(void)
+{
+ init_mcu();
+ stage0_init();
+ __stack_chk_guard = rand_sync_read32(&RAND_0);
+ _stage0_main();
+}
diff --git a/src/bootloader/stage0/stage0_descriptor.c b/src/bootloader/stage0/stage0_descriptor.c
new file mode 100644
index 00000000..51c14e43
--- /dev/null
+++ b/src/bootloader/stage0/stage0_descriptor.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "stage0_descriptor.h"
+#include "bootloader_upgrade/bootloader_upgrade.h"
+
+#ifdef BB02_STAGE0_DEVELOPMENT
+ #define BB02_STAGE0_DESCRIPTOR_FLAGS BB02_STAGE0_FLAG_DEVELOPMENT
+#else
+ #define BB02_STAGE0_DESCRIPTOR_FLAGS 0u
+#endif
+
+const bb02_stage0_descriptor_t bb02_stage0_descriptor
+ __attribute__((used, section(".stage0_descriptor"), aligned(4))) = {
+ .stage0_version = BB02_STAGE0_IMAGE_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .flags = BB02_STAGE0_DESCRIPTOR_FLAGS,
+ .magic = BB02_STAGE0_DESCRIPTOR_MAGIC,
+};
diff --git a/src/bootloader/stage0/stage0_descriptor.h b/src/bootloader/stage0/stage0_descriptor.h
new file mode 100644
index 00000000..f753077c
--- /dev/null
+++ b/src/bootloader/stage0/stage0_descriptor.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _STAGE0_DESCRIPTOR_H_
+#define _STAGE0_DESCRIPTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "bootloader/stage0/stage0_version.h"
+
+#define BB02_STAGE0_DESCRIPTOR_MAGIC (0x30534242U) // "BBS0" in little-endian flash order.
+#define BB02_STAGE0_FLAG_DEVELOPMENT (1U << 0)
+#define BB02_STAGE0_DESCRIPTOR_ADDR (0x00001ff4U)
+#define BB02_STAGE0_DESCRIPTOR_LEN (12U)
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpacked"
+#pragma GCC diagnostic ignored "-Wattributes"
+typedef struct __attribute__((__packed__)) {
+ uint16_t stage0_version;
+ uint16_t product_id;
+ uint32_t flags;
+ uint32_t magic;
+} bb02_stage0_descriptor_t;
+#pragma GCC diagnostic pop
+
+_Static_assert(
+ sizeof(bb02_stage0_descriptor_t) == BB02_STAGE0_DESCRIPTOR_LEN,
+ "stage0 descriptor ABI changed");
+_Static_assert(
+ offsetof(bb02_stage0_descriptor_t, stage0_version) == BB02_STAGE0_DESCRIPTOR_LEN - 12U,
+ "stage0 descriptor ABI changed");
+_Static_assert(
+ offsetof(bb02_stage0_descriptor_t, product_id) == BB02_STAGE0_DESCRIPTOR_LEN - 10U,
+ "stage0 descriptor ABI changed");
+_Static_assert(
+ offsetof(bb02_stage0_descriptor_t, flags) == BB02_STAGE0_DESCRIPTOR_LEN - 8U,
+ "stage0 descriptor ABI changed");
+_Static_assert(
+ offsetof(bb02_stage0_descriptor_t, magic) == BB02_STAGE0_DESCRIPTOR_LEN - 4U,
+ "stage0 descriptor ABI changed");
+
+extern const bb02_stage0_descriptor_t bb02_stage0_descriptor;
+
+#endif
diff --git a/src/bootloader/stage0/stage0_flash.h b/src/bootloader/stage0/stage0_flash.h
new file mode 100644
index 00000000..dee66502
--- /dev/null
+++ b/src/bootloader/stage0/stage0_flash.h
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _STAGE0_FLASH_H_
+#define _STAGE0_FLASH_H_
+
+#include "bootloader_upgrade/bootloader_upgrade.h"
+#include "util.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#define STAGE0_FLASH_PAGE_SIZE_BYTES (512U)
+#define STAGE0_FLASH_PAGE_WORDS (STAGE0_FLASH_PAGE_SIZE_BYTES / sizeof(uint32_t))
+#define STAGE0_FLASH_BLOCK_SIZE_BYTES (8192U)
+#define STAGE0_FLASH_REGION_SIZE_BYTES (32768U)
+#define STAGE0_STAGE1_PAGE_ADDR BB02_STAGE1_ADDR
+#define STAGE0_STAGE1_BLOCK_ADDR BB02_STAGE1_ADDR
+#define STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR \
+ (BB02_STAGE1_FACTORY_RANDOM_ADDR & ~(STAGE0_FLASH_PAGE_SIZE_BYTES - 1U))
+#define STAGE0_STAGE1_FACTORY_RANDOM_BLOCK_ADDR \
+ (BB02_STAGE1_FACTORY_RANDOM_ADDR & ~(STAGE0_FLASH_BLOCK_SIZE_BYTES - 1U))
+
+#define STAGE0_FACTORY_RANDOM_BACKUP_MAGIC (0x30524642U) // "BFR0" in little-endian flash order.
+#define STAGE0_FACTORY_RANDOM_BACKUP_FORMAT_VERSION (1U)
+#define STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_ERASED UINT32_MAX
+#define STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_WRITTEN (0U)
+
+typedef struct {
+ uint32_t magic;
+ uint32_t format_version;
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ uint32_t commit;
+} stage0_factory_random_backup_t;
+
+typedef enum {
+ STAGE0_FACTORY_RANDOM_SOURCE_CURRENT,
+ STAGE0_FACTORY_RANDOM_SOURCE_BACKUP,
+} stage0_factory_random_source_t;
+
+_Static_assert(
+ STAGE0_STAGE1_PAGE_ADDR % STAGE0_FLASH_PAGE_SIZE_BYTES == 0,
+ "stage1 header page is unaligned");
+_Static_assert(
+ BB02_STAGE1_HEADER_LEN % STAGE0_FLASH_PAGE_SIZE_BYTES == 0,
+ "stage1 header must occupy whole flash pages");
+_Static_assert(
+ STAGE0_STAGE1_BLOCK_ADDR % STAGE0_FLASH_BLOCK_SIZE_BYTES == 0,
+ "stage1 block is unaligned");
+_Static_assert(
+ BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR % STAGE0_FLASH_PAGE_SIZE_BYTES == 0,
+ "factory randomness backup page is unaligned");
+_Static_assert(
+ BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR % STAGE0_FLASH_BLOCK_SIZE_BYTES == 0,
+ "factory randomness backup block is unaligned");
+_Static_assert(
+ BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_LEN == STAGE0_FLASH_BLOCK_SIZE_BYTES,
+ "factory randomness backup must occupy one erase block");
+_Static_assert(
+ sizeof(stage0_factory_random_backup_t) <= STAGE0_FLASH_PAGE_SIZE_BYTES,
+ "factory randomness backup record does not fit in one page");
+_Static_assert(
+ STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR + STAGE0_FLASH_PAGE_SIZE_BYTES ==
+ BB02_STAGE1_FACTORY_RANDOM_ADDR + BB02_STAGE1_FACTORY_RANDOM_LEN,
+ "factory randomness must be at the end of its flash page");
+_Static_assert(
+ STAGE0_STAGE1_FACTORY_RANDOM_BLOCK_ADDR + STAGE0_FLASH_BLOCK_SIZE_BYTES ==
+ BB02_STAGE1_FACTORY_RANDOM_ADDR + BB02_STAGE1_FACTORY_RANDOM_LEN,
+ "factory randomness must be at the end of its flash block");
+
+// GCC needs noclone; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+static secbool_u32 __attribute__((noinline, noclone)) stage0_flash_page_addr_ok(
+ volatile uint32_t addr)
+{
+ if ((addr % STAGE0_FLASH_PAGE_SIZE_BYTES) != 0) {
+ return secfalse_u32;
+ }
+ if (addr >= STAGE0_STAGE1_PAGE_ADDR && addr <= STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR) {
+ return sectrue_u32;
+ }
+ if (addr == BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR) {
+ return sectrue_u32;
+ }
+ return secfalse_u32;
+}
+
+// GCC needs noclone; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+static secbool_u32 __attribute__((noinline, noclone)) stage0_flash_block_addr_ok(
+ volatile uint32_t addr)
+{
+ if ((addr % STAGE0_FLASH_BLOCK_SIZE_BYTES) != 0) {
+ return secfalse_u32;
+ }
+ if (addr >= STAGE0_STAGE1_BLOCK_ADDR && addr <= STAGE0_STAGE1_FACTORY_RANDOM_BLOCK_ADDR) {
+ return sectrue_u32;
+ }
+ if (addr == BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR) {
+ return sectrue_u32;
+ }
+ return secfalse_u32;
+}
+
+static void stage0_flash_make_invalid_header_page(uint32_t page_words[STAGE0_FLASH_PAGE_WORDS])
+{
+ // Flash programming can only clear bits. Keep all other words at 0xff so
+ // they are left unchanged, and clear only the magic word to invalidate.
+ memset(page_words, 0xff, STAGE0_FLASH_PAGE_SIZE_BYTES);
+ page_words[0] = 0;
+}
+
+static bool stage0_flash_page_contains_addr(uint32_t page_addr, uint32_t addr)
+{
+ return page_addr <= addr && page_addr + STAGE0_FLASH_PAGE_SIZE_BYTES > addr;
+}
+
+static void stage0_flash_make_stage1_page(
+ uint32_t page_words[STAGE0_FLASH_PAGE_WORDS],
+ const uint8_t* image,
+ uint32_t image_len,
+ uint32_t dst_addr,
+ const uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN])
+{
+ memset(page_words, 0xff, STAGE0_FLASH_PAGE_SIZE_BYTES);
+ if (dst_addr >= BB02_STAGE1_ADDR) {
+ const uint32_t image_offset = dst_addr - BB02_STAGE1_ADDR;
+ if (image_offset < image_len) {
+ uint32_t copy_len = image_len - image_offset;
+ if (copy_len > STAGE0_FLASH_PAGE_SIZE_BYTES) {
+ copy_len = STAGE0_FLASH_PAGE_SIZE_BYTES;
+ }
+ memcpy(page_words, image + image_offset, copy_len);
+ }
+ }
+ if (factory_random != NULL &&
+ stage0_flash_page_contains_addr(dst_addr, BB02_STAGE1_FACTORY_RANDOM_ADDR)) {
+ memcpy(
+ ((uint8_t*)page_words) + (BB02_STAGE1_FACTORY_RANDOM_ADDR - dst_addr),
+ factory_random,
+ BB02_STAGE1_FACTORY_RANDOM_LEN);
+ }
+}
+
+static void stage0_factory_random_backup_make_data_page(
+ uint32_t page_words[STAGE0_FLASH_PAGE_WORDS],
+ const uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN])
+{
+ memset(page_words, 0xff, STAGE0_FLASH_PAGE_SIZE_BYTES);
+ stage0_factory_random_backup_t* backup = (stage0_factory_random_backup_t*)page_words;
+ backup->magic = STAGE0_FACTORY_RANDOM_BACKUP_MAGIC;
+ backup->format_version = STAGE0_FACTORY_RANDOM_BACKUP_FORMAT_VERSION;
+ memcpy(backup->factory_random, factory_random, BB02_STAGE1_FACTORY_RANDOM_LEN);
+ backup->commit = STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_ERASED;
+}
+
+static void stage0_factory_random_backup_make_commit_page(
+ uint32_t page_words[STAGE0_FLASH_PAGE_WORDS])
+{
+ memset(page_words, 0xff, STAGE0_FLASH_PAGE_SIZE_BYTES);
+ stage0_factory_random_backup_t* backup = (stage0_factory_random_backup_t*)page_words;
+ backup->commit = STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_WRITTEN;
+}
+
+static secbool_u32 stage0_factory_random_backup_valid(
+ const stage0_factory_random_backup_t* backup,
+ uint8_t factory_random_out[BB02_STAGE1_FACTORY_RANDOM_LEN])
+{
+ if (backup->magic != STAGE0_FACTORY_RANDOM_BACKUP_MAGIC ||
+ backup->format_version != STAGE0_FACTORY_RANDOM_BACKUP_FORMAT_VERSION ||
+ backup->commit != STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_WRITTEN) {
+ return secfalse_u32;
+ }
+
+ if (factory_random_out != NULL) {
+ memcpy(factory_random_out, backup->factory_random, BB02_STAGE1_FACTORY_RANDOM_LEN);
+ }
+ return sectrue_u32;
+}
+
+static stage0_factory_random_source_t stage0_factory_random_source(
+ secbool_u32 installed_header_ok,
+ secbool_u32 backup_valid,
+ secbool_u32 current_matches_backup)
+{
+ if (backup_valid == sectrue_u32) {
+ if (installed_header_ok != sectrue_u32 || current_matches_backup == sectrue_u32) {
+ return STAGE0_FACTORY_RANDOM_SOURCE_BACKUP;
+ }
+ }
+ return STAGE0_FACTORY_RANDOM_SOURCE_CURRENT;
+}
+
+#endif
diff --git a/src/bootloader/stage0/stage0_runtime.c b/src/bootloader/stage0/stage0_runtime.c
new file mode 100644
index 00000000..7a43d242
--- /dev/null
+++ b/src/bootloader/stage0/stage0_runtime.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <stddef.h>
+#include <stdint.h>
+
+// GCC LTO needs externally_visible; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void* __attribute__((used, externally_visible)) memcpy(void* dst, const void* src, size_t n);
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void* __attribute__((used, externally_visible)) memset(void* dst, int c, size_t n);
+
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void* __attribute__((used, externally_visible)) memcpy(void* dst, const void* src, size_t n)
+{
+ uint8_t* d = (uint8_t*)dst;
+ const uint8_t* s = (const uint8_t*)src;
+ while (n-- > 0) {
+ *d++ = *s++;
+ }
+ return dst;
+}
+
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void* __attribute__((used, externally_visible)) memset(void* dst, int c, size_t n)
+{
+ uint8_t* d = (uint8_t*)dst;
+ while (n-- > 0) {
+ *d++ = (uint8_t)c;
+ }
+ return dst;
+}
diff --git a/src/bootloader/stage0/stage0_startup.c b/src/bootloader/stage0/stage0_startup.c
new file mode 100644
index 00000000..7f6bd7e0
--- /dev/null
+++ b/src/bootloader/stage0/stage0_startup.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <sam.h>
+#include <stdint.h>
+
+extern uint32_t _estack;
+extern uint32_t _sfixed;
+extern uint32_t _etext;
+extern uint32_t _srelocate;
+extern uint32_t _erelocate;
+extern uint32_t _szero;
+extern uint32_t _ezero;
+
+int main(void);
+void Dummy_Handler(void);
+
+typedef void (*stage0_handler_t)(void);
+
+typedef union {
+ void* ptr;
+ stage0_handler_t handler;
+ uintptr_t reserved;
+} stage0_vector_t;
+
+__attribute__((section(".vectors"), used)) const stage0_vector_t exception_table[] = {
+ {.ptr = &_estack},
+ {.handler = Reset_Handler},
+ {.handler = Dummy_Handler},
+ {.handler = Dummy_Handler},
+ {.handler = Dummy_Handler},
+ {.handler = Dummy_Handler},
+ {.handler = Dummy_Handler},
+ {.reserved = 0},
+ {.reserved = 0},
+ {.reserved = 0},
+ {.reserved = 0},
+ {.handler = Dummy_Handler},
+ {.handler = Dummy_Handler},
+ {.reserved = 0},
+ {.handler = Dummy_Handler},
+ {.handler = Dummy_Handler},
+};
+
+// GCC LTO needs externally_visible; clang-tidy parses with Clang and does not support it.
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void __attribute__((noreturn, used, externally_visible)) Reset_Handler(void)
+{
+ uint32_t* src = &_etext;
+ for (uint32_t* dst = &_srelocate; dst < &_erelocate;) {
+ // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
+ *dst++ = *src++;
+ }
+
+ for (uint32_t* dst = &_szero; dst < &_ezero;) {
+ // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
+ *dst++ = 0;
+ }
+
+ SCB->VTOR = ((uint32_t)&_sfixed & SCB_VTOR_TBLOFF_Msk);
+
+#if __FPU_USED
+ SCB->CPACR |= (0xFU << 20);
+ __DSB();
+ __ISB();
+#endif
+
+ main();
+
+ while (1) {
+ }
+}
+
+// NOLINTNEXTLINE(clang-diagnostic-unknown-attributes)
+void __attribute__((noreturn, used, externally_visible)) Dummy_Handler(void)
+{
+ while (1) {
+ }
+}
diff --git a/src/bootloader/stage0/stage0_version.h.tmpl b/src/bootloader/stage0/stage0_version.h.tmpl
new file mode 100644
index 00000000..31880388
--- /dev/null
+++ b/src/bootloader/stage0/stage0_version.h.tmpl
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _STAGE0_VERSION_H_
+#define _STAGE0_VERSION_H_
+
+#define BB02_STAGE0_IMAGE_VERSION (${STAGE0_IMAGE_VERSION}u)
+
+#endif
diff --git a/src/bootloader/stage0/stage1_sigcheck.c b/src/bootloader/stage0/stage1_sigcheck.c
new file mode 100644
index 00000000..90adedd3
--- /dev/null
+++ b/src/bootloader/stage0/stage1_sigcheck.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "stage1_sigcheck.h"
+#include "pukcc/curve_p256.h"
+#include "pukcc/pukcc.h"
+#include <hal_sha_sync.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+#define STAGE1_SIGCHECK_ALL_KEYS_MASK ((uint8_t)((1U << BB02_STAGE1_ROOT_KEY_COUNT) - 1U))
+#define STAGE1_SIGCHECK_INVALID_DIGEST_BYTE (0xa5U)
+#define STAGE1_SIGCHECK_SIGNED_HEADER_TAIL_OFFSET \
+ ((uint32_t)offsetof(bb02_stage1_header_t, header_len))
+
+_Static_assert(BB02_STAGE1_ROOT_KEY_COUNT == 3U, "stage1 signature mask expects 3 keys");
+_Static_assert(BB02_STAGE1_SIGNATURE_THRESHOLD == 2U, "stage1 signature threshold changed");
+_Static_assert(offsetof(bb02_stage1_header_t, magic) == 0U, "stage1 header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, flags) == 4U, "stage1 header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, header_version) == 8U, "stage1 header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, product_id) == 10U, "stage1 header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, header_len) == 12U, "stage1 header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, image_len) == 16U, "stage1 header ABI changed");
+_Static_assert(
+ offsetof(bb02_stage1_header_t, monotonic_version) == 24U,
+ "stage1 header ABI changed");
+_Static_assert(
+ offsetof(bb02_stage1_header_t, stage1_marketing_version_len) == 26U,
+ "stage1 header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, reserved) == 64U, "stage1 header ABI changed");
+
+extern struct sha_sync_descriptor HASH_ALGORITHM_0;
+
+__attribute__((aligned(128))) static struct sha_context _sha_context;
+
+typedef struct {
+ uint8_t valid_mask;
+ uint8_t invalid_mask;
+} sigcheck_masks_t;
+
+static uint8_t _is_zero_u8(uint8_t value)
+{
+ const uint32_t v = value;
+ return (uint8_t)(1U ^ ((v | (0U - v)) >> 31));
+}
+
+static secbool_u32 _secbool_from_bit(uint8_t bit)
+{
+ return (uint32_t)(bit & 1U) * sectrue_u32;
+}
+
+static secbool_u32 _secbool_u8_eq(uint8_t a, uint8_t b)
+{
+ return _secbool_from_bit(_is_zero_u8((uint8_t)(a ^ b)));
+}
+
+static secbool_u32 _secbool_u8_ne(uint8_t a, uint8_t b)
+{
+ return sectrue_u32 ^ _secbool_u8_eq(a, b);
+}
+
+static secbool_u32 _secbool_u32_eq(uint32_t a, uint32_t b)
+{
+ const uint32_t v = a ^ b;
+ return _secbool_from_bit((uint8_t)(1U ^ ((v | (0U - v)) >> 31)));
+}
+
+static secbool_u32 _secbool_i32_eq(int32_t a, int32_t b)
+{
+ return _secbool_u32_eq((uint32_t)a, (uint32_t)b);
+}
+
+static int32_t _canonical_signed_digest(
+ const bb02_stage1_header_t* header,
+ uint32_t image_body_len,
+ uint8_t digest[BB02_STAGE1_SIGNED_DIGEST_LEN])
+{
+ memset(digest, STAGE1_SIGCHECK_INVALID_DIGEST_BYTE, BB02_STAGE1_SIGNED_DIGEST_LEN);
+
+ const uint32_t expected_magic = BB02_STAGE1_HEADER_MAGIC;
+ const uint32_t flags = header->flags;
+ const uint16_t header_version = header->header_version;
+ const uint16_t expected_product_id = BB02_STAGE1_PRODUCT_ID;
+ const uint8_t* header_tail =
+ ((const uint8_t*)header) + STAGE1_SIGCHECK_SIGNED_HEADER_TAIL_OFFSET;
+ const uint32_t header_len = (uint32_t)header->header_len;
+ const uint32_t signed_header_len = header_len - BB02_STAGE1_HEADER_SIGNATURES_LEN;
+ const uint32_t header_tail_len = signed_header_len - STAGE1_SIGCHECK_SIGNED_HEADER_TAIL_OFFSET;
+ const uint8_t* image_body = ((const uint8_t*)header) + header_len;
+
+ int32_t status = sha_sync_sha256_start(&HASH_ALGORITHM_0, &_sha_context, false);
+ status |= sha_sync_sha256_update(
+ &HASH_ALGORITHM_0, (const uint8_t*)&expected_magic, sizeof(expected_magic));
+ status |= sha_sync_sha256_update(&HASH_ALGORITHM_0, (const uint8_t*)&flags, sizeof(flags));
+ status |= sha_sync_sha256_update(
+ &HASH_ALGORITHM_0, (const uint8_t*)&header_version, sizeof(header_version));
+ status |= sha_sync_sha256_update(
+ &HASH_ALGORITHM_0, (const uint8_t*)&expected_product_id, sizeof(expected_product_id));
+ status |= sha_sync_sha256_update(&HASH_ALGORITHM_0, header_tail, header_tail_len);
+ status |= sha_sync_sha256_update(&HASH_ALGORITHM_0, image_body, image_body_len);
+ status |= sha_sync_sha256_finish(&HASH_ALGORITHM_0, digest);
+ return status;
+}
+
+static secbool_u32 _threshold_from_valid_table(uint8_t valid_mask)
+{
+ static const secbool_u32 threshold_ok[8] = {
+ secfalse_u32, // 000
+ secfalse_u32, // 001
+ secfalse_u32, // 010
+ sectrue_u32, // 011
+ secfalse_u32, // 100
+ sectrue_u32, // 101
+ sectrue_u32, // 110
+ sectrue_u32, // 111
+ };
+ return threshold_ok[valid_mask & STAGE1_SIGCHECK_ALL_KEYS_MASK];
+}
+
+static secbool_u32 _threshold_from_valid_pairs(uint8_t valid_mask)
+{
+ const uint8_t mask = valid_mask & STAGE1_SIGCHECK_ALL_KEYS_MASK;
+ secbool_u32 ok = _secbool_u8_eq((uint8_t)(mask & 0x03U), 0x03U);
+ ok |= _secbool_u8_eq((uint8_t)(mask & 0x05U), 0x05U);
+ ok |= _secbool_u8_eq((uint8_t)(mask & 0x06U), 0x06U);
+ return ok;
+}
+
+static secbool_u32 _threshold_from_invalid_table(uint8_t invalid_mask)
+{
+ static const secbool_u32 threshold_ok[8] = {
+ sectrue_u32, // 000
+ sectrue_u32, // 001
+ sectrue_u32, // 010
+ secfalse_u32, // 011
+ sectrue_u32, // 100
+ secfalse_u32, // 101
+ secfalse_u32, // 110
+ secfalse_u32, // 111
+ };
+ return threshold_ok[invalid_mask & STAGE1_SIGCHECK_ALL_KEYS_MASK];
+}
+
+static secbool_u32 _threshold_from_invalid_pairs(uint8_t invalid_mask)
+{
+ const uint8_t mask = invalid_mask & STAGE1_SIGCHECK_ALL_KEYS_MASK;
+ secbool_u32 ok = _secbool_u8_ne((uint8_t)(mask & 0x03U), 0x03U);
+ ok &= _secbool_u8_ne((uint8_t)(mask & 0x05U), 0x05U);
+ ok &= _secbool_u8_ne((uint8_t)(mask & 0x06U), 0x06U);
+ return ok;
+}
+
+static sigcheck_masks_t _check_signatures(
+ const uint8_t* signatures,
+ const uint8_t digest[BB02_STAGE1_SIGNED_DIGEST_LEN],
+ const uint8_t pubkeys[BB02_STAGE1_ROOT_KEY_COUNT][64],
+ bool reverse)
+{
+ sigcheck_masks_t masks = {0};
+ for (uint8_t i = 0; i < BB02_STAGE1_ROOT_KEY_COUNT; i++) {
+ const uint8_t key_idx = reverse ? (uint8_t)(BB02_STAGE1_ROOT_KEY_COUNT - 1U - i) : i;
+ const uint8_t key_bit = (uint8_t)(1U << key_idx);
+ const size_t signature_offset = (size_t)key_idx * BB02_STAGE1_SIGNATURE_LEN;
+ const uint8_t valid = _is_zero_u8(pukcc_ecdsa_verify(
+ pubkeys[key_idx],
+ &signatures[signature_offset],
+ digest,
+ BB02_STAGE1_SIGNED_DIGEST_LEN,
+ curve_p256));
+ const uint8_t invalid = (uint8_t)(valid ^ 1U);
+ masks.valid_mask |= (uint8_t)((uint8_t)(0U - valid) & key_bit);
+ masks.invalid_mask |= (uint8_t)((uint8_t)(0U - invalid) & key_bit);
+ }
+ return masks;
+}
+
+static secbool_u32 _stage1_header_len_ok(uint32_t header_len)
+{
+ if (header_len < BB02_STAGE1_HEADER_LEN || header_len > BB02_STAGE1_MAX_LEN) {
+ return secfalse_u32;
+ }
+ if ((header_len % BB02_STAGE1_HEADER_ALIGNMENT) != 0) {
+ return secfalse_u32;
+ }
+ return sectrue_u32;
+}
+
+static const uint8_t* _stage1_signatures(const bb02_stage1_header_t* header)
+{
+ const uint32_t header_len = (uint32_t)header->header_len;
+ const size_t signatures_len = (size_t)BB02_STAGE1_ROOT_KEY_COUNT * BB02_STAGE1_SIGNATURE_LEN;
+ return ((const uint8_t*)header) + (size_t)header_len - signatures_len;
+}
+
+secbool_u32 stage1_sigcheck_image_ok(
+ const bb02_stage1_header_t* header,
+ const uint8_t pubkeys[BB02_STAGE1_ROOT_KEY_COUNT][64])
+{
+ const uint32_t header_len = (uint32_t)header->header_len;
+ if (header->magic != BB02_STAGE1_HEADER_MAGIC || header->product_id != BB02_STAGE1_PRODUCT_ID ||
+ (header->flags & BB02_STAGE1_FLAG_DEVELOPMENT) != 0 ||
+ _stage1_header_len_ok(header_len) != sectrue_u32 || header->image_len <= header_len ||
+ header->image_len > BB02_STAGE1_MAX_LEN) {
+ return secfalse_u32;
+ }
+ const uint32_t image_body_len = (uint32_t)(header->image_len - header_len);
+ const uint8_t* signatures = _stage1_signatures(header);
+
+ uint8_t digest[BB02_STAGE1_SIGNED_DIGEST_LEN];
+ const int32_t digest_status = _canonical_signed_digest(header, image_body_len, digest);
+ const sigcheck_masks_t forward = _check_signatures(signatures, digest, pubkeys, false);
+ const sigcheck_masks_t reverse = _check_signatures(signatures, digest, pubkeys, true);
+
+ secbool_u32 ok = _secbool_i32_eq(digest_status, 0);
+ ok &= _threshold_from_valid_table(forward.valid_mask);
+ ok &= _threshold_from_valid_pairs(reverse.valid_mask);
+ ok &= _threshold_from_invalid_pairs(forward.invalid_mask);
+ ok &= _threshold_from_invalid_table(reverse.invalid_mask);
+ ok &= _secbool_u8_eq(forward.valid_mask, reverse.valid_mask);
+ ok &= _secbool_u8_eq(forward.invalid_mask, reverse.invalid_mask);
+ ok &= _secbool_u8_eq(
+ (uint8_t)(forward.valid_mask | forward.invalid_mask), STAGE1_SIGCHECK_ALL_KEYS_MASK);
+ ok &= _secbool_u8_eq(
+ (uint8_t)(reverse.valid_mask | reverse.invalid_mask), STAGE1_SIGCHECK_ALL_KEYS_MASK);
+ ok &= _secbool_u8_eq((uint8_t)(forward.valid_mask & forward.invalid_mask), 0U);
+ ok &= _secbool_u8_eq((uint8_t)(reverse.valid_mask & reverse.invalid_mask), 0U);
+ return ok;
+}
diff --git a/src/bootloader/stage0/stage1_sigcheck.h b/src/bootloader/stage0/stage1_sigcheck.h
new file mode 100644
index 00000000..53b30230
--- /dev/null
+++ b/src/bootloader/stage0/stage1_sigcheck.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _STAGE1_SIGCHECK_H_
+#define _STAGE1_SIGCHECK_H_
+
+#include "bootloader_upgrade/bootloader_upgrade.h"
+#include "util.h"
+#include <stdint.h>
+
+secbool_u32 stage1_sigcheck_image_ok(
+ const bb02_stage1_header_t* header,
+ const uint8_t pubkeys[BB02_STAGE1_ROOT_KEY_COUNT][64]);
+
+#endif
diff --git a/src/bootloader/stage1_header.c b/src/bootloader/stage1_header.c
new file mode 100644
index 00000000..f43c6c78
--- /dev/null
+++ b/src/bootloader/stage1_header.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "bootloader_upgrade/bootloader_upgrade.h"
+#include <bootloader/bootloader_version.h>
+
+_Static_assert(
+ BOOTLOADER_VERSION_LEN <= BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN,
+ "stage1 marketing version too long for stage1 header");
+
+#ifdef BOOTLOADER_DEVDEVICE
+ #define BB02_STAGE1_HEADER_FLAGS BB02_STAGE1_FLAG_DEVELOPMENT
+#else
+ #define BB02_STAGE1_HEADER_FLAGS 0u
+#endif
+
+const bb02_stage1_header_t bb02_stage1_header_placeholder
+ __attribute__((used, section(".stage1_header"), aligned(4))) = {
+ .magic = BB02_STAGE1_HEADER_MAGIC,
+ .flags = BB02_STAGE1_HEADER_FLAGS,
+ .header_version = BB02_STAGE1_HEADER_FORMAT_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .header_len = BB02_STAGE1_HEADER_LEN,
+ .image_len = 0,
+ .monotonic_version = 1,
+ .stage1_marketing_version_len = BOOTLOADER_VERSION_LEN,
+ .stage1_marketing_version = BOOTLOADER_VERSION,
+ .reserved = {0},
+ .signatures = {{0}},
+};
diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c
index 7b306d43..2b53be85 100644
--- a/src/bootloader/startup.c
+++ b/src/bootloader/startup.c
@@ -3,6 +3,7 @@
#include "bootloader.h"
#include "mpu_regions.h"
#include <bootloader/bootloader_version.h>
+#include <bootloader_upgrade/bootloader_upgrade.h>
#include <driver_init.h>
#include <hardfault.h>
#include <platform_config.h>
@@ -56,8 +57,8 @@ struct RustByteQueue* uart_write_queue = NULL;
int main(void)
{
- // When in bootloader mode, the vector table should be 0. If not, halt.
- if (SCB->VTOR) {
+ // Stage0 enters stage1 with VTOR at the stage1 vector table.
+ if (SCB->VTOR != BB02_STAGE1_VECTOR_ADDR) {
while (1) {
};
}
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-btconly-development.v1.bin b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-btconly-development.v1.bin
new file mode 100755
index 00000000..6f5a8f41
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-btconly-development.v1.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-btconly-development.v1.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-btconly-development.v1.bin.sha256
new file mode 100644
index 00000000..c2010f3c
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-btconly-development.v1.bin.sha256
@@ -0,0 +1 @@
+f782331aa994906c65dddf7e4755feeaf0bb177b131be3914a0981af3425f1db bootloader-stage0-bitbox02-btconly-development.v1.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-multi-development.v1.bin b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-multi-development.v1.bin
new file mode 100755
index 00000000..ccc34c86
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-multi-development.v1.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-multi-development.v1.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-multi-development.v1.bin.sha256
new file mode 100644
index 00000000..5d924572
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02-multi-development.v1.bin.sha256
@@ -0,0 +1 @@
+e8152d0de3024db5f87f1df81a570fb33480d293f5d696de8bfab16e5652b4e6 bootloader-stage0-bitbox02-multi-development.v1.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-btconly-development.v1.bin b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-btconly-development.v1.bin
new file mode 100755
index 00000000..531d6a4e
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-btconly-development.v1.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-btconly-development.v1.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-btconly-development.v1.bin.sha256
new file mode 100644
index 00000000..5ac09655
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-btconly-development.v1.bin.sha256
@@ -0,0 +1 @@
+61719283f9984406098822c6c8ec34509e4c3a0346a7e758d1fcad2123ebae2b bootloader-stage0-bitbox02nova-btconly-development.v1.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-multi-development.v1.bin b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-multi-development.v1.bin
new file mode 100755
index 00000000..e98bfa55
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-multi-development.v1.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-multi-development.v1.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-multi-development.v1.bin.sha256
new file mode 100644
index 00000000..d0171d1e
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage0-bitbox02nova-multi-development.v1.bin.sha256
@@ -0,0 +1 @@
+2fdf5db05b7913bfcea43bb2501a91d529ee5199834b43d7a48a0399c3581e92 bootloader-stage0-bitbox02nova-multi-development.v1.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin
new file mode 100644
index 00000000..42fec9c4
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin.sha256
new file mode 100644
index 00000000..5b9fd12d
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin.sha256
@@ -0,0 +1 @@
+d0527dd5acda577db5d10d7bbd99860b1ec4fd0ed75ade9067356ec9446b1510 bootloader-stage1-bitbox02-btconly-development.v1.2.0.signed.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin
new file mode 100644
index 00000000..ffc6a513
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin.sha256
new file mode 100644
index 00000000..de0c3e02
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin.sha256
@@ -0,0 +1 @@
+7bf8a1e86bfab437089d7117d404810a09312d1d12160c8bb43ed5c5e5759018 bootloader-stage1-bitbox02-multi-development.v1.2.0.signed.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin
new file mode 100644
index 00000000..2ecc9bfd
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin.sha256
new file mode 100644
index 00000000..acddf46c
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin.sha256
@@ -0,0 +1 @@
+2dd00eca43e2affd1ffc3f22343fc8b422d679c3db4dca2eae7da9e218a7a68f bootloader-stage1-bitbox02nova-btconly-development.v1.2.0.signed.bin
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin
new file mode 100644
index 00000000..b126ba0a
Binary files /dev/null and b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin differ
diff --git a/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin.sha256 b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin.sha256
new file mode 100644
index 00000000..6f6f11e4
--- /dev/null
+++ b/src/bootloader_upgrade/bin/bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin.sha256
@@ -0,0 +1 @@
+5a77edd7c87ff04e883efb7af4eb64d3b436b6d4f060c31ab9d709af026d363c bootloader-stage1-bitbox02nova-multi-development.v1.2.0.signed.bin
diff --git a/src/bootloader_upgrade/bootloader_upgrade.h b/src/bootloader_upgrade/bootloader_upgrade.h
new file mode 100644
index 00000000..39075ac1
--- /dev/null
+++ b/src/bootloader_upgrade/bootloader_upgrade.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _BOOTLOADER_UPGRADE_H_
+#define _BOOTLOADER_UPGRADE_H_
+
+#include <bootloader/bootloader_product.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define BB02_BOOTLOADER_UPGRADE_STAGE0_ADDR (0x00000000U)
+#define BB02_BOOTLOADER_UPGRADE_STAGE0_LEN (0x00002000U)
+
+#define BB02_STAGE1_ADDR (0x00002000U)
+#define BB02_STAGE1_HEADER_ADDR BB02_STAGE1_ADDR
+#define BB02_STAGE1_HEADER_ALIGNMENT (1024U)
+#define BB02_STAGE1_VECTOR_OFFSET (0x00000400U)
+#define BB02_STAGE1_VECTOR_ADDR (BB02_STAGE1_ADDR + BB02_STAGE1_VECTOR_OFFSET)
+#define BB02_STAGE1_MAX_LEN (0x0000BFE0U)
+
+#define BB02_STAGE1_FACTORY_RANDOM_ADDR (0x0000DFE0U)
+#define BB02_STAGE1_FACTORY_RANDOM_LEN (32U)
+
+#define BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR (0x000D8000U)
+#define BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_LEN (0x00002000U)
+#define BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR
+
+#define BB02_BOOTLOADER_UPGRADE_STAGE1_UPDATE_ADDR (0x000DB000U)
+
+#define BB02_STAGE1_HEADER_MAGIC (0x31534242U) // "BBS1" in little-endian flash order.
+#define BB02_STAGE1_HEADER_FORMAT_VERSION (1U)
+#define BB02_STAGE1_ROOT_KEY_COUNT (3U)
+#define BB02_STAGE1_SIGNATURE_THRESHOLD (2U)
+#define BB02_STAGE1_SIGNATURE_LEN (64U)
+#define BB02_STAGE1_HEADER_SIGNATURES_LEN (BB02_STAGE1_ROOT_KEY_COUNT * BB02_STAGE1_SIGNATURE_LEN)
+#define BB02_STAGE1_SIGNED_DIGEST_LEN (32U)
+#define BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN (37U)
+#define BB02_STAGE1_FLAG_DEVELOPMENT (1U << 0)
+#define BB02_STAGE1_HEADER_RESERVED_LEN (768U)
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpacked"
+#pragma GCC diagnostic ignored "-Wattributes"
+typedef struct __attribute__((__packed__)) {
+ uint32_t magic;
+ uint32_t flags;
+ uint16_t header_version;
+ uint16_t product_id;
+ // Total header length in bytes. Must be at least this struct size, at most
+ // BB02_STAGE1_MAX_LEN, and a multiple of BB02_STAGE1_HEADER_ALIGNMENT.
+ // image_len must be greater than it. Stage1 vectors start at this offset.
+ uint32_t header_len;
+ // Total stage1 image length, including this header.
+ uint64_t image_len;
+ uint16_t monotonic_version;
+ uint8_t stage1_marketing_version_len;
+ uint8_t stage1_marketing_version[BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN];
+ uint8_t reserved[BB02_STAGE1_HEADER_RESERVED_LEN];
+ // Signatures must remain the last 64*3 bytes of the header. Stage0 derives
+ // their location from header_len - BB02_STAGE1_HEADER_SIGNATURES_LEN.
+ uint8_t signatures[BB02_STAGE1_ROOT_KEY_COUNT][BB02_STAGE1_SIGNATURE_LEN];
+} bb02_stage1_header_t;
+#pragma GCC diagnostic pop
+
+#define BB02_STAGE1_HEADER_LEN ((uint32_t)sizeof(bb02_stage1_header_t))
+#define BB02_STAGE1_HEADER_SIGNED_LEN ((uint32_t)offsetof(bb02_stage1_header_t, signatures))
+
+_Static_assert(sizeof(bb02_stage1_header_t) == 1024, "header ABI changed");
+_Static_assert(
+ BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR + BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_LEN <=
+ BB02_BOOTLOADER_UPGRADE_STAGE1_UPDATE_ADDR,
+ "factory randomness backup overlaps stage1 update slot");
+_Static_assert(
+ BB02_STAGE1_HEADER_SIGNED_LEN + BB02_STAGE1_HEADER_SIGNATURES_LEN == BB02_STAGE1_HEADER_LEN,
+ "header ABI changed");
+_Static_assert(BB02_STAGE1_VECTOR_OFFSET == 1024U, "stage1 vector table offset changed");
+_Static_assert(
+ BB02_STAGE1_VECTOR_OFFSET == BB02_STAGE1_HEADER_LEN,
+ "stage1 header must fill the space before vector table");
+_Static_assert(
+ (BB02_STAGE1_VECTOR_OFFSET % BB02_STAGE1_HEADER_ALIGNMENT) == 0,
+ "stage1 vector table must be 1kB aligned");
+_Static_assert(BB02_STAGE1_HEADER_SIGNED_LEN == 832U, "signed header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, magic) == 0U, "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, flags) == 4U, "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, header_version) == 8U, "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, product_id) == 10U, "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, header_len) == 12U, "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, image_len) == 16U, "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, monotonic_version) == 24U, "header ABI changed");
+_Static_assert(
+ offsetof(bb02_stage1_header_t, stage1_marketing_version_len) == 26U,
+ "header ABI changed");
+_Static_assert(offsetof(bb02_stage1_header_t, reserved) == 64U, "header ABI changed");
+_Static_assert(
+ offsetof(bb02_stage1_header_t, signatures) == BB02_STAGE1_HEADER_SIGNED_LEN,
+ "signed header ABI changed");
+
+static inline const bb02_stage1_header_t* bb02_stage1_update_header(void)
+{
+ return (const bb02_stage1_header_t*)BB02_BOOTLOADER_UPGRADE_STAGE1_UPDATE_ADDR;
+}
+
+static inline const bb02_stage1_header_t* bb02_stage1_installed_header(void)
+{
+ return (const bb02_stage1_header_t*)BB02_STAGE1_HEADER_ADDR;
+}
+
+#endif
diff --git a/src/bootloader_upgrade/firmware_installer.c b/src/bootloader_upgrade/firmware_installer.c
new file mode 100644
index 00000000..31a5d227
--- /dev/null
+++ b/src/bootloader_upgrade/firmware_installer.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "firmware_installer.h"
+#include "bootloader_upgrade.h"
+#include "driver_init.h"
+#include "firmware_installer_check.h"
+#include "flags.h"
+#include "hardfault.h"
+#include "memory/memory_shared.h"
+#include "memory/mpu.h"
+#include "screen.h"
+#ifndef BOOTLOADER_UPGRADE_DEVELOPMENT
+ #include "bootloader/stage0/stage1_sigcheck.h"
+ #include "stage1_pubkeys.h"
+#endif
+#include "system.h"
+#include <hal_flash.h>
+#include <sam.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+extern const uint8_t _binary_bootloader_upgrade_stage0_image_bin_start;
+extern const uint8_t _binary_bootloader_upgrade_stage0_image_bin_size;
+
+#define LEGACY_BOOTLOADER_SCAN_ADDR (FLASH_BOOT_START + 1U)
+#define LEGACY_BOOTLOADER_SCAN_LEN (FLASH_BOOT_LEN - 32U - 1U)
+
+_Static_assert(FLASH_BOOT_LEN > 33U, "legacy bootloader scan length underflows");
+
+#ifdef BOOTLOADER_UPGRADE_DEVELOPMENT
+ #define BB02_BOOTLOADER_UPGRADE_EXPECTED_STAGE1_FLAGS BB02_STAGE1_FLAG_DEVELOPMENT
+#else
+ #define BB02_BOOTLOADER_UPGRADE_EXPECTED_STAGE1_FLAGS 0U
+#endif
+
+static const uint8_t* _stage0_image(void)
+{
+ return &_binary_bootloader_upgrade_stage0_image_bin_start;
+}
+
+static size_t _stage0_image_len(void)
+{
+ return (size_t)&_binary_bootloader_upgrade_stage0_image_bin_size;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+static uint8_t* _stage0_image_for_flash_write(void)
+{
+ return (uint8_t*)_stage0_image();
+}
+#pragma GCC diagnostic pop
+
+static bool _bytes_equal(const uint8_t* a, const uint8_t* b, size_t len)
+{
+ // TODO need to worry about NULL deref?
+ for (size_t i = 0; i < len; i++) {
+ // Stage0 is intentionally memory-mapped at address 0 on the target.
+ if (a[i] != b[i]) { // NOLINT(clang-analyzer-core.NullDereference)
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool _bytes_zero(const uint8_t* data, size_t len)
+{
+ for (size_t i = 0; i < len; i++) {
+ if (data[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void _apply_saved_orientation(void)
+{
+ chunk_shared_t shared_data = {0};
+ memory_read_shared_bootdata(&shared_data);
+ if (shared_data.fields.upside_down) {
+ screen_rotate();
+ }
+}
+
+static bool _stage1_marketing_version_ok(const bb02_stage1_header_t* header)
+{
+ if (header->stage1_marketing_version_len == 0 ||
+ header->stage1_marketing_version_len >
+ BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN) {
+ return false;
+ }
+ for (uint8_t i = 0; i < header->stage1_marketing_version_len; i++) {
+ const uint8_t ch = header->stage1_marketing_version[i];
+ if (ch < 0x21 || ch > 0x7e) {
+ return false;
+ }
+ }
+ return _bytes_zero(
+ &header->stage1_marketing_version[header->stage1_marketing_version_len],
+ BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN - header->stage1_marketing_version_len);
+}
+
+static bool _stage1_header_len_ok(uint32_t header_len)
+{
+ return header_len >= BB02_STAGE1_HEADER_LEN && header_len <= BB02_STAGE1_MAX_LEN &&
+ (header_len % BB02_STAGE1_HEADER_ALIGNMENT) == 0;
+}
+
+static bool _stage1_flags_ok(const bb02_stage1_header_t* header)
+{
+#ifdef BOOTLOADER_UPGRADE_DEVELOPMENT
+ return (header->flags & BB02_STAGE1_FLAG_DEVELOPMENT) != 0;
+#else
+ return (header->flags & BB02_STAGE1_FLAG_DEVELOPMENT) == 0;
+#endif
+}
+
+static void _disable_mpu(void)
+{
+ __disable_irq();
+ __DSB();
+ __ISB();
+ MPU->CTRL = MPU_DISABLE;
+ __DSB();
+ __ISB();
+ __enable_irq();
+}
+
+static void _disable_bootprot(void)
+{
+ while (NVMCTRL->STATUS.bit.READY == 0) {
+ }
+ NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMD_SBPDIS | NVMCTRL_CTRLB_CMDEX_KEY;
+ while (NVMCTRL->STATUS.bit.READY == 0) {
+ }
+}
+
+static bool _stage0_is_installed(void)
+{
+ if (_stage0_image_len() != BB02_BOOTLOADER_UPGRADE_STAGE0_LEN) {
+ AbortAutoenter("stage0 len");
+ }
+ return _bytes_equal(
+ (const uint8_t*)BB02_BOOTLOADER_UPGRADE_STAGE0_ADDR, _stage0_image(), _stage0_image_len());
+}
+
+static bool _update_header_basic_ok(const bb02_stage1_header_t* header)
+{
+ const uint32_t header_len = (uint32_t)header->header_len;
+ return header->magic == BB02_STAGE1_HEADER_MAGIC && _stage1_header_len_ok(header_len) &&
+ header->image_len > header_len && header->product_id == BB02_STAGE1_PRODUCT_ID &&
+ _stage1_flags_ok(header) && header->image_len <= BB02_STAGE1_MAX_LEN &&
+ _stage1_marketing_version_ok(header);
+}
+
+static bool _stage1_update_ok(const bb02_stage1_header_t* update)
+{
+ if (!_update_header_basic_ok(update)) {
+ return false;
+ }
+#ifdef BOOTLOADER_UPGRADE_DEVELOPMENT
+ return true;
+#else
+ return stage1_sigcheck_image_ok(update, bb02_stage1_pubkeys) == sectrue_u32;
+#endif
+}
+
+static void _flash_stage0(void)
+{
+ _disable_mpu();
+ _disable_bootprot();
+ if (flash_unlock(&FLASH_0, BB02_BOOTLOADER_UPGRADE_STAGE0_ADDR, FLASH_REGION_PAGE_NUM) !=
+ FLASH_REGION_PAGE_NUM) {
+ AbortAutoenter("unlock stage0");
+ }
+ if (flash_write(
+ &FLASH_0,
+ BB02_BOOTLOADER_UPGRADE_STAGE0_ADDR,
+ _stage0_image_for_flash_write(),
+ _stage0_image_len()) != ERR_NONE) {
+ AbortAutoenter("write stage0");
+ }
+ if (!_stage0_is_installed()) {
+ AbortAutoenter("verify stage0");
+ }
+}
+
+void bootloader_upgrade_install_or_reboot(void)
+{
+ _apply_saved_orientation();
+
+#ifndef BOOTLOADER_UPGRADE_DEVELOPMENT
+ if (bootloader_upgrade_is_development_bootloader(
+ (const bb02_stage0_descriptor_t*)BB02_STAGE0_DESCRIPTOR_ADDR,
+ bb02_stage1_installed_header(),
+ (const uint8_t*)LEGACY_BOOTLOADER_SCAN_ADDR,
+ LEGACY_BOOTLOADER_SCAN_LEN)) {
+ AbortAutoenter("Development bootloader");
+ }
+#endif
+
+ const bb02_stage1_header_t* update = bb02_stage1_update_header();
+ if (!_stage1_update_ok(update)) {
+ AbortAutoenter("stage1 update");
+ }
+ if (!_stage0_is_installed()) {
+ _flash_stage0();
+ if (!_stage0_is_installed()) {
+ AbortAutoenter("verify stage0");
+ }
+ }
+ boot_bootloader_wait(screen_is_upside_down());
+ AbortAutoenter("reboot");
+}
diff --git a/src/bootloader_upgrade/firmware_installer.h b/src/bootloader_upgrade/firmware_installer.h
new file mode 100644
index 00000000..4a684155
--- /dev/null
+++ b/src/bootloader_upgrade/firmware_installer.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _BOOTLOADER_UPGRADE_FIRMWARE_INSTALLER_H_
+#define _BOOTLOADER_UPGRADE_FIRMWARE_INSTALLER_H_
+
+void bootloader_upgrade_install_or_reboot(void);
+
+#endif
diff --git a/src/bootloader_upgrade/firmware_installer_check.c b/src/bootloader_upgrade/firmware_installer_check.c
new file mode 100644
index 00000000..662f8945
--- /dev/null
+++ b/src/bootloader_upgrade/firmware_installer_check.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "firmware_installer_check.h"
+#include "bootloader_upgrade.h"
+#include <string.h>
+
+_Static_assert(
+ BB02_STAGE0_DESCRIPTOR_ADDR >= BB02_BOOTLOADER_UPGRADE_STAGE0_ADDR,
+ "stage0 descriptor must be inside the bootloader image");
+
+#define LEGACY_DEV_DEVICE_MARKER "DEV DEVICE"
+#define LEGACY_DEV_DEVICE_MARKER_LEN (sizeof(LEGACY_DEV_DEVICE_MARKER) - 1)
+#define LEGACY_NOT_FOR_VALUE_MARKER "NOT FOR VALUE"
+#define LEGACY_NOT_FOR_VALUE_MARKER_LEN (sizeof(LEGACY_NOT_FOR_VALUE_MARKER) - 1)
+
+static bool _matches_at(
+ const uint8_t* haystack,
+ size_t haystack_len,
+ size_t pos,
+ const char* needle,
+ size_t needle_len)
+{
+ if (needle_len == 0 || pos > haystack_len || needle_len > haystack_len - pos) {
+ return false;
+ }
+ return memcmp(&haystack[pos], needle, needle_len) == 0;
+}
+
+bool bootloader_upgrade_has_legacy_development_markers(const uint8_t* bootloader, size_t len)
+{
+ bool has_dev_device = false;
+ bool has_not_for_value = false;
+ if (bootloader == NULL) {
+ return false;
+ }
+ for (size_t i = 0; i < len && (!has_dev_device || !has_not_for_value); i++) {
+ if (!has_dev_device &&
+ _matches_at(
+ bootloader, len, i, LEGACY_DEV_DEVICE_MARKER, LEGACY_DEV_DEVICE_MARKER_LEN)) {
+ has_dev_device = true;
+ }
+ if (!has_not_for_value &&
+ _matches_at(
+ bootloader, len, i, LEGACY_NOT_FOR_VALUE_MARKER, LEGACY_NOT_FOR_VALUE_MARKER_LEN)) {
+ has_not_for_value = true;
+ }
+ }
+ return has_dev_device && has_not_for_value;
+}
+
+static bool _read_stage0_descriptor(
+ const bb02_stage0_descriptor_t* descriptor,
+ bb02_stage0_descriptor_t* descriptor_out)
+{
+ if (descriptor == NULL || descriptor_out == NULL) {
+ return false;
+ }
+ memcpy(descriptor_out, descriptor, sizeof(*descriptor_out));
+ return descriptor_out->magic == BB02_STAGE0_DESCRIPTOR_MAGIC &&
+ descriptor_out->stage0_version == BB02_STAGE0_IMAGE_VERSION &&
+ descriptor_out->product_id == BB02_STAGE1_PRODUCT_ID;
+}
+
+static bool _stage1_header_len_ok(uint32_t header_len)
+{
+ return header_len >= BB02_STAGE1_HEADER_LEN && header_len <= BB02_STAGE1_MAX_LEN &&
+ (header_len % BB02_STAGE1_HEADER_ALIGNMENT) == 0;
+}
+
+static bool _read_stage1_header(
+ const bb02_stage1_header_t* header,
+ bb02_stage1_header_t* header_out)
+{
+ if (header == NULL || header_out == NULL) {
+ return false;
+ }
+ memcpy(header_out, header, sizeof(*header_out));
+ const uint32_t header_len = header_out->header_len;
+ return header_out->magic == BB02_STAGE1_HEADER_MAGIC &&
+ header_out->product_id == BB02_STAGE1_PRODUCT_ID && _stage1_header_len_ok(header_len) &&
+ header_out->image_len > header_len && header_out->image_len <= BB02_STAGE1_MAX_LEN;
+}
+
+bool bootloader_upgrade_is_development_bootloader(
+ const bb02_stage0_descriptor_t* stage0_descriptor,
+ const bb02_stage1_header_t* stage1_header,
+ const uint8_t* legacy_bootloader,
+ size_t legacy_bootloader_len)
+{
+ bb02_stage0_descriptor_t descriptor;
+ if (_read_stage0_descriptor(stage0_descriptor, &descriptor)) {
+ bb02_stage1_header_t header;
+ return (descriptor.flags & BB02_STAGE0_FLAG_DEVELOPMENT) != 0 ||
+ (_read_stage1_header(stage1_header, &header) &&
+ (header.flags & BB02_STAGE1_FLAG_DEVELOPMENT) != 0);
+ }
+ return bootloader_upgrade_has_legacy_development_markers(
+ legacy_bootloader, legacy_bootloader_len);
+}
diff --git a/src/bootloader_upgrade/firmware_installer_check.h b/src/bootloader_upgrade/firmware_installer_check.h
new file mode 100644
index 00000000..7ba14594
--- /dev/null
+++ b/src/bootloader_upgrade/firmware_installer_check.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _BOOTLOADER_UPGRADE_FIRMWARE_INSTALLER_CHECK_H_
+#define _BOOTLOADER_UPGRADE_FIRMWARE_INSTALLER_CHECK_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "bootloader/stage0/stage0_descriptor.h"
+#include "bootloader_upgrade.h"
+
+bool bootloader_upgrade_has_legacy_development_markers(const uint8_t* bootloader, size_t len);
+bool bootloader_upgrade_is_development_bootloader(
+ const bb02_stage0_descriptor_t* stage0_descriptor,
+ const bb02_stage1_header_t* stage1_header,
+ const uint8_t* legacy_bootloader,
+ size_t legacy_bootloader_len);
+
+#endif
diff --git a/src/bootloader_upgrade/stage1_pubkeys.c b/src/bootloader_upgrade/stage1_pubkeys.c
new file mode 100644
index 00000000..9c07ab02
--- /dev/null
+++ b/src/bootloader_upgrade/stage1_pubkeys.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "stage1_pubkeys.h"
+
+const uint8_t bb02_stage1_pubkeys[BB02_STAGE1_ROOT_KEY_COUNT][64] = {
+ {
+ 0x3a, 0x2d, 0x53, 0x8f, 0x0e, 0x6d, 0xb2, 0x86, 0x28, 0x7f, 0x5d, 0xfb, 0xf3,
+ 0x04, 0x6c, 0x2b, 0x43, 0x6e, 0xad, 0x5f, 0x01, 0x53, 0xb0, 0xbe, 0xcb, 0x45,
+ 0x61, 0x95, 0x60, 0x16, 0x22, 0x0e, 0x75, 0x0e, 0x49, 0xa7, 0xa4, 0xba, 0x41,
+ 0x2e, 0xca, 0xce, 0x07, 0xf2, 0x86, 0xc0, 0xb3, 0x4f, 0x6a, 0x0e, 0xb2, 0xd9,
+ 0x52, 0xe3, 0x96, 0xa3, 0xeb, 0xab, 0xda, 0x43, 0x55, 0xd8, 0xe6, 0x77,
+ },
+ {
+ 0x49, 0x93, 0x70, 0xda, 0xa9, 0x0c, 0xb0, 0x08, 0x80, 0x42, 0x37, 0xc6, 0x2c,
+ 0x7d, 0xb4, 0xcb, 0x54, 0xee, 0xfe, 0xd0, 0x43, 0x0a, 0x3d, 0xcd, 0xe7, 0xde,
+ 0x57, 0xa6, 0x1a, 0xe6, 0x4a, 0xd3, 0xbb, 0x16, 0x3a, 0x03, 0x1a, 0xb2, 0xcc,
+ 0x56, 0x47, 0xaa, 0x74, 0xe2, 0x61, 0xc0, 0x23, 0xef, 0xfe, 0xde, 0x98, 0xe6,
+ 0x4b, 0xbe, 0x58, 0xb0, 0x19, 0xfb, 0x4f, 0x71, 0x80, 0xf6, 0x87, 0x2f,
+ },
+ {
+ 0x48, 0x61, 0xae, 0xb6, 0xb1, 0x05, 0x26, 0xb7, 0x3e, 0x97, 0xc6, 0x80, 0x79,
+ 0x18, 0xe9, 0xde, 0x8b, 0x99, 0xd4, 0x98, 0x84, 0x4c, 0x54, 0x4c, 0xf2, 0x2a,
+ 0x64, 0x49, 0xa2, 0x12, 0x0c, 0xf2, 0x90, 0x11, 0xf7, 0xee, 0xcc, 0x14, 0x7f,
+ 0x56, 0xf6, 0x4d, 0xfa, 0xe3, 0x2e, 0x96, 0x3b, 0xeb, 0xd3, 0x40, 0x8e, 0xe5,
+ 0x12, 0x0c, 0xd8, 0x71, 0x23, 0xcf, 0x4d, 0xb9, 0x6e, 0x93, 0x6c, 0x04,
+ },
+};
diff --git a/src/bootloader_upgrade/stage1_pubkeys.h b/src/bootloader_upgrade/stage1_pubkeys.h
new file mode 100644
index 00000000..429b2c94
--- /dev/null
+++ b/src/bootloader_upgrade/stage1_pubkeys.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef _BOOTLOADER_UPGRADE_STAGE1_PUBKEYS_H_
+#define _BOOTLOADER_UPGRADE_STAGE1_PUBKEYS_H_
+
+#include "bootloader_upgrade.h"
+#include <stdint.h>
+
+extern const uint8_t bb02_stage1_pubkeys[BB02_STAGE1_ROOT_KEY_COUNT][64];
+
+#endif
diff --git a/src/da14531/da14531_handler.c b/src/da14531/da14531_handler.c
index 7c72262c..99fff14a 100644
--- a/src/da14531/da14531_handler.c
+++ b/src/da14531/da14531_handler.c
@@ -106,13 +106,13 @@ static void _ctrl_handler(const struct da14531_ctrl_frame* frame, struct RustByt
// util_log("da14531: bond db len %d", len);
uint16_t tmp_len;
uint8_t tmp[12 + sizeof(response) * 2];
- if (len != -1) {
+ if (len >= 0) {
tmp_len = da14531_protocol_format(
&tmp[0],
sizeof(tmp),
DA14531_PROTOCOL_PACKET_TYPE_CTRL_DATA,
&response[0],
- 1 + len);
+ 1 + (uint16_t)len);
} else {
tmp_len = da14531_protocol_format(
&tmp[0], sizeof(tmp), DA14531_PROTOCOL_PACKET_TYPE_CTRL_DATA, &response[0], 1);
@@ -129,7 +129,11 @@ static void _ctrl_handler(const struct da14531_ctrl_frame* frame, struct RustByt
ASSERT(false);
break;
}
- memory_set_ble_bond_db(&frame->cmd_data[0], frame->payload_length - 1);
+ if (!memory_set_ble_bond_db(&frame->cmd_data[0], frame->payload_length - 1)) {
+ util_log("da14531: set bond db failed");
+ ASSERT(false);
+ break;
+ }
#if FACTORYSETUP == 1
_bond_db_set = true;
#endif
diff --git a/src/firmware.c b/src/firmware.c
index 3d251d08..cf7c48a8 100644
--- a/src/firmware.c
+++ b/src/firmware.c
@@ -17,6 +17,10 @@
#include <rust/rust.h>
#include <ui/oled/oled.h>
+#ifdef BOOTLOADER_UPGRADE
+ #include "bootloader_upgrade/firmware_installer.h"
+#endif
+
#if APP_U2F == 1
#include <u2f.h>
#endif
@@ -36,6 +40,9 @@ int main(void)
qtouch_init();
common_main();
bitbox02_smarteeprom_init();
+#ifdef BOOTLOADER_UPGRADE
+ bootloader_upgrade_install_or_reboot();
+#endif
if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
da14531_protocol_init();
}
diff --git a/src/memory/memory_shared.c b/src/memory/memory_shared.c
index cc92c08b..44070622 100644
--- a/src/memory/memory_shared.c
+++ b/src/memory/memory_shared.c
@@ -163,9 +163,11 @@ int16_t memory_get_ble_bond_db(uint8_t* data)
chunk_shared_t chunk = {0};
memory_read_shared_bootdata(&chunk);
int16_t len = chunk.fields.ble_bond_db_len;
- if (len != -1) {
- memcpy(data, &chunk.fields.ble_bond_db[0], len);
+ if (len < 0 || len > MEMORY_BLE_BOND_DB_LEN) {
+ util_zero(&chunk, sizeof(chunk));
+ return -1;
}
+ memcpy(data, &chunk.fields.ble_bond_db[0], len);
util_zero(&chunk, sizeof(chunk));
return len;
@@ -173,8 +175,8 @@ int16_t memory_get_ble_bond_db(uint8_t* data)
bool memory_set_ble_bond_db(const uint8_t* data, int16_t data_len)
{
- ASSERT(data_len <= MEMORY_BLE_BOND_DB_LEN);
- if (data_len > MEMORY_BLE_BOND_DB_LEN) {
+ ASSERT(data_len >= 0 && data_len <= MEMORY_BLE_BOND_DB_LEN);
+ if (data_len < 0 || data_len > MEMORY_BLE_BOND_DB_LEN) {
return false;
}
chunk_shared_t chunk = {0};
diff --git a/src/memory/memory_shared.h b/src/memory/memory_shared.h
index 14dbd3fb..b71f07f5 100644
--- a/src/memory/memory_shared.h
+++ b/src/memory/memory_shared.h
@@ -144,7 +144,7 @@ void memory_get_ble_irk(uint8_t* data);
void memory_get_ble_identity_address(uint8_t* data);
// data_len can be at most MEMORY_BLE_BOND_DB_LEN
-bool memory_set_ble_bond_db(const uint8_t* data, int16_t data_len);
+USE_RESULT bool memory_set_ble_bond_db(const uint8_t* data, int16_t data_len);
typedef struct {
uint8_t allowed_firmware_hash[32];
diff --git a/src/platform/driver_init.c b/src/platform/driver_init.c
index b7af34a8..2069d670 100644
--- a/src/platform/driver_init.c
+++ b/src/platform/driver_init.c
@@ -411,6 +411,30 @@ void bootloader_init(void)
_is_initialized = true;
}
+void stage0_init(void)
+{
+ _delay_driver_init();
+ _oled_set_pins();
+ _spi_init();
+ _flash_memory_init();
+ _sha_init();
+ _rand_init();
+#ifndef BB02_STAGE0_DEVELOPMENT
+ _ecdsa_init();
+#endif
+}
+
+void stage0_deinit(void)
+{
+ // OLED interface bus. Display remains on last screen.
+ SPI_OLED_disable();
+ // Flash
+ flash_deinit(&FLASH_0);
+ // Hardware crypto
+ sha_sync_deinit(&HASH_ALGORITHM_0);
+ rand_sync_deinit(&RAND_0);
+}
+
void system_close_interfaces(void)
{
if (!_is_initialized) {
diff --git a/src/platform/driver_init.h b/src/platform/driver_init.h
index 6a334590..5b2d11f4 100644
--- a/src/platform/driver_init.h
+++ b/src/platform/driver_init.h
@@ -23,7 +23,6 @@
#include <hal_usart_async.h>
#include <hal_usb_device.h>
#include <hpl_rtc_base.h>
- #include <sd_mmc.h>
#include <spi_lite.h>
#endif
#include <utils.h>
@@ -71,4 +70,14 @@ void bootloader_close_interfaces(void);
*/
void bootloader_init(void);
+/**
+ * Perform the subset of system initialization needed by stage0.
+ */
+void stage0_init(void);
+
+/**
+ * Close peripheral interfaces initialized by stage0.
+ */
+void stage0_deinit(void);
+
#endif
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index 619f7db4..1ad4f872 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -135,11 +135,12 @@ const ALLOWLIST_FNS: &[&str] = &[
"random_32_bytes_mcu",
"random_32_bytes",
"random_fake_reset",
- "reboot_to_bootloader",
+ "boot_bootloader_wait",
"reboot",
"reset_ble",
"screen_clear",
"screen_init",
+ "screen_is_upside_down",
"screen_print_debug",
"screen_process_waiting_switch_to_lockscreen",
"screen_process_waiting_switch_to_logo",
@@ -319,10 +320,11 @@ pub fn main() -> BuildResult<()> {
emit_rerun_if_changed("../../../versions.json");
emit_rerun_if_changed("../../../src/version.h.tmpl");
emit_rerun_if_changed("../../../src/bootloader/bootloader_version.h.tmpl");
+ emit_rerun_if_changed("../../../src/bootloader/stage0/stage0_version.h.tmpl");
emit_rerun_if_changed("../../../scripts/generate_version_headers.py");
emit_rerun_if_changed("../../../scripts/generate_rust_header.sh");
- // Generating version.h/bootloader_version.h depends on the current state of the git repo
+ // Generating version headers depends on the current state of the git repo.
emit_git_rerun_if_changed(&repo_root);
ensure_command_exists("bindgen")?;
diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs
index 23184d58..94d69130 100644
--- a/src/rust/bitbox02/src/lib.rs
+++ b/src/rust/bitbox02/src/lib.rs
@@ -118,7 +118,7 @@ pub fn reset_ble() {
#[allow(clippy::empty_loop)]
pub fn reboot_to_bootloader() -> ! {
- unsafe { bitbox02_sys::reboot_to_bootloader() }
+ unsafe { bitbox02_sys::boot_bootloader_wait(bitbox02_sys::screen_is_upside_down()) }
loop {}
}
diff --git a/src/system.c b/src/system.c
index d0f61195..cd7197c0 100644
--- a/src/system.c
+++ b/src/system.c
@@ -2,10 +2,9 @@
#include "system.h"
#include "da14531/da14531.h"
-#include <memory/memory.h>
+#include <bootloader/boot_args.h>
#include <memory/memory_shared.h>
#include <rust/rust.h>
-#include <screen.h>
#ifndef TESTING
#include "uart.h"
#include <driver_init.h>
@@ -28,22 +27,13 @@ static void _ble_clear_product(void)
rust_bytequeue_free(uart_queue);
}
-void reboot_to_bootloader(void)
+void boot_bootloader_wait(bool upside_down)
{
if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
_ble_clear_product();
}
- auto_enter_t auto_enter = {
- .value = sectrue_u8,
- };
- upside_down_t upside_down = {
- .value = screen_is_upside_down(),
- };
- if (!memory_bootloader_set_flags(auto_enter, upside_down)) {
- // If this failed, we might not be able to reboot into the bootloader.
- // We will try anyway, no point in aborting here.
- }
#ifndef TESTING
+ boot_args_write_bootloader_wait(upside_down);
_reset_mcu();
#endif
}
diff --git a/src/system.h b/src/system.h
index 3da6e6cc..8798145c 100644
--- a/src/system.h
+++ b/src/system.h
@@ -3,10 +3,12 @@
#ifndef _SYSTEM_H_
#define _SYSTEM_H_
+#include <stdbool.h>
+
/**
- * Reboots the device to bootloader
+ * Reboots the device to stage1 and waits there.
*/
-void reboot_to_bootloader(void);
+void boot_bootloader_wait(bool upside_down);
/**
* Reboots the device.
diff --git a/src/usb/class/hid/hid.c b/src/usb/class/hid/hid.c
index 3f22d7dd..7b48f9cb 100644
--- a/src/usb/class/hid/hid.c
+++ b/src/usb/class/hid/hid.c
@@ -5,6 +5,7 @@
#if !defined(TESTING)
#include "usb_protocol.h"
#endif
+#include <assert.h>
#include <string.h>
/**
@@ -184,19 +185,26 @@ int32_t hid_req(
}
switch (req->bRequest) {
case 0x03: /* Get Protocol */
- return usbdc_xfer(ep, &func_data->protocol, 1, 0);
+ return usbdc_xfer(ep, &func_data->protocol, 1, false);
case 0x0B: /* Set Protocol */
func_data->protocol = req->wValue;
- return usbdc_xfer(ep, NULL, 0, 0);
- case USB_REQ_HID_SET_REPORT:
+ return usbdc_xfer(ep, NULL, 0, false);
+ case USB_REQ_HID_SET_REPORT: {
+ static_assert(
+ USB_HID_REPORT_OUT_SIZE == USB_REPORT_SIZE,
+ "USB_HID_REPORT_OUT_SIZE must match USB_REPORT_SIZE");
+ if (len > USB_HID_REPORT_OUT_SIZE) {
+ return ERR_INVALID_ARG;
+ }
if (USB_SETUP_STAGE == stage) {
return usbdc_xfer(ep, ctrl_buf, len, false);
- } else {
- if (NULL != func_data->hid_set_report) {
- func_data->hid_set_report(ctrl_buf, len);
- }
- return ERR_NONE;
}
+
+ if (NULL != func_data->hid_set_report) {
+ func_data->hid_set_report(ctrl_buf, len);
+ }
+ return ERR_NONE;
+ }
default:
return ERR_INVALID_ARG;
}
diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt
index 023f6f1e..a7ce11d2 100644
--- a/test/unit-test/CMakeLists.txt
+++ b/test/unit-test/CMakeLists.txt
@@ -45,6 +45,14 @@ else()
""
memory
"-Wl,--wrap=memory_read_chunk_fake,--wrap=memory_write_chunk_fake,--wrap=rust_noise_generate_static_private_key,--wrap=memory_read_shared_bootdata_fake,--wrap=memory_write_to_address_fake,--wrap=random_32_bytes_mcu"
+ stage0_sigcheck
+ "-Wl,--wrap=pukcc_ecdsa_verify,--wrap=sha_sync_sha256_start,--wrap=sha_sync_sha256_update,--wrap=sha_sync_sha256_finish"
+ stage0_flash
+ ""
+ stage0_descriptor
+ ""
+ bootloader_upgrade_check
+ ""
util
""
ugui
@@ -78,6 +86,26 @@ else()
${CMAKE_SOURCE_DIR}/external/asf4-drivers/hal/utils/include
)
endif()
+ if(TEST_NAME STREQUAL "stage0_sigcheck")
+ target_sources(${EXE} PRIVATE
+ ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage1_sigcheck.c
+ ${CMAKE_SOURCE_DIR}/src/pukcc/curve_p256.c
+ )
+ target_compile_definitions(${EXE} PRIVATE PRODUCT_BITBOX_MULTI=1)
+ target_include_directories(${EXE} PRIVATE
+ ${CMAKE_SOURCE_DIR}/external/asf4-drivers/hal/include
+ ${CMAKE_SOURCE_DIR}/external/asf4-drivers/hal/utils/include
+ )
+ endif()
+ if(TEST_NAME STREQUAL "stage0_descriptor")
+ target_sources(${EXE} PRIVATE ${CMAKE_SOURCE_DIR}/src/bootloader/stage0/stage0_descriptor.c)
+ endif()
+ if(TEST_NAME STREQUAL "bootloader_upgrade_check")
+ target_sources(${EXE} PRIVATE
+ ${CMAKE_SOURCE_DIR}/src/bootloader_upgrade/firmware_installer_check.c
+ )
+ target_compile_definitions(${EXE} PRIVATE PRODUCT_BITBOX_MULTI=1)
+ endif()
add_test(NAME test_${TEST_NAME} COMMAND ${EXE})
endforeach()
endif()
diff --git a/test/unit-test/test_bootloader_upgrade_check.c b/test/unit-test/test_bootloader_upgrade_check.c
new file mode 100644
index 00000000..01bf702d
--- /dev/null
+++ b/test/unit-test/test_bootloader_upgrade_check.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <cmocka.h>
+
+#include "bootloader/stage0/stage0_descriptor.h"
+#include "bootloader_upgrade/bootloader_upgrade.h"
+#include "bootloader_upgrade/firmware_installer_check.h"
+
+static void _put_bytes(uint8_t* dst, size_t dst_len, size_t offset, const char* src)
+{
+ const size_t src_len = strlen(src);
+ assert_true(offset <= dst_len);
+ assert_true(src_len <= dst_len - offset);
+ memcpy(&dst[offset], src, src_len);
+}
+
+static void test_legacy_development_markers(void** state)
+{
+ (void)state;
+ uint8_t bootloader[256] = {0};
+
+ _put_bytes(bootloader, sizeof(bootloader), 10, "DEV DEVICE");
+ _put_bytes(bootloader, sizeof(bootloader), 100, "NOT FOR VALUE");
+
+ assert_true(bootloader_upgrade_has_legacy_development_markers(bootloader, sizeof(bootloader)));
+ assert_true(
+ bootloader_upgrade_is_development_bootloader(NULL, NULL, bootloader, sizeof(bootloader)));
+}
+
+static void test_legacy_development_markers_need_both(void** state)
+{
+ (void)state;
+ uint8_t bootloader[256] = {0};
+
+ _put_bytes(bootloader, sizeof(bootloader), 10, "DEV DEVICE");
+ assert_false(bootloader_upgrade_has_legacy_development_markers(bootloader, sizeof(bootloader)));
+
+ memset(bootloader, 0, sizeof(bootloader));
+ _put_bytes(bootloader, sizeof(bootloader), 100, "NOT FOR VALUE");
+ assert_false(bootloader_upgrade_has_legacy_development_markers(bootloader, sizeof(bootloader)));
+}
+
+static void test_legacy_development_markers_absent(void** state)
+{
+ (void)state;
+ uint8_t bootloader[256] = {0};
+
+ assert_false(bootloader_upgrade_has_legacy_development_markers(bootloader, sizeof(bootloader)));
+ assert_false(
+ bootloader_upgrade_is_development_bootloader(NULL, NULL, bootloader, sizeof(bootloader)));
+}
+
+static void test_development_stage0_descriptor(void** state)
+{
+ (void)state;
+ const bb02_stage0_descriptor_t descriptor = {
+ .stage0_version = BB02_STAGE0_IMAGE_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .flags = BB02_STAGE0_FLAG_DEVELOPMENT,
+ .magic = BB02_STAGE0_DESCRIPTOR_MAGIC,
+ };
+ uint8_t legacy_bootloader[256] = {0};
+
+ assert_true(bootloader_upgrade_is_development_bootloader(
+ &descriptor, NULL, legacy_bootloader, sizeof(legacy_bootloader)));
+}
+
+static void test_development_stage1_header(void** state)
+{
+ (void)state;
+ const bb02_stage0_descriptor_t stage0_descriptor = {
+ .stage0_version = BB02_STAGE0_IMAGE_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .flags = 0,
+ .magic = BB02_STAGE0_DESCRIPTOR_MAGIC,
+ };
+ const bb02_stage1_header_t stage1_header = {
+ .magic = BB02_STAGE1_HEADER_MAGIC,
+ .header_version = BB02_STAGE1_HEADER_FORMAT_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .header_len = BB02_STAGE1_HEADER_LEN,
+ .image_len = BB02_STAGE1_HEADER_LEN + 512u,
+ .flags = BB02_STAGE1_FLAG_DEVELOPMENT,
+ };
+ uint8_t legacy_bootloader[256] = {0};
+
+ assert_true(bootloader_upgrade_is_development_bootloader(
+ &stage0_descriptor, &stage1_header, legacy_bootloader, sizeof(legacy_bootloader)));
+}
+
+static void test_development_stage1_header_future_header_version(void** state)
+{
+ (void)state;
+ const bb02_stage0_descriptor_t stage0_descriptor = {
+ .stage0_version = BB02_STAGE0_IMAGE_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .flags = 0,
+ .magic = BB02_STAGE0_DESCRIPTOR_MAGIC,
+ };
+ const bb02_stage1_header_t stage1_header = {
+ .magic = BB02_STAGE1_HEADER_MAGIC,
+ .header_version = BB02_STAGE1_HEADER_FORMAT_VERSION + 1u,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .header_len = BB02_STAGE1_HEADER_LEN,
+ .image_len = BB02_STAGE1_HEADER_LEN + 512u,
+ .flags = BB02_STAGE1_FLAG_DEVELOPMENT,
+ };
+ uint8_t legacy_bootloader[256] = {0};
+
+ assert_true(bootloader_upgrade_is_development_bootloader(
+ &stage0_descriptor, &stage1_header, legacy_bootloader, sizeof(legacy_bootloader)));
+}
+
+static void test_production_stage0_descriptor_skips_legacy_markers(void** state)
+{
+ (void)state;
+ const bb02_stage0_descriptor_t stage0_descriptor = {
+ .stage0_version = BB02_STAGE0_IMAGE_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .flags = 0,
+ .magic = BB02_STAGE0_DESCRIPTOR_MAGIC,
+ };
+ const bb02_stage1_header_t stage1_header = {
+ .magic = BB02_STAGE1_HEADER_MAGIC,
+ .header_version = BB02_STAGE1_HEADER_FORMAT_VERSION,
+ .product_id = BB02_STAGE1_PRODUCT_ID,
+ .header_len = BB02_STAGE1_HEADER_LEN,
+ .image_len = BB02_STAGE1_HEADER_LEN + 512u,
+ .flags = 0,
+ };
+ uint8_t legacy_bootloader[256] = {0};
+
+ _put_bytes(legacy_bootloader, sizeof(legacy_bootloader), 10, "DEV DEVICE");
+ _put_bytes(legacy_bootloader, sizeof(legacy_bootloader), 100, "NOT FOR VALUE");
+
+ assert_false(bootloader_upgrade_is_development_bootloader(
+ &stage0_descriptor, &stage1_header, legacy_bootloader, sizeof(legacy_bootloader)));
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_legacy_development_markers),
+ cmocka_unit_test(test_legacy_development_markers_need_both),
+ cmocka_unit_test(test_legacy_development_markers_absent),
+ cmocka_unit_test(test_development_stage0_descriptor),
+ cmocka_unit_test(test_development_stage1_header),
+ cmocka_unit_test(test_development_stage1_header_future_header_version),
+ cmocka_unit_test(test_production_stage0_descriptor_skips_legacy_markers),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/test/unit-test/test_memory.c b/test/unit-test/test_memory.c
index d947e13a..df178f2f 100644
--- a/test/unit-test/test_memory.c
+++ b/test/unit-test/test_memory.c
@@ -430,6 +430,44 @@ static void _test_memory_reset_hww_ble(void** state)
assert_true(memory_reset_hww());
}
+static void _test_memory_get_ble_bond_db(void** state)
+{
+ (void)state;
+ const uint8_t bond_db[] = {0x01, 0x02, 0x03};
+ chunk_shared_t shared_chunk = {0};
+ shared_chunk.fields.ble_bond_db_len = sizeof(bond_db);
+ memcpy(shared_chunk.fields.ble_bond_db, bond_db, sizeof(bond_db));
+ will_return(__wrap_memory_read_shared_bootdata_fake, shared_chunk.bytes);
+
+ uint8_t data[MEMORY_BLE_BOND_DB_LEN] = {0};
+ assert_int_equal(memory_get_ble_bond_db(data), sizeof(bond_db));
+ assert_memory_equal(data, bond_db, sizeof(bond_db));
+}
+
+static void _test_memory_get_ble_bond_db_invalid_negative_length(void** state)
+{
+ (void)state;
+ chunk_shared_t shared_chunk = {0};
+ shared_chunk.fields.ble_bond_db_len = -2;
+ memset(shared_chunk.fields.ble_bond_db, 0x42, sizeof(shared_chunk.fields.ble_bond_db));
+ will_return(__wrap_memory_read_shared_bootdata_fake, shared_chunk.bytes);
+
+ uint8_t data[MEMORY_BLE_BOND_DB_LEN];
+ uint8_t expected[MEMORY_BLE_BOND_DB_LEN];
+ memset(data, 0xa5, sizeof(data));
+ memset(expected, 0xa5, sizeof(expected));
+
+ assert_int_equal(memory_get_ble_bond_db(data), -1);
+ assert_memory_equal(data, expected, sizeof(data));
+}
+
+static void _test_memory_set_ble_bond_db_invalid_negative_length(void** state)
+{
+ (void)state;
+ const uint8_t bond_db[] = {0x01};
+ assert_false(memory_set_ble_bond_db(bond_db, -1));
+}
+
static void _test_memory_get_device_name_default(void** state)
{
char name_out[MEMORY_DEVICE_MAX_LEN_WITH_NULL] = {0};
@@ -633,6 +671,9 @@ int main(void)
cmocka_unit_test(_test_memory_set_mnemonic_passphrase_enabled),
cmocka_unit_test(_test_memory_reset_hww),
cmocka_unit_test(_test_memory_reset_hww_ble),
+ cmocka_unit_test(_test_memory_get_ble_bond_db),
+ cmocka_unit_test(_test_memory_get_ble_bond_db_invalid_negative_length),
+ cmocka_unit_test(_test_memory_set_ble_bond_db_invalid_negative_length),
cmocka_unit_test(_test_memory_get_device_name_default),
cmocka_unit_test(_test_memory_get_device_name_default_bluetooth),
cmocka_unit_test(_test_memory_get_device_name_invalid),
diff --git a/test/unit-test/test_stage0_descriptor.c b/test/unit-test/test_stage0_descriptor.c
new file mode 100644
index 00000000..0abcaee6
--- /dev/null
+++ b/test/unit-test/test_stage0_descriptor.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <cmocka.h>
+
+#include "bootloader/stage0/stage0_descriptor.h"
+#include "bootloader_upgrade/bootloader_upgrade.h"
+
+static void test_stage0_descriptor_abi(void** state)
+{
+ (void)state;
+
+ assert_int_equal(BB02_STAGE0_DESCRIPTOR_ADDR, 0x00001ff4u);
+ assert_int_equal(BB02_STAGE0_DESCRIPTOR_LEN, 12u);
+ assert_int_equal(sizeof(bb02_stage0_descriptor_t), BB02_STAGE0_DESCRIPTOR_LEN);
+ assert_int_equal(
+ offsetof(bb02_stage0_descriptor_t, stage0_version), BB02_STAGE0_DESCRIPTOR_LEN - 12u);
+ assert_int_equal(
+ offsetof(bb02_stage0_descriptor_t, product_id), BB02_STAGE0_DESCRIPTOR_LEN - 10u);
+ assert_int_equal(offsetof(bb02_stage0_descriptor_t, flags), BB02_STAGE0_DESCRIPTOR_LEN - 8u);
+ assert_int_equal(offsetof(bb02_stage0_descriptor_t, magic), BB02_STAGE0_DESCRIPTOR_LEN - 4u);
+}
+
+static void test_stage0_descriptor_value(void** state)
+{
+ (void)state;
+
+ assert_int_equal(bb02_stage0_descriptor.magic, BB02_STAGE0_DESCRIPTOR_MAGIC);
+ assert_int_equal(bb02_stage0_descriptor.stage0_version, BB02_STAGE0_IMAGE_VERSION);
+ assert_int_equal(bb02_stage0_descriptor.product_id, BB02_STAGE1_PRODUCT_ID);
+ assert_int_equal(bb02_stage0_descriptor.flags, 0u);
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_stage0_descriptor_abi),
+ cmocka_unit_test(test_stage0_descriptor_value),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/test/unit-test/test_stage0_flash.c b/test/unit-test/test_stage0_flash.c
new file mode 100644
index 00000000..7403cfdc
--- /dev/null
+++ b/test/unit-test/test_stage0_flash.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <cmocka.h>
+
+#include "bootloader/stage0/stage0_flash.h"
+
+static void test_stage0_flash_page_addr_ok(void** state)
+{
+ (void)state;
+
+ assert_int_equal(stage0_flash_page_addr_ok(STAGE0_STAGE1_PAGE_ADDR), sectrue_u32);
+ assert_int_equal(
+ stage0_flash_page_addr_ok(STAGE0_STAGE1_PAGE_ADDR + STAGE0_FLASH_PAGE_SIZE_BYTES),
+ sectrue_u32);
+ assert_int_equal(
+ stage0_flash_page_addr_ok(STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR), sectrue_u32);
+ assert_int_equal(
+ stage0_flash_page_addr_ok(BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR), sectrue_u32);
+
+ assert_int_equal(stage0_flash_page_addr_ok(STAGE0_STAGE1_PAGE_ADDR - 1u), secfalse_u32);
+ assert_int_equal(
+ stage0_flash_page_addr_ok(STAGE0_STAGE1_PAGE_ADDR - STAGE0_FLASH_PAGE_SIZE_BYTES),
+ secfalse_u32);
+ assert_int_equal(stage0_flash_page_addr_ok(STAGE0_STAGE1_PAGE_ADDR + 1u), secfalse_u32);
+ assert_int_equal(
+ stage0_flash_page_addr_ok(
+ STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR + STAGE0_FLASH_PAGE_SIZE_BYTES),
+ secfalse_u32);
+ assert_int_equal(
+ stage0_flash_page_addr_ok(
+ BB02_STAGE1_FACTORY_RANDOM_BACKUP_ADDR + STAGE0_FLASH_PAGE_SIZE_BYTES),
+ secfalse_u32);
+}
+
+static void test_stage0_flash_block_addr_ok(void** state)
+{
+ (void)state;
+
+ assert_int_equal(stage0_flash_block_addr_ok(STAGE0_STAGE1_BLOCK_ADDR), sectrue_u32);
+ assert_int_equal(
+ stage0_flash_block_addr_ok(STAGE0_STAGE1_BLOCK_ADDR + STAGE0_FLASH_BLOCK_SIZE_BYTES),
+ sectrue_u32);
+ assert_int_equal(
+ stage0_flash_block_addr_ok(STAGE0_STAGE1_FACTORY_RANDOM_BLOCK_ADDR), sectrue_u32);
+ assert_int_equal(
+ stage0_flash_block_addr_ok(BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR), sectrue_u32);
+
+ assert_int_equal(stage0_flash_block_addr_ok(STAGE0_STAGE1_BLOCK_ADDR - 1u), secfalse_u32);
+ assert_int_equal(
+ stage0_flash_block_addr_ok(STAGE0_STAGE1_BLOCK_ADDR - STAGE0_FLASH_BLOCK_SIZE_BYTES),
+ secfalse_u32);
+ assert_int_equal(stage0_flash_block_addr_ok(STAGE0_STAGE1_BLOCK_ADDR + 1u), secfalse_u32);
+ assert_int_equal(
+ stage0_flash_block_addr_ok(
+ STAGE0_STAGE1_FACTORY_RANDOM_BLOCK_ADDR + STAGE0_FLASH_BLOCK_SIZE_BYTES),
+ secfalse_u32);
+ assert_int_equal(
+ stage0_flash_block_addr_ok(
+ BB02_STAGE1_FACTORY_RANDOM_BACKUP_BLOCK_ADDR + STAGE0_FLASH_BLOCK_SIZE_BYTES),
+ secfalse_u32);
+}
+
+static void test_stage0_flash_make_invalid_header_page(void** state)
+{
+ (void)state;
+
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ stage0_flash_make_invalid_header_page(page);
+
+ assert_int_equal(page[0], 0);
+ for (uint32_t i = 1; i < STAGE0_FLASH_PAGE_WORDS; i++) {
+ assert_int_equal(page[i], UINT32_MAX);
+ }
+}
+
+static void _program_page(
+ uint32_t dst[STAGE0_FLASH_PAGE_WORDS],
+ uint32_t src[STAGE0_FLASH_PAGE_WORDS])
+{
+ for (uint32_t i = 0; i < STAGE0_FLASH_PAGE_WORDS; i++) {
+ dst[i] &= src[i];
+ }
+}
+
+static void _factory_random(uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN])
+{
+ for (uint32_t i = 0; i < BB02_STAGE1_FACTORY_RANDOM_LEN; i++) {
+ factory_random[i] = (uint8_t)(0xa5u ^ i);
+ }
+}
+
+static void test_stage0_flash_make_stage1_header_page(void** state)
+{
+ (void)state;
+
+ uint8_t image[STAGE0_FLASH_PAGE_SIZE_BYTES];
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ for (uint32_t i = 0; i < sizeof(image); i++) {
+ image[i] = (uint8_t)i;
+ }
+
+ stage0_flash_make_stage1_page(page, image, sizeof(image), BB02_STAGE1_HEADER_ADDR, NULL);
+
+ assert_memory_equal(page, image, sizeof(image));
+}
+
+static void test_stage0_flash_make_stage1_second_header_page(void** state)
+{
+ (void)state;
+
+ uint8_t image[BB02_STAGE1_HEADER_LEN];
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ for (uint32_t i = 0; i < sizeof(image); i++) {
+ image[i] = (uint8_t)i;
+ }
+
+ stage0_flash_make_stage1_page(
+ page, image, sizeof(image), BB02_STAGE1_HEADER_ADDR + STAGE0_FLASH_PAGE_SIZE_BYTES, NULL);
+
+ assert_memory_equal(page, image + STAGE0_FLASH_PAGE_SIZE_BYTES, STAGE0_FLASH_PAGE_SIZE_BYTES);
+}
+
+static void test_stage0_flash_make_stage1_factory_random_page(void** state)
+{
+ (void)state;
+
+ static uint8_t image[BB02_STAGE1_MAX_LEN];
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ const uint32_t image_offset = STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR - BB02_STAGE1_ADDR;
+ const uint32_t random_page_offset =
+ BB02_STAGE1_FACTORY_RANDOM_ADDR - STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR;
+
+ memset(image, 0x42, sizeof(image));
+ for (uint32_t i = 0; i < random_page_offset; i++) {
+ image[image_offset + i] = (uint8_t)i;
+ }
+ _factory_random(factory_random);
+
+ stage0_flash_make_stage1_page(
+ page, image, BB02_STAGE1_MAX_LEN, STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR, factory_random);
+
+ assert_memory_equal(page, &image[image_offset], random_page_offset);
+ assert_memory_equal(
+ ((uint8_t*)page) + random_page_offset, factory_random, sizeof(factory_random));
+}
+
+static void test_stage0_flash_make_stage1_factory_random_page_without_image_bytes(void** state)
+{
+ (void)state;
+
+ uint8_t image[STAGE0_FLASH_PAGE_SIZE_BYTES];
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ const uint32_t image_offset = STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR - BB02_STAGE1_ADDR;
+ const uint32_t random_page_offset =
+ BB02_STAGE1_FACTORY_RANDOM_ADDR - STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR;
+
+ memset(image, 0x42, sizeof(image));
+ _factory_random(factory_random);
+
+ stage0_flash_make_stage1_page(
+ page, image, image_offset, STAGE0_STAGE1_FACTORY_RANDOM_PAGE_ADDR, factory_random);
+
+ for (uint32_t i = 0; i < random_page_offset; i++) {
+ assert_int_equal(((uint8_t*)page)[i], 0xff);
+ }
+ assert_memory_equal(
+ ((uint8_t*)page) + random_page_offset, factory_random, sizeof(factory_random));
+}
+
+static void test_factory_random_source_policy(void** state)
+{
+ (void)state;
+
+ assert_int_equal(
+ stage0_factory_random_source(sectrue_u32, sectrue_u32, sectrue_u32),
+ STAGE0_FACTORY_RANDOM_SOURCE_BACKUP);
+ assert_int_equal(
+ stage0_factory_random_source(sectrue_u32, sectrue_u32, secfalse_u32),
+ STAGE0_FACTORY_RANDOM_SOURCE_CURRENT);
+ assert_int_equal(
+ stage0_factory_random_source(secfalse_u32, sectrue_u32, secfalse_u32),
+ STAGE0_FACTORY_RANDOM_SOURCE_BACKUP);
+ assert_int_equal(
+ stage0_factory_random_source(sectrue_u32, secfalse_u32, secfalse_u32),
+ STAGE0_FACTORY_RANDOM_SOURCE_CURRENT);
+}
+
+static void test_factory_random_backup_valid_record(void** state)
+{
+ (void)state;
+
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ uint8_t factory_random_out[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ uint32_t flash[STAGE0_FLASH_PAGE_WORDS];
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+
+ _factory_random(factory_random);
+ memset(flash, 0xff, sizeof(flash));
+ stage0_factory_random_backup_make_data_page(page, factory_random);
+ _program_page(flash, page);
+ stage0_factory_random_backup_make_commit_page(page);
+ _program_page(flash, page);
+
+ assert_int_equal(
+ stage0_factory_random_backup_valid(
+ (const stage0_factory_random_backup_t*)flash, factory_random_out),
+ sectrue_u32);
+ assert_memory_equal(factory_random_out, factory_random, sizeof(factory_random));
+}
+
+static void test_factory_random_backup_erased_page_invalid(void** state)
+{
+ (void)state;
+
+ uint32_t flash[STAGE0_FLASH_PAGE_WORDS];
+ memset(flash, 0xff, sizeof(flash));
+
+ assert_int_equal(
+ stage0_factory_random_backup_valid((const stage0_factory_random_backup_t*)flash, NULL),
+ secfalse_u32);
+}
+
+static void test_factory_random_backup_missing_commit_invalid(void** state)
+{
+ (void)state;
+
+ uint8_t factory_random[BB02_STAGE1_FACTORY_RANDOM_LEN];
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+
+ _factory_random(factory_random);
+ stage0_factory_random_backup_make_data_page(page, factory_random);
+
+ assert_int_equal(
+ stage0_factory_random_backup_valid((const stage0_factory_random_backup_t*)page, NULL),
+ secfalse_u32);
+ assert_int_equal(
+ ((const stage0_factory_random_backup_t*)page)->commit,
+ STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_ERASED);
+}
+
+static void test_factory_random_backup_commit_page_only_clears_commit(void** state)
+{
+ (void)state;
+
+ uint32_t page[STAGE0_FLASH_PAGE_WORDS];
+ stage0_factory_random_backup_make_commit_page(page);
+
+ const stage0_factory_random_backup_t* backup = (const stage0_factory_random_backup_t*)page;
+ assert_int_equal(backup->commit, STAGE0_FACTORY_RANDOM_BACKUP_COMMIT_WRITTEN);
+
+ const uint32_t commit_word =
+ (uint32_t)(offsetof(stage0_factory_random_backup_t, commit) / sizeof(uint32_t));
+ for (uint32_t i = 0; i < STAGE0_FLASH_PAGE_WORDS; i++) {
+ if (i == commit_word) {
+ continue;
+ }
+ assert_int_equal(page[i], UINT32_MAX);
+ }
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_stage0_flash_page_addr_ok),
+ cmocka_unit_test(test_stage0_flash_block_addr_ok),
+ cmocka_unit_test(test_stage0_flash_make_invalid_header_page),
+ cmocka_unit_test(test_stage0_flash_make_stage1_header_page),
+ cmocka_unit_test(test_stage0_flash_make_stage1_second_header_page),
+ cmocka_unit_test(test_stage0_flash_make_stage1_factory_random_page),
+ cmocka_unit_test(test_stage0_flash_make_stage1_factory_random_page_without_image_bytes),
+ cmocka_unit_test(test_factory_random_source_policy),
+ cmocka_unit_test(test_factory_random_backup_valid_record),
+ cmocka_unit_test(test_factory_random_backup_erased_page_invalid),
+ cmocka_unit_test(test_factory_random_backup_missing_commit_invalid),
+ cmocka_unit_test(test_factory_random_backup_commit_page_only_clears_commit),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/test/unit-test/test_stage0_sigcheck.c b/test/unit-test/test_stage0_sigcheck.c
new file mode 100644
index 00000000..eb2942fc
--- /dev/null
+++ b/test/unit-test/test_stage0_sigcheck.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <cmocka.h>
+
+#include "bootloader/stage0/stage1_sigcheck.h"
+#include "hal_sha_sync.h"
+#include "pukcc/pukcc.h"
+#include "rust/rust.h"
+
+#define BODY_LEN (1024u)
+#define EXTRA_HEADER_LEN (BB02_STAGE1_HEADER_ALIGNMENT)
+#define IMAGE_BUF_LEN (BB02_STAGE1_HEADER_LEN + EXTRA_HEADER_LEN + BODY_LEN)
+#define INVALID_DIGEST_BYTE (0xa5u)
+#define SHA_UPDATE_COUNT (6u)
+#define SIGNED_HEADER_TAIL_OFFSET ((uint32_t)offsetof(bb02_stage1_header_t, header_len))
+
+struct sha_sync_descriptor HASH_ALGORITHM_0;
+
+static uint8_t _image[IMAGE_BUF_LEN] __attribute__((aligned(8)));
+static const uint8_t _pubkeys[BB02_STAGE1_ROOT_KEY_COUNT][64] = {{0}, {1}, {2}};
+static uint8_t _accepted_digest[BB02_STAGE1_SIGNED_DIGEST_LEN];
+static uint8_t _expected_digest[BB02_STAGE1_SIGNED_DIGEST_LEN];
+static void* _sha_ctx;
+static int32_t _sha_start_status;
+static int32_t _sha_update_status[SHA_UPDATE_COUNT];
+static int32_t _sha_finish_status;
+static bool _sha_finish_writes_digest;
+static uint8_t _sha_start_count;
+static uint8_t _sha_update_count;
+static uint8_t _sha_finish_count;
+static bool _accept_any_digest;
+static bool _assert_message_not_header_prefix;
+static uint8_t _forward_valid_mask;
+static uint8_t _reverse_valid_mask;
+static uint8_t _call_count;
+static uint8_t _call_order[BB02_STAGE1_ROOT_KEY_COUNT * 2u];
+
+/*
+Recompute these expected values with:
+
+python3 <<'PY'
+import hashlib
+
+MAGIC = b"BBS1"
+FLAGS = 0
+VERSION = 1
+PRODUCT_ID = 1 # stage0_sigcheck defines PRODUCT_BITBOX_MULTI=1.
+SIGS_LEN = 64 * 3
+BODY_LEN = 1024
+MONOTONIC_VERSION = 7
+MARKETING_VERSION = b"dev"
+MARKETING_VERSION_FIELD_LEN = 37
+
+def compute_digest(header_len):
+ image_len = header_len + BODY_LEN
+ marketing_version_size = len(MARKETING_VERSION)
+ marketing_version_field = MARKETING_VERSION + bytes(
+ MARKETING_VERSION_FIELD_LEN - marketing_version_size
+ )
+ signed_header_len = header_len - SIGS_LEN
+
+ signed_payload = b""
+ signed_payload += MAGIC
+ signed_payload += int.to_bytes(FLAGS, 4, "little")
+ signed_payload += int.to_bytes(VERSION, 2, "little")
+ signed_payload += int.to_bytes(PRODUCT_ID, 2, "little")
+ signed_payload += int.to_bytes(header_len, 4, "little")
+ signed_payload += int.to_bytes(image_len, 8, "little")
+ signed_payload += int.to_bytes(MONOTONIC_VERSION, 2, "little")
+ signed_payload += int.to_bytes(marketing_version_size, 1, "little")
+ signed_payload += marketing_version_field
+
+ reserved_len = signed_header_len - len(signed_payload)
+ reserved = bytes(
+ (0x90 + i) & 0xff
+ for i in range(reserved_len)
+ )
+ signatures = bytes((0x80 + i) & 0xff for i in range(SIGS_LEN))
+
+ signed_payload += reserved
+ assert len(signed_payload) == signed_header_len
+ body = bytes((0x40 + i) & 0xff for i in range(BODY_LEN))
+ signed_payload += body
+ assert len(signed_payload) + len(signatures) == image_len
+ return hashlib.sha256(signed_payload).hexdigest()
+
+print(compute_digest(header_len=1024))
+print(compute_digest(header_len=2048))
+PY
+*/
+static const uint8_t _digest_fixture_default[BB02_STAGE1_SIGNED_DIGEST_LEN] = {
+ 0xd4, 0xde, 0xc4, 0x9d, 0xab, 0x5f, 0x53, 0x7a, 0x3b, 0xfd, 0x3b, 0x29, 0xff, 0x34, 0xf3, 0x37,
+ 0xdf, 0x27, 0x68, 0xc3, 0x0e, 0x65, 0x15, 0x5d, 0x6d, 0xca, 0x6b, 0xb8, 0x41, 0xc6, 0xf9, 0x4f,
+};
+
+static const uint8_t _digest_fixture_extended_reserved[BB02_STAGE1_SIGNED_DIGEST_LEN] = {
+ 0x39, 0xf9, 0xc2, 0x93, 0x12, 0x55, 0xb5, 0x0a, 0x1f, 0x86, 0xc0, 0xd7, 0x5c, 0xd7, 0x7e, 0x6a,
+ 0xde, 0xda, 0x6b, 0x3f, 0x4b, 0x22, 0x05, 0xdb, 0x01, 0xda, 0x3c, 0x91, 0x53, 0x8e, 0xf4, 0x55,
+};
+
+static bb02_stage1_header_t* _header_ptr(void)
+{
+ return (bb02_stage1_header_t*)_image;
+}
+
+#define _header (*_header_ptr())
+
+static uint8_t* _body(void)
+{
+ return _image + _header.header_len;
+}
+
+static uint8_t* _signatures(void)
+{
+ return (void*)(_image + _header.header_len - BB02_STAGE1_HEADER_SIGNATURES_LEN);
+}
+
+static void _fill_signature_fixture(void)
+{
+ uint8_t* signatures = _signatures();
+ for (size_t i = 0; i < BB02_STAGE1_HEADER_SIGNATURES_LEN; i++) {
+ signatures[i] = (uint8_t)(0x80u + i);
+ }
+}
+
+static void _fill_reserved_fixture(void)
+{
+ const uint32_t signed_header_len = _header.header_len - BB02_STAGE1_HEADER_SIGNATURES_LEN;
+ const uint32_t reserved_offset = (uint32_t)offsetof(bb02_stage1_header_t, reserved);
+ for (uint32_t offset = reserved_offset; offset < signed_header_len; offset++) {
+ _image[offset] = (uint8_t)(0x90u + offset - reserved_offset);
+ }
+}
+
+static void _fill_body(void)
+{
+ for (size_t i = 0; i < BODY_LEN; i++) {
+ _body()[i] = (uint8_t)(0x40u + i);
+ }
+}
+
+static void _compute_digest(
+ const bb02_stage1_header_t* header,
+ uint8_t digest[BB02_STAGE1_SIGNED_DIGEST_LEN])
+{
+ void* ctx = rust_sha256_new();
+ assert_non_null(ctx);
+ const uint32_t expected_magic = BB02_STAGE1_HEADER_MAGIC;
+ const uint32_t flags = header->flags;
+ const uint16_t header_version = header->header_version;
+ const uint16_t expected_product_id = BB02_STAGE1_PRODUCT_ID;
+ rust_sha256_update(ctx, (const uint8_t*)&expected_magic, sizeof(expected_magic));
+ rust_sha256_update(ctx, (const uint8_t*)&flags, sizeof(flags));
+ rust_sha256_update(ctx, (const uint8_t*)&header_version, sizeof(header_version));
+ rust_sha256_update(ctx, (const uint8_t*)&expected_product_id, sizeof(expected_product_id));
+ const uint32_t signed_header_len = header->header_len - BB02_STAGE1_HEADER_SIGNATURES_LEN;
+ rust_sha256_update(
+ ctx,
+ ((const uint8_t*)header) + SIGNED_HEADER_TAIL_OFFSET,
+ signed_header_len - SIGNED_HEADER_TAIL_OFFSET);
+ rust_sha256_update(
+ ctx, ((const uint8_t*)header) + header->header_len, header->image_len - header->header_len);
+ rust_sha256_finish(&ctx, digest);
+ assert_null(ctx);
+}
+
+static void _reset_image(void)
+{
+ memset(_image, 0, sizeof(_image));
+ _header.magic = BB02_STAGE1_HEADER_MAGIC;
+ _header.header_version = BB02_STAGE1_HEADER_FORMAT_VERSION;
+ _header.product_id = BB02_STAGE1_PRODUCT_ID;
+ _header.flags = 0u;
+ _header.header_len = BB02_STAGE1_HEADER_LEN;
+ _header.image_len = BB02_STAGE1_HEADER_LEN + BODY_LEN;
+ _header.monotonic_version = 7;
+ _header.stage1_marketing_version_len = 3;
+ memcpy(_header.stage1_marketing_version, "dev", 3);
+ _fill_body();
+}
+
+static void _reset_script(uint8_t forward_valid_mask, uint8_t reverse_valid_mask)
+{
+ _compute_digest(&_header, _accepted_digest);
+ memcpy(_expected_digest, _accepted_digest, sizeof(_expected_digest));
+ _sha_ctx = NULL;
+ _sha_start_status = 0;
+ memset(_sha_update_status, 0, sizeof(_sha_update_status));
+ _sha_finish_status = 0;
+ _sha_finish_writes_digest = true;
+ _sha_start_count = 0;
+ _sha_update_count = 0;
+ _sha_finish_count = 0;
+ _accept_any_digest = false;
+ _assert_message_not_header_prefix = false;
+ _forward_valid_mask = forward_valid_mask;
+ _reverse_valid_mask = reverse_valid_mask;
+ _call_count = 0;
+ memset(_call_order, 0xff, sizeof(_call_order));
+}
+
+static void _use_digest_fixture(const uint8_t digest_fixture[static BB02_STAGE1_SIGNED_DIGEST_LEN])
+{
+ memcpy(_accepted_digest, digest_fixture, BB02_STAGE1_SIGNED_DIGEST_LEN);
+ memcpy(_expected_digest, digest_fixture, BB02_STAGE1_SIGNED_DIGEST_LEN);
+}
+
+int32_t __wrap_sha_sync_sha256_start(
+ struct sha_sync_descriptor* descr,
+ struct sha_context* ctx,
+ bool is224)
+{
+ assert_ptr_equal(descr, &HASH_ALGORITHM_0);
+ assert_non_null(ctx);
+ assert_false(is224);
+ assert_int_equal(_sha_start_count, 0);
+
+ _sha_start_count++;
+ _sha_ctx = rust_sha256_new();
+ assert_non_null(_sha_ctx);
+ return _sha_start_status;
+}
+
+int32_t __wrap_sha_sync_sha256_update(
+ struct sha_sync_descriptor* descr,
+ const uint8_t* input,
+ uint32_t length)
+{
+ assert_ptr_equal(descr, &HASH_ALGORITHM_0);
+ assert_true(_sha_update_count < SHA_UPDATE_COUNT);
+
+ switch (_sha_update_count) {
+ case 0: {
+ const uint32_t expected = BB02_STAGE1_HEADER_MAGIC;
+ assert_int_equal(length, sizeof(expected));
+ assert_memory_equal(input, &expected, sizeof(expected));
+ } break;
+ case 1: {
+ const uint32_t expected = _header.flags;
+ assert_int_equal(length, sizeof(expected));
+ assert_memory_equal(input, &expected, sizeof(expected));
+ } break;
+ case 2: {
+ const uint16_t expected = _header.header_version;
+ assert_int_equal(length, sizeof(expected));
+ assert_memory_equal(input, &expected, sizeof(expected));
+ } break;
+ case 3: {
+ const uint16_t expected = BB02_STAGE1_PRODUCT_ID;
+ assert_int_equal(length, sizeof(expected));
+ assert_memory_equal(input, &expected, sizeof(expected));
+ } break;
+ case 4:
+ assert_ptr_equal(input, ((const uint8_t*)&_header) + SIGNED_HEADER_TAIL_OFFSET);
+ assert_int_equal(
+ length,
+ _header.header_len - BB02_STAGE1_HEADER_SIGNATURES_LEN - SIGNED_HEADER_TAIL_OFFSET);
+ break;
+ default:
+ assert_int_equal(_sha_update_count, 5u);
+ assert_ptr_equal(input, _body());
+ assert_int_equal(length, BODY_LEN);
+ break;
+ }
+
+ assert_non_null(_sha_ctx);
+ rust_sha256_update(_sha_ctx, input, length);
+ const int32_t status = _sha_update_status[_sha_update_count];
+ _sha_update_count++;
+ return status;
+}
+
+int32_t __wrap_sha_sync_sha256_finish(
+ struct sha_sync_descriptor* descr,
+ uint8_t output[BB02_STAGE1_SIGNED_DIGEST_LEN])
+{
+ assert_ptr_equal(descr, &HASH_ALGORITHM_0);
+ assert_non_null(output);
+ assert_int_equal(_sha_finish_count, 0);
+ assert_int_equal(_sha_update_count, SHA_UPDATE_COUNT);
+
+ _sha_finish_count++;
+ if (_sha_finish_writes_digest) {
+ rust_sha256_finish(&_sha_ctx, output);
+ } else {
+ uint8_t discarded[BB02_STAGE1_SIGNED_DIGEST_LEN];
+ rust_sha256_finish(&_sha_ctx, discarded);
+ }
+ assert_null(_sha_ctx);
+ return _sha_finish_status;
+}
+
+static uint8_t _key_index(const uint8_t* public_key)
+{
+ for (uint8_t i = 0; i < BB02_STAGE1_ROOT_KEY_COUNT; i++) {
+ if (public_key == _pubkeys[i]) {
+ return i;
+ }
+ }
+ fail_msg("unexpected public key pointer");
+ return 0;
+}
+
+uint8_t __wrap_pukcc_ecdsa_verify(
+ const uint8_t* public_key,
+ const uint8_t* signature,
+ const uint8_t* message,
+ uint32_t message_len,
+ PUKCC_CURVE_256_X curve)
+{
+ (void)curve;
+ assert_true(_call_count < sizeof(_call_order));
+
+ const uint8_t key_idx = _key_index(public_key);
+ assert_ptr_equal(signature, &_signatures()[(uint32_t)key_idx * BB02_STAGE1_SIGNATURE_LEN]);
+ assert_int_equal(message_len, BB02_STAGE1_SIGNED_DIGEST_LEN);
+ assert_memory_equal(message, _expected_digest, sizeof(_expected_digest));
+ if (_assert_message_not_header_prefix) {
+ assert_true(memcmp(message, &_header, BB02_STAGE1_SIGNED_DIGEST_LEN) != 0);
+ }
+
+ _call_order[_call_count] = key_idx;
+ const uint8_t valid_mask =
+ _call_count < BB02_STAGE1_ROOT_KEY_COUNT ? _forward_valid_mask : _reverse_valid_mask;
+ _call_count++;
+
+ const bool digest_accepted =
+ _accept_any_digest || memcmp(message, _accepted_digest, BB02_STAGE1_SIGNED_DIGEST_LEN) == 0;
+ return digest_accepted && (valid_mask & (uint8_t)(1u << key_idx)) != 0 ? 0 : 1;
+}
+
+static void _assert_call_order(void)
+{
+ static const uint8_t expected_order[BB02_STAGE1_ROOT_KEY_COUNT * 2u] = {0, 1, 2, 2, 1, 0};
+
+ assert_int_equal(_call_count, sizeof(expected_order));
+ assert_memory_equal(_call_order, expected_order, sizeof(expected_order));
+}
+
+static void _assert_sha_calls(void)
+{
+ assert_int_equal(_sha_start_count, 1u);
+ assert_int_equal(_sha_update_count, SHA_UPDATE_COUNT);
+ assert_int_equal(_sha_finish_count, 1u);
+}
+
+static void _assert_no_crypto_calls(void)
+{
+ assert_int_equal(_sha_start_count, 0);
+ assert_int_equal(_sha_update_count, 0);
+ assert_int_equal(_sha_finish_count, 0);
+ assert_int_equal(_call_count, 0);
+}
+
+static void _assert_result(
+ uint8_t forward_valid_mask,
+ uint8_t reverse_valid_mask,
+ secbool_u32 expected)
+{
+ _reset_image();
+ _reset_script(forward_valid_mask, reverse_valid_mask);
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), expected);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_matches_default_digest_fixture(void** state)
+{
+ (void)state;
+ _reset_image();
+ _fill_reserved_fixture();
+ _fill_signature_fixture();
+ _reset_script(0x07, 0x07);
+ _use_digest_fixture(_digest_fixture_default);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), sectrue_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_matches_extended_reserved_digest_fixture(void** state)
+{
+ (void)state;
+ _reset_image();
+ _header.header_len = BB02_STAGE1_HEADER_LEN + EXTRA_HEADER_LEN;
+ _header.image_len = _header.header_len + BODY_LEN;
+ _fill_body();
+ _fill_reserved_fixture();
+ _fill_signature_fixture();
+ _reset_script(0x07, 0x07);
+ _use_digest_fixture(_digest_fixture_extended_reserved);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), sectrue_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_invalid_image_len(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.image_len = _header.header_len;
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.image_len = BB02_STAGE1_MAX_LEN + 1u;
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_invalid_header_len(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.header_len = BB02_STAGE1_HEADER_LEN - 1u;
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.header_len = BB02_STAGE1_HEADER_LEN + 512u;
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_invalid_fixed_header_fields(void** state)
+{
+ (void)state;
+
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.magic ^= 1u;
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.product_id++;
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+}
+
+static void test_stage1_sigcheck_image_ok_accepts_signed_header_version(void** state)
+{
+ (void)state;
+ _reset_image();
+ _header.header_version++;
+ _reset_script(0x07, 0x07);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), sectrue_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_zero_signatures(void** state)
+{
+ (void)state;
+ _assert_result(0x00, 0x00, secfalse_u32);
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_one_signature(void** state)
+{
+ (void)state;
+ _assert_result(0x01, 0x01, secfalse_u32);
+ _assert_result(0x02, 0x02, secfalse_u32);
+ _assert_result(0x04, 0x04, secfalse_u32);
+}
+
+static void test_stage1_sigcheck_image_ok_accepts_two_signatures(void** state)
+{
+ (void)state;
+ _assert_result(0x03, 0x03, sectrue_u32);
+ _assert_result(0x05, 0x05, sectrue_u32);
+ _assert_result(0x06, 0x06, sectrue_u32);
+}
+
+static void test_stage1_sigcheck_image_ok_accepts_three_signatures(void** state)
+{
+ (void)state;
+ _assert_result(0x07, 0x07, sectrue_u32);
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_inconsistent_passes(void** state)
+{
+ (void)state;
+ _assert_result(0x03, 0x07, secfalse_u32);
+ _assert_result(0x07, 0x03, secfalse_u32);
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_changed_body(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _body()[17] ^= 0x80u;
+ _compute_digest(&_header, _expected_digest);
+ assert_true(memcmp(_accepted_digest, _expected_digest, BB02_STAGE1_SIGNED_DIGEST_LEN) != 0);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_changed_metadata(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.monotonic_version++;
+ _compute_digest(&_header, _expected_digest);
+ assert_true(memcmp(_accepted_digest, _expected_digest, BB02_STAGE1_SIGNED_DIGEST_LEN) != 0);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_changed_header_version(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.header_version++;
+ _compute_digest(&_header, _expected_digest);
+ assert_true(memcmp(_accepted_digest, _expected_digest, BB02_STAGE1_SIGNED_DIGEST_LEN) != 0);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_accepts_signed_unknown_flags(void** state)
+{
+ (void)state;
+ _reset_image();
+ _header.flags = 0x80000000u;
+ _reset_script(0x07, 0x07);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), sectrue_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_changed_unknown_flags(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.flags = 0x80000000u;
+ _compute_digest(&_header, _expected_digest);
+ assert_true(memcmp(_accepted_digest, _expected_digest, BB02_STAGE1_SIGNED_DIGEST_LEN) != 0);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_development_flag(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _header.flags = BB02_STAGE1_FLAG_DEVELOPMENT;
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ _assert_no_crypto_calls();
+}
+
+static void test_stage1_sigcheck_image_ok_accepts_larger_header_len(void** state)
+{
+ (void)state;
+ _reset_image();
+ _header.header_len = BB02_STAGE1_HEADER_LEN + EXTRA_HEADER_LEN;
+ _header.image_len = _header.header_len + BODY_LEN;
+ _fill_body();
+ _reset_script(0x07, 0x07);
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), sectrue_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_uses_computed_digest_message(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+ _assert_message_not_header_prefix = true;
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), sectrue_u32);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+static void test_stage1_sigcheck_image_ok_rejects_sha_failure_without_stale_digest(void** state)
+{
+ (void)state;
+ _reset_image();
+ _reset_script(0x07, 0x07);
+
+ uint8_t previous_digest[BB02_STAGE1_SIGNED_DIGEST_LEN];
+ memcpy(previous_digest, _accepted_digest, sizeof(previous_digest));
+ memset(_expected_digest, INVALID_DIGEST_BYTE, sizeof(_expected_digest));
+ _sha_finish_status = -1;
+ _sha_finish_writes_digest = false;
+ _accept_any_digest = true;
+
+ assert_int_equal(stage1_sigcheck_image_ok(&_header, _pubkeys), secfalse_u32);
+ assert_true(memcmp(previous_digest, _expected_digest, BB02_STAGE1_SIGNED_DIGEST_LEN) != 0);
+ _assert_sha_calls();
+ _assert_call_order();
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_matches_default_digest_fixture),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_matches_extended_reserved_digest_fixture),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_invalid_image_len),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_invalid_header_len),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_invalid_fixed_header_fields),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_accepts_signed_header_version),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_zero_signatures),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_one_signature),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_accepts_two_signatures),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_accepts_three_signatures),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_inconsistent_passes),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_changed_body),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_changed_metadata),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_changed_header_version),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_accepts_signed_unknown_flags),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_changed_unknown_flags),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_development_flag),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_accepts_larger_header_len),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_uses_computed_digest_message),
+ cmocka_unit_test(test_stage1_sigcheck_image_ok_rejects_sha_failure_without_stale_digest),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/versions.json b/versions.json
index 67dc2cc2..9290571d 100644
--- a/versions.json
+++ b/versions.json
@@ -1,4 +1,5 @@
{
- "firmware": "v9.26.1",
- "bootloader": "v1.1.2"
+ "firmware": "v9.26.2",
+ "bootloader": "v1.2.0",
+ "stage0": 1
}
Why this scored 59/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.