Generates firmware signing keypair; private key stays in gitignored secrets/, public key written as 65-byte C array to firmware/lib/ota_updater/ota_pubkey.h for compile-time OTA verification. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate ECDSA P-256 signing keypair for OTA firmware verification."""
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
from cryptography.hazmat.primitives import serialization
|
|
|
|
|
|
def generate(secrets_dir: Path, header_out: Path) -> None:
|
|
secrets_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
key = ec.generate_private_key(ec.SECP256R1())
|
|
|
|
pem = key.private_bytes(
|
|
encoding=serialization.Encoding.PEM,
|
|
format=serialization.PrivateFormat.PKCS8,
|
|
encryption_algorithm=serialization.NoEncryption(),
|
|
)
|
|
key_path = secrets_dir / "firmware_signing_key.pem"
|
|
key_path.write_bytes(pem)
|
|
key_path.chmod(0o600)
|
|
|
|
pub_bytes = key.public_key().public_bytes(
|
|
encoding=serialization.Encoding.X962,
|
|
format=serialization.PublicFormat.UncompressedPoint,
|
|
)
|
|
assert len(pub_bytes) == 65 and pub_bytes[0] == 0x04
|
|
|
|
hex_values = ", ".join(f"0x{b:02x}" for b in pub_bytes)
|
|
header = (
|
|
"#pragma once\n"
|
|
"// Auto-generated by tools/gen_signing_key.py — DO NOT EDIT\n"
|
|
"// ECDSA P-256 public key, uncompressed X9.62 (04 || X || Y)\n"
|
|
f"static const uint8_t kOtaPublicKey[65] = {{{hex_values}}};\n"
|
|
)
|
|
header_out.parent.mkdir(parents=True, exist_ok=True)
|
|
header_out.write_text(header)
|
|
|
|
print(f"Private key → {key_path}")
|
|
print(f"Public key header → {header_out}")
|
|
|
|
|
|
def main() -> None:
|
|
p = argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument("--secrets-dir", default="secrets",
|
|
help="Directory for private key (default: secrets/)")
|
|
p.add_argument("--header-out",
|
|
default="firmware/lib/ota_updater/ota_pubkey.h",
|
|
help="Path to write the C header")
|
|
args = p.parse_args()
|
|
generate(Path(args.secrets_dir), Path(args.header_out))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|