Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions sagemcom_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
API_ENDPOINT,
DEFAULT_TIMEOUT,
DEFAULT_USER_AGENT,
UINT_MAX,
XMO_ACCESS_RESTRICTION_ERR,
XMO_AUTHENTICATION_ERR,
XMO_INVALID_SESSION_ERR,
Expand Down Expand Up @@ -91,10 +92,10 @@ def __init__(
self.username = username
self.authentication_method = authentication_method
self.password = password
self._current_nonce = None
self._password_hash = self.__generate_hash(password)
self.protocol = "https" if ssl else "http"

self._current_nonce = None
self._server_nonce = ""
self._session_id = 0
self._request_id = -1
Expand Down Expand Up @@ -126,14 +127,31 @@ async def close(self) -> None:
"""Close the websession."""
await self.session.close()

def __generate_nonce(self):
def __generate_nonce(self, upper_limit=500000):
"""Generate pseudo random number (nonce) to avoid replay attacks."""
self._current_nonce = math.floor(random.randrange(0, 500000))
self._current_nonce = math.floor(random.randrange(0, upper_limit))

def __generate_request_id(self):
"""Generate sequential request ID."""
self._request_id += 1

def __generate_md5_nonce_hash(self):
"""Build MD5 with nonce hash token. UINT_MAX is hardcoded in the firmware."""

def md5(input_string):
return hashlib.md5(input_string.encode()).hexdigest()

n = (
self.__generate_nonce(UINT_MAX)
if self._current_nonce is None
else self._current_nonce
)
f = 0
l_nonce = ""
ha1 = md5(self.username + ":" + l_nonce + ":" + md5(self.password))

return md5(ha1 + ":" + str(f) + ":" + str(n) + ":JSON:/cgi/json-req")

def __generate_hash(self, value, authentication_method=None):
"""Hash value with selected encryption method and return HEX value."""
auth_method = authentication_method or self.authentication_method
Expand All @@ -146,6 +164,9 @@ def __generate_hash(self, value, authentication_method=None):
if auth_method == EncryptionMethod.SHA512:
return hashlib.sha512(bytes_object).hexdigest()

if auth_method == EncryptionMethod.MD5_NONCE:
return self.__generate_md5_nonce_hash()

return value

def __get_credential_hash(self):
Expand Down
2 changes: 2 additions & 0 deletions sagemcom_api/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@
XMO_UNKNOWN_PATH_ERR = "XMO_UNKNOWN_PATH_ERR"
XMO_MAX_SESSION_COUNT_ERR = "XMO_MAX_SESSION_COUNT_ERR"
XMO_LOGIN_RETRY_ERR = "XMO_LOGIN_RETRY_ERR"

UINT_MAX = 4294967295
1 change: 1 addition & 0 deletions sagemcom_api/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class EncryptionMethod(StrEnum):
"""Encryption method defining the password hash."""

MD5 = "MD5"
MD5_NONCE = "MD5_NONCE"
SHA512 = "SHA512"