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