- 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:
parent
7f44b60267
commit
44674f7e75
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue