diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/bitmask/core/mail_services.py | 125 | ||||
| -rw-r--r-- | src/leap/bitmask/core/service.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/keymanager/__init__.py | 125 | ||||
| -rw-r--r-- | src/leap/bitmask/keymanager/openpgp.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/mail/testing/__init__.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/mua/pixelizer.py | 4 | 
6 files changed, 97 insertions, 163 deletions
diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 16b20ed3..d1ccdb95 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -29,8 +29,6 @@ from collections import namedtuple  from twisted.application import service  from twisted.internet import defer -from twisted.internet import reactor -from twisted.internet import task  from twisted.logger import Logger  from leap.common.events import catalog, emit_async @@ -74,6 +72,8 @@ class ImproperlyConfigured(Exception):  class SoledadContainer(Container): +    log = Logger() +      def __init__(self, service=None, basedir=DEFAULT_BASEDIR):          self._basedir = os.path.expanduser(basedir)          self._usermap = UserMap() @@ -105,6 +105,14 @@ class SoledadContainer(Container):                  'soledad': soledad}          self.service.trigger_hook('on_new_soledad_instance', **data) +        self.log.debug('Syncing soledad for the first time...') +        d = soledad.sync() +        d.addCallbacks( +            lambda _: +            self.service.trigger_hook('on_soledad_first_sync', **data), +            lambda _: +            self.log.failure('Something failed on soledad first sync')) +      def _create_soledad_instance(self, uuid, passphrase, soledad_path,                                   server_url, cert_file, token):          # setup soledad info @@ -230,10 +238,6 @@ class KeymanagerContainer(Container):          keymanager = self._create_keymanager_instance(              userid, token, uuid, soledad)          super(KeymanagerContainer, self).add_instance(userid, keymanager) -        d = self._get_or_generate_keys(keymanager, userid) -        d.addCallback(self._on_keymanager_ready_cb, userid, soledad) -        d.addCallback(lambda _: self._set_status(userid, "on", keys="found")) -        return d      def set_remote_auth_token(self, userid, token):          self.get_instance(userid).token = token @@ -243,87 +247,58 @@ class KeymanagerContainer(Container):              return {'status': 'off', 'error': None, 'keys': None}          return self._status[userid] -    def _set_status(self, address, status, error=None, keys=None): -        self._status[address] = {"status": status, -                                 "error": error, "keys": keys} -        emit_async(catalog.MAIL_STATUS_CHANGED, address) - -    def _on_keymanager_ready_cb(self, keymanager, userid, soledad): -        data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager} -        self.service.trigger_hook('on_new_keymanager_instance', **data) - -    def _get_or_generate_keys(self, keymanager, userid): - -        def _get_key(_): -            self.log.info('Looking up private key for %s' % userid) -            return keymanager.get_key(userid, private=True, fetch_remote=False) +    def get_or_generate_keys(self, userid): +        keymanager = self.get_instance(userid)          def _found_key(key):              self.log.info('Found key: %r' % key) +            return key          def _if_not_found_generate(failure):              failure.trap(KeyNotFound)              self.log.info('Key not found, generating key for %s' % (userid,))              self._set_status(userid, "starting", keys="generating")              d = keymanager.gen_key() -            d.addCallbacks(_send_key, _log_key_error("generating")) +            d.addErrback(_log_key_error)              return d -        def _send_key(ignored): -            # ---------------------------------------------------------------- -            # It might be the case that we have generated a key-pair -            # but this hasn't been successfully uploaded. How do we know that? -            # XXX Should this be a method of bonafide instead? -            # ----------------------------------------------------------------- -            self.log.info('Key generated for %s' % userid) - -            if not keymanager.token: -                self.log.debug( -                    'Token not available, scheduling ' -                    'a new key sending attempt...') -                return task.deferLater(reactor, 5, _send_key, None) - -            self.log.info('Sending public key to server') -            d = keymanager.send_key() -            d.addCallbacks( -                lambda _: self.log.info('Key sent to server'), -                _log_key_error("sending")) -            return d +        def _log_key_error(failure): +            self.log.failure('Error while generating key!') +            error = "Error generating key: %s" % failure.getErrorMessage() +            self._set_status(userid, "failure", error=error) +            return failure -        def _log_key_error(step): -            def log_error(failure): -                self.log.error('Error while %s key!' % step) -                self.log.failure('error!') -                error = "Error generating key: %s" % failure.getErrorMessage() -                self._set_status(userid, "failure", error=error) -                return failure -            return log_error - -        def _sync_if_never_synced(ever_synced): -            if ever_synced: -                self.log.debug('Soledad has synced in the past') -                return defer.succeed(None) - -            self.log.debug('Soledad has never synced') - -            if not keymanager.token: -                self.log.debug('No token to sync now, scheduling a new check') -                d = task.deferLater(reactor, 5, keymanager.ever_synced) -                d.addCallback(_sync_if_never_synced) -                return d - -            self.log.debug('Syncing soledad for the first time...') -            self._set_status(userid, "starting", keys="sync") -            return keymanager._soledad.sync() - -        self.log.debug('Checking if soledad has ever synced...') -        d = keymanager.ever_synced() -        d.addCallback(_sync_if_never_synced) -        d.addCallback(_get_key) +        self.log.info('Looking up private key for %s' % userid) +        d = keymanager.get_key(userid, private=True, fetch_remote=False)          d.addCallbacks(_found_key, _if_not_found_generate) -        d.addCallback(lambda _: keymanager) +        d.addCallback(self._on_keymanager_ready_cb, keymanager, userid) +        self._set_status(userid, "on", keys="found")          return d +    @defer.inlineCallbacks +    def send_if_outdated_key_in_nicknym(self, userid): +        keymanager = self.get_instance(userid) +        key = yield keymanager.get_key(userid, fetch_remote=False) +        try: +            remote = yield keymanager._nicknym.fetch_key_with_address(userid) +        except Exception: +            remote = {} + +        if (keymanager.OPENPGP_KEY not in remote or +                key.key_data != remote[KeyManager.OPENPGP_KEY]): +            yield keymanager.send_key() + +    def _set_status(self, address, status, error=None, keys=None): +        self._status[address] = {"status": status, +                                 "error": error, "keys": keys} +        emit_async(catalog.MAIL_STATUS_CHANGED, address) + +    def _on_keymanager_ready_cb(self, key, keymanager, userid): +        soledad = keymanager._soledad +        data = {'userid': userid, 'soledad': soledad, 'keymanager': keymanager} +        self.service.trigger_hook('on_new_keymanager_instance', **data) +        return key +      def _create_keymanager_instance(self, userid, token, uuid, soledad):          user, provider = userid.split('@')          nickserver_uri = self._get_nicknym_uri(provider) @@ -384,6 +359,12 @@ class KeymanagerService(HookableService):                  token = self.tokens.get(user)              container.add_instance(user, token, uuid, soledad) +    def hook_on_soledad_first_sync(self, **kw): +        userid = kw['user'] +        d = self._container.get_or_generate_keys(userid) +        d.addCallback( +            lambda _: self._container.send_if_outdated_key_in_nicknym(userid)) +      def hook_on_bonafide_auth(self, **kw):          userid = kw['username']          provider = _get_provider_from_full_userid(userid) diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 310ac08e..0a3ac6bd 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -178,6 +178,8 @@ class BitmaskBackend(configurable.ConfigurableService):          if sol:              sol.register_hook(                  'on_new_soledad_instance', listener='keymanager') +            sol.register_hook( +                'on_soledad_first_sync', listener='keymanager')              # XXX this might not be the right place for hooking the sessions.              # If we want to be offline, we need to authenticate them after diff --git a/src/leap/bitmask/keymanager/__init__.py b/src/leap/bitmask/keymanager/__init__.py index 190e26ff..68184175 100644 --- a/src/leap/bitmask/keymanager/__init__.py +++ b/src/leap/bitmask/keymanager/__init__.py @@ -25,7 +25,7 @@ import tempfile  from urlparse import urlparse  from twisted.logger import Logger -from twisted.internet import defer +from twisted.internet import defer, task, reactor  from twisted.web import client  from twisted.web._responses import NOT_FOUND @@ -132,70 +132,6 @@ class KeyManager(object):          return tmp_file.name      @defer.inlineCallbacks -    def _get_key_from_nicknym(self, address): -        """ -        Send a GET request to C{uri} containing C{data}. - -        :param address: The URI of the request. -        :type address: str - -        :return: A deferred that will be fired with GET content as json (dict) -        :rtype: Deferred -        """ -        try: -            uri = self._nickserver_uri + '?address=' + address -            content = yield self._fetch_and_handle_404_from_nicknym( -                uri, address) -            json_content = json.loads(content) - -        except keymanager_errors.KeyNotFound: -            raise -        except IOError as e: -            self.log.warn("HTTP error retrieving key: %r" % (e,)) -            self.log.warn("%s" % (content,)) -            raise keymanager_errors.KeyNotFound(e.message), \ -                None, sys.exc_info()[2] -        except ValueError as v: -            self.log.warn("Invalid JSON data from key: %s" % (uri,)) -            raise keymanager_errors.KeyNotFound(v.message + ' - ' + uri), \ -                None, sys.exc_info()[2] - -        except Exception as e: -            self.log.warn("Error retrieving key: %r" % (e,)) -            raise keymanager_errors.KeyNotFound(e.message), \ -                None, sys.exc_info()[2] -        # Responses are now text/plain, although it's json anyway, but -        # this will fail when it shouldn't -        # leap_assert( -        #     res.headers['content-type'].startswith('application/json'), -        #     'Content-type is not JSON.') -        defer.returnValue(json_content) - -    def _fetch_and_handle_404_from_nicknym(self, uri, address): -        """ -        Send a GET request to C{uri} containing C{data}. - -        :param uri: The URI of the request. -        :type uri: str -        :param address: The email corresponding to the key. -        :type address: str - -        :return: A deferred that will be fired with GET content as json (dict) -        :rtype: Deferred -        """ -        def check_404(response): -            if response.code == NOT_FOUND: -                message = '%s: %s key not found.' % (response.code, address) -                self.log.warn(message) -                raise KeyNotFound(message), None, sys.exc_info()[2] -            return response - -        d = self._nicknym._async_client_pinned.request( -            str(uri), 'GET', callback=check_404) -        d.addCallback(client.readBody) -        return d - -    @defer.inlineCallbacks      def _get_with_combined_ca_bundle(self, uri, data=None):          """          Send a GET request to C{uri} containing C{data}. @@ -224,6 +160,7 @@ class KeyManager(object):      # key management      # +    @defer.inlineCallbacks      def send_key(self):          """          Send user's key to provider. @@ -237,18 +174,20 @@ class KeyManager(object):          :raise UnsupportedKeyTypeError: if invalid key type          """ -        def send(pubkey): -            d = self._nicknym.put_key(self.uid, pubkey.key_data, -                                      self._api_uri, self._api_version) -            d.addCallback(lambda _: -                          emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, -                                     self._address)) -            return d - -        d = self.get_key( -            self._address, private=False, fetch_remote=False) -        d.addCallback(send) -        return d +        if not self.token: +            self.log.debug( +                'Token not available, scheduling ' +                'a new key sending attempt...') +            yield task.deferLater(reactor, 5, self.send_key) + +        self.log.info('Sending public key to server') +        key = yield self.get_key(self._address, fetch_remote=False) +        yield self._nicknym.put_key(self.uid, key.key_data, +                                    self._api_uri, self._api_version) +        emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, +                   self._address) +        self.log.info('Key sent to server') +        defer.returnValue(key)      @defer.inlineCallbacks      def _fetch_keys_from_server_and_store_local(self, address): @@ -276,7 +215,7 @@ class KeyManager(object):                  validation_level = ValidationLevels.Provider_Trust          yield self.put_raw_key( -            server_keys['openpgp'], +            server_keys[OPENPGP_KEY],              address=address,              validation=validation_level) @@ -355,6 +294,7 @@ class KeyManager(object):          :raise UnsupportedKeyTypeError: if invalid key type          """          def signal_finished(key): +            self.log.info('Key generated for %s' % self._address)              emit_async(                  catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)              return key @@ -662,6 +602,7 @@ class KeyManager(object):          d.addCallback(check_upgrade)          return d +    @defer.inlineCallbacks      def put_raw_key(self, key, address,                      validation=ValidationLevels.Weak_Chain):          """ @@ -688,13 +629,26 @@ class KeyManager(object):          pubkey, privkey = self._openpgp.parse_key(key, address)          if pubkey is None: -            return defer.fail(keymanager_errors.KeyNotFound(key)) +            raise keymanager_errors.KeyNotFound(key) + +        if address == self._address and not privkey: +            try: +                existing = yield self.get_key(address, fetch_remote=False) +            except KeyNotFound: +                existing = None +            if (existing is not None or +                    pubkey.fingerprint != existing.fingerprint): +                raise keymanager_errors.KeyNotValidUpgrade( +                    "Cannot update your %s key without the private part" +                    % (address,))          pubkey.validation = validation -        d = self.put_key(pubkey) +        yield self.put_key(pubkey)          if privkey is not None: -            d.addCallback(lambda _: self.put_key(privkey)) -        return d +            yield self.put_key(privkey) + +        if address == self._address: +            yield self.send_key()      @defer.inlineCallbacks      def fetch_key(self, address, uri, validation=ValidationLevels.Weak_Chain): @@ -731,13 +685,6 @@ class KeyManager(object):          pubkey.validation = validation          yield self.put_key(pubkey) -    def ever_synced(self): -        # TODO: provide this method in soledad api, avoid using a private -        # attribute here -        d = self._soledad._dbpool.runQuery('SELECT * FROM sync_log') -        d.addCallback(lambda result: bool(result)) -        return d -  def _split_email(address):      """ diff --git a/src/leap/bitmask/keymanager/openpgp.py b/src/leap/bitmask/keymanager/openpgp.py index 8c9dc1e8..a27eb3de 100644 --- a/src/leap/bitmask/keymanager/openpgp.py +++ b/src/leap/bitmask/keymanager/openpgp.py @@ -226,7 +226,7 @@ class OpenPGPScheme(object):          d = self.get_key(address)          d.addCallbacks(key_already_exists, _gen_key) -        d.addCallback(lambda _: self.get_key(address, private=True)) +        d.addCallback(lambda _: self.get_key(address, private=False))          return d      def get_key(self, address, private=False): diff --git a/src/leap/bitmask/mail/testing/__init__.py b/src/leap/bitmask/mail/testing/__init__.py index 2fc4c073..e430f6aa 100644 --- a/src/leap/bitmask/mail/testing/__init__.py +++ b/src/leap/bitmask/mail/testing/__init__.py @@ -67,6 +67,8 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest):          self.km._nicknym._async_client_pinned.request = Mock(              return_value=defer.succeed(Response())) +        self.km.send_key = Mock( +            return_value=defer.succeed(Response()))          d1 = self.km.put_raw_key(PRIVATE_KEY, ADDRESS)          d2 = self.km.put_raw_key(PRIVATE_KEY_2, ADDRESS_2) diff --git a/src/leap/bitmask/mua/pixelizer.py b/src/leap/bitmask/mua/pixelizer.py index 98333bd2..819ffa70 100644 --- a/src/leap/bitmask/mua/pixelizer.py +++ b/src/leap/bitmask/mua/pixelizer.py @@ -215,7 +215,9 @@ class NickNym(object):          return self.keymanager.gen_key()      def _send_key_to_leap(self): -        return self.keymanager.send_key() +        # XXX: this needs to be removed in pixels side +        #      km.send_key doesn't exist anymore +        return defer.succeed(None)  class LeapSessionAdapter(object):  | 
