diff options
author | Kali Kaneko <kali@leap.se> | 2015-02-11 14:01:20 -0400 |
---|---|---|
committer | Kali Kaneko <kali@leap.se> | 2015-02-11 14:01:20 -0400 |
commit | 8d8d5f2a3d9dbefcf98a5e3d7245a5f13360a88e (patch) | |
tree | 4e91d829a7185be9cb127592747eb72866d47f77 /src/leap/keymanager/__init__.py | |
parent | c794991d65d987b59014d067e730976684307775 (diff) | |
parent | 82d027b1f471517213bdcdc773dc8eea677fe330 (diff) |
Merge branch 'feature/async-api' into develop
Diffstat (limited to 'src/leap/keymanager/__init__.py')
-rw-r--r-- | src/leap/keymanager/__init__.py | 518 |
1 files changed, 348 insertions, 170 deletions
diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index e4be9c47..a1a59f54 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -25,7 +25,7 @@ try: assert(GPGUtilities) # pyflakes happy from gnupg import __version__ as _gnupg_version from pkg_resources import parse_version - assert(parse_version(_gnupg_version) >= parse_version('1.2.3')) + assert(parse_version(_gnupg_version) >= parse_version('1.4.0')) except (ImportError, AssertionError): print "*******" @@ -43,7 +43,9 @@ except (ImportError, AssertionError): import logging import requests -from leap.common.check import leap_assert, leap_assert_type +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 from leap.common.decorators import memoized_method @@ -51,12 +53,13 @@ from leap.common.decorators import memoized_method from leap.keymanager.errors import ( KeyNotFound, KeyAddressMismatch, - KeyNotValidUpgrade + KeyNotValidUpgrade, + UnsupportedKeyTypeError, + InvalidSignature ) from leap.keymanager.validation import ValidationLevel, can_upgrade from leap.keymanager.keys import ( - EncryptionKey, build_key_from_dict, KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, @@ -197,15 +200,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}) @@ -213,18 +221,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 @@ -232,7 +244,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. @@ -240,27 +252,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. @@ -275,36 +293,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): """ @@ -313,33 +342,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 @@ -395,70 +441,132 @@ class KeyManager(object): # encrypt/decrypt and sign/verify API # - def encrypt(self, data, pubkey, passphrase=None, sign=None, - cipher_algo='AES256'): + def encrypt(self, data, address, ktype, passphrase=None, sign=None, + cipher_algo='AES256', fetch_remote=True): """ - Encrypt C{data} using public @{key} and sign with C{sign} key. + 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 - :param pubkey: The key used to encrypt. - :type pubkey: EncryptionKey + :param address: The address to encrypt it for. + :type address: str + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for the signature. :type passphrase: str - :param sign: The key used for signing. - :type sign: EncryptionKey + :param sign: The address to be used for signature. + :type sign: str :param cipher_algo: The cipher algorithm to use. :type cipher_algo: str + :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 - """ - leap_assert_type(pubkey, EncryptionKey) - leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') - leap_assert(pubkey.private is False, 'Key is not public.') - return self._wrapper_map[pubkey.__class__].encrypt( - data, pubkey, passphrase, sign, cipher_algo=cipher_algo) - - def decrypt(self, data, privkey, passphrase=None, verify=None): - """ - Decrypt C{data} using private @{privkey} and verify with C{verify} key. + :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 UnsupportedKeyTypeError: if invalid key type + """ + self._assert_supported_key_type(ktype) + + def encrypt(keys): + pubkey, signkey = keys + encrypted = self._wrapper_map[ktype].encrypt( + data, pubkey, passphrase, sign=signkey, + cipher_algo=cipher_algo) + pubkey.encr_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: encrypted) + return d + + dpub = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + dpriv = defer.succeed(None) + if sign is not None: + dpriv = self.get_key(sign, ktype, private=True) + d = defer.gatherResults([dpub, dpriv], consumeErrors=True) + d.addCallbacks(encrypt, self._extract_first_error) + return d + + def decrypt(self, data, address, ktype, passphrase=None, verify=None, + fetch_remote=True): + """ + 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 privkey: The key used to decrypt. - :type privkey: OpenPGPKey + :param address: The address to whom data was encrypted. + :type address: str + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for decryption. :type passphrase: str - :param verify: The key used to verify a signature. - :type verify: OpenPGPKey - - :return: The decrypted data. - :rtype: str - - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. - """ - leap_assert_type(privkey, EncryptionKey) - leap_assert( - privkey.__class__ in self._wrapper_map, - 'Unknown key type.') - leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].decrypt( - data, privkey, passphrase, verify) + :param verify: The address to be used for signature. + :type verify: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool - def sign(self, data, privkey, digest_algo='SHA512', clearsign=False, + :return: A Deferred which fires with: + * (decripted str, signing key) if validation works + * (decripted str, KeyNotFound) if signing key not found + * (decripted str, InvalidSignature) if signature is invalid + * KeyNotFound failure if private key not found + * DecryptError failure if decription failed + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type + """ + 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) + if pubkey is None: + signature = KeyNotFound(verify) + elif signed: + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: (decrypted, pubkey)) + return d + else: + signature = InvalidSignature( + 'Failed to verify signature with key %s' % + (pubkey.key_id,)) + return (decrypted, signature) + + dpriv = self.get_key(address, ktype, private=True) + dpub = defer.succeed(None) + if verify is not None: + dpub = self.get_key(verify, ktype, private=False, + fetch_remote=fetch_remote) + dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f) + d = defer.gatherResults([dpub, dpriv], consumeErrors=True) + d.addCallbacks(decrypt, self._extract_first_error) + return d + + def _extract_first_error(self, failure): + return failure.value.subFailure + + def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ - Sign C{data} with C{privkey}. + Sign data with private key bound to address. :param data: The data to be signed. :type data: str - - :param privkey: The private key to be used to sign. - :type privkey: EncryptionKey + :param address: The address to be used to sign. + :type address: EncryptionKey + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param digest_algo: The hash digest to use. :type digest_algo: str :param clearsign: If True, create a cleartext signature. @@ -468,93 +576,137 @@ 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 UnsupportedKeyTypeError: if invalid key type """ - leap_assert_type(privkey, EncryptionKey) - leap_assert( - privkey.__class__ in self._wrapper_map, - 'Unknown key type.') - leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].sign( - data, privkey, digest_algo=digest_algo, clearsign=clearsign, - detach=detach, binary=binary) + self._assert_supported_key_type(ktype) - def verify(self, data, pubkey, detached_sig=None): + 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{pubkey}, 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 - :param pubkey: The public key to be used on verification. - :type pubkey: EncryptionKey + :param address: The address to be used to verify. + :type address: EncryptionKey + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param detached_sig: A detached signature. If given, C{data} is verified using this detached signature. :type detached_sig: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool - :return: signature matches - :rtype: bool - """ - leap_assert_type(pubkey, EncryptionKey) - leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') - leap_assert(pubkey.private is False, 'Key is not public.') - return self._wrapper_map[pubkey.__class__].verify( - data, pubkey, detached_sig=detached_sig) + :return: A Deferred which fires with the signing EncryptionKey if + signature verifies, or which fails with InvalidSignature if + signature don't verifies or 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) + + def verify(pubkey): + signed = self._wrapper_map[ktype].verify( + data, pubkey, detached_sig=detached_sig) + if signed: + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: pubkey) + return d + else: + raise InvalidSignature( + 'Failed to verify signature with key %s' % + (pubkey.key_id,)) + + 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 @@ -566,19 +718,27 @@ 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 - """ - pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) + :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, privkey = self._wrapper_map[ktype].parse_ascii_key(key) pubkey.validation = validation - self.put_key(pubkey, address) + d = self.put_key(pubkey, address) + if privkey is not None: + d.addCallback(lambda _: self.put_key(privkey, address)) + return d 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. @@ -591,22 +751,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'] |