Files
roro9stack/scripts/make_ota.py
T
twislaandClaude Opus 5.5 90cb6ef9b2 OTA step 2: signing key, Update File builder, push client
- scripts/ota_keygen.sh: ECDSA P-256 key pair; the private key goes to
  ~/.config/roro9stack/ (0600), the public key to keys/ and
  src/platform/ota_public_key.h; .gitignore refuses *key.pem
- scripts/make_ota.py: wraps firmware.bin into a signed .ota (openssl)
- scripts/ota_push.py: sends it over TCP 3232, prints the device's answer
- scripts/flash.sh --ota <host>: build, sign, push

Checked: a generated .ota has the documented layout and its signature
verifies with openssl against the committed public key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:31:39 +02:00

51 lines
1.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Wraps a firmware image into a signed Update File (.ota). See lib/ota/src/update_parser.h.
Usage: scripts/make_ota.py <firmware.bin> <version> <out.ota> [private key]
Signs with openssl (ECDSA P-256 over SHA-256 of the header's first 80 bytes).
"""
import hashlib
import os
import struct
import subprocess
import sys
import tempfile
HEADER_SIZE = 160
SIGNED_BYTES = 80
MAX_SIGNATURE = 72
def main():
if len(sys.argv) < 4:
sys.exit(__doc__)
image_path, version, out_path = sys.argv[1:4]
key = sys.argv[4] if len(sys.argv) > 4 else os.environ.get(
"RORO_OTA_KEY", os.path.expanduser("~/.config/roro9stack/ota-key.pem"))
if not os.path.exists(key):
sys.exit(f"No signing key at {key}: run scripts/ota_keygen.sh first.")
image = open(image_path, "rb").read()
version_bytes = version.encode()[:31]
signed = (b"RORO-OTA" + struct.pack("<HHI", 1, HEADER_SIZE, len(image)) +
hashlib.sha256(image).digest() + version_bytes.ljust(32, b"\0"))
assert len(signed) == SIGNED_BYTES
with tempfile.NamedTemporaryFile() as f:
f.write(signed)
f.flush()
signature = subprocess.run(["openssl", "dgst", "-sha256", "-sign", key, f.name],
check=True, capture_output=True).stdout
if len(signature) > MAX_SIGNATURE:
sys.exit("unexpected signature size")
header = signed + struct.pack("<H", len(signature)) + signature
header = header.ljust(HEADER_SIZE, b"\0")
with open(out_path, "wb") as out:
out.write(header + image)
print(f"{out_path}: {version}, {len(image)} bytes, signed")
if __name__ == "__main__":
main()