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