mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-28 05:29:25 +01:00
IRCNetwork: split SSL connection setup into separate functions
* _make_ssl_context(): returns the SSLContext to use on he network (with options set) * _setup_ssl(): sets up TLS by loading certfile / keyfile and calling wrap_socket() * _verify_ssl(): implements certificate fingerprint verification, raising TLSVerificationError (a new subclass of ConnectionError) if this fails This is a prerequisite for #592.
This commit is contained in:
parent
8386edc6d5
commit
579b5ce93f
144
classes.py
144
classes.py
@ -1525,6 +1525,9 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
# This is protocol specific, so stub it here in the base class.
|
# This is protocol specific, so stub it here in the base class.
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class TLSValidationError(ConnectionError):
|
||||||
|
"""Exception raised when additional TLS verifications fail."""
|
||||||
|
|
||||||
class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||||
S2S_BUFSIZE = 510
|
S2S_BUFSIZE = 510
|
||||||
|
|
||||||
@ -1572,6 +1575,73 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
|||||||
else:
|
else:
|
||||||
log.error(*args, **kwargs)
|
log.error(*args, **kwargs)
|
||||||
|
|
||||||
|
def _make_ssl_context(self):
|
||||||
|
"""
|
||||||
|
Returns a ssl.SSLContext instance appropriate for this connection.
|
||||||
|
"""
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
|
||||||
|
# Disable SSLv2 and SSLv3 - these are insecure
|
||||||
|
context.options |= ssl.OP_NO_SSLv2
|
||||||
|
context.options |= ssl.OP_NO_SSLv3
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _setup_ssl(self):
|
||||||
|
"""
|
||||||
|
Initializes SSL/TLS for this network.
|
||||||
|
"""
|
||||||
|
log.info('(%s) Using TLS/SSL for this connection...', self.name)
|
||||||
|
certfile = self.serverdata.get('ssl_certfile')
|
||||||
|
keyfile = self.serverdata.get('ssl_keyfile')
|
||||||
|
|
||||||
|
context = self._make_ssl_context()
|
||||||
|
|
||||||
|
# Cert and key files are optional, load them if specified.
|
||||||
|
if certfile and keyfile:
|
||||||
|
try:
|
||||||
|
context.load_cert_chain(certfile, keyfile)
|
||||||
|
except OSError:
|
||||||
|
log.exception('(%s) Caught OSError trying to initialize the SSL connection; '
|
||||||
|
'are "ssl_certfile" and "ssl_keyfile" set correctly?',
|
||||||
|
self.name)
|
||||||
|
raise
|
||||||
|
|
||||||
|
self._socket = context.wrap_socket(self._socket)
|
||||||
|
|
||||||
|
def _verify_ssl(self):
|
||||||
|
"""
|
||||||
|
Implements additional SSL/TLS verification (so far, only certificate fingerprints if enabled).
|
||||||
|
"""
|
||||||
|
peercert = self._socket.getpeercert(binary_form=True)
|
||||||
|
|
||||||
|
# Hash type is configurable using the ssl_fingerprint_type
|
||||||
|
# value, and defaults to sha256.
|
||||||
|
hashtype = self.serverdata.get('ssl_fingerprint_type', 'sha256').lower()
|
||||||
|
|
||||||
|
try:
|
||||||
|
hashfunc = getattr(hashlib, hashtype)
|
||||||
|
except AttributeError:
|
||||||
|
raise conf.ConfigurationError('Unsupported or invalid TLS/SSL certificate fingerprint type %r',
|
||||||
|
hashtype)
|
||||||
|
else:
|
||||||
|
fp = hashfunc(peercert).hexdigest()
|
||||||
|
expected_fp = self.serverdata.get('ssl_fingerprint')
|
||||||
|
|
||||||
|
if expected_fp:
|
||||||
|
if fp != expected_fp:
|
||||||
|
# SSL Fingerprint doesn't match; break.
|
||||||
|
raise TLSValidationError('Uplink TLS/SSL certificate fingerprint (%s) does not '
|
||||||
|
'match the one configured (%s: %s)' % (expected_fp, hashtype, fp))
|
||||||
|
else:
|
||||||
|
log.info('(%s) Uplink TLS/SSL certificate fingerprint '
|
||||||
|
'(%s) verified: %r', self.name, hashtype, fp)
|
||||||
|
else:
|
||||||
|
log.info('(%s) Uplink\'s TLS/SSL certificate fingerprint (%s) '
|
||||||
|
'is %r. You can enhance the security of your '
|
||||||
|
'link by specifying this in a "ssl_fingerprint"'
|
||||||
|
' option in your server block.', self.name,
|
||||||
|
hashtype, fp)
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
"""
|
"""
|
||||||
Connects to the network.
|
Connects to the network.
|
||||||
@ -1580,7 +1650,6 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
|||||||
|
|
||||||
ip = self.serverdata["ip"]
|
ip = self.serverdata["ip"]
|
||||||
port = self.serverdata["port"]
|
port = self.serverdata["port"]
|
||||||
checks_ok = True
|
|
||||||
try:
|
try:
|
||||||
# Set the socket type (IPv6 or IPv4).
|
# Set the socket type (IPv6 or IPv4).
|
||||||
stype = socket.AF_INET6 if self.serverdata.get("ipv6") else socket.AF_INET
|
stype = socket.AF_INET6 if self.serverdata.get("ipv6") else socket.AF_INET
|
||||||
@ -1600,28 +1669,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
|||||||
# Enable SSL if set to do so.
|
# Enable SSL if set to do so.
|
||||||
self.ssl = self.serverdata.get('ssl')
|
self.ssl = self.serverdata.get('ssl')
|
||||||
if self.ssl:
|
if self.ssl:
|
||||||
log.info('(%s) Attempting SSL for this connection...', self.name)
|
self._setup_ssl()
|
||||||
certfile = self.serverdata.get('ssl_certfile')
|
|
||||||
keyfile = self.serverdata.get('ssl_keyfile')
|
|
||||||
|
|
||||||
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
||||||
# Disable SSLv2 and SSLv3 - these are insecure
|
|
||||||
context.options |= ssl.OP_NO_SSLv2
|
|
||||||
context.options |= ssl.OP_NO_SSLv3
|
|
||||||
|
|
||||||
# Cert and key files are optional, load them if specified.
|
|
||||||
if certfile and keyfile:
|
|
||||||
try:
|
|
||||||
context.load_cert_chain(certfile, keyfile)
|
|
||||||
except OSError:
|
|
||||||
log.exception('(%s) Caught OSError trying to '
|
|
||||||
'initialize the SSL connection; '
|
|
||||||
'are "ssl_certfile" and '
|
|
||||||
'"ssl_keyfile" set correctly?',
|
|
||||||
self.name)
|
|
||||||
checks_ok = False
|
|
||||||
|
|
||||||
self._socket = context.wrap_socket(self._socket)
|
|
||||||
|
|
||||||
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
||||||
|
|
||||||
@ -1653,48 +1701,8 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
|||||||
|
|
||||||
self._selector_key = selectdriver.register(self)
|
self._selector_key = selectdriver.register(self)
|
||||||
|
|
||||||
# If SSL was enabled, optionally verify the certificate
|
if self.ssl:
|
||||||
# fingerprint for some added security. I don't bother to check
|
self._verify_ssl()
|
||||||
# the entire certificate for validity, since most IRC networks
|
|
||||||
# self-sign their certificates anyways.
|
|
||||||
if self.ssl and checks_ok:
|
|
||||||
peercert = self._socket.getpeercert(binary_form=True)
|
|
||||||
|
|
||||||
# Hash type is configurable using the ssl_fingerprint_type
|
|
||||||
# value, and defaults to sha256.
|
|
||||||
hashtype = self.serverdata.get('ssl_fingerprint_type', 'sha256').lower()
|
|
||||||
|
|
||||||
try:
|
|
||||||
hashfunc = getattr(hashlib, hashtype)
|
|
||||||
except AttributeError:
|
|
||||||
log.error('(%s) Unsupported SSL certificate fingerprint type %r given, disconnecting...',
|
|
||||||
self.name, hashtype)
|
|
||||||
checks_ok = False
|
|
||||||
else:
|
|
||||||
fp = hashfunc(peercert).hexdigest()
|
|
||||||
expected_fp = self.serverdata.get('ssl_fingerprint')
|
|
||||||
|
|
||||||
if expected_fp and checks_ok:
|
|
||||||
if fp != expected_fp:
|
|
||||||
# SSL Fingerprint doesn't match; break.
|
|
||||||
log.error('(%s) Uplink\'s SSL certificate '
|
|
||||||
'fingerprint (%s) does not match the '
|
|
||||||
'one configured: expected %r, got %r; '
|
|
||||||
'disconnecting...', self.name, hashtype,
|
|
||||||
expected_fp, fp)
|
|
||||||
checks_ok = False
|
|
||||||
else:
|
|
||||||
log.info('(%s) Uplink SSL certificate fingerprint '
|
|
||||||
'(%s) verified: %r', self.name, hashtype,
|
|
||||||
fp)
|
|
||||||
else:
|
|
||||||
log.info('(%s) Uplink\'s SSL certificate fingerprint (%s) '
|
|
||||||
'is %r. You can enhance the security of your '
|
|
||||||
'link by specifying this in a "ssl_fingerprint"'
|
|
||||||
' option in your server block.', self.name,
|
|
||||||
hashtype, fp)
|
|
||||||
|
|
||||||
if checks_ok:
|
|
||||||
|
|
||||||
self._queue_thread = threading.Thread(name="Queue thread for %s" % self.name,
|
self._queue_thread = threading.Thread(name="Queue thread for %s" % self.name,
|
||||||
target=self._process_queue, daemon=True)
|
target=self._process_queue, daemon=True)
|
||||||
@ -1716,11 +1724,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
|||||||
self._schedule_ping()
|
self._schedule_ping()
|
||||||
log.info('(%s) Server ready; listening for data.', self.name)
|
log.info('(%s) Server ready; listening for data.', self.name)
|
||||||
self.autoconnect_active_multiplier = 1 # Reset any extra autoconnect delays
|
self.autoconnect_active_multiplier = 1 # Reset any extra autoconnect delays
|
||||||
else: # Configuration error :(
|
|
||||||
log.error('(%s) A configuration error was encountered '
|
|
||||||
'trying to set up this connection. Please check'
|
|
||||||
' your configuration file and try again.',
|
|
||||||
self.name)
|
|
||||||
# _run_irc() or the protocol module it called raised an exception, meaning we've disconnected!
|
# _run_irc() or the protocol module it called raised an exception, meaning we've disconnected!
|
||||||
# Note: socket.error, ConnectionError, IOError, etc. are included in OSError since Python 3.3,
|
# Note: socket.error, ConnectionError, IOError, etc. are included in OSError since Python 3.3,
|
||||||
# so we don't need to explicitly catch them here.
|
# so we don't need to explicitly catch them here.
|
||||||
|
Loading…
Reference in New Issue
Block a user