8000 stm32/mboot: Add support for signed and encrypted firmware updates. · mbtronics/micropython@c6f3342 · GitHub
[go: up one dir, main page]

Skip to content

Commit c6f3342

Browse files
committed
stm32/mboot: Add support for signed and encrypted firmware updates.
This commit adds support to stm32's mboot for signe, encrypted and compressed DFU updates. It is based on inital work done by Andrew Leech. The feature is enabled by setting MBOOT_ENABLE_PACKING to 1 in the board's mpconfigboard.mk file, and by providing a header file in the board folder (usually called mboot_keys.h) with a set of signing and encryption keys (which can be generated by mboot_pack_dfu.py). The signing and encryption is provided by libhydrogen. Compression is provided by uzlib. Enabling packing costs about 3k of flash. The included mboot_pack_dfu.py script converts a .dfu file to a .pack.dfu file which can be subsequently deployed to a board with mboot in packing mode. This .pack.dfu file is created as follows: - the firmware from the original .dfu is split into chunks (so the decryption can fit in RAM) - each chunk is compressed, encrypted, a header added, then signed - a special final chunk is added with a signature of the entire firmware - all chunks are concatenated to make the final .pack.dfu file The .pack.dfu file can be deployed over USB or from the internal filesystem on the device (if MBOOT_FSLOAD is enabled). See micropython#5267 and micropython#5309 for additional discussion. Signed-off-by: Damien George <damien@micropython.org>
1 parent 09e67de commit c6f3342

18 files changed

+844
-31
lines changed

ports/stm32/Makefile

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ FROZEN_MANIFEST ?= boards/manifest.py
3030
# include py core make definitions
3131
include $(TOP)/py/py.mk
3232

33-
GIT_SUBMODULES = lib/lwip lib/mbedtls lib/mynewt-nimble lib/stm32lib
33+
GIT_SUBMODULES = lib/libhydrogen lib/lwip lib/mbedtls lib/mynewt-nimble lib/stm32lib
3434

3535
MCU_SERIES_UPPER = $(shell echo $(MCU_SERIES) | tr '[:lower:]' '[:upper:]')
3636
CMSIS_MCU_LOWER = $(shell echo $(CMSIS_MCU) | tr '[:upper:]' '[:lower:]')
@@ -41,6 +41,7 @@ HAL_DIR=lib/stm32lib/STM32$(MCU_SERIES_UPPER)xx_HAL_Driver
4141
USBDEV_DIR=usbdev
4242
#USBHOST_D 8000 IR=usbhost
4343
DFU=$(TOP)/tools/dfu.py
44+
MBOOT_PACK_DFU = mboot/mboot_pack_dfu.py
4445
# may need to prefix dfu-util with sudo
4546
USE_PYDFU ?= 1
4647
PYDFU ?= $(TOP)/tools/pydfu.py
@@ -546,7 +547,13 @@ $(PY_BUILD)/formatfloat.o: COPT += -Os
546547
$(PY_BUILD)/parsenum.o: COPT += -Os
547548
$(PY_BUILD)/mpprint.o: COPT += -Os
548549

549-
all: $(TOP)/lib/stm32lib/README.md $(BUILD)/firmware.dfu $(BUILD)/firmware.hex
550+
all: $(TOP)/lib/stm32lib/README.md all_main $(BUILD)/firmware.hex
551+
552+
ifeq ($(MBOOT_ENABLE_PACKING),1)
553+
all_main: $(BUILD)/firmware.pack.dfu
554+
else
555+
all_main: $(BUILD)/firmware.dfu
556+
endif
550557

551558
# For convenience, automatically fetch required submodules if they don't exist
552559
$(TOP)/lib/stm32lib/README.md:
@@ -607,15 +614,25 @@ define GENERATE_DFU
607614
$(1)
608615
endef
609616

617+
define GENERATE_PACK_DFU
618+
$(ECHO) "GEN $(1)"
619+
$(Q)$(PYTHON) $(MBOOT_PACK_DFU) --keys $(MBOOT_PACK_KEYS_FILE) pack-dfu --gzip $(MBOOT_PACK_CHUNKSIZE) $(2) $(1)
620+
endef
621+
610622
define GENERATE_HEX
611623
$(ECHO) "GEN $(1)"
612624
$(Q)$(OBJCOPY) -O ihex $(2) $(1)
613625
endef
614626

615627
.PHONY: deploy deploy-stlink deploy-openocd
616628

