summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/core/mail_services.py125
-rw-r--r--src/leap/bitmask/core/service.py2
-rw-r--r--src/leap/bitmask/keymanager/__init__.py125
-rw-r--r--src/leap/bitmask/keymanager/openpgp.py2
-rw-r--r--src/leap/bitmask/mail/testing/__init__.py2
-rw-r--r--src/leap/bitmask/mua/pixelizer.py4
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):