d98bd02d945cf5e0aab96983e215bc430ab79e4f
[leap_pycommon.git] / src / leap / common / 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 re
29
30
31 from hashlib import sha256
32 from abc import ABCMeta, abstractmethod
33 from leap.common.check import leap_assert
34
35
36 #
37 # Key handling utilities
38 #
39
40 def is_address(address):
41     """
42     Return whether the given C{address} is in the form user@provider.
43
44     @param address: The address to be tested.
45     @type address: str
46     @return: Whether C{address} is in the form user@provider.
47     @rtype: bool
48     """
49     return bool(re.match('[\w.-]+@[\w.-]+', address))
50
51
52 def build_key_from_dict(kClass, address, kdict):
53     """
54     Build an C{kClass} key bound to C{address} based on info in C{kdict}.
55
56     @param address: The address bound to the key.
57     @type address: str
58     @param kdict: Dictionary with key data.
59     @type kdict: dict
60     @return: An instance of the key.
61     @rtype: C{kClass}
62     """
63     leap_assert(address == kdict['address'], 'Wrong address in key data.')
64     return kClass(
65         address,
66         key_id=kdict['key_id'],
67         fingerprint=kdict['fingerprint'],
68         key_data=kdict['key_data'],
69         private=kdict['private'],
70         length=kdict['length'],
71         expiry_date=kdict['expiry_date'],
72         first_seen_at=kdict['first_seen_at'],
73         last_audited_at=kdict['last_audited_at'],
74         validation=kdict['validation'],  # TODO: verify for validation.
75     )
76
77
78 def keymanager_doc_id(ktype, address, private=False):
79     """
80     Return the document id for the document containing a key for
81     C{address}.
82
83     @param address: The type of the key.
84     @type address: KeyType
85     @param address: The address bound to the key.
86     @type address: str
87     @param private: Whether the key is private or not.
88     @type private: bool
89     @return: The document id for the document that stores a key bound to
90         C{address}.
91     @rtype: str
92     """
93     leap_assert(is_address(address), "Wrong address format: %s" % address)
94     ktype = str(ktype)
95     visibility = 'private' if private else 'public'
96     return sha256('keymanager-'+address+'-'+ktype+'-'+visibility).hexdigest()
97
98
99 #
100 # Abstraction for encryption keys
101 #
102
103 class EncryptionKey(object):
104     """
105     Abstract class for encryption keys.
106
107     A key is "validated" if the nicknym agent has bound the user address to a
108     public key. Nicknym supports three different levels of key validation:
109
110     * Level 3 - path trusted: A path of cryptographic signatures can be traced
111       from a trusted key to the key under evaluation. By default, only the
112       provider key from the user's provider is a "trusted key".
113     * level 2 - provider signed: The key has been signed by a provider key for
114       the same domain, but the provider key is not validated using a trust
115       path (i.e. it is only registered)
116     * level 1 - registered: The key has been encountered and saved, it has no
117       signatures (that are meaningful to the nicknym agent).
118     """
119
120     __metaclass__ = ABCMeta
121
122     def __init__(self, address, key_id=None, fingerprint=None,
123                  key_data=None, private=None, length=None, expiry_date=None,
124                  validation=None, first_seen_at=None, last_audited_at=None):
125         self.address = address
126         self.key_id = key_id
127         self.fingerprint = fingerprint
128         self.key_data = key_data
129         self.private = private
130         self.length = length
131         self.expiry_date = expiry_date
132         self.validation = validation
133         self.first_seen_at = first_seen_at
134         self.last_audited_at = last_audited_at
135
136     def get_json(self):
137         """
138         Return a JSON string describing this key.
139
140         @return: The JSON string describing this key.
141         @rtype: str
142         """
143         return json.dumps({
144             'address': self.address,
145             'type': str(self.__class__),
146             'key_id': self.key_id,
147             'fingerprint': self.fingerprint,
148             'key_data': self.key_data,
149             'private': self.private,
150             'length': self.length,
151             'expiry_date': self.expiry_date,
152             'validation': self.validation,
153             'first_seen_at': self.first_seen_at,
154             'last_audited_at': self.last_audited_at,
155             'tags': ['keymanager-key'],
156         })
157
158     def __repr__(self):
159         """
160         Representation of this class
161         """
162         return u"<%s 0x%s (%s - %s)>" % (
163             self.__class__.__name__,
164             self.key_id,
165             self.address,
166             "priv" if self.private else "publ")
167
168
169 #
170 # Encryption schemes
171 #
172
173 class EncryptionScheme(object):
174     """
175     Abstract class for Encryption Schemes.
176
177     A wrapper for a certain encryption schemes should know how to get and put
178     keys in local storage using Soledad, how to generate new keys and how to
179     find out about possibly encrypted content.
180     """
181
182     __metaclass__ = ABCMeta
183
184     def __init__(self, soledad):
185         """
186         Initialize this Encryption Scheme.
187
188         @param soledad: A Soledad instance for local storage of keys.
189         @type soledad: leap.soledad.Soledad
190         """
191         self._soledad = soledad
192
193     @abstractmethod
194     def get_key(self, address, private=False):
195         """
196         Get key from local storage.
197
198         @param address: The address bound to the key.
199         @type address: str
200         @param private: Look for a private key instead of a public one?
201         @type private: bool
202
203         @return: The key bound to C{address}.
204         @rtype: EncryptionKey
205         @raise KeyNotFound: If the key was not found on local storage.
206         """
207         pass
208
209     @abstractmethod
210     def put_key(self, key):
211         """
212         Put a key in local storage.
213
214         @param key: The key to be stored.
215         @type key: EncryptionKey
216         """
217         pass
218
219     @abstractmethod
220     def gen_key(self, address):
221         """
222         Generate a new key.
223
224         @param address: The address bound to the key.
225         @type address: str
226
227         @return: The key bound to C{address}.
228         @rtype: EncryptionKey
229         """
230         pass
231
232     @abstractmethod
233     def delete_key(self, key):
234         """
235         Remove C{key} from storage.
236
237         @param key: The key to be removed.
238         @type key: EncryptionKey
239         """
240         pass