Add firmware signing and verification

This commit is contained in:
cpq
2026-05-28 10:39:45 +01:00
parent 8012a5943a
commit bbcda0c9af
7 changed files with 132 additions and 3 deletions
+16
View File
@@ -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();
}
+80
View File
@@ -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();
+16
View File
@@ -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();
}
@@ -622,7 +622,7 @@ a, a:visited, a:link { text-decoration: none; }
<path d="M5 21h14" />
</svg>
Select .bin file
<input type="file" accept=".bin" hidden data-upload="1" />
<input type="file" accept=".bin" hidden data-ota="1" />
</label>
</div>
@@ -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 $@
@@ -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
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgto6awcVrVzHfgSaD
WFiy6JrXahab3F5lfGvb7A1XzjShRANCAAQZf+2Bi7eypd7g3gSDYMhpYTkwH8FS
EUu3pNdSAzbGwb8idQRJ+EZH7CDWjUgpfWOz9x9YRILMekteaJgkH4b0
-----END PRIVATE KEY-----