629+
ifeq ($(MBOOT_ENABLE_PACKING),1)
630+
deploy: $(BUILD)/firmware.pack.dfu
631+
$(call RUN_DFU,$^)
632+
else
617633
deploy: $(BUILD)/firmware.dfu
618634
$(call RUN_DFU,$^)
635+
endif
619636

620637
# A board should specify TEXT0_ADDR if to use a different location than the
621638
# default for the firmware memory location. A board can also optionally define
@@ -662,6 +679,9 @@ $(BUILD)/firmware.dfu: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin
662679
$(call GENERATE_DFU,$@,$(word 1,$^),$(TEXT0_ADDR),$(word 2,$^),$(TEXT1_ADDR))
663680
endif
664681

682+
$(BUILD)/firmware.pack.dfu: $(BUILD)/firmware.dfu $(BOARD_DIR)/mboot_keys.h
683+
$(call GENERATE_PACK_DFU,$@,$<)
684+
665685
$(BUILD)/firmware.hex: $(BUILD)/firmware.elf
666686
$(call GENERATE_HEX,$@,$^)
667687

ports/stm32/mboot/Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ BOARD_DIR ?= $(abspath ../boards/$(BOARD))
1212
# that can be built with or without mboot.
1313
USE_MBOOT ?= 1
1414

15+
# Set MBOOT_ENABLE_PACKING to 1 to enable DFU packing with encryption and signing.
16+
# Ensure the MBOOT_PACK_xxx values match stm32/Makefile, to build matching application firmware.
17+
MBOOT_ENABLE_PACKING ?= 0
18+
MBOOT_PACK_CHUNKSIZE ?= 16384
19+
MBOOT_PACK_KEYS_FILE ?= $(BOARD_DIR)/mboot_keys.h
20+
1521
# Sanity check that the board configuration directory exists
1622
ifeq ($(wildcard $(BOARD_DIR)/.),)
1723
$(error Invalid BOARD specified: $(BOARD_DIR))
@@ -110,6 +116,7 @@ SRC_C = \
110116
elem.c \
111117
fsload.c \
112118
gzstream.c \
119+
pack.c \
113120
vfs_fat.c \
114121
vfs_lfs.c \
115122
drivers/bus/softspi.c \
@@ -129,6 +136,15 @@ SRC_O = \
129136
$(SYSTEM_FILE) \
130137
ports/stm32/resethandler.o \
131138

