From 8f1fe8dd4a54fd2bdda2fc78c339ce9b3d0fc331 Mon Sep 17 00:00:00 2001 From: Zara Gebru Date: Fri, 8 Jul 2016 11:55:55 +0200 Subject: [feature] keymanager - background update keys - refresh random key in random time - add get key by fingerprint - refactor nicknym methods to own file - tests - note this do not include a check for revoked key, since that need some changes in gnupg - Related: #6089 --- src/leap/keymanager/__init__.py | 198 +++++++++------------------------------- 1 file changed, 45 insertions(+), 153 deletions(-) (limited to 'src/leap/keymanager/__init__.py') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 0b8a5b3..24f37a9 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -22,13 +22,9 @@ import fileinput import os import sys import tempfile -import json -import urllib from leap.common import ca_bundle -from twisted.web import client -from twisted.web._responses import NOT_FOUND - +from leap.keymanager.refresher import RandomRefreshPublicKey from ._version import get_versions try: @@ -63,10 +59,9 @@ import logging from twisted.internet import defer from urlparse import urlparse -from leap.common.check import leap_assert from leap.common.http import HTTPClient from leap.common.events import emit_async, catalog -from leap.common.decorators import memoized_method +from leap.keymanager.nicknym import Nicknym from leap.keymanager.errors import ( KeyNotFound, @@ -132,7 +127,8 @@ class KeyManager(object): self._openpgp = OpenPGPScheme(soledad, gpgbinary=gpgbinary) self._combined_ca_bundle = self._create_combined_bundle_file() self._async_client = HTTPClient(self._combined_ca_bundle) - self._async_client_pinned = HTTPClient(self._ca_cert_path) + self._nicknym = Nicknym(self._nickserver_uri, self._ca_cert_path, self._token) + self.refresher = None # # destructor @@ -151,6 +147,13 @@ class KeyManager(object): # utilities # + def start_refresher(self): + self.refresher = RandomRefreshPublicKey(self._openpgp, self) + self.refresher.start() + + def stop_refresher(self): + self.refresher.stop() + def _create_combined_bundle_file(self): leap_ca_bundle = ca_bundle.where() @@ -169,65 +172,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 KeyNotFound: - raise - except IOError as e: - logger.warning("HTTP error retrieving key: %r" % (e,)) - logger.warning("%s" % (content,)) - raise KeyNotFound(e.message), None, sys.exc_info()[2] - except ValueError as v: - logger.warning("Invalid JSON data from key: %s" % (uri,)) - raise KeyNotFound(v.message + ' - ' + uri), None, sys.exc_info()[2] - - except Exception as e: - logger.warning("Error retrieving key: %r" % (e,)) - raise 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) - logger.warning(message) - raise KeyNotFound(message), None, sys.exc_info()[2] - return response - - d = self._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): """ @@ -253,76 +197,6 @@ class KeyManager(object): raise KeyNotFound(uri) defer.returnValue(content) - @defer.inlineCallbacks - def _put(self, uri, data=None): - """ - Send a PUT request to C{uri} containing C{data}. - - The request will be sent using the configured CA certificate path to - verify the server certificate and the configured session id for - authentication. - - :param uri: The URI of the request. - :type uri: str - :param data: The body of the request. - :type data: dict, str or file - - :return: A deferred that will be fired when PUT request finishes - :rtype: Deferred - """ - leap_assert( - self._token is not None, - 'We need a token to interact with webapp!') - if type(data) == dict: - data = urllib.urlencode(data) - headers = {'Authorization': [str('Token token=%s' % self._token)]} - headers['Content-Type'] = ['application/x-www-form-urlencoded'] - try: - res = yield self._async_client_pinned.request(str(uri), 'PUT', - body=str(data), - headers=headers) - except Exception as e: - logger.warning("Error uploading key: %r" % (e,)) - raise e - if 'error' in res: - # FIXME: That's a workaround for 500, - # we need to implement a readBody to assert response code - logger.warning("Error uploading key: %r" % (res,)) - raise Exception(res) - - @memoized_method(invalidation=300) - @defer.inlineCallbacks - def _fetch_keys_from_server(self, address): - """ - Fetch keys bound to address from nickserver and insert them in - local database. - - :param address: The address bound to the keys. - :type address: str - - :return: A Deferred which fires when the key is in the storage, - or which fails with KeyNotFound if the key was not found on - nickserver. - :rtype: Deferred - - """ - # request keys from the nickserver - server_keys = yield self._get_key_from_nicknym(address) - - # insert keys in local database - if self.OPENPGP_KEY in server_keys: - # nicknym server is authoritative for its own domain, - # for other domains the key might come from key servers. - validation_level = ValidationLevels.Weak_Chain - _, domain = _split_email(address) - if (domain == _get_domain(self._nickserver_uri)): - validation_level = ValidationLevels.Provider_Trust - - yield self.put_raw_key( - server_keys['openpgp'], - address=address, - validation=validation_level) - # # key management # @@ -341,14 +215,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ def send(pubkey): - data = { - self.PUBKEY_KEY: pubkey.key_data - } - uri = "%s/%s/users/%s.json" % ( - self._api_uri, - self._api_version, - self._uid) - d = self._put(uri, data) + 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)) @@ -359,6 +226,36 @@ class KeyManager(object): d.addCallback(send) return d + @defer.inlineCallbacks + def _fetch_keys_from_server_and_store_local(self, address): + """ + Fetch keys from nickserver and insert them in locale database. + + :param address: The address bound to the keys. + :type address: str + + :return: A Deferred which fires when the key is in the storage, + or which fails with KeyNotFound if the key was not found on + nickserver. + :rtype: Deferred + + """ + server_keys = yield self._nicknym.fetch_key_with_address(address) + + # insert keys in local database + if self.OPENPGP_KEY in server_keys: + # nicknym server is authoritative for its own domain, + # for other domains the key might come from key servers. + validation_level = ValidationLevels.Weak_Chain + _, domain = _split_email(address) + if (domain == _get_domain(self._nickserver_uri)): + validation_level = ValidationLevels.Provider_Trust + + yield self.put_raw_key( + server_keys['openpgp'], + address=address, + validation=validation_level) + def get_key(self, address, private=False, fetch_remote=True): """ Return a key bound to address. @@ -402,7 +299,7 @@ class KeyManager(object): return failure emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) - d = self._fetch_keys_from_server(address) + d = self._fetch_keys_from_server_and_store_local(address) d.addCallback( lambda _: self._openpgp.get_key(address, private=False)) d.addCallback(key_found) @@ -434,7 +331,6 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - def signal_finished(key): emit_async( catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) @@ -674,7 +570,6 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - def verify(pubkey): signed = self._openpgp.verify( data, pubkey, detached_sig=detached_sig) @@ -724,7 +619,6 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - def old_key_not_found(failure): if failure.check(KeyNotFound): return None @@ -801,12 +695,10 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - logger.info("Fetch key for %s from %s" % (address, uri)) - ascii_content = yield self._get_with_combined_ca_bundle(uri) + key_content = yield self._get_with_combined_ca_bundle(uri) - # XXX parse binary keys - pubkey, _ = self._openpgp.parse_key(ascii_content, address) + pubkey, _ = self._openpgp.parse_key(key_content, address) if pubkey is None: raise KeyNotFound(uri) -- cgit v1.2.3