arduino-pico/tools/signing.py
Earle F. Philhower, III da86a8942b
Add OTA update support (#711)
Adds a 12K OTA stub 3rd stage bootloader, which reads new firmware
from the LittleFS filesystem and flashes on reboot.

By storing the OTA commands in a file in flash, it is possible to
recover from a power failure during OTA programming.  On power
resume, the OTA block will simply re-program from the beginning.

Support cryptographic signed OTA updates, if desired.  Includes
host-side signing logic via openssl.

Add PicoOTA library which encapsulates the file format for
the updater, including CRC32 checking.

Add LEAmDNS support to allow Arduino IDE discovery

Add ArduinoOTA class for IDE uploads

Add MD5Builder class

Add Updater class which supports writing and validating
cryptographically signed binaries from any source (http,
Ethernet, WiFi, Serial, etc.)

Add documentation and readmes.
2022-08-12 00:26:51 -07:00

87 lines
3.1 KiB
Python
Executable file

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
import argparse
import hashlib
import os
import subprocess
import sys
def parse_args():
parser = argparse.ArgumentParser(description='Binary signing tool')
parser.add_argument('-m', '--mode', help='Mode (header, sign)')
parser.add_argument('-b', '--bin', help='Unsigned binary')
parser.add_argument('-o', '--out', help='Output file');
parser.add_argument('-p', '--publickey', help='Public key file');
parser.add_argument('-s', '--privatekey', help='Private(secret) key file');
return parser.parse_args()
def sign_and_write(data, priv_key, out_file):
"""Signs the data (bytes) with the private key (file path)."""
"""Save the signed firmware to out_file (file path)."""
signcmd = [ 'openssl', 'dgst', '-sha256', '-sign', priv_key ]
proc = subprocess.Popen(signcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
signout, signerr = proc.communicate(input=data)
if proc.returncode:
sys.stderr.write("OpenSSL returned an error signing the binary: " + str(proc.returncode) + "\nSTDERR: " + str(signerr))
else:
with open(out_file, "wb") as out:
out.write(data)
out.write(signout)
out.write(b'\x00\x01\x00\x00')
sys.stderr.write("Signed binary: " + out_file + "\n")
def main():
args = parse_args()
if args.mode == "header":
val = ""
try:
with open(args.publickey, "rb") as f:
pub = f.read()
val += "#include <pgmspace.h>\n"
val += "#define ARDUINO_SIGNING 1\n"
val += "static const char signing_pubkey[] PROGMEM = {\n"
for i in bytearray(pub):
val += "0x%02x, \n" % i
val = val[:-3]
val +="\n};\n"
sys.stderr.write("Enabling binary signing\n")
except IOError:
# Silence the default case to avoid people thinking something is wrong.
# Only people who care about signing will know what it means, anyway,
# and they can check for the positive acknowledgement above.
# sys.stderr.write("Not enabling binary signing\n")
val += "#define ARDUINO_SIGNING 0\n"
outdir = os.path.dirname(args.out)
if not os.path.exists(outdir):
os.makedirs(outdir)
try:
with open(args.out, "r") as inp:
old_val = inp.read()
if old_val == val:
return
except Exception:
pass
with open(args.out, "w") as f:
f.write(val)
return 0
elif args.mode == "sign":
if not os.path.isfile(args.privatekey):
return
try:
with open(args.bin, "rb") as b:
bin = b.read()
sign_and_write(bin, args.privatekey, args.out)
except Exception as e:
sys.stderr.write(str(e))
sys.stderr.write("Not signing the generated binary\n")
return 0
else:
sys.stderr.write("ERROR: Mode not specified as header or sign\n")
if __name__ == '__main__':
sys.exit(main())