diff --git a/core/controllers/ConnectionController.py b/core/controllers/ConnectionController.py index df63ced..61a08e1 100644 --- a/core/controllers/ConnectionController.py +++ b/core/controllers/ConnectionController.py @@ -1,6 +1,11 @@ from core.errors.exceptions import * from core.errors.logger import logger +from core.services.networking.general_connection_tools.testing_evaluating import await_connection, system_uses_wireguard_interface, await_network_interface +from core.services.networking.systemwide.systemwide_wireguard import establish_system_connection, terminate_system_connection +from core.services.verification.endpoint_verification import verify_wireguard_endpoint + + from collections.abc import Callable from core.Constants import Constants from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError @@ -93,10 +98,10 @@ class ConnectionController: if profile.is_system_profile(): - if ConnectionController.system_uses_wireguard_interface() and SystemStateController.exists(): + if system_uses_wireguard_interface() and SystemStateController.exists(): try: - ConnectionController.terminate_system_connection() + terminate_system_connection() except ConnectionTerminationError: pass @@ -120,14 +125,14 @@ class ConnectionController: if profile.is_system_profile(): try: - return ConnectionController.establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer) + return establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer) except ConnectionError: if ConnectionController.__should_renegotiate(profile): ProfileController.register_wireguard_session(profile, connection_observer=connection_observer) - return ConnectionController.establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer) + return establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer) else: raise ConnectionError('The connection could not be established.') @@ -146,7 +151,7 @@ class ConnectionController: # this is a check from SessionConnection of if there's a systemwide with mask if profile.connection.is_unprotected(): - if not ConnectionController.system_uses_wireguard_interface(): + if not system_uses_wireguard_interface(): if not ConnectionUnprotectedError in ignore: raise ConnectionUnprotectedError('Connection unprotected while the system is not using a WireGuard interface.') @@ -163,7 +168,7 @@ class ConnectionController: elif profile.connection.code == 'wireguard': if ConfigurationController.get_endpoint_verification_enabled(): - ProfileController.verify_wireguard_endpoint(profile, ignore=ignore) + verify_wireguard_endpoint(profile, ignore=ignore) port_number = ConnectionService.get_random_available_port_number() ConnectionController.establish_wireguard_session_connection(profile, session_directory, port_number) @@ -178,57 +183,51 @@ class ConnectionController: session_state.network_port_numbers.proxy.append(proxy_port_number) if not profile.connection.is_unprotected(): - ConnectionController.await_connection(proxy_port_number or port_number, connection_observer=connection_observer) + await_connection(proxy_port_number or port_number, connection_observer=connection_observer) SessionStateController.update_or_create(session_state) return proxy_port_number or port_number - @staticmethod - def establish_system_connection(profile: SystemProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None): + # @staticmethod + # def establish_system_connection(profile: SystemProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None): - if ConfigurationController.get_endpoint_verification_enabled(): - ProfileController.verify_wireguard_endpoint(profile, ignore=ignore) + # if ConfigurationController.get_endpoint_verification_enabled(): + # ProfileController.verify_wireguard_endpoint(profile, ignore=ignore) - try: - ConnectionController.__establish_system_connection(profile, connection_observer) + # try: + # ConnectionController.__establish_system_connection(profile, connection_observer) - except ConnectionError: + # except ConnectionError: - try: - ConnectionController.terminate_system_connection() - except ConnectionTerminationError: - pass + # try: + # ConnectionController.terminate_system_connection() + # except ConnectionTerminationError: + # pass - raise ConnectionError('The connection could not be established.') + # raise ConnectionError('The connection could not be established.') - except CalledProcessError: + # except CalledProcessError: - try: - ConnectionController.terminate_system_connection() - except ConnectionTerminationError: - pass + # try: + # ConnectionController.terminate_system_connection() + # except ConnectionTerminationError: + # pass - try: - ConnectionController.__establish_system_connection(profile, connection_observer) + # try: + # ConnectionController.__establish_system_connection(profile, connection_observer) - except (ConnectionError, CalledProcessError): + # except (ConnectionError, CalledProcessError): - try: - ConnectionController.terminate_system_connection() - except ConnectionTerminationError: - pass + # try: + # ConnectionController.terminate_system_connection() + # except ConnectionTerminationError: + # pass - raise ConnectionError('The connection could not be established.') + # raise ConnectionError('The connection could not be established.') - ConnectionController.terminate_tor_connection() - time.sleep(1.0) - - - @staticmethod - def test_observer(connection_observer: Optional[ConnectionObserver] = None): - if connection_observer is not None: - connection_observer.notify('custom_message', "HELLO") + # ConnectionController.terminate_tor_connection() + # time.sleep(1.0) @staticmethod @@ -245,11 +244,11 @@ class ConnectionController: if connection_observer is not None: connection_observer.notify('custom_message', "Tor Can't Initialize") - @staticmethod - def terminate_tor_connection(): + # @staticmethod + # def terminate_tor_connection(): - tor_module = TorModule(Constants.HV_TOR_STATE_HOME) - tor_module.stop_service() + # tor_module = TorModule(Constants.HV_TOR_STATE_HOME) + # tor_module.stop_service() @staticmethod def establish_tor_session_connection(port_number: int, connection_observer: Optional[ConnectionObserver] = None): @@ -323,26 +322,28 @@ class ConnectionController: return subprocess.Popen(('proxychains4', '-f', proxychains_configuration_file_path, 'microsocks', '-p', str(proxy_port_number)), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) - @staticmethod - def terminate_system_connection(): + # @staticmethod + # def terminate_system_connection(): - if shutil.which('nmcli') is None: - raise CommandNotFoundError('nmcli') + # if shutil.which('nmcli') is None: + # raise CommandNotFoundError('nmcli') - if SystemStateController.exists(): + # if SystemStateController.exists(): - process = subprocess.Popen(('nmcli', 'connection', 'delete', 'wg'), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) - completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8) + # process = subprocess.Popen(('nmcli', 'connection', 'delete', 'wg'), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + # completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8) - if completed_successfully or not ConnectionController.system_uses_wireguard_interface(): + # if completed_successfully or not ConnectionController.system_uses_wireguard_interface(): - subprocess.run(('nmcli', 'connection', 'delete', 'hv-ipv6-sink'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - ConnectionController.terminate_tor_connection() - SystemState.dissolve() + # subprocess.run(('nmcli', 'connection', 'delete', 'hv-ipv6-sink'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + # ConnectionController.terminate_tor_connection() + # SystemState.dissolve() - else: - raise ConnectionTerminationError('The connection could not be terminated.') + # else: + # raise ConnectionTerminationError('The connection could not be terminated.') + + # Stays here for Tor based only. Will move with Tor later. @staticmethod def get_proxies(port_number: int): @@ -351,107 +352,107 @@ class ConnectionController: https=f'socks5h://127.0.0.1:{port_number}' ) - @staticmethod - def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None): + # @staticmethod + # def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None): - if port_number is None: - ConnectionController.await_network_interface() + # if port_number is None: + # ConnectionController.await_network_interface() - for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS): + # for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS): - if connection_observer is not None: + # if connection_observer is not None: - connection_observer.notify('connecting', dict( - retry_interval=Constants.CONNECTION_RETRY_INTERVAL, - maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS, - attempt_count=retry_count + 1 - )) + # connection_observer.notify('connecting', dict( + # retry_interval=Constants.CONNECTION_RETRY_INTERVAL, + # maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS, + # attempt_count=retry_count + 1 + # )) - try: + # try: - ConnectionController.__test_connection(port_number) - return + # ConnectionController.__test_connection(port_number) + # return - except ConnectionError: + # except ConnectionError: - time.sleep(Constants.CONNECTION_RETRY_INTERVAL) - retry_count += 1 + # time.sleep(Constants.CONNECTION_RETRY_INTERVAL) + # retry_count += 1 - raise ConnectionError('The connection could not be established.') + # raise ConnectionError('The connection could not be established.') - @staticmethod - def await_network_interface(): + # @staticmethod + # def await_network_interface(): - network_interface_is_activated = False + # network_interface_is_activated = False - retry_interval = .5 - maximum_number_of_attempts = 10 - attempt = 0 + # retry_interval = .5 + # maximum_number_of_attempts = 10 + # attempt = 0 - while not network_interface_is_activated and attempt < maximum_number_of_attempts: + # while not network_interface_is_activated and attempt < maximum_number_of_attempts: - time.sleep(retry_interval) + # time.sleep(retry_interval) - network_interface_is_activated = ConnectionController.system_uses_wireguard_interface() - attempt += 1 + # network_interface_is_activated = ConnectionController.system_uses_wireguard_interface() + # attempt += 1 - if not network_interface_is_activated: - raise ConnectionError('The network interface could not be activated.') + # if not network_interface_is_activated: + # raise ConnectionError('The network interface could not be activated.') - @staticmethod - def system_uses_wireguard_interface(): + # @staticmethod + # def system_uses_wireguard_interface(): - if shutil.which('ip') is None: - raise CommandNotFoundError('ip') + # if shutil.which('ip') is None: + # raise CommandNotFoundError('ip') - process = subprocess.Popen(('ip', 'route', 'get', '192.0.2.1'), stdout=subprocess.PIPE) - process_output = str(process.stdout.read()) + # process = subprocess.Popen(('ip', 'route', 'get', '192.0.2.1'), stdout=subprocess.PIPE) + # process_output = str(process.stdout.read()) - return bool(re.search('dev wg', str(process_output))) + # return bool(re.search('dev wg', str(process_output))) - @staticmethod - def __establish_system_connection(profile: SystemProfile, connection_observer: Optional[ConnectionObserver] = None): + # @staticmethod + # def __establish_system_connection(profile: SystemProfile, connection_observer: Optional[ConnectionObserver] = None): - if shutil.which('dbus-send') is None: - raise CommandNotFoundError('dbus-send') + # if shutil.which('dbus-send') is None: + # raise CommandNotFoundError('dbus-send') - if shutil.which('nmcli') is None: - raise CommandNotFoundError('nmcli') + # if shutil.which('nmcli') is None: + # raise CommandNotFoundError('nmcli') - ConnectionController.terminate_system_connection() + # ConnectionController.terminate_system_connection() - try: - process_output = subprocess.check_output(('nmcli', 'connection', 'import', '--temporary', 'type', 'wireguard', 'file', profile.get_wireguard_configuration_path()), text=True) - except CalledProcessError: - raise ConnectionError('The connection could not be established.') + # try: + # process_output = subprocess.check_output(('nmcli', 'connection', 'import', '--temporary', 'type', 'wireguard', 'file', profile.get_wireguard_configuration_path()), text=True) + # except CalledProcessError: + # raise ConnectionError('The connection could not be established.') - try: + # try: - connection_id = (m := re.search(r'(?<=\()([a-f0-9-]+?)(?=\))', process_output)) and m.group(1) - ipv6_method = subprocess.check_output(('nmcli', '-g', 'ipv6.method', 'connection', 'show', connection_id), text=True).strip() + # connection_id = (m := re.search(r'(?<=\()([a-f0-9-]+?)(?=\))', process_output)) and m.group(1) + # ipv6_method = subprocess.check_output(('nmcli', '-g', 'ipv6.method', 'connection', 'show', connection_id), text=True).strip() - except CalledProcessError: - raise ConnectionError('The connection could not be established.') + # except CalledProcessError: + # raise ConnectionError('The connection could not be established.') - if ipv6_method in ('disabled', 'ignore'): + # if ipv6_method in ('disabled', 'ignore'): - try: - subprocess.run(('dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager', 'org.freedesktop.DBus.Properties.Set', 'string:org.freedesktop.NetworkManager', 'string:ConnectivityCheckEnabled', 'variant:boolean:false'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) - except CalledProcessError: - raise ConnectionError('The connection could not be established.') + # try: + # subprocess.run(('dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager', 'org.freedesktop.DBus.Properties.Set', 'string:org.freedesktop.NetworkManager', 'string:ConnectivityCheckEnabled', 'variant:boolean:false'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + # except CalledProcessError: + # raise ConnectionError('The connection could not be established.') - try: - subprocess.run(('nmcli', 'connection', 'add', 'type', 'dummy', 'save', 'no', 'con-name', 'hv-ipv6-sink', 'ifname', 'hvipv6sink0', 'ipv6.method', 'manual', 'ipv6.addresses', 'fd7a:fd4b:54e3:077c::/64', 'ipv6.gateway', 'fd7a:fd4b:54e3:077c::1', 'ipv6.dns', '::1', 'ipv6.route-metric', '72'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) - except CalledProcessError: - raise ConnectionError('The connection could not be established.') + # try: + # subprocess.run(('nmcli', 'connection', 'add', 'type', 'dummy', 'save', 'no', 'con-name', 'hv-ipv6-sink', 'ifname', 'hvipv6sink0', 'ipv6.method', 'manual', 'ipv6.addresses', 'fd7a:fd4b:54e3:077c::/64', 'ipv6.gateway', 'fd7a:fd4b:54e3:077c::1', 'ipv6.dns', '::1', 'ipv6.route-metric', '72'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + # except CalledProcessError: + # raise ConnectionError('The connection could not be established.') - SystemStateController.create(profile.id) + # SystemStateController.create(profile.id) - try: - ConnectionController.await_connection(connection_observer=connection_observer) + # try: + # ConnectionController.await_connection(connection_observer=connection_observer) - except ConnectionError: - raise ConnectionError('The connection could not be established.') + # except ConnectionError: + # raise ConnectionError('The connection could not be established.') @staticmethod def __with_tor_connection(*args, task: Callable[..., Any], connection_observer: Optional[ConnectionObserver] = None, **kwargs): @@ -459,52 +460,52 @@ class ConnectionController: port_number = ConnectionService.get_random_available_port_number() ConnectionController.establish_tor_session_connection(port_number, connection_observer=connection_observer) - ConnectionController.await_connection(port_number, connection_observer=connection_observer) + await_connection(port_number, connection_observer=connection_observer) task_output = task(*args, proxies=ConnectionController.get_proxies(port_number), **kwargs) ConnectionController.terminate_tor_session_connection(port_number) return task_output - @staticmethod - def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0): + # @staticmethod + # def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0): - request_urls = [Constants.PING_URL] - proxies = None + # request_urls = [Constants.PING_URL] + # proxies = None - if os.environ.get('PING_URL') is None: + # if os.environ.get('PING_URL') is None: - request_urls.extend([ - 'https://hc1.simplifiedprivacy.net', - 'https://hc2.simplifiedprivacy.org', - 'https://hc3.hydraveil.net' - ]) + # request_urls.extend([ + # 'https://hc1.simplifiedprivacy.net', + # 'https://hc2.simplifiedprivacy.org', + # 'https://hc3.hydraveil.net' + # ]) - random.shuffle(request_urls) + # random.shuffle(request_urls) - if port_number is not None: - proxies = ConnectionController.get_proxies(port_number) + # if port_number is not None: + # proxies = ConnectionController.get_proxies(port_number) - for request_url in request_urls: + # for request_url in request_urls: - command = [ - sys.executable, '-u', '-c', 'import requests, sys\n' - 'try:\n' - f' response = requests.get(\'{request_url}\', proxies={proxies}, timeout={timeout})\n' - ' response.raise_for_status(); print(response.text)\n' - 'except requests.exceptions.RequestException:\n' - ' sys.exit(1)' - ] + # command = [ + # sys.executable, '-u', '-c', 'import requests, sys\n' + # 'try:\n' + # f' response = requests.get(\'{request_url}\', proxies={proxies}, timeout={timeout})\n' + # ' response.raise_for_status(); print(response.text)\n' + # 'except requests.exceptions.RequestException:\n' + # ' sys.exit(1)' + # ] - try: + # try: - _response = subprocess.check_output(command, text=True, timeout=timeout) - return None + # _response = subprocess.check_output(command, text=True, timeout=timeout) + # return None - except (subprocess.CalledProcessError, subprocess.TimeoutExpired): - pass + # except (subprocess.CalledProcessError, subprocess.TimeoutExpired): + # pass - raise ConnectionError('The connection could not be established.') + # raise ConnectionError('The connection could not be established.') @staticmethod def __should_renegotiate(profile: Union[SessionProfile, SystemProfile]): diff --git a/core/controllers/ProfileController.py b/core/controllers/ProfileController.py index 9edd4f4..e2fe2d4 100644 --- a/core/controllers/ProfileController.py +++ b/core/controllers/ProfileController.py @@ -1,3 +1,7 @@ +from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection +from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface +from core.services.verification.endpoint_verification import verify_wireguard_endpoint + from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError from core.controllers.ApplicationController import ApplicationController from core.controllers.ApplicationVersionController import ApplicationVersionController @@ -115,7 +119,7 @@ class ProfileController: raise ProfileDeactivationError('The profile could not be disabled.') try: - ConnectionController.terminate_system_connection() + terminate_system_connection() except ConnectionTerminationError: raise ProfileDeactivationError('The profile could not be disabled.') @@ -177,7 +181,7 @@ class ProfileController: system_state = SystemStateController.get() if system_state is not None and system_state.profile_id is profile.id: - return ConnectionController.system_uses_wireguard_interface() + return system_uses_wireguard_interface() return False @@ -242,47 +246,47 @@ class ProfileController: def has_wireguard_configuration(profile: Union[SessionProfile, SystemProfile]): return profile.has_wireguard_configuration() - @staticmethod - def verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = ()): + # @staticmethod + # def verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = ()): - try: - ProfileController.__verify_wireguard_endpoint(profile) + # try: + # ProfileController.__verify_wireguard_endpoint(profile) - except EndpointVerificationError as error: + # except EndpointVerificationError as error: - if not EndpointVerificationError in ignore: + # if not EndpointVerificationError in ignore: - profile.address_security_incident() - raise error + # profile.address_security_incident() + # raise error - @staticmethod - def __verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile]): + # @staticmethod + # def __verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile]): - from cryptography.hazmat.primitives.asymmetric import ed25519 - import base64 + # from cryptography.hazmat.primitives.asymmetric import ed25519 + # import base64 - signature = profile.get_wireguard_configuration_metadata('Signature') - wireguard_public_keys = profile.get_wireguard_public_keys() - operator = profile.location.operator + # signature = profile.get_wireguard_configuration_metadata('Signature') + # wireguard_public_keys = profile.get_wireguard_public_keys() + # operator = profile.location.operator - if signature is None: - raise EndpointVerificationError('The WireGuard endpoint\'s signature could not be determined.') + # if signature is None: + # raise EndpointVerificationError('The WireGuard endpoint\'s signature could not be determined.') - if not wireguard_public_keys: - raise EndpointVerificationError('The WireGuard endpoint\'s public key could not be determined.') + # if not wireguard_public_keys: + # raise EndpointVerificationError('The WireGuard endpoint\'s public key could not be determined.') - if operator is None: - raise EndpointVerificationError('The WireGuard endpoint\'s operator could not be determined.') + # if operator is None: + # raise EndpointVerificationError('The WireGuard endpoint\'s operator could not be determined.') - try: + # try: - operator_public_key = ed25519.Ed25519PublicKey.from_public_bytes(bytes.fromhex(operator.public_key)) + # operator_public_key = ed25519.Ed25519PublicKey.from_public_bytes(bytes.fromhex(operator.public_key)) - for wireguard_public_key in wireguard_public_keys: - operator_public_key.verify(base64.b64decode(signature), wireguard_public_key.encode('utf-8')) + # for wireguard_public_key in wireguard_public_keys: + # operator_public_key.verify(base64.b64decode(signature), wireguard_public_key.encode('utf-8')) - except Exception: - raise EndpointVerificationError('The WireGuard endpoint could not be verified.') + # except Exception: + # raise EndpointVerificationError('The WireGuard endpoint could not be verified.') @staticmethod def __generate_wireguard_keys(): diff --git a/core/services/networking/general_connection_tools/testing_evaluating.py b/core/services/networking/general_connection_tools/testing_evaluating.py new file mode 100644 index 0000000..81ab482 --- /dev/null +++ b/core/services/networking/general_connection_tools/testing_evaluating.py @@ -0,0 +1,142 @@ +from core.errors.exceptions import * +from core.errors.logger import logger + +from collections.abc import Callable +from core.Constants import Constants +from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError +from core.controllers.ConfigurationController import ConfigurationController +# from core.controllers.ProfileController import ProfileController +from core.controllers.SessionStateController import SessionStateController +from core.controllers.SystemStateController import SystemStateController +from core.models.BaseProfile import ProfileType +from core.models.session.SessionProfile import SessionProfile +from core.models.system.SystemProfile import SystemProfile +from core.models.system.SystemState import SystemState +from core.observers.ConnectionObserver import ConnectionObserver +from core.services.WebServiceApiService import WebServiceApiService +from essentials.modules.TorModule import TorModule +from essentials.services.ConnectionService import ConnectionService +from pathlib import Path +from subprocess import CalledProcessError +from typing import Union, Optional, Any +import os +import random +import re +import shutil +import subprocess +import sys +import tempfile +import time + + +def terminate_tor_connection(): + + tor_module = TorModule(Constants.HV_TOR_STATE_HOME) + tor_module.stop_service() + + +def await_network_interface(): + + network_interface_is_activated = False + + retry_interval = .5 + maximum_number_of_attempts = 10 + attempt = 0 + + while not network_interface_is_activated and attempt < maximum_number_of_attempts: + + time.sleep(retry_interval) + + network_interface_is_activated = system_uses_wireguard_interface() + attempt += 1 + + if not network_interface_is_activated: + raise ConnectionError('The network interface could not be activated.') + + +def system_uses_wireguard_interface(): + + if shutil.which('ip') is None: + raise CommandNotFoundError('ip') + + process = subprocess.Popen(('ip', 'route', 'get', '192.0.2.1'), stdout=subprocess.PIPE) + process_output = str(process.stdout.read()) + + return bool(re.search('dev wg', str(process_output))) + + +def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0): + + request_urls = [Constants.PING_URL] + proxies = None + + if os.environ.get('PING_URL') is None: + + request_urls.extend([ + 'https://hc1.simplifiedprivacy.net', + 'https://hc2.simplifiedprivacy.org', + 'https://hc3.hydraveil.net' + ]) + + random.shuffle(request_urls) + + if port_number is not None: + proxies = get_proxies(port_number) + + for request_url in request_urls: + + command = [ + sys.executable, '-u', '-c', 'import requests, sys\n' + 'try:\n' + f' response = requests.get(\'{request_url}\', proxies={proxies}, timeout={timeout})\n' + ' response.raise_for_status(); print(response.text)\n' + 'except requests.exceptions.RequestException:\n' + ' sys.exit(1)' + ] + + try: + + _response = subprocess.check_output(command, text=True, timeout=timeout) + return None + + except (subprocess.CalledProcessError, subprocess.TimeoutExpired): + pass + + raise ConnectionError('The connection could not be established.') + + +# Kept a duplicate in connection controller +def get_proxies(port_number: int): + + return dict( + http=f'socks5h://127.0.0.1:{port_number}', + https=f'socks5h://127.0.0.1:{port_number}' + ) + + +def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None): + + if port_number is None: + await_network_interface() + + for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS): + + if connection_observer is not None: + + connection_observer.notify('connecting', dict( + retry_interval=Constants.CONNECTION_RETRY_INTERVAL, + maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS, + attempt_count=retry_count + 1 + )) + + try: + + __test_connection(port_number) + return + + except ConnectionError: + + time.sleep(Constants.CONNECTION_RETRY_INTERVAL) + retry_count += 1 + + raise ConnectionError('The connection could not be established.') diff --git a/core/services/networking/systemwide/systemwide_wireguard.py b/core/services/networking/systemwide/systemwide_wireguard.py new file mode 100644 index 0000000..007ade5 --- /dev/null +++ b/core/services/networking/systemwide/systemwide_wireguard.py @@ -0,0 +1,136 @@ +from core.services.networking.general_connection_tools.testing_evaluating import await_connection, system_uses_wireguard_interface, terminate_tor_connection +from core.services.verification.endpoint_verification import verify_wireguard_endpoint + +from core.errors.exceptions import * +from core.errors.logger import logger + +from collections.abc import Callable +from core.Constants import Constants +from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError +from core.controllers.ConfigurationController import ConfigurationController +# from core.controllers.ProfileController import ProfileController +from core.controllers.SessionStateController import SessionStateController +from core.controllers.SystemStateController import SystemStateController +from core.models.BaseProfile import ProfileType +from core.models.session.SessionProfile import SessionProfile +from core.models.system.SystemProfile import SystemProfile +from core.models.system.SystemState import SystemState +from core.observers.ConnectionObserver import ConnectionObserver +from core.services.WebServiceApiService import WebServiceApiService +from essentials.modules.TorModule import TorModule +from essentials.services.ConnectionService import ConnectionService +from pathlib import Path +from subprocess import CalledProcessError +from typing import Union, Optional, Any +import os +import random +import re +import shutil +import subprocess +import sys +import tempfile +import time + +def terminate_system_connection(): + + if shutil.which('nmcli') is None: + raise CommandNotFoundError('nmcli') + + if SystemStateController.exists(): + + process = subprocess.Popen(('nmcli', 'connection', 'delete', 'wg'), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8) + + if completed_successfully or not system_uses_wireguard_interface(): + + subprocess.run(('nmcli', 'connection', 'delete', 'hv-ipv6-sink'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + terminate_tor_connection() + SystemState.dissolve() + + else: + raise ConnectionTerminationError('The connection could not be terminated.') + + +def __establish_system_connection(profile: SystemProfile, connection_observer: Optional[ConnectionObserver] = None): + + if shutil.which('dbus-send') is None: + raise CommandNotFoundError('dbus-send') + + if shutil.which('nmcli') is None: + raise CommandNotFoundError('nmcli') + + terminate_system_connection() + + try: + process_output = subprocess.check_output(('nmcli', 'connection', 'import', '--temporary', 'type', 'wireguard', 'file', profile.get_wireguard_configuration_path()), text=True) + except CalledProcessError: + raise ConnectionError('The connection could not be established.') + + try: + + connection_id = (m := re.search(r'(?<=\()([a-f0-9-]+?)(?=\))', process_output)) and m.group(1) + ipv6_method = subprocess.check_output(('nmcli', '-g', 'ipv6.method', 'connection', 'show', connection_id), text=True).strip() + + except CalledProcessError: + raise ConnectionError('The connection could not be established.') + + if ipv6_method in ('disabled', 'ignore'): + + try: + subprocess.run(('dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager', 'org.freedesktop.DBus.Properties.Set', 'string:org.freedesktop.NetworkManager', 'string:ConnectivityCheckEnabled', 'variant:boolean:false'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except CalledProcessError: + raise ConnectionError('The connection could not be established.') + + try: + subprocess.run(('nmcli', 'connection', 'add', 'type', 'dummy', 'save', 'no', 'con-name', 'hv-ipv6-sink', 'ifname', 'hvipv6sink0', 'ipv6.method', 'manual', 'ipv6.addresses', 'fd7a:fd4b:54e3:077c::/64', 'ipv6.gateway', 'fd7a:fd4b:54e3:077c::1', 'ipv6.dns', '::1', 'ipv6.route-metric', '72'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except CalledProcessError: + raise ConnectionError('The connection could not be established.') + + SystemStateController.create(profile.id) + + try: + await_connection(connection_observer=connection_observer) + + except ConnectionError: + raise ConnectionError('The connection could not be established.') + + + +def establish_system_connection(profile: SystemProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None): + + if ConfigurationController.get_endpoint_verification_enabled(): + verify_wireguard_endpoint(profile, ignore=ignore) + + try: + __establish_system_connection(profile, connection_observer) + + except ConnectionError: + + try: + terminate_system_connection() + except ConnectionTerminationError: + pass + + raise ConnectionError('The connection could not be established.') + + except CalledProcessError: + + try: + terminate_system_connection() + except ConnectionTerminationError: + pass + + try: + __establish_system_connection(profile, connection_observer) + + except (ConnectionError, CalledProcessError): + + try: + terminate_system_connection() + except ConnectionTerminationError: + pass + + raise ConnectionError('The connection could not be established.') + + terminate_tor_connection() + time.sleep(1.0) diff --git a/core/services/verification/endpoint_verification.py b/core/services/verification/endpoint_verification.py new file mode 100644 index 0000000..d1344c3 --- /dev/null +++ b/core/services/verification/endpoint_verification.py @@ -0,0 +1,48 @@ +from core.models.session.SessionProfile import SessionProfile +from core.models.system.SystemProfile import SystemProfile +from core.models.BaseProfile import BaseProfile as Profile +from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError +from typing import Union, Optional +import base64 + + +def verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = ()): + + try: + __verify_wireguard_endpoint(profile) + + except EndpointVerificationError as error: + + if not EndpointVerificationError in ignore: + + profile.address_security_incident() + raise error + +def __verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile]): + + from cryptography.hazmat.primitives.asymmetric import ed25519 + import base64 + + signature = profile.get_wireguard_configuration_metadata('Signature') + wireguard_public_keys = profile.get_wireguard_public_keys() + operator = profile.location.operator + + if signature is None: + raise EndpointVerificationError('The WireGuard endpoint\'s signature could not be determined.') + + if not wireguard_public_keys: + raise EndpointVerificationError('The WireGuard endpoint\'s public key could not be determined.') + + if operator is None: + raise EndpointVerificationError('The WireGuard endpoint\'s operator could not be determined.') + + try: + + operator_public_key = ed25519.Ed25519PublicKey.from_public_bytes(bytes.fromhex(operator.public_key)) + + for wireguard_public_key in wireguard_public_keys: + operator_public_key.verify(base64.b64decode(signature), wireguard_public_key.encode('utf-8')) + + except Exception: + raise EndpointVerificationError('The WireGuard endpoint could not be verified.') +