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