Accept ascii keys on put_key
[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{nickserver_uri}.
86
87         :param address: The email address of the user of this Key Manager.
88         :type address: str
89         :param nickserver_uri: The URI of the nickserver.
90         :type nickserver_uri: 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 user's 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     @memoized_method(invalidation=300)
193     def _fetch_keys_from_server(self, address):
194         """
195         Fetch keys bound to C{address} from nickserver and insert them in
196         local database.
197
198         :param address: The address bound to the keys.
199         :type address: str
200
201         :raise KeyNotFound: If the key was not found on nickserver.
202         """
203         # request keys from the nickserver
204         res = None
205         try:
206             res = self._get(self._nickserver_uri, {'address': address})
207             res.raise_for_status()
208             server_keys = res.json()
209             # insert keys in local database
210             if self.OPENPGP_KEY in server_keys:
211                 self._wrapper_map[OpenPGPKey].put_ascii_key(
212                     server_keys['openpgp'])
213         except requests.exceptions.HTTPError as e:
214             if e.response.status_code == 404:
215                 raise KeyNotFound(address)
216             logger.warning("HTTP error retrieving key: %r" % (e,))
217             logger.warning("%s" % (res.content,))
218         except Exception as e:
219             logger.warning("Error retrieving key: %r" % (e,))
220
221     #
222     # key management
223     #
224
225     def send_key(self, ktype):
226         """
227         Send user's key of type C{ktype} to provider.
228
229         Public key bound to user's is sent to provider, which will sign it and
230         replace any prior keys for the same address in its database.
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         :param fetch_remote: If key not found in local storage try to fetch
267                              from nickserver
268         :type fetch_remote: bool
269
270         :return: A key of type C{ktype} bound to C{address}.
271         :rtype: EncryptionKey
272         :raise KeyNotFound: If the key was not found both locally and in
273                             keyserver.
274         """
275         logger.debug("getting key for %s" % (address,))
276         leap_assert(
277             ktype in self._wrapper_map,
278             'Unkown key type: %s.' % str(ktype))
279         try:
280             signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
281             # return key if it exists in local database
282             key = self._wrapper_map[ktype].get_key(address, private=private)
283             signal(proto.KEYMANAGER_KEY_FOUND, address)
284
285             return key
286         except KeyNotFound:
287             signal(proto.KEYMANAGER_KEY_NOT_FOUND, address)
288
289             # we will only try to fetch a key from nickserver if fetch_remote
290             # is True and the key is not private.
291             if fetch_remote is False or private is True:
292                 raise
293
294             signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address)
295             self._fetch_keys_from_server(address)  # might raise KeyNotFound
296             key = self._wrapper_map[ktype].get_key(address, private=False)
297             signal(proto.KEYMANAGER_KEY_FOUND, address)
298
299             return key
300
301     def get_all_keys(self, private=False):
302         """
303         Return all keys stored in local database.
304
305         :param private: Include private keys
306         :type private: bool
307
308         :return: A list with all keys in local db.
309         :rtype: list
310         """
311         return map(
312             lambda doc: build_key_from_dict(
313                 self._key_class_from_type(doc.content['type']),
314                 doc.content['address'],
315                 doc.content),
316             self._soledad.get_from_index(
317                 TAGS_PRIVATE_INDEX,
318                 KEYMANAGER_KEY_TAG,
319                 '1' if private else '0'))
320
321     def gen_key(self, ktype):
322         """
323         Generate a key of type C{ktype} bound to the user's address.
324
325         :param ktype: The type of the key.
326         :type ktype: KeyType
327
328         :return: The generated key.
329         :rtype: EncryptionKey
330         """
331         signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address)
332         key = self._wrapper_map[ktype].gen_key(self._address)
333         signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address)
334
335         return key
336
337     #
338     # Setters/getters
339     #
340
341     def _get_token(self):
342         return self._token
343
344     def _set_token(self, token):
345         self._token = token
346
347     token = property(
348         _get_token, _set_token, doc='The session token.')
349
350     def _get_ca_cert_path(self):
351         return self._ca_cert_path
352
353     def _set_ca_cert_path(self, ca_cert_path):
354         self._ca_cert_path = ca_cert_path
355
356     ca_cert_path = property(
357         _get_ca_cert_path, _set_ca_cert_path,
358         doc='The path to the CA certificate.')
359
360     def _get_api_uri(self):
361         return self._api_uri
362
363     def _set_api_uri(self, api_uri):
364         self._api_uri = api_uri
365
366     api_uri = property(
367         _get_api_uri, _set_api_uri, doc='The webapp API URI.')
368
369     def _get_api_version(self):
370         return self._api_version
371
372     def _set_api_version(self, api_version):
373         self._api_version = api_version
374
375     api_version = property(
376         _get_api_version, _set_api_version, doc='The webapp API version.')
377
378     def _get_uid(self):
379         return self._uid
380
381     def _set_uid(self, uid):
382         self._uid = uid
383
384     uid = property(
385         _get_uid, _set_uid, doc='The uid of the user.')
386
387     #
388     # encrypt/decrypt and sign/verify API
389     #
390
391     def encrypt(self, data, pubkey, passphrase=None, sign=None,
392                 cipher_algo='AES256'):
393         """
394         Encrypt C{data} using public @{key} and sign with C{sign} key.
395
396         :param data: The data to be encrypted.
397         :type data: str
398         :param pubkey: The key used to encrypt.
399         :type pubkey: EncryptionKey
400         :param passphrase: The passphrase for the secret key used for the
401                            signature.
402         :type passphrase: str
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, cipher_algo=cipher_algo)
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 passphrase: The passphrase for the secret key used for
426                            decryption.
427         :type passphrase: str
428         :param verify: The key used to verify a signature.
429         :type verify: OpenPGPKey
430
431         :return: The decrypted data.
432         :rtype: str
433
434         :raise InvalidSignature: Raised if unable to verify the signature with
435                                  C{verify} key.
436         """
437         leap_assert_type(privkey, EncryptionKey)
438         leap_assert(
439             privkey.__class__ in self._wrapper_map,
440             'Unknown key type.')
441         leap_assert(privkey.private is True, 'Key is not private.')
442         return self._wrapper_map[privkey.__class__].decrypt(
443             data, privkey, passphrase, verify)
444
445     def sign(self, data, privkey, digest_algo='SHA512', clearsign=False,
446              detach=True, binary=False):
447         """
448         Sign C{data} with C{privkey}.
449
450         :param data: The data to be signed.
451         :type data: str
452
453         :param privkey: The private key to be used to sign.
454         :type privkey: EncryptionKey
455         :param digest_algo: The hash digest to use.
456         :type digest_algo: str
457         :param clearsign: If True, create a cleartext signature.
458         :type clearsign: bool
459         :param detach: If True, create a detached signature.
460         :type detach: bool
461         :param binary: If True, do not ascii armour the output.
462         :type binary: bool
463
464         :return: The signed data.
465         :rtype: str
466         """
467         leap_assert_type(privkey, EncryptionKey)
468         leap_assert(
469             privkey.__class__ in self._wrapper_map,
470             'Unknown key type.')
471         leap_assert(privkey.private is True, 'Key is not private.')
472         return self._wrapper_map[privkey.__class__].sign(
473             data, privkey, digest_algo=digest_algo, clearsign=clearsign,
474             detach=detach, binary=binary)
475
476     def verify(self, data, pubkey, detached_sig=None):
477         """
478         Verify signed C{data} with C{pubkey}, eventually using
479         C{detached_sig}.
480
481         :param data: The data to be verified.
482         :type data: str
483         :param pubkey: The public key to be used on verification.
484         :type pubkey: EncryptionKey
485         :param detached_sig: A detached signature. If given, C{data} is
486                              verified using this detached signature.
487         :type detached_sig: str
488
489         :return: The signed data.
490         :rtype: str
491         """
492         leap_assert_type(pubkey, EncryptionKey)
493         leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.')
494         leap_assert(pubkey.private is False, 'Key is not public.')
495         return self._wrapper_map[pubkey.__class__].verify(
496             data, pubkey, detached_sig=detached_sig)
497
498     def delete_key(self, key):
499         """
500         Remove C{key} from storage.
501
502         May raise:
503             openpgp.errors.KeyNotFound
504             openpgp.errors.KeyAttributesDiffer
505
506         :param key: The key to be removed.
507         :type key: EncryptionKey
508         """
509         try:
510             self._wrapper_map[type(key)].delete_key(key)
511         except IndexError as e:
512             leap_assert(False, "Unsupported key type. Error {0!r}".format(e))
513
514     def put_key(self, key):
515         """
516         Put C{key} in local storage.
517
518         :param key: The key to be stored. It can be ascii key or an OpenPGPKey
519         :type key: str or OpenPGPKey
520         """
521         try:
522             if isinstance(key, basestring):
523                 self._wrapper_map[OpenPGPKey].put_ascii_key(key)
524             else:
525                 self._wrapper_map[type(key)].put_key(key)
526         except IndexError as e:
527             leap_assert(False, "Unsupported key type. Error {0!r}".format(e))
528
529 from ._version import get_versions
530 __version__ = get_versions()['version']
531 del get_versions