71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
import base64
|
|
from operator import itemgetter
|
|
from urllib.parse import parse_qsl
|
|
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
from cryptography.exceptions import InvalidSignature
|
|
|
|
from .web_app import parse_webapp_init_data, WebAppInitData
|
|
|
|
PRODUCTION_PUBLIC_KEY = bytes.fromhex(
|
|
"e7bf03a2fa4602af4580703d88dda5bb59f32ed8b02a56c187fe7d34caed242d"
|
|
)
|
|
TEST_PUBLIC_KEY = bytes.fromhex("40055058a4ee38156a06562e52eece92a771bcd8346a8c4615cb7376eddf72ec")
|
|
|
|
|
|
def check_webapp_signature(
|
|
bot_id: int, init_data: str, public_key_bytes: bytes = PRODUCTION_PUBLIC_KEY
|
|
) -> bool:
|
|
"""
|
|
Check incoming WebApp init data signature without bot token using only bot id.
|
|
|
|
Source: https://core.telegram.org/bots/webapps#validating-data-for-third-party-use
|
|
|
|
:param bot_id: Bot ID
|
|
:param init_data: WebApp init data
|
|
:param public_key: Public key
|
|
:return: True if signature is valid, False otherwise
|
|
"""
|
|
try:
|
|
parsed_data = dict(parse_qsl(init_data, strict_parsing=True))
|
|
except ValueError:
|
|
return False
|
|
|
|
signature_b64 = parsed_data.pop("signature", None)
|
|
if not signature_b64:
|
|
return False
|
|
|
|
parsed_data.pop("hash", None)
|
|
|
|
data_check_string = f"{bot_id}:WebAppData\n" + "\n".join(
|
|
f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0))
|
|
)
|
|
message = data_check_string.encode()
|
|
|
|
padding = "=" * (-len(signature_b64) % 4)
|
|
signature = base64.urlsafe_b64decode(signature_b64 + padding)
|
|
|
|
public_key = Ed25519PublicKey.from_public_bytes(public_key_bytes)
|
|
|
|
try:
|
|
public_key.verify(signature, message)
|
|
return True
|
|
except InvalidSignature:
|
|
return False
|
|
|
|
|
|
def safe_check_webapp_init_data_from_signature(
|
|
bot_id: int, init_data: str, public_key_bytes: bytes = PRODUCTION_PUBLIC_KEY
|
|
) -> WebAppInitData:
|
|
"""
|
|
Validate raw WebApp init data using only bot id and return it as WebAppInitData object
|
|
|
|
:param bot_id: bot id
|
|
:param init_data: data from frontend to be parsed and validated
|
|
:param public_key_bytes: public key
|
|
:return: WebAppInitData object
|
|
"""
|
|
if check_webapp_signature(bot_id, init_data, public_key_bytes):
|
|
return parse_webapp_init_data(init_data)
|
|
raise ValueError("Invalid init data signature")
|