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