Use datetime for key expiration
[keymanager.git] / src / leap / keymanager / tests / test_keymanager.py
1 # -*- coding: utf-8 -*-
2 # test_keymanager.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 Tests for the Key Manager.
21 """
22
23
24 from mock import Mock
25 from leap.common.testing.basetest import BaseLeapTest
26 from leap.keymanager import (
27     openpgp,
28     KeyNotFound,
29     KeyAddressMismatch,
30     errors,
31 )
32 from leap.keymanager.openpgp import OpenPGPKey
33 from leap.keymanager.keys import (
34     is_address,
35     build_key_from_dict,
36 )
37 from leap.keymanager.validation import (
38     ValidationLevel,
39     toValidationLevel
40 )
41 from leap.keymanager.tests import (
42     KeyManagerWithSoledadTestCase,
43     ADDRESS,
44     KEY_FINGERPRINT,
45     PUBLIC_KEY,
46     PRIVATE_KEY,
47     GPG_BINARY_PATH
48 )
49
50
51 ADDRESS_2 = 'anotheruser@leap.se'
52
53
54 class KeyManagerUtilTestCase(BaseLeapTest):
55
56     def setUp(self):
57         pass
58
59     def tearDown(self):
60         pass
61
62     def test_is_address(self):
63         self.assertTrue(
64             is_address('user@leap.se'),
65             'Incorrect address detection.')
66         self.assertFalse(
67             is_address('userleap.se'),
68             'Incorrect address detection.')
69         self.assertFalse(
70             is_address('user@'),
71             'Incorrect address detection.')
72         self.assertFalse(
73             is_address('@leap.se'),
74             'Incorrect address detection.')
75
76     def test_build_key_from_dict(self):
77         kdict = {
78             'address': ADDRESS,
79             'key_id': 'key_id',
80             'fingerprint': 'fingerprint',
81             'key_data': 'key_data',
82             'private': 'private',
83             'length': 'length',
84             'expiry_date': '',
85             'first_seen_at': 'first_seen_at',
86             'last_audited_at': 'last_audited_at',
87             'validation': str(ValidationLevel.Weak_Chain),
88         }
89         key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict)
90         self.assertEqual(
91             kdict['address'], key.address,
92             'Wrong data in key.')
93         self.assertEqual(
94             kdict['key_id'], key.key_id,
95             'Wrong data in key.')
96         self.assertEqual(
97             kdict['fingerprint'], key.fingerprint,
98             'Wrong data in key.')
99         self.assertEqual(
100             kdict['key_data'], key.key_data,
101             'Wrong data in key.')
102         self.assertEqual(
103             kdict['private'], key.private,
104             'Wrong data in key.')
105         self.assertEqual(
106             kdict['length'], key.length,
107             'Wrong data in key.')
108         self.assertEqual(
109             None, key.expiry_date,
110             'Wrong data in key.')
111         self.assertEqual(
112             kdict['first_seen_at'], key.first_seen_at,
113             'Wrong data in key.')
114         self.assertEqual(
115             kdict['last_audited_at'], key.last_audited_at,
116             'Wrong data in key.')
117         self.assertEqual(
118             toValidationLevel(kdict['validation']), key.validation,
119             'Wrong data in key.')
120
121
122 class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase):
123
124     def _test_openpgp_gen_key(self):
125         pgp = openpgp.OpenPGPScheme(self._soledad)
126         self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se')
127         key = pgp.gen_key('user@leap.se')
128         self.assertIsInstance(key, openpgp.OpenPGPKey)
129         self.assertEqual(
130             'user@leap.se', key.address, 'Wrong address bound to key.')
131         self.assertEqual(
132             '4096', key.length, 'Wrong key length.')
133
134     def test_openpgp_put_delete_key(self):
135         pgp = openpgp.OpenPGPScheme(
136             self._soledad, gpgbinary=GPG_BINARY_PATH)
137         self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS)
138         pgp.put_ascii_key(PUBLIC_KEY)
139         key = pgp.get_key(ADDRESS, private=False)
140         pgp.delete_key(key)
141         self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS)
142
143     def test_openpgp_put_ascii_key(self):
144         pgp = openpgp.OpenPGPScheme(
145             self._soledad, gpgbinary=GPG_BINARY_PATH)
146         self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS)
147         pgp.put_ascii_key(PUBLIC_KEY)
148         key = pgp.get_key(ADDRESS, private=False)
149         self.assertIsInstance(key, openpgp.OpenPGPKey)
150         self.assertEqual(
151             ADDRESS, key.address, 'Wrong address bound to key.')
152         self.assertEqual(
153             '4096', key.length, 'Wrong key length.')
154         pgp.delete_key(key)
155         self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS)
156
157     def test_get_public_key(self):
158         pgp = openpgp.OpenPGPScheme(
159             self._soledad, gpgbinary=GPG_BINARY_PATH)
160         self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS)
161         pgp.put_ascii_key(PUBLIC_KEY)
162         self.assertRaises(
163             KeyNotFound, pgp.get_key, ADDRESS, private=True)
164         key = pgp.get_key(ADDRESS, private=False)
165         self.assertEqual(ADDRESS, key.address)
166         self.assertFalse(key.private)
167         self.assertEqual(KEY_FINGERPRINT, key.fingerprint)
168         pgp.delete_key(key)
169         self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS)
170
171     def test_openpgp_encrypt_decrypt(self):
172         # encrypt
173         pgp = openpgp.OpenPGPScheme(
174             self._soledad, gpgbinary=GPG_BINARY_PATH)
175         pgp.put_ascii_key(PUBLIC_KEY)
176         pubkey = pgp.get_key(ADDRESS, private=False)
177         cyphertext = pgp.encrypt('data', pubkey)
178         # assert
179         self.assertTrue(cyphertext is not None)
180         self.assertTrue(cyphertext != '')
181         self.assertTrue(cyphertext != 'data')
182         self.assertTrue(pgp.is_encrypted(cyphertext))
183         self.assertTrue(pgp.is_encrypted(cyphertext))
184         # decrypt
185         self.assertRaises(
186             KeyNotFound, pgp.get_key, ADDRESS, private=True)
187         pgp.put_ascii_key(PRIVATE_KEY)
188         privkey = pgp.get_key(ADDRESS, private=True)
189         pgp.delete_key(pubkey)
190         pgp.delete_key(privkey)
191         self.assertRaises(
192             KeyNotFound, pgp.get_key, ADDRESS, private=False)
193         self.assertRaises(
194             KeyNotFound, pgp.get_key, ADDRESS, private=True)
195
196     def test_verify_with_private_raises(self):
197         pgp = openpgp.OpenPGPScheme(
198             self._soledad, gpgbinary=GPG_BINARY_PATH)
199         pgp.put_ascii_key(PRIVATE_KEY)
200         data = 'data'
201         privkey = pgp.get_key(ADDRESS, private=True)
202         signed = pgp.sign(data, privkey)
203         self.assertRaises(
204             AssertionError,
205             pgp.verify, signed, privkey)
206
207     def test_sign_with_public_raises(self):
208         pgp = openpgp.OpenPGPScheme(
209             self._soledad, gpgbinary=GPG_BINARY_PATH)
210         pgp.put_ascii_key(PUBLIC_KEY)
211         data = 'data'
212         pubkey = pgp.get_key(ADDRESS, private=False)
213         self.assertRaises(
214             AssertionError,
215             pgp.sign, data, pubkey)
216
217     def test_verify_with_wrong_key_raises(self):
218         pgp = openpgp.OpenPGPScheme(
219             self._soledad, gpgbinary=GPG_BINARY_PATH)
220         pgp.put_ascii_key(PRIVATE_KEY)
221         data = 'data'
222         privkey = pgp.get_key(ADDRESS, private=True)
223         signed = pgp.sign(data, privkey)
224         pgp.put_ascii_key(PUBLIC_KEY_2)
225         wrongkey = pgp.get_key(ADDRESS_2)
226         self.assertRaises(
227             errors.InvalidSignature,
228             pgp.verify, signed, wrongkey)
229
230     def test_encrypt_sign_with_public_raises(self):
231         pgp = openpgp.OpenPGPScheme(
232             self._soledad, gpgbinary=GPG_BINARY_PATH)
233         pgp.put_ascii_key(PRIVATE_KEY)
234         data = 'data'
235         privkey = pgp.get_key(ADDRESS, private=True)
236         pubkey = pgp.get_key(ADDRESS, private=False)
237         self.assertRaises(
238             AssertionError,
239             pgp.encrypt, data, privkey, sign=pubkey)
240
241     def test_decrypt_verify_with_private_raises(self):
242         pgp = openpgp.OpenPGPScheme(
243             self._soledad, gpgbinary=GPG_BINARY_PATH)
244         pgp.put_ascii_key(PRIVATE_KEY)
245         data = 'data'
246         privkey = pgp.get_key(ADDRESS, private=True)
247         pubkey = pgp.get_key(ADDRESS, private=False)
248         encrypted_and_signed = pgp.encrypt(
249             data, pubkey, sign=privkey)
250         self.assertRaises(
251             AssertionError,
252             pgp.decrypt,
253             encrypted_and_signed, privkey, verify=privkey)
254
255     def test_decrypt_verify_with_wrong_key_raises(self):
256         pgp = openpgp.OpenPGPScheme(
257             self._soledad, gpgbinary=GPG_BINARY_PATH)
258         pgp.put_ascii_key(PRIVATE_KEY)
259         data = 'data'
260         privkey = pgp.get_key(ADDRESS, private=True)
261         pubkey = pgp.get_key(ADDRESS, private=False)
262         encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey)
263         pgp.put_ascii_key(PUBLIC_KEY_2)
264         wrongkey = pgp.get_key(ADDRESS_2)
265         self.assertRaises(
266             errors.InvalidSignature,
267             pgp.verify, encrypted_and_signed, wrongkey)
268
269     def test_sign_verify(self):
270         pgp = openpgp.OpenPGPScheme(
271             self._soledad, gpgbinary=GPG_BINARY_PATH)
272         pgp.put_ascii_key(PRIVATE_KEY)
273         data = 'data'
274         privkey = pgp.get_key(ADDRESS, private=True)
275         signed = pgp.sign(data, privkey, detach=False)
276         pubkey = pgp.get_key(ADDRESS, private=False)
277         self.assertTrue(pgp.verify(signed, pubkey))
278
279     def test_encrypt_sign_decrypt_verify(self):
280         pgp = openpgp.OpenPGPScheme(
281             self._soledad, gpgbinary=GPG_BINARY_PATH)
282         pgp.put_ascii_key(PRIVATE_KEY)
283         pubkey = pgp.get_key(ADDRESS, private=False)
284         privkey = pgp.get_key(ADDRESS, private=True)
285         pgp.put_ascii_key(PRIVATE_KEY_2)
286         pubkey2 = pgp.get_key(ADDRESS_2, private=False)
287         privkey2 = pgp.get_key(ADDRESS_2, private=True)
288         data = 'data'
289         encrypted_and_signed = pgp.encrypt(
290             data, pubkey2, sign=privkey)
291         res = pgp.decrypt(
292             encrypted_and_signed, privkey2, verify=pubkey)
293         self.assertTrue(data, res)
294
295     def test_sign_verify_detached_sig(self):
296         pgp = openpgp.OpenPGPScheme(
297             self._soledad, gpgbinary=GPG_BINARY_PATH)
298         pgp.put_ascii_key(PRIVATE_KEY)
299         data = 'data'
300         privkey = pgp.get_key(ADDRESS, private=True)
301         signature = pgp.sign(data, privkey, detach=True)
302         pubkey = pgp.get_key(ADDRESS, private=False)
303         self.assertTrue(pgp.verify(data, pubkey, detached_sig=signature))
304
305
306 class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase):
307
308     def test_get_all_keys_in_db(self):
309         km = self._key_manager()
310         km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY)
311         # get public keys
312         keys = km.get_all_keys(False)
313         self.assertEqual(len(keys), 1, 'Wrong number of keys')
314         self.assertEqual(ADDRESS, keys[0].address)
315         self.assertFalse(keys[0].private)
316         # get private keys
317         keys = km.get_all_keys(True)
318         self.assertEqual(len(keys), 1, 'Wrong number of keys')
319         self.assertEqual(ADDRESS, keys[0].address)
320         self.assertTrue(keys[0].private)
321
322     def test_get_public_key(self):
323         km = self._key_manager()
324         km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY)
325         # get the key
326         key = km.get_key(ADDRESS, OpenPGPKey, private=False,
327                          fetch_remote=False)
328         self.assertTrue(key is not None)
329         self.assertEqual(key.address, ADDRESS)
330         self.assertEqual(
331             key.fingerprint.lower(), KEY_FINGERPRINT.lower())
332         self.assertFalse(key.private)
333
334     def test_get_private_key(self):
335         km = self._key_manager()
336         km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY)
337         # get the key
338         key = km.get_key(ADDRESS, OpenPGPKey, private=True,
339                          fetch_remote=False)
340         self.assertTrue(key is not None)
341         self.assertEqual(key.address, ADDRESS)
342         self.assertEqual(
343             key.fingerprint.lower(), KEY_FINGERPRINT.lower())
344         self.assertTrue(key.private)
345
346     def test_send_key_raises_key_not_found(self):
347         km = self._key_manager()
348         self.assertRaises(
349             KeyNotFound,
350             km.send_key, OpenPGPKey)
351
352     def test_send_key(self):
353         """
354         Test that request is well formed when sending keys to server.
355         """
356         token = "mytoken"
357         km = self._key_manager(token=token)
358         km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY)
359         km._fetcher.put = Mock()
360         # the following data will be used on the send
361         km.ca_cert_path = 'capath'
362         km.session_id = 'sessionid'
363         km.uid = 'myuid'
364         km.api_uri = 'apiuri'
365         km.api_version = 'apiver'
366         km.send_key(OpenPGPKey)
367         # setup expected args
368         data = {
369             km.PUBKEY_KEY: km.get_key(km._address, OpenPGPKey).key_data,
370         }
371         url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid')
372         km._fetcher.put.assert_called_once_with(
373             url, data=data, verify='capath',
374             headers={'Authorization': 'Token token=%s' % token},
375         )
376
377     def test__fetch_keys_from_server(self):
378         """
379         Test that the request is well formed when fetching keys from server.
380         """
381         km = self._key_manager(url='http://nickserver.domain')
382
383         class Response(object):
384             status_code = 200
385             headers = {'content-type': 'application/json'}
386
387             def json(self):
388                 return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2}
389
390             def raise_for_status(self):
391                 pass
392
393         # mock the fetcher so it returns the key for ADDRESS_2
394         km._fetcher.get = Mock(
395             return_value=Response())
396         km.ca_cert_path = 'cacertpath'
397         # do the fetch
398         km._fetch_keys_from_server(ADDRESS_2)
399         # and verify the call
400         km._fetcher.get.assert_called_once_with(
401             'http://nickserver.domain',
402             data={'address': ADDRESS_2},
403             verify='cacertpath',
404         )
405
406     def test_get_key_fetches_from_server(self):
407         """
408         Test that getting a key successfuly fetches from server.
409         """
410         km = self._key_manager(url='http://nickserver.domain')
411
412         class Response(object):
413             status_code = 200
414             headers = {'content-type': 'application/json'}
415
416             def json(self):
417                 return {'address': ADDRESS, 'openpgp': PUBLIC_KEY}
418
419             def raise_for_status(self):
420                 pass
421
422         # mock the fetcher so it returns the key for ADDRESS_2
423         km._fetcher.get = Mock(return_value=Response())
424         km.ca_cert_path = 'cacertpath'
425         # try to key get without fetching from server
426         self.assertRaises(
427             KeyNotFound, km.get_key, ADDRESS, OpenPGPKey,
428             fetch_remote=False
429         )
430         # try to get key fetching from server.
431         key = km.get_key(ADDRESS, OpenPGPKey)
432         self.assertIsInstance(key, OpenPGPKey)
433         self.assertEqual(ADDRESS, key.address)
434
435     def test_put_key_ascii(self):
436         """
437         Test that putting ascii key works
438         """
439         km = self._key_manager(url='http://nickserver.domain')
440
441         km.put_raw_key(PUBLIC_KEY, OpenPGPKey)
442         key = km.get_key(ADDRESS, OpenPGPKey)
443         self.assertIsInstance(key, OpenPGPKey)
444         self.assertEqual(ADDRESS, key.address)
445
446     def test_fetch_uri_ascii_key(self):
447         """
448         Test that fetch key downloads the ascii key and gets included in
449         the local storage
450         """
451         km = self._key_manager()
452
453         class Response(object):
454             ok = True
455             content = PUBLIC_KEY
456
457         km._fetcher.get = Mock(return_value=Response())
458         km.ca_cert_path = 'cacertpath'
459
460         km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey)
461         key = km.get_key(ADDRESS, OpenPGPKey)
462         self.assertEqual(KEY_FINGERPRINT, key.fingerprint)
463
464     def test_fetch_uri_empty_key(self):
465         """
466         Test that fetch key raises KeyNotFound if no key in the url
467         """
468         km = self._key_manager()
469
470         class Response(object):
471             ok = True
472             content = ""
473
474         km._fetcher.get = Mock(return_value=Response())
475         km.ca_cert_path = 'cacertpath'
476         self.assertRaises(KeyNotFound, km.fetch_key,
477                           ADDRESS, "http://site.domain/key", OpenPGPKey)
478
479     def test_fetch_uri_address_differ(self):
480         """
481         Test that fetch key raises KeyAttributesDiffer if the address
482         don't match
483         """
484         km = self._key_manager()
485
486         class Response(object):
487             ok = True
488             content = PUBLIC_KEY
489
490         km._fetcher.get = Mock(return_value=Response())
491         km.ca_cert_path = 'cacertpath'
492         self.assertRaises(KeyAddressMismatch, km.fetch_key,
493                           ADDRESS_2, "http://site.domain/key", OpenPGPKey)
494
495
496 class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase):
497
498     RAW_DATA = 'data'
499
500     def test_keymanager_openpgp_encrypt_decrypt(self):
501         km = self._key_manager()
502         # put raw private key
503         km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY)
504         # get public key
505         pubkey = km.get_key(
506             ADDRESS, OpenPGPKey, private=False, fetch_remote=False)
507         # encrypt
508         encdata = km.encrypt(self.RAW_DATA, pubkey)
509         self.assertNotEqual(self.RAW_DATA, encdata)
510         # get private key
511         privkey = km.get_key(
512             ADDRESS, OpenPGPKey, private=True, fetch_remote=False)
513         # decrypt
514         rawdata = km.decrypt(encdata, privkey)
515         self.assertEqual(self.RAW_DATA, rawdata)
516
517     def test_keymanager_openpgp_sign_verify(self):
518         km = self._key_manager()
519         # put raw private keys
520         km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY)
521         # get private key for signing
522         privkey = km.get_key(
523             ADDRESS, OpenPGPKey, private=True, fetch_remote=False)
524         # encrypt
525         signdata = km.sign(self.RAW_DATA, privkey, detach=False)
526         self.assertNotEqual(self.RAW_DATA, signdata)
527         # get public key for verifying
528         pubkey = km.get_key(
529             ADDRESS, OpenPGPKey, private=False, fetch_remote=False)
530         # decrypt
531         self.assertTrue(km.verify(signdata, pubkey))
532
533
534 # Key material for testing
535
536 # key 7FEE575A: public key "anotheruser <anotheruser@leap.se>"
537 PUBLIC_KEY_2 = """
538 -----BEGIN PGP PUBLIC KEY BLOCK-----
539 Version: GnuPG v1.4.10 (GNU/Linux)
540
541 mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR
542 gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq
543 Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0
544 IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle
545 AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E
546 gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw
547 ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4
548 JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz
549 VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt
550 Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63
551 yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ
552 f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X
553 Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck
554 I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ=
555 =Thdu
556 -----END PGP PUBLIC KEY BLOCK-----
557 """
558
559 PRIVATE_KEY_2 = """
560 -----BEGIN PGP PRIVATE KEY BLOCK-----
561 Version: GnuPG v1.4.10 (GNU/Linux)
562
563 lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD
564 kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1
565 6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB
566 AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8
567 H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks
568 7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X
569 C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje
570 uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty
571 GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI
572 1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v
573 dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG
574 CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh
575 8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD
576 izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT
577 oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL
578 juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw
579 cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe
580 94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC
581 rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx
582 77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2
583 3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF
584 UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO
585 2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB
586 /0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE
587 JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda
588 z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk
589 o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6
590 THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0
591 =a5gs
592 -----END PGP PRIVATE KEY BLOCK-----
593 """
594 import unittest
595 if __name__ == "__main__":
596     unittest.main()