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.
25 from leap.common.check import leap_assert
26 from leap.keymanager.errors import (
30 from leap.keymanager.keys import (
35 from leap.keymanager.openpgp import (
45 class KeyManager(object):
48 # server's key storage constants
51 OPENPGP_KEY = 'openpgp'
52 PUBKEY_KEY = "user[public_key]"
54 def __init__(self, address, nickserver_uri, soledad, session_id=None,
55 ca_cert_path=None, api_uri=None, api_version=None, uid=None):
57 Initialize a Key Manager for user's C{address} with provider's
58 nickserver reachable in C{url}.
60 :param address: The address of the user of this Key Manager.
62 :param url: The URL of the nickserver.
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.
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.
72 :param api_version: The version of the webapp API.
73 :type api_version: str
74 :param uid: The users' UID.
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
85 # a dict to map key types to their handlers
87 OpenPGPKey: OpenPGPScheme(soledad),
88 # other types of key will be added to this mapper.
90 # the following are used to perform https requests
91 self._fetcher = requests
92 self._session = self._fetcher.session()
98 def _key_class_from_type(self, ktype):
100 Return key class from string representation of key type.
103 lambda klass: str(klass) == ktype,
104 self._wrapper_map).pop()
106 def _get(self, uri, data=None):
108 Send a GET request to C{uri} containing C{data}.
110 :param uri: The URI of the request.
112 :param data: The body of the request.
113 :type data: dict, str or file
115 :return: The response to the request.
116 :rtype: requests.Response
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()
125 res.headers['content-type'].startswith('application/json'),
126 'Content-type is not JSON.')
129 def _put(self, uri, data=None):
131 Send a PUT request to C{uri} containing C{data}.
133 The request will be sent using the configured CA certificate path to
134 verify the server certificate and the configured session id for
137 :param uri: The URI of the request.
139 :param data: The body of the request.
140 :type data: dict, str or file
142 :return: The response to the request.
143 :rtype: requests.Response
146 self._ca_cert_path is not None,
147 'We need the CA certificate path!')
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()
158 def _fetch_keys_from_server(self, address):
160 Fetch keys bound to C{address} from nickserver and insert them in
163 :param address: The address bound to the keys.
166 @raise KeyNotFound: If the key was not found on nickserver.
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'])
180 def send_key(self, ktype):
182 Send user's key of type C{ktype} to provider.
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.
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.
193 :param ktype: The type of the key.
196 @raise KeyNotFound: If the key was not found in local database.
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)
205 self.PUBKEY_KEY: pubkey.key_data
207 uri = "%s/%s/users/%s.json" % (
213 def get_key(self, address, ktype, private=False, fetch_remote=True):
215 Return a key of type C{ktype} bound to C{address}.
217 First, search for the key in local storage. If it is not available,
218 then try to fetch from nickserver.
220 :param address: The address bound to the key.
222 :param ktype: The type of the key.
224 :param private: Look for a private key instead of a public one?
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
233 ktype in self._wrapper_map,
234 'Unkown key type: %s.' % str(ktype))
236 # return key if it exists in local database
237 return self._wrapper_map[ktype].get_key(address, private=private)
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:
243 self._fetch_keys_from_server(address)
244 return self._wrapper_map[ktype].get_key(address, private=False)
246 def get_all_keys_in_local_db(self, private=False):
248 Return all keys stored in local database.
250 :return: A list with all keys in local db.
254 lambda doc: build_key_from_dict(
255 self._key_class_from_type(doc.content['type']),
256 doc.content['address'],
258 self._soledad.get_from_index(
261 '1' if private else '0'))
263 def refresh_keys(self):
265 Fetch keys from nickserver and update them locally.
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:
274 self._fetch_keys_from_server(address)
276 def gen_key(self, ktype):
278 Generate a key of type C{ktype} bound to the user's address.
280 :param ktype: The type of the key.
283 :return: The generated key.
284 :rtype: EncryptionKey
286 return self._wrapper_map[ktype].gen_key(self._address)
292 def _get_session_id(self):
293 return self._session_id
295 def _set_session_id(self, session_id):
296 self._session_id = session_id
298 session_id = property(
299 _get_session_id, _set_session_id, doc='The session id.')
301 def _get_ca_cert_path(self):
302 return self._ca_cert_path
304 def _set_ca_cert_path(self, ca_cert_path):
305 self._ca_cert_path = ca_cert_path
307 ca_cert_path = property(
308 _get_ca_cert_path, _set_ca_cert_path,
309 doc='The path to the CA certificate.')
311 def _get_api_uri(self):
314 def _set_api_uri(self, api_uri):
315 self._api_uri = api_uri
318 _get_api_uri, _set_api_uri, doc='The webapp API URI.')
320 def _get_api_version(self):
321 return self._api_version
323 def _set_api_version(self, api_version):
324 self._api_version = api_version
326 api_version = property(
327 _get_api_version, _set_api_version, doc='The webapp API version.')
332 def _set_uid(self, uid):
336 _get_uid, _set_uid, doc='The uid of the user.')