diff --git a/pyrogram/client.py b/pyrogram/client.py index 238b2efb..5ff18f29 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -982,7 +982,10 @@ class Client(Methods): # https://core.telegram.org/cdn#verifying-files for i, h in enumerate(hashes): cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - CDNFileHashMismatch.check(h.hash == sha256(cdn_chunk).digest()) + CDNFileHashMismatch.check( + h.hash == sha256(cdn_chunk).digest(), + "h.hash == sha256(cdn_chunk).digest()" + ) yield decrypted_chunk diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index c893e3e5..e147c22a 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -62,7 +62,7 @@ def unpack( auth_key_id: bytes, stored_msg_ids: List[int] ) -> Message: - SecurityCheckMismatch.check(b.read(8) == auth_key_id) + SecurityCheckMismatch.check(b.read(8) == auth_key_id, "b.read(8) == auth_key_id") msg_key = b.read(16) aes_key, aes_iv = kdf(auth_key, msg_key, False) @@ -70,7 +70,7 @@ def unpack( data.read(8) # Salt # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - SecurityCheckMismatch.check(data.read(8) == session_id) + SecurityCheckMismatch.check(data.read(8) == session_id, "data.read(8) == session_id") try: message = Message.read(data) @@ -88,39 +88,40 @@ def unpack( # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key # 96 = 88 + 8 (incoming message) - SecurityCheckMismatch.check(msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]) + SecurityCheckMismatch.check( + msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24], + "msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]" + ) # https://core.telegram.org/mtproto/security_guidelines#checking-message-length data.seek(32) # Get to the payload, skip salt (8) + session_id (8) + msg_id (8) + seq_no (4) + length (4) payload = data.read() padding = payload[message.length:] - SecurityCheckMismatch.check(12 <= len(padding) <= 1024) - SecurityCheckMismatch.check(len(payload) % 4 == 0) + SecurityCheckMismatch.check(12 <= len(padding) <= 1024, "12 <= len(padding) <= 1024") + SecurityCheckMismatch.check(len(payload) % 4 == 0, "len(payload) % 4 == 0") # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - SecurityCheckMismatch.check(message.msg_id % 2 != 0) + SecurityCheckMismatch.check(message.msg_id % 2 != 0, "message.msg_id % 2 != 0") if len(stored_msg_ids) > STORED_MSG_IDS_MAX_SIZE: del stored_msg_ids[:STORED_MSG_IDS_MAX_SIZE // 2] if stored_msg_ids: - # Ignored message: msg_id is lower than all of the stored values if message.msg_id < stored_msg_ids[0]: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id is lower than all the stored values") - # Ignored message: msg_id is equal to any of the stored values if message.msg_id in stored_msg_ids: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id is equal to any of the stored values") time_diff = (message.msg_id - MsgId()) / 2 ** 32 - # Ignored message: msg_id belongs over 30 seconds in the future if time_diff > 30: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. " + "Most likely the client time has to be synchronized.") - # Ignored message: msg_id belongs over 300 seconds in the past if time_diff < -300: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " + "Most likely the client time has to be synchronized.") bisect.insort(stored_msg_ids, message.msg_id) diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index 0ae39362..aa3a042c 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -45,21 +45,21 @@ class SecurityError(Exception): """Generic security error.""" @classmethod - def check(cls, cond: bool): + def check(cls, cond: bool, msg: str): """Raises this exception if the condition is false""" if not cond: - raise cls + raise cls(f"Check failed: {msg}") class SecurityCheckMismatch(SecurityError): """Raised when a security check mismatch occurs.""" - def __init__(self): - super().__init__("A security check mismatch has occurred.") + def __init__(self, msg: str = None): + super().__init__("A security check mismatch has occurred." if msg is None else msg) class CDNFileHashMismatch(SecurityError): """Raised when a CDN file hash mismatch occurs.""" - def __init__(self): - super().__init__("A CDN file hash mismatch has occurred.") + def __init__(self, msg: str = None): + super().__init__("A CDN file hash mismatch has occurred." if msg is None else msg) diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 0bb39398..973bad8b 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -211,33 +211,51 @@ class Auth: # Security checks ####################### - SecurityCheckMismatch.check(dh_prime == prime.CURRENT_DH_PRIME) + SecurityCheckMismatch.check(dh_prime == prime.CURRENT_DH_PRIME, "dh_prime == prime.CURRENT_DH_PRIME") log.debug("DH parameters check: OK") # https://core.telegram.org/mtproto/security_guidelines#g-a-and-g-b-validation g_b = int.from_bytes(g_b, "big") - SecurityCheckMismatch.check(1 < g < dh_prime - 1) - SecurityCheckMismatch.check(1 < g_a < dh_prime - 1) - SecurityCheckMismatch.check(1 < g_b < dh_prime - 1) - SecurityCheckMismatch.check(2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)) - SecurityCheckMismatch.check(2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)) + SecurityCheckMismatch.check(1 < g < dh_prime - 1, "1 < g < dh_prime - 1") + SecurityCheckMismatch.check(1 < g_a < dh_prime - 1, "1 < g_a < dh_prime - 1") + SecurityCheckMismatch.check(1 < g_b < dh_prime - 1, "1 < g_b < dh_prime - 1") + SecurityCheckMismatch.check( + 2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64), + "2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)" + ) + SecurityCheckMismatch.check( + 2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64), + "2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)" + ) log.debug("g_a and g_b validation: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-sha1-hash-values answer = server_dh_inner_data.write() # Call .write() to remove padding - SecurityCheckMismatch.check(answer_with_hash[:20] == sha1(answer).digest()) + SecurityCheckMismatch.check( + answer_with_hash[:20] == sha1(answer).digest(), + "answer_with_hash[:20] == sha1(answer).digest()" + ) log.debug("SHA1 hash values check: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-nonce-server-nonce-and-new-nonce-fields # 1st message - SecurityCheckMismatch.check(nonce == res_pq.nonce) + SecurityCheckMismatch.check(nonce == res_pq.nonce, "nonce == res_pq.nonce") # 2nd message server_nonce = int.from_bytes(server_nonce, "little", signed=True) - SecurityCheckMismatch.check(nonce == server_dh_params.nonce) - SecurityCheckMismatch.check(server_nonce == server_dh_params.server_nonce) + SecurityCheckMismatch.check(nonce == server_dh_params.nonce, "nonce == server_dh_params.nonce") + SecurityCheckMismatch.check( + server_nonce == server_dh_params.server_nonce, + "server_nonce == server_dh_params.server_nonce" + ) # 3rd message - SecurityCheckMismatch.check(nonce == set_client_dh_params_answer.nonce) - SecurityCheckMismatch.check(server_nonce == set_client_dh_params_answer.server_nonce) + SecurityCheckMismatch.check( + nonce == set_client_dh_params_answer.nonce, + "nonce == set_client_dh_params_answer.nonce" + ) + SecurityCheckMismatch.check( + server_nonce == set_client_dh_params_answer.server_nonce, + "server_nonce == set_client_dh_params_answer.server_nonce" + ) server_nonce = server_nonce.to_bytes(16, "little", signed=True) log.debug("Nonce fields check: OK") @@ -248,6 +266,8 @@ class Auth: log.info(f"Done auth key exchange: {set_client_dh_params_answer.__class__.__name__}") except Exception as e: + log.info(f"Retrying due to {type(e).__name__}: {e}") + if retries_left: retries_left -= 1 else: diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6c3dbc03..ed498245 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -188,7 +188,8 @@ class Session: self.auth_key_id, self.stored_msg_ids ) - except SecurityCheckMismatch: + except SecurityCheckMismatch as e: + log.info(f"Discarding packet: {e}") return messages = (