1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2013 LEAP
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.
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.
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/>.
20 Key Manager is a Nicknym agent for LEAP client.
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
30 from leap.keymanager.errors import KeyNotFound
32 from leap.keymanager.keys import (
38 from leap.keymanager.openpgp import (
43 logger = logging.getLogger(__name__)
50 class KeyManager(object):
53 # server's key storage constants
56 OPENPGP_KEY = 'openpgp'
57 PUBKEY_KEY = "user[public_key]"
59 def __init__(self, address, nickserver_uri, soledad, session_id=None,
60 ca_cert_path=None, api_uri=None, api_version=None, uid=None,
63 Initialize a Key Manager for user's C{address} with provider's
64 nickserver reachable in C{url}.
66 :param address: The address of the user of this Key Manager.
68 :param url: The URL of the nickserver.
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.
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.
78 :param api_version: The version of the webapp API.
79 :type api_version: str
80 :param uid: The users' UID.
82 :param gpgbinary: Name for GnuPG binary executable.
83 :type gpgbinary: C{str}
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
93 # a dict to map key types to their handlers
95 OpenPGPKey: OpenPGPScheme(soledad, gpgbinary=gpgbinary),
96 # other types of key will be added to this mapper.
98 # the following are used to perform https requests
99 self._fetcher = requests
100 self._session = self._fetcher.session()
106 def _key_class_from_type(self, ktype):
108 Return key class from string representation of key type.
111 lambda klass: str(klass) == ktype,
112 self._wrapper_map).pop()
114 def _get(self, uri, data=None):
116 Send a GET request to C{uri} containing C{data}.
118 :param uri: The URI of the request.
120 :param data: The body of the request.
121 :type data: dict, str or file
123 :return: The response to the request.
124 :rtype: requests.Response
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()
133 res.headers['content-type'].startswith('application/json'),
134 'Content-type is not JSON.')
137 def _put(self, uri, data=None):
139 Send a PUT request to C{uri} containing C{data}.
141 The request will be sent using the configured CA certificate path to
142 verify the server certificate and the configured session id for
145 :param uri: The URI of the request.
147 :param data: The body of the request.
148 :type data: dict, str or file
150 :return: The response to the request.
151 :rtype: requests.Response
154 self._ca_cert_path is not None,
155 'We need the CA certificate path!')
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()
166 def _fetch_keys_from_server(self, address):
168 Fetch keys bound to C{address} from nickserver and insert them in
171 :param address: The address bound to the keys.
174 @raise KeyNotFound: If the key was not found on nickserver.
176 # request keys from the nickserver
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,))
188 logger.warning("%s" % (res.content,))
194 def send_key(self, ktype):
196 Send user's key of type C{ktype} to provider.
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.
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.
207 :param ktype: The type of the key.
210 @raise KeyNotFound: If the key was not found in local database.
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)
219 self.PUBKEY_KEY: pubkey.key_data
221 uri = "%s/%s/users/%s.json" % (
226 signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)
228 def get_key(self, address, ktype, private=False, fetch_remote=True):
230 Return a key of type C{ktype} bound to C{address}.
232 First, search for the key in local storage. If it is not available,
233 then try to fetch from nickserver.
235 :param address: The address bound to the key.
237 :param ktype: The type of the key.
239 :param private: Look for a private key instead of a public one?
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
248 ktype in self._wrapper_map,
249 'Unkown key type: %s.' % str(ktype))
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)
258 signal(proto.KEYMANAGER_KEY_NOT_FOUND, address)
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:
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)
272 def get_all_keys_in_local_db(self, private=False):
274 Return all keys stored in local database.
276 :return: A list with all keys in local db.
280 lambda doc: build_key_from_dict(
281 self._key_class_from_type(doc.content['type']),
282 doc.content['address'],
284 self._soledad.get_from_index(
287 '1' if private else '0'))
289 def refresh_keys(self):
291 Fetch keys from nickserver and update them locally.
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:
300 self._fetch_keys_from_server(address)
302 def gen_key(self, ktype):
304 Generate a key of type C{ktype} bound to the user's address.
306 :param ktype: The type of the key.
309 :return: The generated key.
310 :rtype: EncryptionKey
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)
322 def _get_session_id(self):
323 return self._session_id
325 def _set_session_id(self, session_id):
326 self._session_id = session_id
328 session_id = property(
329 _get_session_id, _set_session_id, doc='The session id.')
331 def _get_ca_cert_path(self):
332 return self._ca_cert_path
334 def _set_ca_cert_path(self, ca_cert_path):
335 self._ca_cert_path = ca_cert_path
337 ca_cert_path = property(
338 _get_ca_cert_path, _set_ca_cert_path,
339 doc='The path to the CA certificate.')
341 def _get_api_uri(self):
344 def _set_api_uri(self, api_uri):
345 self._api_uri = api_uri
348 _get_api_uri, _set_api_uri, doc='The webapp API URI.')
350 def _get_api_version(self):
351 return self._api_version
353 def _set_api_version(self, api_version):
354 self._api_version = api_version
356 api_version = property(
357 _get_api_version, _set_api_version, doc='The webapp API version.')
362 def _set_uid(self, uid):
366 _get_uid, _set_uid, doc='The uid of the user.')
369 # encrypt/decrypt and sign/verify API
372 def encrypt(self, data, pubkey, passphrase=None, sign=None):
374 Encrypt C{data} using public @{key} and sign with C{sign} key.
376 :param data: The data to be encrypted.
378 :param pubkey: The key used to encrypt.
379 :type pubkey: EncryptionKey
380 :param sign: The key used for signing.
381 :type sign: EncryptionKey
383 :return: The encrypted data.
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)
392 def decrypt(self, data, privkey, passphrase=None, verify=None):
394 Decrypt C{data} using private @{privkey} and verify with C{verify} key.
396 :param data: The data to be decrypted.
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
403 :return: The decrypted data.
406 @raise InvalidSignature: Raised if unable to verify the signature with
409 leap_assert_type(privkey, EncryptionKey)
411 privkey.__class__ in self._wrapper_map,
413 leap_assert(privkey.private is True, 'Key is not private.')
414 return self._wrapper_map[privkey.__class__].decrypt(
415 data, privkey, passphrase, verify)
417 def sign(self, data, privkey):
419 Sign C{data} with C{privkey}.
421 :param data: The data to be signed.
424 :param privkey: The private key to be used to sign.
425 :type privkey: EncryptionKey
427 :return: The signed data.
430 leap_assert_type(privkey, EncryptionKey)
432 privkey.__class__ in self._wrapper_map,
434 leap_assert(privkey.private is True, 'Key is not private.')
435 return self._wrapper_map[privkey.__class__].sign(data, privkey)
437 def verify(self, data, pubkey):
439 Verify signed C{data} with C{pubkey}.
441 :param data: The data to be verified.
444 :param pubkey: The public key to be used on verification.
445 :type pubkey: EncryptionKey
447 :return: The signed data.
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)