summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
authorkali <kali@leap.se>2012-10-18 09:30:53 +0900
committerkali <kali@leap.se>2012-10-18 09:30:53 +0900
commite1dbfc454180a77ebb38ecae6244ac4abe6d0ac5 (patch)
treedc160544313ab1e7a5e14ab5aa9fb8373fe8fae8 /src/leap
parent17896b9f9cbfbca7bc0a0344050dddea8ba61880 (diff)
catch cert verification errors and ask user for trust
with a little helper function using gnutls
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/base/checks.py17
-rw-r--r--src/leap/base/exceptions.py5
-rw-r--r--src/leap/crypto/certs.py31
-rw-r--r--src/leap/eip/checks.py27
-rw-r--r--src/leap/eip/exceptions.py11
-rwxr-xr-xsrc/leap/gui/firstrunwizard.py128
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):