[feat] Make EncryptionKey aware of the active address
[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         # TODO: should it be based on activedocs?
443         def build_keys(docs):
444             return map(
445                 lambda doc: build_key_from_dict(
446                     self._key_class_from_type(doc.content['type']),
447                     doc.content),
448                 docs)
449
450         # XXX: there is no check that the soledad indexes are ready, as it
451         #      happens with EncryptionScheme.
452         #      The usecases right now are not problematic. This could be solve
453         #      adding a keytype to this funciont and moving the soledad request
454         #      to the EncryptionScheme.
455         d = self._soledad.get_from_index(
456             TAGS_PRIVATE_INDEX,
457             KEYMANAGER_KEY_TAG,
458             '1' if private else '0')
459         d.addCallback(build_keys)
460         return d
461
462     def gen_key(self, ktype):
463         """
464         Generate a key of type ktype bound to the user's address.
465
466         :param ktype: The type of the key.
467         :type ktype: subclass of EncryptionKey
468
469         :return: A Deferred which fires with the generated EncryptionKey.
470         :rtype: Deferred
471
472         :raise UnsupportedKeyTypeError: if invalid key type
473         """
474         self._assert_supported_key_type(ktype)
475         _keys = self._wrapper_map[ktype]
476
477         def signal_finished(key):
478             emit_async(
479                 catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
480             return key
481
482         emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
483
484         d = _keys.gen_key(self._address)
485         d.addCallback(signal_finished)
486         return d
487
488     #
489     # Setters/getters
490     #
491
492     def _get_token(self):
493         return self._token
494
495     def _set_token(self, token):
496         self._token = token
497
498     token = property(
499         _get_token, _set_token, doc='The session token.')
500
501     def _get_ca_cert_path(self):
502         return self._ca_cert_path
503
504     def _set_ca_cert_path(self, ca_cert_path):
505         self._ca_cert_path = ca_cert_path
506
507     ca_cert_path = property(
508         _get_ca_cert_path, _set_ca_cert_path,
509         doc='The path to the CA certificate.')
510
511     def _get_api_uri(self):
512         return self._api_uri
513
514     def _set_api_uri(self, api_uri):
515         self._api_uri = api_uri
516
517     api_uri = property(
518         _get_api_uri, _set_api_uri, doc='The webapp API URI.')
519
520     def _get_api_version(self):
521         return self._api_version
522
523     def _set_api_version(self, api_version):
524         self._api_version = api_version
525
526     api_version = property(
527         _get_api_version, _set_api_version, doc='The webapp API version.')
528
529     def _get_uid(self):
530         return self._uid
531
532     def _set_uid(self, uid):
533         self._uid = uid
534
535     uid = property(
536         _get_uid, _set_uid, doc='The uid of the user.')
537
538     #
539     # encrypt/decrypt and sign/verify API
540     #
541
542     def encrypt(self, data, address, ktype, passphrase=None, sign=None,
543                 cipher_algo='AES256', fetch_remote=True):
544         """
545         Encrypt data with the public key bound to address and sign with with
546         the private key bound to sign address.
547
548         :param data: The data to be encrypted.
549         :type data: str
550         :param address: The address to encrypt it for.
551         :type address: str
552         :param ktype: The type of the key.
553         :type ktype: subclass of EncryptionKey
554         :param passphrase: The passphrase for the secret key used for the
555                            signature.
556         :type passphrase: str
557         :param sign: The address to be used for signature.
558         :type sign: str
559         :param cipher_algo: The cipher algorithm to use.
560         :type cipher_algo: str
561         :param fetch_remote: If key is not found in local storage try to fetch
562                              from nickserver
563         :type fetch_remote: bool
564
565         :return: A Deferred which fires with the encrypted data as str, or
566                  which fails with KeyNotFound if no keys were found neither
567                  locally or in keyserver or fails with EncryptError if failed
568                  encrypting for some reason.
569         :rtype: Deferred
570
571         :raise UnsupportedKeyTypeError: if invalid key type
572         """
573         self._assert_supported_key_type(ktype)
574         _keys = self._wrapper_map[ktype]
575
576         @defer.inlineCallbacks
577         def encrypt(keys):
578             pubkey, signkey = keys
579             encrypted = yield _keys.encrypt(
580                 data, pubkey, passphrase, sign=signkey,
581                 cipher_algo=cipher_algo)
582             pubkey.encr_used = True
583             yield _keys.put_key(pubkey)
584             defer.returnValue(encrypted)
585
586         dpub = self.get_key(address, ktype, private=False,
587                             fetch_remote=fetch_remote)
588         dpriv = defer.succeed(None)
589         if sign is not None:
590             dpriv = self.get_key(sign, ktype, private=True)
591         d = defer.gatherResults([dpub, dpriv], consumeErrors=True)
592         d.addCallbacks(encrypt, self._extract_first_error)
593         return d
594
595     def decrypt(self, data, address, ktype, passphrase=None, verify=None,
596                 fetch_remote=True):
597         """
598         Decrypt data using private key from address and verify with public key
599         bound to verify address.
600
601         :param data: The data to be decrypted.
602         :type data: str
603         :param address: The address to whom data was encrypted.
604         :type address: str
605         :param ktype: The type of the key.
606         :type ktype: subclass of EncryptionKey
607         :param passphrase: The passphrase for the secret key used for
608                            decryption.
609         :type passphrase: str
610         :param verify: The address to be used for signature.
611         :type verify: str
612         :param fetch_remote: If key for verify not found in local storage try
613                              to fetch from nickserver
614         :type fetch_remote: bool
615
616         :return: A Deferred which fires with:
617             * (decripted str, signing key) if validation works
618             * (decripted str, KeyNotFound) if signing key not found
619             * (decripted str, InvalidSignature) if signature is invalid
620             * KeyNotFound failure if private key not found
621             * DecryptError failure if decription failed
622         :rtype: Deferred
623
624         :raise UnsupportedKeyTypeError: if invalid key type
625         """
626         self._assert_supported_key_type(ktype)
627         _keys = self._wrapper_map[ktype]
628
629         @defer.inlineCallbacks
630         def decrypt(keys):
631             pubkey, privkey = keys
632             decrypted, signed = yield _keys.decrypt(
633                 data, privkey, passphrase=passphrase, verify=pubkey)
634             if pubkey is None:
635                 signature = KeyNotFound(verify)
636             elif signed:
637                 signature = pubkey
638                 if not pubkey.sign_used:
639                     pubkey.sign_used = True
640                     yield _keys.put_key(pubkey)
641                     defer.returnValue((decrypted, signature))
642             else:
643                 signature = InvalidSignature(
644                     'Failed to verify signature with key %s' %
645                     (pubkey.fingerprint,))
646             defer.returnValue((decrypted, signature))
647
648         dpriv = self.get_key(address, ktype, private=True)
649         dpub = defer.succeed(None)
650         if verify is not None:
651             dpub = self.get_key(verify, ktype, private=False,
652                                 fetch_remote=fetch_remote)
653             dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f)
654         d = defer.gatherResults([dpub, dpriv], consumeErrors=True)
655         d.addCallbacks(decrypt, self._extract_first_error)
656         return d
657
658     def _extract_first_error(self, failure):
659         return failure.value.subFailure
660
661     def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False,
662              detach=True, binary=False):
663         """
664         Sign data with private key bound to address.
665
666         :param data: The data to be signed.
667         :type data: str
668         :param address: The address to be used to sign.
669         :type address: EncryptionKey
670         :param ktype: The type of the key.
671         :type ktype: subclass of EncryptionKey
672         :param digest_algo: The hash digest to use.
673         :type digest_algo: str
674         :param clearsign: If True, create a cleartext signature.
675         :type clearsign: bool
676         :param detach: If True, create a detached signature.
677         :type detach: bool
678         :param binary: If True, do not ascii armour the output.
679         :type binary: bool
680
681         :return: A Deferred which fires with the signed data as str or fails
682                  with KeyNotFound if no key was found neither locally or in
683                  keyserver or fails with SignFailed if there was any error
684                  signing.
685         :rtype: Deferred
686
687         :raise UnsupportedKeyTypeError: if invalid key type
688         """
689         self._assert_supported_key_type(ktype)
690         _keys = self._wrapper_map[ktype]
691
692         def sign(privkey):
693             return _keys.sign(
694                 data, privkey, digest_algo=digest_algo, clearsign=clearsign,
695                 detach=detach, binary=binary)
696
697         d = self.get_key(address, ktype, private=True)
698         d.addCallback(sign)
699         return d
700
701     def verify(self, data, address, ktype, detached_sig=None,
702                fetch_remote=True):
703         """
704         Verify signed data with private key bound to address, eventually using
705         detached_sig.
706
707         :param data: The data to be verified.
708         :type data: str
709         :param address: The address to be used to verify.
710         :type address: EncryptionKey
711         :param ktype: The type of the key.
712         :type ktype: subclass of EncryptionKey
713         :param detached_sig: A detached signature. If given, C{data} is
714                              verified using this detached signature.
715         :type detached_sig: str
716         :param fetch_remote: If key for verify not found in local storage try
717                              to fetch from nickserver
718         :type fetch_remote: bool
719
720         :return: A Deferred which fires with the signing EncryptionKey if
721                  signature verifies, or which fails with InvalidSignature if
722                  signature don't verifies or fails with KeyNotFound if no key
723                  was found neither locally or in keyserver.
724         :rtype: Deferred
725
726         :raise UnsupportedKeyTypeError: if invalid key type
727         """
728         self._assert_supported_key_type(ktype)
729         _keys = self._wrapper_map[ktype]
730
731         def verify(pubkey):
732             signed = _keys.verify(
733                 data, pubkey, detached_sig=detached_sig)
734             if signed:
735                 if not pubkey.sign_used:
736                     pubkey.sign_used = True
737                     d = _keys.put_key(pubkey)
738                     d.addCallback(lambda _: pubkey)
739                     return d
740                 return pubkey
741             else:
742                 raise InvalidSignature(
743                     'Failed to verify signature with key %s' %
744                     (pubkey.fingerprint,))
745
746         d = self.get_key(address, ktype, private=False,
747                          fetch_remote=fetch_remote)
748         d.addCallback(verify)
749         return d
750
751     def delete_key(self, key):
752         """
753         Remove key from storage.
754
755         :param key: The key to be removed.
756         :type key: EncryptionKey
757
758         :return: A Deferred which fires when the key is deleted, or which fails
759                  KeyNotFound if the key was not found on local storage.
760         :rtype: Deferred
761
762         :raise UnsupportedKeyTypeError: if invalid key type
763         """
764         self._assert_supported_key_type(type(key))
765         _keys = self._wrapper_map[type(key)]
766         return _keys.delete_key(key)
767
768     def put_key(self, key):
769         """
770         Put key bound to address in local storage.
771
772         :param key: The key to be stored
773         :type key: EncryptionKey
774
775         :return: A Deferred which fires when the key is in the storage, or
776                  which fails with KeyNotValidUpdate if a key with the same
777                  uid exists and the new one is not a valid update for it.
778         :rtype: Deferred
779
780         :raise UnsupportedKeyTypeError: if invalid key type
781         """
782         ktype = type(key)
783         self._assert_supported_key_type(ktype)
784         _keys = self._wrapper_map[ktype]
785
786         def old_key_not_found(failure):
787             if failure.check(KeyNotFound):
788                 return None
789             else:
790                 return failure
791
792         def check_upgrade(old_key):
793             if key.private or can_upgrade(key, old_key):
794                 return _keys.put_key(key)
795             else:
796                 raise KeyNotValidUpgrade(
797                     "Key %s can not be upgraded by new key %s"
798                     % (old_key.fingerprint, key.fingerprint))
799
800         d = _keys.get_key(key.address, private=key.private)
801         d.addErrback(old_key_not_found)
802         d.addCallback(check_upgrade)
803         return d
804
805     def put_raw_key(self, key, ktype, address,
806                     validation=ValidationLevels.Weak_Chain):
807         """
808         Put raw key bound to address in local storage.
809
810         :param key: The ascii key to be stored
811         :type key: str
812         :param ktype: the type of the key.
813         :type ktype: subclass of EncryptionKey
814         :param address: address for which this key will be active
815         :type address: str
816         :param validation: validation level for this key
817                            (default: 'Weak_Chain')
818         :type validation: ValidationLevels
819
820         :return: A Deferred which fires when the key is in the storage, or
821                  which fails with KeyAddressMismatch if address doesn't match
822                  any uid on the key or fails with KeyNotValidUpdate if a key
823                  with the same uid exists and the new one is not a valid update
824                  for it.
825         :rtype: Deferred
826
827         :raise UnsupportedKeyTypeError: if invalid key type
828         """
829         self._assert_supported_key_type(ktype)
830         _keys = self._wrapper_map[ktype]
831
832         pubkey, privkey = _keys.parse_ascii_key(key, address)
833         pubkey.validation = validation
834         d = self.put_key(pubkey)
835         if privkey is not None:
836             d.addCallback(lambda _: self.put_key(privkey))
837         return d
838
839     @defer.inlineCallbacks
840     def fetch_key(self, address, uri, ktype,
841                   validation=ValidationLevels.Weak_Chain):
842         """
843         Fetch a public key bound to address from the network and put it in
844         local storage.
845
846         :param address: The email address of the key.
847         :type address: str
848         :param uri: The URI of the key.
849         :type uri: str
850         :param ktype: the type of the key.
851         :type ktype: subclass of EncryptionKey
852         :param validation: validation level for this key
853                            (default: 'Weak_Chain')
854         :type validation: ValidationLevels
855
856         :return: A Deferred which fires when the key is in the storage, or
857                  which fails with KeyNotFound: if not valid key on uri or fails
858                  with KeyAddressMismatch if address doesn't match any uid on
859                  the key or fails with KeyNotValidUpdate if a key with the same
860                  uid exists and the new one is not a valid update for it.
861         :rtype: Deferred
862
863         :raise UnsupportedKeyTypeError: if invalid key type
864         """
865         self._assert_supported_key_type(ktype)
866         _keys = self._wrapper_map[ktype]
867
868         logger.info("Fetch key for %s from %s" % (address, uri))
869         ascii_content = yield self._get_with_combined_ca_bundle(uri)
870
871         # XXX parse binary keys
872         pubkey, _ = _keys.parse_ascii_key(ascii_content, address)
873         if pubkey is None:
874             raise KeyNotFound(uri)
875
876         pubkey.validation = validation
877         yield self.put_key(pubkey)
878
879     def _assert_supported_key_type(self, ktype):
880         """
881         Check if ktype is one of the supported key types
882
883         :param ktype: the type of the key.
884         :type ktype: subclass of EncryptionKey
885
886         :raise UnsupportedKeyTypeError: if invalid key type
887         """
888         if ktype not in self._wrapper_map:
889             raise UnsupportedKeyTypeError(str(ktype))
890
891
892 def _split_email(address):
893     """
894     Split username and domain from an email address
895
896     :param address: an email address
897     :type address: str
898
899     :return: username and domain from the email address
900     :rtype: (str, str)
901     """
902     if address.count("@") != 1:
903         return None
904     return address.split("@")
905
906
907 def _get_domain(url):
908     """
909     Get the domain from an url
910
911     :param url: an url
912     :type url: str
913
914     :return: the domain part of the url
915     :rtype: str
916     """
917     return urlparse(url).hostname