Spaces:
Sleeping
Sleeping
| """ | |
| Hardware handshake: HMAC-based device authentication. | |
| A device proves its identity by computing an HMAC over a server-issued | |
| challenge using the shared hardware ID as the key. The hardware ID is read | |
| from the ``ARK_S10_HWID`` environment variable. | |
| This implementation uses :func:`hmac.compare_digest` for constant-time | |
| comparison to avoid timing side-channels. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import hmac | |
| import os | |
| import secrets | |
| from typing import Optional | |
| from .constants import DEFAULT_HMAC_ALGORITHM | |
| HWID_ENV_VAR = "ARK_S10_HWID" | |
| class DeviceAuthError(Exception): | |
| """Raised when device authentication fails or cannot be performed.""" | |
| class HardwareHandshake: | |
| """Perform HMAC-based device authentication. | |
| Parameters | |
| ---------- | |
| hwid: | |
| Shared hardware identifier used as the HMAC key. If ``None`` (the | |
| default) the value is read from the ``ARK_S10_HWID`` environment | |
| variable. | |
| algorithm: | |
| Hash algorithm name accepted by :mod:`hashlib`. Defaults to | |
| ``"sha256"``. | |
| """ | |
| def __init__( | |
| self, | |
| hwid: Optional[str] = None, | |
| algorithm: str = DEFAULT_HMAC_ALGORITHM, | |
| ) -> None: | |
| resolved = hwid if hwid is not None else os.environ.get(HWID_ENV_VAR) | |
| if not resolved: | |
| raise DeviceAuthError( | |
| f"hardware ID not provided and {HWID_ENV_VAR} is not set" | |
| ) | |
| if algorithm not in hashlib.algorithms_available: | |
| raise DeviceAuthError(f"unsupported hash algorithm: {algorithm!r}") | |
| self._key = resolved.encode("utf-8") | |
| self.algorithm = algorithm | |
| def generate_challenge(nbytes: int = 32) -> bytes: | |
| """Return a cryptographically random challenge of ``nbytes`` bytes.""" | |
| if nbytes <= 0: | |
| raise ValueError("nbytes must be positive") | |
| return secrets.token_bytes(nbytes) | |
| def sign(self, challenge: bytes) -> str: | |
| """Compute the hex-encoded HMAC of ``challenge``.""" | |
| if not isinstance(challenge, (bytes, bytearray)): | |
| raise TypeError("challenge must be bytes") | |
| return hmac.new(self._key, bytes(challenge), self.algorithm).hexdigest() | |
| def verify(self, challenge: bytes, signature: str) -> bool: | |
| """Return ``True`` iff ``signature`` is a valid HMAC for ``challenge``. | |
| Comparison is performed in constant time. | |
| """ | |
| if not isinstance(signature, str): | |
| return False | |
| expected = self.sign(challenge) | |
| return hmac.compare_digest(expected, signature) | |