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