"""Hardware ID binding and activation token -- Sprint 8. The 6-byte ESP32 MAC (read via Modbus INPUT_HWID_MAC_01/23/45) is used to bind a project license to a specific hardware unit. Activation token format ----------------------- The factory generates a token by HMAC-SHA256(key=SECRET, msg=hwid_hex), where ``hwid_hex`` is the 12-character lower-case hex representation of the 6-byte MAC. The token is the first 16 bytes (32 hex chars) of the HMAC output. The SECRET is a per-product deployment key embedded in the Studio binary (not in the open-source firmware). This file ships the *verification* side only; token generation happens offline in the factory tooling. For development / local testing a deterministic stub secret is used (STUB_SECRET_KEY). The production key must be injected via the environment variable ``AR_ACTIVATION_KEY``. """ from __future__ import annotations import hashlib import hmac import os STUB_SECRET_KEY = b"AR-Autopilot-Dev-Key-2026" TOKEN_BYTES = 16 # 32 hex chars def _get_secret() -> bytes: key = os.environ.get("AR_ACTIVATION_KEY", "").encode() return key if key else STUB_SECRET_KEY def hwid_from_mac_words(mac01: int, mac23: int, mac45: int) -> str: """Convert three 16-bit Modbus words to a 12-char lower-case hex HWID string.""" mac = bytes([ (mac01 >> 8) & 0xFF, mac01 & 0xFF, (mac23 >> 8) & 0xFF, mac23 & 0xFF, (mac45 >> 8) & 0xFF, mac45 & 0xFF, ]) return mac.hex() def generate_token(hwid_hex: str) -> str: """Generate the activation token for a given HWID. Should only be called by factory tooling. In the Studio this is called only during development / bench testing with the stub key. """ h = hmac.new(_get_secret(), hwid_hex.lower().encode(), hashlib.sha256) return h.hexdigest()[:TOKEN_BYTES * 2] def verify_token(hwid_hex: str, token: str) -> bool: """Return True iff the token is valid for the given HWID. Uses constant-time comparison to resist timing attacks. """ expected = generate_token(hwid_hex) return hmac.compare_digest(expected.lower(), token.lower()) def format_hwid(hwid_hex: str) -> str: """Format a 12-char hex HWID as 'AA:BB:CC:DD:EE:FF'.""" if len(hwid_hex) != 12: raise ValueError(f"HWID must be 12 hex chars, got {len(hwid_hex)}") h = hwid_hex.upper() return ":".join(h[i:i+2] for i in range(0, 12, 2))