From d9f5cdfeafac18fb738c0561b1df1d3f58efb416 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 12 Aug 2015 02:00:43 -0700 Subject: [PATCH] Irc: optionally validate SSL cert fingerprints (#80) --- config.yml.example | 5 +++++ main.py | 52 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/config.yml.example b/config.yml.example index 1f6be96..c091cce 100644 --- a/config.yml.example +++ b/config.yml.example @@ -56,9 +56,14 @@ servers: # Toggles SSL for this network. Defaults to false if not specified, and requires the # ssl_certfile and ssl_keyfile options to work. # ssl: true + # ssl_certfile: pylink-cert.pem # ssl_keyfile: pylink-key.pem + # Optionally, you can set this option to verify the SSL certificate + # fingerprint (SHA1) of your uplink. + # ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc" + ts6net: ip: 127.0.0.1 port: 7000 diff --git a/main.py b/main.py index 2c6336f..a1ef7d5 100755 --- a/main.py +++ b/main.py @@ -8,6 +8,7 @@ import sys from collections import defaultdict import threading import ssl +import hashlib from log import log import conf @@ -75,31 +76,62 @@ class Irc(): port = self.serverdata["port"] while True: self.initVars() + checks_ok = True try: self.socket = socket.socket() self.socket.setblocking(0) # Initial connection timeout is a lot smaller than the timeout after # we've connected; this is intentional. self.socket.settimeout(self.pingfreq) - - if self.serverdata.get('ssl'): + self.ssl = self.serverdata.get('ssl') + if self.ssl: log.info('(%s) Attempting SSL for this connection...', self.name) certfile = self.serverdata.get('ssl_certfile') keyfile = self.serverdata.get('ssl_keyfile') if certfile and keyfile: self.socket = ssl.wrap_socket(self.socket, certfile=certfile, keyfile=keyfile) else: - log.warning('(%s) SSL certfile/keyfile was not set correctly. ' - 'SSL will be disabled for this connection.', self.name) + log.error('(%s) SSL certfile/keyfile was not set ' + 'correctly, aborting... ', self.name) + checks_ok = False log.info("Connecting to network %r on %s:%s", self.name, ip, port) self.socket.connect((ip, port)) self.socket.settimeout(self.pingtimeout) - self.proto.connect(self) - self.spawnMain() - log.info('(%s) Starting ping schedulers....', self.name) - self.schedulePing() - log.info('(%s) Server ready; listening for data.', self.name) - self.run() + + if self.ssl and checks_ok: + peercert = self.socket.getpeercert(binary_form=True) + sha1fp = hashlib.sha1(peercert).hexdigest() + expected_fp = self.serverdata.get('ssl_fingerprint') + if expected_fp: + if sha1fp != expected_fp: + log.error('(%s) Uplink\'s SSL certificate ' + 'fingerprint (SHA1) does not match the ' + 'one configured: expected %r, got %r; ' + 'disconnecting...', self.name, + expected_fp, sha1fp) + checks_ok = False + else: + log.info('(%s) Uplink SSL certificate fingerprint ' + '(SHA1) verified: %r', self.name, sha1fp) + else: + log.info('(%s) Uplink\'s SSL certificate fingerprint ' + 'is %r. You can enhance the security of your ' + 'link by specifying this in a "ssl_fingerprint"' + ' option in your server block.', self.name, + sha1fp) + + if checks_ok: + self.proto.connect(self) + self.spawnMain() + log.info('(%s) Starting ping schedulers....', self.name) + self.schedulePing() + log.info('(%s) Server ready; listening for data.', self.name) + self.run() + else: + log.error('(%s) A configuration error was encountered ' + 'trying to set up this connection. Please check' + ' your configuration file and try again.', + self.name) except (socket.error, classes.ProtocolError, ConnectionError) as e: log.warning('(%s) Disconnected from IRC: %s: %s', self.name, type(e).__name__, str(e))