summaryrefslogtreecommitdiff
path: root/src/leap/keymanager/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/keymanager/__init__.py')
-rw-r--r--src/leap/keymanager/__init__.py235
1 files changed, 125 insertions, 110 deletions
diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py
index c7886e0..1106c23 100644
--- a/src/leap/keymanager/__init__.py
+++ b/src/leap/keymanager/__init__.py
@@ -22,6 +22,8 @@ import fileinput
import os
import sys
import tempfile
+import json
+import urllib
from leap.common import ca_bundle
@@ -61,6 +63,7 @@ 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
@@ -141,6 +144,8 @@ class KeyManager(object):
# the following are used to perform https requests
self._fetcher = requests
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)
#
# destructor
@@ -179,40 +184,55 @@ class KeyManager(object):
def _key_class_from_type(self, ktype):
"""
- Return key class from string representation of key type.
+ Given a class type, return a class
+
+ :param ktype: string representation of a class name
+ :type ktype: str
+
+ :return: A class with the matching name
+ :rtype: classobj or type
"""
return filter(
lambda klass: klass.__name__ == ktype,
self._wrapper_map).pop()
- def _get(self, uri, data=None):
+ @defer.inlineCallbacks
+ def _get_key_from_nicknym(self, address):
"""
Send a GET request to C{uri} containing C{data}.
- :param uri: The URI of the request.
- :type uri: str
- :param data: The body of the request.
- :type data: dict, str or file
+ :param address: The URI of the request.
+ :type address: str
- :return: The response to the request.
- :rtype: requests.Response
+ :return: A deferred that will be fired with GET content as json (dict)
+ :rtype: Deferred
"""
- leap_assert(
- self._ca_cert_path is not None,
- 'We need the CA certificate path!')
- res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path)
- # Nickserver now returns 404 for key not found and 500 for
- # other cases (like key too small), so we are skipping this
- # check for the time being
- # res.raise_for_status()
+ try:
+ uri = self._nickserver_uri + '?address=' + address
+ content = yield self._async_client_pinned.request(str(uri), 'GET')
+ json_content = json.loads(content)
+ except IOError as e:
+ # FIXME: 404 doesnt raise today, but it wont produce json anyway
+ # if e.response.status_code == 404:
+ # raise KeyNotFound(address)
+ 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.')
- return res
+ defer.returnValue(json_content)
+ @defer.inlineCallbacks
def _get_with_combined_ca_bundle(self, uri, data=None):
"""
Send a GET request to C{uri} containing C{data}.
@@ -225,12 +245,19 @@ class KeyManager(object):
:param data: The body of the request.
:type data: dict, str or file
- :return: The response to the request.
- :rtype: requests.Response
+ :return: A deferred that will be fired with the GET response
+ :rtype: Deferred
"""
- return self._fetcher.get(
- uri, data=data, verify=self._combined_ca_bundle)
+ try:
+ content = yield self._async_client.request(str(uri), 'GET')
+ except Exception as e:
+ logger.warning("There was a problem fetching key: %s" % (e,))
+ raise KeyNotFound(uri)
+ if not content:
+ raise KeyNotFound(uri)
+ defer.returnValue(content)
+ @defer.inlineCallbacks
def _put(self, uri, data=None):
"""
Send a PUT request to C{uri} containing C{data}.
@@ -244,23 +271,31 @@ class KeyManager(object):
:param data: The body of the request.
:type data: dict, str or file
- :return: The response to the request.
- :rtype: requests.Response
+ :return: A deferred that will be fired when PUT request finishes
+ :rtype: Deferred
"""
leap_assert(
- self._ca_cert_path is not None,
- 'We need the CA certificate path!')
- leap_assert(
self._token is not None,
'We need a token to interact with webapp!')
- res = self._fetcher.put(
- uri, data=data, verify=self._ca_cert_path,
- headers={'Authorization': 'Token token=%s' % self._token})
- # assert that the response is valid
- res.raise_for_status()
- return res
+ 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
@@ -276,38 +311,22 @@ class KeyManager(object):
"""
# request keys from the nickserver
- d = defer.succeed(None)
- res = None
- try:
- res = self._get(self._nickserver_uri, {'address': address})
- res.raise_for_status()
- server_keys = res.json()
-
- # 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
-
- d = self.put_raw_key(
- server_keys['openpgp'],
- OpenPGPKey,
- address=address,
- validation=validation_level)
- except requests.exceptions.HTTPError as e:
- if e.response.status_code == 404:
- d = defer.fail(KeyNotFound(address))
- else:
- d = defer.fail(KeyNotFound(e.message))
- logger.warning("HTTP error retrieving key: %r" % (e,))
- logger.warning("%s" % (res.content,))
- except Exception as e:
- d = defer.fail(KeyNotFound(e.message))
- logger.warning("Error retrieving key: %r" % (e,))
- return d
+ 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'],
+ OpenPGPKey,
+ address=address,
+ validation=validation_level)
#
# key management
@@ -339,8 +358,11 @@ class KeyManager(object):
self._api_uri,
self._api_version,
self._uid)
- self._put(uri, data)
- emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
+ d = self._put(uri, data)
+ d.addCallback(lambda _:
+ emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS,
+ self._address))
+ return d
d = self.get_key(
self._address, ktype, private=False, fetch_remote=False)
@@ -417,6 +439,7 @@ class KeyManager(object):
:return: A Deferred which fires with a list of all keys in local db.
:rtype: Deferred
"""
+ # TODO: should it be based on activedocs?
def build_keys(docs):
return map(
lambda doc: build_key_from_dict(
@@ -550,15 +573,16 @@ class KeyManager(object):
self._assert_supported_key_type(ktype)
_keys = self._wrapper_map[ktype]
+ @defer.inlineCallbacks
def encrypt(keys):
pubkey, signkey = keys
- encrypted = _keys.encrypt(
+ encrypted = yield _keys.encrypt(
data, pubkey, passphrase, sign=signkey,
cipher_algo=cipher_algo)
- pubkey.encr_used = True
- d = _keys.put_key(pubkey, address)
- d.addCallback(lambda _: encrypted)
- return d
+ if not pubkey.encr_used:
+ pubkey.encr_used = True
+ yield _keys.put_key(pubkey)
+ defer.returnValue(encrypted)
dpub = self.get_key(address, ktype, private=False,
fetch_remote=fetch_remote)
@@ -603,9 +627,10 @@ class KeyManager(object):
self._assert_supported_key_type(ktype)
_keys = self._wrapper_map[ktype]
+ @defer.inlineCallbacks
def decrypt(keys):
pubkey, privkey = keys
- decrypted, signed = _keys.decrypt(
+ decrypted, signed = yield _keys.decrypt(
data, privkey, passphrase=passphrase, verify=pubkey)
if pubkey is None:
signature = KeyNotFound(verify)
@@ -613,14 +638,13 @@ class KeyManager(object):
signature = pubkey
if not pubkey.sign_used:
pubkey.sign_used = True
- d = _keys.put_key(pubkey, verify)
- d.addCallback(lambda _: (decrypted, signature))
- return d
+ yield _keys.put_key(pubkey)
+ defer.returnValue((decrypted, signature))
else:
signature = InvalidSignature(
'Failed to verify signature with key %s' %
- (pubkey.key_id,))
- return (decrypted, signature)
+ (pubkey.fingerprint,))
+ defer.returnValue((decrypted, signature))
dpriv = self.get_key(address, ktype, private=True)
dpub = defer.succeed(None)
@@ -711,14 +735,14 @@ class KeyManager(object):
if signed:
if not pubkey.sign_used:
pubkey.sign_used = True
- d = _keys.put_key(pubkey, address)
+ d = _keys.put_key(pubkey)
d.addCallback(lambda _: pubkey)
return d
return pubkey
else:
raise InvalidSignature(
'Failed to verify signature with key %s' %
- (pubkey.key_id,))
+ (pubkey.fingerprint,))
d = self.get_key(address, ktype, private=False,
fetch_remote=fetch_remote)
@@ -742,20 +766,16 @@ class KeyManager(object):
_keys = self._wrapper_map[type(key)]
return _keys.delete_key(key)
- def put_key(self, key, address):
+ def put_key(self, key):
"""
Put key bound to address in local storage.
:param key: The key to be stored
:type key: EncryptionKey
- :param address: address for which this key will be active
- :type address: str
:return: A Deferred which fires when the key is in the storage, or
- which fails with KeyAddressMismatch if address doesn't match
- any uid on the key or fails with KeyNotValidUpdate if a key
- with the same uid exists and the new one is not a valid update
- for it.
+ which fails with KeyNotValidUpdate if a key with the same
+ uid exists and the new one is not a valid update for it.
:rtype: Deferred
:raise UnsupportedKeyTypeError: if invalid key type
@@ -764,11 +784,6 @@ class KeyManager(object):
self._assert_supported_key_type(ktype)
_keys = self._wrapper_map[ktype]
- if address not in key.address:
- return defer.fail(
- KeyAddressMismatch("UID %s found, but expected %s"
- % (str(key.address), address)))
-
def old_key_not_found(failure):
if failure.check(KeyNotFound):
return None
@@ -777,13 +792,13 @@ class KeyManager(object):
def check_upgrade(old_key):
if key.private or can_upgrade(key, old_key):
- return _keys.put_key(key, address)
+ return _keys.put_key(key)
else:
raise KeyNotValidUpgrade(
"Key %s can not be upgraded by new key %s"
- % (old_key.key_id, key.key_id))
+ % (old_key.fingerprint, key.fingerprint))
- d = _keys.get_key(address, private=key.private)
+ d = _keys.get_key(key.address, private=key.private)
d.addErrback(old_key_not_found)
d.addCallback(check_upgrade)
return d
@@ -805,9 +820,10 @@ class KeyManager(object):
:return: A Deferred which fires when the key is in the storage, or
which fails with KeyAddressMismatch if address doesn't match
- any uid on the key or fails with KeyNotValidUpdate if a key
- with the same uid exists and the new one is not a valid update
- for it.
+ any uid on the key or fails with KeyNotFound if no OpenPGP
+ material was found in key or fails with KeyNotValidUpdate if a
+ key with the same uid exists and the new one is not a valid
+ update for it.
:rtype: Deferred
:raise UnsupportedKeyTypeError: if invalid key type
@@ -815,13 +831,18 @@ class KeyManager(object):
self._assert_supported_key_type(ktype)
_keys = self._wrapper_map[ktype]
- pubkey, privkey = _keys.parse_ascii_key(key)
+ pubkey, privkey = _keys.parse_ascii_key(key, address)
+
+ if pubkey is None:
+ return defer.fail(KeyNotFound(key))
+
pubkey.validation = validation
- d = self.put_key(pubkey, address)
+ d = self.put_key(pubkey)
if privkey is not None:
- d.addCallback(lambda _: self.put_key(privkey, address))
+ d.addCallback(lambda _: self.put_key(privkey))
return d
+ @defer.inlineCallbacks
def fetch_key(self, address, uri, ktype,
validation=ValidationLevels.Weak_Chain):
"""
@@ -851,21 +872,15 @@ class KeyManager(object):
_keys = self._wrapper_map[ktype]
logger.info("Fetch key for %s from %s" % (address, uri))
- try:
- res = self._get_with_combined_ca_bundle(uri)
- except Exception as e:
- logger.warning("There was a problem fetching key: %s" % (e,))
- return defer.fail(KeyNotFound(uri))
- if not res.ok:
- return defer.fail(KeyNotFound(uri))
+ ascii_content = yield self._get_with_combined_ca_bundle(uri)
# XXX parse binary keys
- pubkey, _ = _keys.parse_ascii_key(res.content)
+ pubkey, _ = _keys.parse_ascii_key(ascii_content, address)
if pubkey is None:
- return defer.fail(KeyNotFound(uri))
+ raise KeyNotFound(uri)
pubkey.validation = validation
- return self.put_key(pubkey, address)
+ yield self.put_key(pubkey)
def _assert_supported_key_type(self, ktype):
"""