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