summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomas Touceda <chiiph@leap.se>2013-05-15 16:16:32 -0300
committerTomás Touceda <chiiph@leap.se>2013-05-16 17:17:32 -0300
commitb0abf507bb8eb570328172b659ab072bc4b08634 (patch)
treefc63016e64d35fbcda5f836cb4e0dbb93ab6e850
parent709cbe02bee29cc75ffe9ccbc65c2db4e53e270f (diff)
Integrate soledad and keymanager in the client
-rw-r--r--changes/feature_integrate_soledad1
-rw-r--r--pkg/requirements.pip6
-rw-r--r--src/leap/crypto/srpauth.py8
-rw-r--r--src/leap/gui/mainwindow.py34
-rw-r--r--src/leap/services/soledad/__init__.py0
-rw-r--r--src/leap/services/soledad/soledadbootstrapper.py279
-rw-r--r--src/leap/services/soledad/soledadconfig.py48
-rw-r--r--src/leap/services/soledad/soledadspec.py57
8 files changed, 431 insertions, 2 deletions
diff --git a/changes/feature_integrate_soledad b/changes/feature_integrate_soledad
new file mode 100644
index 00000000..95ee7d4c
--- /dev/null
+++ b/changes/feature_integrate_soledad
@@ -0,0 +1 @@
+ o Integrate soledad and keymanager. \ No newline at end of file
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 3c5bfad0..d8091ad9 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -16,4 +16,8 @@ ipaddr
twisted
qt4reactor
-leap.common>=0.2.3-dev
+leap.common>=0.2.4
+# TODO: add soledad dependency
+
+# Remove this when nickserver is online
+mock
diff --git a/src/leap/crypto/srpauth.py b/src/leap/crypto/srpauth.py
index ce6c28f4..2f3cbd1c 100644
--- a/src/leap/crypto/srpauth.py
+++ b/src/leap/crypto/srpauth.py
@@ -373,7 +373,7 @@ class SRPAuth(QtCore.QObject):
QtCore.QMutexLocker(self._token_lock)
self._token = token
- def get_token(self, token):
+ def get_token(self):
QtCore.QMutexLocker(self._token_lock)
return self._token
@@ -431,6 +431,12 @@ class SRPAuth(QtCore.QObject):
def get_session_id(self):
return self.__instance.get_session_id()
+ def get_uid(self):
+ return self.__instance.get_uid()
+
+ def get_token(self):
+ return self.__instance.get_token()
+
def logout(self):
"""
Logs out the current session.
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index fdf84766..7eb956cb 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -37,6 +37,7 @@ from leap.gui.wizard import Wizard
from leap.services.eip.eipbootstrapper import EIPBootstrapper
from leap.services.eip.eipconfig import EIPConfig
from leap.services.eip.providerbootstrapper import ProviderBootstrapper
+from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper
from leap.platform_init import IS_MAC, IS_WIN
from leap.platform_init.initializers import init_platform
from leap.services.eip.vpn import VPN
@@ -175,6 +176,10 @@ class MainWindow(QtGui.QMainWindow):
self._eip_bootstrapper.download_client_certificate.connect(
self._finish_eip_bootstrap)
+ self._soledad_bootstrapper = SoledadBootstrapper()
+ self._soledad_bootstrapper.download_config.connect(
+ self._soledad_bootstrapped_stage)
+
self._vpn = VPN()
self._vpn.state_changed.connect(self._update_vpn_state)
self._vpn.status_changed.connect(self._update_vpn_status)
@@ -233,6 +238,8 @@ class MainWindow(QtGui.QMainWindow):
self._bypass_checks = bypass_checks
+ self._soledad = None
+
if self._first_run():
self._wizard_firstrun = True
self._wizard = Wizard(self._checker_thread,
@@ -686,8 +693,35 @@ class MainWindow(QtGui.QMainWindow):
"""
self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX)
self._systray.setIcon(self.LOGGED_IN_ICON)
+
+ self._soledad_bootstrapper.run_soledad_setup_checks(
+ self._checker_thread,
+ self._provider_config,
+ self.ui.lnUser.text(),
+ self.ui.lnPassword.text(),
+ download_if_needed=True)
+
self._download_eip_config()
+ def _soledad_bootstrapped_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self._soledad_bootstrapper.download_config
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+
+ :param data: result from the bootstrapping stage for Soledad
+ :type data: dict
+ """
+ passed = data[self._soledad_bootstrapper.PASSED_KEY]
+ if not passed:
+ logger.error(data[self._soledad_bootstrapper.ERROR_KEY])
+ else:
+ logger.debug("Done bootstrapping Soledad")
+
def _get_socket_host(self):
"""
Returns the socket and port to be used for VPN
diff --git a/src/leap/services/soledad/__init__.py b/src/leap/services/soledad/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/leap/services/soledad/__init__.py
diff --git a/src/leap/services/soledad/soledadbootstrapper.py b/src/leap/services/soledad/soledadbootstrapper.py
new file mode 100644
index 00000000..51c53a6e
--- /dev/null
+++ b/src/leap/services/soledad/soledadbootstrapper.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+# soledadbootstrapper.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Soledad bootstrapping
+"""
+
+import logging
+import os
+
+import requests
+
+from PySide import QtCore
+from mock import Mock
+
+from leap.common.check import leap_assert, leap_assert_type
+from leap.common.files import get_mtime
+from leap.common.keymanager import KeyManager, openpgp
+from leap.common.keymanager.errors import KeyNotFound
+from leap.config.providerconfig import ProviderConfig
+from leap.crypto.srpauth import SRPAuth
+from leap.services.soledad.soledadconfig import SoledadConfig
+from leap.util.request_helpers import get_content
+from leap.soledad import Soledad
+
+logger = logging.getLogger(__name__)
+
+
+class SoledadBootstrapper(QtCore.QObject):
+ """
+ Soledad init procedure
+ """
+
+ PASSED_KEY = "passed"
+ ERROR_KEY = "error"
+
+ PUBKEY_KEY = "user[public_key]"
+
+ IDLE_SLEEP_INTERVAL = 100
+
+ # All dicts returned are of the form
+ # {"passed": bool, "error": str}
+ download_config = QtCore.Signal(dict)
+ gen_key = QtCore.Signal(dict)
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+
+ # **************************************************** #
+ # Dependency injection helpers, override this for more
+ # granular testing
+ self._fetcher = requests
+ # **************************************************** #
+
+ self._session = self._fetcher.session()
+ self._provider_config = None
+ self._soledad_config = None
+ self._keymanager = None
+ self._download_if_needed = False
+ self._user = ""
+ self._password = ""
+
+ def _load_and_sync_soledad(self, srp_auth):
+ """
+ Once everthing is in the right place, we instantiate and sync
+ Soledad
+
+ :param srp_auth: SRPAuth object used
+ :type srp_auth: SRPAuth
+ """
+ uuid = srp_auth.get_uid()
+
+ prefix = os.path.join(self._soledad_config.get_path_prefix(),
+ "leap", "soledad")
+ secrets_path = "%s/%s.secret" % (prefix, uuid)
+ local_db_path = "%s/%s.db" % (prefix, uuid)
+
+ # TODO: use the proper URL
+ server_url = 'https://mole.dev.bitmask.net:2424/user-%s' % (uuid,)
+ # server_url = self._soledad_config.get_hosts(...)
+
+ cert_file = self._provider_config.get_ca_cert_path()
+
+ self._soledad = Soledad(uuid,
+ self._password.encode("utf-8"),
+ secrets_path,
+ local_db_path,
+ server_url,
+ cert_file,
+ srp_auth.get_token())
+ self._soledad.sync()
+
+ def _download_config(self):
+ """
+ Downloads the Soledad config for the given provider
+
+ :return: True if everything went as expected, False otherwise
+ :rtype: bool
+ """
+
+ leap_assert(self._provider_config,
+ "We need a provider configuration!")
+
+ logger.debug("Downloading Soledad config for %s" %
+ (self._provider_config.get_domain(),))
+
+ download_config_data = {
+ self.PASSED_KEY: False,
+ self.ERROR_KEY: ""
+ }
+
+ self._soledad_config = SoledadConfig()
+
+ try:
+ headers = {}
+ mtime = get_mtime(os.path.join(self._soledad_config
+ .get_path_prefix(),
+ "leap",
+ "providers",
+ self._provider_config.get_domain(),
+ "soledad-service.json"))
+
+ if self._download_if_needed and mtime:
+ headers['if-modified-since'] = mtime
+
+ # there is some confusion with this uri,
+ config_uri = "%s/%s/config/soledad-service.json" % (
+ self._provider_config.get_api_uri(),
+ self._provider_config.get_api_version())
+ logger.debug('Downloading soledad config from: %s' % config_uri)
+
+ srp_auth = SRPAuth(self._provider_config)
+ session_id = srp_auth.get_session_id()
+ cookies = None
+ if session_id:
+ cookies = {"_session_id": session_id}
+
+ res = self._session.get(config_uri,
+ verify=self._provider_config
+ .get_ca_cert_path(),
+ headers=headers,
+ cookies=cookies)
+ res.raise_for_status()
+
+ # Not modified
+ if res.status_code == 304:
+ logger.debug("Soledad definition has not been modified")
+ else:
+ soledad_definition, mtime = get_content(res)
+
+ self._soledad_config.load(data=soledad_definition, mtime=mtime)
+ self._soledad_config.save(["leap",
+ "providers",
+ self._provider_config.get_domain(),
+ "soledad-service.json"])
+
+ self._load_and_sync_soledad(srp_auth)
+
+ download_config_data[self.PASSED_KEY] = True
+ except Exception as e:
+ download_config_data[self.PASSED_KEY] = False
+ download_config_data[self.ERROR_KEY] = "%s" % (e,)
+
+ logger.debug("Emitting download_config %s" % (download_config_data,))
+ self.download_config.emit(download_config_data)
+
+ return download_config_data[self.PASSED_KEY]
+
+ def _gen_key(self):
+ """
+ Generates the key pair if needed, uploads it to the webapp and
+ nickserver
+
+ :return: True if everything is done successfully, False
+ otherwise
+ :rtype: bool
+ """
+ leap_assert(self._provider_config,
+ "We need a provider configuration!")
+
+ # XXX Sanitize this
+ address = "%s@%s" % (self._user, self._provider_config.get_domain())
+
+ logger.debug("Retrieving key for %s" % (address,))
+
+ genkey_data = {
+ self.PASSED_KEY: False,
+ self.ERROR_KEY: ""
+ }
+
+ try:
+ srp_auth = SRPAuth(self._provider_config)
+ self._keymanager = KeyManager(
+ address,
+ "https://nickserver", # TODO: nickserver url, none for now
+ self._soledad,
+ token=srp_auth.get_token())
+ self._keymanager._fetcher.put = Mock()
+ try:
+ self._keymanager.get_key(address, openpgp.OpenPGPKey,
+ private=True, fetch_remote=False)
+ except KeyNotFound:
+ logger.debug("Key not found. Generating key for %s" % (address,))
+ self._keymanager.gen_key(openpgp.OpenPGPKey)
+
+ logger.debug("Key generated successfully.")
+
+ cookies = None
+ session_id = srp_auth.get_session_id()
+ if session_id:
+ cookies = {"_session_id": session_id}
+
+ key_uri = "%s/%s/users/%s.json" % (
+ self._provider_config.get_api_uri(),
+ self._provider_config.get_api_version(),
+ srp_auth.get_uid())
+
+ logger.debug("Uploading public key to %s" % (key_uri,))
+
+ pubkey = self._keymanager.get_key(address, openpgp.OpenPGPKey,
+ private=False, fetch_remote=False)
+ key_data = {
+ self.PUBKEY_KEY: pubkey.key_data,
+ }
+
+ # TODO: check if uploaded before uploading it
+ key_result = self._session.put(key_uri,
+ data=key_data,
+ verify=self._provider_config
+ .get_ca_cert_path(),
+ cookies=cookies)
+ key_result.raise_for_status()
+ genkey_data[self.PASSED_KEY] = True
+ except Exception as e:
+ genkey_data[self.PASSED_KEY] = False
+ genkey_data[self.ERROR_KEY] = "%s" % (e,)
+
+ logger.debug("Emitting gen_key %s" % (genkey_data,))
+ self.gen_key.emit(genkey_data)
+
+ return genkey_data[self.PASSED_KEY]
+
+ def run_soledad_setup_checks(self,
+ checker,
+ provider_config,
+ user,
+ password,
+ download_if_needed=False):
+ """
+ Starts the checks needed for a new soledad setup
+
+ :param provider_config: Provider configuration
+ :type provider_config: ProviderConfig
+ """
+ leap_assert_type(provider_config, ProviderConfig)
+
+ self._provider_config = provider_config
+ self._download_if_needed = download_if_needed
+ self._user = user
+ self._password = password
+
+ checker.add_checks([
+ self._download_config,
+ self._gen_key
+ ])
diff --git a/src/leap/services/soledad/soledadconfig.py b/src/leap/services/soledad/soledadconfig.py
new file mode 100644
index 00000000..836265f3
--- /dev/null
+++ b/src/leap/services/soledad/soledadconfig.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# soledadconfig.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Soledad configuration
+"""
+import logging
+
+from leap.common.config.baseconfig import BaseConfig
+from leap.services.soledad.soledadspec import soledad_config_spec
+
+logger = logging.getLogger(__name__)
+
+
+class SoledadConfig(BaseConfig):
+ """
+ Soledad configuration abstraction class
+ """
+
+ def __init__(self):
+ BaseConfig.__init__(self)
+
+ def _get_spec(self):
+ """
+ Returns the spec object for the specific configuration
+ """
+ return soledad_config_spec
+
+ def get_hosts(self):
+ return self._safe_get_value("hosts")
+
+ def get_locations(self):
+ return self._safe_get_value("locations")
+
diff --git a/src/leap/services/soledad/soledadspec.py b/src/leap/services/soledad/soledadspec.py
new file mode 100644
index 00000000..d5a437cc
--- /dev/null
+++ b/src/leap/services/soledad/soledadspec.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# soledadspec.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+soledad_config_spec = {
+ 'description': 'sample soledad service config',
+ 'type': 'object',
+ 'properties': {
+ 'serial': {
+ 'type': int,
+ 'default': 1,
+ 'required': True
+ },
+ 'version': {
+ 'type': int,
+ 'default': 1,
+ 'required': True
+ },
+ 'hosts': {
+ 'type': dict,
+ 'default': {
+ "python": {
+ "hostname": "someprovider",
+ "ip_address": "1.1.1.1",
+ "location": "loc",
+ "port": 1111
+ },
+ },
+ },
+ 'locations': {
+ 'type': dict,
+ 'default': {
+ "locations": {
+ "ankara": {
+ "country_code": "TR",
+ "hemisphere": "N",
+ "name": "loc",
+ "timezone": "+0"
+ }
+ }
+ }
+ }
+ }
+}