From 88ffd1b843c33402045bd2facde04be0a73027ec Mon Sep 17 00:00:00 2001 From: DjLegolas Date: Fri, 1 May 2020 11:37:11 +0300 Subject: [PATCH] [Servers] Moved check_ssl_keys and generate_ssl_keys to crypto_utils.py With this change, we drop a core dependency from the UI. This will help group together all related functionality in one place, i.e. all security related functions. Also updated testssl.sh version to 3.0.6 (SECURITY_TEST) Closes: deluge-torrent/deluge#288 --- .github/workflows/ci.yml | 15 ++++++++- deluge/core/rpcserver.py | 60 +-------------------------------- deluge/crypto_utils.py | 62 +++++++++++++++++++++++++++++++++++ deluge/tests/test_security.py | 7 ++-- deluge/ui/web/server.py | 3 +- 5 files changed, 82 insertions(+), 65 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3c7dc990..206da5f58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,12 +41,25 @@ jobs: key-server: keyserver.ubuntu.com install: python3-libtorrent-dbg + - name: Sets env var for security + if: (github.event_name == 'pull_request' && contains(github.event.pull_request.body, 'security_test')) || (github.event_name == 'push' && contains(github.event.head_commit.message, 'security_test')) + run: echo "SECURITY_TESTS=True" >> $GITHUB_ENV + - name: Install dependencies run: | pip install --upgrade pip wheel pip install -r requirements.txt -r requirements-tests.txt pip install -e . + - name: Install security dependencies + if: contains(env.SECURITY_TESTS, 'True') + run: | + wget -O- $TESTSSL_URL$TESTSSL_VER | tar xz + mv -t deluge/tests/data testssl.sh-$TESTSSL_VER/testssl.sh testssl.sh-$TESTSSL_VER/etc/; + env: + TESTSSL_VER: 3.0.6 + TESTSSL_URL: https://codeload.github.com/drwetter/testssl.sh/tar.gz/refs/tags/v + - name: Setup core dump directory run: | sudo mkdir /cores/ && sudo chmod 777 /cores/ @@ -57,7 +70,7 @@ jobs: ulimit -c unlimited # Enable core dumps to be captured cp /usr/lib/python3/dist-packages/libtorrent*.so $GITHUB_WORKSPACE/deluge python -c 'from deluge._libtorrent import lt; print(lt.__version__)'; - catchsegv python -X dev -m pytest -v -m "not (todo or gtkui or security)" deluge + catchsegv python -X dev -m pytest -v -m "not (todo or gtkui)" deluge - uses: actions/upload-artifact@v2 # capture all crashes as build artifacts diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index adb521901..4fa94c387 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -12,13 +12,11 @@ from __future__ import unicode_literals import logging import os -import stat import sys import traceback from collections import namedtuple from types import FunctionType -from OpenSSL import crypto from twisted.internet import defer, reactor from twisted.internet.protocol import Factory, connectionDone @@ -29,7 +27,7 @@ from deluge.core.authmanager import ( AUTH_LEVEL_DEFAULT, AUTH_LEVEL_NONE, ) -from deluge.crypto_utils import get_context_factory +from deluge.crypto_utils import check_ssl_keys, get_context_factory from deluge.error import ( DelugeError, IncompatibleClient, @@ -588,59 +586,3 @@ class RPCServer(component.Component): def stop(self): self.factory.state = 'stopping' - - -def check_ssl_keys(): - """ - Check for SSL cert/key and create them if necessary - """ - ssl_dir = deluge.configmanager.get_config_dir('ssl') - if not os.path.exists(ssl_dir): - # The ssl folder doesn't exist so we need to create it - os.makedirs(ssl_dir) - generate_ssl_keys() - else: - for f in ('daemon.pkey', 'daemon.cert'): - if not os.path.exists(os.path.join(ssl_dir, f)): - generate_ssl_keys() - break - - -def generate_ssl_keys(): - """ - This method generates a new SSL key/cert. - """ - from deluge.common import PY2 - - digest = 'sha256' if not PY2 else b'sha256' - - # Generate key pair - pkey = crypto.PKey() - pkey.generate_key(crypto.TYPE_RSA, 2048) - - # Generate cert request - req = crypto.X509Req() - subj = req.get_subject() - setattr(subj, 'CN', 'Deluge Daemon') - req.set_pubkey(pkey) - req.sign(pkey, digest) - - # Generate certificate - cert = crypto.X509() - cert.set_serial_number(0) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years - cert.set_issuer(req.get_subject()) - cert.set_subject(req.get_subject()) - cert.set_pubkey(req.get_pubkey()) - cert.sign(pkey, digest) - - # Write out files - ssl_dir = deluge.configmanager.get_config_dir('ssl') - with open(os.path.join(ssl_dir, 'daemon.pkey'), 'wb') as _file: - _file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) - with open(os.path.join(ssl_dir, 'daemon.cert'), 'wb') as _file: - _file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) - # Make the files only readable by this user - for f in ('daemon.pkey', 'daemon.cert'): - os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE) diff --git a/deluge/crypto_utils.py b/deluge/crypto_utils.py index 7672efa71..978eb0410 100644 --- a/deluge/crypto_utils.py +++ b/deluge/crypto_utils.py @@ -9,6 +9,10 @@ from __future__ import division, print_function, unicode_literals +import os +import stat + +from OpenSSL import crypto from OpenSSL.crypto import FILETYPE_PEM from twisted.internet.ssl import ( AcceptableCiphers, @@ -18,6 +22,8 @@ from twisted.internet.ssl import ( TLSVersion, ) +import deluge.configmanager + # A TLS ciphers list. # Sources for more information on TLS ciphers: # - https://wiki.mozilla.org/Security/Server_Side_TLS @@ -77,3 +83,59 @@ def get_context_factory(cert_path, pkey_path): ctx.set_options(SSL_OP_NO_RENEGOTIATION) return cert_options + + +def check_ssl_keys(): + """ + Check for SSL cert/key and create them if necessary + """ + ssl_dir = deluge.configmanager.get_config_dir('ssl') + if not os.path.exists(ssl_dir): + # The ssl folder doesn't exist so we need to create it + os.makedirs(ssl_dir) + generate_ssl_keys() + else: + for f in ('daemon.pkey', 'daemon.cert'): + if not os.path.exists(os.path.join(ssl_dir, f)): + generate_ssl_keys() + break + + +def generate_ssl_keys(): + """ + This method generates a new SSL key/cert. + """ + from deluge.common import PY2 + + digest = 'sha256' if not PY2 else b'sha256' + + # Generate key pair + pkey = crypto.PKey() + pkey.generate_key(crypto.TYPE_RSA, 2048) + + # Generate cert request + req = crypto.X509Req() + subj = req.get_subject() + setattr(subj, 'CN', 'Deluge Daemon') + req.set_pubkey(pkey) + req.sign(pkey, digest) + + # Generate certificate + cert = crypto.X509() + cert.set_serial_number(0) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years + cert.set_issuer(req.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(pkey, digest) + + # Write out files + ssl_dir = deluge.configmanager.get_config_dir('ssl') + with open(os.path.join(ssl_dir, 'daemon.pkey'), 'wb') as _file: + _file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) + with open(os.path.join(ssl_dir, 'daemon.cert'), 'wb') as _file: + _file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + # Make the files only readable by this user + for f in ('daemon.pkey', 'daemon.cert'): + os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE) diff --git a/deluge/tests/test_security.py b/deluge/tests/test_security.py index 700fc9967..4ad66ab51 100644 --- a/deluge/tests/test_security.py +++ b/deluge/tests/test_security.py @@ -45,6 +45,7 @@ class SecurityBaseTestCase(object): get_test_data_file('testssl.sh'), '--quiet', '--nodns', + 'none', '--color', '0', test, @@ -55,11 +56,11 @@ class SecurityBaseTestCase(object): def on_result(results): if test == '-e': - results = results[0].split('\n')[7:-6] + results = results[0].split(b'\n')[7:-6] self.assertTrue(len(results) > 3) else: - self.assertIn('OK', results[0]) - self.assertNotIn('NOT ok', results[0]) + self.assertIn(b'OK', results[0]) + self.assertNotIn(b'NOT ok', results[0]) d.addCallback(on_result) return d diff --git a/deluge/ui/web/server.py b/deluge/ui/web/server.py index ea2658071..2f8921b22 100644 --- a/deluge/ui/web/server.py +++ b/deluge/ui/web/server.py @@ -23,8 +23,7 @@ from twisted.web.resource import EncodingResourceWrapper from deluge import common, component, configmanager from deluge.common import is_ipv6 -from deluge.core.rpcserver import check_ssl_keys -from deluge.crypto_utils import get_context_factory +from deluge.crypto_utils import check_ssl_keys, get_context_factory from deluge.i18n import set_language, setup_translation from deluge.ui.tracker_icons import TrackerIcons from deluge.ui.web.auth import Auth