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