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