Philipp Hörist pushed to branch master at gajim / gajim

Commits:

5 changed files:

Changes:

  • gajim/common/config.py
    ... ... @@ -409,6 +409,7 @@ class Config:
    409 409
                         'recent_groupchats': [ opt_str, '' ],
    
    410 410
                         'httpupload_verify': [ opt_bool, True, _('HTTP Upload: Enable HTTPS Verification')],
    
    411 411
                         'filetransfer_preference' : [ opt_str, 'httpupload', _('Preferred file transfer mechanism for file drag&drop on chat window. Can be \'httpupload\' (default) or \'jingle\'')],
    
    412
    +                    'allow_posh': [ opt_bool, True, _('Allow cert verification with POSH')],
    
    412 413
                 }, {}),
    
    413 414
                 'statusmsg': ({
    
    414 415
                         'message': [ opt_str, '' ],
    

  • gajim/common/connection.py
    ... ... @@ -43,8 +43,11 @@ import hmac
    43 43
     import hashlib
    
    44 44
     import json
    
    45 45
     import logging
    
    46
    +import base64
    
    46 47
     from functools import partial
    
    47 48
     from string import Template
    
    49
    +from urllib.request import urlopen
    
    50
    +from urllib.error import URLError
    
    48 51
     
    
    49 52
     try:
    
    50 53
         randomsource = random.SystemRandom()
    
    ... ... @@ -75,42 +78,6 @@ from gajim.gtkgui_helpers import get_action
    75 78
     
    
    76 79
     log = logging.getLogger('gajim.c.connection')
    
    77 80
     
    
    78
    -ssl_error = {
    
    79
    -    2: _("Unable to get issuer certificate"),
    
    80
    -    3: _("Unable to get certificate CRL"),
    
    81
    -    4: _("Unable to decrypt certificate's signature"),
    
    82
    -    5: _("Unable to decrypt CRL's signature"),
    
    83
    -    6: _("Unable to decode issuer public key"),
    
    84
    -    7: _("Certificate signature failure"),
    
    85
    -    8: _("CRL signature failure"),
    
    86
    -    9: _("Certificate is not yet valid"),
    
    87
    -    10: _("Certificate has expired"),
    
    88
    -    11: _("CRL is not yet valid"),
    
    89
    -    12: _("CRL has expired"),
    
    90
    -    13: _("Format error in certificate's notBefore field"),
    
    91
    -    14: _("Format error in certificate's notAfter field"),
    
    92
    -    15: _("Format error in CRL's lastUpdate field"),
    
    93
    -    16: _("Format error in CRL's nextUpdate field"),
    
    94
    -    17: _("Out of memory"),
    
    95
    -    18: _("Self signed certificate"),
    
    96
    -    19: _("Self signed certificate in certificate chain"),
    
    97
    -    20: _("Unable to get local issuer certificate"),
    
    98
    -    21: _("Unable to verify the first certificate"),
    
    99
    -    22: _("Certificate chain too long"),
    
    100
    -    23: _("Certificate revoked"),
    
    101
    -    24: _("Invalid CA certificate"),
    
    102
    -    25: _("Path length constraint exceeded"),
    
    103
    -    26: _("Unsupported certificate purpose"),
    
    104
    -    27: _("Certificate not trusted"),
    
    105
    -    28: _("Certificate rejected"),
    
    106
    -    29: _("Subject issuer mismatch"),
    
    107
    -    30: _("Authority and subject key identifier mismatch"),
    
    108
    -    31: _("Authority and issuer serial number mismatch"),
    
    109
    -    32: _("Key usage does not include certificate signing"),
    
    110
    -    50: _("Application verification failure")
    
    111
    -    #100 is for internal usage: host not correct
    
    112
    -}
    
    113
    -
    
    114 81
     SERVICE_START_TLS = 'xmpp-client'
    
    115 82
     SERVICE_DIRECT_TLS = 'xmpps-client'
    
    116 83
     
    
    ... ... @@ -693,6 +660,13 @@ class Connection(CommonConnection, ConnectionHandlers):
    693 660
             self.secret_hmac = str(random.random())[2:].encode('utf-8')
    
    694 661
             self.removing_account = False
    
    695 662
     
    
    663
    +        # We only request POSH once
    
    664
    +        self._posh_requested = False
    
    665
    +        # Fingerprints received via POSH
    
    666
    +        self._posh_hashes = []
    
    667
    +        # The SSL Errors that we can override with POSH
    
    668
    +        self._posh_errors = [18, 19]
    
    669
    +
    
    696 670
             self.sm = Smacks(self) # Stream Management
    
    697 671
     
    
    698 672
             app.ged.register_event_handler('privacy-list-received', ged.CORE,
    
    ... ... @@ -1374,25 +1348,85 @@ class Connection(CommonConnection, ConnectionHandlers):
    1374 1348
     
    
    1375 1349
             cert = self.connection.Connection.ssl_certificate
    
    1376 1350
             errnum = self._ssl_errors.pop()
    
    1377
    -        hostname = app.config.get_per('accounts', self.name, 'hostname')
    
    1378
    -        text = _('The authenticity of the %s '
    
    1379
    -                 'certificate could be invalid') % hostname
    
    1380
    -        if errnum in ssl_error:
    
    1381
    -            text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
    
    1382
    -        else:
    
    1383
    -            text += _('\nUnknown SSL error: %d') % errnum
    
    1384
    -        fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
    
    1385
    -        fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
    
    1386
    -        pem = OpenSSL.crypto.dump_certificate(
    
    1387
    -            OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8')
    
    1388
    -        app.nec.push_incoming_event(
    
    1389
    -            SSLErrorEvent(None, conn=self,
    
    1390
    -                          error_text=text,
    
    1391
    -                          error_num=errnum,
    
    1392
    -                          cert=pem,
    
    1393
    -                          fingerprint_sha1=fingerprint_sha1,
    
    1394
    -                          fingerprint_sha256=fingerprint_sha256,
    
    1395
    -                          certificate=cert))
    
    1351
    +
    
    1352
    +        # Check if we can verify the cert with POSH
    
    1353
    +        if errnum in self._posh_errors:
    
    1354
    +            # Request the POSH json file
    
    1355
    +            self._get_posh_file(self._hostname)
    
    1356
    +            self._posh_requested = True
    
    1357
    +            cert_hash256 = self._calculate_cert_sha256(cert)
    
    1358
    +            print(cert_hash256)
    
    1359
    +            if cert_hash256 in self._posh_hashes:
    
    1360
    +                # Ignore this error if this cert is
    
    1361
    +                # verifyed with POSH
    
    1362
    +                self.process_ssl_errors()
    
    1363
    +                return
    
    1364
    +
    
    1365
    +        app.nec.push_incoming_event(SSLErrorEvent(None, conn=self,
    
    1366
    +                                                  error_num=errnum,
    
    1367
    +                                                  cert=cert))
    
    1368
    +
    
    1369
    +    @staticmethod
    
    1370
    +    def _calculate_cert_sha256(cert):
    
    1371
    +        der_encoded = OpenSSL.crypto.dump_certificate(
    
    1372
    +            OpenSSL.crypto.FILETYPE_ASN1, cert)
    
    1373
    +        hash_obj = hashlib.sha256(der_encoded)
    
    1374
    +        hash256 = base64.b64encode(hash_obj.digest()).decode('utf8')
    
    1375
    +        return hash256
    
    1376
    +
    
    1377
    +    def _get_posh_file(self, hostname=None, redirect=None):
    
    1378
    +        if self._posh_requested:
    
    1379
    +            # We already have requested POSH
    
    1380
    +            return
    
    1381
    +
    
    1382
    +        if not app.config.get_per('accounts', self.name, 'allow_posh'):
    
    1383
    +            return
    
    1384
    +
    
    1385
    +        if hostname is None and redirect is None:
    
    1386
    +            raise ValueError('There must be either a hostname or a url')
    
    1387
    +
    
    1388
    +        url = redirect
    
    1389
    +        if hostname is not None:
    
    1390
    +            url = 'https://%s/.well-known/posh/xmpp-client.json' % hostname
    
    1391
    +
    
    1392
    +        cafile = None
    
    1393
    +        if os.name == 'nt':
    
    1394
    +            cafile = certifi.where()
    
    1395
    +
    
    1396
    +        log.info('Request POSH from %s', url)
    
    1397
    +        try:
    
    1398
    +            file = urlopen(
    
    1399
    +                url, cafile=cafile, timeout=2)
    
    1400
    +        except URLError as exc:
    
    1401
    +            log.info('Error while requesting POSH: %s' % exc)
    
    1402
    +            return
    
    1403
    +
    
    1404
    +        if file.getcode() != 200:
    
    1405
    +            log.info('No POSH file found at %s', url)
    
    1406
    +            return
    
    1407
    +
    
    1408
    +        try:
    
    1409
    +            posh = json.loads(file.read())
    
    1410
    +        except json.decoder.JSONDecodeError as json_error:
    
    1411
    +            log.warning(json_error)
    
    1412
    +            return
    
    1413
    +
    
    1414
    +        # Redirect
    
    1415
    +        if 'url' in posh and redirect is None:
    
    1416
    +            # We dont allow redirects in redirects
    
    1417
    +            log.info('POSH redirect found')
    
    1418
    +            self._get_posh_file(redirect=posh['url'])
    
    1419
    +            return
    
    1420
    +
    
    1421
    +        if 'fingerprints' in posh:
    
    1422
    +            fingerprints = posh['fingerprints']
    
    1423
    +            for fingerprint in fingerprints:
    
    1424
    +                if 'sha-256' not in fingerprint:
    
    1425
    +                    continue
    
    1426
    +                self._posh_hashes.append(fingerprint['sha-256'])
    
    1427
    +
    
    1428
    +        log.info('POSH sha-256 fingerprints found: %s',
    
    1429
    +                 self._posh_hashes)
    
    1396 1430
     
    
    1397 1431
         def ssl_certificate_accepted(self):
    
    1398 1432
             if not self.connection:
    

  • gajim/common/connection_handlers_events.py
    ... ... @@ -42,7 +42,7 @@ from gajim.common import i18n
    42 42
     from gajim.common import dataforms
    
    43 43
     from gajim.common import configpaths
    
    44 44
     from gajim.common.zeroconf.zeroconf import Constant
    
    45
    -from gajim.common.const import KindConstant
    
    45
    +from gajim.common.const import KindConstant, SSLError
    
    46 46
     from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS
    
    47 47
     from gajim.common.jingle_transport import JingleTransportSocks5
    
    48 48
     from gajim.common.file_props import FilesProp
    
    ... ... @@ -1985,8 +1985,7 @@ class NewAccountConnectedEvent(nec.NetworkIncomingEvent):
    1985 1985
                 self.errnum = 0 # we don't have an errnum
    
    1986 1986
             self.ssl_msg = ''
    
    1987 1987
             if self.errnum > 0:
    
    1988
    -            from gajim.common.connection import ssl_error
    
    1989
    -            self.ssl_msg = ssl_error.get(self.errnum,
    
    1988
    +            self.ssl_msg = SSLError.get(self.errnum,
    
    1990 1989
                     _('Unknown SSL error: %d') % self.errnum)
    
    1991 1990
             self.ssl_cert = ''
    
    1992 1991
             self.ssl_fingerprint_sha1 = ''
    

  • gajim/common/const.py
    ... ... @@ -107,6 +107,42 @@ class JIDConstant(IntEnum):
    107 107
         ROOM_TYPE = 1
    
    108 108
     
    
    109 109
     
    
    110
    +SSLError = {
    
    111
    +    2: _("Unable to get issuer certificate"),
    
    112
    +    3: _("Unable to get certificate CRL"),
    
    113
    +    4: _("Unable to decrypt certificate's signature"),
    
    114
    +    5: _("Unable to decrypt CRL's signature"),
    
    115
    +    6: _("Unable to decode issuer public key"),
    
    116
    +    7: _("Certificate signature failure"),
    
    117
    +    8: _("CRL signature failure"),
    
    118
    +    9: _("Certificate is not yet valid"),
    
    119
    +    10: _("Certificate has expired"),
    
    120
    +    11: _("CRL is not yet valid"),
    
    121
    +    12: _("CRL has expired"),
    
    122
    +    13: _("Format error in certificate's notBefore field"),
    
    123
    +    14: _("Format error in certificate's notAfter field"),
    
    124
    +    15: _("Format error in CRL's lastUpdate field"),
    
    125
    +    16: _("Format error in CRL's nextUpdate field"),
    
    126
    +    17: _("Out of memory"),
    
    127
    +    18: _("Self signed certificate"),
    
    128
    +    19: _("Self signed certificate in certificate chain"),
    
    129
    +    20: _("Unable to get local issuer certificate"),
    
    130
    +    21: _("Unable to verify the first certificate"),
    
    131
    +    22: _("Certificate chain too long"),
    
    132
    +    23: _("Certificate revoked"),
    
    133
    +    24: _("Invalid CA certificate"),
    
    134
    +    25: _("Path length constraint exceeded"),
    
    135
    +    26: _("Unsupported certificate purpose"),
    
    136
    +    27: _("Certificate not trusted"),
    
    137
    +    28: _("Certificate rejected"),
    
    138
    +    29: _("Subject issuer mismatch"),
    
    139
    +    30: _("Authority and subject key identifier mismatch"),
    
    140
    +    31: _("Authority and issuer serial number mismatch"),
    
    141
    +    32: _("Key usage does not include certificate signing"),
    
    142
    +    50: _("Application verification failure"),
    
    143
    +    }
    
    144
    +
    
    145
    +
    
    110 146
     THANKS = u"""\
    
    111 147
     Alexander Futász
    
    112 148
     Alexander V. Butenko
    

  • gajim/gui_interface.py
    ... ... @@ -98,7 +98,7 @@ from gajim.common.connection import Connection
    98 98
     from gajim.common.file_props import FilesProp
    
    99 99
     from gajim.common import pep
    
    100 100
     from gajim import emoticons
    
    101
    -from gajim.common.const import AvatarSize
    
    101
    +from gajim.common.const import AvatarSize, SSLError
    
    102 102
     
    
    103 103
     from gajim import roster_window
    
    104 104
     from gajim import profile_window
    
    ... ... @@ -1343,7 +1343,6 @@ class Interface:
    1343 1343
                 obj.exchange_items_list, obj.fjid)
    
    1344 1344
     
    
    1345 1345
         def handle_event_ssl_error(self, obj):
    
    1346
    -        # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint, sha256_fingerprint))
    
    1347 1346
             account = obj.conn.name
    
    1348 1347
             server = app.config.get_per('accounts', account, 'hostname')
    
    1349 1348
     
    
    ... ... @@ -1379,22 +1378,32 @@ class Interface:
    1379 1378
                 app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
    
    1380 1379
                     show='offline'))
    
    1381 1380
     
    
    1381
    +        text = _('The authenticity of the %s '
    
    1382
    +                 'certificate could be invalid') % server
    
    1383
    +
    
    1384
    +        default_text = _('\nUnknown SSL error: %d') % obj.error_num
    
    1385
    +        ssl_error_text = SSLError.get(obj.error_num, default_text)
    
    1386
    +        text += _('\nSSL Error: <b>%s</b>') % ssl_error_text
    
    1387
    +
    
    1388
    +        fingerprint_sha1 = obj.cert.digest('sha1').decode('utf-8')
    
    1389
    +        fingerprint_sha256 = obj.cert.digest('sha256').decode('utf-8')
    
    1390
    +
    
    1382 1391
             pritext = _('Error verifying SSL certificate')
    
    1383 1392
             sectext = _('There was an error verifying the SSL certificate of your '
    
    1384 1393
                 'XMPP server: %(error)s\nDo you still want to connect to this '
    
    1385
    -            'server?') % {'error': obj.error_text}
    
    1394
    +            'server?') % {'error': text}
    
    1386 1395
             if obj.error_num in (18, 27):
    
    1387 1396
                 checktext1 = _('Add this certificate to the list of trusted '
    
    1388 1397
                 'certificates.\nSHA-1 fingerprint of the certificate:\n%(sha1)s'
    
    1389 1398
                 '\nSHA-256 fingerprint of the certificate:\n%(sha256)s') % \
    
    1390
    -            {'sha1': obj.fingerprint_sha1, 'sha256': obj.fingerprint_sha256}
    
    1399
    +            {'sha1': fingerprint_sha1, 'sha256': fingerprint_sha256}
    
    1391 1400
             else:
    
    1392 1401
                 checktext1 = ''
    
    1393 1402
             checktext2 = _('Ignore this error for this certificate.')
    
    1394 1403
             if 'ssl_error' in self.instances[account]['online_dialog']:
    
    1395 1404
                 self.instances[account]['online_dialog']['ssl_error'].destroy()
    
    1396 1405
             self.instances[account]['online_dialog']['ssl_error'] = \
    
    1397
    -            dialogs.SSLErrorDialog(obj.conn.name, obj.certificate, pritext,
    
    1406
    +            dialogs.SSLErrorDialog(obj.conn.name, obj.cert, pritext,
    
    1398 1407
                 sectext, checktext1, checktext2, on_response_ok=on_ok,
    
    1399 1408
                 on_response_cancel=on_cancel)
    
    1400 1409
             self.instances[account]['online_dialog']['ssl_error'].set_title(