- 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.
This commit is contained in:
junglecow 2006-12-22 23:30:23 +00:00
parent 7f44b60267
commit 44674f7e75
5 changed files with 139 additions and 12 deletions

View File

@ -32,6 +32,7 @@
</item>
<item jid="amessage.info" name="Jabber server from amessage">
<active port="5222"/>
<digest algo="sha1" value="69:FA:D6:32:BF:84:CE:01:D9:FC:C5:1A:E3:04:12:FB:A5:28:03:1A"/>
</item>
<item jid="amessage.li" name="Jabber server in Liechtenstein">
<active port="5222"/>
@ -191,6 +192,7 @@
</item>
<item jid="jabber.org" name="Server hosted by the Jabber Software Foundation">
<active port="5222"/>
<digest algo="sha1" value="69:AE:AE:B0:3E:5A:55:56:68:65:BB:76:87:EA:FB:B5:21:E6:07:E7"/>
</item>
<item jid="jabber.org.au" name="Server in Australia">
<active port="5222"/>
@ -215,6 +217,7 @@
</item>
<item jid="jabber.sk" name="Slovak Jabber server">
<active port="5222"/>
<digest algo="sha1" value="EB:2C:ED:CC:FD:C1:F8:8F:58:07:CE:EF:09:0F:72:45:5E:1E:7A:96"/>
</item>
<item jid="jabber.snc.ru" name="Jabber server in Russia">
<active port="5222"/>
@ -369,4 +372,8 @@
<item jid="xmpp.us" name="">
<active port="5222"/>
</item>
</query>
<item jid="gajim.org" name="Official server of Gajim" hidden="True">
<active port="5222"/>
<digest algo="sha1" value="78:CE:51:E9:0D:56:5D:8B:16:9C:10:9E:24:ED:5C:F8:46:59:B1:19"/>
</item>
</query>

View File

@ -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):

View File

@ -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()

View File

@ -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)

View File

@ -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