diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index f5ee81a..b825e58 100644 --- a/sagemcom_api/client.py +++ b/sagemcom_api/client.py @@ -27,6 +27,7 @@ API_ENDPOINT, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, + UINT_MAX, XMO_ACCESS_RESTRICTION_ERR, XMO_AUTHENTICATION_ERR, XMO_INVALID_SESSION_ERR, @@ -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 @@ -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 @@ -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): diff --git a/sagemcom_api/const.py b/sagemcom_api/const.py index f6a126a..4e35a4e 100644 --- a/sagemcom_api/const.py +++ b/sagemcom_api/const.py @@ -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 diff --git a/sagemcom_api/enums.py b/sagemcom_api/enums.py index 757b575..6025b59 100644 --- a/sagemcom_api/enums.py +++ b/sagemcom_api/enums.py @@ -16,4 +16,5 @@ class EncryptionMethod(StrEnum): """Encryption method defining the password hash.""" MD5 = "MD5" + MD5_NONCE = "MD5_NONCE" SHA512 = "SHA512"