From 968065893ec164ee4dfe6afff79275c951df741f Mon Sep 17 00:00:00 2001 From: bunnis Date: Fri, 4 Oct 2024 19:50:47 +0000 Subject: [PATCH 1/4] added new EncryptionMethod MD5_NONCE --- sagemcom_api/client.py | 26 +++++++++++++++++++++++--- sagemcom_api/enums.py | 1 + 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index f5ee81a..10706d3 100644 --- a/sagemcom_api/client.py +++ b/sagemcom_api/client.py @@ -91,10 +91,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 +126,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)) + if self.authentication_method == EncryptionMethod.MD5_NONCE: + UINTMAX = 4294967295 + upper_limit = UINTMAX + 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. UINTMAX is hardcoded in the firmware.""" + UINTMAX = 4294967295 + + def md5(input_string): + return hashlib.md5(input_string.encode()).hexdigest() + + n = self.__generate_nonce(UINTMAX) if self._current_nonce == 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 +163,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/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" From 35ea98f230aa212b4ed934d28fc45d3c26c67131 Mon Sep 17 00:00:00 2001 From: bunnis Date: Mon, 7 Oct 2024 16:38:01 +0000 Subject: [PATCH 2/4] removed leftover code from __generate_nonce --- sagemcom_api/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index 10706d3..c49b3bd 100644 --- a/sagemcom_api/client.py +++ b/sagemcom_api/client.py @@ -128,9 +128,6 @@ async def close(self) -> None: def __generate_nonce(self, upper_limit=500000): """Generate pseudo random number (nonce) to avoid replay attacks.""" - if self.authentication_method == EncryptionMethod.MD5_NONCE: - UINTMAX = 4294967295 - upper_limit = UINTMAX self._current_nonce = math.floor(random.randrange(0, upper_limit)) def __generate_request_id(self): From e01ea9200eb7f992c1c130c184a2b238bd51c91f Mon Sep 17 00:00:00 2001 From: Mick Date: Tue, 15 Oct 2024 16:22:21 +0000 Subject: [PATCH 3/4] Fix sagemcom_api/client.py:146:36: E711 comparison to None should be 'if cond is None:' --- sagemcom_api/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index c49b3bd..ee9d57a 100644 --- a/sagemcom_api/client.py +++ b/sagemcom_api/client.py @@ -141,9 +141,13 @@ def __generate_md5_nonce_hash(self): def md5(input_string): return hashlib.md5(input_string.encode()).hexdigest() - n = self.__generate_nonce(UINTMAX) if self._current_nonce == None else self._current_nonce + n = ( + self.__generate_nonce(UINTMAX) + if self._current_nonce is None + else self._current_nonce + ) f = 0 - l_nonce = "" + l_nonce = "" ha1 = md5(self.username + ":" + l_nonce + ":" + md5(self.password)) return md5(ha1 + ":" + str(f) + ":" + str(n) + ":JSON:/cgi/json-req") From c77e37bcf39d358696771dcd795cab1d568f58ba Mon Sep 17 00:00:00 2001 From: Mick Date: Tue, 15 Oct 2024 16:24:22 +0000 Subject: [PATCH 4/4] sagemcom_api/client.py:139:8: C0103: Variable name UINTMAX doesn't conform to snake_case naming style (invalid-name) --- sagemcom_api/client.py | 6 +++--- sagemcom_api/const.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index ee9d57a..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, @@ -135,14 +136,13 @@ def __generate_request_id(self): self._request_id += 1 def __generate_md5_nonce_hash(self): - """Build MD5 with nonce hash token. UINTMAX is hardcoded in the firmware.""" - UINTMAX = 4294967295 + """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(UINTMAX) + self.__generate_nonce(UINT_MAX) if self._current_nonce is None else self._current_nonce ) 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