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