feat(tools): add ECDSA P-256 key generation tool and public key header
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>
This commit is contained in:
57
tools/gen_signing_key.py
Normal file
57
tools/gen_signing_key.py
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user