Files
2026-06-09 11:56:40 +01:00

81 lines
2.8 KiB
JavaScript

#!/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, Buffer.from('MGSG')]));
console.log(`Signed: ${out} (${fw.length} bytes firmware + 64 bytes signature + 4 bytes magic)`);
}
const [,, cmd, arg1, arg2] = process.argv;
if (cmd === 'keygen') keygen();
else if (cmd === 'sign' && arg1) sign(arg1, arg2);
else usage();