Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Implement SSL peers support #432

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions deluge/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1231,12 +1231,9 @@ def __lt__(self, other):
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL


def create_auth_file():
def create_auth_file(auth_file):
import stat

import deluge.configmanager

auth_file = deluge.configmanager.get_config_dir('auth')
# Check for auth file and create if necessary
if not os.path.exists(auth_file):
with open(auth_file, 'w', encoding='utf8') as _file:
Expand All @@ -1246,29 +1243,34 @@ def create_auth_file():
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)


def create_localclient_account(append=False):
def create_localclient_account(append=False, auth_file=None):
import random
from hashlib import sha1 as sha

import deluge.configmanager

auth_file = deluge.configmanager.get_config_dir('auth')
if not auth_file:
auth_file = deluge.configmanager.get_config_dir('auth')

if not os.path.exists(auth_file):
create_auth_file()
create_auth_file(auth_file)

username = 'localclient'
password = sha(str(random.random()).encode('utf8')).hexdigest()
with open(auth_file, 'a' if append else 'w', encoding='utf8') as _file:
_file.write(
':'.join(
[
'localclient',
sha(str(random.random()).encode('utf8')).hexdigest(),
username,
password,
str(AUTH_LEVEL_ADMIN),
]
)
+ '\n'
)
_file.flush()
os.fsync(_file.fileno())
return username, password


def get_localhost_auth():
Expand Down
68 changes: 44 additions & 24 deletions deluge/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,44 @@ async def client(request, config_dir, monkeypatch, listen_port):


@pytest_twisted.async_yield_fixture
async def daemon(request, config_dir, tmp_path):
async def daemon_factory():
created_daemons = []

async def _make_daemon(listen_port, logfile=None, custom_script='', config_dir=''):
for dummy in range(10):
try:
d, daemon = common.start_core(
listen_port=listen_port,
logfile=logfile,
timeout=5,
timeout_msg='Timeout!',
custom_script=custom_script,
print_stdout=True,
print_stderr=True,
config_directory=config_dir,
)
await d
daemon.listen_port = listen_port
created_daemons.append(daemon)
return daemon
except CannotListenError as ex:
exception_error = ex
listen_port += 1
except (KeyboardInterrupt, SystemExit):
raise
else:
break
else:
raise exception_error

yield _make_daemon

for d in created_daemons:
await d.kill()


@pytest_twisted.async_yield_fixture
async def daemon(request, config_dir, tmp_path, daemon_factory):
listen_port = DEFAULT_LISTEN_PORT
logfile = tmp_path / 'daemon.log'

Expand All @@ -97,29 +134,12 @@ async def daemon(request, config_dir, tmp_path):
else:
custom_script = ''

for dummy in range(10):
try:
d, daemon = common.start_core(
listen_port=listen_port,
logfile=logfile,
timeout=5,
timeout_msg='Timeout!',
custom_script=custom_script,
print_stdout=True,
print_stderr=True,
config_directory=config_dir,
)
await d
except CannotListenError as ex:
exception_error = ex
listen_port += 1
except (KeyboardInterrupt, SystemExit):
raise
else:
break
else:
raise exception_error
daemon.listen_port = listen_port
daemon = await daemon_factory(
listen_port=listen_port,
logfile=logfile,
custom_script=custom_script,
config_dir=config_dir,
)
yield daemon
try:
await daemon.kill()
Expand Down
67 changes: 67 additions & 0 deletions deluge/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import shutil
import tempfile
from base64 import b64decode, b64encode
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.request import URLError, urlopen

Expand Down Expand Up @@ -674,6 +675,57 @@ def connect_peer(self, torrent_id: str, ip: str, port: int):
if not self.torrentmanager[torrent_id].connect_peer(ip, port):
log.warning('Error adding peer %s:%s to %s', ip, port, torrent_id)

@export
def set_ssl_torrent_cert(
self,
torrent_id: str,
certificate: str,
private_key: str,
dh_params: str,
save_to_disk: bool = True,
):
"""
Set the SSL certificates used to connect to SSL peers of the given torrent.
"""
log.debug('adding ssl certificate to %s', torrent_id)
if save_to_disk:
(
crt_file,
key_file,
dh_params_file,
) = self.torrentmanager.ssl_file_paths_for_torrent(torrent_id)

cert_dir = Path(self.config['ssl_torrents_certs'])
if not cert_dir.exists():
cert_dir.mkdir(exist_ok=True)

for file, content in (
(crt_file, certificate),
(key_file, private_key),
(dh_params_file, dh_params),
):
try:
with open(file, 'w') as f:
f.write(content)
except OSError as err:
log.warning('Error writing file %f to disk: %s', file, err)
return

