updated crypt

This commit is contained in:
machka pasla
2025-12-29 14:51:50 +03:00
parent 48f8656ef8
commit e3ffb87cfc
11 changed files with 35 additions and 58 deletions
+1 -1
View File
@@ -253,7 +253,7 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
traffic_label = ( traffic_label = (
str(int(traffic_amount_gb)) if float(traffic_amount_gb).is_integer() else f"{traffic_amount_gb:g}" str(int(traffic_amount_gb)) if float(traffic_amount_gb).is_integer() else f"{traffic_amount_gb:g}"
) )
config_link_display, connect_button_url = prepare_config_links( config_link_display, connect_button_url = await prepare_config_links(
settings, activation_details.get("subscription_url") if activation_details else None settings, activation_details.get("subscription_url") if activation_details else None
) )
config_link_text = config_link_display or _("config_link_not_available") config_link_text = config_link_display or _("config_link_not_available")
+2 -2
View File
@@ -88,7 +88,7 @@ async def request_trial_confirmation_handler(
pass pass
end_date_obj = activation_result.get("end_date") end_date_obj = activation_result.get("end_date")
config_link_display_for_trial, connect_button_url_for_trial = prepare_config_links( config_link_display_for_trial, connect_button_url_for_trial = await prepare_config_links(
settings, activation_result.get("subscription_url") settings, activation_result.get("subscription_url")
) )
config_link_for_trial = config_link_display_for_trial or _("config_link_not_available") config_link_for_trial = config_link_display_for_trial or _("config_link_not_available")
@@ -240,7 +240,7 @@ async def confirm_activate_trial_handler(
pass pass
end_date_obj = activation_result.get("end_date") end_date_obj = activation_result.get("end_date")
config_link_display_for_trial, connect_button_url_for_trial = prepare_config_links( config_link_display_for_trial, connect_button_url_for_trial = await prepare_config_links(
settings, activation_result.get("subscription_url") settings, activation_result.get("subscription_url")
) )
config_link_for_trial = config_link_display_for_trial or _("config_link_not_available") config_link_for_trial = config_link_display_for_trial or _("config_link_not_available")
+1 -1
View File
@@ -190,7 +190,7 @@ class CryptoPayService:
_ = lambda k, **kw: i18n.gettext(lang, k, **kw) _ = lambda k, **kw: i18n.gettext(lang, k, **kw)
raw_config_link = activation.get("subscription_url") if activation else None raw_config_link = activation.get("subscription_url") if activation else None
display_link, button_link = prepare_config_links(settings, raw_config_link) display_link, button_link = await prepare_config_links(settings, raw_config_link)
config_link_text = display_link or _("config_link_not_available") config_link_text = display_link or _("config_link_not_available")
final_end = activation.get("end_date") final_end = activation.get("end_date")
applied_days = 0 applied_days = 0
+1 -1
View File
@@ -319,7 +319,7 @@ class FreeKassaService:
_ = lambda k, **kw: self.i18n.gettext(lang, k, **kw) if self.i18n else k _ = lambda k, **kw: self.i18n.gettext(lang, k, **kw) if self.i18n else k
raw_config_link = activation.get("subscription_url") if activation else None raw_config_link = activation.get("subscription_url") if activation else None
config_link_display, connect_button_url = prepare_config_links(self.settings, raw_config_link) config_link_display, connect_button_url = await prepare_config_links(self.settings, raw_config_link)
config_link_text = config_link_display or _("config_link_not_available") config_link_text = config_link_display or _("config_link_not_available")
final_end = activation.get("end_date") if activation else None final_end = activation.get("end_date") if activation else None
months = payment.subscription_duration_months or 1 months = payment.subscription_duration_months or 1
+17
View File
@@ -562,3 +562,20 @@ class PanelApiService:
if response_data and not response_data.get("error") and "response" in response_data: if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response") return response_data.get("response")
return None return None
async def encrypt_happ_link(self, link_to_encrypt: str) -> Optional[str]:
"""Encrypt a subscription link using the panel's happ crypt4 API.
Returns the encrypted link string or None if encryption failed.
"""
payload = {"linkToEncrypt": link_to_encrypt}
response_data = await self._request(
"POST",
"/system/tools/happ/encrypt",
json=payload,
log_full_response=False
)
if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response", {}).get("encryptedLink")
logging.error(f"Failed to encrypt happ link. Response: {response_data}")
return None
+1 -1
View File
@@ -215,7 +215,7 @@ class PlategaService:
_ = lambda k, **kw: self.i18n.gettext(lang, k, **kw) if self.i18n else k _ = lambda k, **kw: self.i18n.gettext(lang, k, **kw) if self.i18n else k
raw_config_link = activation.get("subscription_url") if activation else None raw_config_link = activation.get("subscription_url") if activation else None
config_link_display, connect_button_url = prepare_config_links(self.settings, raw_config_link) config_link_display, connect_button_url = await prepare_config_links(self.settings, raw_config_link)
config_link_text = config_link_display or _("config_link_not_available") config_link_text = config_link_display or _("config_link_not_available")
final_end = activation.get("end_date") if activation else None final_end = activation.get("end_date") if activation else None
applied_days = 0 applied_days = 0
+1 -1
View File
@@ -233,7 +233,7 @@ class SeverPayService:
_ = lambda k, **kw: self.i18n.gettext(lang, k, **kw) if self.i18n else k _ = lambda k, **kw: self.i18n.gettext(lang, k, **kw) if self.i18n else k
raw_config_link = activation.get("subscription_url") if activation else None raw_config_link = activation.get("subscription_url") if activation else None
config_link_display, connect_button_url = prepare_config_links(self.settings, raw_config_link) config_link_display, connect_button_url = await prepare_config_links(self.settings, raw_config_link)
config_link_text = config_link_display or _("config_link_not_available") config_link_text = config_link_display or _("config_link_not_available")
final_end = activation.get("end_date") if activation else None final_end = activation.get("end_date") if activation else None
applied_days = 0 applied_days = 0
+1 -1
View File
@@ -123,7 +123,7 @@ class StarsService:
_ = lambda k, **kw: i18n.gettext(current_lang, k, **kw) if i18n else k _ = lambda k, **kw: i18n.gettext(current_lang, k, **kw) if i18n else k
raw_config_link = activation_details.get("subscription_url") if activation_details else None raw_config_link = activation_details.get("subscription_url") if activation_details else None
config_link_display, connect_button_url = prepare_config_links(self.settings, raw_config_link) config_link_display, connect_button_url = await prepare_config_links(self.settings, raw_config_link)
config_link_text = config_link_display or _("config_link_not_available") config_link_text = config_link_display or _("config_link_not_available")
if sale_mode == "traffic": if sale_mode == "traffic":
+1 -1
View File
@@ -902,7 +902,7 @@ class SubscriptionService:
else None else None
) )
config_link_raw = panel_user_data.get("subscriptionUrl") config_link_raw = panel_user_data.get("subscriptionUrl")
display_link, connect_button_url = prepare_config_links(self.settings, config_link_raw) display_link, connect_button_url = await prepare_config_links(self.settings, config_link_raw)
hwid_limit = panel_user_data.get("hwidDeviceLimit") hwid_limit = panel_user_data.get("hwidDeviceLimit")
if hwid_limit is None: if hwid_limit is None:
hwid_limit = self.settings.USER_HWID_DEVICE_LIMIT hwid_limit = self.settings.USER_HWID_DEVICE_LIMIT
+9 -35
View File
@@ -1,48 +1,22 @@
import base64
import logging import logging
from functools import lru_cache
from pathlib import Path
from typing import Optional, Tuple from typing import Optional, Tuple
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from config.settings import Settings from config.settings import Settings
from bot.services.panel_api_service import PanelApiService
CRYPT4_PREFIX = "happ://crypt4/" CRYPT4_PREFIX = "happ://crypt4/"
@lru_cache(maxsize=1) async def _encrypt_raw_link(settings: Settings, raw_link: str) -> Optional[str]:
def _load_crypt4_public_key() -> Optional[RSAPublicKey]: """Encrypt the raw subscription URL using the panel's happ crypt4 API."""
"""Load and cache the happ crypt4 public key from the project root.""" async with PanelApiService(settings) as panel_service:
pem_path = Path(__file__).resolve().parent.parent.parent / "happ-crypt4.pem" encrypted_link = await panel_service.encrypt_happ_link(raw_link)
try: if encrypted_link:
pem_bytes = pem_path.read_bytes() return encrypted_link
key = serialization.load_pem_public_key(pem_bytes)
return key if isinstance(key, RSAPublicKey) else None
except FileNotFoundError:
logging.error("Crypt4 public key file not found at %s", pem_path)
except Exception as exc:
logging.error("Failed to load crypt4 public key: %s", exc, exc_info=True)
return None return None
def _encrypt_raw_link(raw_link: str) -> Optional[str]: async def prepare_config_links(settings: Settings, raw_link: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
"""Encrypt the raw subscription URL with RSA PKCS#1 v1.5 and return base64 payload."""
public_key = _load_crypt4_public_key()
if not public_key:
return None
try:
encrypted = public_key.encrypt(raw_link.encode("utf-8"), padding.PKCS1v15())
return base64.b64encode(encrypted).decode("utf-8")
except Exception as exc:
logging.error("Failed to encrypt config link with crypt4: %s", exc, exc_info=True)
return None
def prepare_config_links(settings: Settings, raw_link: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
""" """
Build the user-facing connection key and the URL for the connect button. Build the user-facing connection key and the URL for the connect button.
@@ -61,7 +35,7 @@ def prepare_config_links(settings: Settings, raw_link: Optional[str]) -> Tuple[O
button_link = cleaned button_link = cleaned
if settings.CRYPT4_ENABLED: if settings.CRYPT4_ENABLED:
encrypted_payload = _encrypt_raw_link(cleaned) encrypted_payload = await _encrypt_raw_link(settings, cleaned)
if encrypted_payload: if encrypted_payload:
display_link = f"{CRYPT4_PREFIX}{encrypted_payload}" display_link = f"{CRYPT4_PREFIX}{encrypted_payload}"
button_link = display_link button_link = display_link
-14
View File
@@ -1,14 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3UZ0M3L4K+WjM3vkbQnz
ozHg/cRbEXvQ6i4A8RVN4OM3rK9kU01FdjyoIgywve8OEKsFnVwERZAQZ1Trv60B
hmaM76QQEE+EUlIOL9EpwKWGtTL5lYC1sT9XJMNP3/CI0gP5wwQI88cY/xedpOEB
W72EmOOShHUm/b/3m+HPmqwc4ugKj5zWV5SyiT829aFA5DxSjmIIFBAms7DafmSq
LFTYIQL5cShDY2u+/sqyAw9yZIOoqW2TFIgIHhLPWek/ocDU7zyOrlu1E0SmcQQb
LFqHq02fsnH6IcqTv3N5Adb/CkZDDQ6HvQVBmqbKZKf7ZdXkqsc/Zw27xhG7OfXC
tUmWsiL7zA+KoTd3avyOh93Q9ju4UQsHthL3Gs4vECYOCS9dsXXSHEY/1ngU/hjO
WFF8QEE/rYV6nA4PTyUvo5RsctSQL/9DJX7XNh3zngvif8LsCN2MPvx6X+zLouBX
zgBkQ9DFfZAGLWf9TR7KVjZC/3NsuUCDoAOcpmN8pENBbeB0puiKMMWSvll36+2M
YR1Xs0MgT8Y9TwhE2+TnnTJOhzmHi/BxiUlY/w2E0s4ax9GHAmX0wyF4zeV7kDkc
vHuEdc0d7vDmdw0oqCqWj0Xwq86HfORu6tm1A8uRATjb4SzjTKclKuoElVAVa5Jo
oh/uZMozC65SmDw+N5p6Su8CAwEAAQ==
-----END PUBLIC KEY-----