From 0a327aa8a47cc9b2977c1b86fdddc10acb83e538 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 9 Sep 2015 14:59:38 -0400 Subject: [docs] fix broken pypi badge --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index acf2335..781f2b8 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ LEAP's Key Manager ================== -.. image:: https://pypip.in/v/leap.keymanager/badge.png - :target: https://crate.io/packages/leap.keymanager +.. image:: https://badge.fury.io/py/leap.keymanager.svg + :target: http://badge.fury.io/py/leap.keymanager The Key Manager is a Nicknym agent for the LEAP project: -- cgit v1.2.3 From 7867d9e9cbc26ac83a569a52b43c65f6367cc30a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 9 Sep 2015 15:18:45 -0400 Subject: [docs] add downloads info --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 781f2b8..a06721c 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,8 @@ LEAP's Key Manager ================== .. image:: https://badge.fury.io/py/leap.keymanager.svg :target: http://badge.fury.io/py/leap.keymanager +.. image:: https://img.shields.io/pypi/dm/leap.keymanager.svg + :target: http://badge.fury.io/py/leap.keymanager The Key Manager is a Nicknym agent for the LEAP project: -- cgit v1.2.3 From 95a0d917578b3604804cd5022fb92319c71320b9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 9 Sep 2015 15:22:03 -0400 Subject: [docs] update link to nicknym docs --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a06721c..e759b9b 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,9 @@ LEAP's Key Manager .. image:: https://img.shields.io/pypi/dm/leap.keymanager.svg :target: http://badge.fury.io/py/leap.keymanager -The Key Manager is a Nicknym agent for the LEAP project: +The Key Manager is a Nicknym agent for the LEAP project:: - https://leap.se/pt/docs/design/nicknym + https://leap.se/nicknym running tests ------------- -- cgit v1.2.3 From dc9ef4d802855b24b8f74826b3f4d725b88dfa4d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 9 Sep 2015 15:27:55 -0400 Subject: [docs] beautify links to docs --- README.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e759b9b..1d4b53e 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,12 @@ LEAP's Key Manager .. image:: https://img.shields.io/pypi/dm/leap.keymanager.svg :target: http://badge.fury.io/py/leap.keymanager -The Key Manager is a Nicknym agent for the LEAP project:: +The Key Manager is a `Nicknym`_ agent for the `LEAP`_ project, written in python using the `twisted`_ async framework. + +.. _`Nicknym`: https://leap.se/nicknym +.. _`LEAP`: https://leap.se/docs/ +.. _`twisted`: https://twistedmatrix.com/trac/ - https://leap.se/nicknym running tests ------------- @@ -15,3 +18,12 @@ running tests Use trial to run the test suite:: trial leap.keymanager + +License +======= + +.. image:: https://raw.github.com/leapcode/bitmask_client/develop/docs/user/gpl.png + +leap.keymanager is released under the terms of the `GNU GPL version 3`_ or later. + +.. _`GNU GPL version 3`: http://www.gnu.org/licenses/gpl.txt -- cgit v1.2.3 From ff15f95460036485a417964c6ed61ebffe7b9e46 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 14 Sep 2015 23:08:41 -0400 Subject: [feat] use async events api this avoids using a separate thread with tornado ioloop for events client, since we can use twisted reactor. - Resolves: #7274 --- src/leap/keymanager/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 999b53c..34bc964 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -55,7 +55,7 @@ from twisted.internet import defer from urlparse import urlparse from leap.common.check import leap_assert -from leap.common.events import emit, catalog +from leap.common.events import emit_async, catalog from leap.common.decorators import memoized_method from leap.keymanager.errors import ( @@ -287,7 +287,7 @@ class KeyManager(object): self._api_version, self._uid) self._put(uri, data) - emit(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) d = self.get_key( self._address, ktype, private=False, fetch_remote=False) @@ -323,24 +323,24 @@ class KeyManager(object): leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) - emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) + emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): - emit(catalog.KEYMANAGER_KEY_FOUND, address) + emit_async(catalog.KEYMANAGER_KEY_FOUND, address) return key def key_not_found(failure): if not failure.check(KeyNotFound): return failure - emit(catalog.KEYMANAGER_KEY_NOT_FOUND, address) + emit_async(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure - emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) + emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: @@ -397,10 +397,10 @@ class KeyManager(object): self._assert_supported_key_type(ktype) def signal_finished(key): - emit(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + emit_async(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key - emit(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) d = self._wrapper_map[ktype].gen_key(self._address) d.addCallback(signal_finished) return d -- cgit v1.2.3 From 481c5807ae99575d20fd15a96321103cbcfcdfd2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 16 Sep 2015 12:55:08 -0400 Subject: [style] pep8 fix --- src/leap/keymanager/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 34bc964..c4534e5 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -74,8 +74,6 @@ from leap.keymanager.keys import ( ) from leap.keymanager.openpgp import OpenPGPKey, OpenPGPScheme -from ._version import get_versions - __version__ = get_versions()['version'] del get_versions @@ -397,7 +395,8 @@ class KeyManager(object): self._assert_supported_key_type(ktype) def signal_finished(key): - emit_async(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + emit_async( + catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) -- cgit v1.2.3 From 0b9f64faef0ba9c5cf2a9efe485794ef9b999fab Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 16 Sep 2015 11:04:33 +0200 Subject: [feat] add logging to fetch_key In case of failure of fetch_key will be useful to have some logging telling us wich key is fetching. - Related: #7410 --- src/leap/keymanager/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index c4534e5..cf43004 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -779,6 +779,7 @@ class KeyManager(object): """ self._assert_supported_key_type(ktype) + logger.info("Fetch key for %s from %s" % (address, uri)) res = self._get(uri) if not res.ok: return defer.fail(KeyNotFound(uri)) -- cgit v1.2.3 From 9546348c3603f390fdd6d5a119414142e9bd02ea Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Fri, 18 Sep 2015 17:03:14 +0200 Subject: [feature] Use ca_bundle when fetching keys by url This is necessary as a fetch by url will talk to remote sites or, for providers with a commercial cert, with a cert that had not been signed with the provider CA. - support lookup of local keys by url for providers with a commercial cert - combine ca_bundle with ca_cert_path if specified - close soledad after each test --- src/leap/keymanager/__init__.py | 42 ++++++++++++++++- src/leap/keymanager/tests/__init__.py | 5 +- src/leap/keymanager/tests/test_keymanager.py | 68 ++++++++++++++++++++++++++-- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index cf43004..1220402 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -18,7 +18,10 @@ Key Manager is a Nicknym agent for LEAP client. """ # let's do a little sanity check to see if we're using the wrong gnupg +import fileinput import sys +import tempfile +from leap.common import ca_bundle from ._version import get_versions try: @@ -134,12 +137,30 @@ class KeyManager(object): } # the following are used to perform https requests self._fetcher = requests - self._session = self._fetcher.session() + self._combined_ca_bundle = self._create_combined_bundle_file() # # utilities # + def _create_combined_bundle_file(self): + leap_ca_bundle = ca_bundle.where() + + if self._ca_cert_path == leap_ca_bundle: + return self._ca_cert_path # don't merge file with itself + elif self._ca_cert_path is None: + return leap_ca_bundle + + tmp_file = tempfile.NamedTemporaryFile(delete=True) # file is auto deleted when python process ends + + with open(tmp_file.name, 'w') as fout: + fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) + for line in fin: + fout.write(line) + fin.close() + + return tmp_file.name + def _key_class_from_type(self, ktype): """ Return key class from string representation of key type. @@ -176,6 +197,23 @@ class KeyManager(object): # 'Content-type is not JSON.') return res + def _get_with_combined_ca_bundle(self, uri, data=None): + """ + Send a GET request to C{uri} containing C{data}. + + Instead of using the ca_cert provided on construction time, this version also uses + the default certificates shipped with leap.common + + :param uri: The URI of the request. + :type uri: str + :param data: The body of the request. + :type data: dict, str or file + + :return: The response to the request. + :rtype: requests.Response + """ + return self._fetcher.get(uri, data=data, verify=self._combined_ca_bundle) + def _put(self, uri, data=None): """ Send a PUT request to C{uri} containing C{data}. @@ -780,7 +818,7 @@ class KeyManager(object): self._assert_supported_key_type(ktype) logger.info("Fetch key for %s from %s" % (address, uri)) - res = self._get(uri) + res = self._get_with_combined_ca_bundle(uri) if not res.ok: return defer.fail(KeyNotFound(uri)) diff --git a/src/leap/keymanager/tests/__init__.py b/src/leap/keymanager/tests/__init__.py index 7128d20..6b647a4 100644 --- a/src/leap/keymanager/tests/__init__.py +++ b/src/leap/keymanager/tests/__init__.py @@ -73,11 +73,12 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): d = km._wrapper_map[OpenPGPKey].deferred_indexes d.addCallback(get_and_delete_keys) d.addCallback(lambda _: self.tearDownEnv()) + d.addCallback(lambda _: self._soledad.close()) return d - def _key_manager(self, user=ADDRESS, url='', token=None): + def _key_manager(self, user=ADDRESS, url='', token=None, ca_cert_path=None): return KeyManager(user, url, self._soledad, token=token, - gpgbinary=self.gpg_binary_path) + gpgbinary=self.gpg_binary_path, ca_cert_path=ca_cert_path) def _find_gpg(self): gpg_path = distutils.spawn.find_executable('gpg') diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index a12cac0..984b037 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -22,7 +22,9 @@ Tests for the Key Manager. from datetime import datetime -from mock import Mock +import tempfile +from leap.common import ca_bundle +from mock import Mock, MagicMock, patch from twisted.internet.defer import inlineCallbacks from twisted.trial import unittest @@ -50,6 +52,7 @@ from leap.keymanager.tests import ( NICKSERVER_URI = "http://leap.se/" +REMOTE_KEY_URL = "http://site.domain/key" class KeyManagerUtilTestCase(unittest.TestCase): @@ -287,7 +290,6 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = PUBLIC_KEY km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) key = yield km.get_key(ADDRESS, OpenPGPKey) @@ -304,7 +306,6 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = "" km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' d = km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) return self.assertFailure(d, KeyNotFound) @@ -320,10 +321,69 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = PUBLIC_KEY km._fetcher.get = Mock(return_value=Response()) - km.ca_cert_path = 'cacertpath' d = km.fetch_key(ADDRESS_2, "http://site.domain/key", OpenPGPKey) return self.assertFailure(d, KeyAddressMismatch) + def _mock_get_response(self, km, body): + class Response(object): + ok = True + content = body + + mock = MagicMock(return_value=Response()) + km._fetcher.get = mock + + return mock + + @inlineCallbacks + def test_fetch_key_uses_ca_bundle_if_none_specified(self): + ca_cert_path = None + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + + @inlineCallbacks + def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): + ca_cert_path = ca_bundle.where() + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + + @inlineCallbacks + def test_fetch_uses_combined_ca_bundle_otherwise(self): + with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile() as tmp_output: + ca_content = 'some\ncontent\n' + ca_cert_path = tmp_input.name + self._dump_to_file(ca_cert_path, ca_content) + + with patch('leap.keymanager.tempfile.NamedTemporaryFile') as mock: + mock.return_value = tmp_output + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + # assert that combined bundle file is passed to get call + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=tmp_output.name) + + # assert that files got appended + expected = self._slurp_file(ca_bundle.where()) + ca_content + self.assertEqual(expected, self._slurp_file(tmp_output.name)) + + def _dump_to_file(self, filename, content): + with open(filename, 'w') as out: + out.write(content) + + def _slurp_file(self, filename): + with open(filename) as f: + content = f.read() + return content + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 3b0e1694bc3280896a845d92f55590b1553c4a3f Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Sep 2015 19:59:11 +0200 Subject: [style] fix pep8 problems --- src/leap/keymanager/__init__.py | 10 ++++++---- src/leap/keymanager/tests/__init__.py | 6 ++++-- src/leap/keymanager/tests/test_keymanager.py | 14 +++++++++----- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 1220402..5248cb0 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -151,7 +151,8 @@ class KeyManager(object): elif self._ca_cert_path is None: return leap_ca_bundle - tmp_file = tempfile.NamedTemporaryFile(delete=True) # file is auto deleted when python process ends + # file is auto deleted when python process ends + tmp_file = tempfile.NamedTemporaryFile(delete=True) with open(tmp_file.name, 'w') as fout: fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) @@ -201,8 +202,8 @@ class KeyManager(object): """ Send a GET request to C{uri} containing C{data}. - Instead of using the ca_cert provided on construction time, this version also uses - the default certificates shipped with leap.common + Instead of using the ca_cert provided on construction time, this + version also uses the default certificates shipped with leap.common :param uri: The URI of the request. :type uri: str @@ -212,7 +213,8 @@ class KeyManager(object): :return: The response to the request. :rtype: requests.Response """ - return self._fetcher.get(uri, data=data, verify=self._combined_ca_bundle) + return self._fetcher.get( + uri, data=data, verify=self._combined_ca_bundle) def _put(self, uri, data=None): """ diff --git a/src/leap/keymanager/tests/__init__.py b/src/leap/keymanager/tests/__init__.py index 6b647a4..9b95e1a 100644 --- a/src/leap/keymanager/tests/__init__.py +++ b/src/leap/keymanager/tests/__init__.py @@ -76,9 +76,11 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): d.addCallback(lambda _: self._soledad.close()) return d - def _key_manager(self, user=ADDRESS, url='', token=None, ca_cert_path=None): + def _key_manager(self, user=ADDRESS, url='', token=None, + ca_cert_path=None): return KeyManager(user, url, self._soledad, token=token, - gpgbinary=self.gpg_binary_path, ca_cert_path=ca_cert_path) + gpgbinary=self.gpg_binary_path, + ca_cert_path=ca_cert_path) def _find_gpg(self): gpg_path = distutils.spawn.find_executable('gpg') diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 984b037..7c00292 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -342,21 +342,24 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=ca_bundle.where()) @inlineCallbacks - def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): + def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=ca_bundle.where()) + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=ca_bundle.where()) @inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile() as tmp_output: + with tempfile.NamedTemporaryFile() as tmp_input, \ + tempfile.NamedTemporaryFile() as tmp_output: ca_content = 'some\ncontent\n' ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) @@ -369,7 +372,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) # assert that combined bundle file is passed to get call - get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, verify=tmp_output.name) + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=tmp_output.name) # assert that files got appended expected = self._slurp_file(ca_bundle.where()) + ca_content -- cgit v1.2.3 From 6d823d4e94e70d19a31894f973754ff05d557493 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 21 Sep 2015 23:15:33 +0200 Subject: [feat] more verbosity in get_key wrong address log --- src/leap/keymanager/openpgp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 794a0ec..b8b47d0 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -321,7 +321,9 @@ class OpenPGPScheme(EncryptionScheme): raise errors.KeyNotFound(address) leap_assert( address in doc.content[KEY_ADDRESS_KEY], - 'Wrong address in key data.') + 'Wrong address in key %s. Expected %s, found %s.' + % (doc.content[KEY_ID_KEY], address, + doc.content[KEY_ADDRESS_KEY])) key = build_key_from_dict(OpenPGPKey, doc.content) key._gpgbinary = self._gpgbinary return key -- cgit v1.2.3 From 1b805754f1d6efe9af25dcf0ab60b3aa2100ee20 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 22 Sep 2015 16:18:36 +0200 Subject: [bug] catch request exceptions On fetch_key we were not catching the request exceptions, now they are returned as failure in the deferred as it should. - Related: #7410 --- changes/bug-7410_fetch_key | 1 + src/leap/keymanager/__init__.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/bug-7410_fetch_key diff --git a/changes/bug-7410_fetch_key b/changes/bug-7410_fetch_key new file mode 100644 index 0000000..4aec9fe --- /dev/null +++ b/changes/bug-7410_fetch_key @@ -0,0 +1 @@ +- catch request exceptions on key fetching diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 5248cb0..e378c91 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -820,7 +820,11 @@ class KeyManager(object): self._assert_supported_key_type(ktype) logger.info("Fetch key for %s from %s" % (address, uri)) - res = self._get_with_combined_ca_bundle(uri) + try: + res = self._get_with_combined_ca_bundle(uri) + except Exception as e: + logger.warning("There was a problem fetching key: %s" % (e,)) + return defer.fail(KeyNotFound(uri)) if not res.ok: return defer.fail(KeyNotFound(uri)) -- cgit v1.2.3 From 3da04e10ce961e2606ce00125d092d9daa621636 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Tue, 22 Sep 2015 17:43:29 +0200 Subject: [bug] treat empty string ca_cert_path as None Fixup for 9546348c36. This problem only occurs in test setups where '' is passed to ca_cert_path. --- src/leap/keymanager/__init__.py | 2 +- src/leap/keymanager/tests/test_keymanager.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index e378c91..cf099bb 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -148,7 +148,7 @@ class KeyManager(object): if self._ca_cert_path == leap_ca_bundle: return self._ca_cert_path # don't merge file with itself - elif self._ca_cert_path is None: + elif not self._ca_cert_path: return leap_ca_bundle # file is auto deleted when python process ends diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 7c00292..3b4aa0f 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -346,7 +346,18 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): verify=ca_bundle.where()) @inlineCallbacks - def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert(self): + def test_fetch_key_uses_ca_bundle_if_empty_string_specified(self): + ca_cert_path = '' + km = self._key_manager(ca_cert_path=ca_cert_path) + get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) + + yield km.fetch_key(ADDRESS_OTHER, REMOTE_KEY_URL, OpenPGPKey) + + get_mock.assert_called_once_with(REMOTE_KEY_URL, data=None, + verify=ca_bundle.where()) + + @inlineCallbacks + def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) -- cgit v1.2.3 From 7242d003877ef08cb7fa0e55a05c915a03b602ab Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 24 Sep 2015 01:15:11 +0200 Subject: [bug] don't repush a public key with different address During decryption the signing public key was getting repush with a different address as part of the verify usage flagging. - Resolves: https://github.com/pixelated/pixelated-user-agent/issues/466 - Related: #7420 --- changes/bug-address_mixup | 1 + src/leap/keymanager/__init__.py | 20 ++++++++++++-------- src/leap/keymanager/tests/test_validation.py | 26 +++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 changes/bug-address_mixup diff --git a/changes/bug-address_mixup b/changes/bug-address_mixup new file mode 100644 index 0000000..24170c9 --- /dev/null +++ b/changes/bug-address_mixup @@ -0,0 +1 @@ +- Don't repush a public key with different address diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index cf099bb..22fb725 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -590,10 +590,12 @@ class KeyManager(object): if pubkey is None: signature = KeyNotFound(verify) elif signed: - pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) - d.addCallback(lambda _: (decrypted, pubkey)) - return d + signature = pubkey + if not pubkey.sign_used: + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, verify) + d.addCallback(lambda _: (decrypted, signature)) + return d else: signature = InvalidSignature( 'Failed to verify signature with key %s' % @@ -685,10 +687,12 @@ class KeyManager(object): signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) if signed: - pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) - d.addCallback(lambda _: pubkey) - return d + if not pubkey.sign_used: + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: pubkey) + return d + return pubkey else: raise InvalidSignature( 'Failed to verify signature with key %s' % diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index ddf1170..bcf41c4 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -30,6 +30,9 @@ from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, PUBLIC_KEY, + ADDRESS_2, + PUBLIC_KEY_2, + PRIVATE_KEY_2, KEY_FINGERPRINT ) from leap.keymanager.validation import ValidationLevels @@ -101,7 +104,7 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @inlineCallbacks - def test_used(self): + def test_used_with_verify(self): TEXT = "some text" km = self._key_manager() @@ -118,6 +121,27 @@ class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks + def test_used_with_decrypt(self): + TEXT = "some text" + + km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2) + yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) + + km2 = self._key_manager() + yield km2.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) + yield km2.put_raw_key(PUBLIC_KEY_2, OpenPGPKey, ADDRESS_2) + encrypted = yield km2.encrypt(TEXT, ADDRESS_2, OpenPGPKey, + sign=ADDRESS) + + yield km.decrypt(encrypted, ADDRESS_2, OpenPGPKey, verify=ADDRESS) + d = km.put_raw_key( + UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevels.Provider_Endorsement) + yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks def test_signed_key(self): km = self._key_manager() -- cgit v1.2.3 From 4e82d6ef4dc5e3bb311bf5b80b1e92d67cb0f346 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Thu, 24 Sep 2015 11:46:14 +0200 Subject: [tests] Add regression tests for sign_used Fails if wrong address is passed to the put_key method, or wrong key is marked as sign_used. - Related: #7420 --- src/leap/keymanager/tests/test_keymanager.py | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 3b4aa0f..7b90ae1 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -399,6 +399,39 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): content = f.read() return content + @inlineCallbacks + def test_decrypt_updates_sign_used_for_signer(self): + # given + km = self._key_manager() + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY_2, ADDRESS_2) + encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) + yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + + # when + key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) + + # then + self.assertEqual(True, key.sign_used) + + @inlineCallbacks + def test_decrypt_does_not_update_sign_used_for_recipient(self): + # given + km = self._key_manager() + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) + yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + + # when + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + + # then + self.assertEqual(False, key.sign_used) + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 4a090d6e405415607f9c811a7961f8dc0cdd2af0 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Mon, 21 Sep 2015 15:59:53 +0200 Subject: [bug] keep combined file longer in scope In previous commit 9546348c, the combined bundle ca was not long enough in scope and was therefore deleted when it actually was used. Adopted test to check whether file is deleted. --- src/leap/keymanager/__init__.py | 16 ++++++++++++++-- src/leap/keymanager/tests/test_keymanager.py | 8 +++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 22fb725..f00e049 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -19,6 +19,7 @@ Key Manager is a Nicknym agent for LEAP client. """ # let's do a little sanity check to see if we're using the wrong gnupg import fileinput +import os import sys import tempfile from leap.common import ca_bundle @@ -139,6 +140,18 @@ class KeyManager(object): self._fetcher = requests self._combined_ca_bundle = self._create_combined_bundle_file() + # + # destructor + # + + def __del__(self): + try: + created_tmp_combined_ca_bundle = self._combined_ca_bundle not in [ca_bundle.where(), self._ca_cert_path] + if created_tmp_combined_ca_bundle: + os.remove(self._combined_ca_bundle) + except OSError: + pass + # # utilities # @@ -151,8 +164,7 @@ class KeyManager(object): elif not self._ca_cert_path: return leap_ca_bundle - # file is auto deleted when python process ends - tmp_file = tempfile.NamedTemporaryFile(delete=True) + tmp_file = tempfile.NamedTemporaryFile(delete=False) # delete when keymanager expires with open(tmp_file.name, 'w') as fout: fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 7b90ae1..b2722b2 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -20,7 +20,7 @@ Tests for the Key Manager. """ - +from os import path from datetime import datetime import tempfile from leap.common import ca_bundle @@ -369,8 +369,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, \ - tempfile.NamedTemporaryFile() as tmp_output: + with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile(delete=False) as tmp_output: ca_content = 'some\ncontent\n' ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) @@ -390,6 +389,9 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): expected = self._slurp_file(ca_bundle.where()) + ca_content self.assertEqual(expected, self._slurp_file(tmp_output.name)) + del km # force km out of scope + self.assertFalse(path.exists(tmp_output.name)) + def _dump_to_file(self, filename, content): with open(filename, 'w') as out: out.write(content) -- cgit v1.2.3 From 7fa74c8f099fa8e6fedd95ce8a203b46fa9186c5 Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Thu, 24 Sep 2015 14:04:43 +0200 Subject: [style] fix pep8 warnings --- src/leap/keymanager/__init__.py | 7 +++++-- src/leap/keymanager/tests/test_keymanager.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index f00e049..01f3b6e 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -22,7 +22,9 @@ import fileinput import os import sys import tempfile + from leap.common import ca_bundle + from ._version import get_versions try: @@ -146,7 +148,8 @@ class KeyManager(object): def __del__(self): try: - created_tmp_combined_ca_bundle = self._combined_ca_bundle not in [ca_bundle.where(), self._ca_cert_path] + created_tmp_combined_ca_bundle = self._combined_ca_bundle not in \ + [ca_bundle.where(), self._ca_cert_path] if created_tmp_combined_ca_bundle: os.remove(self._combined_ca_bundle) except OSError: @@ -164,7 +167,7 @@ class KeyManager(object): elif not self._ca_cert_path: return leap_ca_bundle - tmp_file = tempfile.NamedTemporaryFile(delete=False) # delete when keymanager expires + tmp_file = tempfile.NamedTemporaryFile(delete=False) with open(tmp_file.name, 'w') as fout: fin = fileinput.input(files=(leap_ca_bundle, self._ca_cert_path)) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index b2722b2..8d4c5da 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -357,7 +357,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): verify=ca_bundle.where()) @inlineCallbacks - def test_fetch_key_uses_default_ca_bundle_if_also_set_as_ca_cert_path(self): + def test_fetch_key_use_default_ca_bundle_if_set_as_ca_cert_path(self): ca_cert_path = ca_bundle.where() km = self._key_manager(ca_cert_path=ca_cert_path) get_mock = self._mock_get_response(km, PUBLIC_KEY_OTHER) @@ -369,7 +369,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_fetch_uses_combined_ca_bundle_otherwise(self): - with tempfile.NamedTemporaryFile() as tmp_input, tempfile.NamedTemporaryFile(delete=False) as tmp_output: + with tempfile.NamedTemporaryFile() as tmp_input, \ + tempfile.NamedTemporaryFile(delete=False) as tmp_output: ca_content = 'some\ncontent\n' ca_cert_path = tmp_input.name self._dump_to_file(ca_cert_path, ca_content) -- cgit v1.2.3 From 3a28f215f0fca26387507ec770ee248907014f55 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Sep 2015 12:11:37 -0400 Subject: [style] more pep8 fixes --- src/leap/keymanager/tests/test_keymanager.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 8d4c5da..856d6da 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -411,7 +411,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): PRIVATE_KEY_2, ADDRESS_2) encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, sign=ADDRESS_2, fetch_remote=False) - yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) # when key = yield km.get_key(ADDRESS_2, OpenPGPKey, fetch_remote=False) @@ -423,14 +424,18 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_does_not_update_sign_used_for_recipient(self): # given km = self._key_manager() - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key( + PRIVATE_KEY_2, ADDRESS_2) encdata = yield km.encrypt('data', ADDRESS, OpenPGPKey, sign=ADDRESS_2, fetch_remote=False) - yield km.decrypt(encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) + yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) # when - key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) + key = yield km.get_key( + ADDRESS, OpenPGPKey, private=False, fetch_remote=False) # then self.assertEqual(False, key.sign_used) -- cgit v1.2.3 From cce42536ac2c7588a9df2f6e012bb8991f8bbd75 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 10 Sep 2015 10:36:53 -0400 Subject: [refactor] refactor key parsing so that it can be tested without needing to instantiate the whole OpenPGPScheme object, that receives a soledad instance. --- src/leap/keymanager/openpgp.py | 130 +++++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 71 deletions(-) diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index b8b47d0..069a78e 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # openpgp.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2013-2015 LEAP # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -251,7 +251,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(is_address(address), 'Not an user address: %s' % address) def _gen_key(_): - with self._temporary_gpgwrapper() as gpg: + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: # TODO: inspect result, or use decorator params = gpg.gen_key_input( key_type='RSA', @@ -348,37 +348,25 @@ class OpenPGPScheme(EncryptionScheme): # TODO: add more checks for correct key data. leap_assert(key_data is not None, 'Data does not represent a key.') - with self._temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - gpg.import_keys(key_data) - privkey = None - pubkey = None + priv_info, privkey = process_ascii_key( + key_data, self._gpgbinary, secret=True) + pub_info, pubkey = process_ascii_key( + key_data, self._gpgbinary, secret=False) - try: - privkey = gpg.list_keys(secret=True).pop() - except IndexError: - pass - try: - pubkey = gpg.list_keys(secret=False).pop() # unitary keyring - except IndexError: - return (None, None) - - openpgp_privkey = None - if privkey is not None: - # build private key - openpgp_privkey = self._build_key_from_gpg( - privkey, - gpg.export_keys(privkey['fingerprint'], secret=True)) - leap_check(pubkey['fingerprint'] == privkey['fingerprint'], - 'Fingerprints for public and private key differ.', - errors.KeyFingerprintMismatch) - - # build public key - openpgp_pubkey = self._build_key_from_gpg( - pubkey, - gpg.export_keys(pubkey['fingerprint'], secret=False)) - - return (openpgp_pubkey, openpgp_privkey) + if not pubkey: + return (None, None) + + openpgp_privkey = None + if privkey: + # build private key + openpgp_privkey = self._build_key_from_gpg(priv_info, privkey) + leap_check(pub_info['fingerprint'] == priv_info['fingerprint'], + 'Fingerprints for public and private key differ.', + errors.KeyFingerprintMismatch) + # build public key + openpgp_pubkey = self._build_key_from_gpg(pub_info, pubkey) + + return (openpgp_pubkey, openpgp_privkey) def put_ascii_key(self, key_data, address): """ @@ -439,7 +427,7 @@ class OpenPGPScheme(EncryptionScheme): oldkey = build_key_from_dict(OpenPGPKey, doc.content) if key.fingerprint == oldkey.fingerprint: # in case of an update of the key merge them with gnupg - with self._temporary_gpgwrapper() as gpg: + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: gpg.import_keys(oldkey.key_data) gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() @@ -577,24 +565,7 @@ class OpenPGPScheme(EncryptionScheme): :return: An instance of the key. :rtype: OpenPGPKey """ - expiry_date = None - if key['expires']: - expiry_date = datetime.fromtimestamp(int(key['expires'])) - address = [] - for uid in key['uids']: - address.append(_parse_address(uid)) - - return OpenPGPKey( - address, - gpgbinary=self._gpgbinary, - key_id=key['keyid'], - fingerprint=key['fingerprint'], - key_data=key_data, - private=True if key['type'] == 'sec' else False, - length=int(key['length']), - expiry_date=expiry_date, - refreshed_at=datetime.now(), - ) + return build_gpg_key(key, key_data, self._gpgbinary) def delete_key(self, key): """ @@ -654,21 +625,6 @@ class OpenPGPScheme(EncryptionScheme): # Data encryption, decryption, signing and verifying # - def _temporary_gpgwrapper(self, keys=None): - """ - Return a gpg wrapper that implements the context manager protocol and - contains C{keys}. - - :param keys: keys to conform the keyring. - :type key: list(OpenPGPKey) - - :return: a TempGPGWrapper instance - :rtype: TempGPGWrapper - """ - # TODO do here checks on key_data - return TempGPGWrapper( - keys=keys, gpgbinary=self._gpgbinary) - @staticmethod def _assert_gpg_result_ok(result): """ @@ -713,7 +669,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert_type(sign, OpenPGPKey) leap_assert(sign.private is True) keys.append(sign) - with self._temporary_gpgwrapper(keys) as gpg: + with TempGPGWrapper(keys, self._gpgbinary) as gpg: result = gpg.encrypt( data, pubkey.fingerprint, default_key=sign.key_id if sign else None, @@ -755,7 +711,7 @@ class OpenPGPScheme(EncryptionScheme): leap_assert_type(verify, OpenPGPKey) leap_assert(verify.private is False) keys.append(verify) - with self._temporary_gpgwrapper(keys) as gpg: + with TempGPGWrapper(keys, self._gpgbinary) as gpg: try: result = gpg.decrypt( data, passphrase=passphrase, always_trust=True) @@ -783,7 +739,7 @@ class OpenPGPScheme(EncryptionScheme): :return: Whether C{data} was encrypted using this wrapper. :rtype: bool """ - with self._temporary_gpgwrapper() as gpg: + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: gpgutil = GPGUtilities(gpg) return gpgutil.is_encrypted_asym(data) @@ -814,7 +770,7 @@ class OpenPGPScheme(EncryptionScheme): # result.fingerprint - contains the fingerprint of the key used to # sign. - with self._temporary_gpgwrapper(privkey) as gpg: + with TempGPGWrapper(privkey, self._gpgbinary) as gpg: result = gpg.sign(data, default_key=privkey.key_id, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) @@ -849,7 +805,7 @@ class OpenPGPScheme(EncryptionScheme): """ leap_assert_type(pubkey, OpenPGPKey) leap_assert(pubkey.private is False) - with self._temporary_gpgwrapper(pubkey) as gpg: + with TempGPGWrapper(pubkey, self._gpgbinary) as gpg: result = None if detached_sig is None: result = gpg.verify(data) @@ -867,3 +823,35 @@ class OpenPGPScheme(EncryptionScheme): rfprint = result.fingerprint kfprint = gpgpubkey['fingerprint'] return valid and rfprint == kfprint + + +def process_ascii_key(key_data, gpgbinary, secret=False): + with TempGPGWrapper(gpgbinary=gpgbinary) as gpg: + try: + gpg.import_keys(key_data) + info = gpg.list_keys(secret=secret).pop() + key = gpg.export_keys(info['fingerprint'], secret=secret) + except IndexError: + info = {} + key = None + return info, key + + +def build_gpg_key(key_info, key_data, gpgbinary=None): + expiry_date = None + if key_info['expires']: + expiry_date = datetime.fromtimestamp(int(key_info['expires'])) + address = [] + for uid in key_info['uids']: + address.append(_parse_address(uid)) + + return OpenPGPKey( + address, + gpgbinary=gpgbinary, + key_id=key_info['keyid'], + fingerprint=key_info['fingerprint'], + key_data=key_data, + private=True if key_info['type'] == 'sec' else False, + length=int(key_info['length']), + expiry_date=expiry_date, + refreshed_at=datetime.now()) -- cgit v1.2.3 From 34573a93a19d1d157a271c4ebdbb76ff5a7d1e63 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 24 Sep 2015 12:44:28 -0400 Subject: [refactor] improve readability Improve readability of operations on generic keys, by assigning the class matching the type of key (_wrapper_map[ktype]) at the beginning of each block. in the future, we could pass the type of key (only PGP keys being used at the moment) on initialization of the Keymanager, so we don't have to pass the ktype on each method call. --- src/leap/keymanager/__init__.py | 48 +++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 01f3b6e..4826ba7 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -376,6 +376,8 @@ class KeyManager(object): leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) + _keys = self._wrapper_map[ktype] + emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): @@ -396,13 +398,12 @@ class KeyManager(object): emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( - lambda _: - self._wrapper_map[ktype].get_key(address, private=False)) + lambda _: _keys.get_key(address, private=False)) d.addCallback(key_found) return d # return key if it exists in local database - d = self._wrapper_map[ktype].get_key(address, private=private) + d = _keys.get_key(address, private=private) d.addCallbacks(key_found, key_not_found) return d @@ -448,6 +449,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def signal_finished(key): emit_async( @@ -455,7 +457,8 @@ class KeyManager(object): return key emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) - d = self._wrapper_map[ktype].gen_key(self._address) + + d = _keys.gen_key(self._address) d.addCallback(signal_finished) return d @@ -545,14 +548,15 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def encrypt(keys): pubkey, signkey = keys - encrypted = self._wrapper_map[ktype].encrypt( + encrypted = _keys.encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) pubkey.encr_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) + d = _keys.put_key(pubkey, address) d.addCallback(lambda _: encrypted) return d @@ -597,10 +601,11 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def decrypt(keys): pubkey, privkey = keys - decrypted, signed = self._wrapper_map[ktype].decrypt( + decrypted, signed = _keys.decrypt( data, privkey, passphrase=passphrase, verify=pubkey) if pubkey is None: signature = KeyNotFound(verify) @@ -608,7 +613,7 @@ class KeyManager(object): signature = pubkey if not pubkey.sign_used: pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, verify) + d = _keys.put_key(pubkey, verify) d.addCallback(lambda _: (decrypted, signature)) return d else: @@ -659,9 +664,10 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def sign(privkey): - return self._wrapper_map[ktype].sign( + return _keys.sign( data, privkey, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) @@ -697,14 +703,15 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] def verify(pubkey): - signed = self._wrapper_map[ktype].verify( + signed = _keys.verify( data, pubkey, detached_sig=detached_sig) if signed: if not pubkey.sign_used: pubkey.sign_used = True - d = self._wrapper_map[ktype].put_key(pubkey, address) + d = keys.put_key(pubkey, address) d.addCallback(lambda _: pubkey) return d return pubkey @@ -732,7 +739,8 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(type(key)) - return self._wrapper_map[type(key)].delete_key(key) + _keys = self._wrapper_map[type(key)] + return _keys.delete_key(key) def put_key(self, key, address): """ @@ -752,7 +760,9 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ - self._assert_supported_key_type(type(key)) + ktype = type(key) + self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] if address not in key.address: return defer.fail( @@ -767,14 +777,13 @@ class KeyManager(object): def check_upgrade(old_key): if key.private or can_upgrade(key, old_key): - return self._wrapper_map[type(key)].put_key(key, address) + return _keys.put_key(key, address) else: raise KeyNotValidUpgrade( "Key %s can not be upgraded by new key %s" % (old_key.key_id, key.key_id)) - d = self._wrapper_map[type(key)].get_key(address, - private=key.private) + d = _keys.get_key(address, private=key.private) d.addErrback(old_key_not_found) d.addCallback(check_upgrade) return d @@ -804,7 +813,9 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) - pubkey, privkey = self._wrapper_map[ktype].parse_ascii_key(key) + _keys = self._wrapper_map[ktype] + + pubkey, privkey = _keys.parse_ascii_key(key) pubkey.validation = validation d = self.put_key(pubkey, address) if privkey is not None: @@ -837,6 +848,7 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) + _keys = self._wrapper_map[ktype] logger.info("Fetch key for %s from %s" % (address, uri)) try: @@ -848,7 +860,7 @@ class KeyManager(object): return defer.fail(KeyNotFound(uri)) # XXX parse binary keys - pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) + pubkey, _ = _keys.parse_ascii_key(res.content) if pubkey is None: return defer.fail(KeyNotFound(uri)) -- cgit v1.2.3 From 6a8cd66c656b810fb5052c75fa21002de5330273 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 28 Sep 2015 19:06:20 +0200 Subject: [bug] fix verify keys usage The latests refactor missed one line. --- src/leap/keymanager/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 4826ba7..c7886e0 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -711,7 +711,7 @@ class KeyManager(object): if signed: if not pubkey.sign_used: pubkey.sign_used = True - d = keys.put_key(pubkey, address) + d = _keys.put_key(pubkey, address) d.addCallback(lambda _: pubkey) return d return pubkey -- cgit v1.2.3 From 9a9c53eea49092e80737c84a2f850dd682c33ae3 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 29 Sep 2015 16:36:20 +0200 Subject: [feat] self-repair the keyring if keys get duplicated In some cases in the past keys got stored twice in different documents. Hopefully this issue is solved now, this tries to self-repair the keyring if encounters that. This is not really solving the problem, if it keeps happening we need to investigate the source. - Resolves: #7498 --- changes/bug-7498_multiple_keys | 1 + src/leap/keymanager/openpgp.py | 143 +++++++++++++++++++----------- src/leap/keymanager/tests/__init__.py | 7 ++ src/leap/keymanager/tests/test_openpgp.py | 104 +++++++++++++++++++++- 4 files changed, 204 insertions(+), 51 deletions(-) create mode 100644 changes/bug-7498_multiple_keys diff --git a/changes/bug-7498_multiple_keys b/changes/bug-7498_multiple_keys new file mode 100644 index 0000000..90cf675 --- /dev/null +++ b/changes/bug-7498_multiple_keys @@ -0,0 +1 @@ +- self-repair the keyring if keys get duplicated (Closes: #7485) diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 069a78e..d648137 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -22,6 +22,7 @@ import os import re import shutil import tempfile +import traceback import io @@ -41,6 +42,8 @@ from leap.keymanager.keys import ( TYPE_ADDRESS_PRIVATE_INDEX, KEY_ADDRESS_KEY, KEY_ID_KEY, + KEY_FINGERPRINT_KEY, + KEY_REFRESHED_AT_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -422,41 +425,42 @@ class OpenPGPScheme(EncryptionScheme): :rtype: Deferred """ def check_and_put(docs, key): - if len(docs) == 1: - doc = docs.pop() - oldkey = build_key_from_dict(OpenPGPKey, doc.content) - if key.fingerprint == oldkey.fingerprint: - # in case of an update of the key merge them with gnupg - with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: - gpg.import_keys(oldkey.key_data) - gpg.import_keys(key.key_data) - gpgkey = gpg.list_keys(secret=key.private).pop() - mergedkey = self._build_key_from_gpg( - gpgkey, - gpg.export_keys(gpgkey['fingerprint'], - secret=key.private)) - mergedkey.validation = max( - [key.validation, oldkey.validation]) - mergedkey.last_audited_at = oldkey.last_audited_at - mergedkey.refreshed_at = key.refreshed_at - mergedkey.encr_used = key.encr_used or oldkey.encr_used - mergedkey.sign_used = key.sign_used or oldkey.sign_used - doc.set_json(mergedkey.get_json()) - d = self._soledad.put_doc(doc) - else: - logger.critical( - "Can't put a key whith the same key_id and different " - "fingerprint: %s, %s" - % (key.fingerprint, oldkey.fingerprint)) - d = defer.fail( - errors.KeyFingerprintMismatch(key.fingerprint)) + deferred_repair = defer.succeed(None) + if len(docs) == 0: + return self._soledad.create_doc_from_json(key.get_json()) elif len(docs) > 1: + deferred_repair = self._repair_key_docs(docs, key.key_id) + + doc = docs[0] + oldkey = build_key_from_dict(OpenPGPKey, doc.content) + if key.fingerprint != oldkey.fingerprint: logger.critical( - "There is more than one key with the same key_id %s" - % (key.key_id,)) - d = defer.fail(errors.KeyAttributesDiffer(key.key_id)) - else: - d = self._soledad.create_doc_from_json(key.get_json()) + "Can't put a key whith the same key_id and different " + "fingerprint: %s, %s" + % (key.fingerprint, oldkey.fingerprint)) + return defer.fail( + errors.KeyFingerprintMismatch(key.fingerprint)) + + # in case of an update of the key merge them with gnupg + with TempGPGWrapper(gpgbinary=self._gpgbinary) as gpg: + gpg.import_keys(oldkey.key_data) + gpg.import_keys(key.key_data) + gpgkey = gpg.list_keys(secret=key.private).pop() + mergedkey = self._build_key_from_gpg( + gpgkey, + gpg.export_keys(gpgkey['fingerprint'], + secret=key.private)) + mergedkey.validation = max( + [key.validation, oldkey.validation]) + mergedkey.last_audited_at = oldkey.last_audited_at + mergedkey.refreshed_at = key.refreshed_at + mergedkey.encr_used = key.encr_used or oldkey.encr_used + mergedkey.sign_used = key.sign_used or oldkey.sign_used + doc.set_json(mergedkey.get_json()) + deferred_put = self._soledad.put_doc(doc) + + d = defer.gatherResults([deferred_put, deferred_repair]) + d.addCallback(lambda res: res[0]) return d d = self._soledad.get_from_index( @@ -533,14 +537,21 @@ class OpenPGPScheme(EncryptionScheme): self.KEY_TYPE, key_id, '1' if private else '0') - d.addCallback(get_doc, key_id) + d.addCallback(get_doc, key_id, activedoc) return d - def get_doc(doclist, key_id): - leap_assert( - len(doclist) is 1, - 'There is %d keys for id %s!' % (len(doclist), key_id)) - return doclist.pop() + def get_doc(doclist, key_id, activedoc): + if len(doclist) == 0: + logger.warning('There is no key for id %s! Self-repairing it.' + % (key_id)) + d = self._soledad.delete_doc(activedoc) + d.addCallback(lambda _: None) + return d + elif len(doclist) > 1: + d = self._repair_key_docs(doclist, key_id) + d.addCallback(lambda _: doclist[0]) + return d + return doclist[0] d = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, @@ -598,18 +609,20 @@ class OpenPGPScheme(EncryptionScheme): def delete_key(docs): if len(docs) == 0: raise errors.KeyNotFound(key) - if len(docs) > 1: - logger.critical("There is more than one key for key_id %s" - % key.key_id) - - doc = None - for d in docs: - if d.content['fingerprint'] == key.fingerprint: - doc = d - break - if doc is None: + elif len(docs) > 1: + logger.warning("There is more than one key for key_id %s" + % key.key_id) + + has_deleted = False + deferreds = [] + for doc in docs: + if doc.content['fingerprint'] == key.fingerprint: + d = self._soledad.delete_doc(doc) + deferreds.append(d) + has_deleted = True + if not has_deleted: raise errors.KeyNotFound(key) - return self._soledad.delete_doc(doc) + return defer.gatherResults(deferreds) d = self._soledad.get_from_index( TYPE_ID_PRIVATE_INDEX, @@ -621,6 +634,36 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(delete_key) return d + def _repair_key_docs(self, doclist, key_id): + """ + If there is more than one key for a key id try to self-repair it + + :return: a Deferred that will be fired once all the deletions are + completed + :rtype: Deferred + """ + logger.error("BUG ---------------------------------------------------") + logger.error("There is more than one key with the same key_id %s:" + % (key_id,)) + + def log_key_doc(doc): + logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], + doc.content[KEY_FINGERPRINT_KEY])) + + doclist.sort(key=lambda doc: doc.content[KEY_REFRESHED_AT_KEY], + reverse=True) + log_key_doc(doclist[0]) + deferreds = [] + for doc in doclist[1:]: + log_key_doc(doc) + d = self._soledad.delete_doc(doc) + deferreds.append(d) + + logger.error("") + logger.error(traceback.extract_stack()) + logger.error("BUG (please report above info) ------------------------") + return defer.gatherResults(deferreds, consumeErrors=True) + # # Data encryption, decryption, signing and verifying # diff --git a/src/leap/keymanager/tests/__init__.py b/src/leap/keymanager/tests/__init__.py index 9b95e1a..cd612c4 100644 --- a/src/leap/keymanager/tests/__init__.py +++ b/src/leap/keymanager/tests/__init__.py @@ -66,9 +66,15 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): for private in [True, False]: d = km.get_all_keys(private=private) d.addCallback(delete_keys) + d.addCallback(check_deleted, private) deferreds.append(d) return gatherResults(deferreds) + def check_deleted(_, private): + d = km.get_all_keys(private=private) + d.addCallback(lambda keys: self.assertEqual(keys, [])) + return d + # wait for the indexes to be ready for the tear down d = km._wrapper_map[OpenPGPKey].deferred_indexes d.addCallback(get_and_delete_keys) @@ -91,6 +97,7 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): # key 24D18DDF: public key "Leap Test Key " +KEY_ID = "2F455E2824D18DDF" KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" PUBLIC_KEY = """ -----BEGIN PGP PUBLIC KEY BLOCK----- diff --git a/src/leap/keymanager/tests/test_openpgp.py b/src/leap/keymanager/tests/test_openpgp.py index 5f85c74..bae83db 100644 --- a/src/leap/keymanager/tests/test_openpgp.py +++ b/src/leap/keymanager/tests/test_openpgp.py @@ -21,12 +21,15 @@ Tests for the OpenPGP support on Key Manager. """ -from twisted.internet.defer import inlineCallbacks +from datetime import datetime +from mock import Mock +from twisted.internet.defer import inlineCallbacks, gatherResults, succeed from leap.keymanager import ( KeyNotFound, openpgp, ) +from leap.keymanager.keys import TYPE_ID_PRIVATE_INDEX from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, @@ -34,6 +37,7 @@ from leap.keymanager.tests import ( ADDRESS_2, KEY_FINGERPRINT, PUBLIC_KEY, + KEY_ID, PUBLIC_KEY_2, PRIVATE_KEY, PRIVATE_KEY_2, @@ -247,6 +251,104 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): validsign = pgp.verify(data, pubkey, detached_sig=signature) self.assertTrue(validsign) + @inlineCallbacks + def test_self_repair_three_keys(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ID_PRIVATE_INDEX and + args[2] == KEY_ID): + k1 = OpenPGPKey(ADDRESS, key_id="1", + refreshed_at=datetime(2005, 1, 1)) + k2 = OpenPGPKey(ADDRESS, key_id="2", + refreshed_at=datetime(2007, 1, 1)) + k3 = OpenPGPKey(ADDRESS, key_id="3", + refreshed_at=datetime(2001, 1, 1)) + d1 = self._soledad.create_doc_from_json(k1.get_json()) + d2 = self._soledad.create_doc_from_json(k2.get_json()) + d3 = self._soledad.create_doc_from_json(k3.get_json()) + return gatherResults([d1, d2, d3]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + key = yield pgp.get_key(ADDRESS, private=False) + + try: + self.assertEqual(key.key_id, "2") + self.assertEqual(self._soledad.delete_doc.call_count, 2) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + + @inlineCallbacks + def test_self_repair_no_keys(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ID_PRIVATE_INDEX and + args[2] == KEY_ID): + return succeed([]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + try: + yield self.assertFailure(pgp.get_key(ADDRESS, private=False), + KeyNotFound) + self.assertEqual(self._soledad.delete_doc.call_count, 1) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + + @inlineCallbacks + def test_self_repair_put_keys(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=self.gpg_binary_path) + + get_from_index = self._soledad.get_from_index + delete_doc = self._soledad.delete_doc + + def my_get_from_index(*args): + if (args[0] == TYPE_ID_PRIVATE_INDEX and + args[2] == KEY_ID): + k1 = OpenPGPKey(ADDRESS, key_id="1", + fingerprint=KEY_FINGERPRINT, + refreshed_at=datetime(2005, 1, 1)) + k2 = OpenPGPKey(ADDRESS, key_id="2", + fingerprint=KEY_FINGERPRINT, + refreshed_at=datetime(2007, 1, 1)) + k3 = OpenPGPKey(ADDRESS, key_id="3", + fingerprint=KEY_FINGERPRINT, + refreshed_at=datetime(2001, 1, 1)) + d1 = self._soledad.create_doc_from_json(k1.get_json()) + d2 = self._soledad.create_doc_from_json(k2.get_json()) + d3 = self._soledad.create_doc_from_json(k3.get_json()) + return gatherResults([d1, d2, d3]) + return get_from_index(*args) + + self._soledad.get_from_index = my_get_from_index + self._soledad.delete_doc = Mock(return_value=succeed(None)) + + try: + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + self.assertEqual(self._soledad.delete_doc.call_count, 2) + finally: + self._soledad.get_from_index = get_from_index + self._soledad.delete_doc = delete_doc + def _assert_key_not_found(self, pgp, address, private=False): d = pgp.get_key(address, private=private) return self.assertFailure(d, KeyNotFound) -- cgit v1.2.3