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