[feat] defer decrypt, gen_key and encrypt
[keymanager.git] / src / leap / keymanager / tests / test_openpgp.py
1 # -*- coding: utf-8 -*-
2 # test_keymanager.py
3 # Copyright (C) 2014 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 Tests for the OpenPGP support on Key Manager.
21 """
22
23
24 from datetime import datetime
25 from mock import Mock
26 from twisted.internet.defer import inlineCallbacks, gatherResults, succeed
27
28 from leap.keymanager import (
29     KeyNotFound,
30     openpgp,
31 )
32 from leap.keymanager.keys import TYPE_ID_PRIVATE_INDEX
33 from leap.keymanager.openpgp import OpenPGPKey
34 from leap.keymanager.tests import (
35     KeyManagerWithSoledadTestCase,
36     ADDRESS,
37     ADDRESS_2,
38     KEY_FINGERPRINT,
39     PUBLIC_KEY,
40     KEY_ID,
41     PUBLIC_KEY_2,
42     PRIVATE_KEY,
43     PRIVATE_KEY_2,
44 )
45
46
47 class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase):
48
49     # set the trial timeout to 20min, needed by the key generation test
50     timeout = 1200
51
52     @inlineCallbacks
53     def _test_openpgp_gen_key(self):
54         pgp = openpgp.OpenPGPScheme(
55             self._soledad, gpgbinary=self.gpg_binary_path)
56         yield self._assert_key_not_found(pgp, 'user@leap.se')
57         key = yield pgp.gen_key('user@leap.se')
58         self.assertIsInstance(key, openpgp.OpenPGPKey)
59         self.assertEqual(
60             ['user@leap.se'], key.address, 'Wrong address bound to key.')
61         self.assertEqual(
62             4096, key.length, 'Wrong key length.')
63
64     @inlineCallbacks
65     def test_openpgp_put_delete_key(self):
66         pgp = openpgp.OpenPGPScheme(
67             self._soledad, gpgbinary=self.gpg_binary_path)
68         yield self._assert_key_not_found(pgp, ADDRESS)
69         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
70         key = yield pgp.get_key(ADDRESS, private=False)
71         yield pgp.delete_key(key)
72         yield self._assert_key_not_found(pgp, ADDRESS)
73
74     @inlineCallbacks
75     def test_openpgp_put_ascii_key(self):
76         pgp = openpgp.OpenPGPScheme(
77             self._soledad, gpgbinary=self.gpg_binary_path)
78         yield self._assert_key_not_found(pgp, ADDRESS)
79         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
80         key = yield pgp.get_key(ADDRESS, private=False)
81         self.assertIsInstance(key, openpgp.OpenPGPKey)
82         self.assertTrue(
83             ADDRESS in key.address, 'Wrong address bound to key.')
84         self.assertEqual(
85             4096, key.length, 'Wrong key length.')
86         yield pgp.delete_key(key)
87         yield self._assert_key_not_found(pgp, ADDRESS)
88
89     @inlineCallbacks
90     def test_get_public_key(self):
91         pgp = openpgp.OpenPGPScheme(
92             self._soledad, gpgbinary=self.gpg_binary_path)
93         yield self._assert_key_not_found(pgp, ADDRESS)
94         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
95         yield self._assert_key_not_found(pgp, ADDRESS, private=True)
96         key = yield pgp.get_key(ADDRESS, private=False)
97         self.assertTrue(ADDRESS in key.address)
98         self.assertFalse(key.private)
99         self.assertEqual(KEY_FINGERPRINT, key.fingerprint)
100         yield pgp.delete_key(key)
101         yield self._assert_key_not_found(pgp, ADDRESS)
102
103     @inlineCallbacks
104     def test_openpgp_encrypt_decrypt(self):
105         data = 'data'
106         pgp = openpgp.OpenPGPScheme(
107             self._soledad, gpgbinary=self.gpg_binary_path)
108
109         # encrypt
110         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
111         pubkey = yield pgp.get_key(ADDRESS, private=False)
112         cyphertext = yield pgp.encrypt(data, pubkey)
113
114         self.assertTrue(cyphertext is not None)
115         self.assertTrue(cyphertext != '')
116         self.assertTrue(cyphertext != data)
117         self.assertTrue(pgp.is_encrypted(cyphertext))
118         self.assertTrue(pgp.is_encrypted(cyphertext))
119
120         # decrypt
121         yield self._assert_key_not_found(pgp, ADDRESS, private=True)
122         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
123         privkey = yield pgp.get_key(ADDRESS, private=True)
124         decrypted, _ = yield pgp.decrypt(cyphertext, privkey)
125         self.assertEqual(decrypted, data)
126
127         yield pgp.delete_key(pubkey)
128         yield pgp.delete_key(privkey)
129         yield self._assert_key_not_found(pgp, ADDRESS, private=False)
130         yield self._assert_key_not_found(pgp, ADDRESS, private=True)
131
132     @inlineCallbacks
133     def test_verify_with_private_raises(self):
134         data = 'data'
135         pgp = openpgp.OpenPGPScheme(
136             self._soledad, gpgbinary=self.gpg_binary_path)
137         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
138         privkey = yield pgp.get_key(ADDRESS, private=True)
139         signed = pgp.sign(data, privkey)
140         self.assertRaises(
141             AssertionError,
142             pgp.verify, signed, privkey)
143
144     @inlineCallbacks
145     def test_sign_with_public_raises(self):
146         data = 'data'
147         pgp = openpgp.OpenPGPScheme(
148             self._soledad, gpgbinary=self.gpg_binary_path)
149         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
150         self.assertRaises(
151             AssertionError,
152             pgp.sign, data, ADDRESS, OpenPGPKey)
153
154     @inlineCallbacks
155     def test_verify_with_wrong_key_raises(self):
156         data = 'data'
157         pgp = openpgp.OpenPGPScheme(
158             self._soledad, gpgbinary=self.gpg_binary_path)
159         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
160         privkey = yield pgp.get_key(ADDRESS, private=True)
161         signed = pgp.sign(data, privkey)
162         yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2)
163         wrongkey = yield pgp.get_key(ADDRESS_2)
164         self.assertFalse(pgp.verify(signed, wrongkey))
165
166     @inlineCallbacks
167     def test_encrypt_sign_with_public_raises(self):
168         data = 'data'
169         pgp = openpgp.OpenPGPScheme(
170             self._soledad, gpgbinary=self.gpg_binary_path)
171         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
172         privkey = yield pgp.get_key(ADDRESS, private=True)
173         pubkey = yield pgp.get_key(ADDRESS, private=False)
174         self.failureResultOf(
175             pgp.encrypt(data, privkey, sign=pubkey),
176             AssertionError)
177
178     @inlineCallbacks
179     def test_decrypt_verify_with_private_raises(self):
180         data = 'data'
181         pgp = openpgp.OpenPGPScheme(
182             self._soledad, gpgbinary=self.gpg_binary_path)
183         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
184         privkey = yield pgp.get_key(ADDRESS, private=True)
185         pubkey = yield pgp.get_key(ADDRESS, private=False)
186         encrypted_and_signed = yield pgp.encrypt(
187             data, pubkey, sign=privkey)
188         self.failureResultOf(
189             pgp.decrypt(encrypted_and_signed, privkey, verify=privkey),
190             AssertionError)
191
192     @inlineCallbacks
193     def test_decrypt_verify_with_wrong_key(self):
194         data = 'data'
195         pgp = openpgp.OpenPGPScheme(
196             self._soledad, gpgbinary=self.gpg_binary_path)
197         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
198         privkey = yield pgp.get_key(ADDRESS, private=True)
199         pubkey = yield pgp.get_key(ADDRESS, private=False)
200         encrypted_and_signed = yield pgp.encrypt(data, pubkey, sign=privkey)
201         yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2)
202         wrongkey = yield pgp.get_key(ADDRESS_2)
203         decrypted, validsign = yield pgp.decrypt(encrypted_and_signed,
204                                                  privkey,
205                                                  verify=wrongkey)
206         self.assertEqual(decrypted, data)
207         self.assertFalse(validsign)
208
209     @inlineCallbacks
210     def test_sign_verify(self):
211         data = 'data'
212         pgp = openpgp.OpenPGPScheme(
213             self._soledad, gpgbinary=self.gpg_binary_path)
214         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
215         privkey = yield pgp.get_key(ADDRESS, private=True)
216         signed = pgp.sign(data, privkey, detach=False)
217         pubkey = yield pgp.get_key(ADDRESS, private=False)
218         validsign = pgp.verify(signed, pubkey)
219         self.assertTrue(validsign)
220
221     @inlineCallbacks
222     def test_encrypt_sign_decrypt_verify(self):
223         pgp = openpgp.OpenPGPScheme(
224             self._soledad, gpgbinary=self.gpg_binary_path)
225
226         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
227         pubkey = yield pgp.get_key(ADDRESS, private=False)
228         privkey = yield pgp.get_key(ADDRESS, private=True)
229
230         yield pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2)
231         pubkey2 = yield pgp.get_key(ADDRESS_2, private=False)
232         privkey2 = yield pgp.get_key(ADDRESS_2, private=True)
233
234         data = 'data'
235         encrypted_and_signed = yield pgp.encrypt(
236             data, pubkey2, sign=privkey)
237         res, validsign = yield pgp.decrypt(
238             encrypted_and_signed, privkey2, verify=pubkey)
239         self.assertEqual(data, res)
240         self.assertTrue(validsign)
241
242     @inlineCallbacks
243     def test_sign_verify_detached_sig(self):
244         data = 'data'
245         pgp = openpgp.OpenPGPScheme(
246             self._soledad, gpgbinary=self.gpg_binary_path)
247         yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS)
248         privkey = yield pgp.get_key(ADDRESS, private=True)
249         signature = yield pgp.sign(data, privkey, detach=True)
250         pubkey = yield pgp.get_key(ADDRESS, private=False)
251         validsign = pgp.verify(data, pubkey, detached_sig=signature)
252         self.assertTrue(validsign)
253
254     @inlineCallbacks
255     def test_self_repair_three_keys(self):
256         pgp = openpgp.OpenPGPScheme(
257             self._soledad, gpgbinary=self.gpg_binary_path)
258         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
259
260         get_from_index = self._soledad.get_from_index
261         delete_doc = self._soledad.delete_doc
262
263         def my_get_from_index(*args):
264             if (args[0] == TYPE_ID_PRIVATE_INDEX and
265                     args[2] == KEY_ID):
266                 k1 = OpenPGPKey(ADDRESS, key_id="1",
267                                 refreshed_at=datetime(2005, 1, 1))
268                 k2 = OpenPGPKey(ADDRESS, key_id="2",
269                                 refreshed_at=datetime(2007, 1, 1))
270                 k3 = OpenPGPKey(ADDRESS, key_id="3",
271                                 refreshed_at=datetime(2001, 1, 1))
272                 d1 = self._soledad.create_doc_from_json(k1.get_json())
273                 d2 = self._soledad.create_doc_from_json(k2.get_json())
274                 d3 = self._soledad.create_doc_from_json(k3.get_json())
275                 return gatherResults([d1, d2, d3])
276             return get_from_index(*args)
277
278         self._soledad.get_from_index = my_get_from_index
279         self._soledad.delete_doc = Mock(return_value=succeed(None))
280
281         key = yield pgp.get_key(ADDRESS, private=False)
282
283         try:
284             self.assertEqual(key.key_id, "2")
285             self.assertEqual(self._soledad.delete_doc.call_count, 2)
286         finally:
287             self._soledad.get_from_index = get_from_index
288             self._soledad.delete_doc = delete_doc
289
290     @inlineCallbacks
291     def test_self_repair_no_keys(self):
292         pgp = openpgp.OpenPGPScheme(
293             self._soledad, gpgbinary=self.gpg_binary_path)
294         yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
295
296         get_from_index = self._soledad.get_from_index
297         delete_doc = self._soledad.delete_doc
298
299         def my_get_from_index(*args):
300             if (args[0] == TYPE_ID_PRIVATE_INDEX and
301                     args[2] == KEY_ID):
302                 return succeed([])
303             return get_from_index(*args)
304
305         self._soledad.get_from_index = my_get_from_index
306         self._soledad.delete_doc = Mock(return_value=succeed(None))
307
308         try:
309             yield self.assertFailure(pgp.get_key(ADDRESS, private=False),
310                                      KeyNotFound)
311             self.assertEqual(self._soledad.delete_doc.call_count, 1)
312         finally:
313             self._soledad.get_from_index = get_from_index
314             self._soledad.delete_doc = delete_doc
315
316     @inlineCallbacks
317     def test_self_repair_put_keys(self):
318         pgp = openpgp.OpenPGPScheme(
319             self._soledad, gpgbinary=self.gpg_binary_path)
320
321         get_from_index = self._soledad.get_from_index
322         delete_doc = self._soledad.delete_doc
323
324         def my_get_from_index(*args):
325             if (args[0] == TYPE_ID_PRIVATE_INDEX and
326                     args[2] == KEY_ID):
327                 k1 = OpenPGPKey(ADDRESS, key_id="1",
328                                 fingerprint=KEY_FINGERPRINT,
329                                 refreshed_at=datetime(2005, 1, 1))
330                 k2 = OpenPGPKey(ADDRESS, key_id="2",
331                                 fingerprint=KEY_FINGERPRINT,
332                                 refreshed_at=datetime(2007, 1, 1))
333                 k3 = OpenPGPKey(ADDRESS, key_id="3",
334                                 fingerprint=KEY_FINGERPRINT,
335                                 refreshed_at=datetime(2001, 1, 1))
336                 d1 = self._soledad.create_doc_from_json(k1.get_json())
337                 d2 = self._soledad.create_doc_from_json(k2.get_json())
338                 d3 = self._soledad.create_doc_from_json(k3.get_json())
339                 return gatherResults([d1, d2, d3])
340             return get_from_index(*args)
341
342         self._soledad.get_from_index = my_get_from_index
343         self._soledad.delete_doc = Mock(return_value=succeed(None))
344
345         try:
346             yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS)
347             self.assertEqual(self._soledad.delete_doc.call_count, 2)
348         finally:
349             self._soledad.get_from_index = get_from_index
350             self._soledad.delete_doc = delete_doc
351
352     def _assert_key_not_found(self, pgp, address, private=False):
353         d = pgp.get_key(address, private=private)
354         return self.assertFailure(d, KeyNotFound)