1 /*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2020-2021 Damien P. George
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
27 #include <string.h>
28
29 #include "dfu.h"
30 #include "gzstream.h"
31 #include "mboot.h"
32 #include "pack.h"
33
34 #if MBOOT_ENABLE_PACKING
35
36 // Keys provided externally by the board, will be built into mboot flash.
37 #include MBOOT_PACK_KEYS_FILE
38
39 // Encrypted dfu files using gzip require a decompress buffer. Larger can be faster.
40 // This setting is independent to the incoming encrypted/signed/compressed DFU file.
41 #ifndef MBOOT_PACK_GZIP_BUFFER_SIZE
42 #define MBOOT_PACK_GZIP_BUFFER_SIZE (2048)
43 #endif
44
45 // State to manage automatic flash erasure.
46 static uint32_t erased_base_addr;
47 static uint32_t erased_top_addr;
48
49 // DFU chunk buffer, used to cache incoming blocks of data from USB.
50 static uint32_t firmware_chunk_base_addr;
51 static mboot_pack_chunk_buf_t firmware_chunk_buf;
52
53 // Temporary buffer for decrypted data.
54 static uint8_t decrypted_buf[MBOOT_PACK_DFU_CHUNK_BUF_SIZE] __attribute__((aligned(8)));
55
56 // Temporary buffer for uncompressing.
57 static uint8_t uncompressed_buf[MBOOT_PACK_GZIP_BUFFER_SIZE] __attribute__((aligned(8)));
58
59 // Buffer to hold the start of the firmware, which is only written once the
60 // entire firmware is validated. This is 8 bytes due to STM32WB MCUs requiring
61 // that a double-word write to flash can only be done once (due to ECC).
62 static uint8_t firmware_head[8];
63
64 // Flag to indicate that firmware_head contains valid data.
65 static bool firmware_head_valid;
66
mboot_pack_init(void)67 void mboot_pack_init(void) {
68 erased_base_addr = 0;
69 erased_top_addr = 0;
70 firmware_chunk_base_addr = 0;
71 firmware_head_valid = false;
72 }
73
74 // In encrypted mode the erase is automatically managed.
75 // Note: this scheme requires blocks be written in sequence, which is the case.
mboot_pack_erase(uint32_t addr,size_t len)76 static int mboot_pack_erase(uint32_t addr, size_t len) {
77 while (!(erased_base_addr <= addr && addr + len <= erased_top_addr)) {
78 uint32_t erase;
79 if (erased_base_addr <= addr && addr < erased_top_addr) {
80 erase = erased_top_addr;
81 } else {
82 erase = addr;
83 erased_base_addr = addr;
84 }
85 uint32_t next_addr;
86 int ret = hw_page_erase(erase, &next_addr);
87 if (ret != 0) {
88 return ret;
89 }
90 erased_top_addr = next_addr;
91 }
92 return 0;
93 }
94
95 // Commit an unencrypted and uncompressed chunk of firmware to the flash.
mboot_pack_commit_chunk(uint32_t addr,uint8_t * data,size_t len)96 static int mboot_pack_commit_chunk(uint32_t addr, uint8_t *data, size_t len) {
97 // Erase any required sectors before writing.
98 int ret = mboot_pack_erase(addr, len);
99 if (ret != 0) {
100 return ret;
101 }
102
103 if (addr == APPLICATION_ADDR) {
104 // Don't write the very start of the firmware, just copy it into a temporary buffer.
105 // It will be written only if the full firmware passes the checksum/signature.
106 memcpy(firmware_head, data, sizeof(firmware_head));
107 addr += sizeof(firmware_head);
108 data += sizeof(firmware_head);
109 len -= sizeof(firmware_head);
110 firmware_head_valid = true;
111 }
112
113 // Commit this piece of the firmware.
114 return hw_write(addr, data, len);
115 }
116
117 // Handle a chunk with the full firmware signature.
mboot_pack_handle_full_sig(void)118 static int mboot_pack_handle_full_sig(void) {
119 if (firmware_chunk_buf.header.length < hydro_sign_BYTES) {
120 return -MBOOT_ERRNO_PACK_INVALID_CHUNK;
121 }
122
123 uint8_t *full_sig = &firmware_chunk_buf.data[firmware_chunk_buf.header.length - hydro_sign_BYTES];
124 uint32_t *region_data = (uint32_t *)&firmware_chunk_buf.data[0];
125 size_t num_regions = (full_sig - (uint8_t *)region_data) / sizeof(uint32_t) / 2;
126
127 uint8_t *buf = decrypted_buf;
128 const size_t buf_alloc = sizeof(decrypted_buf);
129
130 // Compute the signature of the full firmware.
131 hydro_sign_state sign_state;
132 hydro_sign_init(&sign_state, MBOOT_PACK_HYDRO_CONTEXT);
133 for (size_t region = 0; region < num_regions; ++region) {
134 uint32_t addr = region_data[2 * region];
135 uint32_t len = region_data[2 * region + 1];
136 while (len) {
137 uint32_t l = len <= buf_alloc ? len : buf_alloc;
138 hw_read(addr, l, buf);
139 if (addr == APPLICATION_ADDR && firmware_head_valid) {
140 // The start of the firmware was not yet written to flash so copy
141 // it out of the temporary buffer to compute the full signature.
142 memcpy(buf, firmware_head, sizeof(firmware_head));
143 }
144 int ret = hydro_sign_update(&sign_state, buf, l);
145 if (ret != 0) {
146 return -MBOOT_ERRNO_PACK_SIGN_FAILED;
147 }
148 addr += l;
149 len -= l;
150 }
151 }
152
153 // Verify the signature of the full firmware.
154 int ret = hydro_sign_final_verify(&sign_state, full_sig, mboot_pack_sign_public_key);
155 if (ret != 0) {
156 dfu_context.status = DFU_STATUS_ERROR_VERIFY;
157 dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX;
158 return -MBOOT_ERRNO_PACK_SIGN_FAILED;
159 }
160
161 // Full firmware passed the signature check.
162
163 if (firmware_head_valid) {
164 // Write the start of the firmware so it boots.
165 ret = hw_write(APPLICATION_ADDR, firmware_head, sizeof(firmware_head));
166 }
167
168 return ret;
169 }
170
171 // Handle a chunk with firmware data.
mboot_pack_handle_firmware(void)172 static int mboot_pack_handle_firmware(void) {
173 const uint8_t *fw_data = &firmware_chunk_buf.data[0];
174 const size_t fw_len = firmware_chunk_buf.header.length;
175
176 // Decrypt the chunk.
177 if (hydro_secretbox_decrypt(decrypted_buf, fw_data, fw_len, 0, MBOOT_PACK_HYDRO_CONTEXT, mboot_pack_secretbox_key) != 0) {
178 dfu_context.status = DFU_STATUS_ERROR_VERIFY;
179 dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX;
180 return -MBOOT_ERRNO_PACK_DECRYPT_FAILED;
181 }
182
183 // Use the decrypted message contents going formward.
184 size_t len = fw_len - hydro_secretbox_HEADERBYTES;
185 uint32_t addr = firmware_chunk_buf.header.address;
186
187 if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FW_GZIP) {
188 // Decompress chunk data.
189 gz_stream_init_from_raw_data(decrypted_buf, len);
190 for (;;) {
191 int read = gz_stream_read(sizeof(uncompressed_buf), uncompressed_buf);
192 if (read == 0) {
193 return 0; // finished decompressing
194 } else if (read < 0) {
195 return -MBOOT_ERRNO_GUNZIP_FAILED; // error reading
196 }
197 int ret = mboot_pack_commit_chunk(addr, uncompressed_buf, read);
198 if (ret != 0) {
199 return ret;
200 }
201 addr += read;
202 }
203 } else {
204 // Commit chunk data directly.
205 return mboot_pack_commit_chunk(addr, decrypted_buf, len);
206 }
207 }
208
mboot_pack_write(uint32_t addr,const uint8_t * src8,size_t len)209 int mboot_pack_write(uint32_t addr, const uint8_t *src8, size_t len) {
210 if (addr == APPLICATION_ADDR) {
211 // Base address of main firmware, reset any previous state
212 firmware_chunk_base_addr = 0;
213 }
214
215 if (firmware_chunk_base_addr == 0) {
216 // First piece of data starting a new chunk, so set the base address.
217 firmware_chunk_base_addr = addr;
218 }
219
220 if (addr < firmware_chunk_base_addr) {
221 // Address out of range.
222 firmware_chunk_base_addr = 0;
223 return -MBOOT_ERRNO_PACK_INVALID_ADDR;
224 }
225
226 size_t offset = addr - firmware_chunk_base_addr;
227 if (offset + len > sizeof(firmware_chunk_buf)) {
228 // Address/length out of range.
229 firmware_chunk_base_addr = 0;
230 return -MBOOT_ERRNO_PACK_INVALID_ADDR;
231 }
232
233 // Copy in the new data piece into the chunk buffer.
234 memcpy((uint8_t *)&firmware_chunk_buf + offset, src8, len);
235
236 if (offset + len < sizeof(firmware_chunk_buf.header)) {
237 // Don't have the header yet.
238 return 0;
239 }
240
241 if (firmware_chunk_buf.header.header_vers != MBOOT_PACK_HEADER_VERSION) {
242 // Chunk header has the wrong version.
243 dfu_context.status = DFU_STATUS_ERROR_FILE;
244 dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX;
245 return -MBOOT_ERRNO_PACK_INVALID_VERSION;
246 }
247
248 if (firmware_chunk_buf.header.address != firmware_chunk_base_addr) {
249 // Chunk address doesn't agree with dfu address, abort.
250 dfu_context.status = DFU_STATUS_ERROR_ADDRESS;
251 dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX;
252 return -MBOOT_ERRNO_PACK_INVALID_ADDR;
253 }
254
255 if (offset + len < sizeof(firmware_chunk_buf.header) + firmware_chunk_buf.header.length + sizeof(firmware_chunk_buf.signature)) {
256 // Don't have the full chunk yet.
257 return 0;
258 }
259
260 // Have the full chunk in firmware_chunk_buf, process it now.
261
262 // Reset the chunk base address for the next chunk that comes in.
263 firmware_chunk_base_addr = 0;
264
265 // Verify the signature of the chunk.
266 const size_t fw_len = firmware_chunk_buf.header.length;
267 const uint8_t *sig = &firmware_chunk_buf.data[0] + fw_len;
268 if (hydro_sign_verify(sig, &firmware_chunk_buf, sizeof(firmware_chunk_buf.header) + fw_len,
269 MBOOT_PACK_HYDRO_CONTEXT, mboot_pack_sign_public_key) != 0) {
270 // Signature failed
271 dfu_context.status = DFU_STATUS_ERROR_VERIFY;
272 dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX;
273 return -MBOOT_ERRNO_PACK_SIGN_FAILED;
274 }
275
276 // Signature passed, we have valid chunk.
277
278 if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_META) {
279 // Ignore META chunks.
280 return 0;
281 } else if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FULL_SIG) {
282 return mboot_pack_handle_full_sig();
283 } else if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FW_RAW
284 || firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FW_GZIP) {
285 return mboot_pack_handle_firmware();
286 } else {
287 // Unsupported contents.
288 return -MBOOT_ERRNO_PACK_INVALID_CHUNK;
289 }
290 }
291
292 #endif // MBOOT_ENABLE_PACKING
293