From 70004e064599062f8a7e9e586046205d8deac0d1 Mon Sep 17 00:00:00 2001 From: boyko_vn Date: Wed, 15 Jul 2026 10:36:55 +0300 Subject: [PATCH] new connection --- 1.py | 6 ++++++ README.md | 4 ++++ init_vpn.py | 5 +++-- launch_vpn.py | 12 +++++++++--- launch_vpn_gui.py | 21 +++++++++++++++++++-- 5 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 1.py diff --git a/1.py b/1.py new file mode 100644 index 0000000..4c66566 --- /dev/null +++ b/1.py @@ -0,0 +1,6 @@ +import keyring + +# keyring.get_password(service_name, username) +value = keyring.get_password("openvpn-totp-offad", "static") +print(value) # None, если ничего не сохранено + diff --git a/README.md b/README.md index ac3d6be..5dd860e 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,7 @@ Правятся вручную или через пункт меню «Открыть настройки»; перечитываются на лету. Секреты (`secret`, `static`) в конфиге не хранятся — они в keyring под именем `keyring_service`. + +## Сервис для декодирования + +https://2fafast.com/tools/qr-decoder/ diff --git a/init_vpn.py b/init_vpn.py index 2bcd0e8..ce0d784 100644 --- a/init_vpn.py +++ b/init_vpn.py @@ -25,14 +25,15 @@ def main(): static = os.environ.get("OVPN_STATIC_PASS") missing = [name for name, val in - (("OVPN_TOTP_SECRET", secret), ("OVPN_STATIC_PASS", static)) + (("OVPN_TOTP_SECRET", secret),) if not val] if missing: sys.exit(f"Не заданы переменные окружения: {', '.join(missing)}") service = keyring_service() keyring.set_password(service, "secret", secret) - keyring.set_password(service, "static", static) + if static: + keyring.set_password(service, "static", static) print(f"Сохранено в keyring (сервис: {service}).") if __name__ == "__main__": diff --git a/launch_vpn.py b/launch_vpn.py index 9bd3855..aed4ab0 100644 --- a/launch_vpn.py +++ b/launch_vpn.py @@ -2,7 +2,7 @@ """launch_vpn.py — auth = static+TOTP, конкатенация без разделителя. Параметры берутся из общего vpn_config.json (см. launch_vpn_gui.py); секреты — из keyring.""" -import subprocess, pyotp, keyring, tempfile, json, os +import subprocess, pyotp, keyring, tempfile, json, os, base64 CONFIG_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), "vpn_config.json") @@ -28,12 +28,18 @@ def main(): cfg = load_config() service = cfg.get("keyring_service", "openvpn-totp") secret = keyring.get_password(service, "secret") - static = keyring.get_password(service, "static") + # static — необязателен: если его нет в keyring, пароль = только TOTP-код. + static = keyring.get_password(service, "static") or "" code = pyotp.TOTP(secret).now() + if cfg.get("static_challenge"): + password = (f"SCRV1:{base64.b64encode(static.encode()).decode()}" + f":{base64.b64encode(code.encode()).decode()}") + else: + password = f"{static}{code}" fd, path = tempfile.mkstemp(suffix=".txt") try: with os.fdopen(fd, "w", newline="\n") as f: - f.write(f"{cfg['username']}\n{static}{code}\n") + f.write(f"{cfg['username']}\n{password}\n") subprocess.run([cfg["openvpn_bin"], "--config", cfg["ovpn_config"], "--auth-user-pass", path]) finally: diff --git a/launch_vpn_gui.py b/launch_vpn_gui.py index a10e6ca..32f2d70 100644 --- a/launch_vpn_gui.py +++ b/launch_vpn_gui.py @@ -7,6 +7,7 @@ import threading import tempfile import json import os +import base64 import pyotp import keyring import pystray @@ -32,6 +33,12 @@ DEFAULT_CONFIG = { "dev_node": "OpenVPN Data Channel Offload", # имя сервиса в keyring, откуда берутся secret (TOTP) и static (пароль). "keyring_service": "openvpn-totp", + # формат передачи OTP серверу. + # False -> пароль = static+code одной строкой (сервер сам отрезает OTP). + # True -> формат SCRV1 (static и OTP кодируются раздельно в base64); нужен + # для серверов со static-challenge. Саму строку static-challenge при + # этом надо УБРАТЬ из .ovpn, иначе openvpn спросит код интерактивно. + "static_challenge": False, # путь к лог-файлу openvpn. "log_path": "ovpn.log", @@ -153,14 +160,24 @@ def is_running() -> bool: return proc is not None and proc.poll() is None +def auth_password(static: str, code: str) -> str: + """Собрать поле пароля для auth-user-pass с учётом режима static_challenge.""" + if config.get("static_challenge"): + s = base64.b64encode(static.encode()).decode() + r = base64.b64encode(code.encode()).decode() + return f"SCRV1:{s}:{r}" + return f"{static}{code}" + + def build_auth_file() -> str: service = config.get("keyring_service", "openvpn-totp") secret = keyring.get_password(service, "secret") - static = keyring.get_password(service, "static") + # static — необязателен: если его нет в keyring, пароль = только TOTP-код. + static = keyring.get_password(service, "static") or "" code = pyotp.TOTP(secret).now() fd, path = tempfile.mkstemp(suffix=".txt") with os.fdopen(fd, "w", newline="\n") as f: - f.write(f"{config['username']}\n{static}{code}\n") + f.write(f"{config['username']}\n{auth_password(static, code)}\n") return path