139+
ifeq ($(MBOOT_ENABLE_PACKING), 1)
140+
141+
SRC_C += lib/libhydrogen/hydrogen.c
142+
143+
CFLAGS += -DMBOOT_ENABLE_PACKING=1 -DPARTICLE -DPLATFORM_ID=3
144+
CFLAGS += -DMBOOT_PACK_CHUNKSIZE=$(MBOOT_PACK_CHUNKSIZE)
145+
CFLAGS += -DMBOOT_PACK_KEYS_FILE=\"$(MBOOT_PACK_KEYS_FILE)\"
146+
endif
147+
132148
$(BUILD)/$(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_ll_usb.o: CFLAGS += -Wno-attributes
133149
SRC_HAL = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\
134150
hal_cortex.c \

ports/stm32/mboot/Particle.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Header for libhydrogen use only. Act like a Particle board for the random
2+
// implementation. This code is not actually called when just decrypting and
3+
// verifying a signature, but a correct implementation is provided anyway.
4+
5+
#include "py/mphal.h"
6+
#include "rng.h"
7+
8+
static inline uint32_t HAL_RNG_GetRandomNumber(void) {
9+
return rng_get();
10+
}

ports/stm32/mboot/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,34 @@ firmware.dfu.gz stored on the default FAT filesystem:
155155
The 0x80000000 value is the address understood by Mboot as the location of
156156
the external SPI flash, configured via `MBOOT_SPIFLASH_ADDR`.
157157

158+
Signed and encrypted DFU support
159+
--------------------------------
160+
161+
Mboot optionally supports signing and encrypting the binary firmware in the DFU file.
162+
In general this is refered to as a packed DFU file. This requires additional settings
163+
in the board config and requires the `pyhy` Python module to be installed for `python3`
164+
to be used when building packed firmware, eg:
165+
166+
$ pip3 install pyhy
167+
168+
In addition to the changes made to mpconfigboard.mk earlier, for encrypted
169+
support you also need to add:
170+
171+
MBOOT_ENABLE_PACKING = 1
172+
173+
You will also need to generate signing and encryption keys which will be built into
174+
mboot and used for all subsequent installations of firmware. This can be done via:
175+
176+
$ python3 ports/stm32/mboot/mboot_pack_dfu.py generate-keys
177+
178+
This command generates a `mboot_keys.h` file which should be stored in the board
179+
definition folder (next to mpconfigboard.mk).
180+
181+
Once you build the firmware, the `firmware.pack.dfu` file will contain the encrypted
182+
and signed firmware, and can be deployed via USB DFU, or by copying it to the device's
183+
internal filesystem (if `MBOOT_FSLOAD` is enabled). `firmware.dfu` is still unencrypted
184+
and can be directly flashed with jtag etc.
185+
158186
Example: Mboot on PYBv1.x
159187
-------------------------
160188

ports/stm32/mboot/dfu.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
#define MBOOT_ERROR_STR_INVALID_ADDRESS_IDX 0x11
4040
#define MBOOT_ERROR_STR_INVALID_ADDRESS "Address out of range"
4141

42+
#if MBOOT_ENABLE_PACKING
43+
#define MBOOT_ERROR_STR_INVALID_SIG_IDX 0x12
44+
#define MBOOT_ERROR_STR_INVALID_SIG "Invalid signature in file"
45+
46+
#define MBOOT_ERROR_STR_INVALID_READ_IDX 0x13
47+
#define MBOOT_ERROR_STR_INVALID_READ "Read support disabled on encrypted bootloader"
48+
#endif
49+
4250
// DFU class requests
4351
enum {
4452
DFU_DETACH = 0,
@@ -104,6 +112,6 @@ typedef struct _dfu_state_t {
104112
uint8_t buf[DFU_XFER_SIZE] __attribute__((aligned(4)));
105113
} dfu_context_t;
106114

107-
static dfu_context_t dfu_context SECTION_NOZERO_BSS;
115+
extern dfu_context_t dfu_context;
108116

109117
#endif // MICROPY_INCLUDED_STM32_MBOOT_DFU_H

ports/stm32/mboot/fsload.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include "py/mphal.h"
3030
#include "mboot.h"
31+
#include "pack.h"
3132
#include "vfs.h"
3233

3334
#if MBOOT_FSLOAD
@@ -36,13 +37,43 @@
3637
#error Must enable at least one VFS component
3738
#endif
3839

40+
#if MBOOT_ENABLE_PACKING
41+
// Packed DFU files are gzip'd internally, not on the outside, so reads of the file
42+
// just read the file directly.
43+
44+
static void *input_stream_data;
45+
static stream_read_t input_stream_read_meth;
46+
47+
static inline int input_stream_init(void *stream_data, stream_read_t stream_read) {
48+
input_stream_data = stream_data;
49+
input_stream_read_meth = stream_read;
50+
return 0;
51+
}
52+
53+
static inline int input_stream_read(size_t len, uint8_t *buf) {
54+
return input_stream_read_meth(input_stream_data, buf, len);
55+
}
56+
57+
#else
58+
// Standard (non-packed) DFU files must be gzip'd externally / on the outside, so
59+
// reads of the file go through gz_stream.
60+
61+
static inline int input_stream_init(void *stream_data, stream_read_t stream_read) {
62+
return gz_stream_init_from_stream(stream_data, stream_read);
63+
}
64+
65+
static inline int input_stream_read(size_t len, uint8_t *buf) {
66+
return gz_stream_read(len, buf);
67+
}
68+
#endif
69+
3970
static int fsload_program_file(bool write_to_flash) {
4071
// Parse DFU
4172
uint8_t buf[512];
4273
size_t file_offset;
4374

4475
// Read file header, <5sBIB
45-
int res = gz_stream_read(11, buf);
76+
int res = input_stream_read(11, buf);
4677
if (res != 11) {
4778
return -1;
4879
}
@@ -62,7 +93,7 @@ static int fsload_program_file(bool write_to_flash) {
6293
uint32_t total_size = get_le32(buf + 6);
6394

6495
// Read target header, <6sBi255sII
65-
res = gz_stream_read(274, buf);
96+
res = input_stream_read(274, buf);
6697
if (res != 274) {
6798
return -1;
6899
}
@@ -82,7 +113,7 @@ static int fsload_program_file(bool write_to_flash) {
82113
// Parse each element
83114
for (size_t elem = 0; elem < num_elems; ++elem) {
84115
// Read element header, <II
85-
res = gz_stream_read(8, buf);
116+
res = input_stream_read(8, buf);
86117
if (res != 8) {
87118
return -1;
88119
}
@@ -92,6 +123,7 @@ static int fsload_program_file(bool write_to_flash) {
92123
uint32_t elem_addr = get_le32(buf);
93124
uint32_t elem_size = get_le32(buf + 4);
94125

126+
#if !MBOOT_ENABLE_PACKING
95127
// Erase flash before writing
96128
if (write_to_flash) {
97129
uint32_t addr = elem_addr;
@@ -102,14 +134,15 @@ static int fsload_program_file(bool write_to_flash) {
102134
}
103135
}
104136
}
137+
#endif
105138

106139
// Read element data and possibly write to flash
107140
for (uint32_t s = elem_size; s;) {
108141
uint32_t l = s;
109142
if (l > sizeof(buf)) {
110143
l = sizeof(buf);
111144
}
112-
res = gz_stream_read(l, buf);
145+
res = input_stream_read(l, buf);
113146
if (res != l) {
114147
return -1;
115148
}
@@ -135,7 +168,7 @@ static int fsload_program_file(bool write_to_flash) {
135168
}
136169

137170
// Read trailing info
138-
res = gz_stream_read(16, buf);
171+
res = input_stream_read(16, buf);
139172
if (res != 16) {
140173
return -1;
141174
}
@@ -151,7 +184,7 @@ static int fsload_validate_and_program_file(void *stream, const stream_methods_t
151184
led_state_all(pass == 0 ? 2 : 4);
152185
int res = meth->open(stream, fname);
153186
if (res == 0) {
154-
res = gz_stream_init(stream, meth->read);
187+
res = input_stream_init(stream, meth->read);
155188
if (res == 0) {
156189
res = fsload_program_file(pass == 0 ? false : true);
157190
}

ports/stm32/mboot/fwupdate.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,15 @@ def update_mboot(filename):
157157

158158

159159
def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT):
160-
# Check firmware is of .dfu.gz type
160+
# Check firmware is of .dfu or .dfu.gz type
161161
try:
162162
with open(filename, "rb") as f:
163163
hdr = uzlib.DecompIO(f, 16 + 15).read(6)
164164
except Exception:
165-
hdr = None
165+
with open(filename, "rb") as f:
166+
hdr = f.read(6)
166167
if hdr != b"DfuSe\x01":
167-
print("Firmware must be a .dfu.gz file.")
168+
print("Firmware must be a .dfu(.gz) file.")
168169
return
169170

170171
ELEM_TYPE_END = 1

ports/stm32/mboot/gzstream.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include "gzstream.h"
3232
#include "mboot.h"
3333

34-
#if MBOOT_FSLOAD
34+
#if MBOOT_FSLOAD || MBOOT_ENABLE_PACKING
3535

3636
#define DICT_SIZE (1 << 15)
3737

@@ -61,7 +61,17 @@ static int gz_stream_read_src(TINF_DATA *tinf) {
6161
return gz_stream.buf[0];
6262
}
6363

64-
int gz_stream_init(void *stream_data, stream_read_t stream_read) {
64+
int gz_stream_init_from_raw_data(const uint8_t *data, size_t len) {
65+
memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf));
66+
gz_stream.tinf.source = data;
67+
gz_stream.tinf.source_limit = data + len;
68+
69+
uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE);
70+
71+
return 0;
72+
}
73+
74+
int gz_stream_init_from_stream(void *stream_data, stream_read_t stream_read) {
6575
gz_stream.stream_data = stream_data;
6676
gz_stream.stream_read = stream_read;
6777

@@ -97,4 +107,4 @@ int gz_stream_read(size_t len, uint8_t *buf) {
97107
return gz_stream.tinf.dest - buf;
98108
}
99109

100-
#endif // MBOOT_FSLOAD
110+
#endif // MBOOT_FSLOAD || MBOOT_ENABLE_PACKING

ports/stm32/mboot/gzstream.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ typedef struct _stream_methods_t {
3939
stream_read_t read;
4040
} stream_methods_t;
4141

42-
int gz_stream_init(void *stream_data, stream_read_t stream_read);
42+
int gz_stream_init_from_raw_data(const uint8_t *data, size_t len);
43+
int gz_stream_init_from_stream(void *stream_data, stream_read_t stream_read);
4344
int gz_stream_read(size_t len, uint8_t *buf);
4445

4546
#endif // MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H

0 commit comments

Comments
 (0)
0