mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-12 10:13:29 +00:00
Add firmware signing and verification
This commit is contained in:
+16
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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-----
|
||||
Reference in New Issue
Block a user