From 89735a5fd3c81e8aba3cb7b1d4836c1bf1e8c098 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 18 Sep 2012 22:55:45 +0900 Subject: cert verification and malformed json checks --- src/leap/base/config.py | 20 ++++++++++--- src/leap/base/constants.py | 2 +- src/leap/base/exceptions.py | 2 ++ src/leap/baseapp/eip.py | 2 ++ src/leap/eip/checks.py | 65 ++++++++++++++++++++++++++++++------------- src/leap/eip/eipconnection.py | 26 +++++++++++++++-- src/leap/eip/exceptions.py | 15 +++++++++- 7 files changed, 104 insertions(+), 28 deletions(-) (limited to 'src/leap') diff --git a/src/leap/base/config.py b/src/leap/base/config.py index e896ffd2..eb9bf88a 100644 --- a/src/leap/base/config.py +++ b/src/leap/base/config.py @@ -18,6 +18,9 @@ from leap.base import exceptions from leap.base import constants from leap.util.fileutil import (mkdir_p) +# move to base! +from leap.eip import exceptions as eipexceptions + class BaseLeapConfig(object): slug = None @@ -145,9 +148,9 @@ class JSONLeapConfig(BaseLeapConfig): config[k] = v() self._config.serialize(to) - def load(self, fromfile=None, from_uri=None, fetcher=None): + def load(self, fromfile=None, from_uri=None, fetcher=None, verify=False): if from_uri is not None: - fetched = self.fetch(from_uri, fetcher=fetcher) + fetched = self.fetch(from_uri, fetcher=fetcher, verify=verify) if fetched: return if fromfile is None: @@ -156,12 +159,21 @@ class JSONLeapConfig(BaseLeapConfig): # XXX check for no errors, etc self._config.config = newconfig - def fetch(self, uri, fetcher=None): + def fetch(self, uri, fetcher=None, verify=True): if not fetcher: fetcher = self.fetcher - request = fetcher.get(uri) + logger.debug('verify: %s', verify) + request = fetcher.get(uri, verify=verify) + + # XXX get 404, ... + # and raise a UnableToFetch... request.raise_for_status() fd, fname = tempfile.mkstemp(suffix=".json") + if not request.json: + try: + json.loads(request.content) + except ValueError: + raise eipexceptions.LeapBadConfigFetchedError with open(fname, 'w') as tmp: tmp.write(json.dumps(request.json)) self._loadtemp(fname) diff --git a/src/leap/base/constants.py b/src/leap/base/constants.py index f3e24715..7a1415fb 100644 --- a/src/leap/base/constants.py +++ b/src/leap/base/constants.py @@ -11,7 +11,7 @@ DEFAULT_PROVIDER = __branding.get( "provider_domain", "testprovider.example.org") -DEFINITION_EXPECTED_PATH = "definition.json" +DEFINITION_EXPECTED_PATH = "provider.json" DEFAULT_PROVIDER_DEFINITION = { u'api_uri': u'https://api.%s/' % DEFAULT_PROVIDER, diff --git a/src/leap/base/exceptions.py b/src/leap/base/exceptions.py index 9c4aa77b..caaa3be6 100644 --- a/src/leap/base/exceptions.py +++ b/src/leap/base/exceptions.py @@ -4,3 +4,5 @@ class MissingConfigFileError(Exception): class ImproperlyConfigured(Exception): pass + + diff --git a/src/leap/baseapp/eip.py b/src/leap/baseapp/eip.py index ff6a79ac..8ebd84ae 100644 --- a/src/leap/baseapp/eip.py +++ b/src/leap/baseapp/eip.py @@ -131,6 +131,8 @@ class EIPConductorAppMixin(object): ErrorDialog(errtype="critical", msg=message, label="critical error") + elif error.warning: + logger.warning(error.message) else: dialog = ErrorDialog() diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py index aea5a5d7..b55f5827 100644 --- a/src/leap/eip/checks.py +++ b/src/leap/eip/checks.py @@ -9,6 +9,8 @@ import netifaces import ping import requests +from leap import __branding as BRANDING +from leap import certs from leap.base import constants as baseconstants from leap.base import providers from leap.eip import config as eipconfig @@ -20,6 +22,11 @@ from leap.util.fileutil import mkdir_p logger = logging.getLogger(name=__name__) """ +ProviderCertChecker +------------------- +Checks on certificates. To be moved to base. +docs TBD + EIPConfigChecker ---------- It is used from the eip conductor (a instance of EIPConnection that is @@ -36,14 +43,15 @@ LeapNetworkChecker ------------------ Network checks. To be moved to base. docs TBD - -ProviderCertChecker -------------------- -Checks on certificates. -docs TBD """ +def get_ca_cert(): + ca_file = BRANDING.get('provider_ca_file') + if ca_file: + return certs.where(ca_file) + + class LeapNetworkChecker(object): """ all network related checks @@ -67,6 +75,7 @@ class LeapNetworkChecker(object): # XXX we probably should raise an exception here? # unless we use this as smoke test try: + # XXX remove this hardcoded random ip requests.get('http://216.172.161.165') except (requests.HTTPError, requests.RequestException) as e: self.error = e.message @@ -124,7 +133,7 @@ class ProviderCertChecker(object): """ def __init__(self, fetcher=requests): self.fetcher = fetcher - self.cacert = None + self.cacert = get_ca_cert() def run_all(self, checker=None, skip_download=False): if not checker: @@ -159,25 +168,34 @@ class ProviderCertChecker(object): raise NotImplementedError def is_there_provider_ca(self): - # XXX fake it till you make it! :P + from leap import certs + logger.debug('do we have provider_ca?') + cacert_path = BRANDING.get('provider_ca_file', None) + if not cacert_path: + logger.debug('False') + return False + self.cacert = certs.where(cacert_path) + logger.debug('True') return True - # enable this when we have - # a custom "branded" bundle - # certs package. - try: - from leap.custom import certs - except ImportError: - raise - self.cacert = certs.where('cacert.pem') - def is_https_working(self, uri=None, verify=True): + if uri is None: + uri = self._get_root_uri() # XXX raise InsecureURI or something better + logger.debug('is https working?') + logger.debug('uri: %s', uri) + #import ipdb;ipdb.set_trace() assert uri.startswith('https') if verify is True and self.cacert is not None: + logger.debug('verify cert: %s', self.cacert) verify = self.cacert - self.fetcher.get(uri, verify=verify) - return True + try: + self.fetcher.get(uri, verify=verify) + except requests.exceptions.SSLError: + raise eipexceptions.EIPBadCertError + else: + logger.debug('True') + return True def check_new_cert_needed(self, skip_download=False): if not self.is_cert_valid(do_raise=False): @@ -256,7 +274,11 @@ class ProviderCertChecker(object): raise return True + def _get_root_uri(self): + return u"https://%s/" % baseconstants.DEFAULT_PROVIDER + def _get_client_cert_uri(self): + # XXX get the whole thing from constants return "https://%s/cert/get" % (baseconstants.DEFAULT_PROVIDER) def _get_client_cert_path(self): @@ -370,7 +392,12 @@ class EIPConfigChecker(object): domain = config.get('provider', None) uri = self._get_provider_definition_uri(domain=domain) - self.defaultprovider.load(from_uri=uri, fetcher=self.fetcher) + # FIXME! Pass ca path verify!!! + self.defaultprovider.load( + from_uri=uri, + fetcher=self.fetcher, + verify=False) + #import ipdb;ipdb.set_trace() self.defaultprovider.save() def fetch_eip_service_config(self, skip_download=False, diff --git a/src/leap/eip/eipconnection.py b/src/leap/eip/eipconnection.py index d1c84b2a..4e240f16 100644 --- a/src/leap/eip/eipconnection.py +++ b/src/leap/eip/eipconnection.py @@ -6,6 +6,7 @@ import logging import Queue import sys +from leap.eip.checks import ProviderCertChecker from leap.eip.checks import EIPConfigChecker from leap.eip import config as eipconfig from leap.eip import exceptions as eip_exceptions @@ -22,7 +23,10 @@ class EIPConnection(OpenVPNConnection): Status updates (connected, bandwidth, etc) are signaled to the GUI. """ - def __init__(self, config_checker=EIPConfigChecker, *args, **kwargs): + def __init__(self, + provider_cert_checker=ProviderCertChecker, + config_checker=EIPConfigChecker, + *args, **kwargs): self.settingsfile = kwargs.get('settingsfile', None) self.logfile = kwargs.get('logfile', None) @@ -30,6 +34,8 @@ class EIPConnection(OpenVPNConnection): status_signals = kwargs.pop('status_signals', None) self.status = EIPConnectionStatus(callbacks=status_signals) + + self.provider_cert_checker = provider_cert_checker() self.config_checker = config_checker() host = eipconfig.get_socket_path() @@ -45,12 +51,25 @@ class EIPConnection(OpenVPNConnection): run all eip checks previous to attempting a connection """ logger.debug('running conductor checks') + + def push_err(exc): + # keep the original traceback! + exc_traceback = sys.exc_info()[2] + self.error_queue.put((exc, exc_traceback)) + + try: + # network (1) + self.provider_cert_checker.run_all() + except Exception as exc: + push_err(exc) try: self.config_checker.run_all(skip_download=skip_download) + except Exception as exc: + push_err(exc) + try: self.run_openvpn_checks() except Exception as exc: - exc_traceback = sys.exc_info()[2] - self.error_queue.put((exc, exc_traceback)) + push_err(exc) def connect(self): """ @@ -84,6 +103,7 @@ class EIPConnection(OpenVPNConnection): # XXX this separation does not # make sense anymore after having # merged Connection and Manager classes. + # XXX GET RID OF THIS FUNCTION HERE! try: state = self.get_connection_state() except eip_exceptions.ConnectionRefusedError: diff --git a/src/leap/eip/exceptions.py b/src/leap/eip/exceptions.py index 467be7fe..f048621f 100644 --- a/src/leap/eip/exceptions.py +++ b/src/leap/eip/exceptions.py @@ -40,6 +40,8 @@ class EIPClientError(Exception): base EIPClient exception """ critical = False + failfirst = False + warning = False class CriticalError(EIPClientError): @@ -54,7 +56,7 @@ class Warning(EIPClientError): """ just that, warnings """ - pass + warning = True class EIPNoPolkitAuthAgentAvailable(CriticalError): @@ -81,10 +83,21 @@ class EIPNoCommandError(EIPClientError): "
(Might be a permissions problem)") +class EIPBadCertError(Warning): + # XXX this should be critical and fail close + message = "cert verification failed" + usermessage = "there is a problem with provider certificate" + + +class LeapBadConfigFetchedError(Warning): + message = "provider sent a malformed json file" + usermessage = "an error occurred during configuratio of leap services" + # # errors still needing some love # + class EIPInitNoKeyFileError(CriticalError): message = "No vpn keys found in the expected path" usermessage = "We could not find your eip certs in the expected path" -- cgit v1.2.3