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__.py438
1 files changed, 286 insertions, 152 deletions
diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py
index b2b05f4a..7ff437e5 100644
--- a/src/leap/keymanager/__init__.py
+++ b/src/leap/keymanager/__init__.py
@@ -43,6 +43,8 @@ except (ImportError, AssertionError):
import logging
import requests
+from twisted.internet import defer
+
from leap.common.check import leap_assert
from leap.common.events import signal
from leap.common.events import events_pb2 as proto
@@ -51,7 +53,8 @@ from leap.common.decorators import memoized_method
from leap.keymanager.errors import (
KeyNotFound,
KeyAddressMismatch,
- KeyNotValidUpgrade
+ KeyNotValidUpgrade,
+ UnsupportedKeyTypeError
)
from leap.keymanager.validation import ValidationLevel, can_upgrade
@@ -196,15 +199,20 @@ class KeyManager(object):
@memoized_method(invalidation=300)
def _fetch_keys_from_server(self, address):
"""
- Fetch keys bound to C{address} from nickserver and insert them in
+ Fetch keys bound to address from nickserver and insert them in
local database.
:param address: The address bound to the keys.
:type address: str
- :raise KeyNotFound: If the key was not found on nickserver.
+ :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
+ d = defer.succeed(None)
res = None
try:
res = self._get(self._nickserver_uri, {'address': address})
@@ -212,18 +220,22 @@ class KeyManager(object):
server_keys = res.json()
# insert keys in local database
if self.OPENPGP_KEY in server_keys:
- self.put_raw_key(
+ d = self.put_raw_key(
server_keys['openpgp'],
OpenPGPKey,
address=address,
validation=ValidationLevel.Provider_Trust)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
- raise KeyNotFound(address)
+ d = defer.fail(KeyNotFound(address))
+ else:
+ d = defer.fail(e)
logger.warning("HTTP error retrieving key: %r" % (e,))
logger.warning("%s" % (res.content,))
except Exception as e:
+ d = defer.fail(e)
logger.warning("Error retrieving key: %r" % (e,))
+ return d
#
# key management
@@ -231,7 +243,7 @@ class KeyManager(object):
def send_key(self, ktype):
"""
- Send user's key of type C{ktype} to provider.
+ Send user's key of type ktype to provider.
Public key bound to user's is sent to provider, which will sign it and
replace any prior keys for the same address in its database.
@@ -239,27 +251,33 @@ class KeyManager(object):
:param ktype: The type of the key.
:type ktype: subclass of EncryptionKey
- :raise KeyNotFound: If the key was not found in local database.
+ :return: A Deferred which fires when the key is sent, or which fails
+ with KeyNotFound if the key was not found in local database.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- leap_assert(
- ktype is OpenPGPKey,
- 'For now we only know how to send OpenPGP public keys.')
- # prepare the public key bound to address
- pubkey = self.get_key(
+ self._assert_supported_key_type(ktype)
+
+ def send(pubkey):
+ data = {
+ self.PUBKEY_KEY: pubkey.key_data
+ }
+ uri = "%s/%s/users/%s.json" % (
+ self._api_uri,
+ self._api_version,
+ self._uid)
+ self._put(uri, data)
+ signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
+
+ d = self.get_key(
self._address, ktype, private=False, fetch_remote=False)
- data = {
- self.PUBKEY_KEY: pubkey.key_data
- }
- uri = "%s/%s/users/%s.json" % (
- self._api_uri,
- self._api_version,
- self._uid)
- self._put(uri, data)
- signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
+ d.addCallback(send)
+ return d
def get_key(self, address, ktype, private=False, fetch_remote=True):
"""
- Return a key of type C{ktype} bound to C{address}.
+ Return a key of type ktype bound to address.
First, search for the key in local storage. If it is not available,
then try to fetch from nickserver.
@@ -274,36 +292,47 @@ class KeyManager(object):
from nickserver
:type fetch_remote: bool
- :return: A key of type C{ktype} bound to C{address}.
- :rtype: EncryptionKey
- :raise KeyNotFound: If the key was not found both locally and in
- keyserver.
+ :return: A Deferred which fires with an EncryptionKey of type ktype
+ bound to address, or which fails with KeyNotFound if no key
+ was found neither locally or in keyserver.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
+ self._assert_supported_key_type(ktype)
logger.debug("getting key for %s" % (address,))
leap_assert(
ktype in self._wrapper_map,
'Unkown key type: %s.' % str(ktype))
- try:
- signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
- # return key if it exists in local database
- key = self._wrapper_map[ktype].get_key(address, private=private)
- signal(proto.KEYMANAGER_KEY_FOUND, address)
+ signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
+ def key_found(key):
+ signal(proto.KEYMANAGER_KEY_FOUND, address)
return key
- except KeyNotFound:
+
+ def key_not_found(failure):
+ if not failure.check(KeyNotFound):
+ return failure
+
signal(proto.KEYMANAGER_KEY_NOT_FOUND, address)
# we will only try to fetch a key from nickserver if fetch_remote
# is True and the key is not private.
if fetch_remote is False or private is True:
- raise
+ return failure
signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
- self._fetch_keys_from_server(address) # might raise KeyNotFound
- key = self._wrapper_map[ktype].get_key(address, private=False)
- signal(proto.KEYMANAGER_KEY_FOUND, address)
-
- return key
+ d = self._fetch_keys_from_server(address)
+ d.addCallback(
+ lambda _:
+ self._wrapper_map[ktype].get_key(address, private=False))
+ d.addCallback(key_found)
+ return d
+
+ # return key if it exists in local database
+ d = self._wrapper_map[ktype].get_key(address, private=private)
+ d.addCallbacks(key_found, key_not_found)
+ return d
def get_all_keys(self, private=False):
"""
@@ -312,33 +341,50 @@ class KeyManager(object):
:param private: Include private keys
:type private: bool
- :return: A list with all keys in local db.
- :rtype: list
- """
- return map(
- lambda doc: build_key_from_dict(
- self._key_class_from_type(doc.content['type']),
- doc.content),
- self._soledad.get_from_index(
- TAGS_PRIVATE_INDEX,
- KEYMANAGER_KEY_TAG,
- '1' if private else '0'))
+ :return: A Deferred which fires with a list of all keys in local db.
+ :rtype: Deferred
+ """
+ def build_keys(docs):
+ return map(
+ lambda doc: build_key_from_dict(
+ self._key_class_from_type(doc.content['type']),
+ doc.content),
+ docs)
+
+ # XXX: there is no check that the soledad indexes are ready, as it
+ # happens with EncryptionScheme.
+ # The usecases right now are not problematic. This could be solve
+ # adding a keytype to this funciont and moving the soledad request
+ # to the EncryptionScheme.
+ d = self._soledad.get_from_index(
+ TAGS_PRIVATE_INDEX,
+ KEYMANAGER_KEY_TAG,
+ '1' if private else '0')
+ d.addCallback(build_keys)
+ return d
def gen_key(self, ktype):
"""
- Generate a key of type C{ktype} bound to the user's address.
+ Generate a key of type ktype bound to the user's address.
:param ktype: The type of the key.
:type ktype: subclass of EncryptionKey
- :return: The generated key.
- :rtype: EncryptionKey
+ :return: A Deferred which fires with the generated EncryptionKey.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
- key = self._wrapper_map[ktype].gen_key(self._address)
- signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
+ self._assert_supported_key_type(ktype)
- return key
+ def signal_finished(key):
+ signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
+ return key
+
+ signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
+ d = self._wrapper_map[ktype].gen_key(self._address)
+ d.addCallback(signal_finished)
+ return d
#
# Setters/getters
@@ -397,7 +443,8 @@ class KeyManager(object):
def encrypt(self, data, address, ktype, passphrase=None, sign=None,
cipher_algo='AES256', fetch_remote=True):
"""
- Encrypt C{data} for C{address} and sign with C{sign} address.
+ Encrypt data with the public key bound to address and sign with with
+ the private key bound to sign address.
:param data: The data to be encrypted.
:type data: str
@@ -412,34 +459,39 @@ class KeyManager(object):
:type sign: str
:param cipher_algo: The cipher algorithm to use.
:type cipher_algo: str
- :param fetch_remote: If key not found in local storage try to fetch
+ :param fetch_remote: If key is not found in local storage try to fetch
from nickserver
:type fetch_remote: bool
- :return: The encrypted data.
- :rtype: str
+ :return: A Deferred which fires with the encrypted data as str, or
+ which fails with KeyNotFound if no keys were found neither
+ locally or in keyserver or fails with EncryptError if failed
+ encrypting for some reason.
+ :rtype: Deferred
- :raise KeyNotFound: If any of the keys was not found both locally and
- in keyserver.
- :raise EncryptError: Raised if failed encrypting for some reason.
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- pubkey = self.get_key(address, ktype, private=False,
- fetch_remote=fetch_remote)
- privkey = None
- if sign is not None:
- privkey = self.get_key(sign, ktype, private=True)
- return self._wrapper_map[ktype].encrypt(
- data, pubkey, passphrase, privkey, cipher_algo=cipher_algo)
+ self._assert_supported_key_type(ktype)
+
+ def encrypt(keys):
+ pubkey, signkey = keys
+ return self._wrapper_map[ktype].encrypt(
+ data, pubkey, passphrase, sign=signkey,
+ cipher_algo=cipher_algo)
+
+ d = self._get_keys(ktype, address, sign, fetch_remote=fetch_remote)
+ d.addCallback(encrypt)
+ return d
def decrypt(self, data, address, ktype, passphrase=None, verify=None,
fetch_remote=True):
"""
- Decrypt C{data} using private key from @{address} and verify with
- C{verify} address.
+ Decrypt data using private key from address and verify with public key
+ bound to verify address.
:param data: The data to be decrypted.
:type data: str
- :param address: The address to who was encrypted.
+ :param address: The address to whom data was encrypted.
:type address: str
:param ktype: The type of the key.
:type ktype: subclass of EncryptionKey
@@ -452,26 +504,59 @@ class KeyManager(object):
to fetch from nickserver
:type fetch_remote: bool
- :return: The decrypted data and the signing key if signature verifies
- :rtype: (unicode, EncryptionKey)
+ :return: A Deferred which fires with the decrypted data as str and the
+ signing EncryptionKey if signature verifies, or which fails
+ with KeyNotFound if no keys were found neither locally or in
+ keyserver or fails with DecryptError if failed decrypting for
+ some reason.
+ :rtype: Deferred
- :raise KeyNotFound: If any of the keys was not found both locally and
- in keyserver.
- :raise DecryptError: Raised if failed decrypting for some reason.
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- privkey = self.get_key(address, ktype, private=True)
- pubkey = None
- if verify is not None:
- pubkey = self.get_key(verify, ktype, private=False,
- fetch_remote=fetch_remote)
- decrypted, signed = self._wrapper_map[ktype].decrypt(
- data, privkey, passphrase, pubkey)
- return (decrypted, pubkey if signed else None)
+ self._assert_supported_key_type(ktype)
+
+ def decrypt(keys):
+ pubkey, privkey = keys
+ decrypted, signed = self._wrapper_map[ktype].decrypt(
+ data, privkey, passphrase=passphrase, verify=pubkey)
+ return (decrypted, pubkey if signed else None)
+
+ d = self._get_keys(ktype, verify, address, fetch_remote)
+ d.addCallback(decrypt)
+ return d
+
+ def _get_keys(self, ktype, public, private, fetch_remote=True):
+ """
+ Get public and private keys of ktype.
+
+ :param ktype: The type of the key.
+ :type ktype: subclass of EncryptionKey
+ :param public: The address of the public key.
+ :type public: str
+ :param private: The address of the private key.
+ :type private: str
+ :param fetch_remote: If key for verify not found in local storage try
+ to fetch from nickserver
+ :type fetch_remote: bool
+
+ :return: A Deferred which fires with a tuple with public and private
+ EncryptionKeys, or which fails with KeyNotFound if no keys
+ were found neither locally or in keyserver.
+ :rtype: Deferred
+ """
+ dpub = defer.succeed(None)
+ if public is not None:
+ dpub = self.get_key(public, ktype, private=False,
+ fetch_remote=fetch_remote)
+ dpriv = defer.succeed(None)
+ if private is not None:
+ dpriv = self.get_key(private, ktype, private=True)
+ return defer.gatherResults([dpub, dpriv])
def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False,
detach=True, binary=False):
"""
- Sign C{data} with C{address}.
+ Sign data with private key bound to address.
:param data: The data to be signed.
:type data: str
@@ -488,23 +573,30 @@ class KeyManager(object):
:param binary: If True, do not ascii armour the output.
:type binary: bool
- :return: The signed data.
- :rtype: str
+ :return: A Deferred which fires with the signed data as str or fails
+ with KeyNotFound if no key was found neither locally or in
+ keyserver or fails with SignFailed if there was any error
+ signing.
+ :rtype: Deferred
- :raise KeyNotFound: If the key was not found both locally and
- in keyserver.
- :raise SignFailed: If there was any error signing.
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- privkey = self.get_key(address, ktype, private=True)
- return self._wrapper_map[ktype].sign(
- data, privkey, digest_algo=digest_algo, clearsign=clearsign,
- detach=detach, binary=binary)
+ self._assert_supported_key_type(ktype)
+
+ def sign(privkey):
+ return self._wrapper_map[ktype].sign(
+ data, privkey, digest_algo=digest_algo, clearsign=clearsign,
+ detach=detach, binary=binary)
+
+ d = self.get_key(address, ktype, private=True)
+ d.addCallback(sign)
+ return d
def verify(self, data, address, ktype, detached_sig=None,
fetch_remote=True):
"""
- Verify signed C{data} with C{address}, eventually using
- C{detached_sig}.
+ Verify signed data with private key bound to address, eventually using
+ detached_sig.
:param data: The data to be verified.
:type data: str
@@ -519,71 +611,90 @@ class KeyManager(object):
to fetch from nickserver
:type fetch_remote: bool
- :return: The signing key if signature verifies else None
- :rtype: EncryptionKey
+ :return: A Deferred which fires with the signing EncryptionKey if
+ signature verifies else None, or which fails with KeyNotFound
+ if no key was found neither locally or in keyserver.
+ :rtype: Deferred
- :raise KeyNotFound: If the key was not found both locally and
- in keyserver.
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- pubkey = self.get_key(address, ktype, private=False,
- fetch_remote=fetch_remote)
- signed = self._wrapper_map[ktype].verify(
- data, pubkey, detached_sig=detached_sig)
- return pubkey if signed else None
+ self._assert_supported_key_type(ktype)
+
+ def verify(pubkey):
+ signed = self._wrapper_map[ktype].verify(
+ data, pubkey, detached_sig=detached_sig)
+ return pubkey if signed else None
+
+ d = self.get_key(address, ktype, private=False,
+ fetch_remote=fetch_remote)
+ d.addCallback(verify)
+ return d
def delete_key(self, key):
"""
- Remove C{key} from storage.
-
- May raise:
- openpgp.errors.KeyNotFound
- openpgp.errors.KeyAttributesDiffer
+ Remove key from storage.
:param key: The key to be removed.
:type key: EncryptionKey
+
+ :return: A Deferred which fires when the key is deleted, or which fails
+ KeyNotFound if the key was not found on local storage.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- try:
- self._wrapper_map[type(key)].delete_key(key)
- except IndexError as e:
- leap_assert(False, "Unsupported key type. Error {0!r}".format(e))
+ self._assert_supported_key_type(type(key))
+ return self._wrapper_map[type(key)].delete_key(key)
def put_key(self, key, address):
"""
- Put C{key} in local storage.
+ 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
- :raises KeyAddressMismatch: if address doesn't match any uid on the key
- :raises KeyNotValidUpdate: if a key with the same uid exists and the
- new one is not a valid update for it
+ :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.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
- if address not in key.address:
- raise KeyAddressMismatch("UID %s found, but expected %s"
- % (str(key.address), address))
+ self._assert_supported_key_type(type(key))
- try:
- old_key = self._wrapper_map[type(key)].get_key(address,
- private=key.private)
- except KeyNotFound:
- old_key = None
-
- if key.private or can_upgrade(key, old_key):
- try:
- self._wrapper_map[type(key)].put_key(key, address)
- except IndexError as e:
- leap_assert(
- False, "Unsupported key type. Error {0!r}".format(e))
- else:
- raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s"
- % (old_key.key_id, key.key_id))
+ 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
+ else:
+ return failure
+
+ def check_upgrade(old_key):
+ if key.private or can_upgrade(key, old_key):
+ return self._wrapper_map[type(key)].put_key(key, address)
+ else:
+ raise KeyNotValidUpgrade(
+ "Key %s can not be upgraded by new key %s"
+ % (old_key.key_id, key.key_id))
+
+ d = self._wrapper_map[type(key)].get_key(address,
+ private=key.private)
+ d.addErrback(old_key_not_found)
+ d.addCallback(check_upgrade)
+ return d
def put_raw_key(self, key, ktype, address,
validation=ValidationLevel.Weak_Chain):
"""
- Put C{key} in local storage.
+ Put raw key bound to address in local storage.
:param key: The ascii key to be stored
:type key: str
@@ -595,19 +706,24 @@ class KeyManager(object):
(default: 'Weak_Chain')
:type validation: ValidationLevel
- :raises KeyAddressMismatch: if address doesn't match any uid on the key
- :raises KeyNotValidUpdate: if a key with the same uid exists and the
- new one is not a valid update for it
+ :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.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
+ self._assert_supported_key_type(ktype)
pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key)
-
pubkey.validation = validation
- self.put_key(pubkey, address)
+ return self.put_key(pubkey, address)
def fetch_key(self, address, uri, ktype,
validation=ValidationLevel.Weak_Chain):
"""
- Fetch a public key for C{address} from the network and put it in
+ Fetch a public key bound to address from the network and put it in
local storage.
:param address: The email address of the key.
@@ -620,22 +736,40 @@ class KeyManager(object):
(default: 'Weak_Chain')
:type validation: ValidationLevel
- :raises KeyNotFound: if not valid key on C{uri}
- :raises KeyAddressMismatch: if address doesn't match any uid on the key
- :raises KeyNotValidUpdate: if a key with the same uid exists and the
- new one is not a valid update for it
+ :return: A Deferred which fires when the key is in the storage, or
+ which fails with KeyNotFound: if not valid key on uri or 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.
+ :rtype: Deferred
+
+ :raise UnsupportedKeyTypeError: if invalid key type
"""
+ self._assert_supported_key_type(ktype)
+
res = self._get(uri)
if not res.ok:
- raise KeyNotFound(uri)
+ return defer.fail(KeyNotFound(uri))
# XXX parse binary keys
pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content)
if pubkey is None:
- raise KeyNotFound(uri)
+ return defer.fail(KeyNotFound(uri))
pubkey.validation = validation
- self.put_key(pubkey, address)
+ return self.put_key(pubkey, address)
+
+ def _assert_supported_key_type(self, ktype):
+ """
+ Check if ktype is one of the supported key types
+
+ :param ktype: the type of the key.
+ :type ktype: subclass of EncryptionKey
+
+ :raise UnsupportedKeyTypeError: if invalid key type
+ """
+ if ktype not in self._wrapper_map:
+ raise UnsupportedKeyTypeError(str(ktype))
from ._version import get_versions
__version__ = get_versions()['version']