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-----