Update auth to interact with webapp v2. Closes #5120.
[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 KeyNotFound
52
53 from leap.keymanager.keys import (
54     EncryptionKey,
55     build_key_from_dict,
56     KEYMANAGER_KEY_TAG,
57     TAGS_PRIVATE_INDEX,
58 )
59 from leap.keymanager.openpgp import (
60     OpenPGPKey,
61     OpenPGPScheme,
62 )
63
64 logger = logging.getLogger(__name__)
65
66
67 #
68 # The Key Manager
69 #
70
71 class KeyManager(object):
72
73     #
74     # server's key storage constants
75     #
76
77     OPENPGP_KEY = 'openpgp'
78     PUBKEY_KEY = "user[public_key]"
79
80     def __init__(self, address, nickserver_uri, soledad, token=None,
81                  ca_cert_path=None, api_uri=None, api_version=None, uid=None,
82                  gpgbinary=None):
83         """
84         Initialize a Key Manager for user's C{address} with provider's
85         nickserver reachable in C{url}.
86
87         :param address: The address of the user of this Key Manager.
88         :type address: str
89         :param url: The URL of the nickserver.
90         :type url: str
91         :param soledad: A Soledad instance for local storage of keys.
92         :type soledad: leap.soledad.Soledad
93         :param token: The token for interacting with the webapp API.
94         :type token: str
95         :param ca_cert_path: The path to the CA certificate.
96         :type ca_cert_path: str
97         :param api_uri: The URI of the webapp API.
98         :type api_uri: str
99         :param api_version: The version of the webapp API.
100         :type api_version: str
101         :param uid: The users' UID.
102         :type uid: str
103         :param gpgbinary: Name for GnuPG binary executable.
104         :type gpgbinary: C{str}
105         """
106         self._address = address
107         self._nickserver_uri = nickserver_uri
108         self._soledad = soledad
109         self._token = token
110         self.ca_cert_path = ca_cert_path
111         self.api_uri = api_uri
112         self.api_version = api_version
113         self.uid = uid
114         # a dict to map key types to their handlers
115         self._wrapper_map = {
116             OpenPGPKey: OpenPGPScheme(soledad, gpgbinary=gpgbinary),
117             # other types of key will be added to this mapper.
118         }
119         # the following are used to perform https requests
120         self._fetcher = requests
121         self._session = self._fetcher.session()
122
123     #
124     # utilities
125     #
126
127     def _key_class_from_type(self, ktype):
128         """
129         Return key class from string representation of key type.
130         """
131         return filter(
132             lambda klass: str(klass) == ktype,
133             self._wrapper_map).pop()
134
135     def _get(self, uri, data=None):
136         """
137         Send a GET request to C{uri} containing C{data}.
138
139         :param uri: The URI of the request.
140         :type uri: str
141         :param data: The body of the request.
142         :type data: dict, str or file
143
144         :return: The response to the request.
145         :rtype: requests.Response
146         """
147         leap_assert(
148             self._ca_cert_path is not None,
149             'We need the CA certificate path!')
150         res = self._fetcher.get(uri, data=data, verify=self._ca_cert_path)
151         # Nickserver now returns 404 for key not found and 500 for
152         # other cases (like key too small), so we are skipping this
153         # check for the time being
154         # res.raise_for_status()
155
156         # Responses are now text/plain, although it's json anyway, but
157         # this will fail when it shouldn't
158         # leap_assert(
159         #     res.headers['content-type'].startswith('application/json'),
160         #     'Content-type is not JSON.')
161         return res
162
163     def _put(self, uri, data=None):
164         """
165         Send a PUT request to C{uri} containing C{data}.
166
167         The request will be sent using the configured CA certificate path to
168         verify the server certificate and the configured session id for
169         authentication.
170
171         :param uri: The URI of the request.
172         :type uri: str
173         :param data: The body of the request.
174         :type data: dict, str or file
175
176         :return: The response to the request.
177         :rtype: requests.Response
178         """
179         leap_assert(
180             self._ca_cert_path is not None,
181             'We need the CA certificate path!')
182         leap_assert(
183             self._token is not None,
184             'We need a token to interact with webapp!')
185         res = self._fetcher.put(
186             uri, data=data, verify=self._ca_cert_path,
187             headers={'Authorization': 'Token token=%s' % self._token})
188         # assert that the response is valid
189         res.raise_for_status()
190         return res
191
192     def _fetch_keys_from_server(self, address):
193         """
194         Fetch keys bound to C{address} from nickserver and insert them in
195         local database.
196
197         :param address: The address bound to the keys.
198         :type address: str
199
200         @raise KeyNotFound: If the key was not found on nickserver.
201         """
202         # request keys from the nickserver
203         res = None
204         try:
205             res = self._get(self._nickserver_uri, {'address': address})
206             server_keys = res.json()
207             # insert keys in local database
208             if self.OPENPGP_KEY in server_keys:
209                 self._wrapper_map[OpenPGPKey].put_ascii_key(
210                     server_keys['openpgp'])
211         except Exception as e:
212             logger.warning("Error retrieving the keys: %r" % (e,))
213             if res:
214                 logger.warning("%s" % (res.content,))
215
216     #
217     # key management
218     #
219
220     def send_key(self, ktype):
221         """
222         Send user's key of type C{ktype} to provider.
223
224         Public key bound to user's is sent to provider, which will sign it and
225         replace any prior keys for the same address in its database.
226
227         If C{send_private} is True, then the private key is encrypted with
228         C{password} and sent to server in the same request, together with a
229         hash string of user's address and password. The encrypted private key
230         will be saved in the server in a way it is publicly retrievable
231         through the hash string.
232
233         :param ktype: The type of the key.
234         :type ktype: KeyType
235
236         @raise KeyNotFound: If the key was not found in local database.
237         """
238         leap_assert(
239             ktype is OpenPGPKey,
240             'For now we only know how to send OpenPGP public keys.')
241         # prepare the public key bound to address
242         pubkey = self.get_key(
243             self._address, ktype, private=False, fetch_remote=False)
244         data = {
245             self.PUBKEY_KEY: pubkey.key_data
246         }
247         uri = "%s/%s/users/%s.json" % (
248             self._api_uri,
249             self._api_version,
250             self._uid)
251         self._put(uri, data)
252         signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
253
254     @memoized_method
255     def get_key_from_cache(self, *args, **kwargs):
256         """
257         Public interface to `get_key`, that is memoized.
258         """
259         return self.get_key(*args, **kwargs)
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: KeyType
272         :param private: Look for a private key instead of a public one?
273         :type private: bool
274
275         :return: A key of type C{ktype} bound to C{address}.
276         :rtype: EncryptionKey
277         :raise KeyNotFound: If the key was not found both locally and in
278                             keyserver.
279         """
280         logger.debug("getting key for %s" % (address,))
281         leap_assert(
282             ktype in self._wrapper_map,
283             'Unkown key type: %s.' % str(ktype))
284         try:
285             signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
286             # return key if it exists in local database
287             key = self._wrapper_map[ktype].get_key(address, private=private)
288             signal(proto.KEYMANAGER_KEY_FOUND, address)
289
290             return key
291         except KeyNotFound:
292             signal(proto.KEYMANAGER_KEY_NOT_FOUND, address)
293
294             # we will only try to fetch a key from nickserver if fetch_remote
295             # is True and the key is not private.
296             if fetch_remote is False or private is True:
297                 raise
298
299             signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
300             self._fetch_keys_from_server(address)
301             key = self._wrapper_map[ktype].get_key(address, private=False)
302             signal(proto.KEYMANAGER_KEY_FOUND, address)
303
304             return key
305
306     def get_all_keys_in_local_db(self, private=False):
307         """
308         Return all keys stored in local database.
309
310         :return: A list with all keys in local db.
311         :rtype: list
312         """
313         return map(
314             lambda doc: build_key_from_dict(
315                 self._key_class_from_type(doc.content['type']),
316                 doc.content['address'],
317                 doc.content),
318             self._soledad.get_from_index(
319                 TAGS_PRIVATE_INDEX,
320                 KEYMANAGER_KEY_TAG,
321                 '1' if private else '0'))
322
323     def refresh_keys(self):
324         """
325         Fetch keys from nickserver and update them locally.
326         """
327         addresses = set(map(
328             lambda doc: doc.address,
329             self.get_all_keys_in_local_db(private=False)))
330         for address in addresses:
331             # do not attempt to refresh our own key
332             if address == self._address:
333                 continue
334             self._fetch_keys_from_server(address)
335
336     def gen_key(self, ktype):
337         """
338         Generate a key of type C{ktype} bound to the user's address.
339
340         :param ktype: The type of the key.
341         :type ktype: KeyType
342
343         :return: The generated key.
344         :rtype: EncryptionKey
345         """
346         signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
347         key = self._wrapper_map[ktype].gen_key(self._address)
348         signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
349
350         return key
351
352     #
353     # Setters/getters
354     #
355
356     def _get_token(self):
357         return self._token
358
359     def _set_token(self, token):
360         self._token = token
361
362     token = property(
363         _get_token, _set_token, doc='The session token.')
364
365     def _get_ca_cert_path(self):
366         return self._ca_cert_path
367
368     def _set_ca_cert_path(self, ca_cert_path):
369         self._ca_cert_path = ca_cert_path
370
371     ca_cert_path = property(
372         _get_ca_cert_path, _set_ca_cert_path,
373         doc='The path to the CA certificate.')
374
375     def _get_api_uri(self):
376         return self._api_uri
377
378     def _set_api_uri(self, api_uri):
379         self._api_uri = api_uri
380
381     api_uri = property(
382         _get_api_uri, _set_api_uri, doc='The webapp API URI.')
383
384     def _get_api_version(self):
385         return self._api_version
386
387     def _set_api_version(self, api_version):
388         self._api_version = api_version
389
390     api_version = property(
391         _get_api_version, _set_api_version, doc='The webapp API version.')
392
393     def _get_uid(self):
394         return self._uid
395
396     def _set_uid(self, uid):
397         self._uid = uid
398
399     uid = property(
400         _get_uid, _set_uid, doc='The uid of the user.')
401
402     #
403     # encrypt/decrypt and sign/verify API
404     #
405
406     def encrypt(self, data, pubkey, passphrase=None, sign=None,
407                 cipher_algo='AES256'):
408         """
409         Encrypt C{data} using public @{key} and sign with C{sign} key.
410
411         :param data: The data to be encrypted.
412         :type data: str
413         :param pubkey: The key used to encrypt.
414         :type pubkey: EncryptionKey
415         :param sign: The key used for signing.
416         :type sign: EncryptionKey
417         :param cipher_algo: The cipher algorithm to use.
418         :type cipher_algo: str
419
420         :return: The encrypted data.
421         :rtype: str
422         """
423         leap_assert_type(pubkey, EncryptionKey)
424         leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.')
425         leap_assert(pubkey.private is False, 'Key is not public.')
426         return self._wrapper_map[pubkey.__class__].encrypt(
427             data, pubkey, passphrase, sign)
428
429     def decrypt(self, data, privkey, passphrase=None, verify=None):
430         """
431         Decrypt C{data} using private @{privkey} and verify with C{verify} key.
432
433         :param data: The data to be decrypted.
434         :type data: str
435         :param privkey: The key used to decrypt.
436         :type privkey: OpenPGPKey
437         :param passphrase: The passphrase for the secret key used for
438                            decryption.
439         :type passphrase: str
440         :param verify: The key used to verify a signature.
441         :type verify: OpenPGPKey
442
443         :return: The decrypted data.
444         :rtype: str
445
446         @raise InvalidSignature: Raised if unable to verify the signature with
447             C{verify} key.
448         """
449         leap_assert_type(privkey, EncryptionKey)
450         leap_assert(
451             privkey.__class__ in self._wrapper_map,
452             'Unknown key type.')
453         leap_assert(privkey.private is True, 'Key is not private.')
454         return self._wrapper_map[privkey.__class__].decrypt(
455             data, privkey, passphrase, verify)
456
457     def sign(self, data, privkey, digest_algo='SHA512', clearsign=False,
458              detach=True, binary=False):
459         """
460         Sign C{data} with C{privkey}.
461
462         :param data: The data to be signed.
463         :type data: str
464
465         :param privkey: The private key to be used to sign.
466         :type privkey: EncryptionKey
467         :param digest_algo: The hash digest to use.
468         :type digest_algo: str
469         :param clearsign: If True, create a cleartext signature.
470         :type clearsign: bool
471         :param detach: If True, create a detached signature.
472         :type detach: bool
473         :param binary: If True, do not ascii armour the output.
474         :type binary: bool
475
476         :return: The signed data.
477         :rtype: str
478         """
479         leap_assert_type(privkey, EncryptionKey)
480         leap_assert(
481             privkey.__class__ in self._wrapper_map,
482             'Unknown key type.')
483         leap_assert(privkey.private is True, 'Key is not private.')
484         return self._wrapper_map[privkey.__class__].sign(
485             data, privkey, digest_algo=digest_algo, clearsign=clearsign,
486             detach=detach, binary=binary)
487
488     def verify(self, data, pubkey, detached_sig=None):
489         """
490         Verify signed C{data} with C{pubkey}, eventually using
491         C{detached_sig}.
492
493         :param data: The data to be verified.
494         :type data: str
495         :param pubkey: The public key to be used on verification.
496         :type pubkey: EncryptionKey
497         :param detached_sig: A detached signature. If given, C{data} is
498                              verified using this detached signature.
499         :type detached_sig: str
500
501         :return: The signed data.
502         :rtype: str
503         """
504         leap_assert_type(pubkey, EncryptionKey)
505         leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.')
506         leap_assert(pubkey.private is False, 'Key is not public.')
507         return self._wrapper_map[pubkey.__class__].verify(
508             data, pubkey, detached_sig=detached_sig)
509
510     def parse_openpgp_ascii_key(self, key_data):
511         """
512         Parses an ascii armored key (or key pair) data and returns
513         the OpenPGPKey keys.
514
515         :param key_data: the key data to be parsed.
516         :type key_data: str or unicode
517
518         :returns: the public key and private key (if applies) for that data.
519         :rtype: (public, private) -> tuple(OpenPGPKey, OpenPGPKey)
520                 the tuple may have one or both components None
521         """
522         return self._wrapper_map[OpenPGPKey].parse_ascii_key(key_data)
523
524     def delete_key(self, key):
525         """
526         Remove C{key} from storage.
527
528         May raise:
529             openpgp.errors.KeyNotFound
530             openpgp.errors.KeyAttributesDiffer
531
532         :param key: The key to be removed.
533         :type key: EncryptionKey
534         """
535         try:
536             self._wrapper_map[type(key)].delete_key(key)
537         except IndexError as e:
538             leap_assert(False, "Unsupported key type. Error {0!r}".format(e))
539
540     def put_key(self, key):
541         """
542         Put C{key} in local storage.
543
544         :param key: The key to be stored.
545         :type key: OpenPGPKey
546         """
547         try:
548             self._wrapper_map[type(key)].put_key(key)
549         except IndexError as e:
550             leap_assert(False, "Unsupported key type. Error {0!r}".format(e))
551
552 from ._version import get_versions
553 __version__ = get_versions()['version']
554 del get_versions