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