mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-06-01 02:55:07 +08:00
feat(bootloader): Revive secure-boot with example, docs, and various fixes (#27237)
* feat(secure_bootloader): add ed25519 key and signing helpers
Scaffolding for PX4 secure-boot firmware signing, split into two
self-contained scripts:
- generate_signing_keys.py: produces <name>.json (private+public hex)
for use by sign_firmware.py and <name>.pub (C-array public key)
for inclusion in the bootloader build via CONFIG_PUBLIC_KEYn.
Refuses to overwrite existing private-key files.
- sign_firmware.py: pads an input .bin to a 4-byte boundary and
appends a 64-byte ed25519 signature, producing a file that drops
directly into the flash slot described by the image TOC.
Optionally appends an R&D certificate binary after the signature.
Replaces the signing path of the old Tools/cryptotools.py that was
removed along with the log-encryption cleanup; the new layout keeps
bootloader-signing tooling separate from log encryption to avoid
confusing the two independent crypto surfaces.
* fix(bootloader): panic if px4_get_secure_random is ever called
sw_crypto's crypto_open() unconditionally references
px4_get_secure_random from its XCHACHA20 path, so even a bootloader
that only performs ed25519 signature verification (and never touches
stream ciphers) pulls in an undefined symbol at link time.
The app build resolves it through nuttx_random.c, which is gated
behind CONFIG_CRYPTO_RANDOM_POOL and only compiled in when the
NuttX random pool is enabled. That config isn't on in the tiny
bootloader NuttX defconfig, and enabling it would pull in a pile
of kernel code the bootloader doesn't need.
Supply the symbol locally, but make it call up_assert() instead of
returning zeros. Silently handing out predictable bytes would be a
serious security bug if anyone later enables the XCHACHA20 path in
the bootloader without wiring up a real RNG. Aborting makes the
mistake impossible to miss.
* fix(bootloader): add PROTO_VERIFY_SIG opcode for upload-time signature check
Today a signed-boot failure is invisible to the uploader: verify_app()
runs inside jump_to_app() after PROTO_BOOT has already rebooted the
chip, so the host sees a successful upload followed by the device
silently staying in the bootloader. There is no protocol-level signal
that the image that was just written is not going to run.
Add PROTO_VERIFY_SIG (0x39) so the host can ask the bootloader to run
find_toc() + verify_app(0) *before* the reboot and get a concrete
OK / FAILED / INVALID answer back over the still-open USB connection.
The new opcode is gated on BOOTLOADER_USE_SECURITY: bootloaders built
without secure boot return cmd_bad (INSYNC/INVALID) so an uploader
can tell "I don't know this command" apart from "verification failed".
Two subtleties required care:
1. PROG_MULTI deliberately defers the very first word of the app
image to a RAM variable (first_word) and only commits it to flash
inside PROTO_BOOT, so a partial upload can never become bootable.
But verify_app() reads directly from flash, so if we verified
before committing first_word, even a valid image would always
fail (the first four bytes at APP_LOAD_ADDRESS would still be
0xffffffff). The handler therefore mirrors the first half of the
PROTO_BOOT handler: gate on STATE_ALLOWS_REBOOT, program the
deferred first word, then run the crypto check.
2. On failure we deliberately do not try to "undo" the first-word
write — H7 flash programming granularity makes it impossible to
revert in place, and the device was going to end up in the same
reject state at the next boot either way. The improvement is
purely that the uploader sees the failure before REBOOT instead
of after.
Chose opcode 0x39 to stay clear of ArduPilot's 0x28 (READ_MULTI) and
0x40 (CHIP_FULL_ERASE), which PX4 does not currently use but which
an AP-compatible uploader might.
ArduPilot also has bootloader secure boot (monocypher ed25519, like
us) but their verification runs at boot time, not upload time — so
neither project currently solves the "upload silently wrote a bad
image" problem. This puts PX4 ahead on that.
* feat(Tools): add --image_signed to mark signed firmware for uploader
px_uploader.py only needs to run signature verification over USB for
images that actually carry a signature — asking the bootloader to
verify an unsigned image would always fail, and the extra round trip
is wasted time for the common unsigned case. It therefore needs a
reliable way to tell the two apart.
We cannot tell by inspecting the bytes: a sign_firmware.py output is
just the raw .bin with 64 bytes of ed25519 signature glued on the
end, indistinguishable in content from an unsigned image of the same
padded length. The natural place to put the flag is the .px4 JSON
envelope, which already carries board_id / version / summary / etc.
Add --image_signed to px_mkfw.py, which sets "image_signed": true
in the emitted JSON. The flag has no effect on what bytes actually
end up on the device — it just tells the uploader "this blob has a
signature, please verify it before booting".
* feat(Tools): verify firmware signature before reboot
After PROG_MULTI + GET_CRC, send a new VERIFY_SIG opcode (0x39) to
the bootloader to check the ed25519 signature over the freshly
flashed image *before* sending REBOOT. This way a signature failure
is reported as a clean error from the uploader script, instead of a
silent "device stays in bootloader after reboot" that leaves the user
guessing.
The uploader always probes VERIFY_SIG. The bootloader is the source
of truth for whether secure boot is enabled:
- INSYNC/OK -> verification passed, proceed to REBOOT
- INSYNC/FAILED -> raise; "Signature does not verify against any
trusted key" if the firmware claims to be signed,
"Secure bootloader rejected an unsigned image"
otherwise (= helpful guidance for the common
misconfiguration of uploading default firmware
to a secureboot bootloader)
- INSYNC/INVALID -> bootloader has no secure boot. Quietly proceed
unless the firmware metadata says image_signed,
in which case raise.
- recv timeout -> assume a pre-VERIFY_SIG bootloader, proceed.
Surfaces a "Verifying image signature... passed" line on the upload
status path when the firmware is marked signed, so users get a clear
positive signal that the secure-boot pipeline ran end-to-end.
Also drop the redundant logger.error in the upload() loop, which was
duplicating the error message printed by the top-level handler in
main() for every UploadError path (not specific to verify_signature,
but only became obvious once these clean error messages started
firing in normal usage).
* fix(cmake): fix .px4board variant resolution to require exact match
px4_config.cmake matched the requested CONFIG against each candidate
.px4board with `MATCHES`, which is a regex partial match. For a
config like `px4_fmu-v6x_bootloader_secureboot`, the iteration over
.px4board files (alphabetical glob order) would match
`bootloader.px4board` first — because "px4_fmu-v6x_bootloader" is a
prefix of "px4_fmu-v6x_bootloader_secureboot" — and stop, silently
selecting the wrong board config.
Use `STREQUAL` instead so each candidate has to match the full
requested CONFIG. Existing single-label cases (e.g. exact match on
`px4_fmu-v2_default` or `px4_fmu-v2`) are unaffected because they
were already exact in practice; this just plugs the prefix-match
hole that any future `<label>_<suffix>` variant would trip over.
* fix(nuttx): support bootloader_<variant> labels
Boards can ship bootloader variants beyond the default `bootloader`
label — e.g. a `bootloader_secureboot` that adds crypto + keystore
Kconfig on top of the same source tree. Two pieces of build glue
were hardcoded to the exact label `bootloader` and need to relax to
match any `bootloader_*` label:
1. platforms/nuttx/CMakeLists.txt picked the bootloader linker
script and bootloader-specific library list only when the label
was exactly `bootloader`. Any other label (including
`bootloader_secureboot`) silently fell through to the app build,
producing nonsensical link flags. Match `^bootloader` instead,
and explicitly set SCRIPT_PREFIX to `bootloader_` so all
bootloader variants share the single existing linker script
regardless of their full label.
2. platforms/nuttx/cmake/px4_impl_os.cmake selects the NuttX config
subdirectory by exact label match, falling back to `nsh` when no
matching directory exists. For `bootloader_secureboot` that fell
through to `nsh`, dragging in a full app-style NuttX with cromfs,
networking, and the full heap subsystem — a 128 KB bootloader
sector overflowed by ~50 KB. Add an intermediate fallback: if the
label starts with `bootloader` and a `bootloader/` subdir exists,
use that instead of `nsh`.
Both changes are backward-compatible: existing single-label
`bootloader` builds take exactly the same path as before. They only
gain the ability for boards to add `bootloader_<suffix>.px4board`
files without duplicating bootloader build wiring.
* feat(build): auto-sign secure-boot images via BOARD_SECUREBOOT
Add a Kconfig pair that boards can opt into:
CONFIG_BOARD_SECUREBOOT -- bool: sign the .px4 with ed25519
CONFIG_BOARD_SECUREBOOT_KEY -- string: path to JSON private key
(default: Tools/test_keys/test_keys.json)
When set, the .px4 build rule inserts a Tools/secure_bootloader/sign_firmware.py
step between the unsigned .bin and px_mkfw.py, and passes --image_signed
so the .px4 envelope's metadata flags it for the uploader's VERIFY_SIG
step. The unsigned .bin is still produced alongside the .px4, so users
can sign with their own key out-of-tree if they prefer.
A BOARD_SECUREBOOT_KEY environment variable overrides the Kconfig path
at build time, mirroring the override pattern used for CONFIG_PUBLIC_KEYn
in stub_keystore. This makes release builds practical:
BOARD_SECUREBOOT_KEY=/secure/path/release.json make px4_<board>_secureboot
Relative paths in the Kconfig are resolved against the repo root (not
the build directory) so the same value works regardless of where the
build runs from.
Default builds are byte-identical to before; the new code path is gated
on CONFIG_BOARD_SECUREBOOT being set.
* feat(boards): add secureboot demo variant + docs
Two coordinated build variants demonstrate end-to-end secure boot
on px4_fmu-v6x without touching the default builds:
px4_fmu-v6x_secureboot -- the app, with TOC + signing
px4_fmu-v6x_bootloader_secureboot -- the matching secure bootloader
App side (secureboot.px4board):
- Selects nuttx-config/scripts/secureboot-script.ld via
CONFIG_BOARD_LINKER_PREFIX. The script is a copy of the default
layout plus a fixed 0x800 reservation past the vector table for
the image TOC, and an empty .signature section at end-of-FLASH
so sign_firmware.py knows where the appended ed25519 signature
will land.
- Compiles src/toc.c (a four-entry IMAGE_MAIN_TOC matching the
layout used elsewhere in the tree) into drivers_board, gated on
PX4_BOARD_LABEL == secureboot so the default build still
produces the existing layout.
- Sets CONFIG_BOARD_SECUREBOOT=y so the .px4 build rule signs the
image with the upstream test key by default.
Bootloader side (bootloader_secureboot.px4board):
- Enables CONFIG_BOARD_CRYPTO + DRIVERS_SW_CRYPTO + DRIVERS_STUB_KEYSTORE,
which links monocypher and the stub keystore into the bootloader.
- Bakes Tools/test_keys/key0.pub in as CONFIG_PUBLIC_KEY0, paired
with the test_keys.json the app variant signs with.
- hw_config.h gates BOOTLOADER_USE_SECURITY, BOOTLOADER_SIGNING_ALGORITHM
(CRYPTO_ED25519), and BOARD_IMAGE_TOC_OFFSET (0x800) on PX4_CRYPTO,
so they only activate in this variant.
Together the two variants implement the workflow:
make px4_fmu-v6x_bootloader_secureboot # build + flash via SWD once
make px4_fmu-v6x_secureboot upload # signs with test key, verifies
Bootloader fits in 128 KB (~57 KB used) thanks to --gc-sections
stripping the unused libtomcrypt code; nothing in the bootloader
binary depends on monocypher beyond the ed25519 verifier.
Replace the bundled test key for production with
Tools/secure_bootloader/generate_signing_keys.py output and update
both .px4board files (or set BOARD_SECUREBOOT_KEY at build time) —
see docs/en/advanced_config/bootloader_secure_boot.md.
* fix(boards): cap H7 bootloader linker scripts at 128 KB
Roughly half of the H7 boards in tree had `LENGTH = 2048K` for the
bootloader sector in their bootloader_script.ld, even though the
matching app linker script places APP_LOAD_ADDRESS at 0x08020000 —
i.e. the bootloader actually only owns the first 128 KB sector.
The 2048K constraint is wrong: it lets a bootloader that grew past
the 128 KB sector silently overflow into the app sector at link
time and corrupt the start of the app on flash. The linker should
fail the build instead.
The current default bootloader is ~46 KB, well under 128 KB on every
affected board, so this change is a no-op for default builds. It
just plugs a footgun for anyone adding bootloader features (secure
boot, extra UI, network boot, ...) that would have otherwise
silently grown into the app's flash range.
Boards fixed:
3dr-style H7 reference boards: cubepilot/cubeorange,
cubepilot/cubeorangeplus, holybro/durandal-v1, narinfc/h7
vendor variants: corvon/743v1, cuav/nora, cuav/x7pro,
gearup/airbrainh743, hkust/nxt-dual, hkust/nxt-v1,
matek/h743, matek/h743-mini, matek/h743-slim,
micoair/h743, micoair/h743-aio, micoair/h743-lite,
micoair/h743-v2, x-mav/ap-h743r1, x-mav/ap-h743v2
Boards already correct (LENGTH = 128K) are unchanged.
* fix(ci): add pynacl to Python requirements
Tools/secure_bootloader/sign_firmware.py uses PyNaCl for ed25519
signing, and the .px4 build rule for boards with CONFIG_BOARD_SECUREBOOT
calls it as part of the normal build (e.g. px4_fmu-v6x_secureboot).
Without pynacl in the dev requirements, those builds fail in CI and
fresh dev setups.
* docs(update): Subedit
* docs(update): Fix example error I made
* fix(docs): document BOOT and SIG1 regions
* fix(platforms): style fix
* fix(ci): workaround to get pip dependency
* fix(ci): fall back to plain pip on older build containers
The voxl2 build image ships a pip that predates --break-system-packages
(added in pip 23.0.1), so the install step blew up with "no such option".
Modern containers enforce PEP 668 and need the flag; older ones don't
support it but also don't enforce PEP 668, so plain pip works there.
Try the modern flag first, fall back if pip rejects it.
Signed-off-by: Julian Oes <julian@oes.ch>
* fix(build): strip BUILD_DIR_SUFFIX from CONFIG in cmake-build
cmake-build was passing the full build-dir name (including any
BUILD_DIR_SUFFIX like _replay or _failsafe_web) as -DCONFIG=, which
the board-lookup in cmake/px4_config.cmake then tried to match
against <vendor>_<model>_<label> .px4board files. That used to work
by accident because px4_config.cmake matched with regex MATCHES, but
since the switch to STREQUAL (needed to disambiguate
bootloader_secureboot from bootloader) the suffixed CONFIG no longer
matches anything and LABEL ends up empty, tripping a CMake error in
kconfig.cmake.
Pass the bare CONFIG and keep the suffix only on the build dir so
both concerns are independent.
Signed-off-by: Julian Oes <julian@oes.ch>
---------
Signed-off-by: Julian Oes <julian@oes.ch>
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
This commit is contained in:
@@ -201,6 +201,14 @@ jobs:
|
|||||||
ccache -s
|
ccache -s
|
||||||
ccache -z
|
ccache -z
|
||||||
|
|
||||||
|
- name: Install pynacl for secure-boot signing
|
||||||
|
# TODO: drop once the build container ships with pynacl preinstalled
|
||||||
|
# (currently pinned to v1.17.0-rc2 in Tools/ci/build_all_config.yml).
|
||||||
|
# Modern containers enforce PEP 668 and need --break-system-packages;
|
||||||
|
# older ones (e.g. voxl2) ship a pip that doesn't know the flag and
|
||||||
|
# don't need it either, so fall back to plain pip install.
|
||||||
|
run: pip install pynacl --break-system-packages || pip install pynacl
|
||||||
|
|
||||||
- name: Building Artifacts for [${{ matrix.targets }}]
|
- name: Building Artifacts for [${{ matrix.targets }}]
|
||||||
run: |
|
run: |
|
||||||
./Tools/ci/build_all_runner.sh ${{matrix.targets}} ${{matrix.arch}}
|
./Tools/ci/build_all_runner.sh ${{matrix.targets}} ${{matrix.arch}}
|
||||||
|
|||||||
@@ -143,6 +143,31 @@ config BOARD_CRYPTO
|
|||||||
help
|
help
|
||||||
Enable PX4 Crypto Support. Select the implementation under drivers
|
Enable PX4 Crypto Support. Select the implementation under drivers
|
||||||
|
|
||||||
|
config BOARD_SECUREBOOT
|
||||||
|
bool "Sign firmware image for secure boot"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Run Tools/secure_bootloader/sign_firmware.py over the built .bin
|
||||||
|
to append an ed25519 signature, and mark the resulting .px4 as
|
||||||
|
image_signed: true so the uploader runs the bootloader's
|
||||||
|
VERIFY_SIG step before reboot. The matching public key must be
|
||||||
|
baked into the bootloader via CONFIG_PUBLIC_KEY0 in the
|
||||||
|
bootloader's .px4board file.
|
||||||
|
|
||||||
|
config BOARD_SECUREBOOT_KEY
|
||||||
|
string "Path to JSON private key (absolute or relative to repo root)"
|
||||||
|
depends on BOARD_SECUREBOOT
|
||||||
|
default "Tools/test_keys/test_keys.json"
|
||||||
|
help
|
||||||
|
Private key used by sign_firmware.py to sign the firmware
|
||||||
|
image. The default points at the upstream development test
|
||||||
|
key, which is pre-paired with Tools/test_keys/key0.pub. For
|
||||||
|
production builds, generate a new keypair with
|
||||||
|
Tools/secure_bootloader/generate_signing_keys.py and update
|
||||||
|
both this option and the bootloader's CONFIG_PUBLIC_KEY0.
|
||||||
|
The environment variable BOARD_SECUREBOOT_KEY overrides this
|
||||||
|
value at build time.
|
||||||
|
|
||||||
config BOARD_PROTECTED
|
config BOARD_PROTECTED
|
||||||
bool "Memory protection"
|
bool "Memory protection"
|
||||||
help
|
help
|
||||||
|
|||||||
@@ -177,7 +177,10 @@ endif
|
|||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# describe how to build a cmake config
|
# describe how to build a cmake config
|
||||||
define cmake-build
|
define cmake-build
|
||||||
$(eval override CMAKE_ARGS += -DCONFIG=$(1))
|
# Strip BUILD_DIR_SUFFIX (e.g. _replay, _failsafe_web) from CONFIG so the
|
||||||
|
# board lookup in cmake/px4_config.cmake sees the bare <vendor>_<model>_<label>
|
||||||
|
# even when the build dir is suffixed for variant builds.
|
||||||
|
$(eval override CMAKE_ARGS += -DCONFIG=$(patsubst %$(BUILD_DIR_SUFFIX),%,$(1)))
|
||||||
@$(eval BUILD_DIR = "$(SRC_DIR)/build/$(1)")
|
@$(eval BUILD_DIR = "$(SRC_DIR)/build/$(1)")
|
||||||
@# check if the desired cmake configuration matches the cache then CMAKE_CACHE_CHECK stays empty
|
@# check if the desired cmake configuration matches the cache then CMAKE_CACHE_CHECK stays empty
|
||||||
@$(call cmake-cache-check)
|
@$(call cmake-cache-check)
|
||||||
|
|||||||
+98
-1
@@ -201,6 +201,7 @@ class BootloaderCommand(IntEnum):
|
|||||||
GET_CHIP_DES = 0x2E # rev5+, get chip description in ASCII
|
GET_CHIP_DES = 0x2E # rev5+, get chip description in ASCII
|
||||||
GET_VERSION = 0x2F # rev5+, get bootloader version in ASCII
|
GET_VERSION = 0x2F # rev5+, get bootloader version in ASCII
|
||||||
REBOOT = 0x30
|
REBOOT = 0x30
|
||||||
|
VERIFY_SIG = 0x39 # verify signature of programmed image (secure boot)
|
||||||
CHIP_FULL_ERASE = 0x40 # Full erase of flash, rev6+
|
CHIP_FULL_ERASE = 0x40 # Full erase of flash, rev6+
|
||||||
|
|
||||||
|
|
||||||
@@ -294,6 +295,7 @@ class Firmware:
|
|||||||
image: bytes = field(init=False)
|
image: bytes = field(init=False)
|
||||||
image_size: int = field(init=False)
|
image_size: int = field(init=False)
|
||||||
image_maxsize: int = field(init=False)
|
image_maxsize: int = field(init=False)
|
||||||
|
image_signed: bool = field(init=False)
|
||||||
description: dict = field(init=False)
|
description: dict = field(init=False)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
@@ -331,6 +333,7 @@ class Firmware:
|
|||||||
self.board_revision = self.description.get("board_revision", 0)
|
self.board_revision = self.description.get("board_revision", 0)
|
||||||
self.image_size = self.description["image_size"]
|
self.image_size = self.description["image_size"]
|
||||||
self.image_maxsize = self.description["image_maxsize"]
|
self.image_maxsize = self.description["image_maxsize"]
|
||||||
|
self.image_signed = bool(self.description.get("image_signed", False))
|
||||||
|
|
||||||
# Decompress image
|
# Decompress image
|
||||||
try:
|
try:
|
||||||
@@ -1165,6 +1168,81 @@ class BootloaderProtocol:
|
|||||||
else:
|
else:
|
||||||
self.verify_read(firmware, progress_callback)
|
self.verify_read(firmware, progress_callback)
|
||||||
|
|
||||||
|
def verify_signature(self, image_signed: bool) -> None:
|
||||||
|
"""Ask the bootloader to verify the signature of the programmed image.
|
||||||
|
|
||||||
|
Always call this — the bootloader is the source of truth for whether
|
||||||
|
signature verification is required, not the host. If the bootloader
|
||||||
|
was built without BOOTLOADER_USE_SECURITY it returns INVALID and
|
||||||
|
we proceed normally; if it does verify, we surface a clean error
|
||||||
|
before REBOOT instead of letting the device silently stay in BL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_signed: True if the firmware metadata claimed the image
|
||||||
|
is signed. Used only to upgrade the "bootloader doesn't
|
||||||
|
support secure boot" warning into an error: a signed image
|
||||||
|
being uploaded to a non-secure bootloader is a config
|
||||||
|
mismatch worth flagging loudly.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ProtocolError: If the bootloader reports the signature is bad,
|
||||||
|
or if a signed image was sent to a non-secure bootloader.
|
||||||
|
"""
|
||||||
|
logger.info("Verifying image signature")
|
||||||
|
self._send_command(BootloaderCommand.VERIFY_SIG)
|
||||||
|
self.transport.flush()
|
||||||
|
|
||||||
|
# ed25519 verification over a multi-megabyte image runs a full
|
||||||
|
# SHA-512 over every byte in flash, which on a slow-clocked H7
|
||||||
|
# bootloader with monocypher can comfortably exceed the default
|
||||||
|
# serial timeout. Give the response a generous window.
|
||||||
|
try:
|
||||||
|
insync = self.transport.recv(1, timeout=3.0)
|
||||||
|
except TimeoutError:
|
||||||
|
# Old bootloader from before VERIFY_SIG existed: no response,
|
||||||
|
# just silently dropped the byte. Treat as "no secure boot
|
||||||
|
# support" and continue. The downside is that a signed image
|
||||||
|
# going to a really old bootloader won't be caught here.
|
||||||
|
logger.debug("VERIFY_SIG timed out; assuming pre-secureboot bootloader")
|
||||||
|
return
|
||||||
|
|
||||||
|
if insync[0] != BootloaderResponse.INSYNC:
|
||||||
|
raise ProtocolError(
|
||||||
|
f"Expected INSYNC (0x{BootloaderResponse.INSYNC:02X}), "
|
||||||
|
f"got 0x{insync[0]:02X}",
|
||||||
|
port=self.transport.port_name,
|
||||||
|
operation="verify_signature",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.transport.recv(1)
|
||||||
|
if result[0] == BootloaderResponse.OK:
|
||||||
|
logger.info("Signature verification passed")
|
||||||
|
return
|
||||||
|
if result[0] == BootloaderResponse.FAILED:
|
||||||
|
if image_signed:
|
||||||
|
msg = ("Signature does not verify against any trusted key "
|
||||||
|
"in the bootloader.")
|
||||||
|
else:
|
||||||
|
msg = ("Secure bootloader rejected an unsigned image. "
|
||||||
|
"Build with CONFIG_BOARD_SECUREBOOT or flash a "
|
||||||
|
"non-secure bootloader.")
|
||||||
|
raise ProtocolError(msg, port=self.transport.port_name)
|
||||||
|
if result[0] == BootloaderResponse.INVALID:
|
||||||
|
if image_signed:
|
||||||
|
raise ProtocolError(
|
||||||
|
"Image is marked signed but the bootloader has no "
|
||||||
|
"secure boot support.",
|
||||||
|
port=self.transport.port_name,
|
||||||
|
)
|
||||||
|
logger.debug("Bootloader has no secure boot; skipping verification")
|
||||||
|
return
|
||||||
|
|
||||||
|
raise ProtocolError(
|
||||||
|
f"Unexpected VERIFY_SIG response: 0x{result[0]:02X}",
|
||||||
|
port=self.transport.port_name,
|
||||||
|
operation="verify_signature",
|
||||||
|
)
|
||||||
|
|
||||||
def reboot(self) -> None:
|
def reboot(self) -> None:
|
||||||
"""Reboot into the application.
|
"""Reboot into the application.
|
||||||
|
|
||||||
@@ -1678,7 +1756,10 @@ class Uploader:
|
|||||||
last_error = e
|
last_error = e
|
||||||
continue
|
continue
|
||||||
except UploadError as e:
|
except UploadError as e:
|
||||||
logger.error(f"Upload failed on {port}: {e}")
|
# Don't log here: we re-raise and the top-level handler
|
||||||
|
# in main() will print the error message. Logging here
|
||||||
|
# too produces duplicate user-facing output.
|
||||||
|
logger.debug(f"Upload failed on {port}: {e}")
|
||||||
last_error = e
|
last_error = e
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -1928,6 +2009,22 @@ class Uploader:
|
|||||||
# Verify
|
# Verify
|
||||||
protocol.verify(firmware, progress_callback=progress.update_verify)
|
protocol.verify(firmware, progress_callback=progress.update_verify)
|
||||||
|
|
||||||
|
# Always ask the bootloader to verify the signature before reboot.
|
||||||
|
# The bootloader is the source of truth for whether secure boot
|
||||||
|
# is enabled — non-secure bootloaders return INVALID and we just
|
||||||
|
# proceed. Reporting a bad signature here is much more actionable
|
||||||
|
# than letting the device silently stay in the bootloader.
|
||||||
|
# The progress bar above uses carriage-return overwrites and
|
||||||
|
# leaves the cursor on the same line; drop to a fresh line so
|
||||||
|
# any verify-step output doesn't clobber the progress bar.
|
||||||
|
if not self.config.json_output:
|
||||||
|
print()
|
||||||
|
if firmware.image_signed:
|
||||||
|
print("Verifying image signature...", end="", flush=True)
|
||||||
|
protocol.verify_signature(firmware.image_signed)
|
||||||
|
if not self.config.json_output and firmware.image_signed:
|
||||||
|
print(" passed")
|
||||||
|
|
||||||
# Reboot and show summary
|
# Reboot and show summary
|
||||||
protocol.reboot()
|
protocol.reboot()
|
||||||
progress.finish()
|
progress.finish()
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ parser.add_argument("--git_identity", action="store", help="the working director
|
|||||||
parser.add_argument("--parameter_xml", action="store", help="the parameters.xml file")
|
parser.add_argument("--parameter_xml", action="store", help="the parameters.xml file")
|
||||||
parser.add_argument("--airframe_xml", action="store", help="the airframes.xml file")
|
parser.add_argument("--airframe_xml", action="store", help="the airframes.xml file")
|
||||||
parser.add_argument("--image", action="store", help="the firmware image")
|
parser.add_argument("--image", action="store", help="the firmware image")
|
||||||
|
parser.add_argument("--image_signed", action="store_true", help="mark the image as signed for secure-boot verification by the uploader")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Fetch the firmware descriptor prototype if specified
|
# Fetch the firmware descriptor prototype if specified
|
||||||
@@ -123,5 +124,7 @@ if args.image != None:
|
|||||||
bytes = f.read()
|
bytes = f.read()
|
||||||
desc['image_size'] = len(bytes)
|
desc['image_size'] = len(bytes)
|
||||||
desc['image'] = base64.b64encode(zlib.compress(bytes,9)).decode('utf-8')
|
desc['image'] = base64.b64encode(zlib.compress(bytes,9)).decode('utf-8')
|
||||||
|
if args.image_signed:
|
||||||
|
desc['image_signed'] = True
|
||||||
|
|
||||||
print(json.dumps(desc, indent=4))
|
print(json.dumps(desc, indent=4))
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate an ed25519 keypair for PX4 secure bootloader firmware signing.
|
||||||
|
|
||||||
|
Produces two files next to each other:
|
||||||
|
<name>.json - private + public key, used by sign_firmware.py
|
||||||
|
<name>.pub - public key as a C array, included in the bootloader build
|
||||||
|
via CONFIG_PUBLIC_KEY0..3
|
||||||
|
|
||||||
|
The .json file contains the private key. Do not publish it. Do not lose it:
|
||||||
|
without it you cannot sign new firmware images for devices that already
|
||||||
|
trust its public key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import binascii
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import nacl.encoding
|
||||||
|
import nacl.signing
|
||||||
|
|
||||||
|
|
||||||
|
def write_public_key_header(signing_key, key_name):
|
||||||
|
raw = signing_key.verify_key.encode(encoder=nacl.encoding.RawEncoder)
|
||||||
|
lines = []
|
||||||
|
for i in range(0, len(raw), 8):
|
||||||
|
chunk = ", ".join(f"0x{b:02x}" for b in raw[i:i + 8])
|
||||||
|
lines.append(chunk + ",")
|
||||||
|
body = "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
with open(key_name + ".pub", "w") as f:
|
||||||
|
f.write("// Public key to verify signed binaries\n")
|
||||||
|
f.write(body)
|
||||||
|
|
||||||
|
|
||||||
|
def write_key_json(signing_key, key_name):
|
||||||
|
path = Path(key_name + ".json")
|
||||||
|
if path.exists():
|
||||||
|
print(f"ERROR: {path} already exists. Refusing to overwrite.")
|
||||||
|
print("Remove the file and run again if you really want a new key.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
keys = {
|
||||||
|
"date": time.asctime(),
|
||||||
|
"public": signing_key.verify_key.encode(encoder=nacl.encoding.HexEncoder).decode(),
|
||||||
|
"private": binascii.hexlify(signing_key._seed).decode(),
|
||||||
|
}
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(keys, f)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate an ed25519 keypair for PX4 firmware signing.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"name",
|
||||||
|
help="Output base name. Writes <name>.json and <name>.pub.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
signing_key = nacl.signing.SigningKey.generate()
|
||||||
|
write_key_json(signing_key, args.name)
|
||||||
|
write_public_key_header(signing_key, args.name)
|
||||||
|
|
||||||
|
print(f"Wrote {args.name}.json (private + public)")
|
||||||
|
print(f"Wrote {args.name}.pub (public, C array for bootloader build)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Sign a PX4 firmware .bin with an ed25519 key for the secure bootloader.
|
||||||
|
|
||||||
|
The output binary is the input padded to a 4-byte boundary with 0xff,
|
||||||
|
followed by a 64-byte signature, followed (optionally) by an R&D
|
||||||
|
certificate appended verbatim.
|
||||||
|
|
||||||
|
The signed image lives in the flash slot the bootloader expects to find
|
||||||
|
the main app at, so the layout looks like:
|
||||||
|
|
||||||
|
[ vectors / .text / .data ... ][ pad to 4B ][ 64B signature ][ rdct? ]
|
||||||
|
|
||||||
|
The bootloader walks the image TOC, finds the BOOT entry's signature_idx
|
||||||
|
slot, and verifies it against a key stored in the bootloader's keystore.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import binascii
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import nacl.encoding
|
||||||
|
import nacl.signing
|
||||||
|
|
||||||
|
|
||||||
|
def ed25519_sign(private_key_hex, signee_bin):
|
||||||
|
signing_key = nacl.signing.SigningKey(private_key_hex, encoder=nacl.encoding.HexEncoder)
|
||||||
|
signed = signing_key.sign(signee_bin, encoder=nacl.encoding.RawEncoder)
|
||||||
|
public_key = signing_key.verify_key.encode(encoder=nacl.encoding.RawEncoder)
|
||||||
|
return signed.signature, public_key
|
||||||
|
|
||||||
|
|
||||||
|
def sign(bin_file_path, key_file_path):
|
||||||
|
with open(bin_file_path, mode="rb") as f:
|
||||||
|
signee_bin = f.read()
|
||||||
|
|
||||||
|
if len(signee_bin) % 4 != 0:
|
||||||
|
signee_bin += bytearray(b"\xff") * (4 - len(signee_bin) % 4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(key_file_path, mode="r") as f:
|
||||||
|
keys = json.load(f)
|
||||||
|
except OSError:
|
||||||
|
print(f"ERROR: Key file {key_file_path} not found")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
signature, public_key = ed25519_sign(keys["private"], signee_bin)
|
||||||
|
assert len(signature) == 64
|
||||||
|
|
||||||
|
print(f'Binary "{bin_file_path}" signed.')
|
||||||
|
print("Signature:", binascii.hexlify(signature).decode())
|
||||||
|
print("Public key:", binascii.hexlify(public_key).decode())
|
||||||
|
|
||||||
|
return signee_bin + signature
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Sign a PX4 .bin firmware image with an ed25519 key.",
|
||||||
|
)
|
||||||
|
parser.add_argument("signee", help="Input .bin to sign")
|
||||||
|
parser.add_argument("signed", help="Output signed .bin")
|
||||||
|
parser.add_argument(
|
||||||
|
"--key",
|
||||||
|
default="Tools/test_keys/test_keys.json",
|
||||||
|
help="Key file produced by generate_signing_keys.py",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--rdct",
|
||||||
|
default=None,
|
||||||
|
help="Optional R&D certificate binary, appended after signature",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.key == "Tools/test_keys/test_keys.json":
|
||||||
|
print("WARNING: Signing with PX4 test key — do not use in production")
|
||||||
|
|
||||||
|
signed_bytes = sign(args.signee, args.key)
|
||||||
|
|
||||||
|
with open(args.signed, mode="wb") as f:
|
||||||
|
f.write(signed_bytes)
|
||||||
|
|
||||||
|
if args.rdct is not None:
|
||||||
|
with open(args.rdct, mode="rb") as rdct_in, open(args.signed, mode="ab") as out:
|
||||||
|
out.write(rdct_in.read())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -18,6 +18,7 @@ psutil
|
|||||||
pycryptodome
|
pycryptodome
|
||||||
pygments
|
pygments
|
||||||
pymavlink
|
pymavlink
|
||||||
|
pynacl
|
||||||
pyros-genmsg
|
pyros-genmsg
|
||||||
pyserial
|
pyserial
|
||||||
pyulog>=0.5.0
|
pyulog>=0.5.0
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
DTCM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
DTCM2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
CONFIG_BOARD_TOOLCHAIN="arm-none-eabi"
|
||||||
|
CONFIG_BOARD_ARCHITECTURE="cortex-m7"
|
||||||
|
CONFIG_BOARD_ROMFSROOT=""
|
||||||
|
CONFIG_BOARD_CRYPTO=y
|
||||||
|
CONFIG_DRIVERS_SW_CRYPTO=y
|
||||||
|
CONFIG_DRIVERS_STUB_KEYSTORE=y
|
||||||
|
CONFIG_PUBLIC_KEY0="../../../Tools/test_keys/key0.pub"
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* scripts/secureboot-script.ld
|
||||||
|
*
|
||||||
|
* Linker script for the px4_fmu-v6x_secureboot variant.
|
||||||
|
*
|
||||||
|
* Same layout as script.ld, except:
|
||||||
|
* - The image table-of-contents (.main_toc, defined by src/toc.c)
|
||||||
|
* is placed at a fixed offset past the vector table so the
|
||||||
|
* bootloader can find it via BOARD_IMAGE_TOC_OFFSET (0x800).
|
||||||
|
* - An empty .signature output section reserves the symbol
|
||||||
|
* _boot_signature at the end of the FLASH content. The TOC
|
||||||
|
* references it as the signed-image end address; sign_firmware.py
|
||||||
|
* appends the 64-byte ed25519 signature there.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
ITCM_RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
|
FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 1920K
|
||||||
|
|
||||||
|
DTCM1_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
|
DTCM2_RAM (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
|
AXI_SRAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512K /* D1 domain AXI bus */
|
||||||
|
SRAM1 (rwx) : ORIGIN = 0x30000000, LENGTH = 128K /* D2 domain AHB bus */
|
||||||
|
SRAM2 (rwx) : ORIGIN = 0x30020000, LENGTH = 128K /* D2 domain AHB bus */
|
||||||
|
SRAM3 (rwx) : ORIGIN = 0x30040000, LENGTH = 32K /* D2 domain AHB bus */
|
||||||
|
SRAM4 (rwx) : ORIGIN = 0x38000000, LENGTH = 64K /* D3 domain */
|
||||||
|
BKPRAM (rwx) : ORIGIN = 0x38800000, LENGTH = 4K
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT_ARCH(arm)
|
||||||
|
EXTERN(_vectors)
|
||||||
|
ENTRY(_stext)
|
||||||
|
|
||||||
|
EXTERN(abort)
|
||||||
|
EXTERN(board_get_manifest)
|
||||||
|
EXTERN(_main_toc)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text : {
|
||||||
|
_stext = ABSOLUTE(.);
|
||||||
|
*(.vectors)
|
||||||
|
/* Place the image TOC at a fixed offset past the vector table
|
||||||
|
* so the bootloader can find it via BOARD_IMAGE_TOC_OFFSET.
|
||||||
|
* 0x800 leaves comfortable room past the STM32H7 vector table
|
||||||
|
* (~0x298 bytes) and lands on a clean 2 KiB boundary.
|
||||||
|
*/
|
||||||
|
. = ABSOLUTE(ORIGIN(FLASH) + 0x800);
|
||||||
|
KEEP(*(.main_toc))
|
||||||
|
*(.text .text.*)
|
||||||
|
*(.fixup)
|
||||||
|
*(.gnu.warning)
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
*(.gnu.linkonce.t.*)
|
||||||
|
*(.glue_7)
|
||||||
|
*(.glue_7t)
|
||||||
|
*(.got)
|
||||||
|
*(.gcc_except_table)
|
||||||
|
*(.gnu.linkonce.r.*)
|
||||||
|
_etext = ABSOLUTE(.);
|
||||||
|
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
.init_section : {
|
||||||
|
_sinit = ABSOLUTE(.);
|
||||||
|
KEEP(*(.init_array .init_array.*))
|
||||||
|
_einit = ABSOLUTE(.);
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
|
||||||
|
.ARM.extab : {
|
||||||
|
*(.ARM.extab*)
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
__exidx_start = ABSOLUTE(.);
|
||||||
|
.ARM.exidx : {
|
||||||
|
*(.ARM.exidx*)
|
||||||
|
} > FLASH
|
||||||
|
__exidx_end = ABSOLUTE(.);
|
||||||
|
|
||||||
|
_eronly = ABSOLUTE(.);
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
_sdata = ABSOLUTE(.);
|
||||||
|
*(.data .data.*)
|
||||||
|
*(.gnu.linkonce.d.*)
|
||||||
|
CONSTRUCTORS
|
||||||
|
_edata = ABSOLUTE(.);
|
||||||
|
|
||||||
|
/* Pad out last section as the STM32H7 Flash write size is 256 bits. 32 bytes */
|
||||||
|
. = ALIGN(16);
|
||||||
|
FILL(0xffff)
|
||||||
|
. += 16;
|
||||||
|
} > AXI_SRAM AT > FLASH = 0xffff
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
_sbss = ABSOLUTE(.);
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(.gnu.linkonce.b.*)
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_ebss = ABSOLUTE(.);
|
||||||
|
} > AXI_SRAM
|
||||||
|
|
||||||
|
.sram4_reserve (NOLOAD) :
|
||||||
|
{
|
||||||
|
*(.sram4)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_sram4_heap_start = ABSOLUTE(.);
|
||||||
|
} > SRAM4
|
||||||
|
|
||||||
|
/* Marker for the end of the signed image. sign_firmware.py
|
||||||
|
* appends the 64-byte ed25519 signature at this address; the TOC's
|
||||||
|
* SIG1 entry references the same range so the bootloader hashes
|
||||||
|
* exactly the bytes that were signed.
|
||||||
|
*/
|
||||||
|
.signature : {
|
||||||
|
_boot_signature = ALIGN(4);
|
||||||
|
} > FLASH
|
||||||
|
|
||||||
|
/* Stabs debugging sections. */
|
||||||
|
.stab 0 : { *(.stab) }
|
||||||
|
.stabstr 0 : { *(.stabstr) }
|
||||||
|
.stab.excl 0 : { *(.stab.excl) }
|
||||||
|
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||||
|
.stab.index 0 : { *(.stab.index) }
|
||||||
|
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||||
|
.comment 0 : { *(.comment) }
|
||||||
|
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||||
|
.debug_info 0 : { *(.debug_info) }
|
||||||
|
.debug_line 0 : { *(.debug_line) }
|
||||||
|
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||||
|
.debug_aranges 0 : { *(.debug_aranges) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Secure-boot variant of the fmu-v6x app.
|
||||||
|
#
|
||||||
|
# Inherits everything from default.px4board, then:
|
||||||
|
# - selects nuttx-config/scripts/secureboot-script.ld as the linker
|
||||||
|
# script (via CONFIG_BOARD_LINKER_PREFIX, same mechanism
|
||||||
|
# flash-analysis.px4board uses), which reserves the image TOC
|
||||||
|
# slot at FLASH+0x800 and a .signature section at end-of-FLASH.
|
||||||
|
# - the board CMakeLists.txt detects this label and adds src/toc.c
|
||||||
|
# to drivers_board so the TOC is actually populated.
|
||||||
|
#
|
||||||
|
# The output .bin is meant to be signed by Tools/secure_bootloader/sign_firmware.py
|
||||||
|
# and uploaded to a board running the px4_fmu-v6x_bootloader_secureboot bootloader.
|
||||||
|
|
||||||
|
CONFIG_BOARD_LINKER_PREFIX="secureboot"
|
||||||
|
CONFIG_BOARD_SECUREBOOT=y
|
||||||
|
# CONFIG_BOARD_SECUREBOOT_KEY defaults to Tools/test_keys/test_keys.json,
|
||||||
|
# which is pre-paired with Tools/test_keys/key0.pub baked into the
|
||||||
|
# matching bootloader_secureboot.px4board build. Set the env var
|
||||||
|
# BOARD_SECUREBOOT_KEY to a custom JSON private key path to override
|
||||||
|
# without editing this file.
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
############################################################################
|
############################################################################
|
||||||
if("${PX4_BOARD_LABEL}" STREQUAL "bootloader")
|
if("${PX4_BOARD_LABEL}" MATCHES "^bootloader")
|
||||||
add_compile_definitions(BOOTLOADER)
|
add_compile_definitions(BOOTLOADER)
|
||||||
add_library(drivers_board
|
add_library(drivers_board
|
||||||
bootloader_main.c
|
bootloader_main.c
|
||||||
@@ -49,7 +49,7 @@ if("${PX4_BOARD_LABEL}" STREQUAL "bootloader")
|
|||||||
target_include_directories(drivers_board PRIVATE ${PX4_SOURCE_DIR}/platforms/nuttx/src/bootloader/common)
|
target_include_directories(drivers_board PRIVATE ${PX4_SOURCE_DIR}/platforms/nuttx/src/bootloader/common)
|
||||||
|
|
||||||
else()
|
else()
|
||||||
add_library(drivers_board
|
set(_drivers_board_sources
|
||||||
can.c
|
can.c
|
||||||
i2c.cpp
|
i2c.cpp
|
||||||
init.cpp
|
init.cpp
|
||||||
@@ -60,6 +60,14 @@ else()
|
|||||||
timer_config.cpp
|
timer_config.cpp
|
||||||
usb.c
|
usb.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Secure-boot variant: populate the image TOC slot the linker
|
||||||
|
# script (selected via CONFIG_BOARD_LINKER_PREFIX) reserves.
|
||||||
|
if("${PX4_BOARD_LABEL}" STREQUAL "secureboot")
|
||||||
|
list(APPEND _drivers_board_sources toc.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(drivers_board ${_drivers_board_sources})
|
||||||
add_dependencies(drivers_board arch_board_hw_info)
|
add_dependencies(drivers_board arch_board_hw_info)
|
||||||
|
|
||||||
target_link_libraries(drivers_board
|
target_link_libraries(drivers_board
|
||||||
|
|||||||
@@ -122,4 +122,17 @@
|
|||||||
# define BOOT_DEVICES_FILTER_ONUSB USB0_DEV|SERIAL0_DEV|SERIAL1_DEV
|
# define BOOT_DEVICES_FILTER_ONUSB USB0_DEV|SERIAL0_DEV|SERIAL1_DEV
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Secure-boot variant: when the bootloader build pulls in crypto
|
||||||
|
* support (CONFIG_BOARD_CRYPTO=y -> PX4_CRYPTO), enable signature
|
||||||
|
* verification in the bootloader and tell it where to find the TOC
|
||||||
|
* the app linker reserves. The matching app variant
|
||||||
|
* (px4_fmu-v6x_secureboot) places the TOC at the same offset.
|
||||||
|
*/
|
||||||
|
#if defined(PX4_CRYPTO)
|
||||||
|
#include <px4_platform_common/crypto_algorithms.h>
|
||||||
|
#define BOOTLOADER_USE_SECURITY 1
|
||||||
|
#define BOOTLOADER_SIGNING_ALGORITHM CRYPTO_ED25519
|
||||||
|
#define BOARD_IMAGE_TOC_OFFSET 0x800
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* HW_CONFIG_H_ */
|
#endif /* HW_CONFIG_H_ */
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 PX4 Development Team. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in
|
||||||
|
* the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
* 3. Neither the name PX4 nor the names of its contributors may be
|
||||||
|
* used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||||
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||||
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
#include <image_toc.h>
|
||||||
|
|
||||||
|
/* (Maximum) size of the signature */
|
||||||
|
#define SIGNATURE_SIZE 64
|
||||||
|
|
||||||
|
extern uint32_t _vectors[];
|
||||||
|
extern const int *_boot_signature;
|
||||||
|
|
||||||
|
#define BOOT_ADDR _vectors
|
||||||
|
#define BOOT_END ((const void *)&_boot_signature)
|
||||||
|
|
||||||
|
#define BOOTSIG_ADDR ((const void *)&_boot_signature)
|
||||||
|
#define BOOTSIG_END ((const void *)((const uint8_t *)BOOTSIG_ADDR + SIGNATURE_SIZE))
|
||||||
|
|
||||||
|
#define RDCT_ADDR BOOTSIG_END
|
||||||
|
#define RDCT_END ((const void *)((const uint8_t *)BOOTSIG_END + sizeof(image_cert_t)))
|
||||||
|
|
||||||
|
#define RDCTSIG_ADDR RDCT_END
|
||||||
|
#define RDCTSIG_END ((const void *)((const uint8_t *)RDCTSIG_ADDR + SIGNATURE_SIZE))
|
||||||
|
|
||||||
|
IMAGE_MAIN_TOC(4) = {
|
||||||
|
{TOC_START_MAGIC, TOC_VERSION},
|
||||||
|
{
|
||||||
|
{"BOOT", BOOT_ADDR, BOOT_END, 0, 1, 0, 0, TOC_FLAG1_BOOT | TOC_FLAG1_CHECK_SIGNATURE},
|
||||||
|
{"SIG1", BOOTSIG_ADDR, BOOTSIG_END, 0, 0, 0, 0, 0},
|
||||||
|
{"RDCT", RDCT_ADDR, RDCT_END, 0, 3, 0, 0, TOC_FLAG1_RDCT | TOC_FLAG1_CHECK_SIGNATURE},
|
||||||
|
{"RDSG", RDCTSIG_ADDR, RDCTSIG_END, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
TOC_END_MAGIC
|
||||||
|
};
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
itcm (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 128K
|
||||||
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
dtcm1 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||||
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
dtcm2 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
|
||||||
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
sram (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ if(NOT PX4_CONFIG_FILE)
|
|||||||
|
|
||||||
# <VENDOR>_<MODEL>_<LABEL> (eg px4_fmu-v2_default)
|
# <VENDOR>_<MODEL>_<LABEL> (eg px4_fmu-v2_default)
|
||||||
# <VENDOR>_<MODEL>_default (eg px4_fmu-v2) # allow skipping label if "default"
|
# <VENDOR>_<MODEL>_default (eg px4_fmu-v2) # allow skipping label if "default"
|
||||||
if ((${CONFIG} MATCHES "${vendor}_${model}_${label}") OR # match full vendor, model, label
|
if ((${CONFIG} STREQUAL "${vendor}_${model}_${label}") OR # match full vendor, model, label
|
||||||
((${label} STREQUAL "default") AND (${CONFIG} STREQUAL "${vendor}_${model}")) # default label can be omitted
|
((${label} STREQUAL "default") AND (${CONFIG} STREQUAL "${vendor}_${model}")) # default label can be omitted
|
||||||
)
|
)
|
||||||
set(PX4_CONFIG_FILE "${PX4_SOURCE_DIR}/boards/${filename}" CACHE FILEPATH "path to PX4 CONFIG file" FORCE)
|
set(PX4_CONFIG_FILE "${PX4_SOURCE_DIR}/boards/${filename}" CACHE FILEPATH "path to PX4 CONFIG file" FORCE)
|
||||||
@@ -84,7 +84,7 @@ if(NOT PX4_CONFIG_FILE)
|
|||||||
|
|
||||||
# <BOARD>_<LABEL> (eg px4_fmu-v2_default)
|
# <BOARD>_<LABEL> (eg px4_fmu-v2_default)
|
||||||
# <BOARD>_default (eg px4_fmu-v2) # allow skipping label if "default"
|
# <BOARD>_default (eg px4_fmu-v2) # allow skipping label if "default"
|
||||||
if ((${CONFIG} MATCHES "${board}_${label}") OR # match full board, label
|
if ((${CONFIG} STREQUAL "${board}_${label}") OR # match full board, label
|
||||||
((${label} STREQUAL "default") AND (${CONFIG} STREQUAL "${board}")) # default label can be omitted
|
((${label} STREQUAL "default") AND (${CONFIG} STREQUAL "${board}")) # default label can be omitted
|
||||||
)
|
)
|
||||||
set(PX4_CONFIG_FILE "${PX4_SOURCE_DIR}/boards/${filename}" CACHE FILEPATH "path to PX4 CONFIG file" FORCE)
|
set(PX4_CONFIG_FILE "${PX4_SOURCE_DIR}/boards/${filename}" CACHE FILEPATH "path to PX4 CONFIG file" FORCE)
|
||||||
|
|||||||
@@ -409,6 +409,7 @@
|
|||||||
- [PX4 Ethernet Setup](advanced_config/ethernet_setup.md)
|
- [PX4 Ethernet Setup](advanced_config/ethernet_setup.md)
|
||||||
- [Standard Configuration](config/index.md)
|
- [Standard Configuration](config/index.md)
|
||||||
- [OEM Configuration](advanced_config/oem.md)
|
- [OEM Configuration](advanced_config/oem.md)
|
||||||
|
- [Bootloader Secure Boot](advanced_config/bootloader_secure_boot.md)
|
||||||
- [Advanced Configuration](advanced_config/index.md)
|
- [Advanced Configuration](advanced_config/index.md)
|
||||||
- [Using PX4's Navigation Filter (EKF2)](advanced_config/tuning_the_ecl_ekf.md)
|
- [Using PX4's Navigation Filter (EKF2)](advanced_config/tuning_the_ecl_ekf.md)
|
||||||
- [GNSS-Denied & Degraded Flight](advanced_config/gnss_degraded_or_denied_flight.md)
|
- [GNSS-Denied & Degraded Flight](advanced_config/gnss_degraded_or_denied_flight.md)
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# Bootloader Secure Boot
|
||||||
|
|
||||||
|
Secure boot is a feature that ensures that only cryptographically authorized PX4 firmware is executed.
|
||||||
|
This is used by OEMs to ensure that only their validated, tested firmware runs on the vehicle — protecting safety, brand integrity, and regulatory compliance, and stopping others from running unauthorized software on their hardware.
|
||||||
|
|
||||||
|
The _PX4 Bootloader_ can verify a cryptographic signature over the PX4 firmware before it is run.
|
||||||
|
When enabled, only a firmware image signed with a private key whose matching public key is baked into the bootloader will boot.
|
||||||
|
For any unsigned image, tampered image, or image signed by the wrong key, the device will wait in the bootloader screen (where it can be recovered safely over USB), and will not start the rest of the PX4 flight stack.
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
This feature is intended for OEMs.
|
||||||
|
|
||||||
|
If you flash a bootloader that trusts a public key whose private counterpart you have lost, you can no longer sign new firmware for that device and will need a debug probe to recover it.
|
||||||
|
Keep private keys backed up and never commit them to source control.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
PX4's secure boot uses [ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) signatures (via [monocypher](https://monocypher.org)).
|
||||||
|
The signing key is 32 bytes and the resulting signature is 64 bytes.
|
||||||
|
Verification is fast (no random number generator is required on the device), and the implementation is small enough to fit in a 128 KB bootloader sector alongside the rest of the bootloader.
|
||||||
|
|
||||||
|
A signed firmware image lays out in flash like this:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
+---------------------------+ APP_LOAD_ADDRESS ─┐
|
||||||
|
| Vector table | │
|
||||||
|
+---------------------------+ APP_LOAD_ADDRESS │
|
||||||
|
| Image TOC | + BOARD_IMAGE_TOC_OFFSET │ BOOT region
|
||||||
|
+---------------------------+ │ (hashed and signed)
|
||||||
|
| .text / .rodata / .data | │
|
||||||
|
+---------------------------+ &_boot_signature ─┤
|
||||||
|
| 64-byte ed25519 signature | │ SIG1
|
||||||
|
+---------------------------+ ─┘
|
||||||
|
```
|
||||||
|
|
||||||
|
The **Table of Contents (TOC)** is a small data structure compiled into the firmware that tells the bootloader which region to hash and which key slot to verify against.
|
||||||
|
Its format is defined in [`src/include/image_toc.h`](https://github.com/PX4/PX4-Autopilot/blob/main/src/include/image_toc.h).
|
||||||
|
For px4_fmu-v6x the TOC declares two entries: **BOOT** (the firmware bytes to verify) and **SIG1** (the 64-byte ed25519 signature to verify them against).
|
||||||
|
|
||||||
|
On reset the bootloader reads the TOC at a fixed offset, computes an ed25519 signature over the BOOT region, and compares it to the SIG1 entry.
|
||||||
|
If the signature verifies it jumps to the app; otherwise it stays in the bootloader and waits for a new upload.
|
||||||
|
|
||||||
|
The host-side uploader (`Tools/px4_uploader.py`) also asks the bootloader to verify the signature before sending the final reboot, via a dedicated `VERIFY_SIG` opcode.
|
||||||
|
This means a signature mismatch is reported as a clean error from `px_uploader.py` instead of a silent "device stays in bootloader after reboot".
|
||||||
|
|
||||||
|
## Trying It Out
|
||||||
|
|
||||||
|
PX4 ships a secure-boot example for **px4_fmu-v6x**.
|
||||||
|
This consists of two build variants:
|
||||||
|
|
||||||
|
- `px4_fmu-v6x_bootloader_secureboot` — the secure bootloader, with ed25519 verification enabled, and the upstream _test public key_ baked in.
|
||||||
|
- `px4_fmu-v6x_secureboot` — PX4 firmware, with TOC and automatic signing.
|
||||||
|
|
||||||
|
The steps are:
|
||||||
|
|
||||||
|
1. Build and flash the secure bootloader (one-time, via SWD)
|
||||||
|
- Build the bootloader:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make px4_fmu-v6x_bootloader_secureboot
|
||||||
|
```
|
||||||
|
|
||||||
|
- Flash the resulting `build/px4_fmu-v6x_bootloader_secureboot/px4_fmu-v6x_bootloader_secureboot.elf` via a debug probe.
|
||||||
|
See [Bootloader Update](../advanced_config/bootloader_update).
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
This is the only step that needs SWD — once the secure bootloader is in place, all firmware updates go over USB.
|
||||||
|
:::
|
||||||
|
|
||||||
|
2. Build and upload signed firmware
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make px4_fmu-v6x_secureboot upload
|
||||||
|
```
|
||||||
|
|
||||||
|
The build produces an unsigned `.bin`, signs it with the upstream test key (`Tools/test_keys/test_keys.json`), wraps it in a `.px4` envelope marked `image_signed: true`, and uploads it.
|
||||||
|
You will see something like:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Verify ▕██████████████████████████████▏ 100%
|
||||||
|
Verifying image signature... passed
|
||||||
|
Uploaded in 18s
|
||||||
|
```
|
||||||
|
|
||||||
|
If you upload an image that the bootloader can't verify (e.g. an unsigned `.px4`, a tampered `.bin`, or one signed with a different key), `px_uploader.py` reports the failure before reboot:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Upload failed: Signature verification failed: image will not boot.
|
||||||
|
The bootloader computed a signature over the flashed image that does not
|
||||||
|
match any public key it trusts.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating Your Own Keys
|
||||||
|
|
||||||
|
The default test key is committed to the PX4 tree, so a real deployment must replace it (in order to protect the public key).
|
||||||
|
Generate a new ed25519 key pair with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 Tools/secure_bootloader/generate_signing_keys.py /path/to/my_key
|
||||||
|
```
|
||||||
|
|
||||||
|
This writes:
|
||||||
|
|
||||||
|
- `my_key.json` — the private key used by `sign_firmware.py`. **Keep this private. Do not commit it.**
|
||||||
|
- `my_key.pub` — the public key as a C-array fragment, suitable for `#include` in the bootloader's keystore.
|
||||||
|
|
||||||
|
Make the following changes use your new keys in the build:
|
||||||
|
|
||||||
|
1. **Update the bootloader to trust your public key.**
|
||||||
|
|
||||||
|
Edit `boards/px4/fmu-v6x/bootloader_secureboot.px4board` and change `CONFIG_PUBLIC_KEY0` to point at `my_key.pub`. Rebuild and reflash the bootloader via SWD.
|
||||||
|
|
||||||
|
2. **Tell the app build to sign with the matching private key.**
|
||||||
|
|
||||||
|
Either edit `boards/px4/fmu-v6x/secureboot.px4board` and change `CONFIG_BOARD_SECUREBOOT_KEY` to point at `my_key.json`, or set the environment variable at build time:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BOARD_SECUREBOOT_KEY=/path/to/my_key.json make px4_fmu-v6x_secureboot upload
|
||||||
|
```
|
||||||
|
|
||||||
|
The keys used must always be the corresponding cryptographic pair — if you flash a bootloader that trusts a key whose private half you don't have, you can only recover via SWD.
|
||||||
|
|
||||||
|
## Enabling Secure Boot on a New Board
|
||||||
|
|
||||||
|
To enable secure boot up on a board that doesn't already have a `secureboot` variant, you'll need:
|
||||||
|
|
||||||
|
- a `toc.c` file placed in the board's `src/` (modeled on `boards/px4/fmu-v6x/src/toc.c`),
|
||||||
|
- a linker script with `_main_toc` reserved at a fixed offset past the vector table and a `.signature` section at the end of FLASH (see `boards/px4/fmu-v6x/nuttx-config/scripts/secureboot-script.ld`)
|
||||||
|
- a `secureboot.px4board` setting `CONFIG_BOARD_SECUREBOOT=y` and the linker prefix,
|
||||||
|
- a `bootloader_secureboot.px4board` enabling `CONFIG_BOARD_CRYPTO`, `CONFIG_DRIVERS_SW_CRYPTO`, `CONFIG_DRIVERS_STUB_KEYSTORE` and the public-key path,
|
||||||
|
- `BOOTLOADER_USE_SECURITY` + `BOOTLOADER_SIGNING_ALGORITHM` + `BOARD_IMAGE_TOC_OFFSET` defines in the board's `hw_config.h`, gated on `PX4_CRYPTO`.
|
||||||
|
|
||||||
|
The fmu-v6x variant files are kept small and self-contained for exactly this reason — they are intended to be copied as a starting point.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Bootloader Update](../advanced_config/bootloader_update.md)
|
||||||
|
- [OEM/Factory Configuration](../advanced_config/oem.md)
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The _PX4 Bootloader_ is used to load firmware for [Pixhawk boards](../flight_controller/pixhawk_series.md) (PX4FMU, PX4IO).
|
The _PX4 Bootloader_ is used to load firmware for [Pixhawk boards](../flight_controller/pixhawk_series.md) (PX4FMU, PX4IO).
|
||||||
|
|
||||||
Pixhawk controllers usually comes with an appropriate bootloader version pre-installed.
|
Pixhawk controllers usually come with an appropriate bootloader version pre-installed.
|
||||||
However in some cases it is not present, or an older version is present that needs to be updated, or the board has been bricked and needs to be erased and the bootloader reinstalled.
|
However in some cases it is not present, or an older version is present that needs to be updated, or the board has been bricked and needs to be erased and the bootloader reinstalled.
|
||||||
|
|
||||||
This topic explains how to build the PX4 bootloader, and several methods for flashing it to a board.
|
This topic explains how to build the PX4 bootloader, and several methods for flashing it to a board.
|
||||||
@@ -26,7 +26,7 @@ You can then initiate bootloader update on next restart by setting the parameter
|
|||||||
This approach can be used if the [`bl-update` module](../modules/modules_command.md#bl-update) is present in the firmware.
|
This approach can be used if the [`bl-update` module](../modules/modules_command.md#bl-update) is present in the firmware.
|
||||||
The easiest way to check this is just to see if the [SYS_BL_UPDATE](../advanced_config/parameter_reference.md#SYS_BL_UPDATE) parameter is [found in QGroundControl](../advanced_config/parameters.md#finding-a-parameter).
|
The easiest way to check this is just to see if the [SYS_BL_UPDATE](../advanced_config/parameter_reference.md#SYS_BL_UPDATE) parameter is [found in QGroundControl](../advanced_config/parameters.md#finding-a-parameter).
|
||||||
|
|
||||||
:::warning
|
::: warning
|
||||||
Boards that include the module will have the line `CONFIG_SYSTEMCMDS_BL_UPDATE=y` in their `default.px4board` file (for examples [see this search](https://github.com/search?q=repo%3APX4%2FPX4-Autopilot+path%3A**%2Fdefault.px4board+CONFIG_SYSTEMCMDS_BL_UPDATE%3Dy&type=code)).
|
Boards that include the module will have the line `CONFIG_SYSTEMCMDS_BL_UPDATE=y` in their `default.px4board` file (for examples [see this search](https://github.com/search?q=repo%3APX4%2FPX4-Autopilot+path%3A**%2Fdefault.px4board+CONFIG_SYSTEMCMDS_BL_UPDATE%3Dy&type=code)).
|
||||||
You can enable this key in your own custom firmware if needed.
|
You can enable this key in your own custom firmware if needed.
|
||||||
:::
|
:::
|
||||||
@@ -34,20 +34,20 @@ You can enable this key in your own custom firmware if needed.
|
|||||||
The steps are:
|
The steps are:
|
||||||
|
|
||||||
1. Insert an SD card (enables boot logging to debug any problems).
|
1. Insert an SD card (enables boot logging to debug any problems).
|
||||||
1. [Update the Firmware](../config/firmware.md#custom) with an image containing the new/desired bootloader.
|
2. [Update the Firmware](../config/firmware.md#custom) with an image containing the new/desired bootloader.
|
||||||
|
|
||||||
::: info
|
::: info
|
||||||
The updated bootloader might be included the default firmware for your board or supplied in custom firmware.
|
The updated bootloader might be included in the default firmware for your board or supplied in custom firmware.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
1. Wait for the vehicle to reboot.
|
3. Wait for the vehicle to reboot.
|
||||||
1. [Find and enable](../advanced_config/parameters.md) the parameter [SYS_BL_UPDATE](../advanced_config/parameter_reference.md#SYS_BL_UPDATE).
|
4. [Find and enable](../advanced_config/parameters.md) the parameter [SYS_BL_UPDATE](../advanced_config/parameter_reference.md#SYS_BL_UPDATE).
|
||||||
1. Reboot (disconnect/reconnect the board).
|
5. Reboot (disconnect/reconnect the board).
|
||||||
The bootloader update will only take a few seconds.
|
The bootloader update will only take a few seconds.
|
||||||
|
|
||||||
Generally at this point you may then want to [update the firmware](../config/firmware.md) again using the correct/newly installed bootloader.
|
Generally at this point you may then want to [update the firmware](../config/firmware.md) again using the correct/newly installed bootloader.
|
||||||
|
|
||||||
An specific example of this process for updating the [FMUv2 bootloader](#fmuv2-bootloader-update) is given below.
|
A specific example of this process for updating the [FMUv2 bootloader](#fmuv2-bootloader-update) is given below.
|
||||||
|
|
||||||
## Building the PX4 Bootloader
|
## Building the PX4 Bootloader
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ The following steps explain how you can "manually" update the bootloader using a
|
|||||||
|
|
||||||
1. Get a binary containing the bootloader (either from dev team or [build it yourself](#building-the-px4-bootloader)).
|
1. Get a binary containing the bootloader (either from dev team or [build it yourself](#building-the-px4-bootloader)).
|
||||||
2. Get a [Debug Probe](../debug/swd_debug.md#debug-probes-for-px4-hardware).
|
2. Get a [Debug Probe](../debug/swd_debug.md#debug-probes-for-px4-hardware).
|
||||||
Connect the probe your PC via USB and setup the `gdbserver`.
|
Connect the probe to your PC via USB and setup the `gdbserver`.
|
||||||
3. Go into the directory containing the binary and run the command for your target bootloader in the terminal:
|
3. Go into the directory containing the binary and run the command for your target bootloader in the terminal:
|
||||||
- FMUv6X
|
- FMUv6X
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ The following steps explain how you can "manually" update the bootloader using a
|
|||||||
If using a Zubax BugFace BF1 you may need to remove the case in order to connect to the `FMU-DEBUG` port (e.g. on Pixhawk 4 you would do this using a T6 Torx screwdriver).
|
If using a Zubax BugFace BF1 you may need to remove the case in order to connect to the `FMU-DEBUG` port (e.g. on Pixhawk 4 you would do this using a T6 Torx screwdriver).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
8. Use the following command to scan for the Pixhawk`s SWD and connect to it:
|
8. Use the following command to scan for the Pixhawk's SWD and connect to it:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
(gdb) mon swdp_scan
|
(gdb) mon swdp_scan
|
||||||
@@ -198,4 +198,5 @@ For boards that are preflashed with Betaflight, see [Bootloader Flashing onto Be
|
|||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
- [Bootloader Secure Boot](../advanced_config/bootloader_secure_boot.md)
|
||||||
- [OEM/Factory Configuration](../advanced_config/oem.md)
|
- [OEM/Factory Configuration](../advanced_config/oem.md)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# OEM/Factory Configuration
|
# OEM/Factory Configuration
|
||||||
|
|
||||||
This topic lists configuration and calibration topics that are more relevant to manufacturers/OEMs (though is some cases individual developers may find some relevant).
|
This topic lists configuration and calibration topics that are more relevant to manufacturers/OEMs (though in some cases individual developers may find some relevant).
|
||||||
|
|
||||||
- [IMU Factory Calibration](../advanced_config/imu_factory_calibration.md)
|
- [IMU Factory Calibration](../advanced_config/imu_factory_calibration.md)
|
||||||
- [Sensor Thermal Compensation](../advanced_config/sensor_thermal_calibration.md)
|
- [Sensor Thermal Compensation](../advanced_config/sensor_thermal_calibration.md)
|
||||||
@@ -8,6 +8,7 @@ This topic lists configuration and calibration topics that are more relevant to
|
|||||||
- [Advanced Controller Orientation](../advanced_config/advanced_flight_controller_orientation_leveling.md)
|
- [Advanced Controller Orientation](../advanced_config/advanced_flight_controller_orientation_leveling.md)
|
||||||
- [Static Pressure Buildup](../advanced_config/static_pressure_buildup.md)
|
- [Static Pressure Buildup](../advanced_config/static_pressure_buildup.md)
|
||||||
- [Bootloader Update](../advanced_config/bootloader_update.md)
|
- [Bootloader Update](../advanced_config/bootloader_update.md)
|
||||||
|
- [Bootloader Secure Boot](../advanced_config/bootloader_secure_boot.md)
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,15 @@ endif()
|
|||||||
|
|
||||||
set(nuttx_libs)
|
set(nuttx_libs)
|
||||||
set(SCRIPT_PREFIX)
|
set(SCRIPT_PREFIX)
|
||||||
if("${PX4_BOARD_LABEL}" STREQUAL "bootloader")
|
# Match "bootloader" and any "bootloader_<variant>" label (e.g.
|
||||||
set(SCRIPT_PREFIX ${PX4_BOARD_LABEL}_)
|
# "bootloader_secureboot") so a board can ship a bootloader variant
|
||||||
|
# with extra Kconfig (crypto, keystore, etc.) without duplicating
|
||||||
|
# the bootloader build wiring.
|
||||||
|
if("${PX4_BOARD_LABEL}" MATCHES "^bootloader")
|
||||||
|
# All bootloader variants share the same linker script
|
||||||
|
# (bootloader_script.ld); the label suffix only changes which
|
||||||
|
# Kconfig and which Tools/test_keys/*.pub are baked in.
|
||||||
|
set(SCRIPT_PREFIX bootloader_)
|
||||||
add_subdirectory(src/bootloader)
|
add_subdirectory(src/bootloader)
|
||||||
list(APPEND nuttx_libs
|
list(APPEND nuttx_libs
|
||||||
bootloader
|
bootloader
|
||||||
@@ -425,6 +432,49 @@ if (TARGET parameters_xml AND TARGET airframes_xml)
|
|||||||
|
|
||||||
string(REPLACE ".elf" ".px4" fw_package ${PX4_BINARY_DIR}/${FW_NAME})
|
string(REPLACE ".elf" ".px4" fw_package ${PX4_BINARY_DIR}/${FW_NAME})
|
||||||
|
|
||||||
|
# Secure-boot variants sign the .bin with sign_firmware.py before
|
||||||
|
# wrapping it in the .px4 envelope, and tag the envelope so the
|
||||||
|
# uploader runs VERIFY_SIG against the matching bootloader. The
|
||||||
|
# resulting .px4 is the only one that will boot on a secure
|
||||||
|
# bootloader (the unsigned .bin still ships alongside, for users
|
||||||
|
# who want to sign with a different key out-of-tree).
|
||||||
|
set(_mkfw_image ${PX4_BINARY_DIR}/${PX4_CONFIG}.bin)
|
||||||
|
set(_mkfw_signed_flag)
|
||||||
|
set(_mkfw_extra_depends)
|
||||||
|
|
||||||
|
if(CONFIG_BOARD_SECUREBOOT)
|
||||||
|
# Allow an env var to override the Kconfig default so a release
|
||||||
|
# build can point at a private key that doesn't live in the
|
||||||
|
# repo without having to edit the .px4board. Relative paths
|
||||||
|
# from the .px4board are resolved against the repo root, to
|
||||||
|
# match how CONFIG_PUBLIC_KEYn paths are written.
|
||||||
|
if(DEFINED ENV{BOARD_SECUREBOOT_KEY})
|
||||||
|
set(_secureboot_key $ENV{BOARD_SECUREBOOT_KEY})
|
||||||
|
else()
|
||||||
|
set(_secureboot_key ${CONFIG_BOARD_SECUREBOOT_KEY})
|
||||||
|
endif()
|
||||||
|
if(NOT IS_ABSOLUTE ${_secureboot_key})
|
||||||
|
get_filename_component(_secureboot_key
|
||||||
|
${_secureboot_key} ABSOLUTE BASE_DIR ${PX4_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(_signed_bin ${PX4_BINARY_DIR}/${PX4_CONFIG}_signed.bin)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${_signed_bin}
|
||||||
|
COMMAND
|
||||||
|
${PYTHON_EXECUTABLE} ${PX4_SOURCE_DIR}/Tools/secure_bootloader/sign_firmware.py
|
||||||
|
--key ${_secureboot_key}
|
||||||
|
${PX4_BINARY_DIR}/${PX4_CONFIG}.bin
|
||||||
|
${_signed_bin}
|
||||||
|
DEPENDS ${PX4_BINARY_DIR}/${PX4_CONFIG}.bin
|
||||||
|
COMMENT "Signing ${PX4_CONFIG}.bin with ${_secureboot_key}"
|
||||||
|
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||||
|
)
|
||||||
|
set(_mkfw_image ${_signed_bin})
|
||||||
|
set(_mkfw_signed_flag --image_signed)
|
||||||
|
set(_mkfw_extra_depends ${_signed_bin})
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${fw_package}
|
OUTPUT ${fw_package}
|
||||||
COMMAND
|
COMMAND
|
||||||
@@ -433,9 +483,11 @@ if (TARGET parameters_xml AND TARGET airframes_xml)
|
|||||||
--git_identity ${PX4_SOURCE_DIR}
|
--git_identity ${PX4_SOURCE_DIR}
|
||||||
--parameter_xml ${PX4_BINARY_DIR}/parameters.xml
|
--parameter_xml ${PX4_BINARY_DIR}/parameters.xml
|
||||||
--airframe_xml ${PX4_BINARY_DIR}/airframes.xml
|
--airframe_xml ${PX4_BINARY_DIR}/airframes.xml
|
||||||
--image ${PX4_BINARY_DIR}/${PX4_CONFIG}.bin > ${fw_package}
|
${_mkfw_signed_flag}
|
||||||
|
--image ${_mkfw_image} > ${fw_package}
|
||||||
DEPENDS
|
DEPENDS
|
||||||
${PX4_BINARY_DIR}/${PX4_CONFIG}.bin
|
${PX4_BINARY_DIR}/${PX4_CONFIG}.bin
|
||||||
|
${_mkfw_extra_depends}
|
||||||
airframes_xml
|
airframes_xml
|
||||||
parameters_xml
|
parameters_xml
|
||||||
COMMENT "Creating ${fw_package}"
|
COMMENT "Creating ${fw_package}"
|
||||||
|
|||||||
@@ -214,6 +214,12 @@ function(px4_os_prebuild_targets)
|
|||||||
|
|
||||||
if(EXISTS ${PX4_BOARD_DIR}/nuttx-config/${PX4_BOARD_LABEL})
|
if(EXISTS ${PX4_BOARD_DIR}/nuttx-config/${PX4_BOARD_LABEL})
|
||||||
set(NUTTX_CONFIG "${PX4_BOARD_LABEL}" CACHE INTERNAL "NuttX config" FORCE)
|
set(NUTTX_CONFIG "${PX4_BOARD_LABEL}" CACHE INTERNAL "NuttX config" FORCE)
|
||||||
|
elseif("${PX4_BOARD_LABEL}" MATCHES "^bootloader" AND EXISTS ${PX4_BOARD_DIR}/nuttx-config/bootloader)
|
||||||
|
# Bootloader variants (e.g. bootloader_secureboot) share the
|
||||||
|
# minimal "bootloader" NuttX config; falling back to "nsh"
|
||||||
|
# would silently pull in the full app-style NuttX subsystem
|
||||||
|
# (full heap, fs, net) and overflow the bootloader sector.
|
||||||
|
set(NUTTX_CONFIG "bootloader" CACHE INTERNAL "NuttX config" FORCE)
|
||||||
else()
|
else()
|
||||||
set(NUTTX_CONFIG "nsh" CACHE INTERNAL "NuttX config" FORCE)
|
set(NUTTX_CONFIG "nsh" CACHE INTERNAL "NuttX config" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -110,6 +110,7 @@
|
|||||||
#define PROTO_BOOT 0x30 // boot the application
|
#define PROTO_BOOT 0x30 // boot the application
|
||||||
#define PROTO_DEBUG 0x31 // emit debug information - format not defined
|
#define PROTO_DEBUG 0x31 // emit debug information - format not defined
|
||||||
#define PROTO_SET_BAUD 0x33 // set baud rate on uart
|
#define PROTO_SET_BAUD 0x33 // set baud rate on uart
|
||||||
|
#define PROTO_VERIFY_SIG 0x39 // verify the signature of the programmed image
|
||||||
|
|
||||||
// Reserved for external flash programming
|
// Reserved for external flash programming
|
||||||
// #define PROTO_EXTF_ERASE 0x34 // Erase sectors from external flash
|
// #define PROTO_EXTF_ERASE 0x34 // Erase sectors from external flash
|
||||||
@@ -1035,6 +1036,74 @@ bootloader(unsigned timeout)
|
|||||||
*/
|
*/
|
||||||
goto cmd_bad;
|
goto cmd_bad;
|
||||||
|
|
||||||
|
// verify the signature of the programmed image
|
||||||
|
//
|
||||||
|
// command: VERIFY_SIG/EOC
|
||||||
|
// reply: INSYNC/OK if image verifies
|
||||||
|
// reply: INSYNC/FAILED if TOC is missing or signature check fails
|
||||||
|
// reply: INSYNC/INVALID if the bootloader was built without
|
||||||
|
// BOOTLOADER_USE_SECURITY
|
||||||
|
//
|
||||||
|
// The uploader is expected to call this after GET_CRC and before
|
||||||
|
// BOOT, and only when the firmware metadata claims the image is
|
||||||
|
// signed. Running it always would add unnecessary verification time
|
||||||
|
// (and fail noisily) on bootloaders or images that are not secure.
|
||||||
|
case PROTO_VERIFY_SIG:
|
||||||
|
if (!wait_for_eoc(2)) {
|
||||||
|
goto cmd_bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef BOOTLOADER_USE_SECURITY
|
||||||
|
|
||||||
|
/* Only accept VERIFY_SIG at the same point in the upload
|
||||||
|
* state machine where we would accept BOOT — i.e. after
|
||||||
|
* PROG_MULTI + GET_CRC have been issued in the right order.
|
||||||
|
*/
|
||||||
|
if (first_word != 0xffffffff && (bl_state & STATE_ALLOWS_REBOOT) != STATE_ALLOWS_REBOOT) {
|
||||||
|
goto cmd_bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* During PROG_MULTI the first word of the app was held in a
|
||||||
|
* RAM variable instead of written to flash, to prevent a
|
||||||
|
* partial upload from becoming bootable. Signature
|
||||||
|
* verification has to hash the actual image, so we have to
|
||||||
|
* commit that word to flash first. This mirrors the first
|
||||||
|
* half of the BOOT handler below.
|
||||||
|
*/
|
||||||
|
if (first_word != 0xffffffff) {
|
||||||
|
flash_func_write_word(APP_VECTOR_OFFSET, first_word);
|
||||||
|
|
||||||
|
if (flash_func_read_word(APP_VECTOR_OFFSET) != first_word) {
|
||||||
|
goto cmd_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
first_word = 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const image_toc_entry_t *toc_entries;
|
||||||
|
uint8_t toc_len;
|
||||||
|
|
||||||
|
crypto_init();
|
||||||
|
|
||||||
|
if (!find_toc(&toc_entries, &toc_len) ||
|
||||||
|
!verify_app(0, toc_entries)) {
|
||||||
|
crypto_deinit();
|
||||||
|
goto cmd_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto_deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
/* Signature verification not compiled in — tell the host we
|
||||||
|
* don't know this opcode so it can warn the user instead of
|
||||||
|
* silently assuming the image is trusted.
|
||||||
|
*/
|
||||||
|
goto cmd_bad;
|
||||||
|
#endif
|
||||||
|
|
||||||
// finalise programming and boot the system
|
// finalise programming and boot the system
|
||||||
//
|
//
|
||||||
// command: BOOT/EOC
|
// command: BOOT/EOC
|
||||||
|
|||||||
@@ -38,6 +38,31 @@
|
|||||||
#ifdef BOOTLOADER_USE_SECURITY
|
#ifdef BOOTLOADER_USE_SECURITY
|
||||||
|
|
||||||
#include <px4_platform_common/crypto_backend.h>
|
#include <px4_platform_common/crypto_backend.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <nuttx/arch.h>
|
||||||
|
|
||||||
|
/* sw_crypto unconditionally references px4_get_secure_random from the
|
||||||
|
* XCHACHA20 path in crypto_open(). The bootloader only verifies
|
||||||
|
* ed25519 signatures and has no reason to generate randomness, but
|
||||||
|
* the linker still demands the symbol. Supplying it here avoids
|
||||||
|
* pulling NuttX's random pool (CONFIG_CRYPTO_RANDOM_POOL) into the
|
||||||
|
* bootloader build just to resolve an unreachable call site.
|
||||||
|
*
|
||||||
|
* The implementation panics rather than returning zeros: if anyone
|
||||||
|
* ever enables bootloader-side encryption without also wiring up a
|
||||||
|
* real RNG, silently handing out predictable bytes would be a
|
||||||
|
* serious security bug. up_assert() makes that mistake loud.
|
||||||
|
*/
|
||||||
|
size_t px4_get_secure_random(uint8_t *out, size_t outlen)
|
||||||
|
{
|
||||||
|
(void)out;
|
||||||
|
(void)outlen;
|
||||||
|
|
||||||
|
up_assert(__FILE__, __LINE__);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool verify_app(uint16_t idx, const image_toc_entry_t *toc_entries)
|
bool verify_app(uint16_t idx, const image_toc_entry_t *toc_entries)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user