fix: tighten version parsing, propagate HMAC sign failure, add deployment docs

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 11:26:44 -07:00
parent 5cf122b922
commit 5ec678dfa3
3 changed files with 44 additions and 14 deletions

View File

@@ -2,18 +2,29 @@
"""
OTA firmware update endpoints.
To register in the server main app:
from server.ota_endpoint import router as ota_router
app.include_router(ota_router)
Deployment workflow:
1. Generate signing key (one-time):
python tools/gen_signing_key.py
→ secrets/firmware_signing_key.pem (keep offline)
→ firmware/lib/ota_updater/ota_pubkey.h (commit this)
Route handlers have HMAC auth commented out pending import-path confirmation:
from .main import verify_device_hmac # adjust to actual module
Then uncomment the Depends lines in ota_check() and ota_firmware().
2. Build and deploy a new firmware version:
pio run -e timercam # build
python tools/deploy_firmware.py \\
firmware/.pio/build/timercam/firmware.bin 1.2.3
→ server/firmware/ updated with current.bin, current.sig, manifest.json
Firmware artifacts expected under FIRMWARE_DIR (default: server/firmware/):
current.bin — raw firmware binary
current.sig — 64-byte r‖s Ed25519/ECDSA signature
manifest.json — {"version": "X.Y.Z", "size": N, "sha256": "hex..."}
3. Bump FW_VERSION in firmware/include/version.h before each release.
4. Register in server main app:
from server.ota_endpoint import router as ota_router
app.include_router(ota_router)
Also uncomment Depends(verify_device_hmac) on both route handlers
and confirm the HMAC format matches hmac.cpp:
method + "\\n" + path + "\\n" + timestamp + "\\n" + sha256_hex(body)
Note: HMAC auth is currently commented out on route handlers — must be wired
before production use. verify_device_hmac must use the same format as hmac.cpp.
"""
import base64
import json
@@ -35,6 +46,8 @@ def _parse_version(v: str) -> tuple:
"""Parse semver string to comparable tuple; returns (0,0,0) on malformed input."""
try:
parts = v.strip().split(".")
if len(parts) != 3:
return (0, 0, 0)
return tuple(int(x) for x in parts)
except (ValueError, AttributeError):
return (0, 0, 0)