[bug] remove the dependency on enum34
[keymanager.git] / src / leap / keymanager / __init__.py
1 # -*- coding: utf-8 -*-
2 # __init__.py
3 # Copyright (C) 2013 LEAP
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 """
18 Key Manager is a Nicknym agent for LEAP client.
19 """
20 # let's do a little sanity check to see if we're using the wrong gnupg
21 import sys
22
23 try:
24     from gnupg.gnupg import GPGUtilities
25     assert(GPGUtilities)  # pyflakes happy
26     from gnupg import __version__ as _gnupg_version
27     from pkg_resources import parse_version
28     assert(parse_version(_gnupg_version) >= parse_version('1.4.0'))
29
30 except (ImportError, AssertionError):
31     print "*******"
32     print "Ooops! It looks like there is a conflict in the installed version "
33     print "of gnupg."
34     print
35     print "Disclaimer: Ideally, we would need to work a patch and propose the "
36     print "merge to upstream. But until then do: "
37     print
38     print "% pip uninstall python-gnupg"
39     print "% pip install gnupg"
40     print "*******"
41     sys.exit(1)
42
43 import logging
44 import requests
45
46 from twisted.internet import defer
47 from urlparse import urlparse
48
49 from leap.common.check import leap_assert
50 from leap.common.events import emit, catalog
51 from leap.common.decorators import memoized_method
52
53 from leap.keymanager.errors import (
54     KeyNotFound,
55     KeyAddressMismatch,
56     KeyNotValidUpgrade,
57     UnsupportedKeyTypeError,
58     InvalidSignature
59 )
60 from leap.keymanager.validation import ValidationLevels, can_upgrade
61
62 from leap.keymanager.keys import (
63     build_key_from_dict,
64     KEYMANAGER_KEY_TAG,
65     TAGS_PRIVATE_INDEX,
66 )
67 from leap.keymanager.openpgp import (
68     OpenPGPKey,
69     OpenPGPScheme,
70 )
71
72 logger = logging.getLogger(__name__)
73
74
75 #
76 # The Key Manager
77 #
78
79 class KeyManager(object):
80
81     #
82     # server's key storage constants
83     #
84
85     OPENPGP_KEY = 'openpgp'
86     PUBKEY_KEY = "user[public_key]"
87
88     def __init__(self, address, nickserver_uri, soledad, token=None,
89                  ca_cert_path=None, api_uri=None, api_version=None, uid=None,
90                  gpgbinary=None):
91         """
92         Initialize a Key Manager for user's C{address} with provider's
93         nickserver reachable in C{nickserver_uri}.
94
95         :param address: The email address of the user of this Key Manager.
96         :type address: str
97         :param nickserver_uri: The URI of the nickserver.
98         :type nickserver_uri: str
99         :param soledad: A Soledad instance for local storage of keys.
100         :type soledad: leap.soledad.Soledad
101         :param token: The token for interacting with the webapp API.
102         :type token: str
103         :param ca_cert_path: The path to the CA certificate.
104         :type ca_cert_path: str
105         :param api_uri: The URI of the webapp API.
106         :type api_uri: str
107         :param api_version: The version of the webapp API.
108         :type api_version: str
109         :param uid: The user's UID.
110         :type uid: str
111         :param gpgbinary: Name for GnuPG binary executable.
112         :type gpgbinary: C{str}
113         """
114         self._address = address
115         self._nickserver_uri = nickserver_uri
116         self._soledad = soledad
117         self._token = token
118         self.ca_cert_path = ca_cert_path
119         self.api_uri = api_uri
120         self.api_version = api_version
121         self.uid = uid
122         # a dict to map key types to their handlers
123         self._wrapper_map = {
124             OpenPGPKey: OpenPGPScheme(soledad, gpgbinary=gpgbinary),
125             # other types of key will be added to this mapper.
126         }
127         # the following are used to perform https requests
128         self._fetcher = requests
129         self._session = self._fetcher.session()
130
131     #
132     # utilities
133     #
134
135     def _key_class_from_type(self, ktype):
136         """
137         Return key class from string representation of key type.
138         """
139         return filter(
140             lambda klass: klass.__name__ == ktype,
141             self._wrapper_map).pop()
142
143     def _get(self, uri, data=None):
144         """
145         Send a GET request to C{uri} containing C{data}.
146
147         :param uri: The URI of the request.
148         :type uri: str
149         :param data: The body of the request.
150         :type data: dict, str or file
151
152         :return: The response to the request.
153         :rtype: requests.Response
154         """
155         leap_assert(
156             self._ca_cert_path is not None,
157             'We need the CA certificate path!')
158         res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path)
159         # Nickserver now returns 404 for key not found and 500 for
160         # other cases (like key too small), so we are skipping this
161         # check for the time being
162         # res.raise_for_status()
163
164         # Responses are now text/plain, although it's json anyway, but
165         # this will fail when it shouldn't
166         # leap_assert(
167         #     res.headers['content-type'].startswith('application/json'),
168         #     'Content-type is not JSON.')
169         return res
170
171     def _put(self, uri, data=None):
172         """
173         Send a PUT request to C{uri} containing C{data}.
174
175         The request will be sent using the configured CA certificate path to
176         verify the server certificate and the configured session id for
177         authentication.
178
179         :param uri: The URI of the request.
180         :type uri: str
181         :param data: The body of the request.
182         :type data: dict, str or file
183
184         :return: The response to the request.
185         :rtype: requests.Response
186         """
187         leap_assert(
188             self._ca_cert_path is not None,
189             'We need the CA certificate path!')
190         leap_assert(
191             self._token is not None,
192             'We need a token to interact with webapp!')
193         res = self._fetcher.put(
194             uri, data=data, verify=self._ca_cert_path,
195             headers={'Authorization': 'Token token=%s' % self._token})
196         # assert that the response is valid
197         res.raise_for_status()
198         return res
199
200     @memoized_method(invalidation=300)
201     def _fetch_keys_from_server(self, address):
202         """
203         Fetch keys bound to address from nickserver and insert them in
204         local database.
205
206         :param address: The address bound to the keys.
207         :type address: str
208
209         :return: A Deferred which fires when the key is in the storage,
210                  or which fails with KeyNotFound if the key was not found on
211                  nickserver.
212         :rtype: Deferred
213
214         """
215         # request keys from the nickserver
216         d = defer.succeed(None)
217         res = None
218         try:
219             res = self._get(self._nickserver_uri, {'address': address})
220             res.raise_for_status()
221             server_keys = res.json()
222
223             # insert keys in local database
224             if self.OPENPGP_KEY in server_keys:
225                 # nicknym server is authoritative for its own domain,
226                 # for other domains the key might come from key servers.
227                 validation_level = ValidationLevels.Weak_Chain
228                 _, domain = _split_email(address)
229                 if (domain == _get_domain(self._nickserver_uri)):
230                     validation_level = ValidationLevels.Provider_Trust
231
232                 d = self.put_raw_key(
233                     server_keys['openpgp'],
234                     OpenPGPKey,
235                     address=address,
236                     validation=validation_level)
237         except requests.exceptions.HTTPError as e:
238             if e.response.status_code == 404:
239                 d = defer.fail(KeyNotFound(address))
240             else:
241                 d = defer.fail(KeyNotFound(e.message))
242             logger.warning("HTTP error retrieving key: %r" % (e,))
243             logger.warning("%s" % (res.content,))
244         except Exception as e:
245             d = defer.fail(KeyNotFound(e.message))
246             logger.warning("Error retrieving key: %r" % (e,))
247         return d
248
249     #
250     # key management
251     #
252
253     def send_key(self, ktype):
254         """
255         Send user's key of type ktype to provider.
256
257         Public key bound to user's is sent to provider, which will sign it and
258         replace any prior keys for the same address in its database.
259
260         :param ktype: The type of the key.
261         :type ktype: subclass of EncryptionKey
262
263         :return: A Deferred which fires when the key is sent, or which fails
264                  with KeyNotFound if the key was not found in local database.
265         :rtype: Deferred
266
267         :raise UnsupportedKeyTypeError: if invalid key type
268         """
269         self._assert_supported_key_type(ktype)
270
271         def send(pubkey):
272             data = {
273                 self.PUBKEY_KEY: pubkey.key_data
274             }
275             uri = "%s/%s/users/%s.json" % (
276                 self._api_uri,
277                 self._api_version,
278                 self._uid)
279             self._put(uri, data)
280             emit(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
281
282         d = self.get_key(
283             self._address, ktype, private=False, fetch_remote=False)
284         d.addCallback(send)
285         return d
286
287     def get_key(self, address, ktype, private=False, fetch_remote=True):
288         """
289         Return a key of type ktype bound to address.
290
291         First, search for the key in local storage. If it is not available,
292         then try to fetch from nickserver.
293
294         :param address: The address bound to the key.
295         :type address: str
296         :param ktype: The type of the key.
297         :type ktype: subclass of EncryptionKey
298         :param private: Look for a private key instead of a public one?
299         :type private: bool
300         :param fetch_remote: If key not found in local storage try to fetch
301                              from nickserver
302         :type fetch_remote: bool
303
304         :return: A Deferred which fires with an EncryptionKey of type ktype
305                  bound to address, or which fails with KeyNotFound if no key
306                  was found neither locally or in keyserver.
307         :rtype: Deferred
308
309         :raise UnsupportedKeyTypeError: if invalid key type
310         """
311         self._assert_supported_key_type(ktype)
312         logger.debug("getting key for %s" % (address,))
313         leap_assert(
314             ktype in self._wrapper_map,
315             'Unkown key type: %s.' % str(ktype))
316         emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address)
317
318         def key_found(key):
319             emit(catalog.KEYMANAGER_KEY_FOUND, address)
320             return key
321
322         def key_not_found(failure):
323             if not failure.check(KeyNotFound):
324                 return failure
325
326             emit(catalog.KEYMANAGER_KEY_NOT_FOUND, address)
327
328             # we will only try to fetch a key from nickserver if fetch_remote
329             # is True and the key is not private.
330             if fetch_remote is False or private is True:
331                 return failure
332
333             emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address)
334             d = self._fetch_keys_from_server(address)
335             d.addCallback(
336                 lambda _:
337                 self._wrapper_map[ktype].get_key(address, private=False))
338             d.addCallback(key_found)
339             return d
340
341         # return key if it exists in local database
342         d = self._wrapper_map[ktype].get_key(address, private=private)
343         d.addCallbacks(key_found, key_not_found)
344         return d
345
346     def get_all_keys(self, private=False):
347         """
348         Return all keys stored in local database.
349
350         :param private: Include private keys
351         :type private: bool
352
353         :return: A Deferred which fires with a list of all keys in local db.
354         :rtype: Deferred
355         """
356         def build_keys(docs):
357             return map(
358                 lambda doc: build_key_from_dict(
359                     self._key_class_from_type(doc.content['type']),
360                     doc.content),
361                 docs)
362
363         # XXX: there is no check that the soledad indexes are ready, as it
364         #      happens with EncryptionScheme.
365         #      The usecases right now are not problematic. This could be solve
366         #      adding a keytype to this funciont and moving the soledad request
367         #      to the EncryptionScheme.
368         d = self._soledad.get_from_index(
369             TAGS_PRIVATE_INDEX,
370             KEYMANAGER_KEY_TAG,
371             '1' if private else '0')
372         d.addCallback(build_keys)
373         return d
374
375     def gen_key(self, ktype):
376         """
377         Generate a key of type ktype bound to the user's address.
378
379         :param ktype: The type of the key.
380         :type ktype: subclass of EncryptionKey
381
382         :return: A Deferred which fires with the generated EncryptionKey.
383         :rtype: Deferred
384
385         :raise UnsupportedKeyTypeError: if invalid key type
386         """
387         self._assert_supported_key_type(ktype)
388
389         def signal_finished(key):
390             emit(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
391             return key
392
393         emit(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
394         d = self._wrapper_map[ktype].gen_key(self._address)
395         d.addCallback(signal_finished)
396         return d
397
398     #
399     # Setters/getters
400     #
401
402     def _get_token(self):
403         return self._token
404
405     def _set_token(self, token):
406         self._token = token
407
408     token = property(
409         _get_token, _set_token, doc='The session token.')
410
411     def _get_ca_cert_path(self):
412         return self._ca_cert_path
413
414     def _set_ca_cert_path(self, ca_cert_path):
415         self._ca_cert_path = ca_cert_path
416
417     ca_cert_path = property(
418         _get_ca_cert_path, _set_ca_cert_path,
419         doc='The path to the CA certificate.')
420
421     def _get_api_uri(self):
422         return self._api_uri
423
424     def _set_api_uri(self, api_uri):
425         self._api_uri = api_uri
426
427     api_uri = property(
428         _get_api_uri, _set_api_uri, doc='The webapp API URI.')
429
430     def _get_api_version(self):
431         return self._api_version
432
433     def _set_api_version(self, api_version):
434         self._api_version = api_version
435
436     api_version = property(
437         _get_api_version, _set_api_version, doc='The webapp API version.')
438
439     def _get_uid(self):
440         return self._uid
441
442     def _set_uid(self, uid):
443         self._uid = uid
444
445     uid = property(
446         _get_uid, _set_uid, doc='The uid of the user.')
447
448     #
449     # encrypt/decrypt and sign/verify API
450     #
451
452     def encrypt(self, data, address, ktype, passphrase=None, sign=None,
453                 cipher_algo='AES256', fetch_remote=True):
454         """
455         Encrypt data with the public key bound to address and sign with with
456         the private key bound to sign address.
457
458         :param data: The data to be encrypted.
459         :type data: str
460         :param address: The address to encrypt it for.
461         :type address: str
462         :param ktype: The type of the key.
463         :type ktype: subclass of EncryptionKey
464         :param passphrase: The passphrase for the secret key used for the
465                            signature.
466         :type passphrase: str
467         :param sign: The address to be used for signature.
468         :type sign: str
469         :param cipher_algo: The cipher algorithm to use.
470         :type cipher_algo: str
471         :param fetch_remote: If key is not found in local storage try to fetch
472                              from nickserver
473         :type fetch_remote: bool
474
475         :return: A Deferred which fires with the encrypted data as str, or
476                  which fails with KeyNotFound if no keys were found neither
477                  locally or in keyserver or fails with EncryptError if failed
478                  encrypting for some reason.
479         :rtype: Deferred
480
481         :raise UnsupportedKeyTypeError: if invalid key type
482         """
483         self._assert_supported_key_type(ktype)
484
485         def encrypt(keys):
486             pubkey, signkey = keys
487             encrypted = self._wrapper_map[ktype].encrypt(
488                 data, pubkey, passphrase, sign=signkey,
489                 cipher_algo=cipher_algo)
490             pubkey.encr_used = True
491             d = self._wrapper_map[ktype].put_key(pubkey, address)
492             d.addCallback(lambda _: encrypted)
493             return d
494
495         dpub = self.get_key(address, ktype, private=False,
496                             fetch_remote=fetch_remote)
497         dpriv = defer.succeed(None)
498         if sign is not None:
499             dpriv = self.get_key(sign, ktype, private=True)
500         d = defer.gatherResults([dpub, dpriv], consumeErrors=True)
501         d.addCallbacks(encrypt, self._extract_first_error)
502         return d
503
504     def decrypt(self, data, address, ktype, passphrase=None, verify=None,
505                 fetch_remote=True):
506         """
507         Decrypt data using private key from address and verify with public key
508         bound to verify address.
509
510         :param data: The data to be decrypted.
511         :type data: str
512         :param address: The address to whom data was encrypted.
513         :type address: str
514         :param ktype: The type of the key.
515         :type ktype: subclass of EncryptionKey
516         :param passphrase: The passphrase for the secret key used for
517                            decryption.
518         :type passphrase: str
519         :param verify: The address to be used for signature.
520         :type verify: str
521         :param fetch_remote: If key for verify not found in local storage try
522                              to fetch from nickserver
523         :type fetch_remote: bool
524
525         :return: A Deferred which fires with:
526             * (decripted str, signing key) if validation works
527             * (decripted str, KeyNotFound) if signing key not found
528             * (decripted str, InvalidSignature) if signature is invalid
529             * KeyNotFound failure if private key not found
530             * DecryptError failure if decription failed
531         :rtype: Deferred
532
533         :raise UnsupportedKeyTypeError: if invalid key type
534         """
535         self._assert_supported_key_type(ktype)
536
537         def decrypt(keys):
538             pubkey, privkey = keys
539             decrypted, signed = self._wrapper_map[ktype].decrypt(
540                 data, privkey, passphrase=passphrase, verify=pubkey)
541             if pubkey is None:
542                 signature = KeyNotFound(verify)
543             elif signed:
544                 pubkey.sign_used = True
545                 d = self._wrapper_map[ktype].put_key(pubkey, address)
546                 d.addCallback(lambda _: (decrypted, pubkey))
547                 return d
548             else:
549                 signature = InvalidSignature(
550                     'Failed to verify signature with key %s' %
551                     (pubkey.key_id,))
552             return (decrypted, signature)
553
554         dpriv = self.get_key(address, ktype, private=True)
555         dpub = defer.succeed(None)
556         if verify is not None:
557             dpub = self.get_key(verify, ktype, private=False,
558                                 fetch_remote=fetch_remote)
559             dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f)
560         d = defer.gatherResults([dpub, dpriv], consumeErrors=True)
561         d.addCallbacks(decrypt, self._extract_first_error)
562         return d
563
564     def _extract_first_error(self, failure):
565         return failure.value.subFailure
566
567     def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False,
568              detach=True, binary=False):
569         """
570         Sign data with private key bound to address.
571
572         :param data: The data to be signed.
573         :type data: str
574         :param address: The address to be used to sign.
575         :type address: EncryptionKey
576         :param ktype: The type of the key.
577         :type ktype: subclass of EncryptionKey
578         :param digest_algo: The hash digest to use.
579         :type digest_algo: str
580         :param clearsign: If True, create a cleartext signature.
581         :type clearsign: bool
582         :param detach: If True, create a detached signature.
583         :type detach: bool
584         :param binary: If True, do not ascii armour the output.
585         :type binary: bool
586
587         :return: A Deferred which fires with the signed data as str or fails
588                  with KeyNotFound if no key was found neither locally or in
589                  keyserver or fails with SignFailed if there was any error
590                  signing.
591         :rtype: Deferred
592
593         :raise UnsupportedKeyTypeError: if invalid key type
594         """
595         self._assert_supported_key_type(ktype)
596
597         def sign(privkey):
598             return self._wrapper_map[ktype].sign(
599                 data, privkey, digest_algo=digest_algo, clearsign=clearsign,
600                 detach=detach, binary=binary)
601
602         d = self.get_key(address, ktype, private=True)
603         d.addCallback(sign)
604         return d
605
606     def verify(self, data, address, ktype, detached_sig=None,
607                fetch_remote=True):
608         """
609         Verify signed data with private key bound to address, eventually using
610         detached_sig.
611
612         :param data: The data to be verified.
613         :type data: str
614         :param address: The address to be used to verify.
615         :type address: EncryptionKey
616         :param ktype: The type of the key.
617         :type ktype: subclass of EncryptionKey
618         :param detached_sig: A detached signature. If given, C{data} is
619                              verified using this detached signature.
620         :type detached_sig: str
621         :param fetch_remote: If key for verify not found in local storage try
622                              to fetch from nickserver
623         :type fetch_remote: bool
624
625         :return: A Deferred which fires with the signing EncryptionKey if
626                  signature verifies, or which fails with InvalidSignature if
627                  signature don't verifies or fails with KeyNotFound if no key
628                  was found neither locally or in keyserver.
629         :rtype: Deferred
630
631         :raise UnsupportedKeyTypeError: if invalid key type
632         """
633         self._assert_supported_key_type(ktype)
634
635         def verify(pubkey):
636             signed = self._wrapper_map[ktype].verify(
637                 data, pubkey, detached_sig=detached_sig)
638             if signed:
639                 pubkey.sign_used = True
640                 d = self._wrapper_map[ktype].put_key(pubkey, address)
641                 d.addCallback(lambda _: pubkey)
642                 return d
643             else:
644                 raise InvalidSignature(
645                     'Failed to verify signature with key %s' %
646                     (pubkey.key_id,))
647
648         d = self.get_key(address, ktype, private=False,
649                          fetch_remote=fetch_remote)
650         d.addCallback(verify)
651         return d
652
653     def delete_key(self, key):
654         """
655         Remove key from storage.
656
657         :param key: The key to be removed.
658         :type key: EncryptionKey
659
660         :return: A Deferred which fires when the key is deleted, or which fails
661                  KeyNotFound if the key was not found on local storage.
662         :rtype: Deferred
663
664         :raise UnsupportedKeyTypeError: if invalid key type
665         """
666         self._assert_supported_key_type(type(key))
667         return self._wrapper_map[type(key)].delete_key(key)
668
669     def put_key(self, key, address):
670         """
671         Put key bound to address in local storage.
672
673         :param key: The key to be stored
674         :type key: EncryptionKey
675         :param address: address for which this key will be active
676         :type address: str
677
678         :return: A Deferred which fires when the key is in the storage, or
679                  which fails with KeyAddressMismatch if address doesn't match
680                  any uid on the key or fails with KeyNotValidUpdate if a key
681                  with the same uid exists and the new one is not a valid update
682                  for it.
683         :rtype: Deferred
684
685         :raise UnsupportedKeyTypeError: if invalid key type
686         """
687         self._assert_supported_key_type(type(key))
688
689         if address not in key.address:
690             return defer.fail(
691                 KeyAddressMismatch("UID %s found, but expected %s"
692                                    % (str(key.address), address)))
693
694         def old_key_not_found(failure):
695             if failure.check(KeyNotFound):
696                 return None
697             else:
698                 return failure
699
700         def check_upgrade(old_key):
701             if key.private or can_upgrade(key, old_key):
702                 return self._wrapper_map[type(key)].put_key(key, address)
703             else:
704                 raise KeyNotValidUpgrade(
705                     "Key %s can not be upgraded by new key %s"
706                     % (old_key.key_id, key.key_id))
707
708         d = self._wrapper_map[type(key)].get_key(address,
709                                                  private=key.private)
710         d.addErrback(old_key_not_found)
711         d.addCallback(check_upgrade)
712         return d
713
714     def put_raw_key(self, key, ktype, address,
715                     validation=ValidationLevels.Weak_Chain):
716         """
717         Put raw key bound to address in local storage.
718
719         :param key: The ascii key to be stored
720         :type key: str
721         :param ktype: the type of the key.
722         :type ktype: subclass of EncryptionKey
723         :param address: address for which this key will be active
724         :type address: str
725         :param validation: validation level for this key
726                            (default: 'Weak_Chain')
727         :type validation: ValidationLevels
728
729         :return: A Deferred which fires when the key is in the storage, or
730                  which fails with KeyAddressMismatch if address doesn't match
731                  any uid on the key or fails with KeyNotValidUpdate if a key
732                  with the same uid exists and the new one is not a valid update
733                  for it.
734         :rtype: Deferred
735
736         :raise UnsupportedKeyTypeError: if invalid key type
737         """
738         self._assert_supported_key_type(ktype)
739         pubkey, privkey = self._wrapper_map[ktype].parse_ascii_key(key)
740         pubkey.validation = validation
741         d = self.put_key(pubkey, address)
742         if privkey is not None:
743             d.addCallback(lambda _: self.put_key(privkey, address))
744         return d
745
746     def fetch_key(self, address, uri, ktype,
747                   validation=ValidationLevels.Weak_Chain):
748         """
749         Fetch a public key bound to address from the network and put it in
750         local storage.
751
752         :param address: The email address of the key.
753         :type address: str
754         :param uri: The URI of the key.
755         :type uri: str
756         :param ktype: the type of the key.
757         :type ktype: subclass of EncryptionKey
758         :param validation: validation level for this key
759                            (default: 'Weak_Chain')
760         :type validation: ValidationLevels
761
762         :return: A Deferred which fires when the key is in the storage, or
763                  which fails with KeyNotFound: if not valid key on uri or fails
764                  with KeyAddressMismatch if address doesn't match any uid on
765                  the key or fails with KeyNotValidUpdate if a key with the same
766                  uid exists and the new one is not a valid update for it.
767         :rtype: Deferred
768
769         :raise UnsupportedKeyTypeError: if invalid key type
770         """
771         self._assert_supported_key_type(ktype)
772
773         res = self._get(uri)
774         if not res.ok:
775             return defer.fail(KeyNotFound(uri))
776
777         # XXX parse binary keys
778         pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content)
779         if pubkey is None:
780             return defer.fail(KeyNotFound(uri))
781
782         pubkey.validation = validation
783         return self.put_key(pubkey, address)
784
785     def _assert_supported_key_type(self, ktype):
786         """
787         Check if ktype is one of the supported key types
788
789         :param ktype: the type of the key.
790         :type ktype: subclass of EncryptionKey
791
792         :raise UnsupportedKeyTypeError: if invalid key type
793         """
794         if ktype not in self._wrapper_map:
795             raise UnsupportedKeyTypeError(str(ktype))
796
797
798 def _split_email(address):
799     """
800     Split username and domain from an email address
801
802     :param address: an email address
803     :type address: str
804
805     :return: username and domain from the email address
806     :rtype: (str, str)
807     """
808     if address.count("@") != 1:
809         return None
810     return address.split("@")
811
812
813 def _get_domain(url):
814     """
815     Get the domain from an url
816
817     :param url: an url
818     :type url: str
819
820     :return: the domain part of the url
821     :rtype: str
822     """
823     return urlparse(url).hostname
824
825
826 from ._version import get_versions
827 __version__ = get_versions()['version']
828 del get_versions