Basic validation levels support
[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 sys
22
23 try:
24     from gnupg.gnupg import GPGUtilities
25     assert(GPGUtilities)  # pyflakes happy
26     from gnupg import __version__ as _gnupg_version
27     from pkg_resources import parse_version
28     assert(parse_version(_gnupg_version) >= parse_version('1.2.3'))
29
30 except (ImportError, AssertionError):
31     print "*******"
32     print "Ooops! It looks like there is a conflict in the installed version "
33     print "of gnupg."
34     print
35     print "Disclaimer: Ideally, we would need to work a patch and propose the "
36     print "merge to upstream. But until then do: "
37     print
38     print "% pip uninstall python-gnupg"
39     print "% pip install gnupg"
40     print "*******"
41     sys.exit(1)
42
43 import logging
44 import requests
45
46 from leap.common.check import leap_assert, leap_assert_type
47 from leap.common.events import signal
48 from leap.common.events import events_pb2 as proto
49 from leap.common.decorators import memoized_method
50
51 from leap.keymanager.errors import (
52     KeyNotFound,
53     KeyAddressMismatch,
54     KeyNotValidUpgrade
55 )
56 from leap.keymanager.validation import ValidationLevel, can_upgrade
57
58 from leap.keymanager.keys import (
59     EncryptionKey,
60     build_key_from_dict,
61     KEYMANAGER_KEY_TAG,
62     TAGS_PRIVATE_INDEX,
63 )
64 from leap.keymanager.openpgp import (
65     OpenPGPKey,
66     OpenPGPScheme,
67 )
68
69 logger = logging.getLogger(__name__)
70
71
72 #
73 # The Key Manager
74 #
75
76 class KeyManager(object):
77
78     #
79     # server's key storage constants
80     #
81
82     OPENPGP_KEY = 'openpgp'
83     PUBKEY_KEY = "user[public_key]"
84
85     def __init__(self, address, nickserver_uri, soledad, token=None,
86                  ca_cert_path=None, api_uri=None, api_version=None, uid=None,
87                  gpgbinary=None):
88         """
89         Initialize a Key Manager for user's C{address} with provider's
90         nickserver reachable in C{nickserver_uri}.
91
92         :param address: The email address of the user of this Key Manager.
93         :type address: str
94         :param nickserver_uri: The URI of the nickserver.
95         :type nickserver_uri: str
96         :param soledad: A Soledad instance for local storage of keys.
97         :type soledad: leap.soledad.Soledad
98         :param token: The token for interacting with the webapp API.
99         :type token: str
100         :param ca_cert_path: The path to the CA certificate.
101         :type ca_cert_path: str
102         :param api_uri: The URI of the webapp API.
103         :type api_uri: str
104         :param api_version: The version of the webapp API.
105         :type api_version: str
106         :param uid: The user's UID.
107         :type uid: str
108         :param gpgbinary: Name for GnuPG binary executable.
109         :type gpgbinary: C{str}
110         """
111         self._address = address
112         self._nickserver_uri = nickserver_uri
113         self._soledad = soledad
114         self._token = token
115         self.ca_cert_path = ca_cert_path
116         self.api_uri = api_uri
117         self.api_version = api_version
118         self.uid = uid
119         # a dict to map key types to their handlers
120         self._wrapper_map = {
121             OpenPGPKey: OpenPGPScheme(soledad, gpgbinary=gpgbinary),
122             # other types of key will be added to this mapper.
123         }
124         # the following are used to perform https requests
125         self._fetcher = requests
126         self._session = self._fetcher.session()
127
128     #
129     # utilities
130     #
131
132     def _key_class_from_type(self, ktype):
133         """
134         Return key class from string representation of key type.
135         """
136         return filter(
137             lambda klass: str(klass) == ktype,
138             self._wrapper_map).pop()
139
140     def _get(self, uri, data=None):
141         """
142         Send a GET request to C{uri} containing C{data}.
143
144         :param uri: The URI of the request.
145         :type uri: str
146         :param data: The body of the request.
147         :type data: dict, str or file
148
149         :return: The response to the request.
150         :rtype: requests.Response
151         """
152         leap_assert(
153             self._ca_cert_path is not None,
154             'We need the CA certificate path!')
155         res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path)
156         # Nickserver now returns 404 for key not found and 500 for
157         # other cases (like key too small), so we are skipping this
158         # check for the time being
159         # res.raise_for_status()
160
161         # Responses are now text/plain, although it's json anyway, but
162         # this will fail when it shouldn't
163         # leap_assert(
164         #     res.headers['content-type'].startswith('application/json'),
165         #     'Content-type is not JSON.')
166         return res
167
168     def _put(self, uri, data=None):
169         """
170         Send a PUT request to C{uri} containing C{data}.
171
172         The request will be sent using the configured CA certificate path to
173         verify the server certificate and the configured session id for
174         authentication.
175
176         :param uri: The URI of the request.
177         :type uri: str
178         :param data: The body of the request.
179         :type data: dict, str or file
180
181         :return: The response to the request.
182         :rtype: requests.Response
183         """
184         leap_assert(
185             self._ca_cert_path is not None,
186             'We need the CA certificate path!')
187         leap_assert(
188             self._token is not None,
189             'We need a token to interact with webapp!')
190         res = self._fetcher.put(
191             uri, data=data, verify=self._ca_cert_path,
192             headers={'Authorization': 'Token token=%s' % self._token})
193         # assert that the response is valid
194         res.raise_for_status()
195         return res
196
197     @memoized_method(invalidation=300)
198     def _fetch_keys_from_server(self, address):
199         """
200         Fetch keys bound to C{address} from nickserver and insert them in
201         local database.
202
203         :param address: The address bound to the keys.
204         :type address: str
205
206         :raise KeyNotFound: If the key was not found on nickserver.
207         """
208         # request keys from the nickserver
209         res = None
210         try:
211             res = self._get(self._nickserver_uri, {'address': address})
212             res.raise_for_status()
213             server_keys = res.json()
214             # insert keys in local database
215             if self.OPENPGP_KEY in server_keys:
216                 self.put_raw_key(
217                     server_keys['openpgp'],
218                     OpenPGPKey,
219                     address=address,
220                     validation=ValidationLevel.Provider_Trust)
221         except requests.exceptions.HTTPError as e:
222             if e.response.status_code == 404:
223                 raise KeyNotFound(address)
224             logger.warning("HTTP error retrieving key: %r" % (e,))
225             logger.warning("%s" % (res.content,))
226         except Exception as e:
227             logger.warning("Error retrieving key: %r" % (e,))
228
229     #
230     # key management
231     #
232
233     def send_key(self, ktype):
234         """
235         Send user's key of type C{ktype} to provider.
236
237         Public key bound to user's is sent to provider, which will sign it and
238         replace any prior keys for the same address in its database.
239
240         :param ktype: The type of the key.
241         :type ktype: subclass of EncryptionKey
242
243         :raise KeyNotFound: If the key was not found in local database.
244         """
245         leap_assert(
246             ktype is OpenPGPKey,
247             'For now we only know how to send OpenPGP public keys.')
248         # prepare the public key bound to address
249         pubkey = self.get_key(
250             self._address, ktype, private=False, fetch_remote=False)
251         data = {
252             self.PUBKEY_KEY: pubkey.key_data
253         }
254         uri = "%s/%s/users/%s.json" % (
255             self._api_uri,
256             self._api_version,
257             self._uid)
258         self._put(uri, data)
259         signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
260
261     def get_key(self, address, ktype, private=False, fetch_remote=True):
262         """
263         Return a key of type C{ktype} bound to C{address}.
264
265         First, search for the key in local storage. If it is not available,
266         then try to fetch from nickserver.
267
268         :param address: The address bound to the key.
269         :type address: str
270         :param ktype: The type of the key.
271         :type ktype: subclass of EncryptionKey
272         :param private: Look for a private key instead of a public one?
273         :type private: bool
274         :param fetch_remote: If key not found in local storage try to fetch
275                              from nickserver
276         :type fetch_remote: bool
277
278         :return: A key of type C{ktype} bound to C{address}.
279         :rtype: EncryptionKey
280         :raise KeyNotFound: If the key was not found both locally and in
281                             keyserver.
282         """
283         logger.debug("getting key for %s" % (address,))
284         leap_assert(
285             ktype in self._wrapper_map,
286             'Unkown key type: %s.' % str(ktype))
287         try:
288             signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
289             # return key if it exists in local database
290             key = self._wrapper_map[ktype].get_key(address, private=private)
291             signal(proto.KEYMANAGER_KEY_FOUND, address)
292
293             return key
294         except KeyNotFound:
295             signal(proto.KEYMANAGER_KEY_NOT_FOUND, address)
296
297             # we will only try to fetch a key from nickserver if fetch_remote
298             # is True and the key is not private.
299             if fetch_remote is False or private is True:
300                 raise
301
302             signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
303             self._fetch_keys_from_server(address)  # might raise KeyNotFound
304             key = self._wrapper_map[ktype].get_key(address, private=False)
305             signal(proto.KEYMANAGER_KEY_FOUND, address)
306
307             return key
308
309     def get_all_keys(self, private=False):
310         """
311         Return all keys stored in local database.
312
313         :param private: Include private keys
314         :type private: bool
315
316         :return: A list with all keys in local db.
317         :rtype: list
318         """
319         return map(
320             lambda doc: build_key_from_dict(
321                 self._key_class_from_type(doc.content['type']),
322                 doc.content['address'],
323                 doc.content),
324             self._soledad.get_from_index(
325                 TAGS_PRIVATE_INDEX,
326                 KEYMANAGER_KEY_TAG,
327                 '1' if private else '0'))
328
329     def gen_key(self, ktype):
330         """
331         Generate a key of type C{ktype} bound to the user's address.
332
333         :param ktype: The type of the key.
334         :type ktype: subclass of EncryptionKey
335
336         :return: The generated key.
337         :rtype: EncryptionKey
338         """
339         signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
340         key = self._wrapper_map[ktype].gen_key(self._address)
341         signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
342
343         return key
344
345     #
346     # Setters/getters
347     #
348
349     def _get_token(self):
350         return self._token
351
352     def _set_token(self, token):
353         self._token = token
354
355     token = property(
356         _get_token, _set_token, doc='The session token.')
357
358     def _get_ca_cert_path(self):
359         return self._ca_cert_path
360
361     def _set_ca_cert_path(self, ca_cert_path):
362         self._ca_cert_path = ca_cert_path
363
364     ca_cert_path = property(
365         _get_ca_cert_path, _set_ca_cert_path,
366         doc='The path to the CA certificate.')
367
368     def _get_api_uri(self):
369         return self._api_uri
370
371     def _set_api_uri(self, api_uri):
372         self._api_uri = api_uri
373
374     api_uri = property(
375         _get_api_uri, _set_api_uri, doc='The webapp API URI.')
376
377     def _get_api_version(self):
378         return self._api_version
379
380     def _set_api_version(self, api_version):
381         self._api_version = api_version
382
383     api_version = property(
384         _get_api_version, _set_api_version, doc='The webapp API version.')
385
386     def _get_uid(self):
387         return self._uid
388
389     def _set_uid(self, uid):
390         self._uid = uid
391
392     uid = property(
393         _get_uid, _set_uid, doc='The uid of the user.')
394
395     #
396     # encrypt/decrypt and sign/verify API
397     #
398
399     def encrypt(self, data, pubkey, passphrase=None, sign=None,
400                 cipher_algo='AES256'):
401         """
402         Encrypt C{data} using public @{key} and sign with C{sign} key.
403
404         :param data: The data to be encrypted.
405         :type data: str
406         :param pubkey: The key used to encrypt.
407         :type pubkey: EncryptionKey
408         :param passphrase: The passphrase for the secret key used for the
409                            signature.
410         :type passphrase: str
411         :param sign: The key used for signing.
412         :type sign: EncryptionKey
413         :param cipher_algo: The cipher algorithm to use.
414         :type cipher_algo: str
415
416         :return: The encrypted data.
417         :rtype: str
418         """
419         leap_assert_type(pubkey, EncryptionKey)
420         leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.')
421         leap_assert(pubkey.private is False, 'Key is not public.')
422         return self._wrapper_map[pubkey.__class__].encrypt(
423             data, pubkey, passphrase, sign, cipher_algo=cipher_algo)
424
425     def decrypt(self, data, privkey, passphrase=None, verify=None):
426         """
427         Decrypt C{data} using private @{privkey} and verify with C{verify} key.
428
429         :param data: The data to be decrypted.
430         :type data: str
431         :param privkey: The key used to decrypt.
432         :type privkey: OpenPGPKey
433         :param passphrase: The passphrase for the secret key used for
434                            decryption.
435         :type passphrase: str
436         :param verify: The key used to verify a signature.
437         :type verify: OpenPGPKey
438
439         :return: The decrypted data.
440         :rtype: str
441
442         :raise InvalidSignature: Raised if unable to verify the signature with
443                                  C{verify} key.
444         """
445         leap_assert_type(privkey, EncryptionKey)
446         leap_assert(
447             privkey.__class__ in self._wrapper_map,
448             'Unknown key type.')
449         leap_assert(privkey.private is True, 'Key is not private.')
450         return self._wrapper_map[privkey.__class__].decrypt(
451             data, privkey, passphrase, verify)
452
453     def sign(self, data, privkey, digest_algo='SHA512', clearsign=False,
454              detach=True, binary=False):
455         """
456         Sign C{data} with C{privkey}.
457
458         :param data: The data to be signed.
459         :type data: str
460
461         :param privkey: The private key to be used to sign.
462         :type privkey: EncryptionKey
463         :param digest_algo: The hash digest to use.
464         :type digest_algo: str
465         :param clearsign: If True, create a cleartext signature.
466         :type clearsign: bool
467         :param detach: If True, create a detached signature.
468         :type detach: bool
469         :param binary: If True, do not ascii armour the output.
470         :type binary: bool
471
472         :return: The signed data.
473         :rtype: str
474         """
475         leap_assert_type(privkey, EncryptionKey)
476         leap_assert(
477             privkey.__class__ in self._wrapper_map,
478             'Unknown key type.')
479         leap_assert(privkey.private is True, 'Key is not private.')
480         return self._wrapper_map[privkey.__class__].sign(
481             data, privkey, digest_algo=digest_algo, clearsign=clearsign,
482             detach=detach, binary=binary)
483
484     def verify(self, data, pubkey, detached_sig=None):
485         """
486         Verify signed C{data} with C{pubkey}, eventually using
487         C{detached_sig}.
488
489         :param data: The data to be verified.
490         :type data: str
491         :param pubkey: The public key to be used on verification.
492         :type pubkey: EncryptionKey
493         :param detached_sig: A detached signature. If given, C{data} is
494                              verified using this detached signature.
495         :type detached_sig: str
496
497         :return: The signed data.
498         :rtype: str
499         """
500         leap_assert_type(pubkey, EncryptionKey)
501         leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.')
502         leap_assert(pubkey.private is False, 'Key is not public.')
503         return self._wrapper_map[pubkey.__class__].verify(
504             data, pubkey, detached_sig=detached_sig)
505
506     def delete_key(self, key):
507         """
508         Remove C{key} from storage.
509
510         May raise:
511             openpgp.errors.KeyNotFound
512             openpgp.errors.KeyAttributesDiffer
513
514         :param key: The key to be removed.
515         :type key: EncryptionKey
516         """
517         try:
518             self._wrapper_map[type(key)].delete_key(key)
519         except IndexError as e:
520             leap_assert(False, "Unsupported key type. Error {0!r}".format(e))
521
522     def put_key(self, key):
523         """
524         Put C{key} in local storage.
525
526         :param key: The key to be stored
527         :type key: EncryptionKey
528         :raises KeyNotValidUpdate: if a key with the same uid exists and the
529                                    new one is not a valid update for it
530         """
531         try:
532             old_key = self._wrapper_map[type(key)].get_key(key.address,
533                                                            private=key.private)
534         except KeyNotFound:
535             old_key = None
536
537         if key.private or can_upgrade(key, old_key):
538             try:
539                 self._wrapper_map[type(key)].put_key(key)
540             except IndexError as e:
541                 leap_assert(
542                     False, "Unsupported key type. Error {0!r}".format(e))
543         else:
544             raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s"
545                                      % (old_key.key_id, key.key_id))
546
547     def put_raw_key(self, key, ktype, address=None,
548                     validation=ValidationLevel.Weak_Chain):
549         """
550         Put C{key} in local storage.
551
552         :param key: The ascii key to be stored
553         :type key: str
554         :param ktype: the type of the key.
555         :type ktype: subclass of EncryptionKey
556         :param address: if set used to check that the key is for this address
557         :type address: str
558         :param validation: validation level for this key
559                            (default: 'Weak_Chain')
560         :type validation: ValidationLevel
561
562         :raises KeyAddressMismatch: if address doesn't match any uid on the key
563         :raises KeyNotValidUpdate: if a key with the same uid exists and the
564                                    new one is not a valid update for it
565         """
566         pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key)
567         if address is not None and address != pubkey.address:
568             raise KeyAddressMismatch("Key UID %s, but expected %s"
569                                      % (pubkey.address, address))
570
571         pubkey.validation = validation
572         self.put_key(pubkey)
573
574     def fetch_key(self, address, uri, ktype,
575                   validation=ValidationLevel.Weak_Chain):
576         """
577         Fetch a public key for C{address} from the network and put it in
578         local storage.
579
580         :param address: The email address of the key.
581         :type address: str
582         :param uri: The URI of the key.
583         :type uri: str
584         :param ktype: the type of the key.
585         :type ktype: subclass of EncryptionKey
586         :param validation: validation level for this key
587                            (default: 'Weak_Chain')
588         :type validation: ValidationLevel
589
590         :raises KeyNotFound: if not valid key on C{uri}
591         :raises KeyAddressMismatch: if address doesn't match any uid on the key
592         :raises KeyNotValidUpdate: if a key with the same uid exists and the
593                                    new one is not a valid update for it
594         """
595         res = self._get(uri)
596         if not res.ok:
597             raise KeyNotFound(uri)
598
599         # XXX parse binary keys
600         pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content)
601         if pubkey is None:
602             raise KeyNotFound(uri)
603         if pubkey.address != address:
604             raise KeyAddressMismatch("UID %s found, but expected %s"
605                                      % (pubkey.address, address))
606
607         pubkey.validation = validation
608         self.put_key(pubkey)
609
610 from ._version import get_versions
611 __version__ = get_versions()['version']
612 del get_versions