From 44674f7e75b5a475d0c7738d0dae95698a3912d2 Mon Sep 17 00:00:00 2001 From: junglecow Date: Fri, 22 Dec 2006 23:30:23 +0000 Subject: [PATCH] - Implement matching of fingerprints against servers.xml - Add support for fingerprints to servers.xml parser. - Add support for 'hidden' servers to servers.xml parser. - Add some fingerprints to servers.xml, for testing and as example for the new format. - Force asynchronous (nonblocking) SSL handshake in all case - Add logging to c/connection.py Known issues: - Checking of fingerprints doesn't work on in-band SSL (Typically port 5222) because of stuff happening out of sequence. Workaround: use immediate SSL mode ("Legacy SSL" option in server config). Because there is as of yet no other way to /force/ SSL, this is also the most secure setting. - A lot of code is still looking for a better place to live. --- data/other/servers.xml | 9 +++- src/common/connection.py | 58 ++++++++++++++++++++++++ src/common/xmpp/transports_nb.py | 4 +- src/config.py | 3 +- src/gtkgui_helpers.py | 77 ++++++++++++++++++++++++++++---- 5 files changed, 139 insertions(+), 12 deletions(-) diff --git a/data/other/servers.xml b/data/other/servers.xml index 0a044c1c2..f8bcf7161 100644 --- a/data/other/servers.xml +++ b/data/other/servers.xml @@ -32,6 +32,7 @@ + @@ -191,6 +192,7 @@ + @@ -215,6 +217,7 @@ + @@ -369,4 +372,8 @@ - \ No newline at end of file + + diff --git a/src/common/connection.py b/src/common/connection.py index 606725a6b..945cbf497 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -42,6 +42,18 @@ USE_GPG = GnuPG.USE_GPG from common.rst_xhtml_generator import create_xhtml +import logging +h = logging.StreamHandler() +f = logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s') +h.setFormatter(f) +log = logging.getLogger('Gajim.connection') +log.addHandler(h) +log.setLevel(logging.DEBUG) +log.propagate = False +del h, f + +import gtkgui_helpers + class Connection(ConnectionHandlers): '''Connection class''' def __init__(self, name): @@ -434,6 +446,52 @@ class Connection(ConnectionHandlers): hostname = gajim.config.get_per('accounts', self.name, 'hostname') resource = gajim.config.get_per('accounts', self.name, 'resource') self.connection = con + + # FIXME: find a more permanent place for loading servers.xml + servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml') + servers = gtkgui_helpers.parse_server_xml(servers_xml) + servers = dict(map(lambda e: (e[0], e), servers)) + + fpr_good = None # None: No fpr in database, False: mismatch, True: match + try: + log.debug("con: %s", con) + log.debug("con.Connection: %s", con.Connection) + log.debug("con.Connection.serverDigestSHA1: %s", con.Connection.serverDigestSHA1) + log.debug("con.Connection.serverDigestMD5: %s", con.Connection.serverDigestMD5) + sha1 = gtkgui_helpers.HashDigest('sha1', con.Connection.serverDigestSHA1) + md5 = gtkgui_helpers.HashDigest('md5', con.Connection.serverDigestMD5) + log.debug("sha1: %s", repr(sha1)) + log.debug("md5: %s", repr(md5)) + + for got in (sha1, md5): + svent = servers.get(hostname) + if not svent: continue + expected = svent[2]['digest'].get(got.algo) + if expected: + fpr_good = got == expected + break + + except AttributeError: + log.debug("Connection to %s doesn't seem to have a fingerprint:", hostname, exc_info=True) + + if fpr_good == False: + log.error("Fingerprint mismatch for %s: Got %s, expected %s", hostname, got, expected) + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('CONNECTION_LOST', + (_('Bad fingerprint for "%s"') % self._hostname, + _("Server's key changed, or spy attack."))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + + if fpr_good == None: + log.warning(_("No fingerprint in database for %s. Connection could be insecure."), hostname) + + if fpr_good == True: + log.debug("Fingerprint found and matched for %s.", hostname) + con.auth(name, self.password, resource, 1, self.__on_auth) def __on_auth(self, con, auth): diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py index 4585f1cb2..fff25f013 100644 --- a/src/common/xmpp/transports_nb.py +++ b/src/common/xmpp/transports_nb.py @@ -721,7 +721,7 @@ class NonBlockingTLS(PlugIn): tcpsock._send = wrapper.send log.debug("Initiating handshake...") - #tcpsock._sslObj.setblocking(True) + tcpsock._sslObj.setblocking(False) try: self.starttls='in progress' tcpsock._sslObj.do_handshake() @@ -744,6 +744,8 @@ class NonBlockingTLS(PlugIn): issuer = cert.get_issuer() tcpsock._sslIssuer = unicode(issuer) tcpsock._sslServer = unicode(peer) + tcpsock.serverDigestSHA1 = cert.digest('sha1') + tcpsock.serverDigestMD5 = cert.digest('md5') # FIXME: remove debug prints peercert = tcpsock._sslObj.get_peer_certificate() diff --git a/src/config.py b/src/config.py index 6225b896d..5faca1f17 100644 --- a/src/config.py +++ b/src/config.py @@ -2916,7 +2916,8 @@ class AccountCreationWizardWindow: servers = gtkgui_helpers.parse_server_xml(servers_xml) servers_model = gtk.ListStore(str, int) for server in servers: - servers_model.append((str(server[0]), int(server[1]))) + if not server[2]['hidden']: + servers_model.append((str(server[0]), int(server[1]))) completion.set_model(servers_model) completion.set_text_column(0) diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index ce36d706f..3f329cb0a 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -258,34 +258,93 @@ def resize_window(window, w, h): h = screen_h window.resize(abs(w), abs(h)) -class TagInfoHandler(xml.sax.ContentHandler): - def __init__(self, tagname1, tagname2): +class HashDigest: + def __init__(self, algo, digest): + self.algo = self.cleanAlgo(algo) + self.digest = self.cleanDigest(digest) + + def cleanAlgo(self, algo): + algo = algo.strip().lower() + for strip in (' :.-_'): algo = algo.replace(strip, '') + return algo + + def cleanDigest(self, digest): + digest = digest.strip().lower() + for strip in (' :.'): digest = digest.replace(strip, '') + return digest + + def __eq__(self, other): + sa, sd = self.algo, self.digest + if isinstance(other, self.__class__): + oa, od = other.algo, other.digest + elif isinstance(other, basestring): + sa, oa, od = None, None, self.cleanDigest(other) + elif isinstance(other, tuple) and len(other) == 2: + oa, od = self.cleanAlgo(other[0]), self.cleanDigest(other[1]) + else: + return False + + return sa == oa and sd == od + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return self.algo ^ self.digest + + def __str__(self): + prettydigest = '' + for i in xrange(0, len(self.digest), 2): + prettydigest += self.digest[i:i + 2] + ':' + return prettydigest[:-1] + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self))) + +class ServersXMLHandler(xml.sax.ContentHandler): + def __init__(self): xml.sax.ContentHandler.__init__(self) - self.tagname1 = tagname1 - self.tagname2 = tagname2 self.servers = [] def startElement(self, name, attributes): - if name == self.tagname1: + if name == 'item': + # we will get the port next time so we just set it 0 here + sitem = [None, 0, {}] + sitem[2]['digest'] = {} + sitem[2]['hidden'] = False for attribute in attributes.getNames(): if attribute == 'jid': jid = attributes.getValue(attribute) - # we will get the port next time so we just set it 0 here - self.servers.append([jid, 0]) - elif name == self.tagname2: + sitem[0] = jid + elif attribute == 'hidden': + hidden = attributes.getValue(attribute) + if hidden.lower() in ('1', 'yes', 'true'): + sitem[2]['hidden'] = True + self.servers.append(sitem) + elif name == 'active': for attribute in attributes.getNames(): if attribute == 'port': port = attributes.getValue(attribute) # we received the jid last time, so we now assign the port # number to the last jid in the list self.servers[-1][1] = port + elif name == 'digest': + algo=None + digest=None + for attribute in attributes.getNames(): + if attribute == 'algo': + algo = attributes.getValue(attribute) + elif attribute == 'value': + digest = attributes.getValue(attribute) + hd = HashDigest(algo, digest) + self.servers[-1][2]['digest'][hd.algo] = hd def endElement(self, name): pass def parse_server_xml(path_to_file): try: - handler = TagInfoHandler('item', 'active') + handler = ServersXMLHandler() xml.sax.parse(path_to_file, handler) return handler.servers # handle exception if unable to open file