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