if not self.torrentmanager[torrent_id].set_ssl_certificate(
str(crt_file), str(key_file), str(dh_params_file)
):
log.warning('Error adding certificate to %s', torrent_id)
else:
try:
if not self.torrentmanager[torrent_id].set_ssl_certificate_buffer(
certificate, private_key, dh_params
):
log.warning('Error adding certificate to %s', torrent_id)
except AttributeError:
log.warning(
'libtorrent version >=2.0.10 required to set ssl torrent cert without writing to disk'
)

@export
def move_storage(self, torrent_ids: List[str], dest: str):
log.debug('Moving storage %s to %s', torrent_ids, dest)
Expand Down Expand Up @@ -821,6 +873,17 @@ def get_listen_port(self) -> int:
"""Returns the active listen port"""
return self.session.listen_port()

@export
def get_ssl_listen_port(self) -> int:
"""Returns the active SSL listen port"""
try:
return self.session.ssl_listen_port()
except AttributeError:
log.warning(
'libtorrent version >=2.0.10 required to get active SSL listen port'
)
return -1

@export
def get_proxy(self) -> Dict[str, Any]:
"""Returns the proxy settings
Expand Down Expand Up @@ -999,6 +1062,7 @@ def create_torrent(
trackers=None,
add_to_session=False,
torrent_format=metafile.TorrentFormat.V1,
ca_cert=None,
):
if isinstance(torrent_format, str):
torrent_format = metafile.TorrentFormat(torrent_format)
Expand All @@ -1017,6 +1081,7 @@ def create_torrent(
trackers=trackers,
add_to_session=add_to_session,
torrent_format=torrent_format,
ca_cert=ca_cert,
)

def _create_torrent_thread(
Expand All @@ -1032,6 +1097,7 @@ def _create_torrent_thread(
trackers,
add_to_session,
torrent_format,
ca_cert,
):
from deluge import metafile

Expand All @@ -1045,6 +1111,7 @@ def _create_torrent_thread(
created_by=created_by,
trackers=trackers,
torrent_format=torrent_format,
ca_cert=ca_cert,
)

write_file = False
Expand Down
23 changes: 23 additions & 0 deletions deluge/core/preferencesmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
'listen_random_port': None,
'listen_use_sys_port': False,
'listen_reuse_port': True,
'ssl_torrents': False,
'ssl_listen_ports': [6892, 6896],
'ssl_torrents_certs': os.path.join(
deluge.configmanager.get_config_dir(), 'ssl_torrents_certs'
),
'outgoing_ports': [0, 0],
'random_outgoing_ports': True,
'copy_torrent_file': False,
Expand Down Expand Up @@ -224,6 +229,24 @@ def __set_listen_on(self):
f'{interface}:{port}'
for port in range(listen_ports[0], listen_ports[1] + 1)
]

if self.config['ssl_torrents']:
if self.config['random_port']:
ssl_listen_ports = [self.config['listen_random_port'] + 1] * 2
else:
ssl_listen_ports = self.config['ssl_listen_ports']
interfaces.extend(
[
f'{interface}:{port}s'
for port in range(ssl_listen_ports[0], ssl_listen_ports[1] + 1)
]
)
log.debug(
'SSL listen Interface: %s, Ports: %s',
interface,
listen_ports,
)

self.core.apply_session_settings(
{
'listen_system_port_fallback': self.config['listen_use_sys_port'],
Expand Down
50 changes: 50 additions & 0 deletions deluge/core/torrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,56 @@ def connect_peer(self, peer_ip, peer_port):
return False
return True

def set_ssl_certificate(
self,
certificate_path: str,
private_key_path: str,
dh_params_path: str,
password: str = '',
):
"""add a peer to the torrent

Args:
certificate_path(str) : Path to the PEM-encoded x509 certificate
private_key_path(str) : Path to the PEM-encoded private key
dh_params_path(str) : Path to the PEM-encoded Diffie-Hellman parameter
password(str) : (Optional) password used to decrypt the private key

Returns:
bool: True is successful, otherwise False
"""
try:
self.handle.set_ssl_certificate(
certificate_path, private_key_path, dh_params_path, password
)
except RuntimeError as ex:
log.error('Unable to set ssl certificate from file: %s', ex)
return False
return True

def set_ssl_certificate_buffer(
self,
certificate: str,
private_key: str,
dh_params: str,
):
"""add a peer to the torrent

Args:
certificate(str) : PEM-encoded content of the x509 certificate
private_key(str) : PEM-encoded content of the private key
dh_params(str) : PEM-encoded content of the Diffie-Hellman parameters

Returns:
bool: True is successful, otherwise False
"""
try:
self.handle.set_ssl_certificate_buffer(certificate, private_key, dh_params)
except RuntimeError as ex:
log.error('Unable to set ssl certificate from buffer: %s', ex)
return False
return True

def move_storage(self, dest):
"""Move a torrent's storage location

Expand Down
Loading
Loading