Port validation levels to enum34
[keymanager.git] / src / leap / keymanager / keys.py
1 # -*- coding: utf-8 -*-
2 # keys.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 Abstact key type and encryption scheme representations.
21 """
22
23
24 try:
25     import simplejson as json
26 except ImportError:
27     import json  # noqa
28 import logging
29 import re
30 import time
31
32
33 from abc import ABCMeta, abstractmethod
34 from datetime import datetime
35 from leap.common.check import leap_assert
36 from twisted.internet import defer
37
38 from leap.keymanager.validation import ValidationLevel, toValidationLevel
39
40 logger = logging.getLogger(__name__)
41
42
43 #
44 # Dictionary keys used for storing cryptographic keys.
45 #
46
47 KEY_ADDRESS_KEY = 'address'
48 KEY_TYPE_KEY = 'type'
49 KEY_ID_KEY = 'key_id'
50 KEY_FINGERPRINT_KEY = 'fingerprint'
51 KEY_DATA_KEY = 'key_data'
52 KEY_PRIVATE_KEY = 'private'
53 KEY_LENGTH_KEY = 'length'
54 KEY_EXPIRY_DATE_KEY = 'expiry_date'
55 KEY_LAST_AUDITED_AT_KEY = 'last_audited_at'
56 KEY_REFRESHED_AT_KEY = 'refreshed_at'
57 KEY_VALIDATION_KEY = 'validation'
58 KEY_ENCR_USED_KEY = 'encr_used'
59 KEY_SIGN_USED_KEY = 'sign_used'
60 KEY_TAGS_KEY = 'tags'
61
62
63 #
64 # Key storage constants
65 #
66
67 KEYMANAGER_KEY_TAG = 'keymanager-key'
68 KEYMANAGER_ACTIVE_TAG = 'keymanager-active'
69 KEYMANAGER_ACTIVE_TYPE = '-active'
70
71
72 #
73 # key indexing constants.
74 #
75
76 TAGS_PRIVATE_INDEX = 'by-tags-private'
77 TYPE_ID_PRIVATE_INDEX = 'by-type-id-private'
78 TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private'
79 INDEXES = {
80     TAGS_PRIVATE_INDEX: [
81         KEY_TAGS_KEY,
82         'bool(%s)' % KEY_PRIVATE_KEY,
83     ],
84     TYPE_ID_PRIVATE_INDEX: [
85         KEY_TYPE_KEY,
86         KEY_ID_KEY,
87         'bool(%s)' % KEY_PRIVATE_KEY,
88     ],
89     TYPE_ADDRESS_PRIVATE_INDEX: [
90         KEY_TYPE_KEY,
91         KEY_ADDRESS_KEY,
92         'bool(%s)' % KEY_PRIVATE_KEY,
93     ]
94 }
95
96
97 #
98 # Key handling utilities
99 #
100
101 def is_address(address):
102     """
103     Return whether the given C{address} is in the form user@provider.
104
105     :param address: The address to be tested.
106     :type address: str
107     :return: Whether C{address} is in the form user@provider.
108     :rtype: bool
109     """
110     return bool(re.match('[\w.-]+@[\w.-]+', address))
111
112
113 def build_key_from_dict(kClass, kdict):
114     """
115     Build an C{kClass} key based on info in C{kdict}.
116
117     :param kdict: Dictionary with key data.
118     :type kdict: dict
119     :return: An instance of the key.
120     :rtype: C{kClass}
121     """
122     try:
123         validation = toValidationLevel(kdict[KEY_VALIDATION_KEY])
124     except ValueError:
125         logger.error("Not valid validation level (%s) for key %s",
126                      (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY]))
127         validation = ValidationLevel.Weak_Chain
128
129     expiry_date = _to_datetime(kdict[KEY_EXPIRY_DATE_KEY])
130     last_audited_at = _to_datetime(kdict[KEY_LAST_AUDITED_AT_KEY])
131     refreshed_at = _to_datetime(kdict[KEY_REFRESHED_AT_KEY])
132
133     return kClass(
134         kdict[KEY_ADDRESS_KEY],
135         key_id=kdict[KEY_ID_KEY],
136         fingerprint=kdict[KEY_FINGERPRINT_KEY],
137         key_data=kdict[KEY_DATA_KEY],
138         private=kdict[KEY_PRIVATE_KEY],
139         length=kdict[KEY_LENGTH_KEY],
140         expiry_date=expiry_date,
141         last_audited_at=last_audited_at,
142         refreshed_at=refreshed_at,
143         validation=validation,
144         encr_used=kdict[KEY_ENCR_USED_KEY],
145         sign_used=kdict[KEY_SIGN_USED_KEY],
146     )
147
148
149 def _to_datetime(unix_time):
150     if unix_time != 0:
151         return datetime.fromtimestamp(unix_time)
152     else:
153         return None
154
155
156 def _to_unix_time(date):
157     if date is not None:
158         return int(time.mktime(date.timetuple()))
159     else:
160         return 0
161
162
163 #
164 # Abstraction for encryption keys
165 #
166
167 class EncryptionKey(object):
168     """
169     Abstract class for encryption keys.
170
171     A key is "validated" if the nicknym agent has bound the user address to a
172     public key.
173     """
174
175     __metaclass__ = ABCMeta
176
177     def __init__(self, address, key_id="", fingerprint="",
178                  key_data="", private=False, length=0, expiry_date=None,
179                  validation=ValidationLevel.Weak_Chain, last_audited_at=None,
180                  refreshed_at=None, encr_used=False, sign_used=False):
181         self.address = address
182         self.key_id = key_id
183         self.fingerprint = fingerprint
184         self.key_data = key_data
185         self.private = private
186         self.length = length
187         self.expiry_date = expiry_date
188         self.validation = validation
189         self.last_audited_at = last_audited_at
190         self.refreshed_at = refreshed_at
191         self.encr_used = encr_used
192         self.sign_used = sign_used
193
194     def get_json(self):
195         """
196         Return a JSON string describing this key.
197
198         :return: The JSON string describing this key.
199         :rtype: str
200         """
201         expiry_date = _to_unix_time(self.expiry_date)
202         last_audited_at = _to_unix_time(self.last_audited_at)
203         refreshed_at = _to_unix_time(self.refreshed_at)
204
205         return json.dumps({
206             KEY_ADDRESS_KEY: self.address,
207             KEY_TYPE_KEY: self.__class__.__name__,
208             KEY_ID_KEY: self.key_id,
209             KEY_FINGERPRINT_KEY: self.fingerprint,
210             KEY_DATA_KEY: self.key_data,
211             KEY_PRIVATE_KEY: self.private,
212             KEY_LENGTH_KEY: self.length,
213             KEY_EXPIRY_DATE_KEY: expiry_date,
214             KEY_LAST_AUDITED_AT_KEY: last_audited_at,
215             KEY_REFRESHED_AT_KEY: refreshed_at,
216             KEY_VALIDATION_KEY: self.validation.name,
217             KEY_ENCR_USED_KEY: self.encr_used,
218             KEY_SIGN_USED_KEY: self.sign_used,
219             KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG],
220         })
221
222     def get_active_json(self, address):
223         """
224         Return a JSON string describing this key.
225
226         :param address: Address for wich the key is active
227         :type address: str
228         :return: The JSON string describing this key.
229         :rtype: str
230         """
231         return json.dumps({
232             KEY_ADDRESS_KEY: address,
233             KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE,
234             KEY_ID_KEY: self.key_id,
235             KEY_PRIVATE_KEY: self.private,
236             KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG],
237         })
238
239     def __repr__(self):
240         """
241         Representation of this class
242         """
243         return u"<%s 0x%s (%s - %s)>" % (
244             self.__class__.__name__,
245             self.key_id,
246             self.address,
247             "priv" if self.private else "publ")
248
249
250 #
251 # Encryption schemes
252 #
253
254 class EncryptionScheme(object):
255     """
256     Abstract class for Encryption Schemes.
257
258     A wrapper for a certain encryption schemes should know how to get and put
259     keys in local storage using Soledad, how to generate new keys and how to
260     find out about possibly encrypted content.
261     """
262
263     __metaclass__ = ABCMeta
264
265     def __init__(self, soledad):
266         """
267         Initialize this Encryption Scheme.
268
269         :param soledad: A Soledad instance for local storage of keys.
270         :type soledad: leap.soledad.Soledad
271         """
272         self._soledad = soledad
273         self._init_indexes()
274
275     def _init_indexes(self):
276         """
277         Initialize the database indexes.
278         """
279         leap_assert(self._soledad is not None,
280                     "Cannot init indexes with null soledad")
281
282         def init_idexes(indexes):
283             deferreds = []
284             db_indexes = dict(indexes)
285             # Loop through the indexes we expect to find.
286             for name, expression in INDEXES.items():
287                 if name not in db_indexes:
288                     # The index does not yet exist.
289                     d = self._soledad.create_index(name, *expression)
290                     deferreds.append(d)
291                 elif expression != db_indexes[name]:
292                     # The index exists but the definition is not what expected,
293                     # so we delete it and add the proper index expression.
294                     d = self._soledad.delete_index(name)
295                     d.addCallback(
296                         lambda _:
297                             self._soledad.create_index(name, *expression))
298                     deferreds.append(d)
299             return defer.gatherResults(deferreds, consumeErrors=True)
300
301         self.deferred_indexes = self._soledad.list_indexes()
302         self.deferred_indexes.addCallback(init_idexes)
303
304     def _wait_indexes(self, *methods):
305         """
306         Methods that need to wait for the indexes to be ready.
307
308         Heavily based on
309         http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/
310
311         :param methods: methods that need to wait for the indexes to be ready
312         :type methods: tuple(str)
313         """
314         self.waiting = []
315         self.stored = {}
316
317         def restore(_):
318             for method in self.stored:
319                 setattr(self, method, self.stored[method])
320             for d in self.waiting:
321                 d.callback(None)
322
323         def makeWrapper(method):
324             def wrapper(*args, **kw):
325                 d = defer.Deferred()
326                 d.addCallback(lambda _: self.stored[method](*args, **kw))
327                 self.waiting.append(d)
328                 return d
329             return wrapper
330
331         for method in methods:
332             self.stored[method] = getattr(self, method)
333             setattr(self, method, makeWrapper(method))
334
335         self.deferred_indexes.addCallback(restore)
336
337     @abstractmethod
338     def get_key(self, address, private=False):
339         """
340         Get key from local storage.
341
342         :param address: The address bound to the key.
343         :type address: str
344         :param private: Look for a private key instead of a public one?
345         :type private: bool
346
347         :return: A Deferred which fires with the EncryptionKey bound to
348                  address, or which fails with KeyNotFound if the key was not
349                  found on local storage.
350         :rtype: Deferred
351         """
352         pass
353
354     @abstractmethod
355     def put_key(self, key, address):
356         """
357         Put a key in local storage.
358
359         :param key: The key to be stored.
360         :type key: EncryptionKey
361         :param address: address for which this key will be active.
362         :type address: str
363
364         :return: A Deferred which fires when the key is in the storage.
365         :rtype: Deferred
366         """
367         pass
368
369     @abstractmethod
370     def gen_key(self, address):
371         """
372         Generate a new key.
373
374         :param address: The address bound to the key.
375         :type address: str
376
377         :return: The key bound to C{address}.
378         :rtype: EncryptionKey
379         """
380         pass
381
382     @abstractmethod
383     def delete_key(self, key):
384         """
385         Remove C{key} from storage.
386
387         :param key: The key to be removed.
388         :type key: EncryptionKey
389
390         :return: A Deferred which fires when the key is deleted, or which
391                  fails with KeyNotFound if the key was not found on local
392                  storage.
393         :rtype: Deferred
394         """
395         pass
396
397     @abstractmethod
398     def encrypt(self, data, pubkey, passphrase=None, sign=None):
399         """
400         Encrypt C{data} using public @{pubkey} and sign with C{sign} key.
401
402         :param data: The data to be encrypted.
403         :type data: str
404         :param pubkey: The key used to encrypt.
405         :type pubkey: EncryptionKey
406         :param sign: The key used for signing.
407         :type sign: EncryptionKey
408
409         :return: The encrypted data.
410         :rtype: str
411         """
412         pass
413
414     @abstractmethod
415     def decrypt(self, data, privkey, passphrase=None, verify=None):
416         """
417         Decrypt C{data} using private @{privkey} and verify with C{verify} key.
418
419         :param data: The data to be decrypted.
420         :type data: str
421         :param privkey: The key used to decrypt.
422         :type privkey: OpenPGPKey
423         :param verify: The key used to verify a signature.
424         :type verify: OpenPGPKey
425
426         :return: The decrypted data and if signature verifies
427         :rtype: (unicode, bool)
428
429         :raise DecryptError: Raised if failed decrypting for some reason.
430         """
431         pass
432
433     @abstractmethod
434     def sign(self, data, privkey):
435         """
436         Sign C{data} with C{privkey}.
437
438         :param data: The data to be signed.
439         :type data: str
440
441         :param privkey: The private key to be used to sign.
442         :type privkey: EncryptionKey
443
444         :return: The signed data.
445         :rtype: str
446         """
447         pass
448
449     @abstractmethod
450     def verify(self, data, pubkey, detached_sig=None):
451         """
452         Verify signed C{data} with C{pubkey}, eventually using
453         C{detached_sig}.
454
455         :param data: The data to be verified.
456         :type data: str
457         :param pubkey: The public key to be used on verification.
458         :type pubkey: EncryptionKey
459         :param detached_sig: A detached signature. If given, C{data} is
460                              verified against this sdetached signature.
461         :type detached_sig: str
462
463         :return: signature matches
464         :rtype: bool
465         """
466         pass