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