diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/base/checks.py | 17 | ||||
| -rw-r--r-- | src/leap/base/exceptions.py | 5 | ||||
| -rw-r--r-- | src/leap/crypto/certs.py | 31 | ||||
| -rw-r--r-- | src/leap/eip/checks.py | 27 | ||||
| -rw-r--r-- | src/leap/eip/exceptions.py | 11 | ||||
| -rwxr-xr-x | src/leap/gui/firstrunwizard.py | 128 | 
6 files changed, 182 insertions, 37 deletions
| diff --git a/src/leap/base/checks.py b/src/leap/base/checks.py index 7285e74f..23446f4a 100644 --- a/src/leap/base/checks.py +++ b/src/leap/base/checks.py @@ -1,6 +1,7 @@  # -*- coding: utf-8 -*-  import logging  import platform +import socket  import netifaces  import ping @@ -23,7 +24,7 @@ class LeapNetworkChecker(object):      def run_all(self, checker=None):          if not checker:              checker = self -        self.error = None  # ? +        #self.error = None  # ?          # for MVS          checker.check_tunnel_default_interface() @@ -118,11 +119,9 @@ class LeapNetworkChecker(object):          if packet_loss > constants.MAX_ICMP_PACKET_LOSS:              raise exceptions.NoConnectionToGateway -     # XXX check for name resolution servers -     # dunno what's the best way to do this... -     # check for etc/resolv entries or similar? -     # just try to resolve? -     # is there something in psutil? - -     # def check_name_resolution(self): -     #     pass +    def check_name_resolution(self, domain_name): +        try: +            socket.gethostbyname(domain_name) +            return True +        except socket.gaierror: +            raise exceptions.CannotResolveDomainError diff --git a/src/leap/base/exceptions.py b/src/leap/base/exceptions.py index f12a49d5..227da953 100644 --- a/src/leap/base/exceptions.py +++ b/src/leap/base/exceptions.py @@ -67,6 +67,11 @@ class NoInternetConnection(CriticalError):      # and now we try to connect to our web to troubleshoot LOL :P +class CannotResolveDomainError(LeapException): +    message = "Cannot resolve domain" +    usermessage = "Domain cannot be found" + +  class TunnelNotDefaultRouteError(CriticalError):      message = "Tunnel connection dissapeared. VPN down?"      usermessage = "The Encrypted Connection was lost. Shutting down..." diff --git a/src/leap/crypto/certs.py b/src/leap/crypto/certs.py new file mode 100644 index 00000000..aa1fc9e9 --- /dev/null +++ b/src/leap/crypto/certs.py @@ -0,0 +1,31 @@ +import ctypes +import socket + +import gnutls.connection +import gnutls.library + + +def get_https_cert_fingerprint(domain): +    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +    cred = gnutls.connection.X509Credentials() + +    session = gnutls.connection.ClientSession(sock, cred) +    session.connect((domain, 443)) +    session.handshake() +    cert = session.peer_certificate + +    _buffer = ctypes.create_string_buffer(20) +    buffer_length = ctypes.c_size_t(20) + +    gnutls.library.functions.gnutls_x509_crt_get_fingerprint( +        cert._c_object, gnutls.library.constants.GNUTLS_DIG_SHA1,  # 3 +        ctypes.byref(_buffer), ctypes.byref(buffer_length)) + +    # deinit +    #server_cert._X509Certificate__deinit(server_cert._c_object) +    # needed? is segfaulting + +    fpr = ctypes.string_at(_buffer, buffer_length.value) +    hex_fpr = u":".join(u"%02X" % ord(char) for char in fpr) + +    return hex_fpr diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py index f739c3e8..c704aef3 100644 --- a/src/leap/eip/checks.py +++ b/src/leap/eip/checks.py @@ -94,6 +94,7 @@ class ProviderCertChecker(object):          raise NotImplementedError      def is_there_provider_ca(self): +        # XXX remove for generic build          from leap import certs          logger.debug('do we have provider_ca?')          cacert_path = BRANDING.get('provider_ca_file', None) @@ -104,30 +105,46 @@ class ProviderCertChecker(object):          logger.debug('True')          return True -    def is_https_working(self, uri=None, verify=True): +    def is_https_working( +            self, uri=None, verify=True, +            autocacert=False):          if uri is None:              uri = self._get_root_uri()          # XXX raise InsecureURI or something better -        assert uri.startswith('https') -        if verify is True and self.cacert is not None: +        try: +            assert uri.startswith('https') +        except AssertionError: +            raise AssertionError( +                "uri passed should start with https") +        if autocacert and verify is True and self.cacert is not None:              logger.debug('verify cert: %s', self.cacert)              verify = self.cacert          logger.debug('is https working?')          logger.debug('uri: %s (verify:%s)', uri, verify)          try:              self.fetcher.get(uri, verify=verify) + +        except requests.exceptions.SSLError as exc: +            logger.error("SSLError") +            raise eipexceptions.HttpsBadCertError + +        except requests.exceptions.ConnectionError: +            logger.error('ConnectionError') +            raise eipexceptions.HttpsNotSupported +          except requests.exceptions.SSLError as exc:              logger.warning('False! CERT VERIFICATION FAILED! '                             '(this should be CRITICAL)')              logger.warning('SSLError: %s', exc.message)              # XXX RAISE! See #638              #raise eipexceptions.EIPBadCertError -        # XXX get requests.exceptions.ConnectionError Errno 110 -        # Connection timed out, and raise ours.          else:              logger.debug('True')              return True +    def get_certificate_fingerprint(self, domain): +        pass +      def check_new_cert_needed(self, skip_download=False, verify=True):          logger.debug('is new cert needed?')          if not self.is_cert_valid(do_raise=False): diff --git a/src/leap/eip/exceptions.py b/src/leap/eip/exceptions.py index 11bfd620..41eed77a 100644 --- a/src/leap/eip/exceptions.py +++ b/src/leap/eip/exceptions.py @@ -32,8 +32,10 @@ TODO:  * gettext / i18n for user messages.  """ +from leap.base.exceptions import LeapException +# This should inherit from LeapException  class EIPClientError(Exception):      """      base EIPClient exception @@ -99,6 +101,15 @@ class OpenVPNAlreadyRunning(EIPClientError):                     "Please close it before starting leap-client") +class HttpsNotSupported(LeapException): +    message = "connection refused while accessing via https" +    usermessage = "Server does not allow secure connections." + + +class HttpsBadCertError(LeapException): +    message = "verification error on cert" +    usermessage = "Server certificate could not be verified." +  #  # errors still needing some love  # diff --git a/src/leap/gui/firstrunwizard.py b/src/leap/gui/firstrunwizard.py index bc36a35f..53e551ac 100755 --- a/src/leap/gui/firstrunwizard.py +++ b/src/leap/gui/firstrunwizard.py @@ -11,7 +11,12 @@ from PyQt4 import QtCore  from PyQt4 import QtGui  from leap.base.auth import LeapSRPRegister +from leap.base import checks as basechecks +from leap.base import exceptions as baseexceptions +from leap.crypto import certs  from leap.crypto import leapkeyring +from leap.eip import checks as eipchecks +from leap.eip import exceptions as eipexceptions  from leap.gui import mainwindow_rc  try: @@ -39,7 +44,10 @@ class FirstRunWizard(QtGui.QWizard):      def __init__(              self, parent=None, providers=None,              success_cb=None, is_provider_setup=False, -            is_previously_registered=False): +            is_previously_registered=False, +            netchecker=basechecks.LeapNetworkChecker, +            providercertchecker=eipchecks.ProviderCertChecker, +            eipconfigchecker=eipchecks.EIPConfigChecker):          super(FirstRunWizard, self).__init__(              parent,              QtCore.Qt.WindowStaysOnTopHint) @@ -59,25 +67,25 @@ class FirstRunWizard(QtGui.QWizard):          # if True, jumps to LogIn page.          self.is_previously_registered = is_previously_registered -        # FIXME remove kwargs, we can access -        # wizard as self.wizard() +        # Checkers +        self.netchecker = netchecker +        self.providercertchecker = providercertchecker +        self.eipconfigchecker = eipconfigchecker          # FIXME add param for previously_registered          # should start at login page.          pages_dict = OrderedDict(( -            # (name, (WizardPage, **kwargs)) -            ('intro', (IntroPage, {})), -            ('providerselection', ( -                SelectProviderPage, -                {'providers': providers})), -            ('login', (LogInPage, {})), -            ('providerinfo', (ProviderInfoPage, {})), -            ('providersetup', (ProviderSetupPage, {})), -            ('signup', ( -                RegisterUserPage, {})), -            ('connecting', (ConnectingPage, {})), -            ('lastpage', (LastPage, {})) +            # (name, WizardPage) +            ('intro', IntroPage), +            ('providerselection', +                SelectProviderPage), +            ('login', LogInPage), +            ('providerinfo', ProviderInfoPage), +            ('providersetup', ProviderSetupPage), +            ('signup', RegisterUserPage), +            ('connecting', ConnectingPage), +            ('lastpage', LastPage)          ))          self.add_pages_from_dict(pages_dict) @@ -99,10 +107,10 @@ class FirstRunWizard(QtGui.QWizard):              values are a tuple of InstanceofWizardPage, kwargs.          @type pages_dict: dict          """ -        for name, (page, page_args) in pages_dict.items(): +        for name, page in pages_dict.items():              # XXX check for is_previously registered              # and skip adding the signup branch if so -            self.addPage(page(**page_args)) +            self.addPage(page())          self.pages_dict = pages_dict      def get_page_index(self, page_name): @@ -234,7 +242,7 @@ class SelectProviderPage(QtGui.QWizardPage):      def __init__(self, parent=None, providers=None):          super(SelectProviderPage, self).__init__(parent) -        self.setTitle("Select Provider") +        self.setTitle("Enter Provider")          self.setSubTitle(              "Please enter the domain of the provider you want "              "to use for your connection." @@ -243,7 +251,9 @@ class SelectProviderPage(QtGui.QWizardPage):              QtGui.QWizard.LogoPixmap,              QtGui.QPixmap(APP_LOGO)) -        providerNameLabel = QtGui.QLabel("&Provider:") +        providerNameLabel = QtGui.QLabel("h&ttps://") +        # note that we expect the bare domain name +        # we will add the scheme later          providerNameEdit = QtGui.QLineEdit()          providerNameEdit.cursorPositionChanged.connect(              self.reset_validation_status) @@ -269,13 +279,28 @@ class SelectProviderPage(QtGui.QWizardPage):          validationMsg = QtGui.QLabel("")          validationMsg.setStyleSheet(ErrorLabelStyleSheet) -          self.validationMsg = validationMsg +        # XXX cert info +        self.certInfo = QtGui.QLabel("") +        self.certInfo.setWordWrap(True) +        self.certWarning = QtGui.QLabel("") +        self.trustProviderCertCheckBox = QtGui.QCheckBox( +            "&Trust this provider certificate.") +          layout = QtGui.QGridLayout() -        layout.addWidget(validationMsg, 0, 0) -        layout.addWidget(providerNameLabel, 0, 1) -        layout.addWidget(providerNameEdit, 0, 2) +        layout.addWidget(validationMsg, 0, 2) +        layout.addWidget(providerNameLabel, 1, 1) +        layout.addWidget(providerNameEdit, 1, 2) + +        # XXX get a groupbox or something.... +        layout.addWidget(self.certInfo, 4, 1, 4, 2) +        layout.addWidget(self.certWarning, 6, 1, 6, 2) +        layout.addWidget( +            self.trustProviderCertCheckBox, +            8, 1, 8, 2) +        self.trustProviderCertCheckBox.hide() +          self.setLayout(layout)      def reset_validation_status(self): @@ -284,7 +309,64 @@ class SelectProviderPage(QtGui.QWizardPage):          """          self.validationMsg.setText('') +    def set_validation_status(self, status): +        self.validationMsg.setText(status) + +    def add_cert_info(self, certinfo): +        self.certWarning.setText( +            "Do you want to trust this provider certificate?") +        self.certInfo.setText( +            'Certificate sha1: <i>%s</i><br>' % certinfo) +        self.trustProviderCertCheckBox.show() +        # XXX when checkbox is marked, remove +        # the red warning. +        # XXX also, disable the next button! + +    def initializePage(self): +        self.certWarning.setText('') +        self.certInfo.setText('') +        self.trustProviderCertCheckBox.hide() +      def validatePage(self): +        wizard = self.wizard() +        netchecker = wizard.netchecker() +        providercertchecker = wizard.providercertchecker() + +        domain = self.providerNameEdit.text() + +        # try name resolution +        try: +            netchecker.check_name_resolution( +                domain) + +        except baseexceptions.LeapException as exc: +            self.set_validation_status(exc.usermessage) +            return False + +        # try https connection +        try: +            providercertchecker.is_https_working( +                "https://%s" % domain, +                verify=True) + +        except eipexceptions.HttpsBadCertError as exc: +            if self.trustProviderCertCheckBox.isChecked(): +                pass +            else: +                self.set_validation_status(exc.usermessage) +                fingerprint = certs.get_https_cert_fingerprint( +                    domain) +                self.add_cert_info(fingerprint) +                return False + +        except baseexceptions.LeapException as exc: +            self.set_validation_status(exc.usermessage) +            return False + +        # try download provider info... +        # TODO ... + +        # all ok, go on...          return True      def nextId(self): | 
