mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-10 16:31:44 +00:00
Add firmware signing and verification
This commit is contained in:
1 parent
8012a5943a
commit
bbcda0c9af
7 files changed
+132
-3
No files matched your search
+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
|
#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;
|
if (size == s_size && crc32 == s_crc32) ok = true;
|
||||||
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
|
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
|
||||||
size, ok ? "ok" : "fail"));
|
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;
|
s_size = 0;
|
||||||
if (ok) ok = flash->swap_fn();
|
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 "flash.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "ota.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
|
#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;
|
if (size == s_size && crc32 == s_crc32) ok = true;
|
||||||
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
|
MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
|
||||||
size, ok ? "ok" : "fail"));
|
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;
|
s_size = 0;
|
||||||
if (ok) ok = flash->swap_fn();
|
if (ok) ok = flash->swap_fn();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -622,7 +622,7 @@ a, a:visited, a:link { text-decoration: none; }
|
|||||||
<path d="M5 21h14" />
|
<path d="M5 21h14" />
|
||||||
</svg>
|
</svg>
|
||||||
Select .bin file
|
Select .bin file
|
||||||
<input type="file" accept=".bin" hidden data-upload="1" />
|
<input type="file" accept=".bin" hidden data-ota="1" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ SOURCES = main.c hal.c
|
|||||||
SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s
|
SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s
|
||||||
SOURCES += mongoose/mongoose.c
|
SOURCES += mongoose/mongoose.c
|
||||||
|
|
||||||
|
|
||||||
all build example: firmware.bin
|
all build example: firmware.bin
|
||||||
|
|
||||||
mongoose/mongoose.c mongoose/mongoose.h:
|
mongoose/mongoose.c mongoose/mongoose.h:
|
||||||
@@ -26,6 +25,9 @@ firmware.bin: firmware.elf
|
|||||||
@echo
|
@echo
|
||||||
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
|
@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
|
flash: firmware.bin
|
||||||
STM32_Programmer_CLI -c port=swd mode=UR -w firmware.elf -hardRst
|
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:
|
gdb_server:
|
||||||
ST-LINK_gdbserver --port-number 61234 --log-level 1 --swd --verify --stm32cubeprogrammer-path $(CUBEPROG_DIR) --apid 1 --initialize-reset
|
ST-LINK_gdbserver --port-number 61234 --log-level 1 --swd --verify --stm32cubeprogrammer-path $(CUBEPROG_DIR) --apid 1 --initialize-reset
|
||||||
|
|
||||||
|
|
||||||
cmsis_core:
|
cmsis_core:
|
||||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
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_PACKED_FS 1
|
||||||
#define MG_ENABLE_DRIVER_STM32H 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_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 4
|
||||||
// #define MG_TCPIP_PHY_ADDR 0 // PHY address
|
// #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