Using the stm32-secure-patching-bootloader as the back-end firmware update engine, this design feeds digitally signed and encrypted firmware update patch or full-image files through the HTML multipart/form-data file upload method. This method is natively supported in most browsers. The back-end multipart form processing has been integrated into an existing FreeRTOS and LwIP-netconn-enabled web server application.
This designs runs on a NUCLEO-F429ZI development board. It is inexpensive, readily available and includes USB OTG and Ethernet ports.
You can find the design sources right here.
What is multipart form file upload? How does it work?
The following HTML code is inserted into a webpage and served up to a browser. The browser automatically generates a file chooser button and binds another button to a form submit action (generates the POST request when pressed). No javascript is required.
<form id="form" action="upload" method="post" enctype="multipart/form-data">
<input id="file" type="file" name="binary" accept=".sfb,.sfbp"/>
<button type="submit">upload</button>
</form>
The server will see an HTTP POST request at the /upload URL. There are a few of the usual HTTP protocol headers in the request including the expected length (file octet data plus HTTP data), and Content-type indicators. Each "part" that came from the form will have its own Content-type indicator in the header. What we're looking for is the Content-Type: application/octet-stream followed by the HTTP header terminator of "\r\n". All data received in the LwIP buffers after this are firmware update file bytes and consumed as such.
POST /upload HTTP/1.1\r\n
...
Content-Length: 129773\r\n
...
Content-Type: multipart/form-data;
...
Content-Type: application/octet-stream\r\n
\r\n
In the web server source code (httpserver-netconn.c), I've built a state machine that parses this data stream and uses the SE_PATCH_Init and SE_PATCH_Data APIs appropriately.
Essentially, when the HTTP header has been found and verified, the SE_PATCH_Init function is called. This function just resets and readies the patching engine internal state machine. Then all bytes received thereafter as part of the "octet-stream" are fed into the SE_PATCH_Data function. The patching engine manages the byte counters and all of the details of erasing and writing to flash and performing the patching (if using an.sfbp file) so there's really nothing to do for updating firmware beyond correctly parsing the received network packets within the web server processing loop.
The patching engine first verifies the firmware update file header (256 bytes) with ECDSA signature verification before it will accept any further bytes. When it is satisfied that a valid firmware update file is on its way, it will accumulate bytes and either write them to the download slot (using.sfb file) or combine them with the existing firmware image to re-create the new firmware image in the download slot (using.sfbp file) in internal flash.
When all bytes have been correctly received and processed, the patching engine can either reboot immediately or manually by the user. I've chosen to reboot manually so that a "success" message can be delivered back to the client browser and the connection closed before rebooting. The "success" page has a redirect timer included to reload the firmware update page automatically after a delay to show the updated firmware version.
The reboot is required because it is the stm32-secure-patching-bootloader that actually takes the update image candidate in the download slot (SLOT1) and overwrites the active firmware image in SLOT0. It only does this if all the signatures, decryptions and SHA256 hashes all check out, making it super robust and great for any packetized delivery framework like ethernet, wifi, bluetooth, lora, lorawan, CAN, modbus, UART and so on.
In the repository, you can use pre-built files in the bin directory to test the update immediately or you can build the update files yourself with STM32CubeIDE and the included project files. More instructions on how to do that is in the project readme.txt.
Using the Web Update Interface
[ 0.000] Bootloader starting up.STM32 Secure Patching Bootloader. github.com/firmwaremodules/stm32-secure-patching-bootloader Build: v1.3.0Registered to: unregisteredTarget: NUCLEO-F429ZIUID: 175053388a356f20Clock:HSE,8,168 Crypto:SW UART:3,115200,N81SLOT0:08020000 SLOT1:08060000 SIZE:40000APP RAMSTART:20002700 VTOR:08020200[ 0.029] Target check: CPUID:410FC241 IDCODE:20036419 FLASHSZ:0800 .. OK.[ 0.288] Verify bootloader.[ 0.315] SHA256: 3e8790d4724c52e5f5f2352bc6b132f1c8320134e0c89de983b98904c80e73a5 Valid[ 0.323] UART loader check trigger.[ 1.328] Check USB flash media.[ 2.330] No valid firmware found on flash media, status=1[ 2.336] Verify slot 1 header.[ 2.339] Slot 1 is empty.[ 2.341] Verify slot 0.[ 2.343] Verify slot 0 header.[ 2.484] Verify slot 0 signature.[ 2.535] Verify slot 0 ready.[ 2.538] Verify slot 0 fw ver.[ 2.679] Slot 0 has valid active firmware version 1.0.0[ 2.684] Preparing to launch application in slot 0.LwIP_HTTP_Server_Netconn_RTOS - NUCLEO-F429ZI Built FW_UPDATE_VERSION=1Firmware Version: 1.0.0Bootloader Version: v1.3.0State: Looking for DHCP server ...IP address assigned by a DHCP server: 192.168.1.75