diff --git a/mongoose.c b/mongoose.c
index d41cd15e..f8a732f2 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -1476,6 +1476,10 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) {
+#ifdef MG_OTA_PUBLIC_KEY
+
+
+#endif
#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM
@@ -1533,6 +1537,18 @@ bool mg_ota_flash_end(struct mg_flash *flash) {
if (size == s_size && crc32 == s_crc32) ok = true;
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
size, ok ? "ok" : "fail"));
+#ifdef MG_OTA_PUBLIC_KEY
+ if (ok && s_size > 64) {
+ static const uint8_t s_pubkey[] = MG_OTA_PUBLIC_KEY;
+ uint8_t hash[32];
+ size_t fw_size = s_size - 64;
+ mg_sha256(hash, (uint8_t *) base, fw_size);
+ ok = mg_uecc_verify(s_pubkey, hash, sizeof(hash),
+ (uint8_t *) base + fw_size,
+ mg_uecc_secp256r1()) == 1;
+ MG_INFO(("Signature: %s", ok ? "ok" : "fail"));
+ }
+#endif
s_size = 0;
if (ok) ok = flash->swap_fn();
}
diff --git a/resources/sign.js b/resources/sign.js
new file mode 100644
index 00000000..8f8771af
--- /dev/null
+++ b/resources/sign.js
@@ -0,0 +1,80 @@
+#!/usr/bin/env node
+// OTA firmware signing tool. No npm dependencies required.
+// Usage:
+// node sign.js keygen # generate private.pem + print public key #define
+// node sign.js sign firmware.bin # append 64-byte ECDSA-P256 signature
+
+'use strict';
+
+const crypto = require('crypto');
+const fs = require('fs');
+const path = require('path');
+
+function usage() {
+ console.error('Usage:');
+ console.error(' node sign.js keygen # generate key pair');
+ console.error(' node sign.js sign FIRMWARE.bin # sign firmware');
+ process.exit(1);
+}
+
+// DER-encoded ECDSA signature -> raw 64-byte R||S
+function derToRaw(der) {
+ let i = 2; // skip SEQUENCE tag + length
+ i++; // skip INTEGER tag
+ const rLen = der[i++];
+ const r = der.slice(i, i + rLen); i += rLen;
+ i++; // skip INTEGER tag
+ const sLen = der[i++];
+ const s = der.slice(i, i + sLen);
+ return Buffer.concat([to32(r), to32(s)]);
+}
+
+// Normalize a DER integer to exactly 32 bytes (strip leading 0x00, zero-pad)
+function to32(b) {
+ while (b.length > 32 && b[0] === 0) b = b.slice(1);
+ const out = Buffer.alloc(32, 0);
+ b.copy(out, 32 - b.length);
+ return out;
+}
+
+function keygen() {
+ const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
+ namedCurve: 'P-256',
+ });
+
+ fs.writeFileSync('private.pem', privateKey.export({ type: 'pkcs8', format: 'pem' }));
+ console.log('Wrote private.pem (keep secret, never ship in firmware)');
+
+ // SPKI DER for P-256 ends with: 04 [X 32 bytes] [Y 32 bytes]
+ // We want the raw 64-byte X||Y (no 0x04 prefix) for mg_uecc_verify
+ const spki = publicKey.export({ type: 'spki', format: 'der' });
+ const raw = spki.slice(-64);
+
+ const hex = Array.from(raw).map(b => '0x' + b.toString(16).padStart(2, '0'));
+ const rows = [];
+ for (let i = 0; i < 64; i += 8)
+ rows.push(' ' + hex.slice(i, i + 8).join(', '));
+
+ console.log('\nAdd to your mongoose_config.h:');
+ console.log(`#define MG_OTA_PUBLIC_KEY { \\\n${rows.join(', \\\n')}, \\\n}`);
+}
+
+function sign(firmware, privateKeyFile = 'private.pem') {
+ const fw = fs.readFileSync(firmware);
+ const privateKey = crypto.createPrivateKey(fs.readFileSync(privateKeyFile));
+ const derSig = crypto.sign('SHA256', fw, privateKey);
+ const raw = derToRaw(derSig); // 64 bytes: R||S
+
+ const dot = firmware.lastIndexOf('.');
+ const out = dot >= 0
+ ? firmware.slice(0, dot) + '.signed' + firmware.slice(dot)
+ : firmware + '.signed';
+
+ fs.writeFileSync(out, Buffer.concat([fw, raw]));
+ console.log(`Signed: ${out} (${fw.length} bytes firmware + 64 bytes signature)`);
+}
+
+const [,, cmd, arg1, arg2] = process.argv;
+if (cmd === 'keygen') keygen();
+else if (cmd === 'sign' && arg1) sign(arg1, arg2);
+else usage();
diff --git a/src/flash.c b/src/flash.c
index 5213511d..c009ded1 100644
--- a/src/flash.c
+++ b/src/flash.c
@@ -2,6 +2,10 @@
#include "flash.h"
#include "log.h"
#include "ota.h"
+#ifdef MG_OTA_PUBLIC_KEY
+#include "sha256.h"
+#include "tls_uecc.h"
+#endif
#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM
@@ -59,6 +63,18 @@ bool mg_ota_flash_end(struct mg_flash *flash) {
if (size == s_size && crc32 == s_crc32) ok = true;
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
size, ok ? "ok" : "fail"));
+#ifdef MG_OTA_PUBLIC_KEY
+ if (ok && s_size > 64) {
+ static const uint8_t s_pubkey[] = MG_OTA_PUBLIC_KEY;
+ uint8_t hash[32];
+ size_t fw_size = s_size - 64;
+ mg_sha256(hash, (uint8_t *) base, fw_size);
+ ok = mg_uecc_verify(s_pubkey, hash, sizeof(hash),
+ (uint8_t *) base + fw_size,
+ mg_uecc_secp256r1()) == 1;
+ MG_INFO(("Signature: %s", ok ? "ok" : "fail"));
+ }
+#endif
s_size = 0;
if (ok) ok = flash->swap_fn();
}
diff --git a/tutorials/device-dashboard/full/dashboard.html b/tutorials/device-dashboard/full/dashboard.html
index 08606eb7..e599e50a 100644
--- a/tutorials/device-dashboard/full/dashboard.html
+++ b/tutorials/device-dashboard/full/dashboard.html
@@ -622,7 +622,7 @@ a, a:visited, a:link { text-decoration: none; }
Select .bin file
-
+
diff --git a/tutorials/stm32/nucleo-h563zi/minimal/Makefile b/tutorials/stm32/nucleo-h563zi/minimal/Makefile
index a9430bdf..9f5dc0c3 100644
--- a/tutorials/stm32/nucleo-h563zi/minimal/Makefile
+++ b/tutorials/stm32/nucleo-h563zi/minimal/Makefile
@@ -12,7 +12,6 @@ SOURCES = main.c hal.c
SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s
SOURCES += mongoose/mongoose.c
-
all build example: firmware.bin
mongoose/mongoose.c mongoose/mongoose.h:
@@ -26,6 +25,9 @@ firmware.bin: firmware.elf
@echo
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
+sign firmware.signed.bin: firmware.bin
+ node ../../../../resources/sign.js sign firmware.bin private.pem
+
flash: firmware.bin
STM32_Programmer_CLI -c port=swd mode=UR -w firmware.elf -hardRst
@@ -37,7 +39,6 @@ CUBEPROG_DIR ?= $(dir $(shell which STM32_Programmer_CLI))
gdb_server:
ST-LINK_gdbserver --port-number 61234 --log-level 1 --swd --verify --stm32cubeprogrammer-path $(CUBEPROG_DIR) --apid 1 --initialize-reset
-
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
diff --git a/tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h
index dc0ca30b..f482e955 100644
--- a/tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h
+++ b/tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h
@@ -12,6 +12,17 @@
#define MG_ENABLE_PACKED_FS 1
#define MG_ENABLE_DRIVER_STM32H 1
+#define MG_OTA_PUBLIC_KEY { \
+ 0x19, 0x7f, 0xed, 0x81, 0x8b, 0xb7, 0xb2, 0xa5, \
+ 0xde, 0xe0, 0xde, 0x04, 0x83, 0x60, 0xc8, 0x69, \
+ 0x61, 0x39, 0x30, 0x1f, 0xc1, 0x52, 0x11, 0x4b, \
+ 0xb7, 0xa4, 0xd7, 0x52, 0x03, 0x36, 0xc6, 0xc1, \
+ 0xbf, 0x22, 0x75, 0x04, 0x49, 0xf8, 0x46, 0x47, \
+ 0xec, 0x20, 0xd6, 0x8d, 0x48, 0x29, 0x7d, 0x63, \
+ 0xb3, 0xf7, 0x1f, 0x58, 0x44, 0x82, 0xcc, 0x7a, \
+ 0x4b, 0x5e, 0x68, 0x98, 0x24, 0x1f, 0x86, 0xf4, \
+}
+
// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 4
// #define MG_TCPIP_PHY_ADDR 0 // PHY address
diff --git a/tutorials/stm32/nucleo-h563zi/minimal/private.pem b/tutorials/stm32/nucleo-h563zi/minimal/private.pem
new file mode 100644
index 00000000..b8c6698d
--- /dev/null
+++ b/tutorials/stm32/nucleo-h563zi/minimal/private.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgto6awcVrVzHfgSaD
+WFiy6JrXahab3F5lfGvb7A1XzjShRANCAAQZf+2Bi7eypd7g3gSDYMhpYTkwH8FS
+EUu3pNdSAzbGwb8idQRJ+EZH7CDWjUgpfWOz9x9YRILMekteaJgkH4b0
+-----END PRIVATE KEY-----