Use datetime for key expiration
[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
31
32 from abc import ABCMeta, abstractmethod
33 from datetime import datetime
34 from leap.common.check import leap_assert
35
36 from leap.keymanager.validation import ValidationLevel, toValidationLevel
37
38 logger = logging.getLogger(__name__)
39
40
41 #
42 # Dictionary keys used for storing cryptographic keys.
43 #
44
45 KEY_ADDRESS_KEY = 'address'
46 KEY_TYPE_KEY = 'type'
47 KEY_ID_KEY = 'key_id'
48 KEY_FINGERPRINT_KEY = 'fingerprint'
49 KEY_DATA_KEY = 'key_data'
50 KEY_PRIVATE_KEY = 'private'
51 KEY_LENGTH_KEY = 'length'
52 KEY_EXPIRY_DATE_KEY = 'expiry_date'
53 KEY_FIRST_SEEN_AT_KEY = 'first_seen_at'
54 KEY_LAST_AUDITED_AT_KEY = 'last_audited_at'
55 KEY_VALIDATION_KEY = 'validation'
56 KEY_TAGS_KEY = 'tags'
57
58
59 #
60 # Key storage constants
61 #
62
63 KEYMANAGER_KEY_TAG = 'keymanager-key'
64
65
66 #
67 # key indexing constants.
68 #
69
70 TAGS_PRIVATE_INDEX = 'by-tags-private'
71 TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-private'
72 INDEXES = {
73     TAGS_PRIVATE_INDEX: [
74         KEY_TAGS_KEY,
75         'bool(%s)' % KEY_PRIVATE_KEY,
76     ],
77     TAGS_ADDRESS_PRIVATE_INDEX: [
78         KEY_TAGS_KEY,
79         KEY_ADDRESS_KEY,
80         'bool(%s)' % KEY_PRIVATE_KEY,
81     ]
82 }
83
84
85 #
86 # Key handling utilities
87 #
88
89 def is_address(address):
90     """
91     Return whether the given C{address} is in the form user@provider.
92
93     :param address: The address to be tested.
94     :type address: str
95     :return: Whether C{address} is in the form user@provider.
96     :rtype: bool
97     """
98     return bool(re.match('[\w.-]+@[\w.-]+', address))
99
100
101 def build_key_from_dict(kClass, address, kdict):
102     """
103     Build an C{kClass} key bound to C{address} based on info in C{kdict}.
104
105     :param address: The address bound to the key.
106     :type address: str
107     :param kdict: Dictionary with key data.
108     :type kdict: dict
109     :return: An instance of the key.
110     :rtype: C{kClass}
111     """
112     leap_assert(
113         address == kdict[KEY_ADDRESS_KEY],
114         'Wrong address in key data.')
115     try:
116         validation = toValidationLevel(kdict[KEY_VALIDATION_KEY])
117     except ValueError:
118         logger.error("Not valid validation level (%s) for key %s",
119                      (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY]))
120         validation = ValidationLevel.Weak_Chain
121
122     expiry_date = None
123     if kdict[KEY_EXPIRY_DATE_KEY]:
124         expiry_date = datetime.fromtimestamp(int(kdict[KEY_EXPIRY_DATE_KEY]))
125
126     return kClass(
127         address,
128         key_id=kdict[KEY_ID_KEY],
129         fingerprint=kdict[KEY_FINGERPRINT_KEY],
130         key_data=kdict[KEY_DATA_KEY],
131         private=kdict[KEY_PRIVATE_KEY],
132         length=kdict[KEY_LENGTH_KEY],
133         expiry_date=expiry_date,
134         first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY],
135         last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY],
136         validation=validation,
137     )
138
139
140 #
141 # Abstraction for encryption keys
142 #
143
144 class EncryptionKey(object):
145     """
146     Abstract class for encryption keys.
147
148     A key is "validated" if the nicknym agent has bound the user address to a
149     public key.
150     """
151
152     __metaclass__ = ABCMeta
153
154     def __init__(self, address, key_id=None, fingerprint=None,
155                  key_data=None, private=None, length=None, expiry_date=None,
156                  validation=None, first_seen_at=None, last_audited_at=None):
157         self.address = address
158         self.key_id = key_id
159         self.fingerprint = fingerprint
160         self.key_data = key_data
161         self.private = private
162         self.length = length
163         self.expiry_date = expiry_date
164         self.validation = validation
165         self.first_seen_at = first_seen_at
166         self.last_audited_at = last_audited_at
167
168     def get_json(self):
169         """
170         Return a JSON string describing this key.
171
172         :return: The JSON string describing this key.
173         :rtype: str
174         """
175         expiry_str = ""
176         if self.expiry_date is not None:
177             expiry_str = self.expiry_date.strftime("%s")
178
179         return json.dumps({
180             KEY_ADDRESS_KEY: self.address,
181             KEY_TYPE_KEY: str(self.__class__),
182             KEY_ID_KEY: self.key_id,
183             KEY_FINGERPRINT_KEY: self.fingerprint,
184             KEY_DATA_KEY: self.key_data,
185             KEY_PRIVATE_KEY: self.private,
186             KEY_LENGTH_KEY: self.length,
187             KEY_EXPIRY_DATE_KEY: expiry_str,
188             KEY_VALIDATION_KEY: str(self.validation),
189             KEY_FIRST_SEEN_AT_KEY: self.first_seen_at,
190             KEY_LAST_AUDITED_AT_KEY: self.last_audited_at,
191             KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG],
192         })
193
194     def __repr__(self):
195         """
196         Representation of this class
197         """
198         return u"<%s 0x%s (%s - %s)>" % (
199             self.__class__.__name__,
200             self.key_id,
201             self.address,
202             "priv" if self.private else "publ")
203
204
205 #
206 # Encryption schemes
207 #
208
209 class EncryptionScheme(object):
210     """
211     Abstract class for Encryption Schemes.
212
213     A wrapper for a certain encryption schemes should know how to get and put
214     keys in local storage using Soledad, how to generate new keys and how to
215     find out about possibly encrypted content.
216     """
217
218     __metaclass__ = ABCMeta
219
220     def __init__(self, soledad):
221         """
222         Initialize this Encryption Scheme.
223
224         :param soledad: A Soledad instance for local storage of keys.
225         :type soledad: leap.soledad.Soledad
226         """
227         self._soledad = soledad
228         self._init_indexes()
229
230     def _init_indexes(self):
231         """
232         Initialize the database indexes.
233         """
234         leap_assert(self._soledad is not None,
235                     "Cannot init indexes with null soledad")
236         # Ask the database for currently existing indexes.
237         db_indexes = dict(self._soledad.list_indexes())
238         # Loop through the indexes we expect to find.
239         for name, expression in INDEXES.items():
240             if name not in db_indexes:
241                 # The index does not yet exist.
242                 self._soledad.create_index(name, *expression)
243                 continue
244             if expression == db_indexes[name]:
245                 # The index exists and is up to date.
246                 continue
247             # The index exists but the definition is not what expected, so we
248             # delete it and add the proper index expression.
249             self._soledad.delete_index(name)
250             self._soledad.create_index(name, *expression)
251
252     @abstractmethod
253     def get_key(self, address, private=False):
254         """
255         Get key from local storage.
256
257         :param address: The address bound to the key.
258         :type address: str
259         :param private: Look for a private key instead of a public one?
260         :type private: bool
261
262         :return: The key bound to C{address}.
263         :rtype: EncryptionKey
264         @raise KeyNotFound: If the key was not found on local storage.
265         """
266         pass
267
268     @abstractmethod
269     def put_key(self, key):
270         """
271         Put a key in local storage.
272
273         :param key: The key to be stored.
274         :type key: EncryptionKey
275         """
276         pass
277
278     @abstractmethod
279     def gen_key(self, address):
280         """
281         Generate a new key.
282
283         :param address: The address bound to the key.
284         :type address: str
285
286         :return: The key bound to C{address}.
287         :rtype: EncryptionKey
288         """
289         pass
290
291     @abstractmethod
292     def delete_key(self, key):
293         """
294         Remove C{key} from storage.
295
296         :param key: The key to be removed.
297         :type key: EncryptionKey
298         """
299         pass
300
301     @abstractmethod
302     def encrypt(self, data, pubkey, passphrase=None, sign=None):
303         """
304         Encrypt C{data} using public @{pubkey} and sign with C{sign} key.
305
306         :param data: The data to be encrypted.
307         :type data: str
308         :param pubkey: The key used to encrypt.
309         :type pubkey: EncryptionKey
310         :param sign: The key used for signing.
311         :type sign: EncryptionKey
312
313         :return: The encrypted data.
314         :rtype: str
315         """
316         pass
317
318     @abstractmethod
319     def decrypt(self, data, privkey, passphrase=None, verify=None):
320         """
321         Decrypt C{data} using private @{privkey} and verify with C{verify} key.
322
323         :param data: The data to be decrypted.
324         :type data: str
325         :param privkey: The key used to decrypt.
326         :type privkey: OpenPGPKey
327         :param verify: The key used to verify a signature.
328         :type verify: OpenPGPKey
329
330         :return: The decrypted data.
331         :rtype: str
332
333         @raise InvalidSignature: Raised if unable to verify the signature with
334             C{verify} key.
335         """
336         pass
337
338     @abstractmethod
339     def sign(self, data, privkey):
340         """
341         Sign C{data} with C{privkey}.
342
343         :param data: The data to be signed.
344         :type data: str
345
346         :param privkey: The private key to be used to sign.
347         :type privkey: EncryptionKey
348
349         :return: The signed data.
350         :rtype: str
351         """
352         pass
353
354     @abstractmethod
355     def verify(self, data, pubkey, detached_sig=None):
356         """
357         Verify signed C{data} with C{pubkey}, eventually using
358         C{detached_sig}.
359
360         :param data: The data to be verified.
361         :type data: str
362         :param pubkey: The public key to be used on verification.
363         :type pubkey: EncryptionKey
364         :param detached_sig: A detached signature. If given, C{data} is
365                              verified against this sdetached signature.
366         :type detached_sig: str
367
368         :return: The signed data.
369         :rtype: str
370         """
371         pass