Skip to content

Commit

Permalink
Merge pull request #2246 from half-duplex/ssl-wrap-socket
Browse files Browse the repository at this point in the history
backends: allow setting TLS version and ciphers
  • Loading branch information
dgw authored Jun 5, 2022
2 parents 98eb805 + 1b9f86d commit fd9e038
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 1 deletion.
85 changes: 85 additions & 0 deletions sopel/config/core_section.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from ssl import TLSVersion

from sopel.config.types import (
BooleanAttribute,
ChoiceAttribute,
Expand All @@ -21,6 +23,20 @@
"""Default URL schemes allowed for URLs."""


def _parse_ssl_version(var: str) -> TLSVersion:
"""Parse an ssl_version config variable.
:param var: The input string, e.g. "TLSv1_3".
:return: The TLSVersion object for that version.
"""
try:
return TLSVersion[var]
except KeyError:
raise ValueError(
"'{}' is not a valid TLSVersion. Try e.g. TLSv1_3'".format(var)
)


def configure(config):
"""Interactively configure the bot's ``[core]`` config section.
Expand Down Expand Up @@ -1187,6 +1203,75 @@ def homedir(self):
"""

ssl_ciphers = ListAttribute(
'ssl_ciphers',
default=[
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES256-GCM-SHA384",
],
)
"""The cipher suites enabled for SSL/TLS connections.
:default: ``ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-RSA-CHACHA20-POLY1305
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384``
The default is the `Mozilla Intermediate configuration`__ as of February
2022. This parameter is in OpenSSL format; see the `ciphers manual page`__
for your version. This setting cannot be used to configure TLS 1.3.
.. __: https://wiki.mozilla.org/Security/Server_Side_TLS
.. __: https://www.openssl.org/docs/manmaster/man1/ciphers.html
.. code-block:: ini
ssl_ciphers =
ECDSA+AES256
!SHA1
.. note::
Cipher selection is also subject to the available SSL/TLS versions;
see :attr:`ssl_minimum_version`.
"""

ssl_minimum_version = ValidatedAttribute(
'ssl_minimum_version',
default=TLSVersion.TLSv1_2,
parse=_parse_ssl_version,
serialize=lambda ver: ver.name,
)
"""The minimum version allowed for SSL/TLS connections.
:default: ``TLSv1_2``
You should not set this lower than the default unless the server does not
support modern versions. Set this to a :class:`~ssl.TLSVersion` value,
e.g. ``TLSv1`` or ``TLSv1_3``.
.. code-block:: ini
ssl_minimum_version = TLSv1_3
.. note::
To use insecure SSL/TLS versions, you may also need to enable
insecure cipher suites. See :attr:`ssl_ciphers`.
"""

use_ssl = BooleanAttribute('use_ssl', default=True)
"""Whether to use a SSL/TLS encrypted connection.
Expand Down
2 changes: 2 additions & 0 deletions sopel/irc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ def get_irc_backend(
keyfile=self.settings.core.client_cert_file,
verify_ssl=self.settings.core.verify_ssl,
ca_certs=self.settings.core.ca_certs,
ssl_ciphers=self.settings.core.ssl_ciphers,
ssl_minimum_version=self.settings.core.ssl_minimum_version,
)

def run(self, host: str, port: int = 6667) -> None:
Expand Down
10 changes: 9 additions & 1 deletion sopel/irc/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import signal
import ssl
import threading
from typing import Dict, Optional, Tuple, TYPE_CHECKING
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING

from .abstract_backends import AbstractIRCBackend

Expand Down Expand Up @@ -54,6 +54,8 @@ class AsyncioBackend(AbstractIRCBackend):
``use_ssl`` is not ``True``
:param ca_certs: optional location to the CA certificates; ignored if
``verify_ssl`` is ``False``
:param ssl_ciphers: the OpenSSL cipher suites to use
:param ssl_minimum_version: the lowest SSL/TLS version to accept
"""
def __init__(
self,
Expand All @@ -68,6 +70,8 @@ def __init__(
keyfile: Optional[str] = None,
verify_ssl: bool = True,
ca_certs: Optional[str] = None,
ssl_ciphers: Optional[List[str]] = None,
ssl_minimum_version: Optional[ssl.TLSVersion] = None,
**kwargs,
):
super().__init__(bot)
Expand All @@ -80,6 +84,8 @@ def __init__(
self._keyfile: Optional[str] = keyfile
self._verify_ssl: bool = verify_ssl
self._ca_certs: Optional[str] = ca_certs
self._ssl_ciphers: str = ":".join(ssl_ciphers)
self._ssl_minimum_version: ssl.TLSVersion = ssl_minimum_version

# timeout configuration
self._server_timeout: float = float(server_timeout or 120)
Expand Down Expand Up @@ -263,6 +269,8 @@ def get_connection_kwargs(self) -> Dict:

if self._use_ssl:
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ssl_context.minimum_version = self._ssl_minimum_version
ssl_context.set_ciphers(self._ssl_ciphers)
if self._certfile is not None:
# load_cert_chain requires a certfile (cannot be None)
ssl_context.load_cert_chain(
Expand Down

0 comments on commit fd9e038

Please sign in to comment.