From d922af10436b26074c003f8abdf29340fe9c6340 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 21 Feb 2016 13:20:09 +0100 Subject: [PATCH] Verify server certificate, and deprecate Python < 2.7.9. Closes GH-1031. --- setup.py | 11 +++++----- src/conf.py | 17 +++++++++++++++ src/drivers/Socket.py | 48 +++++++++++++++++++++++++++++++++++-------- src/utils/net.py | 23 +++++++++++++++++++++ 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index 8547ef053..8bec9c6dc 100644 --- a/setup.py +++ b/setup.py @@ -236,10 +236,11 @@ setup( ) -if sys.version_info < (2, 7, 0): - sys.stderr.write('+-----------------------------------------------+\n') - sys.stderr.write('| Running Limnoria on Python 2.6 is deprecated. |\n') - sys.stderr.write('| Please consider upgrading to Python 3.x. |\n') - sys.stderr.write('+-----------------------------------------------+\n') +if sys.version_info < (2, 7, 9): + sys.stderr.write('+------------------------------------------------+\n') + sys.stderr.write('| Running Limnoria on Python versions older than |\n') + sys.stderr.write('| 2.7.9 is deprecated. |\n') + sys.stderr.write('| Please consider upgrading to Python 3.x. |\n') + sys.stderr.write('+------------------------------------------------+\n') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/src/conf.py b/src/conf.py index 95d91980a..79e14fc69 100644 --- a/src/conf.py +++ b/src/conf.py @@ -1163,6 +1163,23 @@ registerGlobalValue(supybot.protocols.http, 'proxy', through. The value should be of the form 'host:port'."""))) utils.web.proxy = supybot.protocols.http.proxy +### +# supybot.protocols.ssl +### +registerGroup(supybot.protocols, 'ssl') +class SSLVerifyMode(registry.OnlySomeStrings): + validStrings = ('required', 'optional', 'none') + def __call__(self): + import ssl + value = super(SSLVerifyMode, self).__call__() + return getattr(ssl, 'CERT_' + value.upper()) +registerGlobalValue(supybot.protocols.ssl, 'verifyMode', + SSLVerifyMode('required', _("""Determines whether server certificates ' + 'will be verified. Valid values are "required", "optional", and "none". ' + 'The default and recommended setting is "required", which checks the ' + 'server certificate is signed by a known Certificate Authority, and ' + 'aborts the connection if it is not."""))) + ### # HTTP server diff --git a/src/drivers/Socket.py b/src/drivers/Socket.py index 6a24c88c2..2da19ee99 100644 --- a/src/drivers/Socket.py +++ b/src/drivers/Socket.py @@ -238,9 +238,9 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): if wait: self.scheduleReconnect() return - server = self._getNextServer() - socks_proxy = getattr(conf.supybot.networks, self.irc.network) \ - .socksproxy() + self.server = self._getNextServer() + network_config = getattr(conf.supybot.networks, self.irc.network) + socks_proxy = network_config.socksproxy() try: if socks_proxy: import socks @@ -249,16 +249,16 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): 'using direct connection instead.') socks_proxy = '' if socks_proxy: - address = server[0] + address = self.server[0] else: try: - address = utils.net.getAddressFromHostname(server[0], + address = utils.net.getAddressFromHostname(self.server[0], attempt=self._attempt) except (socket.gaierror, socket.error) as e: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return - port = server[1] + port = self.server[1] drivers.log.connect(self.currentServer) try: self.conn = utils.net.getSocket(address, port=port, @@ -276,9 +276,17 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): try: # Connect before SSL, otherwise SSL is disabled if we use SOCKS. # See http://stackoverflow.com/q/16136916/539465 - self.conn.connect((address, server[1])) - if getattr(conf.supybot.networks, self.irc.network).ssl(): + self.conn.connect((address, port)) + if network_config.ssl(): self.starttls() + elif not network_config.requireStarttls(): + drivers.log.critical(('Connection to network %s' + 'does not use SSL/TLS, which makes it vulnerable to ' + 'man-in-the-middle attacks and passive eavesdropping. ' + 'You should consider upgrading your connection to SSL/TLS ' + '') + % self.irc.network) + def setTimeout(): self.conn.settimeout(conf.supybot.drivers.poll()) conf.supybot.drivers.poll.addCallback(setTimeout) @@ -353,7 +361,29 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): drivers.log.warning('Could not find cert file %s.' % certfile) certfile = None - self.conn = ssl.wrap_socket(self.conn, certfile=certfile) + try: + self.conn = utils.net.ssl_wrap_socket(self.conn, + logger=drivers.log, hostname=self.server[0], + certfile=certfile, + verify_mode=conf.supybot.protocols.ssl.verifyMode()) + except ssl.CertificateError as e: + drivers.log.critical(('Certificate validation failed when ' + 'connecting to %s: %s\n' + 'This means someone is doing a man-in-the-middle attack ' + 'on your connection.') + % (self.irc.network, e.args[0])) + raise ssl.SSLError('Aborting because of failed certificate ' + 'verification.') + except ssl.SSLError as e: + drivers.log.critical(('Certificate validation failed when ' + 'connecting to %s: %s\n' + 'This means someone is doing a man-in-the-middle attack ' + 'on your connection, or because the server\'s ' + 'certificate is not trusted.') + % (self.irc.network, e.args[1])) + raise ssl.SSLError('Aborting because of failed certificate ' + 'verification.') + Driver = SocketDriver diff --git a/src/utils/net.py b/src/utils/net.py index 66096f140..d2880b198 100644 --- a/src/utils/net.py +++ b/src/utils/net.py @@ -33,6 +33,7 @@ Simple utility modules. """ import re +import ssl import socket from .web import _ipAddr, _domain @@ -128,4 +129,26 @@ def isIPV6(s): return bruteIsIPV6(s) return False +if hasattr(ssl, 'create_default_context'): + def ssl_wrap_socket(conn, hostname, logger, certfile=None, + verify_mode=ssl.CERT_REQUIRED, **kwargs): + context = ssl.create_default_context(**kwargs) + context.verify_mode = verify_mode + if certfile: + context.load_cert_chain(certfile) + return context.wrap_socket(conn, server_hostname=hostname) +else: + def ssl_wrap_socket(conn, hostname, logger, certfile=None, ca_certs=None, + verify_mode=ssl.CERT_REQUIRED): + logger.critical('This Python version does not support SSL contexts, ' + 'which makes your connection vulnerable to man-in-the-middle ' + 'attacks. You should consider upgrading to Python 3 ' + '(or at least 2.7.9).') + # TLSv1.0 is the only TLS version Python < 2.7.9 supports + # (besides SSLv2 and v3, which are known to be insecure) + return ssl.wrap_socket(conn, certfile=certfile, ca_certs=ca_certs, + ssl_version=ssl.ssl.PROTOCOL_TLSv1, verify_mode=verify_mode) + + + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: