changeset f0dce43a7cb1 in /home/hg/repos/gajim
author: Yann Leboulanger asterix@lagaule.org branches: gajim_0.16 details:http://hg.gajim.org/gajim?cmd=changeset;node=f0dce43a7cb1 description: both sender and receiver request remote SSL certificate, but only if it's a new one. Correctly verify remote SSL certificate.
diffstat:
src/common/connection_handlers_events.py | 7 ++- src/common/jingle_content.py | 21 ++++++-- src/common/jingle_ft.py | 37 ++++++++++++---- src/common/jingle_transport.py | 3 +- src/common/jingle_xtls.py | 70 +++++++++++++++++++++---------- src/common/protocol/bytestream.py | 16 +++++-- src/common/socks5.py | 46 +++++++++++++++----- src/gui_interface.py | 3 + 8 files changed, 143 insertions(+), 60 deletions(-)
diffs (truncated from 507 to 300 lines):
diff -r dff50cb9cd04 -r f0dce43a7cb1 src/common/connection_handlers_events.py --- a/src/common/connection_handlers_events.py Mon Dec 02 12:17:32 2013 +0100 +++ b/src/common/connection_handlers_events.py Wed Dec 04 18:43:28 2013 +0100 @@ -2052,8 +2052,11 @@ self.fjid = self.conn._ft_get_from(self.stanza) self.jid = gajim.get_jid_without_resource(self.fjid) if self.jingle_content: - self.FT_content.use_security = bool(self.jingle_content.getTag( - 'security')) + secu = self.jingle_content.getTag('security') + self.FT_content.use_security = bool(secu) + fingerprint = secu.getTag('fingerprint') + if fingerprint: + self.FT_content.x509_fingerprint = fingerprint.getData() if not self.FT_content.transport: self.FT_content.transport = JingleTransportSocks5() self.FT_content.transport.set_our_jid( diff -r dff50cb9cd04 -r f0dce43a7cb1 src/common/jingle_content.py --- a/src/common/jingle_content.py Mon Dec 02 12:17:32 2013 +0100 +++ b/src/common/jingle_content.py Wed Dec 04 18:43:28 2013 +0100 @@ -17,9 +17,12 @@ Handles Jingle contents (XEP 0166) """
+import os import gajim import nbxmpp from jingle_transport import JingleTransportIBB +from jingle_xtls import SELF_SIGNED_CERTIFICATE +from jingle_xtls import load_cert_file
contents = {}
@@ -213,12 +216,18 @@ if self.use_security: security = nbxmpp.simplexml.Node( tag=nbxmpp.NS_JINGLE_XTLS + ' security') - # TODO: add fingerprint element - for m in ('x509', ): # supported authentication methods - method = nbxmpp.simplexml.Node(tag='method') - method.setAttr('name', m) - security.addChild(node=method) - content.addChild(node=security) + certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE)\ + + '.cert' + cert = load_cert_file(certpath) + if cert: + digest_algo = cert.get_signature_algorithm().split('With')[0] + security.addChild('fingerprint').addData(cert.digest( + digest_algo)) + for m in ('x509', ): # supported authentication methods + method = nbxmpp.simplexml.Node(tag='method') + method.setAttr('name', m) + security.addChild(node=method) + content.addChild(node=security) content.addChild(node=description_node)
def destroy(self): diff -r dff50cb9cd04 -r f0dce43a7cb1 src/common/jingle_ft.py --- a/src/common/jingle_ft.py Mon Dec 02 12:17:32 2013 +0100 +++ b/src/common/jingle_ft.py Wed Dec 04 18:43:28 2013 +0100 @@ -22,6 +22,7 @@ import hashlib import gajim import nbxmpp +import jingle_xtls from jingle_content import contents, JingleContent from jingle_transport import * from common import helpers @@ -68,6 +69,7 @@ self.callbacks['transport-info'] += [self.__on_transport_info] self.callbacks['iq-result'] += [self.__on_iq_result] self.use_security = use_security + self.x509_fingerprint = None self.file_props = file_props self.weinitiate = self.session.weinitiate self.werequest = self.session.werequest @@ -164,17 +166,37 @@ h.addHash(hash_, self.file_props.algo) return h
+ def on_cert_received(self): + self.session.approve_session() + self.session.approve_content('file', name=self.name) + def __on_session_accept(self, stanza, content, error, action): log.info("__on_session_accept") con = self.session.connection + # We ack the session accept + response = stanza.buildReply('result') + response.delChild(response.getQuery()) + con.connection.send(response) security = content.getTag('security') if not security: # responder can not verify our fingerprint self.use_security = False + else: + fingerprint = security.getTag('fingerprint') + if fingerprint: + fingerprint = fingerprint.getData() + self.x509_fingerprint = fingerprint + if not jingle_xtls.check_cert(gajim.get_jid_without_resource( + self.session.responder), fingerprint): + id_ = jingle_xtls.send_cert_request(con, + self.session.responder) + jingle_xtls.key_exchange_pend(id_, + self.continue_session_accept, [stanza]) + raise nbxmpp.NodeProcessed + self.continue_session_accept(stanza) + + def continue_session_accept(self, stanza): + con = self.session.connection if self.state == STATE_TRANSPORT_REPLACE: - # We ack the session accept - response = stanza.buildReply('result') - response.delChild(response.getQuery()) - con.connection.send(response) # If we are requesting we don't have the file if self.session.werequest: raise nbxmpp.NodeProcessed @@ -185,16 +207,13 @@ # Calculate file hash in a new thread # if we haven't sent the hash already. if self.file_props.hash_ is None and self.file_props.algo and \ - not self.werequest: + not self.werequest: self.hashThread = threading.Thread(target=self.__send_hash) self.hashThread.start() for host in self.file_props.streamhosts: host['initiator'] = self.session.initiator host['target'] = self.session.responder host['sid'] = self.file_props.sid - response = stanza.buildReply('result') - response.delChild(response.getQuery()) - con.connection.send(response) fingerprint = None if self.use_security: fingerprint = 'client' @@ -203,7 +222,7 @@ self.file_props.sid, self.on_connect, self._on_connect_error, fingerprint=fingerprint, receiving=False) - return + raise nbxmpp.NodeProcessed self.__state_changed(STATE_TRANSFERING) raise nbxmpp.NodeProcessed
diff -r dff50cb9cd04 -r f0dce43a7cb1 src/common/jingle_transport.py --- a/src/common/jingle_transport.py Mon Dec 02 12:17:32 2013 +0100 +++ b/src/common/jingle_transport.py Wed Dec 04 18:43:28 2013 +0100 @@ -156,7 +156,8 @@ candidates.append(cand)
# we need this when we construct file_props on session-initiation - self.remote_candidates = candidates + if candidates: + self.remote_candidates = candidates return candidates
diff -r dff50cb9cd04 -r f0dce43a7cb1 src/common/jingle_xtls.py --- a/src/common/jingle_xtls.py Mon Dec 02 12:17:32 2013 +0100 +++ b/src/common/jingle_xtls.py Wed Dec 04 18:43:28 2013 +0100 @@ -26,15 +26,17 @@
PYOPENSSL_PRESENT = False
-pending_contents = {} # key-exchange id -> session, accept that session once key-exchange completes +# key-exchange id -> [callback, args], accept that session once key-exchange completes +pending_contents = {}
-def key_exchange_pend(id_, content): - pending_contents[id_] = content +def key_exchange_pend(id_, cb, args): + # args is a list + pending_contents[id_] = [cb, args]
def approve_pending_content(id_): - content = pending_contents[id_] - content.session.approve_session() - content.session.approve_content('file', name=content.name) + cb = pending_contents[id_][0] + args = pending_contents[id_][1] + cb(*args)
try: import OpenSSL.SSL @@ -57,18 +59,18 @@ log.info("certificate: %s" % certificate) return return_code
-def load_cert_file(cert_path, cert_store): +def load_cert_file(cert_path, cert_store=None): """ This is almost identical to the one in nbxmpp.tls_nb """ if not os.path.isfile(cert_path): - return + return None try: f = open(cert_path) except IOError, e: log.warning('Unable to open certificate file %s: %s' % (cert_path, str(e))) - return + return None lines = f.readlines() i = 0 begin = -1 @@ -80,7 +82,9 @@ try: x509cert = OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_PEM, cert) - cert_store.add_cert(x509cert) + if cert_store: + cert_store.add_cert(x509cert) + return x509cert except OpenSSL.crypto.Error, exception_obj: log.warning('Unable to load a certificate from file %s: %s' %\ (cert_path, exception_obj.args[0][0][2])) @@ -90,7 +94,7 @@ begin = -1 i += 1
-def get_context(fingerprint, verify_cb=None): +def get_context(fingerprint, verify_cb=None, remote_jid=None): """ constructs and returns the context objects """ @@ -130,22 +134,28 @@ % (default_dh_params_name, err)) raise
- store = ctx.get_cert_store() - for f in os.listdir(os.path.expanduser(gajim.MY_PEER_CERTS_PATH)): - load_cert_file(os.path.join(os.path.expanduser( - gajim.MY_PEER_CERTS_PATH), f), store) - log.debug('certificate file ' + f + ' loaded fingerprint ' + \ - fingerprint) + if remote_jid: + store = ctx.get_cert_store() + path = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), + remote_jid) + '.cert' + if os.path.exists(path): + load_cert_file(path, cert_store=store) + log.debug('certificate file ' + path + ' loaded fingerprint ' + \ + fingerprint) return ctx
-def send_cert(con, jid_from, sid): - certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + \ - '.cert' +def read_cert(certpath): certfile = open(certpath, 'r') certificate = '' for line in certfile.readlines(): if not line.startswith('-'): certificate += line + return certificate + +def send_cert(con, jid_from, sid): + certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + \ + '.cert' + certificate = read_cert(certpath) iq = nbxmpp.Iq('result', to=jid_from); iq.setAttr('id', sid)
@@ -175,9 +185,21 @@ f.write('-----BEGIN CERTIFICATE-----\n') f.write(cert) f.write('-----END CERTIFICATE-----\n') + f.close()
approve_pending_content(id_)
+def check_cert(jid, fingerprint): + certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid) + certpath += '.cert' + if os.path.exists(certpath): + cert = load_cert_file(certpath) + if cert: + digest_algo = cert.get_signature_algorithm().split('With')[0] + if cert.digest(digest_algo) == fingerprint: + return True + return False + def send_cert_request(con, to_jid): iq = nbxmpp.Iq('get', to=to_jid) id_ = con.connection.getAnID() @@ -201,12 +223,12 @@ pkey.generate_key(type, bits) return pkey
-def createCertRequest(pkey, digest="sha1", **name): +def createCertRequest(pkey, digest="sha256", **name): """ Create a certificate request.
Arguments: pkey - The key to associate with the request - digest - Digestion method to use for signing, default is sha1 + digest - Digestion method to use for signing, default is sha256