From fbb711174fc3bb6222c9656546bf7f3bb279e130 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 25 Jul 2014 19:26:19 -0500 Subject: gpg.verify_file() gets the data as a filename not as a binary stream --- src/leap/keymanager/openpgp.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 950d022..46ae2aa 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -649,13 +649,17 @@ class OpenPGPScheme(EncryptionScheme): result = gpg.verify(data) else: # to verify using a detached sig we have to use - # gpg.verify_file(), which receives the data as a binary - # stream and the name of a file containing the signature. + # gpg.verify_file(), which receives the name of + # files containing the date and the signature. sf, sfname = tempfile.mkstemp() with os.fdopen(sf, 'w') as sfd: sfd.write(detached_sig) - with closing(_make_binary_stream(data, gpg._encoding)) as df: - result = gpg.verify_file(df, sig_file=sfname) + df, dfname = tempfile.mkstemp() + with os.fdopen(df, 'w') as sdd: + sdd.write(data) + result = gpg.verify_file(dfname, sig_file=sfname) + os.unlink(sfname) + os.unlink(dfname) gpgpubkey = gpg.list_keys().pop() valid = result.valid rfprint = result.fingerprint -- cgit v1.2.3 From b05c3f0020ab4ca4761cc1042e55cc37b007d297 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 25 Jul 2014 19:28:15 -0500 Subject: Fix test_send_key --- src/leap/keymanager/tests/test_keymanager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index e2558e4..f89dcd9 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -147,8 +147,8 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): for key in km.get_all_keys_in_local_db(private=True): km._wrapper_map[key.__class__].delete_key(key) - def _key_manager(self, user=ADDRESS, url=''): - return KeyManager(user, url, self._soledad, + def _key_manager(self, user=ADDRESS, url='', token=None): + return KeyManager(user, url, self._soledad, token=token, gpgbinary=GPG_BINARY_PATH) @@ -387,7 +387,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ Test that request is well formed when sending keys to server. """ - km = self._key_manager() + token = "mytoken" + km = self._key_manager(token=token) km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) km._fetcher.put = Mock() # the following data will be used on the send @@ -404,7 +405,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') km._fetcher.put.assert_called_once_with( url, data=data, verify='capath', - cookies={'_session_id': 'sessionid'}, + headers={'Authorization': 'Token token=%s' % token}, ) def test__fetch_keys_from_server(self): -- cgit v1.2.3 From 8f01d6b818a36014e21a13baaf6c5112ef9e9a35 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 27 Aug 2014 12:59:28 -0300 Subject: Fix call to python-gnupg verify_file() method (#6022). --- src/leap/keymanager/openpgp.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 46ae2aa..ee37a34 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -22,12 +22,11 @@ import os import re import shutil import tempfile +import io -from contextlib import closing from gnupg import GPG from gnupg.gnupg import GPGUtilities -from gnupg._util import _make_binary_stream from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors @@ -649,17 +648,13 @@ class OpenPGPScheme(EncryptionScheme): result = gpg.verify(data) else: # to verify using a detached sig we have to use - # gpg.verify_file(), which receives the name of - # files containing the date and the signature. + # gpg.verify_file(), which receives the data as a binary + # stream and the name of a file containing the signature. sf, sfname = tempfile.mkstemp() with os.fdopen(sf, 'w') as sfd: sfd.write(detached_sig) - df, dfname = tempfile.mkstemp() - with os.fdopen(df, 'w') as sdd: - sdd.write(data) - result = gpg.verify_file(dfname, sig_file=sfname) + result = gpg.verify_file(io.BytesIO(data), sig_file=sfname) os.unlink(sfname) - os.unlink(dfname) gpgpubkey = gpg.list_keys().pop() valid = result.valid rfprint = result.fingerprint -- cgit v1.2.3 From c9c4da50f5d1bc329d1bb66575068b1eea9503e6 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 15 Sep 2014 14:07:26 -0500 Subject: Update docstrings of the public API --- src/leap/keymanager/__init__.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 41f352e..65352a8 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -82,12 +82,12 @@ class KeyManager(object): gpgbinary=None): """ Initialize a Key Manager for user's C{address} with provider's - nickserver reachable in C{url}. + nickserver reachable in C{nickserver_uri}. - :param address: The address of the user of this Key Manager. + :param address: The email address of the user of this Key Manager. :type address: str - :param url: The URL of the nickserver. - :type url: str + :param nickserver_uri: The URI of the nickserver. + :type nickserver_uri: str :param soledad: A Soledad instance for local storage of keys. :type soledad: leap.soledad.Soledad :param token: The token for interacting with the webapp API. @@ -98,7 +98,7 @@ class KeyManager(object): :type api_uri: str :param api_version: The version of the webapp API. :type api_version: str - :param uid: The users' UID. + :param uid: The user's UID. :type uid: str :param gpgbinary: Name for GnuPG binary executable. :type gpgbinary: C{str} @@ -228,12 +228,6 @@ class KeyManager(object): Public key bound to user's is sent to provider, which will sign it and replace any prior keys for the same address in its database. - If C{send_private} is True, then the private key is encrypted with - C{password} and sent to server in the same request, together with a - hash string of user's address and password. The encrypted private key - will be saved in the server in a way it is publicly retrievable - through the hash string. - :param ktype: The type of the key. :type ktype: KeyType @@ -275,6 +269,9 @@ class KeyManager(object): :type ktype: KeyType :param private: Look for a private key instead of a public one? :type private: bool + :param fetch_remote: If key not found in local storage try to fetch + from nickserver + :type fetch_remote: bool :return: A key of type C{ktype} bound to C{address}. :rtype: EncryptionKey @@ -311,6 +308,9 @@ class KeyManager(object): """ Return all keys stored in local database. + :param private: Include private keys + :type private: bool + :return: A list with all keys in local db. :rtype: list """ @@ -416,6 +416,9 @@ class KeyManager(object): :type data: str :param pubkey: The key used to encrypt. :type pubkey: EncryptionKey + :param passphrase: The passphrase for the secret key used for the + signature. + :type passphrase: str :param sign: The key used for signing. :type sign: EncryptionKey :param cipher_algo: The cipher algorithm to use. @@ -448,7 +451,7 @@ class KeyManager(object): :rtype: str :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + C{verify} key. """ leap_assert_type(privkey, EncryptionKey) leap_assert( -- cgit v1.2.3 From 2e8468ef8a6a763cd100c547b7e658c94d87bdd0 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 15 Sep 2014 14:07:58 -0500 Subject: Remove refresh_keys Never should be done a bulk upload of keys. The updating of the keys should not be a task for the user of the keymanager. Keys will be updated by the keymanager in a background worker one per one. --- src/leap/keymanager/__init__.py | 13 ------------- src/leap/keymanager/tests/test_keymanager.py | 17 ----------------- 2 files changed, 30 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 65352a8..4563d80 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -324,19 +324,6 @@ class KeyManager(object): KEYMANAGER_KEY_TAG, '1' if private else '0')) - def refresh_keys(self): - """ - Fetch keys from nickserver and update them locally. - """ - addresses = set(map( - lambda doc: doc.address, - self.get_all_keys_in_local_db(private=False))) - for address in addresses: - # do not attempt to refresh our own key - if address == self._address: - continue - self._fetch_keys_from_server(address) - def gen_key(self, ktype): """ Generate a key of type C{ktype} bound to the user's address. diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index f89dcd9..932f260 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -437,23 +437,6 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): verify='cacertpath', ) - def test_refresh_keys_does_not_refresh_own_key(self): - """ - Test that refreshing keys will not attempt to refresh our own key. - """ - km = self._key_manager() - # we add 2 keys but we expect it to only refresh the second one. - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY_2) - # mock the key fetching - km._fetch_keys_from_server = Mock(return_value=[]) - km.ca_cert_path = '' # some bogus path so the km does not complain. - # do the refreshing - km.refresh_keys() - km._fetch_keys_from_server.assert_called_once_with( - ADDRESS_2 - ) - def test_get_key_fetches_from_server(self): """ Test that getting a key successfuly fetches from server. -- cgit v1.2.3 From 539caa6c9a860d1be9ebc3295b258e9611efc3bf Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 15 Sep 2014 14:13:49 -0500 Subject: Pass the cipher_algo to the encrypt wrapper --- src/leap/keymanager/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 4563d80..fe9f02c 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -418,7 +418,7 @@ class KeyManager(object): leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') leap_assert(pubkey.private is False, 'Key is not public.') return self._wrapper_map[pubkey.__class__].encrypt( - data, pubkey, passphrase, sign) + data, pubkey, passphrase, sign, cipher_algo=cipher_algo) def decrypt(self, data, privkey, passphrase=None, verify=None): """ -- cgit v1.2.3 From 4019e2fa65f81823fdcb0b81f022e00f168403dd Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 16 Sep 2014 10:55:16 -0500 Subject: Remove get_key_from_cache We only need to cache the fetch with a sort timeout. The tests that fetches keys now have to use different keys or will be cached. --- src/leap/keymanager/__init__.py | 8 +------- src/leap/keymanager/tests/test_keymanager.py | 8 ++++---- 2 files changed, 5 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index fe9f02c..37eb223 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -189,6 +189,7 @@ class KeyManager(object): res.raise_for_status() return res + @memoized_method(invalidation=300) def _fetch_keys_from_server(self, address): """ Fetch keys bound to C{address} from nickserver and insert them in @@ -249,13 +250,6 @@ class KeyManager(object): self._put(uri, data) signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) - @memoized_method - def get_key_from_cache(self, *args, **kwargs): - """ - Public interface to `get_key`, that is memoized. - """ - return self.get_key(*args, **kwargs) - def get_key(self, address, ktype, private=False, fetch_remote=True): """ Return a key of type C{ktype} bound to C{address}. diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 932f260..f9ba04f 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -448,7 +448,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): headers = {'content-type': 'application/json'} def json(self): - return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} + return {'address': ADDRESS, 'openpgp': PUBLIC_KEY} def raise_for_status(self): pass @@ -458,13 +458,13 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.ca_cert_path = 'cacertpath' # try to key get without fetching from server self.assertRaises( - KeyNotFound, km.get_key, ADDRESS_2, OpenPGPKey, + KeyNotFound, km.get_key, ADDRESS, OpenPGPKey, fetch_remote=False ) # try to get key fetching from server. - key = km.get_key(ADDRESS_2, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS_2, key.address) + self.assertEqual(ADDRESS, key.address) class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 7e191e3b5dd5ce0550ea7ca973897d917b81b675 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 16 Sep 2014 11:01:43 -0500 Subject: Rename method get_all_keys_in_local_db -> get_all_keys --- src/leap/keymanager/__init__.py | 2 +- src/leap/keymanager/tests/test_keymanager.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 37eb223..3888c0a 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -298,7 +298,7 @@ class KeyManager(object): return key - def get_all_keys_in_local_db(self, private=False): + def get_all_keys(self, private=False): """ Return all keys stored in local database. diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index f9ba04f..7192bfb 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -142,9 +142,9 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): def tearDown(self): km = self._key_manager() - for key in km.get_all_keys_in_local_db(): + for key in km.get_all_keys(): km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys_in_local_db(private=True): + for key in km.get_all_keys(private=True): km._wrapper_map[key.__class__].delete_key(key) def _key_manager(self, user=ADDRESS, url='', token=None): @@ -343,12 +343,12 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) # get public keys - keys = km.get_all_keys_in_local_db(False) + keys = km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertEqual(ADDRESS, keys[0].address) self.assertFalse(keys[0].private) # get private keys - keys = km.get_all_keys_in_local_db(True) + keys = km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertEqual(ADDRESS, keys[0].address) self.assertTrue(keys[0].private) -- cgit v1.2.3 From 788d0e3f2a442bed2f7c3f292f1ba6fd8955e155 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 16 Sep 2014 11:27:40 -0500 Subject: Remove parse_openpgp_ascii_key Don't fit with the logic of the keymanager and it's not use except for some commented code in bitmask_client --- src/leap/keymanager/__init__.py | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 3888c0a..bd85c2d 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -495,20 +495,6 @@ class KeyManager(object): return self._wrapper_map[pubkey.__class__].verify( data, pubkey, detached_sig=detached_sig) - def parse_openpgp_ascii_key(self, key_data): - """ - Parses an ascii armored key (or key pair) data and returns - the OpenPGPKey keys. - - :param key_data: the key data to be parsed. - :type key_data: str or unicode - - :returns: the public key and private key (if applies) for that data. - :rtype: (public, private) -> tuple(OpenPGPKey, OpenPGPKey) - the tuple may have one or both components None - """ - return self._wrapper_map[OpenPGPKey].parse_ascii_key(key_data) - def delete_key(self, key): """ Remove C{key} from storage. -- cgit v1.2.3 From 13eafd0ad9a34581f37f5c0adde4ab59d13f0b85 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 8 Oct 2014 19:28:33 -0500 Subject: Accept ascii keys on put_key --- src/leap/keymanager/__init__.py | 9 ++++++--- src/leap/keymanager/tests/test_keymanager.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index bd85c2d..da679ac 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -515,11 +515,14 @@ class KeyManager(object): """ Put C{key} in local storage. - :param key: The key to be stored. - :type key: OpenPGPKey + :param key: The key to be stored. It can be ascii key or an OpenPGPKey + :type key: str or OpenPGPKey """ try: - self._wrapper_map[type(key)].put_key(key) + if isinstance(key, basestring): + self._wrapper_map[OpenPGPKey].put_ascii_key(key) + else: + self._wrapper_map[type(key)].put_key(key) except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 7192bfb..39b729d 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -466,6 +466,17 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertIsInstance(key, OpenPGPKey) self.assertEqual(ADDRESS, key.address) + def test_put_key_ascii(self): + """ + Test that putting ascii key works + """ + km = self._key_manager(url='http://nickserver.domain') + + km.put_key(PUBLIC_KEY) + key = km.get_key(ADDRESS, OpenPGPKey) + self.assertIsInstance(key, OpenPGPKey) + self.assertEqual(ADDRESS, key.address) + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 2c8cfffad0cf214951628b771db2322533a8fe50 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 1 Aug 2014 10:20:27 -0500 Subject: Implement 'fetch_key' for ascii keys binary keys support is still missing --- src/leap/keymanager/__init__.py | 31 ++++++++++++++++- src/leap/keymanager/openpgp.py | 5 ++- src/leap/keymanager/tests/test_keymanager.py | 50 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index da679ac..c3423d9 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -48,7 +48,7 @@ from leap.common.events import signal from leap.common.events import events_pb2 as proto from leap.common.decorators import memoized_method -from leap.keymanager.errors import KeyNotFound +from leap.keymanager.errors import KeyNotFound, KeyAttributesDiffer from leap.keymanager.keys import ( EncryptionKey, @@ -526,6 +526,35 @@ class KeyManager(object): except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + def fetch_key(self, address, uri, ktype): + """ + Fetch a public key for C{address} from the network and put it in + local storage. + + Raises C{openpgp.errors.KeyNotFound} if not valid key on C{uri}. + Raises C{openpgp.errors.KeyAttributesDiffer} if address don't match + any uid on the key. + + :param address: The email address of the key. + :type address: str + :param uri: The URI of the key. + :type uri: str + :param ktype: The type of the key. + :type ktype: KeyType + """ + res = self._get(uri) + if not res.ok: + raise KeyNotFound(uri) + + # XXX parse binary keys + pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) + if pubkey is None: + raise KeyNotFound(uri) + if pubkey.address != address: + raise KeyAttributesDiffer("UID %s found, but expected %s" + % (pubkey.address, address)) + self.put_key(pubkey) + from ._version import get_versions __version__ = get_versions()['version'] del get_versions diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index ee37a34..6a825cd 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -327,7 +327,10 @@ class OpenPGPScheme(EncryptionScheme): privkey = gpg.list_keys(secret=True).pop() except IndexError: pass - pubkey = gpg.list_keys(secret=False).pop() # unitary keyring + try: + pubkey = gpg.list_keys(secret=False).pop() # unitary keyring + except IndexError: + return (None, None) # extract adress from first uid on key match = re.match(mail_regex, pubkey['uids'].pop()) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 39b729d..65f8f39 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -28,6 +28,7 @@ from leap.keymanager import ( KeyManager, openpgp, KeyNotFound, + KeyAttributesDiffer, errors, ) from leap.keymanager.openpgp import OpenPGPKey @@ -477,6 +478,55 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): self.assertIsInstance(key, OpenPGPKey) self.assertEqual(ADDRESS, key.address) + def test_fetch_uri_ascii_key(self): + """ + Test that fetch key downloads the ascii key and gets included in + the local storage + """ + km = self._key_manager() + + class Response(object): + ok = True + content = PUBLIC_KEY + + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + + km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + + def test_fetch_uri_empty_key(self): + """ + Test that fetch key raises KeyNotFound if no key in the url + """ + km = self._key_manager() + + class Response(object): + ok = True + content = "" + + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + self.assertRaises(KeyNotFound, km.fetch_key, + ADDRESS, "http://site.domain/key", OpenPGPKey) + + def test_fetch_uri_address_differ(self): + """ + Test that fetch key raises KeyAttributesDiffer if the address + don't match + """ + km = self._key_manager() + + class Response(object): + ok = True + content = PUBLIC_KEY + + km._fetcher.get = Mock(return_value=Response()) + km.ca_cert_path = 'cacertpath' + self.assertRaises(KeyAttributesDiffer, km.fetch_key, + ADDRESS_2, "http://site.domain/key", OpenPGPKey) + class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): -- cgit v1.2.3 From 3ebb6be0c628b3a2b45a28690b6b6dc1bb2ae850 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 12 Oct 2014 03:21:47 -0500 Subject: Basic validation levels support --- src/leap/keymanager/__init__.py | 96 ++++++++--- src/leap/keymanager/errors.py | 6 + src/leap/keymanager/keys.py | 16 +- src/leap/keymanager/openpgp.py | 3 +- src/leap/keymanager/tests/__init__.py | 236 +++++++++++++++++++++++++++ src/leap/keymanager/tests/test_keymanager.py | 234 ++------------------------ src/leap/keymanager/tests/test_validation.py | 150 +++++++++++++++++ src/leap/keymanager/validation.py | 94 +++++++++++ 8 files changed, 594 insertions(+), 241 deletions(-) create mode 100644 src/leap/keymanager/tests/test_validation.py create mode 100644 src/leap/keymanager/validation.py (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index c3423d9..156aaf8 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -48,7 +48,12 @@ from leap.common.events import signal from leap.common.events import events_pb2 as proto from leap.common.decorators import memoized_method -from leap.keymanager.errors import KeyNotFound, KeyAttributesDiffer +from leap.keymanager.errors import ( + KeyNotFound, + KeyAddressMismatch, + KeyNotValidUpgrade +) +from leap.keymanager.validation import ValidationLevel, can_upgrade from leap.keymanager.keys import ( EncryptionKey, @@ -208,8 +213,11 @@ class KeyManager(object): server_keys = res.json() # insert keys in local database if self.OPENPGP_KEY in server_keys: - self._wrapper_map[OpenPGPKey].put_ascii_key( - server_keys['openpgp']) + self.put_raw_key( + server_keys['openpgp'], + OpenPGPKey, + address=address, + validation=ValidationLevel.Provider_Trust) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: raise KeyNotFound(address) @@ -230,7 +238,7 @@ class KeyManager(object): replace any prior keys for the same address in its database. :param ktype: The type of the key. - :type ktype: KeyType + :type ktype: subclass of EncryptionKey :raise KeyNotFound: If the key was not found in local database. """ @@ -260,7 +268,7 @@ class KeyManager(object): :param address: The address bound to the key. :type address: str :param ktype: The type of the key. - :type ktype: KeyType + :type ktype: subclass of EncryptionKey :param private: Look for a private key instead of a public one? :type private: bool :param fetch_remote: If key not found in local storage try to fetch @@ -323,7 +331,7 @@ class KeyManager(object): Generate a key of type C{ktype} bound to the user's address. :param ktype: The type of the key. - :type ktype: KeyType + :type ktype: subclass of EncryptionKey :return: The generated key. :rtype: EncryptionKey @@ -515,32 +523,74 @@ class KeyManager(object): """ Put C{key} in local storage. - :param key: The key to be stored. It can be ascii key or an OpenPGPKey - :type key: str or OpenPGPKey + :param key: The key to be stored + :type key: EncryptionKey + :raises KeyNotValidUpdate: if a key with the same uid exists and the + new one is not a valid update for it """ try: - if isinstance(key, basestring): - self._wrapper_map[OpenPGPKey].put_ascii_key(key) - else: + old_key = self._wrapper_map[type(key)].get_key(key.address, + private=key.private) + except KeyNotFound: + old_key = None + + if key.private or can_upgrade(key, old_key): + try: self._wrapper_map[type(key)].put_key(key) - except IndexError as e: - leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + except IndexError as e: + leap_assert( + False, "Unsupported key type. Error {0!r}".format(e)) + else: + raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s" + % (old_key.key_id, key.key_id)) - def fetch_key(self, address, uri, ktype): + def put_raw_key(self, key, ktype, address=None, + validation=ValidationLevel.Weak_Chain): + """ + Put C{key} in local storage. + + :param key: The ascii key to be stored + :type key: str + :param ktype: the type of the key. + :type ktype: subclass of EncryptionKey + :param address: if set used to check that the key is for this address + :type address: str + :param validation: validation level for this key + (default: 'Weak_Chain') + :type validation: ValidationLevel + + :raises KeyAddressMismatch: if address doesn't match any uid on the key + :raises KeyNotValidUpdate: if a key with the same uid exists and the + new one is not a valid update for it + """ + pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) + if address is not None and address != pubkey.address: + raise KeyAddressMismatch("Key UID %s, but expected %s" + % (pubkey.address, address)) + + pubkey.validation = validation + self.put_key(pubkey) + + def fetch_key(self, address, uri, ktype, + validation=ValidationLevel.Weak_Chain): """ Fetch a public key for C{address} from the network and put it in local storage. - Raises C{openpgp.errors.KeyNotFound} if not valid key on C{uri}. - Raises C{openpgp.errors.KeyAttributesDiffer} if address don't match - any uid on the key. - :param address: The email address of the key. :type address: str :param uri: The URI of the key. :type uri: str - :param ktype: The type of the key. - :type ktype: KeyType + :param ktype: the type of the key. + :type ktype: subclass of EncryptionKey + :param validation: validation level for this key + (default: 'Weak_Chain') + :type validation: ValidationLevel + + :raises KeyNotFound: if not valid key on C{uri} + :raises KeyAddressMismatch: if address doesn't match any uid on the key + :raises KeyNotValidUpdate: if a key with the same uid exists and the + new one is not a valid update for it """ res = self._get(uri) if not res.ok: @@ -551,8 +601,10 @@ class KeyManager(object): if pubkey is None: raise KeyNotFound(uri) if pubkey.address != address: - raise KeyAttributesDiffer("UID %s found, but expected %s" - % (pubkey.address, address)) + raise KeyAddressMismatch("UID %s found, but expected %s" + % (pubkey.address, address)) + + pubkey.validation = validation self.put_key(pubkey) from ._version import get_versions diff --git a/src/leap/keymanager/errors.py b/src/leap/keymanager/errors.py index ebe4fd5..f896582 100644 --- a/src/leap/keymanager/errors.py +++ b/src/leap/keymanager/errors.py @@ -96,3 +96,9 @@ class KeyFingerprintMismatch(Exception): """ A mismatch between fingerprints. """ + + +class KeyNotValidUpgrade(Exception): + """ + Already existing key can not be upgraded with the new key + """ diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index ec1bfeb..ecb0a36 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -25,12 +25,17 @@ try: import simplejson as json except ImportError: import json # noqa +import logging import re from abc import ABCMeta, abstractmethod from leap.common.check import leap_assert +from leap.keymanager.validation import ValidationLevel, toValidationLevel + +logger = logging.getLogger(__name__) + # # Dictionary keys used for storing cryptographic keys. @@ -106,6 +111,13 @@ def build_key_from_dict(kClass, address, kdict): leap_assert( address == kdict[KEY_ADDRESS_KEY], 'Wrong address in key data.') + try: + validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) + except ValueError: + logger.error("Not valid validation level (%s) for key %s", + (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) + validation = ValidationLevel.Weak_Chain + return kClass( address, key_id=kdict[KEY_ID_KEY], @@ -116,7 +128,7 @@ def build_key_from_dict(kClass, address, kdict): expiry_date=kdict[KEY_EXPIRY_DATE_KEY], first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], - validation=kdict[KEY_VALIDATION_KEY], # TODO: verify for validation. + validation=validation, ) @@ -173,7 +185,7 @@ class EncryptionKey(object): KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, KEY_EXPIRY_DATE_KEY: self.expiry_date, - KEY_VALIDATION_KEY: self.validation, + KEY_VALIDATION_KEY: str(self.validation), KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 6a825cd..57a7754 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -38,6 +38,7 @@ from leap.keymanager.keys import ( KEYMANAGER_KEY_TAG, TAGS_ADDRESS_PRIVATE_INDEX, ) +from leap.keymanager.validation import ValidationLevel logger = logging.getLogger(__name__) @@ -183,7 +184,7 @@ def _build_key_from_gpg(address, key, key_data): private=True if key['type'] == 'sec' else False, length=key['length'], expiry_date=key['expires'], - validation=None, # TODO: verify for validation. + validation=ValidationLevel.Weak_Chain, ) diff --git a/src/leap/keymanager/tests/__init__.py b/src/leap/keymanager/tests/__init__.py index e69de29..1ea33b5 100644 --- a/src/leap/keymanager/tests/__init__.py +++ b/src/leap/keymanager/tests/__init__.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# test_keymanager.py +# Copyright (C) 2013 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Base classes for the Key Manager tests. +""" + +from mock import Mock + +from leap.common.testing.basetest import BaseLeapTest +from leap.soledad.client import Soledad +from leap.keymanager import KeyManager + + +ADDRESS = 'leap@leap.se' +# XXX discover the gpg binary path +GPG_BINARY_PATH = '/usr/bin/gpg' + + +class KeyManagerWithSoledadTestCase(BaseLeapTest): + + def setUp(self): + # mock key fetching and storing so Soledad doesn't fail when trying to + # reach the server. + Soledad._get_secrets_from_shared_db = Mock(return_value=None) + Soledad._put_secrets_in_shared_db = Mock(return_value=None) + + class MockSharedDB(object): + + get_doc = Mock(return_value=None) + put_doc = Mock() + lock = Mock(return_value=('atoken', 300)) + unlock = Mock(return_value=True) + + def __call__(self): + return self + + Soledad._shared_db = MockSharedDB() + + self._soledad = Soledad( + u"leap@leap.se", + u"123456", + secrets_path=self.tempdir + "/secret.gpg", + local_db_path=self.tempdir + "/soledad.u1db", + server_url='', + cert_file=None, + auth_token=None, + ) + + def tearDown(self): + km = self._key_manager() + for key in km.get_all_keys(): + km._wrapper_map[key.__class__].delete_key(key) + for key in km.get_all_keys(private=True): + km._wrapper_map[key.__class__].delete_key(key) + + def _key_manager(self, user=ADDRESS, url='', token=None): + return KeyManager(user, url, self._soledad, token=token, + gpgbinary=GPG_BINARY_PATH) + + +# key 24D18DDF: public key "Leap Test Key " +KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" +PUBLIC_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +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 +=MuOY +-----END PGP PUBLIC KEY BLOCK----- +""" +PRIVATE_KEY = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +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 +=JTFu +-----END PGP PRIVATE KEY BLOCK----- +""" diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 65f8f39..1bd6a2e 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -1,4 +1,4 @@ -## -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # test_keymanager.py # Copyright (C) 2013 LEAP # @@ -23,12 +23,10 @@ Tests for the Key Manager. from mock import Mock from leap.common.testing.basetest import BaseLeapTest -from leap.soledad.client import Soledad from leap.keymanager import ( - KeyManager, openpgp, KeyNotFound, - KeyAttributesDiffer, + KeyAddressMismatch, errors, ) from leap.keymanager.openpgp import OpenPGPKey @@ -36,11 +34,21 @@ from leap.keymanager.keys import ( is_address, build_key_from_dict, ) +from leap.keymanager.validation import ( + ValidationLevel, + toValidationLevel +) +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + KEY_FINGERPRINT, + PUBLIC_KEY, + PRIVATE_KEY, + GPG_BINARY_PATH +) -ADDRESS = 'leap@leap.se' ADDRESS_2 = 'anotheruser@leap.se' -GPG_BINARY_PATH = '/usr/bin/gpg' class KeyManagerUtilTestCase(BaseLeapTest): @@ -76,7 +84,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'expiry_date': 'expiry_date', 'first_seen_at': 'first_seen_at', 'last_audited_at': 'last_audited_at', - 'validation': 'validation', + 'validation': str(ValidationLevel.Weak_Chain), } key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) self.assertEqual( @@ -107,52 +115,10 @@ class KeyManagerUtilTestCase(BaseLeapTest): kdict['last_audited_at'], key.last_audited_at, 'Wrong data in key.') self.assertEqual( - kdict['validation'], key.validation, + toValidationLevel(kdict['validation']), key.validation, 'Wrong data in key.') -class KeyManagerWithSoledadTestCase(BaseLeapTest): - - def setUp(self): - # mock key fetching and storing so Soledad doesn't fail when trying to - # reach the server. - Soledad._get_secrets_from_shared_db = Mock(return_value=None) - Soledad._put_secrets_in_shared_db = Mock(return_value=None) - - class MockSharedDB(object): - - get_doc = Mock(return_value=None) - put_doc = Mock() - lock = Mock(return_value=('atoken', 300)) - unlock = Mock(return_value=True) - - def __call__(self): - return self - - Soledad._shared_db = MockSharedDB() - - self._soledad = Soledad( - u"leap@leap.se", - u"123456", - secrets_path=self.tempdir + "/secret.gpg", - local_db_path=self.tempdir + "/soledad.u1db", - server_url='', - cert_file=None, - auth_token=None, - ) - - def tearDown(self): - km = self._key_manager() - for key in km.get_all_keys(): - km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys(private=True): - km._wrapper_map[key.__class__].delete_key(key) - - def _key_manager(self, user=ADDRESS, url='', token=None): - return KeyManager(user, url, self._soledad, token=token, - gpgbinary=GPG_BINARY_PATH) - - class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def _test_openpgp_gen_key(self): @@ -220,7 +186,6 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): KeyNotFound, pgp.get_key, ADDRESS, private=True) pgp.put_ascii_key(PRIVATE_KEY) privkey = pgp.get_key(ADDRESS, private=True) - plaintext = pgp.decrypt(cyphertext, privkey) pgp.delete_key(pubkey) pgp.delete_key(privkey) self.assertRaises( @@ -473,7 +438,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager(url='http://nickserver.domain') - km.put_key(PUBLIC_KEY) + km.put_raw_key(PUBLIC_KEY, OpenPGPKey) key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertEqual(ADDRESS, key.address) @@ -524,7 +489,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - self.assertRaises(KeyAttributesDiffer, km.fetch_key, + self.assertRaises(KeyAddressMismatch, km.fetch_key, ADDRESS_2, "http://site.domain/key", OpenPGPKey) @@ -568,169 +533,6 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): # Key material for testing -# key 24D18DDF: public key "Leap Test Key " -KEY_FINGERPRINT = "E36E738D69173C13D709E44F2F455E2824D18DDF" -PUBLIC_KEY = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -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 -=MuOY ------END PGP PUBLIC KEY BLOCK----- -""" -PRIVATE_KEY = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQcYBFC9+dkBEADNRfwV23TWEoGc/x0wWH1P7PlXt8MnC2Z1kKaKKmfnglVrpOiz -iLWoiU58sfZ0L5vHkzXHXCBf6Eiy/EtUIvdiWAn+yASJ1mk5jZTBKO/WMAHD8wTO -zpMsFmWyg3xc4DkmFa9KQ5EVU0o/nqPeyQxNMQN7px5pPwrJtJFmPxnxm+aDkPYx -irDmz/4DeDNqXliazGJKw7efqBdlwTHkl9Akw2gwy178pmsKwHHEMOBOFFvX61AT -huKqHYmlCGSliwbrJppTG7jc1/ls3itrK+CWTg4txREkSpEVmfcASvw/ZqLbjgfs -d/INMwXnR9U81O8+7LT6yw/ca4ppcFoJD7/XJbkRiML6+bJ4Dakiy6i727BzV17g -wI1zqNvm5rAhtALKfACha6YO43aJzairO4II1wxVHvRDHZn2IuKDDephQ3Ii7/vb -hUOf6XCSmchkAcpKXUOvbxm1yfB1LRa64mMc2RcZxf4mW7KQkulBsdV5QG2276lv -U2UUy2IutXcGP5nXC+f6sJJGJeEToKJ57yiO/VWJFjKN8SvP+7AYsQSqINUuEf6H -T5gCPCraGMkTUTPXrREvu7NOohU78q6zZNaL3GW8ai7eSeANSuQ8Vzffx7Wd8Y7i -Pw9sYj0SMFs1UgjbuL6pO5ueHh+qyumbtAq2K0Bci0kqOcU4E9fNtdiovQARAQAB -AA/+JHtlL39G1wsH9R6UEfUQJGXR9MiIiwZoKcnRB2o8+DS+OLjg0JOh8XehtuCs -E/8oGQKtQqa5bEIstX7IZoYmYFiUQi9LOzIblmp2vxOm+HKkxa4JszWci2/ZmC3t -KtaA4adl9XVnshoQ7pijuCMUKB3naBEOAxd8s9d/JeReGIYkJErdrnVfNk5N71Ds -FmH5Ll3XtEDvgBUQP3nkA6QFjpsaB94FHjL3gDwum/cxzj6pCglcvHOzEhfY0Ddb -J967FozQTaf2JW3O+w3LOqtcKWpq87B7+O61tVidQPSSuzPjCtFF0D2LC9R/Hpky -KTMQ6CaKja4MPhjwywd4QPcHGYSqjMpflvJqi+kYIt8psUK/YswWjnr3r4fbuqVY -VhtiHvnBHQjz135lUqWvEz4hM3Xpnxydx7aRlv5NlevK8+YIO5oFbWbGNTWsPZI5 -jpoFBpSsnR1Q5tnvtNHauvoWV+XN2qAOBTG+/nEbDYH6Ak3aaE9jrpTdYh0CotYF -q7csANsDy3JvkAzeU6WnYpsHHaAjqOGyiZGsLej1UcXPFMosE/aUo4WQhiS8Zx2c -zOVKOi/X5vQ2GdNT9Qolz8AriwzsvFR+bxPzyd8V6ALwDsoXvwEYinYBKK8j0OPv -OOihSR6HVsuP9NUZNU9ewiGzte/+/r6pNXHvR7wTQ8EWLcEIAN6Zyrb0bHZTIlxt -VWur/Ht2mIZrBaO50qmM5RD3T5oXzWXi/pjLrIpBMfeZR9DWfwQwjYzwqi7pxtYx -nJvbMuY505rfnMoYxb4J+cpRXV8MS7Dr1vjjLVUC9KiwSbM3gg6emfd2yuA93ihv -Pe3mffzLIiQa4mRE3wtGcioC43nWuV2K2e1KjxeFg07JhrezA/1Cak505ab/tmvP -4YmjR5c44+yL/YcQ3HdFgs4mV+nVbptRXvRcPpolJsgxPccGNdvHhsoR4gwXMS3F -RRPD2z6x8xeN73Q4KH3bm01swQdwFBZbWVfmUGLxvN7leCdfs9+iFJyqHiCIB6Iv -mQfp8F0IAOwSo8JhWN+V1dwML4EkIrM8wUb4yecNLkyR6TpPH/qXx4PxVMC+vy6x -sCtjeHIwKE+9vqnlhd5zOYh7qYXEJtYwdeDDmDbL8oks1LFfd+FyAuZXY33DLwn0 -cRYsr2OEZmaajqUB3NVmj3H4uJBN9+paFHyFSXrH68K1Fk2o3n+RSf2EiX+eICwI -L6rqoF5sSVUghBWdNegV7qfy4anwTQwrIMGjgU5S6PKW0Dr/3iO5z3qQpGPAj5OW -ATqPWkDICLbObPxD5cJlyyNE2wCA9VVc6/1d6w4EVwSq9h3/WTpATEreXXxTGptd -LNiTA1nmakBYNO2Iyo3djhaqBdWjk+EIAKtVEnJH9FAVwWOvaj1RoZMA5DnDMo7e -SnhrCXl8AL7Z1WInEaybasTJXn1uQ8xY52Ua4b8cbuEKRKzw/70NesFRoMLYoHTO -dyeszvhoDHberpGRTciVmpMu7Hyi33rM31K9epA4ib6QbbCHnxkWOZB+Bhgj1hJ8 -xb4RBYWiWpAYcg0+DAC3w9gfxQhtUlZPIbmbrBmrVkO2GVGUj8kH6k4UV6kUHEGY -HQWQR0HcbKcXW81ZXCCD0l7ROuEWQtTe5Jw7dJ4/QFuqZnPutXVRNOZqpl6eRShw -7X2/a29VXBpmHA95a88rSQsL+qm7Fb3prqRmuMCtrUZgFz7HLSTuUMR867QcTGVh -cCBUZXN0IEtleSA8bGVhcEBsZWFwLnNlPokCNwQTAQgAIQUCUL352QIbAwULCQgH -AwUVCgkICwUWAgMBAAIeAQIXgAAKCRAvRV4oJNGN30+xEACh9yLkZ4jqW0/wwyIM -MI896MQf1tAwzMj16MJYUjrjNK4Bn57QaQW926HsxF8C/OjT0MTRhq7heYZJnnEo -rj0rzpkJapUveTRkKeoTRtGGigqJYfkOTU7KRVwgJBXIfaKlI3tC3cX0j0H1fVKX -hLxsj5pNSPRCVf2A5mePg44HtXe6oVWSJ8+EcdTa0shf03NhAtFaY0BbFGPSm9mA -QUe4rxugwXPLctIyV4uweFo5BXFBCb4kKTBdnQi3aJwnoWLNT6rDdTe4/nhY0Hfo -alTCYGLkhio77gBHwpTOjEMO/hZhcDMi4CvxMPw7bRxAwq4u+0j0pDhkiLcQs4U4 -Ou/fH+pia+1nF5h19cNVXIm+RX2fL0wxVYc/14AIAK3YT6PVev9XYEkogSj0P7Kb -HKOruYpnToXJBERNJZwGL1U+ihPNUyroRf29t7u8flnXsOpCtBEIWAO8Muy5pWjV -3O6zAUCfWetAieCQ7WrQVmdJDa7dlX3Qx1XagUzqZdAq2jVI1hOWDA2rKytnReSF -/A97rmLaWZ8aoNCs8i4NLcy9Lbzi9QtornYGVCEmTTym0tM9L/mn7gAJ8dqUwt7n -s24dibfElky4ZZeItD+D7OZGeh0FDuejvv2dXFqL1/pkHpGBZhEckg0fZ95NbgMC -4pSZkZnqRpr2GwfB5aFfB6sIIJ0HGARQvfnZARAAtCP8Z9bm6KzIA7wbXx9LBIcJ -1wQvOPf99s4nOrnQev9xH5PZ820qS9xUjrlyE2bGYAhz5Cmq56ENs7THErIdZHtQ -uYEBprO+VFZjP50vtmCOL2PDl/xgv6J9r1Mp3KnR/m0esR+YceDW1qX07IkB5s+Q -us80v5LmmxnWcikWmR7dt1kOyV/+M6Y6mwvfQ4x3D/QUpO7SfMCOG5DGA7hVUHU/ -Tuh8MihmMFFOLAEEQI+1wkxr1W09HaYCcB+EZqxLSaBwMeFoYPJie9dBNBgps39o -6pDbjsO+or4JNuyoHvh8NNI5iY0IR2NMlW4mqCcHEazys2koxFTYK6YD95Vz0RkA -K4BErCDk3lVR0PH4StmLU3gmPayIjvi9Dl9saPRyu4Xx2WVi+q6spl3ckn4c4f3+ -iD8hxVp74+wa5ew0fIXjIpMoHCar/nndsse4i8glCddINdiOPPmhI9Wi3nT+5Z2t -9omPP2dEh0CzR+j1zvUpT3KtmhVICqhO+QP9BTJOwrp81NTlq9mbUyzTtVk/9dy3 -zoYbhKvY08k6LJ9FsQYySqtfJZ4cwl5WsOhALWwOwlMLA9wkz0eemgFxStyOylzl -QKoIK7zHuU6XYOXa32KSPIWaLy+WgIG/u2ObWtdE3CXVIUuSt5BQFnv7XVNHJllD -Az9VDEkOSYOiSEFVoUsAEQEAAQAP/1AagnZQZyzHDEgw4QELAspYHCWLXE5aZInX -wTUJhK31IgIXNn9bJ0hFiSpQR2xeMs9oYtRuPOu0P8oOFMn4/z374fkjZy8QVY3e -PlL+3EUeqYtkMwlGNmVw5a/NbNuNfm5Darb7pEfbYd1gPcni4MAYw7R2SG/57GbC -9gucvspHIfOSfBNLBthDzmK8xEKe1yD2eimfc2T7IRYb6hmkYfeds5GsqvGI6mwI -85h4uUHWRc5JOlhVM6yX8hSWx0L60Z3DZLChmc8maWnFXd7C8eQ6P1azJJbW71Ih -7CoK0XW4LE82vlQurSRFgTwfl7wFYszW2bOzCuhHDDtYnwH86Nsu0DC78ZVRnvxn -E8Ke/AJgrdhIOo4UAyR+aZD2+2mKd7/waOUTUrUtTzc7i8N3YXGi/EIaNReBXaq+ -ZNOp24BlFzRp+FCF/pptDW9HjPdiV09x0DgICmeZS4Gq/4vFFIahWctg52NGebT0 -Idxngjj+xDtLaZlLQoOz0n5ByjO/Wi0ANmMv1sMKCHhGvdaSws2/PbMR2r4caj8m -KXpIgdinM/wUzHJ5pZyF2U/qejsRj8Kw8KH/tfX4JCLhiaP/mgeTuWGDHeZQERAT -xPmRFHaLP9/ZhvGNh6okIYtrKjWTLGoXvKLHcrKNisBLSq+P2WeFrlme1vjvJMo/ -jPwLT5o9CADQmcbKZ+QQ1ZM9v99iDZol7SAMZX43JC019sx6GK0u6xouJBcLfeB4 -OXacTgmSYdTa9RM9fbfVpti01tJ84LV2SyL/VJq/enJF4XQPSynT/tFTn1PAor6o -tEAAd8fjKdJ6LnD5wb92SPHfQfXqI84rFEO8rUNIE/1ErT6DYifDzVCbfD2KZdoF -cOSp7TpD77sY1bs74ocBX5ejKtd+aH99D78bJSMM4pSDZsIEwnomkBHTziubPwJb -OwnATy0LmSMAWOw5rKbsh5nfwCiUTM20xp0t5JeXd+wPVWbpWqI2EnkCEN+RJr9i -7dp/ymDQ+Yt5wrsN3NwoyiexPOG91WQVCADdErHsnglVZZq9Z8Wx7KwecGCUurJ2 -H6lKudv5YOxPnAzqZS5HbpZd/nRTMZh2rdXCr5m2YOuewyYjvM757AkmUpM09zJX -MQ1S67/UX2y8/74TcRF97Ncx9HeELs92innBRXoFitnNguvcO6Esx4BTe1OdU6qR -ER3zAmVf22Le9ciXbu24DN4mleOH+OmBx7X2PqJSYW9GAMTsRB081R6EWKH7romQ -waxFrZ4DJzZ9ltyosEJn5F32StyLrFxpcrdLUoEaclZCv2qka7sZvi0EvovDVEBU -e10jOx9AOwf8Gj2ufhquQ6qgVYCzbP+YrodtkFrXRS3IsljIchj1M2ffB/0bfoUs -rtER9pLvYzCjBPg8IfGLw0o754Qbhh/ReplCRTusP/fQMybvCvfxreS3oyEriu/G -GufRomjewZ8EMHDIgUsLcYo2UHZsfF7tcazgxMGmMvazp4r8vpgrvW/8fIN/6Adu -tF+WjWDTvJLFJCe6O+BFJOWrssNrrra1zGtLC1s8s+Wfpe+bGPL5zpHeebGTwH1U -22eqgJArlEKxrfarz7W5+uHZJHSjF/K9ZvunLGD0n9GOPMpji3UO3zeM8IYoWn7E -/EWK1XbjnssNemeeTZ+sDh+qrD7BOi+vCX1IyBxbfqnQfJZvmcPWpruy1UsO+aIC -0GY8Jr3OL69dDQ21jueJAh8EGAEIAAkFAlC9+dkCGwwACgkQL0VeKCTRjd9HCw/+ -LQSVgLLF4ulYlPCjWIIuQwrPbJfWUVVr2dPUFVM85DCv8gBzk5c121snXh9Swovm -laBbw6ate3BmbXLh64jVE9Za5sbTWi7PCcbO/bpRy4d6oLmitmNw6cq0vjTLxUYy -bwuiJxWREkfxuU85EKdouN062YDevH+/YResmlJrcCE7LRlJFeRlKsrrwBU3BqYd -GgFJjKjQC1peeQ9fj62Y7xfwE9+PXbkiWO5u/Bk8hb1VZH1SoIRU98NHVcp6BVvp -VK0jLAXuSauSczULmpRjbyt1lhaAqivDTWEEZXiNNbRyp17c3nVdPWOcgBr42hdQ -z25CgZgyLCsvu82wuXLKJblrIPJX3Yf+si6KqEWBsmwdOWybsjygaF5HvzgFqAAD -U0goPWoQ71PorP2XOUNp5ZLkBQp5etvtkksjVNMIhnHn8PGMuoxO39EUGlWj2B5l -Cu8tSosAzB1pS8NcLZzoNoI9dOHrmgJmP+GrOUkcf5GhNZbMoj4GNfGBRYX0SZlQ -GuDrwNKYj73C4MWyNnnUFyq8nDHJ/G1NpaF2hiof9RBL4PUU/f92JkceXPBXA8gL -Mz2ig1OButwPPLFGQhWqxXAGrsS3Ny+BhTJfnfIbbkaLLphBpDZm1D9XKbAUvdd1 -RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= -=JTFu ------END PGP PRIVATE KEY BLOCK----- -""" - # key 7FEE575A: public key "anotheruser " PUBLIC_KEY_2 = """ -----BEGIN PGP PUBLIC KEY BLOCK----- diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py new file mode 100644 index 0000000..c7170ab --- /dev/null +++ b/src/leap/keymanager/tests/test_validation.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2014 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Tests for the Validation Levels +""" + +from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager.errors import ( + KeyNotValidUpgrade +) +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + PUBLIC_KEY, + KEY_FINGERPRINT +) +from leap.keymanager.validation import ValidationLevel + + +class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): + + def test_none_old_key(self): + km = self._key_manager() + km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + + def test_cant_upgrade(self): + km = self._key_manager() + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, + validation=ValidationLevel.Provider_Trust) + self.assertRaises(KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, + OpenPGPKey) + + def test_fingerprint_level(self): + km = self._key_manager() + km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey, + validation=ValidationLevel.Fingerprint) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + + def test_expired_key(self): + km = self._key_manager() + km.put_raw_key(EXPIRED_KEY, OpenPGPKey) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + + def test_expired_fail_lower_level(self): + km = self._key_manager() + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, + validation=ValidationLevel.Third_Party_Endorsement) + self.assertRaises( + KeyNotValidUpgrade, + km.put_raw_key, + UNRELATED_KEY, + OpenPGPKey, + validation=ValidationLevel.Provider_Trust) + + +# Key material for testing + +# key 901FBCA5: public key "Leap Test Key " +UNRELATED_FINGERPRINT = "ABCCD9C8270B6A8D5633FAC9D04DB2E4901FBCA5" +UNRELATED_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFQ9VDoBCACbKflcEhUXZULOT4Fwc2ifRUllJpusd2uX5oeDlZdZ15uLY2eF +LcxnAdIWkI/PsXimh0ev/Pf4oCynfmt02I3c2d9F0N6JXWnRiP+p098oPOcqeEqL +N3CrkH1RVnEXNeJ/Fu7tkD61SBXl1MytMfcHyhN5arg8OcVAjcmghX53+92jFhC9 +8ss87H/qEe5vEX/ahP3tiL5ULvaS4GIX+XB0O3yCVdRoRG9lqMIBP/ZqCkKrNll8 +dT12a6ByG/rWharZUeUETiM4Y+JjDUUaEC2YhNF9k52JNGanLH9LTTtlKy5WTT+E +C6T6VMAtkwcBDpkXr5sBB/N+Y1z0Fp359lIXABEBAAG0HExlYXAgVGVzdCBLZXkg +PGxlYXBAbGVhcC5zZT6JATgEEwECACIFAlQ9VDoCGwMGCwkIBwMCBhUIAgkKCwQW +AgMBAh4BAheAAAoJENBNsuSQH7ylsSUIAIxUFbkeTdHbCF/LVA2U+ktnR1iVikAY +vFK+U+Bto11/AO4Kew2eWniDch/sqLQOoSydtP42z2z3/Al3u7LhQ8bElQHPDY78 +t49qweyJi00V3vCKCdWwPJnPM5eJOIrZHCbwIgeXCsXxVNJVyziVqMuum+px1h2d +1YJZXYejT8rzwa3yBPAsGWRAWETeTvUuyjPMFa59scbnaDuY+bwQ2r/qG9m7UyHU +h2kAHC5sf1rixVOY6rLhw75gQHE/L2BZJRfVsDQqIpEMh2OgMfNbL928jncjwQvc +/IXXwSUx7y50ll+uNh+TVLf0MlUjKdHmHqnGBMlIIWojWJuKxYmOOoO5AQ0EVD1U +OgEIAM/TlhWVSI+tl5XBUAcf60RxjpHQkmdfq1i1jgwUgu/638EKzBfLcnRYX8Rn +DO9CWnHcql/4hp226fIWZN/SyReE81n7UkLDMAglhHgiezHMSH1GYVu4IlfpLVXn +brLVo83KioH5MPFWmZv5tigpU/G8dTx9yVGv1//YW2qqRYYqeIKJfapBaY/bNqyD +vYRfZo1K2brtHx4bToY6mALRF4ruV5SVZGS69e4Sh692C2pXSVbCpRhQ/2WnvkZH +leFIdmNmQN61MC1k26A620Rm+pAsXX71dln0u96xbrCgEVbi6ccfXzbFKtVmThVB +w11CLvVTviOm99TmcgpmDS4cf08AEQEAAYkBHwQYAQIACQUCVD1UOgIbDAAKCRDQ +TbLkkB+8pR+fB/0SeTcRr1duN7VYWdtng1+jO0ornIBtUraglN01dEEmiwN83DTi +J37i+nll+4is7BtiXqhumRptKh1v8UUMyFX/rjjoojCJBg5NExsiOYl3O4le68oF +3+XC+n7yrlyNmI15+3dcQmC9F6HN8EBZgrn5YPKGIOMHTGatB5PryMKg2IKiN5GZ +E0hmrOQgmcGrkeqysKACQYUHTasSk2IY1l1G5YQglqCaBh4+UC82Dmg5fTBbHjxP +YhhojkP4aD/0YW7dgql3nzYqvPCAjBH1Cf6rA9HvAJwUP9Ig/okcrrPEKm638+mG ++vNIuLqIkA4oFLBAAIrgMiQZ+NZz9uD6DJE7 +=FO7G +-----END PGP PUBLIC KEY BLOCK----- +""" + +# key A1885A7C: public key "Leap Test Key " +EXPIRED_FINGERPRINT = "7C1F68B0E14157B09B5F4ADE6F15F004A1885A7C" +EXPIRED_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQENBBvrfd0BCADGNpspaNhsbhSjKioCWrE2MTTYC+Sdpes22RabdhQyOCWvlSbj +b8p0y3kmnMOtVBT+c22/w7eu2YBfIpS4RswgE5ypr/1kZLFQueVe/cp29GjPvLwJ +82A3EOHcmXs8rSJ76h2bnkySvbJawz9rwCcaXhpdAwC+sjWvbqiwZYEL+90I4Xp3 +acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A +IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV +wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg +PGxlYXBAbGVhcC5zZT6JAT4EEwECACgFAhvrfd0CGwMFCQABUYAGCwkIBwMCBhUI +AgkKCwQWAgMBAh4BAheAAAoJEG8V8AShiFp8VNkH/iCQcXkTfMOVlL2rQRyZtJEO +Lr5uTyyY8O6ubeNCHqZzlIopiPAsv4hIYjjMDvOfZ9R53YgmbacUm0rvh1B4MSUf +k+sa9/tequ3y44LUKp7AB6NyyLgVOU5ngl2w+bi7CgXAep3oP4joYKcU0mmSAc2S +2Gj85DVqP0kdzNs47esvyj7g1TOfdBwmLsTx/219H+w3dNBeyCQWkYCYNh7MX/Ba +SZ+P0xr4FetcOVPM3wAzUtDG7hKsgccoIXt0FWhG/nn8cETfGH+o3W/ky7Jktatx +DGDHoZJvAaG2B2ey1pAQlezr8p/O+ZVABiigHk1S+myBHyhlXzUcjhQnEG7aHZ65 +AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 +XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 +sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm +4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 +leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ +KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb +DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV +D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm +unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j +Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq +K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W +Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci +=WhX+ +-----END PGP PUBLIC KEY BLOCK----- +""" + +import unittest +if __name__ == "__main__": + unittest.main() diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py new file mode 100644 index 0000000..6dceb78 --- /dev/null +++ b/src/leap/keymanager/validation.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# __init__.py +# Copyright (C) 2014 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Validation levels implementation for key managment. + +See: + https://lists.riseup.net/www/arc/leap-discuss/2014-09/msg00000.html +""" + + +from datetime import datetime +from enum import Enum + + +ValidationLevel = Enum( + "Weak_Chain", + "Provider_Trust", + "Provider_Endorsement", + "Third_Party_Endorsement", + "Third_Party_Consensus", + "Historically_Auditing", + "Known_Key", + "Fingerprint") + + +def toValidationLevel(value): + """ + Convert a string representation of a validation level into + C{ValidationLevel} + + :param value: validation level + :type value: str + :rtype: ValidationLevel + :raises ValueError: if C{value} is not a validation level + """ + for level in ValidationLevel: + if value == str(level): + return level + raise ValueError("Not valid validation level: %s" % (value,)) + + +def can_upgrade(new_key, old_key): + """ + :type new_key: EncryptionKey + :type old_key: EncryptionKey + :rtype: bool + """ + # XXX not succesfully used and strict high validation level (#6211) + # XXX implement key signature checking (#6120) + + # First contact + if old_key is None: + return True + + if new_key.address != old_key.address: + # XXX how do we map multiple IDs? (#6212) + return False + + # An update of the same key + if new_key.fingerprint == old_key.fingerprint: + # XXX wich one is newer? is that a downgrade attack? (#6210) + return True + + # Manually verified fingerprint + if new_key.validation == ValidationLevel.Fingerprint: + return True + + # Expired key and higher validation level + if old_key.expiry_date: + old_expiry_date = datetime.fromtimestamp(int(old_key.expiry_date)) + if (old_expiry_date < datetime.now() and + new_key.validation >= old_key.validation): + return True + + # No expiration date and higher validation level + elif new_key.validation >= old_key.validation: + return True + + return False -- cgit v1.2.3 From a5cf287dabc77b7172c2f058696cee1024ea3297 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 20 Oct 2014 18:35:11 -0500 Subject: Update doc string of OpenPGPScheme._temporary_gpgwrapper --- src/leap/keymanager/openpgp.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 57a7754..e84cd29 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -452,10 +452,8 @@ class OpenPGPScheme(EncryptionScheme): Return a gpg wrapper that implements the context manager protocol and contains C{keys}. - :param key_data: ASCII armored key data. - :type key_data: str - :param gpgbinary: Name for GnuPG binary executable. - :type gpgbinary: C{str} + :param keys: keys to conform the keyring. + :type key: list(OpenPGPKey) :return: a TempGPGWrapper instance :rtype: TempGPGWrapper -- cgit v1.2.3 From d9df76ea2504a78865209cda3ae6e41613d5e5aa Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 30 Oct 2014 21:54:32 -0600 Subject: Merge keys when updating an exisiting key This is needed to prevent roll back attacks where the attacker push us to accept a key with an old expiration date that could be use to push an untrusted key when after it's expiration. --- src/leap/keymanager/openpgp.py | 12 ++++++++ src/leap/keymanager/tests/test_validation.py | 43 ++++++++++++++++++++++++++++ src/leap/keymanager/validation.py | 1 - 3 files changed, 55 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index e84cd29..f86b35d 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -37,6 +37,8 @@ from leap.keymanager.keys import ( build_key_from_dict, KEYMANAGER_KEY_TAG, TAGS_ADDRESS_PRIVATE_INDEX, + KEY_FINGERPRINT_KEY, + KEY_DATA_KEY, ) from leap.keymanager.validation import ValidationLevel @@ -394,6 +396,16 @@ class OpenPGPScheme(EncryptionScheme): if doc is None: self._soledad.create_doc_from_json(key.get_json()) else: + if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: + # in case of an update of the key merge them with gnupg + with self._temporary_gpgwrapper() as gpg: + gpg.import_keys(doc.content[KEY_DATA_KEY]) + gpg.import_keys(key.key_data) + gpgkey = gpg.list_keys(secret=key.private).pop() + key = _build_key_from_gpg( + key.address, gpgkey, + gpg.export_keys(gpgkey['fingerprint'], + secret=key.private)) doc.set_json(key.get_json()) self._soledad.put_doc(doc) diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index c7170ab..0f4d81a 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -72,6 +72,13 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): OpenPGPKey, validation=ValidationLevel.Provider_Trust) + def test_roll_back(self): + km = self._key_manager() + km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey) + km.put_raw_key(EXPIRED_KEY, OpenPGPKey) + key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) + # Key material for testing @@ -144,6 +151,42 @@ Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci =WhX+ -----END PGP PUBLIC KEY BLOCK----- """ +# updated expiration date +# Tue 24 Oct 2034 05:13:00 PM BST +EXPIRED_KEY_NEW_EXPIRY_DATE = "2045319180" +EXPIRED_KEY_UPDATED = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +mQENBBvrfd0BCADGNpspaNhsbhSjKioCWrE2MTTYC+Sdpes22RabdhQyOCWvlSbj +b8p0y3kmnMOtVBT+c22/w7eu2YBfIpS4RswgE5ypr/1kZLFQueVe/cp29GjPvLwJ +82A3EOHcmXs8rSJ76h2bnkySvbJawz9rwCcaXhpdAwC+sjWvbqiwZYEL+90I4Xp3 +acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A +IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV +wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg +PGxlYXBAbGVhcC5zZT6JAT4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B +AheABQJUURIXBQld/ZovAAoJEG8V8AShiFp8xUcIALcAHZbaxvyhHRGOrwDddbH0 +fFDK0AqKTsIT7y4D/HLFCP5zG3Ck7qGPZdkHXZfzq8rIb+zUjW3oJIVI1IucHxG2 +T5kppa8RFCBAFlRWYf6R3isX3YL0d3QSragjoxRNPcHNU8ALHcvfSonFHBoi4fH4 +4rvgksAiT68SsdPaoXDlabx5T15evu/7T5e/DGMQVPMxiaSuSQhbOKuMk2wcFdmL +tBYHLZPa54hHPNhEDyxLgtKKph0gObk9ojKfH9kPvLveIcpS5CqTJfN/kqBz7CJW +wEeAi2iG3H1OEB25aCUdTxXSRNlGqEgcWPaWxtc1RzlARu7LB64OUZuRy4puiAG5 +AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 +XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 +sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm +4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 +leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ +KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb +DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV +D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm +unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j +Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq +K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W +Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci +=79Ll +-----END PGP PUBLIC KEY BLOCK----- +""" + import unittest if __name__ == "__main__": diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index 6dceb78..7d68966 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -73,7 +73,6 @@ def can_upgrade(new_key, old_key): # An update of the same key if new_key.fingerprint == old_key.fingerprint: - # XXX wich one is newer? is that a downgrade attack? (#6210) return True # Manually verified fingerprint -- cgit v1.2.3 From 6729bd975e4fe6714c46bcc8c04e63830a2b4491 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 30 Oct 2014 21:57:48 -0600 Subject: Remove outdated comment --- src/leap/keymanager/keys.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index ecb0a36..b5c9118 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -141,16 +141,7 @@ class EncryptionKey(object): Abstract class for encryption keys. A key is "validated" if the nicknym agent has bound the user address to a - public key. Nicknym supports three different levels of key validation: - - * Level 3 - path trusted: A path of cryptographic signatures can be traced - from a trusted key to the key under evaluation. By default, only the - provider key from the user's provider is a "trusted key". - * level 2 - provider signed: The key has been signed by a provider key for - the same domain, but the provider key is not validated using a trust - path (i.e. it is only registered) - * level 1 - registered: The key has been encountered and saved, it has no - signatures (that are meaningful to the nicknym agent). + public key. """ __metaclass__ = ABCMeta -- cgit v1.2.3 From c223cca848e854d0015314ef517a6a4f928a2d0a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 4 Nov 2014 11:53:56 -0600 Subject: Use datetime for key expiration --- src/leap/keymanager/keys.py | 13 +++++++++++-- src/leap/keymanager/openpgp.py | 7 ++++++- src/leap/keymanager/tests/test_keymanager.py | 4 ++-- src/leap/keymanager/tests/test_validation.py | 5 +++-- src/leap/keymanager/validation.py | 9 ++++----- 5 files changed, 26 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index b5c9118..a61a8c7 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -30,6 +30,7 @@ import re from abc import ABCMeta, abstractmethod +from datetime import datetime from leap.common.check import leap_assert from leap.keymanager.validation import ValidationLevel, toValidationLevel @@ -118,6 +119,10 @@ def build_key_from_dict(kClass, address, kdict): (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) validation = ValidationLevel.Weak_Chain + expiry_date = None + if kdict[KEY_EXPIRY_DATE_KEY]: + expiry_date = datetime.fromtimestamp(int(kdict[KEY_EXPIRY_DATE_KEY])) + return kClass( address, key_id=kdict[KEY_ID_KEY], @@ -125,7 +130,7 @@ def build_key_from_dict(kClass, address, kdict): key_data=kdict[KEY_DATA_KEY], private=kdict[KEY_PRIVATE_KEY], length=kdict[KEY_LENGTH_KEY], - expiry_date=kdict[KEY_EXPIRY_DATE_KEY], + expiry_date=expiry_date, first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], validation=validation, @@ -167,6 +172,10 @@ class EncryptionKey(object): :return: The JSON string describing this key. :rtype: str """ + expiry_str = "" + if self.expiry_date is not None: + expiry_str = self.expiry_date.strftime("%s") + return json.dumps({ KEY_ADDRESS_KEY: self.address, KEY_TYPE_KEY: str(self.__class__), @@ -175,7 +184,7 @@ class EncryptionKey(object): KEY_DATA_KEY: self.key_data, KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: self.expiry_date, + KEY_EXPIRY_DATE_KEY: expiry_str, KEY_VALIDATION_KEY: str(self.validation), KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index f86b35d..d3c305e 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -25,6 +25,7 @@ import tempfile import io +from datetime import datetime from gnupg import GPG from gnupg.gnupg import GPGUtilities @@ -178,6 +179,10 @@ def _build_key_from_gpg(address, key, key_data): :return: An instance of the key. :rtype: OpenPGPKey """ + expiry_date = None + if key['expires']: + expiry_date = datetime.fromtimestamp(int(key['expires'])) + return OpenPGPKey( address, key_id=key['keyid'], @@ -185,7 +190,7 @@ def _build_key_from_gpg(address, key, key_data): key_data=key_data, private=True if key['type'] == 'sec' else False, length=key['length'], - expiry_date=key['expires'], + expiry_date=expiry_date, validation=ValidationLevel.Weak_Chain, ) diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 1bd6a2e..6a877bc 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -81,7 +81,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'key_data': 'key_data', 'private': 'private', 'length': 'length', - 'expiry_date': 'expiry_date', + 'expiry_date': '', 'first_seen_at': 'first_seen_at', 'last_audited_at': 'last_audited_at', 'validation': str(ValidationLevel.Weak_Chain), @@ -106,7 +106,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): kdict['length'], key.length, 'Wrong data in key.') self.assertEqual( - kdict['expiry_date'], key.expiry_date, + None, key.expiry_date, 'Wrong data in key.') self.assertEqual( kdict['first_seen_at'], key.first_seen_at, diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 0f4d81a..3ae873d 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -18,6 +18,8 @@ Tests for the Validation Levels """ +from datetime import datetime + from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.errors import ( KeyNotValidUpgrade @@ -152,8 +154,7 @@ Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -----END PGP PUBLIC KEY BLOCK----- """ # updated expiration date -# Tue 24 Oct 2034 05:13:00 PM BST -EXPIRED_KEY_NEW_EXPIRY_DATE = "2045319180" +EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2045319180) EXPIRED_KEY_UPDATED = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index 7d68966..cf5b4a8 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -80,11 +80,10 @@ def can_upgrade(new_key, old_key): return True # Expired key and higher validation level - if old_key.expiry_date: - old_expiry_date = datetime.fromtimestamp(int(old_key.expiry_date)) - if (old_expiry_date < datetime.now() and - new_key.validation >= old_key.validation): - return True + if (old_key.expiry_date is not None and + old_key.expiry_date < datetime.now() and + new_key.validation >= old_key.validation): + return True # No expiration date and higher validation level elif new_key.validation >= old_key.validation: -- cgit v1.2.3 From d1e0322d8c12dfb1511ad0895c5fc1e0271b8a30 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 6 Nov 2014 00:47:32 -0600 Subject: Implement the new encryption-key soledad document --- src/leap/keymanager/__init__.py | 10 ++--- src/leap/keymanager/keys.py | 60 +++++++++++++++++++--------- src/leap/keymanager/openpgp.py | 22 +++++----- src/leap/keymanager/tests/test_keymanager.py | 55 ++++++++++++++----------- 4 files changed, 89 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 156aaf8..53dd9a7 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -319,7 +319,7 @@ class KeyManager(object): return map( lambda doc: build_key_from_dict( self._key_class_from_type(doc.content['type']), - doc.content['address'], + doc.content['address'][0], doc.content), self._soledad.get_from_index( TAGS_PRIVATE_INDEX, @@ -529,7 +529,7 @@ class KeyManager(object): new one is not a valid update for it """ try: - old_key = self._wrapper_map[type(key)].get_key(key.address, + old_key = self._wrapper_map[type(key)].get_key(key.address[0], private=key.private) except KeyNotFound: old_key = None @@ -564,7 +564,7 @@ class KeyManager(object): new one is not a valid update for it """ pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) - if address is not None and address != pubkey.address: + if address is not None and address not in pubkey.address: raise KeyAddressMismatch("Key UID %s, but expected %s" % (pubkey.address, address)) @@ -600,9 +600,9 @@ class KeyManager(object): pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) if pubkey is None: raise KeyNotFound(uri) - if pubkey.address != address: + if address not in pubkey.address: raise KeyAddressMismatch("UID %s found, but expected %s" - % (pubkey.address, address)) + % (str(pubkey.address), address)) pubkey.validation = validation self.put_key(pubkey) diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index a61a8c7..4952b9b 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -27,6 +27,7 @@ except ImportError: import json # noqa import logging import re +import time from abc import ABCMeta, abstractmethod @@ -50,9 +51,11 @@ KEY_DATA_KEY = 'key_data' KEY_PRIVATE_KEY = 'private' KEY_LENGTH_KEY = 'length' KEY_EXPIRY_DATE_KEY = 'expiry_date' -KEY_FIRST_SEEN_AT_KEY = 'first_seen_at' KEY_LAST_AUDITED_AT_KEY = 'last_audited_at' +KEY_REFRESHED_AT_KEY = 'refreshed_at' KEY_VALIDATION_KEY = 'validation' +KEY_ENCR_USED_KEY = 'encr_used' +KEY_SIGN_USED_KEY = 'sign_used' KEY_TAGS_KEY = 'tags' @@ -110,7 +113,7 @@ def build_key_from_dict(kClass, address, kdict): :rtype: C{kClass} """ leap_assert( - address == kdict[KEY_ADDRESS_KEY], + address in kdict[KEY_ADDRESS_KEY], 'Wrong address in key data.') try: validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) @@ -119,24 +122,40 @@ def build_key_from_dict(kClass, address, kdict): (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) validation = ValidationLevel.Weak_Chain - expiry_date = None - if kdict[KEY_EXPIRY_DATE_KEY]: - expiry_date = datetime.fromtimestamp(int(kdict[KEY_EXPIRY_DATE_KEY])) + expiry_date = _to_datetime(kdict[KEY_EXPIRY_DATE_KEY]) + last_audited_at = _to_datetime(kdict[KEY_LAST_AUDITED_AT_KEY]) + refreshed_at = _to_datetime(kdict[KEY_REFRESHED_AT_KEY]) return kClass( - address, + [address], key_id=kdict[KEY_ID_KEY], fingerprint=kdict[KEY_FINGERPRINT_KEY], key_data=kdict[KEY_DATA_KEY], private=kdict[KEY_PRIVATE_KEY], length=kdict[KEY_LENGTH_KEY], expiry_date=expiry_date, - first_seen_at=kdict[KEY_FIRST_SEEN_AT_KEY], - last_audited_at=kdict[KEY_LAST_AUDITED_AT_KEY], + last_audited_at=last_audited_at, + refreshed_at=refreshed_at, validation=validation, + encr_used=kdict[KEY_ENCR_USED_KEY], + sign_used=kdict[KEY_SIGN_USED_KEY], ) +def _to_datetime(unix_time): + if unix_time != 0: + return datetime.fromtimestamp(unix_time) + else: + return None + + +def _to_unix_time(date): + if date is not None: + return int(time.mktime(date.timetuple())) + else: + return 0 + + # # Abstraction for encryption keys # @@ -151,9 +170,10 @@ class EncryptionKey(object): __metaclass__ = ABCMeta - def __init__(self, address, key_id=None, fingerprint=None, - key_data=None, private=None, length=None, expiry_date=None, - validation=None, first_seen_at=None, last_audited_at=None): + def __init__(self, address, key_id="", fingerprint="", + key_data="", private=False, length=0, expiry_date=None, + validation=ValidationLevel.Weak_Chain, last_audited_at=None, + refreshed_at=None, encr_used=False, sign_used=False): self.address = address self.key_id = key_id self.fingerprint = fingerprint @@ -162,8 +182,10 @@ class EncryptionKey(object): self.length = length self.expiry_date = expiry_date self.validation = validation - self.first_seen_at = first_seen_at self.last_audited_at = last_audited_at + self.refreshed_at = refreshed_at + self.encr_used = encr_used + self.sign_used = sign_used def get_json(self): """ @@ -172,9 +194,9 @@ class EncryptionKey(object): :return: The JSON string describing this key. :rtype: str """ - expiry_str = "" - if self.expiry_date is not None: - expiry_str = self.expiry_date.strftime("%s") + expiry_date = _to_unix_time(self.expiry_date) + last_audited_at = _to_unix_time(self.last_audited_at) + refreshed_at = _to_unix_time(self.refreshed_at) return json.dumps({ KEY_ADDRESS_KEY: self.address, @@ -184,10 +206,12 @@ class EncryptionKey(object): KEY_DATA_KEY: self.key_data, KEY_PRIVATE_KEY: self.private, KEY_LENGTH_KEY: self.length, - KEY_EXPIRY_DATE_KEY: expiry_str, + KEY_EXPIRY_DATE_KEY: expiry_date, + KEY_LAST_AUDITED_AT_KEY: last_audited_at, + KEY_REFRESHED_AT_KEY: refreshed_at, KEY_VALIDATION_KEY: str(self.validation), - KEY_FIRST_SEEN_AT_KEY: self.first_seen_at, - KEY_LAST_AUDITED_AT_KEY: self.last_audited_at, + KEY_ENCR_USED_KEY: self.encr_used, + KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index d3c305e..1160434 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -41,7 +41,6 @@ from leap.keymanager.keys import ( KEY_FINGERPRINT_KEY, KEY_DATA_KEY, ) -from leap.keymanager.validation import ValidationLevel logger = logging.getLogger(__name__) @@ -109,9 +108,9 @@ class TempGPGWrapper(object): # itself is enough to also have the public key in the keyring, # and we want to count the keys afterwards. - privaddrs = map(lambda privkey: privkey.address, privkeys) + privaddrs = map(lambda privkey: privkey.address[0], privkeys) publkeys = filter( - lambda pubkey: pubkey.address not in privaddrs, publkeys) + lambda pubkey: pubkey.address[0] not in privaddrs, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) @@ -184,14 +183,14 @@ def _build_key_from_gpg(address, key, key_data): expiry_date = datetime.fromtimestamp(int(key['expires'])) return OpenPGPKey( - address, + [address], key_id=key['keyid'], fingerprint=key['fingerprint'], key_data=key_data, private=True if key['type'] == 'sec' else False, - length=key['length'], + length=int(key['length']), expiry_date=expiry_date, - validation=ValidationLevel.Weak_Chain, + refreshed_at=datetime.now(), ) @@ -397,7 +396,7 @@ class OpenPGPScheme(EncryptionScheme): :param key: The key to be stored. :type key: OpenPGPKey """ - doc = self._get_key_doc(key.address, private=key.private) + doc = self._get_key_doc(key.address[0], private=key.private) if doc is None: self._soledad.create_doc_from_json(key.get_json()) else: @@ -408,7 +407,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() key = _build_key_from_gpg( - key.address, gpgkey, + key.address[0], gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) doc.set_json(key.get_json()) @@ -452,12 +451,11 @@ class OpenPGPScheme(EncryptionScheme): :type key: EncryptionKey """ leap_assert_type(key, OpenPGPKey) - stored_key = self.get_key(key.address, private=key.private) - if stored_key is None: + doc = self._get_key_doc(key.address[0], key.private) + if doc is None: raise errors.KeyNotFound(key) - if stored_key.__dict__ != key.__dict__: + if doc.content[KEY_FINGERPRINT_KEY] != key.fingerprint: raise errors.KeyAttributesDiffer(key) - doc = self._get_key_doc(key.address, key.private) self._soledad.delete_doc(doc) # diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 6a877bc..4daf346 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -21,6 +21,7 @@ Tests for the Key Manager. """ +from datetime import datetime from mock import Mock from leap.common.testing.basetest import BaseLeapTest from leap.keymanager import ( @@ -75,16 +76,18 @@ class KeyManagerUtilTestCase(BaseLeapTest): def test_build_key_from_dict(self): kdict = { - 'address': ADDRESS, - 'key_id': 'key_id', - 'fingerprint': 'fingerprint', - 'key_data': 'key_data', - 'private': 'private', - 'length': 'length', - 'expiry_date': '', - 'first_seen_at': 'first_seen_at', - 'last_audited_at': 'last_audited_at', + 'address': [ADDRESS], + 'key_id': KEY_FINGERPRINT[-16:], + 'fingerprint': KEY_FINGERPRINT, + 'key_data': PUBLIC_KEY, + 'private': False, + 'length': 4096, + 'expiry_date': 0, + 'last_audited_at': 0, + 'refreshed_at': 1311239602, 'validation': str(ValidationLevel.Weak_Chain), + 'encr_used': False, + 'sign_used': True, } key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) self.assertEqual( @@ -109,14 +112,20 @@ class KeyManagerUtilTestCase(BaseLeapTest): None, key.expiry_date, 'Wrong data in key.') self.assertEqual( - kdict['first_seen_at'], key.first_seen_at, + None, key.last_audited_at, 'Wrong data in key.') self.assertEqual( - kdict['last_audited_at'], key.last_audited_at, + datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, 'Wrong data in key.') self.assertEqual( toValidationLevel(kdict['validation']), key.validation, 'Wrong data in key.') + self.assertEqual( + kdict['encr_used'], key.encr_used, + 'Wrong data in key.') + self.assertEqual( + kdict['sign_used'], key.sign_used, + 'Wrong data in key.') class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @@ -127,9 +136,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): key = pgp.gen_key('user@leap.se') self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertEqual( - 'user@leap.se', key.address, 'Wrong address bound to key.') + ['user@leap.se'], key.address, 'Wrong address bound to key.') self.assertEqual( - '4096', key.length, 'Wrong key length.') + 4096, key.length, 'Wrong key length.') def test_openpgp_put_delete_key(self): pgp = openpgp.OpenPGPScheme( @@ -147,10 +156,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp.put_ascii_key(PUBLIC_KEY) key = pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertTrue( + ADDRESS in key.address, 'Wrong address bound to key.') self.assertEqual( - ADDRESS, key.address, 'Wrong address bound to key.') - self.assertEqual( - '4096', key.length, 'Wrong key length.') + 4096, key.length, 'Wrong key length.') pgp.delete_key(key) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) @@ -162,7 +171,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) key = pgp.get_key(ADDRESS, private=False) - self.assertEqual(ADDRESS, key.address) + self.assertTrue(ADDRESS in key.address) self.assertFalse(key.private) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) pgp.delete_key(key) @@ -311,12 +320,12 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): # get public keys keys = km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual(ADDRESS, keys[0].address) + self.assertTrue(ADDRESS in keys[0].address) self.assertFalse(keys[0].private) # get private keys keys = km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') - self.assertEqual(ADDRESS, keys[0].address) + self.assertTrue(ADDRESS in keys[0].address) self.assertTrue(keys[0].private) def test_get_public_key(self): @@ -326,7 +335,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) self.assertTrue(key is not None) - self.assertEqual(key.address, ADDRESS) + self.assertTrue(ADDRESS in key.address) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertFalse(key.private) @@ -338,7 +347,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) self.assertTrue(key is not None) - self.assertEqual(key.address, ADDRESS) + self.assertTrue(ADDRESS in key.address) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertTrue(key.private) @@ -430,7 +439,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): # try to get key fetching from server. key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS, key.address) + self.assertTrue(ADDRESS in key.address) def test_put_key_ascii(self): """ @@ -441,7 +450,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.put_raw_key(PUBLIC_KEY, OpenPGPKey) key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) - self.assertEqual(ADDRESS, key.address) + self.assertTrue(ADDRESS in key.address) def test_fetch_uri_ascii_key(self): """ -- cgit v1.2.3 From 22a16674ce6891de5ea0a9cbea38ddabc9dd6e06 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 09:50:56 -0600 Subject: Use type instead of tags to get docs in openpgp For that that now the type is the class.__name__ instead of str(class) --- src/leap/keymanager/__init__.py | 2 +- src/leap/keymanager/keys.py | 8 ++++---- src/leap/keymanager/openpgp.py | 10 ++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 53dd9a7..0ffb6fc 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -134,7 +134,7 @@ class KeyManager(object): Return key class from string representation of key type. """ return filter( - lambda klass: str(klass) == ktype, + lambda klass: klass.__name__ == ktype, self._wrapper_map).pop() def _get(self, uri, data=None): diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 4952b9b..5aeb794 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -71,14 +71,14 @@ KEYMANAGER_KEY_TAG = 'keymanager-key' # TAGS_PRIVATE_INDEX = 'by-tags-private' -TAGS_ADDRESS_PRIVATE_INDEX = 'by-tags-address-private' +TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' INDEXES = { TAGS_PRIVATE_INDEX: [ KEY_TAGS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ], - TAGS_ADDRESS_PRIVATE_INDEX: [ - KEY_TAGS_KEY, + TYPE_ADDRESS_PRIVATE_INDEX: [ + KEY_TYPE_KEY, KEY_ADDRESS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ] @@ -200,7 +200,7 @@ class EncryptionKey(object): return json.dumps({ KEY_ADDRESS_KEY: self.address, - KEY_TYPE_KEY: str(self.__class__), + KEY_TYPE_KEY: self.__class__.__name__, KEY_ID_KEY: self.key_id, KEY_FINGERPRINT_KEY: self.fingerprint, KEY_DATA_KEY: self.key_data, diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 1160434..38db178 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -36,8 +36,7 @@ from leap.keymanager.keys import ( EncryptionScheme, is_address, build_key_from_dict, - KEYMANAGER_KEY_TAG, - TAGS_ADDRESS_PRIVATE_INDEX, + TYPE_ADDRESS_PRIVATE_INDEX, KEY_FINGERPRINT_KEY, KEY_DATA_KEY, ) @@ -210,6 +209,9 @@ class OpenPGPScheme(EncryptionScheme): signing and verification). """ + # type used on the soledad documents + OPENPGP_KEY_TYPE = OpenPGPKey.__name__ + def __init__(self, soledad, gpgbinary=None): """ Initialize the OpenPGP wrapper. @@ -427,8 +429,8 @@ class OpenPGPScheme(EncryptionScheme): :rtype: leap.soledad.document.SoledadDocument """ doclist = self._soledad.get_from_index( - TAGS_ADDRESS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, + TYPE_ADDRESS_PRIVATE_INDEX, + self.OPENPGP_KEY_TYPE, address, '1' if private else '0') if len(doclist) is 0: -- cgit v1.2.3 From 94251a4689d13ef34786334d9f47ce2c9cc6b200 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 13:36:35 -0600 Subject: Implement active key document --- src/leap/keymanager/__init__.py | 25 ++++--- src/leap/keymanager/keys.py | 25 +++++++ src/leap/keymanager/openpgp.py | 146 +++++++++++++++++++++++++++++++++------- 3 files changed, 161 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 0ffb6fc..562bfbf 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -519,15 +519,24 @@ class KeyManager(object): except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) - def put_key(self, key): + def put_key(self, key, address=None): """ Put C{key} in local storage. :param key: The key to be stored :type key: EncryptionKey + :param address: address for which this key will be active. If not set + all the uids will be activated + :type address: str + + :raises KeyAddressMismatch: if address doesn't match any uid on the key :raises KeyNotValidUpdate: if a key with the same uid exists and the new one is not a valid update for it """ + if address is not None and address not in key.address: + raise KeyAddressMismatch("UID %s found, but expected %s" + % (str(key.address), address)) + try: old_key = self._wrapper_map[type(key)].get_key(key.address[0], private=key.private) @@ -536,7 +545,7 @@ class KeyManager(object): if key.private or can_upgrade(key, old_key): try: - self._wrapper_map[type(key)].put_key(key) + self._wrapper_map[type(key)].put_key(key, address) except IndexError as e: leap_assert( False, "Unsupported key type. Error {0!r}".format(e)) @@ -553,7 +562,7 @@ class KeyManager(object): :type key: str :param ktype: the type of the key. :type ktype: subclass of EncryptionKey - :param address: if set used to check that the key is for this address + :param address: address for which this key will be active :type address: str :param validation: validation level for this key (default: 'Weak_Chain') @@ -564,12 +573,9 @@ class KeyManager(object): new one is not a valid update for it """ pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) - if address is not None and address not in pubkey.address: - raise KeyAddressMismatch("Key UID %s, but expected %s" - % (pubkey.address, address)) pubkey.validation = validation - self.put_key(pubkey) + self.put_key(pubkey, address) def fetch_key(self, address, uri, ktype, validation=ValidationLevel.Weak_Chain): @@ -600,12 +606,9 @@ class KeyManager(object): pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) if pubkey is None: raise KeyNotFound(uri) - if address not in pubkey.address: - raise KeyAddressMismatch("UID %s found, but expected %s" - % (str(pubkey.address), address)) pubkey.validation = validation - self.put_key(pubkey) + self.put_key(pubkey, address) from ._version import get_versions __version__ = get_versions()['version'] diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 5aeb794..d1b5d93 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -64,6 +64,8 @@ KEY_TAGS_KEY = 'tags' # KEYMANAGER_KEY_TAG = 'keymanager-key' +KEYMANAGER_ACTIVE_TAG = 'keymanager-active' +KEYMANAGER_ACTIVE_TYPE = '-active' # @@ -71,12 +73,18 @@ KEYMANAGER_KEY_TAG = 'keymanager-key' # TAGS_PRIVATE_INDEX = 'by-tags-private' +TYPE_ID_PRIVATE_INDEX = 'by-type-id-private' TYPE_ADDRESS_PRIVATE_INDEX = 'by-type-address-private' INDEXES = { TAGS_PRIVATE_INDEX: [ KEY_TAGS_KEY, 'bool(%s)' % KEY_PRIVATE_KEY, ], + TYPE_ID_PRIVATE_INDEX: [ + KEY_TYPE_KEY, + KEY_ID_KEY, + 'bool(%s)' % KEY_PRIVATE_KEY, + ], TYPE_ADDRESS_PRIVATE_INDEX: [ KEY_TYPE_KEY, KEY_ADDRESS_KEY, @@ -215,6 +223,23 @@ class EncryptionKey(object): KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], }) + def get_active_json(self, address): + """ + Return a JSON string describing this key. + + :param address: Address for wich the key is active + :type address: str + :return: The JSON string describing this key. + :rtype: str + """ + return json.dumps({ + KEY_ADDRESS_KEY: address, + KEY_TYPE_KEY: self.__class__.__name__ + KEYMANAGER_ACTIVE_TYPE, + KEY_ID_KEY: self.key_id, + KEY_PRIVATE_KEY: self.private, + KEY_TAGS_KEY: [KEYMANAGER_ACTIVE_TAG], + }) + def __repr__(self): """ Representation of this class diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 38db178..52655d0 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -36,9 +36,12 @@ from leap.keymanager.keys import ( EncryptionScheme, is_address, build_key_from_dict, + TYPE_ID_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_FINGERPRINT_KEY, KEY_DATA_KEY, + KEY_ID_KEY, + KEYMANAGER_ACTIVE_TYPE, ) @@ -193,6 +196,18 @@ def _build_key_from_gpg(address, key, key_data): ) +def _parse_address(address): + """ + Remove the identity suffix after the '+' until the '@' + e.g.: test_user+something@provider.com becomes test_user@provider.com + since the key belongs to the identity without the '+' suffix. + + :type address: str + :rtype: str + """ + return re.sub(r'\+.*\@', '@', address) + + # # The OpenPGP wrapper # @@ -210,7 +225,8 @@ class OpenPGPScheme(EncryptionScheme): """ # type used on the soledad documents - OPENPGP_KEY_TYPE = OpenPGPKey.__name__ + KEY_TYPE = OpenPGPKey.__name__ + ACTIVE_TYPE = KEY_TYPE + KEYMANAGER_ACTIVE_TYPE def __init__(self, soledad, gpgbinary=None): """ @@ -282,7 +298,7 @@ class OpenPGPScheme(EncryptionScheme): openpgp_key = _build_key_from_gpg( address, key, gpg.export_keys(key['fingerprint'], secret=secret)) - self.put_key(openpgp_key) + self.put_key(openpgp_key, address) return self.get_key(address, private=True) @@ -299,10 +315,7 @@ class OpenPGPScheme(EncryptionScheme): :rtype: OpenPGPKey @raise KeyNotFound: If the key was not found on local storage. """ - # Remove the identity suffix after the '+' until the '@' - # e.g.: test_user+something@provider.com becomes test_user@provider.com - # since the key belongs to the identity without the '+' suffix. - address = re.sub(r'\+.*\@', '@', address) + address = _parse_address(address) doc = self._get_key_doc(address, private) if doc is None: @@ -371,12 +384,15 @@ class OpenPGPScheme(EncryptionScheme): return (openpgp_pubkey, openpgp_privkey) - def put_ascii_key(self, key_data): + def put_ascii_key(self, key_data, address=None): """ Put key contained in ascii-armored C{key_data} in local storage. :param key_data: The key data to be stored. :type key_data: str or unicode + :param address: address for which this key will be active. If not set + all the uids will be activated + :type address: str """ leap_assert_type(key_data, (str, unicode)) @@ -387,21 +403,41 @@ class OpenPGPScheme(EncryptionScheme): leap_assert(False, repr(e)) if openpgp_pubkey is not None: - self.put_key(openpgp_pubkey) + self.put_key(openpgp_pubkey, address) if openpgp_privkey is not None: - self.put_key(openpgp_privkey) + self.put_key(openpgp_privkey, address) - def put_key(self, key): + def put_key(self, key, address=None): """ Put C{key} in local storage. :param key: The key to be stored. :type key: OpenPGPKey + :param address: address for which this key will be active. If not set + all the uids will be activated + :type address: str """ - doc = self._get_key_doc(key.address[0], private=key.private) - if doc is None: - self._soledad.create_doc_from_json(key.get_json()) + if address is not None: + active_address = [_parse_address(address)] else: + active_address = key.address + + self._put_key_doc(key) + self._put_active_doc(key, active_address) + + def _put_key_doc(self, key): + """ + Put key document in soledad + + :type key: OpenPGPKey + """ + docs = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key.key_id, + '1' if key.private else '0') + if len(docs) != 0: + doc = docs.pop() if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: # in case of an update of the key merge them with gnupg with self._temporary_gpgwrapper() as gpg: @@ -412,8 +448,41 @@ class OpenPGPScheme(EncryptionScheme): key.address[0], gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) - doc.set_json(key.get_json()) - self._soledad.put_doc(doc) + doc.set_json(key.get_json()) + 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, doc.content[KEY_FINGERPRINT_KEY])) + else: + self._soledad.create_doc_from_json(key.get_json()) + + def _put_active_doc(self, key, addresses): + """ + Put active key document in soledad + + :type key: OpenPGPKey + :type addresses: list(str) + """ + for address in addresses: + docs = self._soledad.get_from_index( + TYPE_ADDRESS_PRIVATE_INDEX, + self.ACTIVE_TYPE, + address, + '1' if key.private else '0') + if len(docs) == 1: + doc = docs.pop() + doc.set_json(key.get_active_json(address)) + self._soledad.put_doc(doc) + else: + if len(docs) > 1: + logger.error("There is more than one active key document " + "for the address %s" % (address,)) + for doc in docs: + self._soledad.delete_doc(doc) + self._soledad.create_doc_from_json( + key.get_active_json(address)) def _get_key_doc(self, address, private=False): """ @@ -428,17 +497,26 @@ class OpenPGPScheme(EncryptionScheme): :return: The document with the key or None if it does not exist. :rtype: leap.soledad.document.SoledadDocument """ - doclist = self._soledad.get_from_index( + activedoc = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, - self.OPENPGP_KEY_TYPE, + self.ACTIVE_TYPE, address, '1' if private else '0') - if len(doclist) is 0: + if len(activedoc) is 0: return None + leap_assert( + len(activedoc) is 1, + 'Found more than one key for address %s!' % (address,)) + + key_id = activedoc[0].content[KEY_ID_KEY] + doclist = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key_id, + '1' if private else '0') leap_assert( len(doclist) is 1, - 'Found more than one %s key for address!' % - 'private' if private else 'public') + 'There is %d keys for id %s!' % (len(doclist), key_id)) return doclist.pop() def delete_key(self, key): @@ -447,17 +525,37 @@ class OpenPGPScheme(EncryptionScheme): May raise: errors.KeyNotFound - errors.KeyAttributesDiffer :param key: The key to be removed. :type key: EncryptionKey """ leap_assert_type(key, OpenPGPKey) - doc = self._get_key_doc(key.address[0], key.private) + activedocs = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.ACTIVE_TYPE, + key.key_id, + '1' if key.private else '0') + for doc in activedocs: + self._soledad.delete_doc(doc) + + docs = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key.key_id, + '1' if key.private else '0') + 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: raise errors.KeyNotFound(key) - if doc.content[KEY_FINGERPRINT_KEY] != key.fingerprint: - raise errors.KeyAttributesDiffer(key) self._soledad.delete_doc(doc) # -- cgit v1.2.3 From f07d407523e6b76076824fa53e4c3568bc88764f Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 18:36:59 -0600 Subject: Implement multi uid support --- src/leap/keymanager/__init__.py | 12 ++- src/leap/keymanager/keys.py | 15 ++-- src/leap/keymanager/openpgp.py | 120 ++++++++++++--------------- src/leap/keymanager/tests/test_keymanager.py | 48 +++++------ src/leap/keymanager/tests/test_validation.py | 21 ++--- src/leap/keymanager/validation.py | 4 - 6 files changed, 100 insertions(+), 120 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 562bfbf..c64cdea 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -319,7 +319,6 @@ class KeyManager(object): return map( lambda doc: build_key_from_dict( self._key_class_from_type(doc.content['type']), - doc.content['address'][0], doc.content), self._soledad.get_from_index( TAGS_PRIVATE_INDEX, @@ -519,26 +518,25 @@ class KeyManager(object): except IndexError as e: leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) - def put_key(self, key, address=None): + def put_key(self, key, address): """ Put C{key} in local storage. :param key: The key to be stored :type key: EncryptionKey - :param address: address for which this key will be active. If not set - all the uids will be activated + :param address: address for which this key will be active :type address: str :raises KeyAddressMismatch: if address doesn't match any uid on the key :raises KeyNotValidUpdate: if a key with the same uid exists and the new one is not a valid update for it """ - if address is not None and address not in key.address: + if address not in key.address: raise KeyAddressMismatch("UID %s found, but expected %s" % (str(key.address), address)) try: - old_key = self._wrapper_map[type(key)].get_key(key.address[0], + old_key = self._wrapper_map[type(key)].get_key(address, private=key.private) except KeyNotFound: old_key = None @@ -553,7 +551,7 @@ class KeyManager(object): raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s" % (old_key.key_id, key.key_id)) - def put_raw_key(self, key, ktype, address=None, + def put_raw_key(self, key, ktype, address, validation=ValidationLevel.Weak_Chain): """ Put C{key} in local storage. diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index d1b5d93..2108aa2 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -109,20 +109,15 @@ def is_address(address): return bool(re.match('[\w.-]+@[\w.-]+', address)) -def build_key_from_dict(kClass, address, kdict): +def build_key_from_dict(kClass, kdict): """ - Build an C{kClass} key bound to C{address} based on info in C{kdict}. + Build an C{kClass} key based on info in C{kdict}. - :param address: The address bound to the key. - :type address: str :param kdict: Dictionary with key data. :type kdict: dict :return: An instance of the key. :rtype: C{kClass} """ - leap_assert( - address in kdict[KEY_ADDRESS_KEY], - 'Wrong address in key data.') try: validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) except ValueError: @@ -135,7 +130,7 @@ def build_key_from_dict(kClass, address, kdict): refreshed_at = _to_datetime(kdict[KEY_REFRESHED_AT_KEY]) return kClass( - [address], + kdict[KEY_ADDRESS_KEY], key_id=kdict[KEY_ID_KEY], fingerprint=kdict[KEY_FINGERPRINT_KEY], key_data=kdict[KEY_DATA_KEY], @@ -315,12 +310,14 @@ class EncryptionScheme(object): pass @abstractmethod - def put_key(self, key): + def put_key(self, key, address): """ Put a key in local storage. :param key: The key to be stored. :type key: EncryptionKey + :param address: address for which this key will be active. + :type address: str """ pass diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 52655d0..4f96574 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -38,6 +38,7 @@ from leap.keymanager.keys import ( build_key_from_dict, TYPE_ID_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, + KEY_ADDRESS_KEY, KEY_FINGERPRINT_KEY, KEY_DATA_KEY, KEY_ID_KEY, @@ -110,9 +111,9 @@ class TempGPGWrapper(object): # itself is enough to also have the public key in the keyring, # and we want to count the keys afterwards. - privaddrs = map(lambda privkey: privkey.address[0], privkeys) + privids = map(lambda privkey: privkey.key_id, privkeys) publkeys = filter( - lambda pubkey: pubkey.address[0] not in privaddrs, publkeys) + lambda pubkey: pubkey.key_id not in privids, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) @@ -163,16 +164,13 @@ class TempGPGWrapper(object): shutil.rmtree(self._gpg.homedir) -def _build_key_from_gpg(address, key, key_data): +def _build_key_from_gpg(key, key_data): """ - Build an OpenPGPKey for C{address} based on C{key} from - local gpg storage. + Build an OpenPGPKey based on C{key} from local gpg storage. ASCII armored GPG key data has to be queried independently in this wrapper, so we receive it in C{key_data}. - :param address: The address bound to the key. - :type address: str :param key: Key obtained from GPG storage. :type key: dict :param key_data: Key data obtained from GPG storage. @@ -183,9 +181,12 @@ def _build_key_from_gpg(address, key, key_data): 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], + address, key_id=key['keyid'], fingerprint=key['fingerprint'], key_data=key_data, @@ -198,14 +199,18 @@ def _build_key_from_gpg(address, key, key_data): def _parse_address(address): """ - Remove the identity suffix after the '+' until the '@' + Remove name, '<', '>' and the identity suffix after the '+' until the '@' e.g.: test_user+something@provider.com becomes test_user@provider.com since the key belongs to the identity without the '+' suffix. :type address: str :rtype: str """ - return re.sub(r'\+.*\@', '@', address) + mail_regex = '(.*<)?([\w.-]+)(\+.*)?(@[\w.-]+)(>.*)?' + match = re.match(mail_regex, address) + if match is None: + return None + return ''.join(match.group(2, 4)) # @@ -289,15 +294,17 @@ class OpenPGPScheme(EncryptionScheme): leap_assert( len(key['uids']) is 1, # with just one uid! 'Wrong number of uids for key: %d.' % len(key['uids'])) - leap_assert( - re.match('.*<%s>$' % address, key['uids'][0]) is not None, - 'Key not correctly bound to address.') + uid_match = False + for uid in key['uids']: + if re.match('.*<%s>$' % address, uid) is not None: + uid_match = True + return + leap_assert(uid_match, 'Key not correctly bound to address.') # insert both public and private keys in storage for secret in [True, False]: key = gpg.list_keys(secret=secret).pop() openpgp_key = _build_key_from_gpg( - address, key, - gpg.export_keys(key['fingerprint'], secret=secret)) + key, gpg.export_keys(key['fingerprint'], secret=secret)) self.put_key(openpgp_key, address) return self.get_key(address, private=True) @@ -320,7 +327,10 @@ class OpenPGPScheme(EncryptionScheme): doc = self._get_key_doc(address, private) if doc is None: raise errors.KeyNotFound(address) - return build_key_from_dict(OpenPGPKey, address, doc.content) + leap_assert( + address in doc.content[KEY_ADDRESS_KEY], + 'Wrong address in key data.') + return build_key_from_dict(OpenPGPKey, doc.content) def parse_ascii_key(self, key_data): """ @@ -337,7 +347,6 @@ class OpenPGPScheme(EncryptionScheme): leap_assert_type(key_data, (str, unicode)) # TODO: add more checks for correct key data. leap_assert(key_data is not None, 'Data does not represent a key.') - mail_regex = '.*<([\w.-]+@[\w.-]+)>.*' with self._temporary_gpgwrapper() as gpg: # TODO: inspect result, or use decorator @@ -354,44 +363,30 @@ class OpenPGPScheme(EncryptionScheme): except IndexError: return (None, None) - # extract adress from first uid on key - match = re.match(mail_regex, pubkey['uids'].pop()) - leap_assert(match is not None, 'No user address in key data.') - address = match.group(1) - openpgp_privkey = None if privkey is not None: - match = re.match(mail_regex, privkey['uids'].pop()) - leap_assert(match is not None, 'No user address in key data.') - privaddress = match.group(1) - # build private key openpgp_privkey = _build_key_from_gpg( - privaddress, privkey, + privkey, gpg.export_keys(privkey['fingerprint'], secret=True)) - - leap_check(address == privaddress, - 'Addresses in public and private key differ.', - errors.KeyAddressMismatch) leap_check(pubkey['fingerprint'] == privkey['fingerprint'], 'Fingerprints for public and private key differ.', errors.KeyFingerprintMismatch) # build public key openpgp_pubkey = _build_key_from_gpg( - address, pubkey, + pubkey, gpg.export_keys(pubkey['fingerprint'], secret=False)) return (openpgp_pubkey, openpgp_privkey) - def put_ascii_key(self, key_data, address=None): + def put_ascii_key(self, key_data, address): """ Put key contained in ascii-armored C{key_data} in local storage. :param key_data: The key data to be stored. :type key_data: str or unicode - :param address: address for which this key will be active. If not set - all the uids will be activated + :param address: address for which this key will be active :type address: str """ leap_assert_type(key_data, (str, unicode)) @@ -407,23 +402,17 @@ class OpenPGPScheme(EncryptionScheme): if openpgp_privkey is not None: self.put_key(openpgp_privkey, address) - def put_key(self, key, address=None): + def put_key(self, key, address): """ Put C{key} in local storage. :param key: The key to be stored. :type key: OpenPGPKey - :param address: address for which this key will be active. If not set - all the uids will be activated + :param address: address for which this key will be active. :type address: str """ - if address is not None: - active_address = [_parse_address(address)] - else: - active_address = key.address - self._put_key_doc(key) - self._put_active_doc(key, active_address) + self._put_active_doc(key, address) def _put_key_doc(self, key): """ @@ -445,7 +434,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() key = _build_key_from_gpg( - key.address[0], gpgkey, + gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) doc.set_json(key.get_json()) @@ -458,31 +447,30 @@ class OpenPGPScheme(EncryptionScheme): else: self._soledad.create_doc_from_json(key.get_json()) - def _put_active_doc(self, key, addresses): + def _put_active_doc(self, key, address): """ Put active key document in soledad :type key: OpenPGPKey - :type addresses: list(str) - """ - for address in addresses: - docs = self._soledad.get_from_index( - TYPE_ADDRESS_PRIVATE_INDEX, - self.ACTIVE_TYPE, - address, - '1' if key.private else '0') - if len(docs) == 1: - doc = docs.pop() - doc.set_json(key.get_active_json(address)) - self._soledad.put_doc(doc) - else: - if len(docs) > 1: - logger.error("There is more than one active key document " - "for the address %s" % (address,)) - for doc in docs: - self._soledad.delete_doc(doc) - self._soledad.create_doc_from_json( - key.get_active_json(address)) + :type addresses: str + """ + docs = self._soledad.get_from_index( + TYPE_ADDRESS_PRIVATE_INDEX, + self.ACTIVE_TYPE, + address, + '1' if key.private else '0') + if len(docs) == 1: + doc = docs.pop() + doc.set_json(key.get_active_json(address)) + self._soledad.put_doc(doc) + else: + if len(docs) > 1: + logger.error("There is more than one active key document " + "for the address %s" % (address,)) + for doc in docs: + self._soledad.delete_doc(doc) + self._soledad.create_doc_from_json( + key.get_active_json(address)) def _get_key_doc(self, address, private=False): """ diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 4daf346..6aeb67a 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -89,7 +89,7 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'encr_used': False, 'sign_used': True, } - key = build_key_from_dict(OpenPGPKey, ADDRESS, kdict) + key = build_key_from_dict(OpenPGPKey, kdict) self.assertEqual( kdict['address'], key.address, 'Wrong data in key.') @@ -144,7 +144,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = pgp.get_key(ADDRESS, private=False) pgp.delete_key(key) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) @@ -153,7 +153,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = pgp.get_key(ADDRESS, private=False) self.assertIsInstance(key, openpgp.OpenPGPKey) self.assertTrue( @@ -167,7 +167,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) key = pgp.get_key(ADDRESS, private=False) @@ -181,7 +181,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # encrypt pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) pubkey = pgp.get_key(ADDRESS, private=False) cyphertext = pgp.encrypt('data', pubkey) # assert @@ -193,7 +193,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): # decrypt self.assertRaises( KeyNotFound, pgp.get_key, ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = pgp.get_key(ADDRESS, private=True) pgp.delete_key(pubkey) pgp.delete_key(privkey) @@ -216,7 +216,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_with_public_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY) + pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) data = 'data' pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( @@ -226,11 +226,11 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_wrong_key_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) + pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, @@ -239,7 +239,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_encrypt_sign_with_public_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) pubkey = pgp.get_key(ADDRESS, private=False) @@ -250,7 +250,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) pubkey = pgp.get_key(ADDRESS, private=False) @@ -264,12 +264,12 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_wrong_key_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) pubkey = pgp.get_key(ADDRESS, private=False) encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) - pgp.put_ascii_key(PUBLIC_KEY_2) + pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) self.assertRaises( errors.InvalidSignature, @@ -278,7 +278,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey, detach=False) @@ -288,10 +288,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_encrypt_sign_decrypt_verify(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) pubkey = pgp.get_key(ADDRESS, private=False) privkey = pgp.get_key(ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY_2) + pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) pubkey2 = pgp.get_key(ADDRESS_2, private=False) privkey2 = pgp.get_key(ADDRESS_2, private=True) data = 'data' @@ -304,7 +304,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify_detached_sig(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signature = pgp.sign(data, privkey, detach=True) @@ -316,7 +316,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_all_keys_in_db(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get public keys keys = km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') @@ -330,7 +330,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_public_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key key = km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) @@ -342,7 +342,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_get_private_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key key = km.get_key(ADDRESS, OpenPGPKey, private=True, fetch_remote=False) @@ -364,7 +364,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ token = "mytoken" km = self._key_manager(token=token) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) km._fetcher.put = Mock() # the following data will be used on the send km.ca_cert_path = 'capath' @@ -447,7 +447,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ km = self._key_manager(url='http://nickserver.domain') - km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) @@ -509,7 +509,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get public key pubkey = km.get_key( ADDRESS, OpenPGPKey, private=False, fetch_remote=False) @@ -526,7 +526,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get private key for signing privkey = km.get_key( ADDRESS, OpenPGPKey, private=True, fetch_remote=False) diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 3ae873d..400d36e 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -37,47 +37,48 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): def test_none_old_key(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey) + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, KEY_FINGERPRINT) def test_cant_upgrade(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Provider_Trust) self.assertRaises(KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, - OpenPGPKey) + OpenPGPKey, ADDRESS) def test_fingerprint_level(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey, + km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Fingerprint) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) def test_expired_key(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey) + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) + km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) def test_expired_fail_lower_level(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Third_Party_Endorsement) self.assertRaises( KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, OpenPGPKey, + ADDRESS, validation=ValidationLevel.Provider_Trust) def test_roll_back(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey) - km.put_raw_key(EXPIRED_KEY, OpenPGPKey) + km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) + km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index cf5b4a8..245013e 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -67,10 +67,6 @@ def can_upgrade(new_key, old_key): if old_key is None: return True - if new_key.address != old_key.address: - # XXX how do we map multiple IDs? (#6212) - return False - # An update of the same key if new_key.fingerprint == old_key.fingerprint: return True -- cgit v1.2.3 From 2f29739946db6cd360296639830a3bfe7d8c3f31 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 10 Nov 2014 19:00:04 -0600 Subject: Fix comments --- src/leap/keymanager/__init__.py | 4 ++-- src/leap/keymanager/keys.py | 4 ++-- src/leap/keymanager/openpgp.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index c64cdea..e4be9c4 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -493,8 +493,8 @@ class KeyManager(object): verified using this detached signature. :type detached_sig: str - :return: The signed data. - :rtype: str + :return: signature matches + :rtype: bool """ leap_assert_type(pubkey, EncryptionKey) leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 2108aa2..7c732e3 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -411,7 +411,7 @@ class EncryptionScheme(object): verified against this sdetached signature. :type detached_sig: str - :return: The signed data. - :rtype: str + :return: signature matches + :rtype: bool """ pass diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 4f96574..3f298f7 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -742,8 +742,8 @@ class OpenPGPScheme(EncryptionScheme): verified against this detached signature. :type detached_sig: str - :return: The ascii-armored signed data. - :rtype: str + :return: signature matches + :rtype: bool """ leap_assert_type(pubkey, OpenPGPKey) leap_assert(pubkey.private is False) -- cgit v1.2.3 From c794991d65d987b59014d067e730976684307775 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 25 Nov 2014 12:36:24 -0200 Subject: Fix test to use correct new signature for openpgp.put_ascii_key(). --- src/leap/keymanager/tests/test_keymanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 6aeb67a..319d2e1 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -205,7 +205,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_private_raises(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY) + pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) data = 'data' privkey = pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) -- cgit v1.2.3 From 7fabed5aad430b418ea4abd488cf8d20e92ab3fe Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 20 Nov 2014 10:55:09 -0600 Subject: Use addresses instead of keys on the public API --- src/leap/keymanager/__init__.py | 125 +++++++++++++++++---------- src/leap/keymanager/tests/test_keymanager.py | 25 ++---- 2 files changed, 85 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index e4be9c4..1704e0b 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -43,7 +43,7 @@ except (ImportError, AssertionError): import logging import requests -from leap.common.check import leap_assert, leap_assert_type +from leap.common.check import leap_assert from leap.common.events import signal from leap.common.events import events_pb2 as proto from leap.common.decorators import memoized_method @@ -56,7 +56,6 @@ from leap.keymanager.errors import ( from leap.keymanager.validation import ValidationLevel, can_upgrade from leap.keymanager.keys import ( - EncryptionKey, build_key_from_dict, KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, @@ -395,70 +394,92 @@ class KeyManager(object): # encrypt/decrypt and sign/verify API # - def encrypt(self, data, pubkey, passphrase=None, sign=None, - cipher_algo='AES256'): + def encrypt(self, data, address, ktype, passphrase=None, sign=None, + cipher_algo='AES256', fetch_remote=True): """ - Encrypt C{data} using public @{key} and sign with C{sign} key. + Encrypt C{data} for C{address} and sign with C{sign} address. :param data: The data to be encrypted. :type data: str - :param pubkey: The key used to encrypt. - :type pubkey: EncryptionKey + :param address: The address to encrypt it for. + :type address: str + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for the signature. :type passphrase: str - :param sign: The key used for signing. - :type sign: EncryptionKey + :param sign: The address to be used for signature. + :type sign: str :param cipher_algo: The cipher algorithm to use. :type cipher_algo: str + :param fetch_remote: If key not found in local storage try to fetch + from nickserver + :type fetch_remote: bool :return: The encrypted data. :rtype: str + + :raise KeyNotFound: If any of the keys was not found both locally and + in keyserver. + :raise EncryptError: Raised if failed encrypting for some reason. """ - leap_assert_type(pubkey, EncryptionKey) - leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') - leap_assert(pubkey.private is False, 'Key is not public.') - return self._wrapper_map[pubkey.__class__].encrypt( - data, pubkey, passphrase, sign, cipher_algo=cipher_algo) + pubkey = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + privkey = None + if sign is not None: + privkey = self.get_key(sign, ktype, private=True) + return self._wrapper_map[ktype].encrypt( + data, pubkey, passphrase, privkey, cipher_algo=cipher_algo) - def decrypt(self, data, privkey, passphrase=None, verify=None): + def decrypt(self, data, address, ktype, passphrase=None, verify=None, + fetch_remote=True): """ - Decrypt C{data} using private @{privkey} and verify with C{verify} key. + Decrypt C{data} using private key from @{address} and verify with + C{verify} address. :param data: The data to be decrypted. :type data: str - :param privkey: The key used to decrypt. - :type privkey: OpenPGPKey + :param address: The address to who was encrypted. + :type address: str + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param passphrase: The passphrase for the secret key used for decryption. :type passphrase: str - :param verify: The key used to verify a signature. - :type verify: OpenPGPKey + :param verify: The address to be used for signature. + :type verify: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool :return: The decrypted data. :rtype: str + :raise KeyNotFound: If any of the keys was not found both locally and + in keyserver. + :raise DecryptError: Raised if failed decrypting for some reason. :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + C{verify} address. """ - leap_assert_type(privkey, EncryptionKey) - leap_assert( - privkey.__class__ in self._wrapper_map, - 'Unknown key type.') - leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].decrypt( - data, privkey, passphrase, verify) + privkey = self.get_key(address, ktype, private=True) + pubkey = None + if verify is not None: + pubkey = self.get_key(verify, ktype, private=False, + fetch_remote=fetch_remote) + return self._wrapper_map[ktype].decrypt( + data, privkey, passphrase, pubkey) - def sign(self, data, privkey, digest_algo='SHA512', clearsign=False, + def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ - Sign C{data} with C{privkey}. + Sign C{data} with C{address}. :param data: The data to be signed. :type data: str - - :param privkey: The private key to be used to sign. - :type privkey: EncryptionKey + :param address: The address to be used to sign. + :type address: EncryptionKey + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param digest_algo: The hash digest to use. :type digest_algo: str :param clearsign: If True, create a cleartext signature. @@ -470,36 +491,46 @@ class KeyManager(object): :return: The signed data. :rtype: str + + :raise KeyNotFound: If the key was not found both locally and + in keyserver. + :raise SignFailed: If there was any error signing. """ - leap_assert_type(privkey, EncryptionKey) - leap_assert( - privkey.__class__ in self._wrapper_map, - 'Unknown key type.') - leap_assert(privkey.private is True, 'Key is not private.') - return self._wrapper_map[privkey.__class__].sign( + privkey = self.get_key(address, ktype, private=True) + return self._wrapper_map[ktype].sign( data, privkey, digest_algo=digest_algo, clearsign=clearsign, detach=detach, binary=binary) - def verify(self, data, pubkey, detached_sig=None): + def verify(self, data, address, ktype, detached_sig=None, + fetch_remote=True): """ - Verify signed C{data} with C{pubkey}, eventually using + Verify signed C{data} with C{address}, eventually using C{detached_sig}. :param data: The data to be verified. :type data: str - :param pubkey: The public key to be used on verification. - :type pubkey: EncryptionKey + :param address: The address to be used to verify. + :type address: EncryptionKey + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey :param detached_sig: A detached signature. If given, C{data} is verified using this detached signature. :type detached_sig: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool :return: signature matches :rtype: bool + + :raise KeyNotFound: If the key was not found both locally and + in keyserver. + :raise InvalidSignature: Raised if unable to verify the signature with + C{verify} address. """ - leap_assert_type(pubkey, EncryptionKey) - leap_assert(pubkey.__class__ in self._wrapper_map, 'Unknown key type.') - leap_assert(pubkey.private is False, 'Key is not public.') - return self._wrapper_map[pubkey.__class__].verify( + pubkey = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + return self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) def delete_key(self, key): diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 319d2e1..8ae12bf 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -218,10 +218,9 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) data = 'data' - pubkey = pgp.get_key(ADDRESS, private=False) self.assertRaises( AssertionError, - pgp.sign, data, pubkey) + pgp.sign, data, ADDRESS, OpenPGPKey) def test_verify_with_wrong_key_raises(self): pgp = openpgp.OpenPGPScheme( @@ -510,34 +509,24 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() # put raw private key km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - # get public key - pubkey = km.get_key( - ADDRESS, OpenPGPKey, private=False, fetch_remote=False) # encrypt - encdata = km.encrypt(self.RAW_DATA, pubkey) + encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) - # get private key - privkey = km.get_key( - ADDRESS, OpenPGPKey, private=True, fetch_remote=False) # decrypt - rawdata = km.decrypt(encdata, privkey) + rawdata = km.decrypt(encdata, ADDRESS, OpenPGPKey) self.assertEqual(self.RAW_DATA, rawdata) def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - # get private key for signing - privkey = km.get_key( - ADDRESS, OpenPGPKey, private=True, fetch_remote=False) # encrypt - signdata = km.sign(self.RAW_DATA, privkey, detach=False) + signdata = km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) - # get public key for verifying - pubkey = km.get_key( - ADDRESS, OpenPGPKey, private=False, fetch_remote=False) # decrypt - self.assertTrue(km.verify(signdata, pubkey)) + self.assertTrue(km.verify(signdata, ADDRESS, OpenPGPKey, + fetch_remote=False)) # Key material for testing -- cgit v1.2.3 From 9774f9b185118e77ee1c59cf3e9eecc0e43e6030 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 20 Nov 2014 10:56:21 -0600 Subject: Return signing key on signature verification Don't throw an exception if verification fails --- src/leap/keymanager/__init__.py | 18 ++++---- src/leap/keymanager/errors.py | 7 ---- src/leap/keymanager/keys.py | 7 ++-- src/leap/keymanager/openpgp.py | 27 +++++------- src/leap/keymanager/tests/test_keymanager.py | 62 ++++++++++++++++++++-------- 5 files changed, 65 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 1704e0b..b2b05f4 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -452,22 +452,21 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: The decrypted data. - :rtype: str + :return: The decrypted data and the signing key if signature verifies + :rtype: (unicode, EncryptionKey) :raise KeyNotFound: If any of the keys was not found both locally and in keyserver. :raise DecryptError: Raised if failed decrypting for some reason. - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} address. """ privkey = self.get_key(address, ktype, private=True) pubkey = None if verify is not None: pubkey = self.get_key(verify, ktype, private=False, fetch_remote=fetch_remote) - return self._wrapper_map[ktype].decrypt( + decrypted, signed = self._wrapper_map[ktype].decrypt( data, privkey, passphrase, pubkey) + return (decrypted, pubkey if signed else None) def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): @@ -520,18 +519,17 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: signature matches - :rtype: bool + :return: The signing key if signature verifies else None + :rtype: EncryptionKey :raise KeyNotFound: If the key was not found both locally and in keyserver. - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} address. """ pubkey = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) - return self._wrapper_map[ktype].verify( + signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) + return pubkey if signed else None def delete_key(self, key): """ diff --git a/src/leap/keymanager/errors.py b/src/leap/keymanager/errors.py index f896582..c068b27 100644 --- a/src/leap/keymanager/errors.py +++ b/src/leap/keymanager/errors.py @@ -51,13 +51,6 @@ class NoPasswordGiven(Exception): pass -class InvalidSignature(Exception): - """ - Raised when signature could not be verified. - """ - pass - - class EncryptError(Exception): """ Raised upon failures of encryption. diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 7c732e3..0e243ba 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -373,11 +373,10 @@ class EncryptionScheme(object): :param verify: The key used to verify a signature. :type verify: OpenPGPKey - :return: The decrypted data. - :rtype: str + :return: The decrypted data and if signature verifies + :rtype: (unicode, bool) - @raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. + :raise DecryptError: Raised if failed decrypting for some reason. """ pass diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 3f298f7..1d1de98 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -640,12 +640,10 @@ class OpenPGPScheme(EncryptionScheme): :param verify: The key used to verify a signature. :type verify: OpenPGPKey - :return: The decrypted data. - :rtype: unicode + :return: The decrypted data and if signature verifies + :rtype: (unicode, bool) :raise DecryptError: Raised if failed decrypting for some reason. - :raise InvalidSignature: Raised if unable to verify the signature with - C{verify} key. """ leap_assert(privkey.private is True, 'Key is not private.') keys = [privkey] @@ -658,15 +656,15 @@ class OpenPGPScheme(EncryptionScheme): result = gpg.decrypt( data, passphrase=passphrase, always_trust=True) self._assert_gpg_result_ok(result) + # verify signature - if (verify is not None): - if result.valid is False or \ - verify.fingerprint != result.pubkey_fingerprint: - raise errors.InvalidSignature( - 'Failed to verify signature with key %s: %s' % - (verify.key_id, result.stderr)) + sign_valid = False + if (verify is not None and + result.valid is True and + verify.fingerprint == result.pubkey_fingerprint): + sign_valid = True - return result.data + return (result.data, sign_valid) except errors.GPGError as e: logger.error('Failed to decrypt: %s.' % str(e)) raise errors.DecryptError(str(e)) @@ -764,9 +762,4 @@ class OpenPGPScheme(EncryptionScheme): valid = result.valid rfprint = result.fingerprint kfprint = gpgpubkey['fingerprint'] - # raise in case sig is invalid - if valid is False or rfprint != kfprint: - raise errors.InvalidSignature( - 'Failed to verify signature ' - 'with key %s.' % gpgpubkey['keyid']) - return True + return valid and rfprint == kfprint diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 8ae12bf..ee4462a 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -183,11 +183,12 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) pubkey = pgp.get_key(ADDRESS, private=False) - cyphertext = pgp.encrypt('data', pubkey) + data = 'data' + cyphertext = pgp.encrypt(data, pubkey) # assert self.assertTrue(cyphertext is not None) self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != 'data') + self.assertTrue(cyphertext != data) self.assertTrue(pgp.is_encrypted(cyphertext)) self.assertTrue(pgp.is_encrypted(cyphertext)) # decrypt @@ -195,6 +196,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): KeyNotFound, pgp.get_key, ADDRESS, private=True) pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = pgp.get_key(ADDRESS, private=True) + decrypted, _ = pgp.decrypt(cyphertext, privkey) + self.assertEqual(decrypted, data) pgp.delete_key(pubkey) pgp.delete_key(privkey) self.assertRaises( @@ -231,9 +234,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): signed = pgp.sign(data, privkey) pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - pgp.verify, signed, wrongkey) + self.assertFalse(pgp.verify(signed, wrongkey)) def test_encrypt_sign_with_public_raises(self): pgp = openpgp.OpenPGPScheme( @@ -260,7 +261,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): pgp.decrypt, encrypted_and_signed, privkey, verify=privkey) - def test_decrypt_verify_with_wrong_key_raises(self): + def test_decrypt_verify_with_wrong_key(self): pgp = openpgp.OpenPGPScheme( self._soledad, gpgbinary=GPG_BINARY_PATH) pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) @@ -270,9 +271,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) wrongkey = pgp.get_key(ADDRESS_2) - self.assertRaises( - errors.InvalidSignature, - pgp.verify, encrypted_and_signed, wrongkey) + decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, + verify=wrongkey) + self.assertEqual(decrypted, data) + self.assertFalse(validsign) def test_sign_verify(self): pgp = openpgp.OpenPGPScheme( @@ -296,9 +298,10 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): data = 'data' encrypted_and_signed = pgp.encrypt( data, pubkey2, sign=privkey) - res = pgp.decrypt( + res, validsign = pgp.decrypt( encrypted_and_signed, privkey2, verify=pubkey) - self.assertTrue(data, res) + self.assertEqual(data, res) + self.assertTrue(validsign) def test_sign_verify_detached_sig(self): pgp = openpgp.OpenPGPScheme( @@ -308,7 +311,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): privkey = pgp.get_key(ADDRESS, private=True) signature = pgp.sign(data, privkey, detach=True) pubkey = pgp.get_key(ADDRESS, private=False) - self.assertTrue(pgp.verify(data, pubkey, detached_sig=signature)) + validsign = pgp.verify(data, pubkey, detached_sig=signature) + self.assertTrue(validsign) class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): @@ -509,24 +513,46 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() # put raw private key km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) # encrypt encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - fetch_remote=False) + sign=ADDRESS_2, fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # decrypt - rawdata = km.decrypt(encdata, ADDRESS, OpenPGPKey) + rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, + verify=ADDRESS_2, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) + key = km.get_key(ADDRESS_2, OpenPGPKey, private=False, + fetch_remote=False) + self.assertEqual(signingkey.fingerprint, key.fingerprint) + + def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): + km = self._key_manager() + # put raw keys + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + # encrypt + encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) + self.assertNotEqual(self.RAW_DATA, encdata) + # verify + rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, + verify=ADDRESS, fetch_remote=False) + self.assertEqual(self.RAW_DATA, rawdata) + self.assertTrue(signingkey is None) def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - # encrypt signdata = km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) self.assertNotEqual(self.RAW_DATA, signdata) - # decrypt - self.assertTrue(km.verify(signdata, ADDRESS, OpenPGPKey, - fetch_remote=False)) + # verify + signingkey = km.verify(signdata, ADDRESS, OpenPGPKey, + fetch_remote=False) + key = km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) + self.assertEqual(signingkey.fingerprint, key.fingerprint) # Key material for testing -- cgit v1.2.3 From 7bd310c0b67c337f4a13ffac631a2068c57e9740 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 28 Nov 2014 10:37:00 -0600 Subject: Port to soledad new async API --- src/leap/keymanager/__init__.py | 438 +++++++++++++++++---------- src/leap/keymanager/errors.py | 6 + src/leap/keymanager/keys.py | 86 ++++-- src/leap/keymanager/openpgp.py | 366 +++++++++++++--------- src/leap/keymanager/tests/__init__.py | 109 +++++-- src/leap/keymanager/tests/test_keymanager.py | 394 +++++------------------- src/leap/keymanager/tests/test_openpgp.py | 250 +++++++++++++++ src/leap/keymanager/tests/test_validation.py | 49 +-- 8 files changed, 1024 insertions(+), 674 deletions(-) create mode 100644 src/leap/keymanager/tests/test_openpgp.py (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index b2b05f4..7ff437e 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -43,6 +43,8 @@ except (ImportError, AssertionError): import logging import requests +from twisted.internet import defer + from leap.common.check import leap_assert from leap.common.events import signal from leap.common.events import events_pb2 as proto @@ -51,7 +53,8 @@ from leap.common.decorators import memoized_method from leap.keymanager.errors import ( KeyNotFound, KeyAddressMismatch, - KeyNotValidUpgrade + KeyNotValidUpgrade, + UnsupportedKeyTypeError ) from leap.keymanager.validation import ValidationLevel, can_upgrade @@ -196,15 +199,20 @@ class KeyManager(object): @memoized_method(invalidation=300) def _fetch_keys_from_server(self, address): """ - Fetch keys bound to C{address} from nickserver and insert them in + Fetch keys bound to address from nickserver and insert them in local database. :param address: The address bound to the keys. :type address: str - :raise KeyNotFound: If the key was not found on nickserver. + :return: A Deferred which fires when the key is in the storage, + or which fails with KeyNotFound if the key was not found on + nickserver. + :rtype: Deferred + """ # request keys from the nickserver + d = defer.succeed(None) res = None try: res = self._get(self._nickserver_uri, {'address': address}) @@ -212,18 +220,22 @@ class KeyManager(object): server_keys = res.json() # insert keys in local database if self.OPENPGP_KEY in server_keys: - self.put_raw_key( + d = self.put_raw_key( server_keys['openpgp'], OpenPGPKey, address=address, validation=ValidationLevel.Provider_Trust) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: - raise KeyNotFound(address) + d = defer.fail(KeyNotFound(address)) + else: + d = defer.fail(e) logger.warning("HTTP error retrieving key: %r" % (e,)) logger.warning("%s" % (res.content,)) except Exception as e: + d = defer.fail(e) logger.warning("Error retrieving key: %r" % (e,)) + return d # # key management @@ -231,7 +243,7 @@ class KeyManager(object): def send_key(self, ktype): """ - Send user's key of type C{ktype} to provider. + Send user's key of type ktype to provider. Public key bound to user's is sent to provider, which will sign it and replace any prior keys for the same address in its database. @@ -239,27 +251,33 @@ class KeyManager(object): :param ktype: The type of the key. :type ktype: subclass of EncryptionKey - :raise KeyNotFound: If the key was not found in local database. + :return: A Deferred which fires when the key is sent, or which fails + with KeyNotFound if the key was not found in local database. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - leap_assert( - ktype is OpenPGPKey, - 'For now we only know how to send OpenPGP public keys.') - # prepare the public key bound to address - pubkey = self.get_key( + self._assert_supported_key_type(ktype) + + def send(pubkey): + data = { + self.PUBKEY_KEY: pubkey.key_data + } + uri = "%s/%s/users/%s.json" % ( + self._api_uri, + self._api_version, + self._uid) + self._put(uri, data) + signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + + d = self.get_key( self._address, ktype, private=False, fetch_remote=False) - data = { - self.PUBKEY_KEY: pubkey.key_data - } - uri = "%s/%s/users/%s.json" % ( - self._api_uri, - self._api_version, - self._uid) - self._put(uri, data) - signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + d.addCallback(send) + return d def get_key(self, address, ktype, private=False, fetch_remote=True): """ - Return a key of type C{ktype} bound to C{address}. + Return a key of type ktype bound to address. First, search for the key in local storage. If it is not available, then try to fetch from nickserver. @@ -274,36 +292,47 @@ class KeyManager(object): from nickserver :type fetch_remote: bool - :return: A key of type C{ktype} bound to C{address}. - :rtype: EncryptionKey - :raise KeyNotFound: If the key was not found both locally and in - keyserver. + :return: A Deferred which fires with an EncryptionKey of type ktype + bound to address, or which fails with KeyNotFound if no key + was found neither locally or in keyserver. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ + self._assert_supported_key_type(ktype) logger.debug("getting key for %s" % (address,)) leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) - try: - signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) - # return key if it exists in local database - key = self._wrapper_map[ktype].get_key(address, private=private) - signal(proto.KEYMANAGER_KEY_FOUND, address) + signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) + def key_found(key): + signal(proto.KEYMANAGER_KEY_FOUND, address) return key - except KeyNotFound: + + def key_not_found(failure): + if not failure.check(KeyNotFound): + return failure + signal(proto.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: - raise + return failure signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) - self._fetch_keys_from_server(address) # might raise KeyNotFound - key = self._wrapper_map[ktype].get_key(address, private=False) - signal(proto.KEYMANAGER_KEY_FOUND, address) - - return key + d = self._fetch_keys_from_server(address) + d.addCallback( + lambda _: + self._wrapper_map[ktype].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.addCallbacks(key_found, key_not_found) + return d def get_all_keys(self, private=False): """ @@ -312,33 +341,50 @@ class KeyManager(object): :param private: Include private keys :type private: bool - :return: A list with all keys in local db. - :rtype: list - """ - return map( - lambda doc: build_key_from_dict( - self._key_class_from_type(doc.content['type']), - doc.content), - self._soledad.get_from_index( - TAGS_PRIVATE_INDEX, - KEYMANAGER_KEY_TAG, - '1' if private else '0')) + :return: A Deferred which fires with a list of all keys in local db. + :rtype: Deferred + """ + def build_keys(docs): + return map( + lambda doc: build_key_from_dict( + self._key_class_from_type(doc.content['type']), + doc.content), + docs) + + # XXX: there is no check that the soledad indexes are ready, as it + # happens with EncryptionScheme. + # The usecases right now are not problematic. This could be solve + # adding a keytype to this funciont and moving the soledad request + # to the EncryptionScheme. + d = self._soledad.get_from_index( + TAGS_PRIVATE_INDEX, + KEYMANAGER_KEY_TAG, + '1' if private else '0') + d.addCallback(build_keys) + return d def gen_key(self, ktype): """ - Generate a key of type C{ktype} bound to the user's address. + Generate a key of type ktype bound to the user's address. :param ktype: The type of the key. :type ktype: subclass of EncryptionKey - :return: The generated key. - :rtype: EncryptionKey + :return: A Deferred which fires with the generated EncryptionKey. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) - key = self._wrapper_map[ktype].gen_key(self._address) - signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + self._assert_supported_key_type(ktype) - return key + def signal_finished(key): + signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + return key + + signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + d = self._wrapper_map[ktype].gen_key(self._address) + d.addCallback(signal_finished) + return d # # Setters/getters @@ -397,7 +443,8 @@ class KeyManager(object): def encrypt(self, data, address, ktype, passphrase=None, sign=None, cipher_algo='AES256', fetch_remote=True): """ - Encrypt C{data} for C{address} and sign with C{sign} address. + Encrypt data with the public key bound to address and sign with with + the private key bound to sign address. :param data: The data to be encrypted. :type data: str @@ -412,34 +459,39 @@ class KeyManager(object): :type sign: str :param cipher_algo: The cipher algorithm to use. :type cipher_algo: str - :param fetch_remote: If key not found in local storage try to fetch + :param fetch_remote: If key is not found in local storage try to fetch from nickserver :type fetch_remote: bool - :return: The encrypted data. - :rtype: str + :return: A Deferred which fires with the encrypted data as str, or + which fails with KeyNotFound if no keys were found neither + locally or in keyserver or fails with EncryptError if failed + encrypting for some reason. + :rtype: Deferred - :raise KeyNotFound: If any of the keys was not found both locally and - in keyserver. - :raise EncryptError: Raised if failed encrypting for some reason. + :raise UnsupportedKeyTypeError: if invalid key type """ - pubkey = self.get_key(address, ktype, private=False, - fetch_remote=fetch_remote) - privkey = None - if sign is not None: - privkey = self.get_key(sign, ktype, private=True) - return self._wrapper_map[ktype].encrypt( - data, pubkey, passphrase, privkey, cipher_algo=cipher_algo) + self._assert_supported_key_type(ktype) + + def encrypt(keys): + pubkey, signkey = keys + return self._wrapper_map[ktype].encrypt( + data, pubkey, passphrase, sign=signkey, + cipher_algo=cipher_algo) + + d = self._get_keys(ktype, address, sign, fetch_remote=fetch_remote) + d.addCallback(encrypt) + return d def decrypt(self, data, address, ktype, passphrase=None, verify=None, fetch_remote=True): """ - Decrypt C{data} using private key from @{address} and verify with - C{verify} address. + Decrypt data using private key from address and verify with public key + bound to verify address. :param data: The data to be decrypted. :type data: str - :param address: The address to who was encrypted. + :param address: The address to whom data was encrypted. :type address: str :param ktype: The type of the key. :type ktype: subclass of EncryptionKey @@ -452,26 +504,59 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: The decrypted data and the signing key if signature verifies - :rtype: (unicode, EncryptionKey) + :return: A Deferred which fires with the decrypted data as str and the + signing EncryptionKey if signature verifies, or which fails + with KeyNotFound if no keys were found neither locally or in + keyserver or fails with DecryptError if failed decrypting for + some reason. + :rtype: Deferred - :raise KeyNotFound: If any of the keys was not found both locally and - in keyserver. - :raise DecryptError: Raised if failed decrypting for some reason. + :raise UnsupportedKeyTypeError: if invalid key type """ - privkey = self.get_key(address, ktype, private=True) - pubkey = None - if verify is not None: - pubkey = self.get_key(verify, ktype, private=False, - fetch_remote=fetch_remote) - decrypted, signed = self._wrapper_map[ktype].decrypt( - data, privkey, passphrase, pubkey) - return (decrypted, pubkey if signed else None) + self._assert_supported_key_type(ktype) + + def decrypt(keys): + pubkey, privkey = keys + decrypted, signed = self._wrapper_map[ktype].decrypt( + data, privkey, passphrase=passphrase, verify=pubkey) + return (decrypted, pubkey if signed else None) + + d = self._get_keys(ktype, verify, address, fetch_remote) + d.addCallback(decrypt) + return d + + def _get_keys(self, ktype, public, private, fetch_remote=True): + """ + Get public and private keys of ktype. + + :param ktype: The type of the key. + :type ktype: subclass of EncryptionKey + :param public: The address of the public key. + :type public: str + :param private: The address of the private key. + :type private: str + :param fetch_remote: If key for verify not found in local storage try + to fetch from nickserver + :type fetch_remote: bool + + :return: A Deferred which fires with a tuple with public and private + EncryptionKeys, or which fails with KeyNotFound if no keys + were found neither locally or in keyserver. + :rtype: Deferred + """ + dpub = defer.succeed(None) + if public is not None: + dpub = self.get_key(public, ktype, private=False, + fetch_remote=fetch_remote) + dpriv = defer.succeed(None) + if private is not None: + dpriv = self.get_key(private, ktype, private=True) + return defer.gatherResults([dpub, dpriv]) def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ - Sign C{data} with C{address}. + Sign data with private key bound to address. :param data: The data to be signed. :type data: str @@ -488,23 +573,30 @@ class KeyManager(object): :param binary: If True, do not ascii armour the output. :type binary: bool - :return: The signed data. - :rtype: str + :return: A Deferred which fires with the signed data as str or fails + with KeyNotFound if no key was found neither locally or in + keyserver or fails with SignFailed if there was any error + signing. + :rtype: Deferred - :raise KeyNotFound: If the key was not found both locally and - in keyserver. - :raise SignFailed: If there was any error signing. + :raise UnsupportedKeyTypeError: if invalid key type """ - privkey = self.get_key(address, ktype, private=True) - return self._wrapper_map[ktype].sign( - data, privkey, digest_algo=digest_algo, clearsign=clearsign, - detach=detach, binary=binary) + self._assert_supported_key_type(ktype) + + def sign(privkey): + return self._wrapper_map[ktype].sign( + data, privkey, digest_algo=digest_algo, clearsign=clearsign, + detach=detach, binary=binary) + + d = self.get_key(address, ktype, private=True) + d.addCallback(sign) + return d def verify(self, data, address, ktype, detached_sig=None, fetch_remote=True): """ - Verify signed C{data} with C{address}, eventually using - C{detached_sig}. + Verify signed data with private key bound to address, eventually using + detached_sig. :param data: The data to be verified. :type data: str @@ -519,71 +611,90 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: The signing key if signature verifies else None - :rtype: EncryptionKey + :return: A Deferred which fires with the signing EncryptionKey if + signature verifies else None, or which fails with KeyNotFound + if no key was found neither locally or in keyserver. + :rtype: Deferred - :raise KeyNotFound: If the key was not found both locally and - in keyserver. + :raise UnsupportedKeyTypeError: if invalid key type """ - pubkey = self.get_key(address, ktype, private=False, - fetch_remote=fetch_remote) - signed = self._wrapper_map[ktype].verify( - data, pubkey, detached_sig=detached_sig) - return pubkey if signed else None + self._assert_supported_key_type(ktype) + + def verify(pubkey): + signed = self._wrapper_map[ktype].verify( + data, pubkey, detached_sig=detached_sig) + return pubkey if signed else None + + d = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + d.addCallback(verify) + return d def delete_key(self, key): """ - Remove C{key} from storage. - - May raise: - openpgp.errors.KeyNotFound - openpgp.errors.KeyAttributesDiffer + Remove key from storage. :param key: The key to be removed. :type key: EncryptionKey + + :return: A Deferred which fires when the key is deleted, or which fails + KeyNotFound if the key was not found on local storage. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - try: - self._wrapper_map[type(key)].delete_key(key) - except IndexError as e: - leap_assert(False, "Unsupported key type. Error {0!r}".format(e)) + self._assert_supported_key_type(type(key)) + return self._wrapper_map[type(key)].delete_key(key) def put_key(self, key, address): """ - Put C{key} in local storage. + Put key bound to address in local storage. :param key: The key to be stored :type key: EncryptionKey :param address: address for which this key will be active :type address: str - :raises KeyAddressMismatch: if address doesn't match any uid on the key - :raises KeyNotValidUpdate: if a key with the same uid exists and the - new one is not a valid update for it + :return: A Deferred which fires when the key is in the storage, or + which fails with KeyAddressMismatch if address doesn't match + any uid on the key or fails with KeyNotValidUpdate if a key + with the same uid exists and the new one is not a valid update + for it. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ - if address not in key.address: - raise KeyAddressMismatch("UID %s found, but expected %s" - % (str(key.address), address)) + self._assert_supported_key_type(type(key)) - try: - old_key = self._wrapper_map[type(key)].get_key(address, - private=key.private) - except KeyNotFound: - old_key = None - - if key.private or can_upgrade(key, old_key): - try: - self._wrapper_map[type(key)].put_key(key, address) - except IndexError as e: - leap_assert( - False, "Unsupported key type. Error {0!r}".format(e)) - else: - raise KeyNotValidUpgrade("Key %s can not be upgraded by new key %s" - % (old_key.key_id, key.key_id)) + if address not in key.address: + return defer.fail( + KeyAddressMismatch("UID %s found, but expected %s" + % (str(key.address), address))) + + def old_key_not_found(failure): + if failure.check(KeyNotFound): + return None + else: + return failure + + def check_upgrade(old_key): + if key.private or can_upgrade(key, old_key): + return self._wrapper_map[type(key)].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.addErrback(old_key_not_found) + d.addCallback(check_upgrade) + return d def put_raw_key(self, key, ktype, address, validation=ValidationLevel.Weak_Chain): """ - Put C{key} in local storage. + Put raw key bound to address in local storage. :param key: The ascii key to be stored :type key: str @@ -595,19 +706,24 @@ class KeyManager(object): (default: 'Weak_Chain') :type validation: ValidationLevel - :raises KeyAddressMismatch: if address doesn't match any uid on the key - :raises KeyNotValidUpdate: if a key with the same uid exists and the - new one is not a valid update for it + :return: A Deferred which fires when the key is in the storage, or + which fails with KeyAddressMismatch if address doesn't match + any uid on the key or fails with KeyNotValidUpdate if a key + with the same uid exists and the new one is not a valid update + for it. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ + self._assert_supported_key_type(ktype) pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) - pubkey.validation = validation - self.put_key(pubkey, address) + return self.put_key(pubkey, address) def fetch_key(self, address, uri, ktype, validation=ValidationLevel.Weak_Chain): """ - Fetch a public key for C{address} from the network and put it in + Fetch a public key bound to address from the network and put it in local storage. :param address: The email address of the key. @@ -620,22 +736,40 @@ class KeyManager(object): (default: 'Weak_Chain') :type validation: ValidationLevel - :raises KeyNotFound: if not valid key on C{uri} - :raises KeyAddressMismatch: if address doesn't match any uid on the key - :raises KeyNotValidUpdate: if a key with the same uid exists and the - new one is not a valid update for it + :return: A Deferred which fires when the key is in the storage, or + which fails with KeyNotFound: if not valid key on uri or fails + with KeyAddressMismatch if address doesn't match any uid on + the key or fails with KeyNotValidUpdate if a key with the same + uid exists and the new one is not a valid update for it. + :rtype: Deferred + + :raise UnsupportedKeyTypeError: if invalid key type """ + self._assert_supported_key_type(ktype) + res = self._get(uri) if not res.ok: - raise KeyNotFound(uri) + return defer.fail(KeyNotFound(uri)) # XXX parse binary keys pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(res.content) if pubkey is None: - raise KeyNotFound(uri) + return defer.fail(KeyNotFound(uri)) pubkey.validation = validation - self.put_key(pubkey, address) + return self.put_key(pubkey, address) + + def _assert_supported_key_type(self, ktype): + """ + Check if ktype is one of the supported key types + + :param ktype: the type of the key. + :type ktype: subclass of EncryptionKey + + :raise UnsupportedKeyTypeError: if invalid key type + """ + if ktype not in self._wrapper_map: + raise UnsupportedKeyTypeError(str(ktype)) from ._version import get_versions __version__ = get_versions()['version'] diff --git a/src/leap/keymanager/errors.py b/src/leap/keymanager/errors.py index c068b27..4041837 100644 --- a/src/leap/keymanager/errors.py +++ b/src/leap/keymanager/errors.py @@ -95,3 +95,9 @@ class KeyNotValidUpgrade(Exception): """ Already existing key can not be upgraded with the new key """ + + +class UnsupportedKeyTypeError(Exception): + """ + Invalid key type + """ diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 0e243ba..4e98de6 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -33,6 +33,7 @@ import time from abc import ABCMeta, abstractmethod from datetime import datetime from leap.common.check import leap_assert +from twisted.internet import defer from leap.keymanager.validation import ValidationLevel, toValidationLevel @@ -277,21 +278,61 @@ class EncryptionScheme(object): """ leap_assert(self._soledad is not None, "Cannot init indexes with null soledad") - # Ask the database for currently existing indexes. - db_indexes = dict(self._soledad.list_indexes()) - # Loop through the indexes we expect to find. - for name, expression in INDEXES.items(): - if name not in db_indexes: - # The index does not yet exist. - self._soledad.create_index(name, *expression) - continue - if expression == db_indexes[name]: - # The index exists and is up to date. - continue - # The index exists but the definition is not what expected, so we - # delete it and add the proper index expression. - self._soledad.delete_index(name) - self._soledad.create_index(name, *expression) + + def init_idexes(indexes): + deferreds = [] + db_indexes = dict(indexes) + # Loop through the indexes we expect to find. + for name, expression in INDEXES.items(): + if name not in db_indexes: + # The index does not yet exist. + d = self._soledad.create_index(name, *expression) + deferreds.append(d) + elif expression != db_indexes[name]: + # The index exists but the definition is not what expected, + # so we delete it and add the proper index expression. + d = self._soledad.delete_index(name) + d.addCallback( + lambda _: + self._soledad.create_index(name, *expression)) + deferreds.append(d) + return defer.gatherResults(deferreds, consumeErrors=True) + + self.deferred_indexes = self._soledad.list_indexes() + self.deferred_indexes.addCallback(init_idexes) + + def _wait_indexes(self, *methods): + """ + Methods that need to wait for the indexes to be ready. + + Heavily based on + http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/ + + :param methods: methods that need to wait for the indexes to be ready + :type methods: tuple(str) + """ + self.waiting = [] + self.stored = {} + + def restore(_): + for method in self.stored: + setattr(self, method, self.stored[method]) + for d in self.waiting: + d.callback(None) + + def makeWrapper(method): + def wrapper(*args, **kw): + d = defer.Deferred() + d.addCallback(lambda _: self.stored[method](*args, **kw)) + self.waiting.append(d) + return d + return wrapper + + for method in methods: + self.stored[method] = getattr(self, method) + setattr(self, method, makeWrapper(method)) + + self.deferred_indexes.addCallback(restore) @abstractmethod def get_key(self, address, private=False): @@ -303,9 +344,10 @@ class EncryptionScheme(object): :param private: Look for a private key instead of a public one? :type private: bool - :return: The key bound to C{address}. - :rtype: EncryptionKey - @raise KeyNotFound: If the key was not found on local storage. + :return: A Deferred which fires with the EncryptionKey bound to + address, or which fails with KeyNotFound if the key was not + found on local storage. + :rtype: Deferred """ pass @@ -318,6 +360,9 @@ class EncryptionScheme(object): :type key: EncryptionKey :param address: address for which this key will be active. :type address: str + + :return: A Deferred which fires when the key is in the storage. + :rtype: Deferred """ pass @@ -341,6 +386,11 @@ class EncryptionScheme(object): :param key: The key to be removed. :type key: EncryptionKey + + :return: A Deferred which fires when the key is deleted, or which + fails with KeyNotFound if the key was not found on local + storage. + :rtype: Deferred """ pass diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 1d1de98..f81fb0a 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -28,6 +28,7 @@ import io from datetime import datetime from gnupg import GPG from gnupg.gnupg import GPGUtilities +from twisted.internet import defer from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import errors @@ -243,6 +244,7 @@ class OpenPGPScheme(EncryptionScheme): :type gpgbinary: C{str} """ EncryptionScheme.__init__(self, soledad) + self._wait_indexes("get_key", "put_key") self._gpgbinary = gpgbinary # @@ -255,59 +257,67 @@ class OpenPGPScheme(EncryptionScheme): :param address: The address bound to the key. :type address: str - :return: The key bound to C{address}. - :rtype: OpenPGPKey - @raise KeyAlreadyExists: If key already exists in local database. + + :return: A Deferred which fires with the key bound to address, or fails + with KeyAlreadyExists if key already exists in local database. + :rtype: Deferred """ # make sure the key does not already exist leap_assert(is_address(address), 'Not an user address: %s' % address) - try: - self.get_key(address) - raise errors.KeyAlreadyExists(address) - except errors.KeyNotFound: - logger.debug('Key for %s not found' % (address,)) - - with self._temporary_gpgwrapper() as gpg: - # TODO: inspect result, or use decorator - params = gpg.gen_key_input( - key_type='RSA', - key_length=4096, - name_real=address, - name_email=address, - name_comment='') - logger.info("About to generate keys... This might take SOME time.") - gpg.gen_key(params) - logger.info("Keys for %s have been successfully " - "generated." % (address,)) - pubkeys = gpg.list_keys() - - # assert for new key characteristics - - # XXX This exception is not properly catched by the soledad - # bootstrapping, so if we do not finish generating the keys - # we end with a blocked thread -- kali - leap_assert( - len(pubkeys) is 1, # a unitary keyring! - 'Keyring has wrong number of keys: %d.' % len(pubkeys)) - key = gpg.list_keys(secret=True).pop() - leap_assert( - len(key['uids']) is 1, # with just one uid! - 'Wrong number of uids for key: %d.' % len(key['uids'])) - uid_match = False - for uid in key['uids']: - if re.match('.*<%s>$' % address, uid) is not None: - uid_match = True - return - leap_assert(uid_match, 'Key not correctly bound to address.') - # insert both public and private keys in storage - for secret in [True, False]: - key = gpg.list_keys(secret=secret).pop() - openpgp_key = _build_key_from_gpg( - key, gpg.export_keys(key['fingerprint'], secret=secret)) - self.put_key(openpgp_key, address) + def _gen_key(_): + with self._temporary_gpgwrapper() as gpg: + # TODO: inspect result, or use decorator + params = gpg.gen_key_input( + key_type='RSA', + key_length=4096, + name_real=address, + name_email=address, + name_comment='') + logger.info("About to generate keys... " + "This might take SOME time.") + gpg.gen_key(params) + logger.info("Keys for %s have been successfully " + "generated." % (address,)) + pubkeys = gpg.list_keys() + + # assert for new key characteristics + + # XXX This exception is not properly catched by the soledad + # bootstrapping, so if we do not finish generating the keys + # we end with a blocked thread -- kali + + leap_assert( + len(pubkeys) is 1, # a unitary keyring! + 'Keyring has wrong number of keys: %d.' % len(pubkeys)) + key = gpg.list_keys(secret=True).pop() + leap_assert( + len(key['uids']) is 1, # with just one uid! + 'Wrong number of uids for key: %d.' % len(key['uids'])) + uid_match = False + for uid in key['uids']: + if re.match('.*<%s>$' % address, uid) is not None: + uid_match = True + return + leap_assert(uid_match, 'Key not correctly bound to address.') + # insert both public and private keys in storage + deferreds = [] + for secret in [True, False]: + key = gpg.list_keys(secret=secret).pop() + openpgp_key = _build_key_from_gpg( + key, + gpg.export_keys(key['fingerprint'], secret=secret)) + d = self.put_key(openpgp_key, address) + deferreds.append(d) + return defer.gatherResults(deferreds) + + def key_already_exists(_): + raise errors.KeyAlreadyExists(address) - return self.get_key(address, private=True) + d = self.get_key(address) + d.addCallbacks(key_already_exists, _gen_key) + d.addCallback(lambda _: self.get_key(address, private=True)) + return d def get_key(self, address, private=False): """ @@ -318,19 +328,24 @@ class OpenPGPScheme(EncryptionScheme): :param private: Look for a private key instead of a public one? :type private: bool - :return: The key bound to C{address}. - :rtype: OpenPGPKey - @raise KeyNotFound: If the key was not found on local storage. + :return: A Deferred which fires with the OpenPGPKey bound to address, + or which fails with KeyNotFound if the key was not found on + local storage. + :rtype: Deferred """ address = _parse_address(address) - doc = self._get_key_doc(address, private) - if doc is None: - raise errors.KeyNotFound(address) - leap_assert( - address in doc.content[KEY_ADDRESS_KEY], - 'Wrong address in key data.') - return build_key_from_dict(OpenPGPKey, doc.content) + def build_key(doc): + if doc is None: + raise errors.KeyNotFound(address) + leap_assert( + address in doc.content[KEY_ADDRESS_KEY], + 'Wrong address in key data.') + return build_key_from_dict(OpenPGPKey, doc.content) + + d = self._get_key_doc(address, private) + d.addCallback(build_key) + return d def parse_ascii_key(self, key_data): """ @@ -388,6 +403,9 @@ class OpenPGPScheme(EncryptionScheme): :type key_data: str or unicode :param address: address for which this key will be active :type address: str + + :return: A Deferred which fires when the OpenPGPKey is in the storage. + :rtype: Deferred """ leap_assert_type(key_data, (str, unicode)) @@ -395,12 +413,17 @@ class OpenPGPScheme(EncryptionScheme): try: openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(key_data) except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e: - leap_assert(False, repr(e)) + return defer.fail(e) + def put_key(_, key): + return self.put_key(key, address) + + d = defer.succeed(None) if openpgp_pubkey is not None: - self.put_key(openpgp_pubkey, address) + d.addCallback(put_key, openpgp_pubkey) if openpgp_privkey is not None: - self.put_key(openpgp_privkey, address) + d.addCallback(put_key, openpgp_privkey) + return d def put_key(self, key, address): """ @@ -410,42 +433,59 @@ class OpenPGPScheme(EncryptionScheme): :type key: OpenPGPKey :param address: address for which this key will be active. :type address: str + + :return: A Deferred which fires when the key is in the storage. + :rtype: Deferred """ - self._put_key_doc(key) - self._put_active_doc(key, address) + d = self._put_key_doc(key) + d.addCallback(lambda _: self._put_active_doc(key, address)) + return d def _put_key_doc(self, key): """ Put key document in soledad :type key: OpenPGPKey - """ - docs = self._soledad.get_from_index( + :rtype: Deferred + """ + def check_and_put(docs, key): + if len(docs) == 1: + doc = docs.pop() + if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: + # in case of an update of the key merge them with gnupg + with self._temporary_gpgwrapper() as gpg: + gpg.import_keys(doc.content[KEY_DATA_KEY]) + gpg.import_keys(key.key_data) + gpgkey = gpg.list_keys(secret=key.private).pop() + key = _build_key_from_gpg( + gpgkey, + gpg.export_keys(gpgkey['fingerprint'], + secret=key.private)) + doc.set_json(key.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, doc.content[KEY_FINGERPRINT_KEY])) + d = defer.fail( + errors.KeyFingerprintMismatch(key.fingerprint)) + elif len(docs) > 1: + 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()) + return d + + d = self._soledad.get_from_index( TYPE_ID_PRIVATE_INDEX, self.KEY_TYPE, key.key_id, '1' if key.private else '0') - if len(docs) != 0: - doc = docs.pop() - if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: - # in case of an update of the key merge them with gnupg - with self._temporary_gpgwrapper() as gpg: - gpg.import_keys(doc.content[KEY_DATA_KEY]) - gpg.import_keys(key.key_data) - gpgkey = gpg.list_keys(secret=key.private).pop() - key = _build_key_from_gpg( - gpgkey, - gpg.export_keys(gpgkey['fingerprint'], - secret=key.private)) - doc.set_json(key.get_json()) - 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, doc.content[KEY_FINGERPRINT_KEY])) - else: - self._soledad.create_doc_from_json(key.get_json()) + d.addCallback(check_and_put, key) + return d def _put_active_doc(self, key, address): """ @@ -453,24 +493,37 @@ class OpenPGPScheme(EncryptionScheme): :type key: OpenPGPKey :type addresses: str + :rtype: Deferred """ - docs = self._soledad.get_from_index( + def check_and_put(docs): + if len(docs) == 1: + doc = docs.pop() + doc.set_json(key.get_active_json(address)) + d = self._soledad.put_doc(doc) + else: + if len(docs) > 1: + logger.error("There is more than one active key document " + "for the address %s" % (address,)) + deferreds = [] + for doc in docs: + delete = self._soledad.delete_doc(doc) + deferreds.append(delete) + d = defer.gatherResults(deferreds, consumeErrors=True) + else: + d = defer.succeed(None) + + d.addCallback( + lambda _: self._soledad.create_doc_from_json( + key.get_active_json(address))) + return d + + d = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, self.ACTIVE_TYPE, address, '1' if key.private else '0') - if len(docs) == 1: - doc = docs.pop() - doc.set_json(key.get_active_json(address)) - self._soledad.put_doc(doc) - else: - if len(docs) > 1: - logger.error("There is more than one active key document " - "for the address %s" % (address,)) - for doc in docs: - self._soledad.delete_doc(doc) - self._soledad.create_doc_from_json( - key.get_active_json(address)) + d.addCallback(check_and_put) + return d def _get_key_doc(self, address, private=False): """ @@ -482,69 +535,94 @@ class OpenPGPScheme(EncryptionScheme): :type address: str :param private: Whether to look for a private key. :type private: bool - :return: The document with the key or None if it does not exist. - :rtype: leap.soledad.document.SoledadDocument + + :return: A Deferred which fires with the SoledadDocument with the key + or None if it does not exist. + :rtype: Deferred """ - activedoc = self._soledad.get_from_index( + def get_key_from_active_doc(activedoc): + if len(activedoc) is 0: + return None + leap_assert( + len(activedoc) is 1, + 'Found more than one key for address %s!' % (address,)) + + key_id = activedoc[0].content[KEY_ID_KEY] + d = self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key_id, + '1' if private else '0') + d.addCallback(get_doc, key_id) + 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() + + d = self._soledad.get_from_index( TYPE_ADDRESS_PRIVATE_INDEX, self.ACTIVE_TYPE, address, '1' if private else '0') - if len(activedoc) is 0: - return None - leap_assert( - len(activedoc) is 1, - 'Found more than one key for address %s!' % (address,)) - - key_id = activedoc[0].content[KEY_ID_KEY] - doclist = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, - self.KEY_TYPE, - key_id, - '1' if private else '0') - leap_assert( - len(doclist) is 1, - 'There is %d keys for id %s!' % (len(doclist), key_id)) - return doclist.pop() + d.addCallback(get_key_from_active_doc) + return d def delete_key(self, key): """ Remove C{key} from storage. - May raise: - errors.KeyNotFound - :param key: The key to be removed. :type key: EncryptionKey + + :return: A Deferred which fires when the key is deleted, or which + fails with KeyNotFound if the key was not found on local + storage. + :rtype: Deferred """ leap_assert_type(key, OpenPGPKey) - activedocs = self._soledad.get_from_index( - TYPE_ID_PRIVATE_INDEX, - self.ACTIVE_TYPE, - key.key_id, - '1' if key.private else '0') - for doc in activedocs: - self._soledad.delete_doc(doc) - docs = self._soledad.get_from_index( + def delete_docs(activedocs): + deferreds = [] + for doc in activedocs: + d = self._soledad.delete_doc(doc) + deferreds.append(d) + return defer.gatherResults(deferreds) + + def get_key_docs(_): + return self._soledad.get_from_index( + TYPE_ID_PRIVATE_INDEX, + self.KEY_TYPE, + key.key_id, + '1' if key.private else '0') + + 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: + raise errors.KeyNotFound(key) + return self._soledad.delete_doc(doc) + + d = self._soledad.get_from_index( TYPE_ID_PRIVATE_INDEX, - self.KEY_TYPE, + self.ACTIVE_TYPE, key.key_id, '1' if key.private else '0') - 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: - raise errors.KeyNotFound(key) - self._soledad.delete_doc(doc) + d.addCallback(delete_docs) + d.addCallback(get_key_docs) + d.addCallback(delete_key) + return d # # Data encryption, decryption, signing and verifying diff --git a/src/leap/keymanager/tests/__init__.py b/src/leap/keymanager/tests/__init__.py index 1ea33b5..05b4487 100644 --- a/src/leap/keymanager/tests/__init__.py +++ b/src/leap/keymanager/tests/__init__.py @@ -18,37 +18,25 @@ Base classes for the Key Manager tests. """ -from mock import Mock +from twisted.internet.defer import gatherResults +from twisted.trial import unittest from leap.common.testing.basetest import BaseLeapTest from leap.soledad.client import Soledad from leap.keymanager import KeyManager +from leap.keymanager.openpgp import OpenPGPKey ADDRESS = 'leap@leap.se' +ADDRESS_2 = 'anotheruser@leap.se' # XXX discover the gpg binary path GPG_BINARY_PATH = '/usr/bin/gpg' -class KeyManagerWithSoledadTestCase(BaseLeapTest): +class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def setUp(self): - # mock key fetching and storing so Soledad doesn't fail when trying to - # reach the server. - Soledad._get_secrets_from_shared_db = Mock(return_value=None) - Soledad._put_secrets_in_shared_db = Mock(return_value=None) - - class MockSharedDB(object): - - get_doc = Mock(return_value=None) - put_doc = Mock() - lock = Mock(return_value=('atoken', 300)) - unlock = Mock(return_value=True) - - def __call__(self): - return self - - Soledad._shared_db = MockSharedDB() + self.setUpEnv() self._soledad = Soledad( u"leap@leap.se", @@ -58,14 +46,32 @@ class KeyManagerWithSoledadTestCase(BaseLeapTest): server_url='', cert_file=None, auth_token=None, + syncable=False ) def tearDown(self): km = self._key_manager() - for key in km.get_all_keys(): - km._wrapper_map[key.__class__].delete_key(key) - for key in km.get_all_keys(private=True): - km._wrapper_map[key.__class__].delete_key(key) + + def delete_keys(keys): + deferreds = [] + for key in keys: + d = km._wrapper_map[key.__class__].delete_key(key) + deferreds.append(d) + return gatherResults(deferreds) + + def get_and_delete_keys(_): + deferreds = [] + for private in [True, False]: + d = km.get_all_keys(private=private) + d.addCallback(delete_keys) + deferreds.append(d) + return gatherResults(deferreds) + + # wait for the indexes to be ready for the tear down + d = km._wrapper_map[OpenPGPKey].deferred_indexes + d.addCallback(get_and_delete_keys) + d.addCallback(lambda _: self.tearDownEnv()) + return d def _key_manager(self, user=ADDRESS, url='', token=None): return KeyManager(user, url, self._soledad, token=token, @@ -234,3 +240,62 @@ RZXoH+FTg9UAW87eqU610npOkT6cRaBxaMK/mDtGNdc= =JTFu -----END PGP PRIVATE KEY BLOCK----- """ + +# key 7FEE575A: public key "anotheruser " +PUBLIC_KEY_2 = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR +gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq +Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 +IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle +AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E +gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw +ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 +JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz +VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt +Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 +yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ +f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X +Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck +I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= +=Thdu +-----END PGP PUBLIC KEY BLOCK----- +""" + +PRIVATE_KEY_2 = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD +kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 +6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB +AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 +H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks +7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X +C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje +uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty +GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI +1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v +dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG +CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh +8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD +izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT +oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL +juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw +cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe +94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC +rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx +77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 +3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF +UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO +2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB +/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE +JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda +z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk +o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 +THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 +=a5gs +-----END PGP PRIVATE KEY BLOCK----- +""" diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index ee4462a..b8ef88a 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -23,12 +23,12 @@ Tests for the Key Manager. from datetime import datetime from mock import Mock -from leap.common.testing.basetest import BaseLeapTest +from twisted.internet.defer import inlineCallbacks +from twisted.trial import unittest + from leap.keymanager import ( - openpgp, KeyNotFound, KeyAddressMismatch, - errors, ) from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.keys import ( @@ -42,23 +42,16 @@ from leap.keymanager.validation import ( from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, + ADDRESS_2, KEY_FINGERPRINT, PUBLIC_KEY, + PUBLIC_KEY_2, PRIVATE_KEY, - GPG_BINARY_PATH + PRIVATE_KEY_2, ) -ADDRESS_2 = 'anotheruser@leap.se' - - -class KeyManagerUtilTestCase(BaseLeapTest): - - def setUp(self): - pass - - def tearDown(self): - pass +class KeyManagerUtilTestCase(unittest.TestCase): def test_is_address(self): self.assertTrue( @@ -128,227 +121,43 @@ class KeyManagerUtilTestCase(BaseLeapTest): 'Wrong data in key.') -class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): - - def _test_openpgp_gen_key(self): - pgp = openpgp.OpenPGPScheme(self._soledad) - self.assertRaises(KeyNotFound, pgp.get_key, 'user@leap.se') - key = pgp.gen_key('user@leap.se') - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertEqual( - ['user@leap.se'], key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - - def test_openpgp_put_delete_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - key = pgp.get_key(ADDRESS, private=False) - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_openpgp_put_ascii_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - key = pgp.get_key(ADDRESS, private=False) - self.assertIsInstance(key, openpgp.OpenPGPKey) - self.assertTrue( - ADDRESS in key.address, 'Wrong address bound to key.') - self.assertEqual( - 4096, key.length, 'Wrong key length.') - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_get_public_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - key = pgp.get_key(ADDRESS, private=False) - self.assertTrue(ADDRESS in key.address) - self.assertFalse(key.private) - self.assertEqual(KEY_FINGERPRINT, key.fingerprint) - pgp.delete_key(key) - self.assertRaises(KeyNotFound, pgp.get_key, ADDRESS) - - def test_openpgp_encrypt_decrypt(self): - # encrypt - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - pubkey = pgp.get_key(ADDRESS, private=False) - data = 'data' - cyphertext = pgp.encrypt(data, pubkey) - # assert - self.assertTrue(cyphertext is not None) - self.assertTrue(cyphertext != '') - self.assertTrue(cyphertext != data) - self.assertTrue(pgp.is_encrypted(cyphertext)) - self.assertTrue(pgp.is_encrypted(cyphertext)) - # decrypt - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - privkey = pgp.get_key(ADDRESS, private=True) - decrypted, _ = pgp.decrypt(cyphertext, privkey) - self.assertEqual(decrypted, data) - pgp.delete_key(pubkey) - pgp.delete_key(privkey) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=False) - self.assertRaises( - KeyNotFound, pgp.get_key, ADDRESS, private=True) - - def test_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) - self.assertRaises( - AssertionError, - pgp.verify, signed, privkey) - - def test_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) - data = 'data' - self.assertRaises( - AssertionError, - pgp.sign, data, ADDRESS, OpenPGPKey) - - def test_verify_with_wrong_key_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey) - pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) - wrongkey = pgp.get_key(ADDRESS_2) - self.assertFalse(pgp.verify(signed, wrongkey)) - - def test_encrypt_sign_with_public_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertRaises( - AssertionError, - pgp.encrypt, data, privkey, sign=pubkey) - - def test_decrypt_verify_with_private_raises(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = pgp.encrypt( - data, pubkey, sign=privkey) - self.assertRaises( - AssertionError, - pgp.decrypt, - encrypted_and_signed, privkey, verify=privkey) - - def test_decrypt_verify_with_wrong_key(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - pubkey = pgp.get_key(ADDRESS, private=False) - encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) - pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) - wrongkey = pgp.get_key(ADDRESS_2) - decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, - verify=wrongkey) - self.assertEqual(decrypted, data) - self.assertFalse(validsign) - - def test_sign_verify(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signed = pgp.sign(data, privkey, detach=False) - pubkey = pgp.get_key(ADDRESS, private=False) - self.assertTrue(pgp.verify(signed, pubkey)) - - def test_encrypt_sign_decrypt_verify(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - pubkey = pgp.get_key(ADDRESS, private=False) - privkey = pgp.get_key(ADDRESS, private=True) - pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) - pubkey2 = pgp.get_key(ADDRESS_2, private=False) - privkey2 = pgp.get_key(ADDRESS_2, private=True) - data = 'data' - encrypted_and_signed = pgp.encrypt( - data, pubkey2, sign=privkey) - res, validsign = pgp.decrypt( - encrypted_and_signed, privkey2, verify=pubkey) - self.assertEqual(data, res) - self.assertTrue(validsign) - - def test_sign_verify_detached_sig(self): - pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) - pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) - data = 'data' - privkey = pgp.get_key(ADDRESS, private=True) - signature = pgp.sign(data, privkey, detach=True) - pubkey = pgp.get_key(ADDRESS, private=False) - validsign = pgp.verify(data, pubkey, detached_sig=signature) - self.assertTrue(validsign) - - class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): + @inlineCallbacks def test_get_all_keys_in_db(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get public keys - keys = km.get_all_keys(False) + keys = yield km.get_all_keys(False) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertTrue(ADDRESS in keys[0].address) self.assertFalse(keys[0].private) # get private keys - keys = km.get_all_keys(True) + keys = yield km.get_all_keys(True) self.assertEqual(len(keys), 1, 'Wrong number of keys') self.assertTrue(ADDRESS in keys[0].address) self.assertTrue(keys[0].private) + @inlineCallbacks def test_get_public_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key - key = km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, + fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.address) self.assertEqual( key.fingerprint.lower(), KEY_FINGERPRINT.lower()) self.assertFalse(key.private) + @inlineCallbacks def test_get_private_key(self): km = self._key_manager() - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) # get the key - key = km.get_key(ADDRESS, OpenPGPKey, private=True, - fetch_remote=False) + key = yield km.get_key(ADDRESS, OpenPGPKey, private=True, + fetch_remote=False) self.assertTrue(key is not None) self.assertTrue(ADDRESS in key.address) self.assertEqual( @@ -357,17 +166,17 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): def test_send_key_raises_key_not_found(self): km = self._key_manager() - self.assertRaises( - KeyNotFound, - km.send_key, OpenPGPKey) + d = km.send_key(OpenPGPKey) + return self.assertFailure(d, KeyNotFound) + @inlineCallbacks def test_send_key(self): """ Test that request is well formed when sending keys to server. """ token = "mytoken" km = self._key_manager(token=token) - km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PUBLIC_KEY, ADDRESS) km._fetcher.put = Mock() # the following data will be used on the send km.ca_cert_path = 'capath' @@ -375,10 +184,11 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.uid = 'myuid' km.api_uri = 'apiuri' km.api_version = 'apiver' - km.send_key(OpenPGPKey) + yield km.send_key(OpenPGPKey) # setup expected args + pubkey = yield km.get_key(km._address, OpenPGPKey) data = { - km.PUBKEY_KEY: km.get_key(km._address, OpenPGPKey).key_data, + km.PUBKEY_KEY: pubkey.key_data, } url = '%s/%s/users/%s.json' % ('apiuri', 'apiver', 'myuid') km._fetcher.put.assert_called_once_with( @@ -386,7 +196,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): headers={'Authorization': 'Token token=%s' % token}, ) - def test__fetch_keys_from_server(self): + def test_fetch_keys_from_server(self): """ Test that the request is well formed when fetching keys from server. """ @@ -406,15 +216,19 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock( return_value=Response()) km.ca_cert_path = 'cacertpath' - # do the fetch - km._fetch_keys_from_server(ADDRESS_2) - # and verify the call - km._fetcher.get.assert_called_once_with( - 'http://nickserver.domain', - data={'address': ADDRESS_2}, - verify='cacertpath', - ) + def verify_the_call(_): + km._fetcher.get.assert_called_once_with( + 'http://nickserver.domain', + data={'address': ADDRESS_2}, + verify='cacertpath', + ) + + d = km._fetch_keys_from_server(ADDRESS_2) + d.addCallback(verify_the_call) + return d + + @inlineCallbacks def test_get_key_fetches_from_server(self): """ Test that getting a key successfuly fetches from server. @@ -435,26 +249,26 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server - self.assertRaises( - KeyNotFound, km.get_key, ADDRESS, OpenPGPKey, - fetch_remote=False - ) + d = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield self.assertFailure(d, KeyNotFound) # try to get key fetching from server. - key = km.get_key(ADDRESS, OpenPGPKey) + key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) + @inlineCallbacks def test_put_key_ascii(self): """ Test that putting ascii key works """ km = self._key_manager(url='http://nickserver.domain') - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) + @inlineCallbacks def test_fetch_uri_ascii_key(self): """ Test that fetch key downloads the ascii key and gets included in @@ -469,8 +283,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) - key = km.get_key(ADDRESS, OpenPGPKey) + yield km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + key = yield km.get_key(ADDRESS, OpenPGPKey) self.assertEqual(KEY_FINGERPRINT, key.fingerprint) def test_fetch_uri_empty_key(self): @@ -485,8 +299,8 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - self.assertRaises(KeyNotFound, km.fetch_key, - ADDRESS, "http://site.domain/key", OpenPGPKey) + d = km.fetch_key(ADDRESS, "http://site.domain/key", OpenPGPKey) + return self.assertFailure(d, KeyNotFound) def test_fetch_uri_address_differ(self): """ @@ -501,120 +315,66 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' - self.assertRaises(KeyAddressMismatch, km.fetch_key, - ADDRESS_2, "http://site.domain/key", OpenPGPKey) + d = km.fetch_key(ADDRESS_2, "http://site.domain/key", OpenPGPKey) + return self.assertFailure(d, KeyAddressMismatch) class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): RAW_DATA = 'data' + @inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt(self): km = self._key_manager() # put raw private key - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - 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) # encrypt - encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # decrypt - rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, - verify=ADDRESS_2, fetch_remote=False) + rawdata, signingkey = yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS_2, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) - key = km.get_key(ADDRESS_2, OpenPGPKey, private=False, - fetch_remote=False) + key = yield km.get_key(ADDRESS_2, OpenPGPKey, private=False, + fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) + @inlineCallbacks def test_keymanager_openpgp_encrypt_decrypt_wrong_sign(self): km = self._key_manager() # put raw keys - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - 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) # encrypt - encdata = km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, - sign=ADDRESS_2, fetch_remote=False) + encdata = yield km.encrypt(self.RAW_DATA, ADDRESS, OpenPGPKey, + sign=ADDRESS_2, fetch_remote=False) self.assertNotEqual(self.RAW_DATA, encdata) # verify - rawdata, signingkey = km.decrypt(encdata, ADDRESS, OpenPGPKey, - verify=ADDRESS, fetch_remote=False) + rawdata, signingkey = yield km.decrypt( + encdata, ADDRESS, OpenPGPKey, verify=ADDRESS, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) self.assertTrue(signingkey is None) + @inlineCallbacks def test_keymanager_openpgp_sign_verify(self): km = self._key_manager() # put raw private keys - km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) - signdata = km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, detach=False) + yield km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + signdata = yield km.sign(self.RAW_DATA, ADDRESS, OpenPGPKey, + detach=False) self.assertNotEqual(self.RAW_DATA, signdata) # verify - signingkey = km.verify(signdata, ADDRESS, OpenPGPKey, + signingkey = yield km.verify(signdata, ADDRESS, OpenPGPKey, + fetch_remote=False) + key = yield km.get_key(ADDRESS, OpenPGPKey, private=False, fetch_remote=False) - key = km.get_key(ADDRESS, OpenPGPKey, private=False, - fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) -# Key material for testing - -# key 7FEE575A: public key "anotheruser " -PUBLIC_KEY_2 = """ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -mI0EUYwJXgEEAMbTKHuPJ5/Gk34l9Z06f+0WCXTDXdte1UBoDtZ1erAbudgC4MOR -gquKqoj3Hhw0/ILqJ88GcOJmKK/bEoIAuKaqlzDF7UAYpOsPZZYmtRfPC2pTCnXq -Z1vdeqLwTbUspqXflkCkFtfhGKMq5rH8GV5a3tXZkRWZhdNwhVXZagC3ABEBAAG0 -IWFub3RoZXJ1c2VyIDxhbm90aGVydXNlckBsZWFwLnNlPoi4BBMBAgAiBQJRjAle -AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRB/nfpof+5XWotuA/4tLN4E -gUr7IfLy2HkHAxzw7A4rqfMN92DIM9mZrDGaWRrOn3aVF7VU1UG7MDkHfPvp/cFw -ezoCw4s4IoHVc/pVlOkcHSyt4/Rfh248tYEJmFCJXGHpkK83VIKYJAithNccJ6Q4 -JE/o06Mtf4uh/cA1HUL4a4ceqUhtpLJULLeKo7iNBFGMCV4BBADsyQI7GR0wSAxz -VayLjuPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQt -Z/hwcLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63 -yuRe94WenT1RJd6xU1aaUff4rKizuQARAQABiJ8EGAECAAkFAlGMCV4CGwwACgkQ -f536aH/uV1rPZQQAqCzRysOlu8ez7PuiBD4SebgRqWlxa1TF1ujzfLmuPivROZ2X -Kw5aQstxgGSjoB7tac49s0huh4X8XK+BtJBfU84JS8Jc2satlfwoyZ35LH6sDZck -I+RS/3we6zpMfHs3vvp9xgca6ZupQxivGtxlJs294TpJorx+mFFqbV17AzQ= -=Thdu ------END PGP PUBLIC KEY BLOCK----- -""" - -PRIVATE_KEY_2 = """ ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.10 (GNU/Linux) - -lQHYBFGMCV4BBADG0yh7jyefxpN+JfWdOn/tFgl0w13bXtVAaA7WdXqwG7nYAuDD -kYKriqqI9x4cNPyC6ifPBnDiZiiv2xKCALimqpcwxe1AGKTrD2WWJrUXzwtqUwp1 -6mdb3Xqi8E21LKal35ZApBbX4RijKuax/BleWt7V2ZEVmYXTcIVV2WoAtwARAQAB -AAP7BLuSAx7tOohnimEs74ks8l/L6dOcsFQZj2bqs4AoY3jFe7bV0tHr4llypb/8 -H3/DYvpf6DWnCjyUS1tTnXSW8JXtx01BUKaAufSmMNg9blKV6GGHlT/Whe9uVyks -7XHk/+9mebVMNJ/kNlqq2k+uWqJohzC8WWLRK+d1tBeqDsECANZmzltPaqUsGV5X -C3zszE3tUBgptV/mKnBtopKi+VH+t7K6fudGcG+bAcZDUoH/QVde52mIIjjIdLje -uajJuHUCAO1mqh+vPoGv4eBLV7iBo3XrunyGXiys4a39eomhxTy3YktQanjjx+ty -GltAGCs5PbWGO6/IRjjvd46wh53kzvsCAO0J97gsWhzLuFnkxFAJSPk7RRlyl7lI -1XS/x0Og6j9XHCyY1OYkfBm0to3UlCfkgirzCYlTYObCofzdKFIPDmSqHbQhYW5v -dGhlcnVzZXIgPGFub3RoZXJ1c2VyQGxlYXAuc2U+iLgEEwECACIFAlGMCV4CGwMG -CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH+d+mh/7ldai24D/i0s3gSBSvsh -8vLYeQcDHPDsDiup8w33YMgz2ZmsMZpZGs6fdpUXtVTVQbswOQd8++n9wXB7OgLD -izgigdVz+lWU6RwdLK3j9F+Hbjy1gQmYUIlcYemQrzdUgpgkCK2E1xwnpDgkT+jT -oy1/i6H9wDUdQvhrhx6pSG2kslQst4qjnQHYBFGMCV4BBADsyQI7GR0wSAxzVayL -juPzgT+bjbFeymIhjuxKIEwnIKwYkovztW+4bbOcQs785k3Lp6RzvigTpQQtZ/hw -cLOqZbZw8t/24+D+Pq9mMP2uUvCFFqLlVvA6D3vKSQ/XNN+YB919WQ04jh63yuRe -94WenT1RJd6xU1aaUff4rKizuQARAQABAAP9EyElqJ3dq3EErXwwT4mMnbd1SrVC -rUJrNWQZL59mm5oigS00uIyR0SvusOr+UzTtd8ysRuwHy5d/LAZsbjQStaOMBILx -77TJveOel0a1QK0YSMF2ywZMCKvquvjli4hAtWYz/EwfuzQN3t23jc5ny+GqmqD2 -3FUxLJosFUfLNmECAO9KhVmJi+L9dswIs+2Dkjd1eiRQzNOEVffvYkGYZyKxNiXF -UA5kvyZcB4iAN9sWCybE4WHZ9jd4myGB0MPDGxkCAP1RsXJbbuD6zS7BXe5gwunO -2q4q7ptdSl/sJYQuTe1KNP5d/uGsvlcFfsYjpsopasPjFBIncc/2QThMKlhoEaEB -/0mVAxpT6SrEvUbJ18z7kna24SgMPr3OnPMxPGfvNLJY/Xv/A17YfoqjmByCvsKE -JCDjopXtmbcrZyoEZbEht9mko4ifBBgBAgAJBQJRjAleAhsMAAoJEH+d+mh/7lda -z2UEAKgs0crDpbvHs+z7ogQ+Enm4EalpcWtUxdbo83y5rj4r0TmdlysOWkLLcYBk -o6Ae7WnOPbNIboeF/FyvgbSQX1POCUvCXNrGrZX8KMmd+Sx+rA2XJCPkUv98Hus6 -THx7N776fcYHGumbqUMYrxrcZSbNveE6SaK8fphRam1dewM0 -=a5gs ------END PGP PRIVATE KEY BLOCK----- -""" import unittest if __name__ == "__main__": unittest.main() diff --git a/src/leap/keymanager/tests/test_openpgp.py b/src/leap/keymanager/tests/test_openpgp.py new file mode 100644 index 0000000..01cf341 --- /dev/null +++ b/src/leap/keymanager/tests/test_openpgp.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +# test_keymanager.py +# Copyright (C) 2014 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +Tests for the OpenPGP support on Key Manager. +""" + + +from twisted.internet.defer import inlineCallbacks + +from leap.keymanager import ( + KeyNotFound, + openpgp, +) +from leap.keymanager.openpgp import OpenPGPKey +from leap.keymanager.tests import ( + KeyManagerWithSoledadTestCase, + ADDRESS, + ADDRESS_2, + KEY_FINGERPRINT, + PUBLIC_KEY, + PUBLIC_KEY_2, + PRIVATE_KEY, + PRIVATE_KEY_2, + GPG_BINARY_PATH +) + + +class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): + + @inlineCallbacks + def _test_openpgp_gen_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, 'user@leap.se') + key = yield pgp.gen_key('user@leap.se') + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertEqual( + ['user@leap.se'], key.address, 'Wrong address bound to key.') + self.assertEqual( + 4096, key.length, 'Wrong key length.') + + @inlineCallbacks + def test_openpgp_put_delete_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, ADDRESS) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + key = yield pgp.get_key(ADDRESS, private=False) + yield pgp.delete_key(key) + yield self._assert_key_not_found(pgp, ADDRESS) + + @inlineCallbacks + def test_openpgp_put_ascii_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, ADDRESS) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + key = yield pgp.get_key(ADDRESS, private=False) + self.assertIsInstance(key, openpgp.OpenPGPKey) + self.assertTrue( + ADDRESS in key.address, 'Wrong address bound to key.') + self.assertEqual( + 4096, key.length, 'Wrong key length.') + yield pgp.delete_key(key) + yield self._assert_key_not_found(pgp, ADDRESS) + + @inlineCallbacks + def test_get_public_key(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield self._assert_key_not_found(pgp, ADDRESS) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + yield self._assert_key_not_found(pgp, ADDRESS, private=True) + key = yield pgp.get_key(ADDRESS, private=False) + self.assertTrue(ADDRESS in key.address) + self.assertFalse(key.private) + self.assertEqual(KEY_FINGERPRINT, key.fingerprint) + yield pgp.delete_key(key) + yield self._assert_key_not_found(pgp, ADDRESS) + + @inlineCallbacks + def test_openpgp_encrypt_decrypt(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + + # encrypt + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + pubkey = yield pgp.get_key(ADDRESS, private=False) + cyphertext = pgp.encrypt(data, pubkey) + + self.assertTrue(cyphertext is not None) + self.assertTrue(cyphertext != '') + self.assertTrue(cyphertext != data) + self.assertTrue(pgp.is_encrypted(cyphertext)) + self.assertTrue(pgp.is_encrypted(cyphertext)) + + # decrypt + yield self._assert_key_not_found(pgp, ADDRESS, private=True) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + decrypted, _ = pgp.decrypt(cyphertext, privkey) + self.assertEqual(decrypted, data) + + yield pgp.delete_key(pubkey) + yield pgp.delete_key(privkey) + yield self._assert_key_not_found(pgp, ADDRESS, private=False) + yield self._assert_key_not_found(pgp, ADDRESS, private=True) + + @inlineCallbacks + def test_verify_with_private_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signed = pgp.sign(data, privkey) + self.assertRaises( + AssertionError, + pgp.verify, signed, privkey) + + @inlineCallbacks + def test_sign_with_public_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) + self.assertRaises( + AssertionError, + pgp.sign, data, ADDRESS, OpenPGPKey) + + @inlineCallbacks + def test_verify_with_wrong_key_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signed = pgp.sign(data, privkey) + yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) + wrongkey = yield pgp.get_key(ADDRESS_2) + self.assertFalse(pgp.verify(signed, wrongkey)) + + @inlineCallbacks + def test_encrypt_sign_with_public_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + self.assertRaises( + AssertionError, + pgp.encrypt, data, privkey, sign=pubkey) + + @inlineCallbacks + def test_decrypt_verify_with_private_raises(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = pgp.encrypt( + data, pubkey, sign=privkey) + self.assertRaises( + AssertionError, + pgp.decrypt, + encrypted_and_signed, privkey, verify=privkey) + + @inlineCallbacks + def test_decrypt_verify_with_wrong_key(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + encrypted_and_signed = pgp.encrypt(data, pubkey, sign=privkey) + yield pgp.put_ascii_key(PUBLIC_KEY_2, ADDRESS_2) + wrongkey = yield pgp.get_key(ADDRESS_2) + decrypted, validsign = pgp.decrypt(encrypted_and_signed, privkey, + verify=wrongkey) + self.assertEqual(decrypted, data) + self.assertFalse(validsign) + + @inlineCallbacks + def test_sign_verify(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signed = pgp.sign(data, privkey, detach=False) + pubkey = yield pgp.get_key(ADDRESS, private=False) + validsign = pgp.verify(signed, pubkey) + self.assertTrue(validsign) + + @inlineCallbacks + def test_encrypt_sign_decrypt_verify(self): + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + pubkey = yield pgp.get_key(ADDRESS, private=False) + privkey = yield pgp.get_key(ADDRESS, private=True) + + yield pgp.put_ascii_key(PRIVATE_KEY_2, ADDRESS_2) + pubkey2 = yield pgp.get_key(ADDRESS_2, private=False) + privkey2 = yield pgp.get_key(ADDRESS_2, private=True) + + data = 'data' + encrypted_and_signed = pgp.encrypt( + data, pubkey2, sign=privkey) + res, validsign = pgp.decrypt( + encrypted_and_signed, privkey2, verify=pubkey) + self.assertEqual(data, res) + self.assertTrue(validsign) + + @inlineCallbacks + def test_sign_verify_detached_sig(self): + data = 'data' + pgp = openpgp.OpenPGPScheme( + self._soledad, gpgbinary=GPG_BINARY_PATH) + yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) + privkey = yield pgp.get_key(ADDRESS, private=True) + signature = yield pgp.sign(data, privkey, detach=True) + pubkey = yield pgp.get_key(ADDRESS, private=False) + validsign = pgp.verify(data, pubkey, detached_sig=signature) + self.assertTrue(validsign) + + def _assert_key_not_found(self, pgp, address, private=False): + d = pgp.get_key(address, private=private) + return self.assertFailure(d, KeyNotFound) diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 400d36e..83a02e0 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -19,6 +19,7 @@ Tests for the Validation Levels """ from datetime import datetime +from twisted.internet.defer import inlineCallbacks from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.errors import ( @@ -35,51 +36,57 @@ from leap.keymanager.validation import ValidationLevel class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): + @inlineCallbacks def test_none_old_key(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + @inlineCallbacks def test_cant_upgrade(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) - self.assertRaises(KeyNotValidUpgrade, km.put_raw_key, UNRELATED_KEY, - OpenPGPKey, ADDRESS) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Trust) + d = km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) + yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks def test_fingerprint_level(self): km = self._key_manager() - km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Fingerprint) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Fingerprint) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + @inlineCallbacks def test_expired_key(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) - km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + @inlineCallbacks def test_expired_fail_lower_level(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Third_Party_Endorsement) - self.assertRaises( - KeyNotValidUpgrade, - km.put_raw_key, + yield km.put_raw_key( + EXPIRED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Third_Party_Endorsement) + d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, validation=ValidationLevel.Provider_Trust) + yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks def test_roll_back(self): km = self._key_manager() - km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) - km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) - key = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + yield km.put_raw_key(EXPIRED_KEY_UPDATED, OpenPGPKey, ADDRESS) + yield km.put_raw_key(EXPIRED_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) -- cgit v1.2.3 From 18d8fc8ab26885e24eaa05cc7843937b2381e4a8 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 5 Dec 2014 12:28:37 -0600 Subject: Fix key generation --- src/leap/keymanager/openpgp.py | 8 ++------ src/leap/keymanager/tests/test_openpgp.py | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index f81fb0a..c95b381 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -282,11 +282,6 @@ class OpenPGPScheme(EncryptionScheme): pubkeys = gpg.list_keys() # assert for new key characteristics - - # XXX This exception is not properly catched by the soledad - # bootstrapping, so if we do not finish generating the keys - # we end with a blocked thread -- kali - leap_assert( len(pubkeys) is 1, # a unitary keyring! 'Keyring has wrong number of keys: %d.' % len(pubkeys)) @@ -298,8 +293,9 @@ class OpenPGPScheme(EncryptionScheme): for uid in key['uids']: if re.match('.*<%s>$' % address, uid) is not None: uid_match = True - return + break leap_assert(uid_match, 'Key not correctly bound to address.') + # insert both public and private keys in storage deferreds = [] for secret in [True, False]: diff --git a/src/leap/keymanager/tests/test_openpgp.py b/src/leap/keymanager/tests/test_openpgp.py index 01cf341..e6f56e2 100644 --- a/src/leap/keymanager/tests/test_openpgp.py +++ b/src/leap/keymanager/tests/test_openpgp.py @@ -43,6 +43,9 @@ from leap.keymanager.tests import ( class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): + # set the trial timeout to 20min, needed by the key generation test + timeout = 1200 + @inlineCallbacks def _test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme( -- cgit v1.2.3 From 7bafe2465cf6b4432a80d4cbe2c938716b911fb2 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sat, 13 Dec 2014 10:15:53 -0600 Subject: Find the gpg path instead of hard code it --- src/leap/keymanager/tests/__init__.py | 15 ++++++++++++--- src/leap/keymanager/tests/test_openpgp.py | 29 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/tests/__init__.py b/src/leap/keymanager/tests/__init__.py index 05b4487..7128d20 100644 --- a/src/leap/keymanager/tests/__init__.py +++ b/src/leap/keymanager/tests/__init__.py @@ -18,6 +18,9 @@ Base classes for the Key Manager tests. """ +import distutils.spawn +import os.path + from twisted.internet.defer import gatherResults from twisted.trial import unittest @@ -29,14 +32,13 @@ from leap.keymanager.openpgp import OpenPGPKey ADDRESS = 'leap@leap.se' ADDRESS_2 = 'anotheruser@leap.se' -# XXX discover the gpg binary path -GPG_BINARY_PATH = '/usr/bin/gpg' class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def setUp(self): self.setUpEnv() + self.gpg_binary_path = self._find_gpg() self._soledad = Soledad( u"leap@leap.se", @@ -75,7 +77,14 @@ class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest): def _key_manager(self, user=ADDRESS, url='', token=None): return KeyManager(user, url, self._soledad, token=token, - gpgbinary=GPG_BINARY_PATH) + gpgbinary=self.gpg_binary_path) + + def _find_gpg(self): + gpg_path = distutils.spawn.find_executable('gpg') + if gpg_path is not None: + return os.path.realpath(gpg_path) + else: + return "/usr/bin/gpg" # key 24D18DDF: public key "Leap Test Key " diff --git a/src/leap/keymanager/tests/test_openpgp.py b/src/leap/keymanager/tests/test_openpgp.py index e6f56e2..5f85c74 100644 --- a/src/leap/keymanager/tests/test_openpgp.py +++ b/src/leap/keymanager/tests/test_openpgp.py @@ -37,7 +37,6 @@ from leap.keymanager.tests import ( PUBLIC_KEY_2, PRIVATE_KEY, PRIVATE_KEY_2, - GPG_BINARY_PATH ) @@ -49,7 +48,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def _test_openpgp_gen_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, 'user@leap.se') key = yield pgp.gen_key('user@leap.se') self.assertIsInstance(key, openpgp.OpenPGPKey) @@ -61,7 +60,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_openpgp_put_delete_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) @@ -71,7 +70,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_openpgp_put_ascii_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) key = yield pgp.get_key(ADDRESS, private=False) @@ -86,7 +85,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_get_public_key(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield self._assert_key_not_found(pgp, ADDRESS) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) yield self._assert_key_not_found(pgp, ADDRESS, private=True) @@ -101,7 +100,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_openpgp_encrypt_decrypt(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) # encrypt yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) @@ -130,7 +129,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_private_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) @@ -142,7 +141,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_with_public_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PUBLIC_KEY, ADDRESS) self.assertRaises( AssertionError, @@ -152,7 +151,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_verify_with_wrong_key_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey) @@ -164,7 +163,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_encrypt_sign_with_public_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -176,7 +175,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_private_raises(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -191,7 +190,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_decrypt_verify_with_wrong_key(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -207,7 +206,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signed = pgp.sign(data, privkey, detach=False) @@ -218,7 +217,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_encrypt_sign_decrypt_verify(self): pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) pubkey = yield pgp.get_key(ADDRESS, private=False) @@ -240,7 +239,7 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase): def test_sign_verify_detached_sig(self): data = 'data' pgp = openpgp.OpenPGPScheme( - self._soledad, gpgbinary=GPG_BINARY_PATH) + self._soledad, gpgbinary=self.gpg_binary_path) yield pgp.put_ascii_key(PRIVATE_KEY, ADDRESS) privkey = yield pgp.get_key(ADDRESS, private=True) signature = yield pgp.sign(data, privkey, detach=True) -- cgit v1.2.3 From 4162fd5a7e6485fdcf068af06a743c1ef7676a50 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 14 Dec 2014 22:13:57 -0600 Subject: Return the right error on signature verification --- src/leap/keymanager/__init__.py | 79 ++++++++++++++-------------- src/leap/keymanager/errors.py | 7 +++ src/leap/keymanager/tests/test_keymanager.py | 3 +- 3 files changed, 48 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 7ff437e..5324d42 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -54,7 +54,8 @@ from leap.keymanager.errors import ( KeyNotFound, KeyAddressMismatch, KeyNotValidUpgrade, - UnsupportedKeyTypeError + UnsupportedKeyTypeError, + InvalidSignature ) from leap.keymanager.validation import ValidationLevel, can_upgrade @@ -479,7 +480,12 @@ class KeyManager(object): data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) - d = self._get_keys(ktype, address, sign, fetch_remote=fetch_remote) + dpub = self.get_key(address, ktype, private=False, + fetch_remote=fetch_remote) + dpriv = defer.succeed(None) + if sign is not None: + dpriv = self.get_key(sign, ktype, private=True) + d = defer.gatherResults([dpub, dpriv]) d.addCallback(encrypt) return d @@ -504,11 +510,12 @@ class KeyManager(object): to fetch from nickserver :type fetch_remote: bool - :return: A Deferred which fires with the decrypted data as str and the - signing EncryptionKey if signature verifies, or which fails - with KeyNotFound if no keys were found neither locally or in - keyserver or fails with DecryptError if failed decrypting for - some reason. + :return: A Deferred which fires with: + * (decripted str, signing key) if validation works + * (decripted str, KeyNotFound) if signing key not found + * (decripted str, InvalidSignature) if signature is invalid + * KeyNotFound failure if private key not found + * DecryptError failure if decription failed :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -519,39 +526,25 @@ class KeyManager(object): pubkey, privkey = keys decrypted, signed = self._wrapper_map[ktype].decrypt( data, privkey, passphrase=passphrase, verify=pubkey) - return (decrypted, pubkey if signed else None) - - d = self._get_keys(ktype, verify, address, fetch_remote) - d.addCallback(decrypt) - return d - - def _get_keys(self, ktype, public, private, fetch_remote=True): - """ - Get public and private keys of ktype. - - :param ktype: The type of the key. - :type ktype: subclass of EncryptionKey - :param public: The address of the public key. - :type public: str - :param private: The address of the private key. - :type private: str - :param fetch_remote: If key for verify not found in local storage try - to fetch from nickserver - :type fetch_remote: bool + if pubkey is None: + signature = KeyNotFound(verify) + elif signed: + signature = pubkey + else: + signature = InvalidSignature( + 'Failed to verify signature with key %s' % + (pubkey.key_id,)) + return (decrypted, signature) - :return: A Deferred which fires with a tuple with public and private - EncryptionKeys, or which fails with KeyNotFound if no keys - were found neither locally or in keyserver. - :rtype: Deferred - """ + dpriv = self.get_key(address, ktype, private=True) dpub = defer.succeed(None) - if public is not None: - dpub = self.get_key(public, ktype, private=False, + if verify is not None: + dpub = self.get_key(verify, ktype, private=False, fetch_remote=fetch_remote) - dpriv = defer.succeed(None) - if private is not None: - dpriv = self.get_key(private, ktype, private=True) - return defer.gatherResults([dpub, dpriv]) + dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f) + d = defer.gatherResults([dpub, dpriv]) + d.addCallback(decrypt) + return d def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): @@ -612,8 +605,9 @@ class KeyManager(object): :type fetch_remote: bool :return: A Deferred which fires with the signing EncryptionKey if - signature verifies else None, or which fails with KeyNotFound - if no key was found neither locally or in keyserver. + signature verifies, or which fails with InvalidSignature if + signature don't verifies or fails with KeyNotFound if no key + was found neither locally or in keyserver. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type @@ -623,7 +617,12 @@ class KeyManager(object): def verify(pubkey): signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) - return pubkey if signed else None + if signed: + return pubkey + else: + raise InvalidSignature( + 'Failed to verify signature with key %s' % + (pubkey.key_id,)) d = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) diff --git a/src/leap/keymanager/errors.py b/src/leap/keymanager/errors.py index 4041837..8a9fb3c 100644 --- a/src/leap/keymanager/errors.py +++ b/src/leap/keymanager/errors.py @@ -51,6 +51,13 @@ class NoPasswordGiven(Exception): pass +class InvalidSignature(Exception): + """ + Raised when signature could not be verified. + """ + pass + + class EncryptError(Exception): """ Raised upon failures of encryption. diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index b8ef88a..dca89e8 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -29,6 +29,7 @@ from twisted.trial import unittest from leap.keymanager import ( KeyNotFound, KeyAddressMismatch, + errors ) from leap.keymanager.openpgp import OpenPGPKey from leap.keymanager.keys import ( @@ -357,7 +358,7 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): rawdata, signingkey = yield km.decrypt( encdata, ADDRESS, OpenPGPKey, verify=ADDRESS, fetch_remote=False) self.assertEqual(self.RAW_DATA, rawdata) - self.assertTrue(signingkey is None) + self.assertTrue(isinstance(signingkey, errors.InvalidSignature)) @inlineCallbacks def test_keymanager_openpgp_sign_verify(self): -- cgit v1.2.3 From 9482f5426dca3c9f053b71501aa08514cfab6672 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 29 Dec 2014 00:27:27 -0600 Subject: Return a valid error from gatherResults --- src/leap/keymanager/__init__.py | 11 +++++++---- src/leap/keymanager/tests/test_keymanager.py | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 5324d42..c4050fa 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -485,8 +485,8 @@ class KeyManager(object): dpriv = defer.succeed(None) if sign is not None: dpriv = self.get_key(sign, ktype, private=True) - d = defer.gatherResults([dpub, dpriv]) - d.addCallback(encrypt) + d = defer.gatherResults([dpub, dpriv], consumeErrors=True) + d.addCallbacks(encrypt, self._extract_first_error) return d def decrypt(self, data, address, ktype, passphrase=None, verify=None, @@ -542,10 +542,13 @@ class KeyManager(object): dpub = self.get_key(verify, ktype, private=False, fetch_remote=fetch_remote) dpub.addErrback(lambda f: None if f.check(KeyNotFound) else f) - d = defer.gatherResults([dpub, dpriv]) - d.addCallback(decrypt) + d = defer.gatherResults([dpub, dpriv], consumeErrors=True) + d.addCallbacks(decrypt, self._extract_first_error) return d + def _extract_first_error(self, failure): + return failure.value.subFailure + def sign(self, data, address, ktype, digest_algo='SHA512', clearsign=False, detach=True, binary=False): """ diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index dca89e8..86832ba 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -375,6 +375,14 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): fetch_remote=False) self.assertEqual(signingkey.fingerprint, key.fingerprint) + def test_keymanager_encrypt_key_not_found(self): + km = self._key_manager() + d = km._wrapper_map[OpenPGPKey].put_ascii_key(PRIVATE_KEY, ADDRESS) + d.addCallback( + lambda _: km.encrypt(self.RAW_DATA, ADDRESS_2, OpenPGPKey, + sign=ADDRESS, fetch_remote=False)) + return self.assertFailure(d, KeyNotFound) + import unittest if __name__ == "__main__": -- cgit v1.2.3 From c6e5296ed7e9c5021d09dde381d77d2d17d5715d Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 07:38:42 -0600 Subject: Fix key upgrade on no expiration date and higher validation level --- src/leap/keymanager/validation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index 245013e..87de2af 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -82,7 +82,8 @@ def can_upgrade(new_key, old_key): return True # No expiration date and higher validation level - elif new_key.validation >= old_key.validation: + if (old_key.expiry_date is None and + new_key.validation > old_key.validation): return True return False -- cgit v1.2.3 From 607e10bab5b81de692a31cfa5624d0f3630f4c55 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 07:40:05 -0600 Subject: On key update merge metadata correctly --- src/leap/keymanager/openpgp.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index c95b381..0adfc52 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -40,8 +40,6 @@ from leap.keymanager.keys import ( TYPE_ID_PRIVATE_INDEX, TYPE_ADDRESS_PRIVATE_INDEX, KEY_ADDRESS_KEY, - KEY_FINGERPRINT_KEY, - KEY_DATA_KEY, KEY_ID_KEY, KEYMANAGER_ACTIVE_TYPE, ) @@ -447,23 +445,30 @@ class OpenPGPScheme(EncryptionScheme): def check_and_put(docs, key): if len(docs) == 1: doc = docs.pop() - if key.fingerprint == doc.content[KEY_FINGERPRINT_KEY]: + 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: - gpg.import_keys(doc.content[KEY_DATA_KEY]) + gpg.import_keys(oldkey.key_data) gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() - key = _build_key_from_gpg( + mergedkey = _build_key_from_gpg( gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) - doc.set_json(key.get_json()) + 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, doc.content[KEY_FINGERPRINT_KEY])) + % (key.fingerprint, oldkey.fingerprint)) d = defer.fail( errors.KeyFingerprintMismatch(key.fingerprint)) elif len(docs) > 1: -- cgit v1.2.3 From 963c3afaf1f9674d876465dd4bffc1c11ce1cb51 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 08:15:43 -0600 Subject: Upgrade keys if not successfully used and strict high validation level --- src/leap/keymanager/__init__.py | 23 ++++-- src/leap/keymanager/tests/test_validation.py | 118 +++++++++++++++++++++++---- src/leap/keymanager/validation.py | 6 +- 3 files changed, 126 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index c4050fa..fdbc206 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -476,9 +476,13 @@ class KeyManager(object): def encrypt(keys): pubkey, signkey = keys - return self._wrapper_map[ktype].encrypt( + encrypted = self._wrapper_map[ktype].encrypt( data, pubkey, passphrase, sign=signkey, cipher_algo=cipher_algo) + pubkey.encr_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: encrypted) + return d dpub = self.get_key(address, ktype, private=False, fetch_remote=fetch_remote) @@ -529,7 +533,10 @@ class KeyManager(object): if pubkey is None: signature = KeyNotFound(verify) elif signed: - signature = pubkey + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: (decrypted, pubkey)) + return d else: signature = InvalidSignature( 'Failed to verify signature with key %s' % @@ -621,7 +628,10 @@ class KeyManager(object): signed = self._wrapper_map[ktype].verify( data, pubkey, detached_sig=detached_sig) if signed: - return pubkey + pubkey.sign_used = True + d = self._wrapper_map[ktype].put_key(pubkey, address) + d.addCallback(lambda _: pubkey) + return d else: raise InvalidSignature( 'Failed to verify signature with key %s' % @@ -718,9 +728,12 @@ class KeyManager(object): :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) - pubkey, _ = self._wrapper_map[ktype].parse_ascii_key(key) + pubkey, privkey = self._wrapper_map[ktype].parse_ascii_key(key) pubkey.validation = validation - return self.put_key(pubkey, address) + d = self.put_key(pubkey, address) + if privkey is not None: + d.addCallback(lambda _: self.put_key(privkey, address)) + return d def fetch_key(self, address, uri, ktype, validation=ValidationLevel.Weak_Chain): diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 83a02e0..a8b35ca 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -89,6 +89,34 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.expiry_date, EXPIRED_KEY_NEW_EXPIRY_DATE) + @inlineCallbacks + def test_not_used(self): + km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Trust) + yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Endorsement) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) + + @inlineCallbacks + def test_used(self): + TEXT = "some text" + + km = self._key_manager() + yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS) + yield km.encrypt(TEXT, ADDRESS, OpenPGPKey) + + km2 = self._key_manager() + yield km2.put_raw_key(UNEXPIRED_PRIVATE, OpenPGPKey, ADDRESS) + signature = yield km2.sign(TEXT, ADDRESS, OpenPGPKey) + + yield km.verify(TEXT, ADDRESS, OpenPGPKey, detached_sig=signature) + d = km.put_raw_key( + UNRELATED_KEY, OpenPGPKey, ADDRESS, + validation=ValidationLevel.Provider_Endorsement) + yield self.assertFailure(d, KeyNotValidUpgrade) + # Key material for testing @@ -162,7 +190,7 @@ Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -----END PGP PUBLIC KEY BLOCK----- """ # updated expiration date -EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2045319180) +EXPIRED_KEY_NEW_EXPIRY_DATE = datetime.fromtimestamp(2049717872) EXPIRED_KEY_UPDATED = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) @@ -174,27 +202,87 @@ acDh9vNtPxDCg5RdI0bfdIEBGgHTfsda3kWGvo1wH5SgrTRq0+EcTI7aJgkMmM/A IhnpACE52NvGdG9eB3x7xyQFsQqK8F0XvEev2UJH4SR7vb+Z7FNTJKCy6likYbSV wGGFuowFSESnzXuUI6PcjyuO6FUbMgeM5euFABEBAAG0HExlYXAgVGVzdCBLZXkg PGxlYXBAbGVhcC5zZT6JAT4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B -AheABQJUURIXBQld/ZovAAoJEG8V8AShiFp8xUcIALcAHZbaxvyhHRGOrwDddbH0 -fFDK0AqKTsIT7y4D/HLFCP5zG3Ck7qGPZdkHXZfzq8rIb+zUjW3oJIVI1IucHxG2 -T5kppa8RFCBAFlRWYf6R3isX3YL0d3QSragjoxRNPcHNU8ALHcvfSonFHBoi4fH4 -4rvgksAiT68SsdPaoXDlabx5T15evu/7T5e/DGMQVPMxiaSuSQhbOKuMk2wcFdmL -tBYHLZPa54hHPNhEDyxLgtKKph0gObk9ojKfH9kPvLveIcpS5CqTJfN/kqBz7CJW -wEeAi2iG3H1OEB25aCUdTxXSRNlGqEgcWPaWxtc1RzlARu7LB64OUZuRy4puiAG5 +AheABQJUlDCSBQleQLiTAAoJEG8V8AShiFp8t3QH/1eqkVIScXmqaCVeno3VSKiH +HqnxiHcEgtpNRfUlP6tLD4H6QPEpvoUI9S/8HSYi3nbDGXEX8ycKlnwxjdIqWSOW +xj91/7uQAo+dP9QaVJ6xgaAiqzN1x3JzX3Js1wTodmNV0TfmGjxwnC5up/xK7/pd +KuDP3woDsRlwy8Lgj67mkn49xfAFHo6hI6SD36UBDAC/ELq6kZaba4Kk0fEVHCEz +HX0B09ZIY9fmf305cEB3dNh6SMQgKtH0wKozaqI2UM2B+cs3z08bC+YuUUh7UJTH +yr+hI7vF4/WEeJB3fuhP3xsumLhV8P47DaJ7oivmtsDEbAJFKqvigEqNES73Xpy5 AQ0EG+t93QEIAKqRq/2sBDW4g3FU+11LhixT+GosrfVvnitz3S9k2tBXok/wYpI1 XeA+kTHiF0LaqoaciDRvkA9DvhDbSrNM1yeuYRyZiHlTmoPZ/Fkl60oA2cyLd1L5 sXbuipY3TEiakugdSU4rzgi0hFycm6Go6yq2G6eC6UALvD9CTMdZHw40TadG9xpm 4thYPuJ1kPH8/bkbTi9sLHoApYgL+7ssje8w4epr0qD4IGxeKwJPf/tbTRpnd8w3 leldixHHKAutNt49p0pkXlORAHRpUmp+KMZhFvCvIPwe9o5mYtMR7sDRxjY61ZEQ -KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwUCG+t93QIb -DAUJAAFRgAAKCRBvFfAEoYhafOPgB/9z4YCyT/N0262HtegHykhsyykuqEeNb1LV -D9INcP+RbCX/0IjFgP4DTMPP7qqF1OBwR276maALT321Gqxc5HN5YrwxGdmoyBLm -unaQJJlD+7B1C+jnO6r4m44obvJ/NMERxVyzkXap3J2VgRIO1wNLI9I0sH6Kj5/j -Mgy06OwXDcqIc+jB4sIJ3Tnm8LZ3phJzNEm9mI8Ak0oJ7IEcMndR6DzmRt1rJQcq -K/D7hOG02zvyRhxF27U1qR1MxeU/gNnOx8q4dnVyWB+EiV1sFl4iTOyYHEsoyd7W -Osuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -=79Ll +KLyKoh5wsJsaPXBjdG7cf6G/cBcwvnQVUHcAEQEAAYkBJQQYAQIADwIbDAUCVJQw +3QUJXkC4/QAKCRBvFfAEoYhafEtiB/9hMfSFNMxtlIJDJArG4JwR7sBOatYUT858 +qZnTgGETZN8wXpeEpXWKdDdmCX9aeE9jsDNgSQ5WWpqU21bGMXh1IGjAzmqTqq3/ +ik1vALuaVfr6OqjTzrJVQujT61CGed26xpP3Zh8hLKyKa+dXnX/VpgZS42wZLPx2 +wcODfANmTfE2AhMap/RyDy21q4nau+z2hMEOKdtF8dpP+pEvzoN5ZexYP1hfT+Av +oFPyVB5YtEMfxTEshDKRPjbdgNmw4faKXd5Cbelo4YxxpO16FHb6gzIdjOX15vQ+ +KwcVXzg9xk4D3cr1mnTCops/iv6TXvcw4Wbo70rrKXwkjl8LKjOP +=sHoe -----END PGP PUBLIC KEY BLOCK----- """ +UNEXPIRED_KEY = EXPIRED_KEY_UPDATED +UNEXPIRED_PRIVATE = """ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +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 +=dZE8 +-----END PGP PRIVATE KEY BLOCK----- +""" import unittest diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index 87de2af..b3aff3e 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -60,7 +60,6 @@ def can_upgrade(new_key, old_key): :type old_key: EncryptionKey :rtype: bool """ - # XXX not succesfully used and strict high validation level (#6211) # XXX implement key signature checking (#6120) # First contact @@ -86,4 +85,9 @@ def can_upgrade(new_key, old_key): new_key.validation > old_key.validation): return True + # Not successfully used and strict high validation level + if (not (old_key.sign_used and old_key.encr_used) and + new_key.validation > old_key.validation): + return True + return False -- cgit v1.2.3 From 6fa8b2a9e7f02c59f794e9dd080fac574841e50b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 19 Dec 2014 22:37:40 -0600 Subject: upgrade key when signed by old key --- src/leap/keymanager/__init__.py | 2 +- src/leap/keymanager/openpgp.py | 99 +++++++++++++++++----------- src/leap/keymanager/tests/test_validation.py | 55 ++++++++++++++++ src/leap/keymanager/validation.py | 6 +- 4 files changed, 121 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index fdbc206..a1a59f5 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -25,7 +25,7 @@ try: assert(GPGUtilities) # pyflakes happy from gnupg import __version__ as _gnupg_version from pkg_resources import parse_version - assert(parse_version(_gnupg_version) >= parse_version('1.2.3')) + assert(parse_version(_gnupg_version) >= parse_version('1.4.0')) except (ImportError, AssertionError): print "*******" diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 0adfc52..794a0ec 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -163,39 +163,6 @@ class TempGPGWrapper(object): shutil.rmtree(self._gpg.homedir) -def _build_key_from_gpg(key, key_data): - """ - Build an OpenPGPKey based on C{key} from local gpg storage. - - ASCII armored GPG key data has to be queried independently in this - wrapper, so we receive it in C{key_data}. - - :param key: Key obtained from GPG storage. - :type key: dict - :param key_data: Key data obtained from GPG storage. - :type key_data: str - :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, - 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(), - ) - - def _parse_address(address): """ Remove name, '<', '>' and the identity suffix after the '+' until the '@' @@ -221,6 +188,26 @@ class OpenPGPKey(EncryptionKey): Base class for OpenPGP keys. """ + def __init__(self, address, gpgbinary=None, **kwargs): + self._gpgbinary = gpgbinary + super(OpenPGPKey, self).__init__(address, **kwargs) + + @property + def signatures(self): + """ + Get the key signatures + + :return: the key IDs that have signed the key + :rtype: list(str) + """ + with TempGPGWrapper(keys=[self], gpgbinary=self._gpgbinary) as gpg: + res = gpg.list_sigs(self.key_id) + for uid, sigs in res.sigs.iteritems(): + if _parse_address(uid) in self.address: + return sigs + + return [] + class OpenPGPScheme(EncryptionScheme): """ @@ -298,7 +285,7 @@ class OpenPGPScheme(EncryptionScheme): deferreds = [] for secret in [True, False]: key = gpg.list_keys(secret=secret).pop() - openpgp_key = _build_key_from_gpg( + openpgp_key = self._build_key_from_gpg( key, gpg.export_keys(key['fingerprint'], secret=secret)) d = self.put_key(openpgp_key, address) @@ -335,7 +322,9 @@ class OpenPGPScheme(EncryptionScheme): leap_assert( address in doc.content[KEY_ADDRESS_KEY], 'Wrong address in key data.') - return build_key_from_dict(OpenPGPKey, doc.content) + key = build_key_from_dict(OpenPGPKey, doc.content) + key._gpgbinary = self._gpgbinary + return key d = self._get_key_doc(address, private) d.addCallback(build_key) @@ -375,7 +364,7 @@ class OpenPGPScheme(EncryptionScheme): openpgp_privkey = None if privkey is not None: # build private key - openpgp_privkey = _build_key_from_gpg( + openpgp_privkey = self._build_key_from_gpg( privkey, gpg.export_keys(privkey['fingerprint'], secret=True)) leap_check(pubkey['fingerprint'] == privkey['fingerprint'], @@ -383,7 +372,7 @@ class OpenPGPScheme(EncryptionScheme): errors.KeyFingerprintMismatch) # build public key - openpgp_pubkey = _build_key_from_gpg( + openpgp_pubkey = self._build_key_from_gpg( pubkey, gpg.export_keys(pubkey['fingerprint'], secret=False)) @@ -452,7 +441,7 @@ class OpenPGPScheme(EncryptionScheme): gpg.import_keys(oldkey.key_data) gpg.import_keys(key.key_data) gpgkey = gpg.list_keys(secret=key.private).pop() - mergedkey = _build_key_from_gpg( + mergedkey = self._build_key_from_gpg( gpgkey, gpg.export_keys(gpgkey['fingerprint'], secret=key.private)) @@ -571,6 +560,40 @@ class OpenPGPScheme(EncryptionScheme): d.addCallback(get_key_from_active_doc) return d + def _build_key_from_gpg(self, key, key_data): + """ + Build an OpenPGPKey for C{address} based on C{key} from + local gpg storage. + + ASCII armored GPG key data has to be queried independently in this + wrapper, so we receive it in C{key_data}. + + :param key: Key obtained from GPG storage. + :type key: dict + :param key_data: Key data obtained from GPG storage. + :type key_data: str + :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(), + ) + def delete_key(self, key): """ Remove C{key} from storage. diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index a8b35ca..15e7d27 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -117,6 +117,14 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): validation=ValidationLevel.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) + @inlineCallbacks + def test_signed_key(self): + km = self._key_manager() + yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) + yield km.put_raw_key(SIGNED_KEY, OpenPGPKey, ADDRESS) + key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) + self.assertEqual(key.fingerprint, SIGNED_FINGERPRINT) + # Key material for testing @@ -284,6 +292,53 @@ HEsoyd7WOsuse7+NkyUHgMXMVW7cz+nU7iO+ht2rkBtv+Z5LGlzgHTeFjKci -----END PGP PRIVATE KEY BLOCK----- """ +# key CA1AD31E: public key "Leap Test Key " +# signed by E36E738D69173C13D709E44F2F455E2824D18DDF +SIGNED_FINGERPRINT = "6704CF3362087DA23E3D2DF8ED81DFD1CA1AD31E" +SIGNED_KEY = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) + +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 +=xprj +-----END PGP PUBLIC KEY BLOCK----- +""" + import unittest if __name__ == "__main__": diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index b3aff3e..c6fe478 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -60,8 +60,6 @@ def can_upgrade(new_key, old_key): :type old_key: EncryptionKey :rtype: bool """ - # XXX implement key signature checking (#6120) - # First contact if old_key is None: return True @@ -90,4 +88,8 @@ def can_upgrade(new_key, old_key): new_key.validation > old_key.validation): return True + # New key signed by the old key + if old_key.key_id in new_key.signatures: + return True + return False -- cgit v1.2.3 From 82d027b1f471517213bdcdc773dc8eea677fe330 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 5 Jan 2015 08:54:24 -0600 Subject: Port validation levels to enum34 --- src/leap/keymanager/keys.py | 2 +- src/leap/keymanager/tests/test_keymanager.py | 2 +- src/leap/keymanager/validation.py | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 4e98de6..562c0a9 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -213,7 +213,7 @@ class EncryptionKey(object): KEY_EXPIRY_DATE_KEY: expiry_date, KEY_LAST_AUDITED_AT_KEY: last_audited_at, KEY_REFRESHED_AT_KEY: refreshed_at, - KEY_VALIDATION_KEY: str(self.validation), + KEY_VALIDATION_KEY: self.validation.name, KEY_ENCR_USED_KEY: self.encr_used, KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 86832ba..93bc42c 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -79,7 +79,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): 'expiry_date': 0, 'last_audited_at': 0, 'refreshed_at': 1311239602, - 'validation': str(ValidationLevel.Weak_Chain), + 'validation': ValidationLevel.Weak_Chain.name, 'encr_used': False, 'sign_used': True, } diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index c6fe478..c81d533 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -24,17 +24,17 @@ See: from datetime import datetime -from enum import Enum +from enum import IntEnum -ValidationLevel = Enum( - "Weak_Chain", - "Provider_Trust", - "Provider_Endorsement", - "Third_Party_Endorsement", - "Third_Party_Consensus", - "Historically_Auditing", - "Known_Key", +ValidationLevel = IntEnum("ValidationLevel", + "Weak_Chain " + "Provider_Trust " + "Provider_Endorsement " + "Third_Party_Endorsement " + "Third_Party_Consensus " + "Historically_Auditing " + "Known_Key " "Fingerprint") @@ -49,7 +49,7 @@ def toValidationLevel(value): :raises ValueError: if C{value} is not a validation level """ for level in ValidationLevel: - if value == str(level): + if value == level.name: return level raise ValueError("Not valid validation level: %s" % (value,)) -- cgit v1.2.3 From c4bcdea3621ad52400047170e3b7c8049755f629 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 19 Feb 2015 10:21:56 -0600 Subject: Fetch keys should return KeyNotFound for unknown errors --- src/leap/keymanager/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index a1a59f5..3ef6350 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -230,11 +230,11 @@ class KeyManager(object): if e.response.status_code == 404: d = defer.fail(KeyNotFound(address)) else: - d = defer.fail(e) + d = defer.fail(KeyNotFound(e.message)) logger.warning("HTTP error retrieving key: %r" % (e,)) logger.warning("%s" % (res.content,)) except Exception as e: - d = defer.fail(e) + d = defer.fail(KeyNotFound(e.message)) logger.warning("Error retrieving key: %r" % (e,)) return d -- cgit v1.2.3 From af593ae913052e5e02e53d572824673a101d09ac Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 30 Mar 2015 11:25:21 +0200 Subject: [feat] set fetched keys as Weak Chain if they are not from the same domain Nicknym server is authoritative for its own domain, but for others it might retrieve keys from key servers. On keys from the same domain we set the validation level to 'Provider Trust'. For other domains in the email address we set it to 'Weak Chain' as we don't have info about its source. Resolves: #6815 Related: #6718 Releases: 0.4.0 --- src/leap/keymanager/__init__.py | 40 +++++++++++- src/leap/keymanager/tests/test_keymanager.py | 96 ++++++++++++++++++++-------- 2 files changed, 109 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 3ef6350..f7b1974 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -44,6 +44,7 @@ import logging import requests from twisted.internet import defer +from urlparse import urlparse from leap.common.check import leap_assert from leap.common.events import signal @@ -219,13 +220,21 @@ class KeyManager(object): res = self._get(self._nickserver_uri, {'address': address}) res.raise_for_status() server_keys = res.json() + # insert keys in local database if self.OPENPGP_KEY in server_keys: + # nicknym server is authoritative for its own domain, + # for other domains the key might come from key servers. + validation_level = ValidationLevel.Weak_Chain + _, domain = _split_email(address) + if (domain == _get_domain(self._nickserver_uri)): + validation_level = ValidationLevel.Provider_Trust + d = self.put_raw_key( server_keys['openpgp'], OpenPGPKey, address=address, - validation=ValidationLevel.Provider_Trust) + validation=validation_level) except requests.exceptions.HTTPError as e: if e.response.status_code == 404: d = defer.fail(KeyNotFound(address)) @@ -786,6 +795,35 @@ class KeyManager(object): if ktype not in self._wrapper_map: raise UnsupportedKeyTypeError(str(ktype)) + +def _split_email(address): + """ + Split username and domain from an email address + + :param address: an email address + :type address: str + + :return: username and domain from the email address + :rtype: (str, str) + """ + if address.count("@") != 1: + return None + return address.split("@") + + +def _get_domain(url): + """ + Get the domain from an url + + :param url: an url + :type url: str + + :return: the domain part of the url + :rtype: str + """ + return urlparse(url).hostname + + from ._version import get_versions __version__ = get_versions()['version'] del get_versions diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 93bc42c..55f892e 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -52,6 +52,9 @@ from leap.keymanager.tests import ( ) +NICKSERVER_URI = "http://leap.se/" + + class KeyManagerUtilTestCase(unittest.TestCase): def test_is_address(self): @@ -201,31 +204,16 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ Test that the request is well formed when fetching keys from server. """ - km = self._key_manager(url='http://nickserver.domain') - - class Response(object): - status_code = 200 - headers = {'content-type': 'application/json'} - - def json(self): - return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2} - - def raise_for_status(self): - pass - - # mock the fetcher so it returns the key for ADDRESS_2 - km._fetcher.get = Mock( - return_value=Response()) - km.ca_cert_path = 'cacertpath' + km = self._key_manager(url=NICKSERVER_URI) def verify_the_call(_): km._fetcher.get.assert_called_once_with( - 'http://nickserver.domain', + NICKSERVER_URI, data={'address': ADDRESS_2}, verify='cacertpath', ) - d = km._fetch_keys_from_server(ADDRESS_2) + d = self._fetch_key(km, ADDRESS_2, PUBLIC_KEY_2) d.addCallback(verify_the_call) return d @@ -234,14 +222,35 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): """ Test that getting a key successfuly fetches from server. """ - km = self._key_manager(url='http://nickserver.domain') + km = self._key_manager(url=NICKSERVER_URI) + + key = yield self._fetch_key(km, ADDRESS, PUBLIC_KEY) + self.assertIsInstance(key, OpenPGPKey) + self.assertTrue(ADDRESS in key.address) + self.assertEqual(key.validation, ValidationLevel.Provider_Trust) + + @inlineCallbacks + def test_get_key_fetches_other_domain(self): + """ + Test that getting a key successfuly fetches from server. + """ + km = self._key_manager(url=NICKSERVER_URI) + + key = yield self._fetch_key(km, ADDRESS_OTHER, PUBLIC_KEY_OTHER) + self.assertIsInstance(key, OpenPGPKey) + self.assertTrue(ADDRESS_OTHER in key.address) + self.assertEqual(key.validation, ValidationLevel.Weak_Chain) + def _fetch_key(self, km, address, key): + """ + :returns: a Deferred that will fire with the OpenPGPKey + """ class Response(object): status_code = 200 headers = {'content-type': 'application/json'} def json(self): - return {'address': ADDRESS, 'openpgp': PUBLIC_KEY} + return {'address': address, 'openpgp': key} def raise_for_status(self): pass @@ -250,19 +259,18 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km._fetcher.get = Mock(return_value=Response()) km.ca_cert_path = 'cacertpath' # try to key get without fetching from server - d = km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) - yield self.assertFailure(d, KeyNotFound) + d_fail = km.get_key(address, OpenPGPKey, fetch_remote=False) + d = self.assertFailure(d_fail, KeyNotFound) # try to get key fetching from server. - key = yield km.get_key(ADDRESS, OpenPGPKey) - self.assertIsInstance(key, OpenPGPKey) - self.assertTrue(ADDRESS in key.address) + d.addCallback(lambda _: km.get_key(address, OpenPGPKey)) + return d @inlineCallbacks def test_put_key_ascii(self): """ Test that putting ascii key works """ - km = self._key_manager(url='http://nickserver.domain') + km = self._key_manager(url=NICKSERVER_URI) yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) key = yield km.get_key(ADDRESS, OpenPGPKey) @@ -387,3 +395,39 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): import unittest if __name__ == "__main__": unittest.main() + +# key 0F91B402: someone@somedomain.org +# 9420 EC7B 6DCB 867F 5592 E6D1 7504 C974 0F91 B402 +ADDRESS_OTHER = "someone@somedomain.org" +PUBLIC_KEY_OTHER = """ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFUZFLwBCADRzTstykRAV3aWysLAV4O3DXdpXhV3Cww8Pfc6m1bVxAT2ifcL +kLWEaIkOB48SYIHbYzqOi1/h5abJf+5n4uhaIks+FsjsXYo1XOiYpVCNf7+xLnUM +jkmglKT5sASr61QDcFMqWfGTJ8iUTNVCJZ2k14QJ4Vss/ntnV9uB7Ef7wU7RZvxr +wINH/0LfKPsGE9l2qNpKUAAmg2bHn9YdsHj1sqlW7eZpwvefYrQej4KBaL2oq3vt +QQOdXGFqWYMe3cX+bQ1DAMG3ttTF6EGkY97BK7A18I/RJiLujWCEAkMzFr5SK9KU +AOMj6MpjfTOE+GfUKsu7/gGt42eMBFsIOvsZABEBAAG0IFNvbWVvbmUgPHNvbWVv +bmVAc29tZWRvbWFpbi5vcmc+iQE4BBMBAgAiBQJVGRS8AhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRB1BMl0D5G0AlFsCAC33LhxBRwO64T6DgTb4/39aLpi +9T3yAmXBAHC7Q+4f37IBX5fJBRKu4Lvfp6KherOl/I/Jj34yv8pm0j+kXeWktfxZ +cW+mv2vjBHQVopiUSyMVh7caFSq9sKm+oQdo6oIl9DHSARegbkCn2+0b4VxgJpyj +TZBMyUMD2AayivQU4QHOM3KCozhLNNDbpKy7LH0MSAUDmRaJsPk1zK15lQocK/7R +Z5yF4rdrdzDWrVucZJc09yntSqTGECue3W2GBCaBlb/O1c9xei4MTb4nSHS5Gp/7 +hcjrvIrgPpehndk8ZRREN/Y8uk1W5fbWzx+5z8g31RCGWBQw4NAnG10NZ3oEuQEN +BFUZFLwBCADocYZmLu1iXIE6gKqniR6Z8UDC5XnqgK+BEJwi1abe9zWhjgKeW9Vv +u1i194wuCUiNkP/bMvwMBZLTslDzqxl32ETk9FvB3kWy80S8MDjQJ15IN4I622fq +MEWwtQ0WrRay9VV6M8H2mIf71/1d5T9ysWK4XRyv+N7eRhfg7T2uhrpNyKdCZzjq +2wlgpVkMY7gtxTqJseM+qS5UNiReGxtoOXFLzzmagFgbqK88eMeZJZt8yKf81xhP +SWLTxaVaeBEAlajvEkxZJrrDQuc+maTwtMxmNUe815wJnpcRF8VD91GUpSLAN6EC +1QuJUl6Lc2o2tcHeo6CGsDZ96o0J8pFhABEBAAGJAR8EGAECAAkFAlUZFLwCGwwA +CgkQdQTJdA+RtAKcdwgApzHPhwwaZ9TBjgOytke/hPE0ht/EJ5nRiIda2PucoPh6 +DwnaI8nvmGXUfC4qFy6LM8/fJHof1BqLnMbx8MCLurnm5z30q8RhLE3YWM11zuMy +6wkHGmi/6S1G4okC+Uu8AA4K//HBo8bLcqGVWRnFAmCqy6VMAofsQvmM7vHbRj56 +U919Bki/7I6kcxPEzO73Umh3o82VP/Hz3JMigRNBRfG3jPrX04RLJj3Ib5lhQIDw +XrO8VHz9foOpY+rJnWj+6QAozxorzZYShu6H0GR1nIuqWMwli1nrx6BeIJAVz5cg +QzEd9yAN+81fkIBaa6Y8LCBxV03JCc2J4eCUKXd1gg== +=gDzy +-----END PGP PUBLIC KEY BLOCK----- +""" -- cgit v1.2.3 From 58ed7b51ccc63155c166a025336e325fc5ffda77 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 7 Apr 2015 12:56:20 +0200 Subject: [doc] added the right link the validation levels documentation The mailing list was linked, but now there is a proper documentation page. - Releases: 0.4.0 --- src/leap/keymanager/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index c81d533..dfe6432 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -19,7 +19,7 @@ Validation levels implementation for key managment. See: - https://lists.riseup.net/www/arc/leap-discuss/2014-09/msg00000.html + https://leap.se/en/docs/design/transitional-key-validation """ -- cgit v1.2.3 From 1a1b0b3309a54371a6631c87f0527b8fde5601f4 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 6 May 2015 18:04:03 -0300 Subject: [feat] adapt to new events api on common - Related: #6359 --- src/leap/keymanager/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index f7b1974..47f479b 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -47,8 +47,7 @@ from twisted.internet import defer from urlparse import urlparse from leap.common.check import leap_assert -from leap.common.events import signal -from leap.common.events import events_pb2 as proto +from leap.common.events import emit, catalog from leap.common.decorators import memoized_method from leap.keymanager.errors import ( @@ -278,7 +277,7 @@ class KeyManager(object): self._api_version, self._uid) self._put(uri, data) - signal(proto.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) + emit(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) d = self.get_key( self._address, ktype, private=False, fetch_remote=False) @@ -314,24 +313,24 @@ class KeyManager(object): leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) - signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) + emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): - signal(proto.KEYMANAGER_KEY_FOUND, address) + emit(catalog.KEYMANAGER_KEY_FOUND, address) return key def key_not_found(failure): if not failure.check(KeyNotFound): return failure - signal(proto.KEYMANAGER_KEY_NOT_FOUND, address) + emit(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 - signal(proto.KEYMANAGER_LOOKING_FOR_KEY, address) + emit(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: @@ -388,10 +387,10 @@ class KeyManager(object): self._assert_supported_key_type(ktype) def signal_finished(key): - signal(proto.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + emit(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key - signal(proto.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + emit(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 d7acaf356d16c795f1481fefc7a75ffc8a36b1d0 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 26 Jun 2015 18:12:22 +0200 Subject: [bug] remove the dependency on enum34 * Resolves: #7188 --- src/leap/keymanager/__init__.py | 14 +++--- src/leap/keymanager/keys.py | 10 ++-- src/leap/keymanager/tests/test_keymanager.py | 13 ++--- src/leap/keymanager/tests/test_validation.py | 18 +++---- src/leap/keymanager/validation.py | 73 +++++++++++++++++++--------- 5 files changed, 77 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 47f479b..c2d7409 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -57,7 +57,7 @@ from leap.keymanager.errors import ( UnsupportedKeyTypeError, InvalidSignature ) -from leap.keymanager.validation import ValidationLevel, can_upgrade +from leap.keymanager.validation import ValidationLevels, can_upgrade from leap.keymanager.keys import ( build_key_from_dict, @@ -224,10 +224,10 @@ class KeyManager(object): if self.OPENPGP_KEY in server_keys: # nicknym server is authoritative for its own domain, # for other domains the key might come from key servers. - validation_level = ValidationLevel.Weak_Chain + validation_level = ValidationLevels.Weak_Chain _, domain = _split_email(address) if (domain == _get_domain(self._nickserver_uri)): - validation_level = ValidationLevel.Provider_Trust + validation_level = ValidationLevels.Provider_Trust d = self.put_raw_key( server_keys['openpgp'], @@ -712,7 +712,7 @@ class KeyManager(object): return d def put_raw_key(self, key, ktype, address, - validation=ValidationLevel.Weak_Chain): + validation=ValidationLevels.Weak_Chain): """ Put raw key bound to address in local storage. @@ -724,7 +724,7 @@ class KeyManager(object): :type address: str :param validation: validation level for this key (default: 'Weak_Chain') - :type validation: ValidationLevel + :type validation: ValidationLevels :return: A Deferred which fires when the key is in the storage, or which fails with KeyAddressMismatch if address doesn't match @@ -744,7 +744,7 @@ class KeyManager(object): return d def fetch_key(self, address, uri, ktype, - validation=ValidationLevel.Weak_Chain): + validation=ValidationLevels.Weak_Chain): """ Fetch a public key bound to address from the network and put it in local storage. @@ -757,7 +757,7 @@ class KeyManager(object): :type ktype: subclass of EncryptionKey :param validation: validation level for this key (default: 'Weak_Chain') - :type validation: ValidationLevel + :type validation: ValidationLevels :return: A Deferred which fires when the key is in the storage, or which fails with KeyNotFound: if not valid key on uri or fails diff --git a/src/leap/keymanager/keys.py b/src/leap/keymanager/keys.py index 562c0a9..91559c2 100644 --- a/src/leap/keymanager/keys.py +++ b/src/leap/keymanager/keys.py @@ -35,7 +35,7 @@ from datetime import datetime from leap.common.check import leap_assert from twisted.internet import defer -from leap.keymanager.validation import ValidationLevel, toValidationLevel +from leap.keymanager.validation import ValidationLevels logger = logging.getLogger(__name__) @@ -120,11 +120,11 @@ def build_key_from_dict(kClass, kdict): :rtype: C{kClass} """ try: - validation = toValidationLevel(kdict[KEY_VALIDATION_KEY]) + validation = ValidationLevels.get(kdict[KEY_VALIDATION_KEY]) except ValueError: logger.error("Not valid validation level (%s) for key %s", (kdict[KEY_VALIDATION_KEY], kdict[KEY_ID_KEY])) - validation = ValidationLevel.Weak_Chain + validation = ValidationLevels.Weak_Chain expiry_date = _to_datetime(kdict[KEY_EXPIRY_DATE_KEY]) last_audited_at = _to_datetime(kdict[KEY_LAST_AUDITED_AT_KEY]) @@ -176,7 +176,7 @@ class EncryptionKey(object): def __init__(self, address, key_id="", fingerprint="", key_data="", private=False, length=0, expiry_date=None, - validation=ValidationLevel.Weak_Chain, last_audited_at=None, + validation=ValidationLevels.Weak_Chain, last_audited_at=None, refreshed_at=None, encr_used=False, sign_used=False): self.address = address self.key_id = key_id @@ -213,7 +213,7 @@ class EncryptionKey(object): KEY_EXPIRY_DATE_KEY: expiry_date, KEY_LAST_AUDITED_AT_KEY: last_audited_at, KEY_REFRESHED_AT_KEY: refreshed_at, - KEY_VALIDATION_KEY: self.validation.name, + KEY_VALIDATION_KEY: str(self.validation), KEY_ENCR_USED_KEY: self.encr_used, KEY_SIGN_USED_KEY: self.sign_used, KEY_TAGS_KEY: [KEYMANAGER_KEY_TAG], diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 55f892e..08d3750 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -36,10 +36,7 @@ from leap.keymanager.keys import ( is_address, build_key_from_dict, ) -from leap.keymanager.validation import ( - ValidationLevel, - toValidationLevel -) +from leap.keymanager.validation import ValidationLevels from leap.keymanager.tests import ( KeyManagerWithSoledadTestCase, ADDRESS, @@ -82,7 +79,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): 'expiry_date': 0, 'last_audited_at': 0, 'refreshed_at': 1311239602, - 'validation': ValidationLevel.Weak_Chain.name, + 'validation': str(ValidationLevels.Weak_Chain), 'encr_used': False, 'sign_used': True, } @@ -115,7 +112,7 @@ class KeyManagerUtilTestCase(unittest.TestCase): datetime.fromtimestamp(kdict['refreshed_at']), key.refreshed_at, 'Wrong data in key.') self.assertEqual( - toValidationLevel(kdict['validation']), key.validation, + ValidationLevels.get(kdict['validation']), key.validation, 'Wrong data in key.') self.assertEqual( kdict['encr_used'], key.encr_used, @@ -227,7 +224,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield self._fetch_key(km, ADDRESS, PUBLIC_KEY) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS in key.address) - self.assertEqual(key.validation, ValidationLevel.Provider_Trust) + self.assertEqual(key.validation, ValidationLevels.Provider_Trust) @inlineCallbacks def test_get_key_fetches_other_domain(self): @@ -239,7 +236,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key = yield self._fetch_key(km, ADDRESS_OTHER, PUBLIC_KEY_OTHER) self.assertIsInstance(key, OpenPGPKey) self.assertTrue(ADDRESS_OTHER in key.address) - self.assertEqual(key.validation, ValidationLevel.Weak_Chain) + self.assertEqual(key.validation, ValidationLevels.Weak_Chain) def _fetch_key(self, km, address, key): """ diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 15e7d27..0c1d155 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -31,10 +31,10 @@ from leap.keymanager.tests import ( PUBLIC_KEY, KEY_FINGERPRINT ) -from leap.keymanager.validation import ValidationLevel +from leap.keymanager.validation import ValidationLevels -class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): +class ValidationLevelsTestCase(KeyManagerWithSoledadTestCase): @inlineCallbacks def test_none_old_key(self): @@ -47,7 +47,7 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): def test_cant_upgrade(self): km = self._key_manager() yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) + validation=ValidationLevels.Provider_Trust) d = km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS) yield self.assertFailure(d, KeyNotValidUpgrade) @@ -56,7 +56,7 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() yield km.put_raw_key(PUBLIC_KEY, OpenPGPKey, ADDRESS) yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Fingerprint) + validation=ValidationLevels.Fingerprint) key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @@ -73,12 +73,12 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): km = self._key_manager() yield km.put_raw_key( EXPIRED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Third_Party_Endorsement) + validation=ValidationLevels.Third_Party_Endorsement) d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) + validation=ValidationLevels.Provider_Trust) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks @@ -93,9 +93,9 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): def test_not_used(self): km = self._key_manager() yield km.put_raw_key(UNEXPIRED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Trust) + validation=ValidationLevels.Provider_Trust) yield km.put_raw_key(UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Endorsement) + validation=ValidationLevels.Provider_Endorsement) key = yield km.get_key(ADDRESS, OpenPGPKey, fetch_remote=False) self.assertEqual(key.fingerprint, UNRELATED_FINGERPRINT) @@ -114,7 +114,7 @@ class ValidationLevelTestCase(KeyManagerWithSoledadTestCase): yield km.verify(TEXT, ADDRESS, OpenPGPKey, detached_sig=signature) d = km.put_raw_key( UNRELATED_KEY, OpenPGPKey, ADDRESS, - validation=ValidationLevel.Provider_Endorsement) + validation=ValidationLevels.Provider_Endorsement) yield self.assertFailure(d, KeyNotValidUpgrade) @inlineCallbacks diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index dfe6432..3bb4032 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -24,34 +24,63 @@ See: from datetime import datetime -from enum import IntEnum -ValidationLevel = IntEnum("ValidationLevel", - "Weak_Chain " - "Provider_Trust " - "Provider_Endorsement " - "Third_Party_Endorsement " - "Third_Party_Consensus " - "Historically_Auditing " - "Known_Key " - "Fingerprint") +class ValidationLevel(object): + """ + A validation level + + Meant to be used to compare levels or get it's string representation. + """ + def __init__(self, name, value): + self.name = name + self.value = value + + def __cmp__(self, other): + return cmp(self.value, other.value) + + def __str__(self): + return self.name + + def __repr__(self): + return "" % (self.name, self.value) -def toValidationLevel(value): +class _ValidationLevels(object): """ - Convert a string representation of a validation level into - C{ValidationLevel} + Handler class to manage validation levels. It should have only one global + instance 'ValidationLevels'. - :param value: validation level - :type value: str - :rtype: ValidationLevel - :raises ValueError: if C{value} is not a validation level + The levels are attributes of the instance and can be used like: + ValidationLevels.Weak_Chain + ValidationLevels.get("Weak_Chain") """ - for level in ValidationLevel: - if value == level.name: - return level - raise ValueError("Not valid validation level: %s" % (value,)) + _level_names = ("Weak_Chain", + "Provider_Trust", + "Provider_Endorsement", + "Third_Party_Endorsement", + "Third_Party_Consensus", + "Historically_Auditing", + "Known_Key", + "Fingerprint") + + def __init__(self): + for name in self._level_names: + setattr(self, name, + ValidationLevel(name, self._level_names.index(name))) + + def get(self, name): + """ + Get the ValidationLevel of a name + + :param name: name of the level + :type name: str + :rtype: ValidationLevel + """ + return getattr(self, name) + + +ValidationLevels = _ValidationLevels() def can_upgrade(new_key, old_key): @@ -69,7 +98,7 @@ def can_upgrade(new_key, old_key): return True # Manually verified fingerprint - if new_key.validation == ValidationLevel.Fingerprint: + if new_key.validation == ValidationLevels.Fingerprint: return True # Expired key and higher validation level -- cgit v1.2.3 From ec0757c5972588d05224ea2d56d75ee9cd57c41f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 29 Jun 2015 12:05:51 -0400 Subject: [style] spelling typo --- src/leap/keymanager/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/validation.py b/src/leap/keymanager/validation.py index 3bb4032..734cfce 100644 --- a/src/leap/keymanager/validation.py +++ b/src/leap/keymanager/validation.py @@ -30,7 +30,7 @@ class ValidationLevel(object): """ A validation level - Meant to be used to compare levels or get it's string representation. + Meant to be used to compare levels or get its string representation. """ def __init__(self, name, value): self.name = name -- cgit v1.2.3 From 8d355cabd0b087b5e7161c2721bb8ae94ace1336 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 23 Jul 2015 14:34:53 -0400 Subject: [pkg] avoid choking on latest gnupg version latest gnupg version (from pypi) was '2.0.2-py2.7.egg', which is parsed as a LegacyVersion and therefore breaks the numeric comparison. this is a workaround to allow the sanity check to continue, by comparing just the numeric part of the version string. --- src/leap/keymanager/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index c2d7409..56633c5 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -24,13 +24,20 @@ try: from gnupg.gnupg import GPGUtilities assert(GPGUtilities) # pyflakes happy from gnupg import __version__ as _gnupg_version + if '-' in _gnupg_version: + # avoid Parsing it as LegacyVersion, get just + # the release numbers: + _gnupg_version = _gnupg_version.split('-')[0] from pkg_resources import parse_version + # We need to make sure that we're not colliding with the infamous + # python-gnupg assert(parse_version(_gnupg_version) >= parse_version('1.4.0')) except (ImportError, AssertionError): print "*******" print "Ooops! It looks like there is a conflict in the installed version " print "of gnupg." + print "GNUPG_VERSION:", _gnupg_version print print "Disclaimer: Ideally, we would need to work a patch and propose the " print "merge to upstream. But until then do: " -- cgit v1.2.3 From 5c967b8c236900f93147b7d286a080d650a9e5d0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 29 Jul 2015 16:27:54 -0400 Subject: [style] pep8 --- src/leap/keymanager/__init__.py | 15 ++++++--------- src/leap/keymanager/tests/test_keymanager.py | 3 +-- src/leap/keymanager/tests/test_validation.py | 3 +-- 3 files changed, 8 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 56633c5..282ff48 100644 --- a/src/leap/keymanager/__init__.py +++ b/src/leap/keymanager/__init__.py @@ -71,10 +71,12 @@ from leap.keymanager.keys import ( KEYMANAGER_KEY_TAG, TAGS_PRIVATE_INDEX, ) -from leap.keymanager.openpgp import ( - OpenPGPKey, - OpenPGPScheme, -) +from leap.keymanager.openpgp import OpenPGPKey, OpenPGPScheme + +from ._version import get_versions + +__version__ = get_versions()['version'] +del get_versions logger = logging.getLogger(__name__) @@ -828,8 +830,3 @@ def _get_domain(url): :rtype: str """ return urlparse(url).hostname - - -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions diff --git a/src/leap/keymanager/tests/test_keymanager.py b/src/leap/keymanager/tests/test_keymanager.py index 08d3750..a12cac0 100644 --- a/src/leap/keymanager/tests/test_keymanager.py +++ b/src/leap/keymanager/tests/test_keymanager.py @@ -388,9 +388,8 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): sign=ADDRESS, fetch_remote=False)) return self.assertFailure(d, KeyNotFound) - -import unittest if __name__ == "__main__": + import unittest unittest.main() # key 0F91B402: someone@somedomain.org diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 0c1d155..561ca50 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -339,7 +339,6 @@ X2+l7IOSt+31KQCBFN/VmhTySJOVQC1d2A56lSH2c/DWVClji+x3suzn -----END PGP PUBLIC KEY BLOCK----- """ - -import unittest if __name__ == "__main__": + import unittest unittest.main() -- cgit v1.2.3 From 3fab338ef4e1ae0efcfaee455ae04881aa013083 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Fri, 24 Jul 2015 16:24:13 -0300 Subject: [style] Fixed pep8 warnings Fixed pep8 warnings to prepare the keymanager for CI --- src/leap/keymanager/__init__.py | 1 + src/leap/keymanager/_version.py | 19 ++++++++----------- src/leap/keymanager/openpgp.py | 7 +++++-- src/leap/keymanager/tests/test_validation.py | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/__init__.py b/src/leap/keymanager/__init__.py index 282ff48..999b53c 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 sys +from ._version import get_versions try: from gnupg.gnupg import GPGUtilities diff --git a/src/leap/keymanager/_version.py b/src/leap/keymanager/_version.py index 28fca96..5153a9b 100644 --- a/src/leap/keymanager/_version.py +++ b/src/leap/keymanager/_version.py @@ -1,5 +1,3 @@ - -IN_LONG_VERSION_PY = True # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (build by setup.py sdist) and build @@ -10,12 +8,15 @@ IN_LONG_VERSION_PY = True # versioneer-0.7+ (https://github.com/warner/python-versioneer) # these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - - import subprocess import sys +import re +import os.path + +IN_LONG_VERSION_PY = True + +git_refnames = "$Format:%d$" +git_full = "$Format:%H$" def run_command(args, cwd=None, verbose=False): @@ -38,10 +39,6 @@ def run_command(args, cwd=None, verbose=False): return stdout -import re -import os.path - - def get_expanded_variables(versionfile_source): # the code embedded in _version.py can just fetch the value of these # variables. When used from setup.py, we don't want to import @@ -86,7 +83,7 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 794a0ec..5d91dc9 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -114,8 +114,11 @@ class TempGPGWrapper(object): publkeys = filter( lambda pubkey: pubkey.key_id not in privids, publkeys) - listkeys = lambda: self._gpg.list_keys() - listsecretkeys = lambda: self._gpg.list_keys(secret=True) + def listkeys(): + return self._gpg.list_keys() + + def listsecretkeys(): + return self._gpg.list_keys(secret=True) self._gpg = GPG(binary=self._gpgbinary, homedir=tempfile.mkdtemp()) diff --git a/src/leap/keymanager/tests/test_validation.py b/src/leap/keymanager/tests/test_validation.py index 561ca50..ddf1170 100644 --- a/src/leap/keymanager/tests/test_validation.py +++ b/src/leap/keymanager/tests/test_validation.py @@ -18,6 +18,7 @@ Tests for the Validation Levels """ +import unittest from datetime import datetime from twisted.internet.defer import inlineCallbacks -- cgit v1.2.3 From d12f883eed4a8205440e8a4422605be1a1cfe914 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Mon, 3 Aug 2015 17:37:09 -0300 Subject: [style] Re-added lambdas to openpgp on keymanager --- src/leap/keymanager/openpgp.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index 5d91dc9..bbdedb2 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -111,14 +111,10 @@ class TempGPGWrapper(object): # and we want to count the keys afterwards. privids = map(lambda privkey: privkey.key_id, privkeys) - publkeys = filter( - lambda pubkey: pubkey.key_id not in privids, publkeys) + publkeys = filter(lambda pubkey: pubkey.key_id not in privids, publkeys) - def listkeys(): - return self._gpg.list_keys() - - def listsecretkeys(): - return self._gpg.list_keys(secret=True) + listkeys = lambda: self._gpg.list_keys() + listsecretkeys = lambda: self._gpg.list_keys(secret=True) self._gpg = GPG(binary=self._gpgbinary, homedir=tempfile.mkdtemp()) -- cgit v1.2.3 From 711fce95bf8f65c0f5a4eaddb4023eda29bc16ad Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Aug 2015 19:22:14 -0400 Subject: [style] pep8 fix --- src/leap/keymanager/openpgp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/keymanager/openpgp.py b/src/leap/keymanager/openpgp.py index bbdedb2..794a0ec 100644 --- a/src/leap/keymanager/openpgp.py +++ b/src/leap/keymanager/openpgp.py @@ -111,7 +111,8 @@ class TempGPGWrapper(object): # and we want to count the keys afterwards. privids = map(lambda privkey: privkey.key_id, privkeys) - publkeys = filter(lambda pubkey: pubkey.key_id not in privids, publkeys) + publkeys = filter( + lambda pubkey: pubkey.key_id not in privids, publkeys) listkeys = lambda: self._gpg.list_keys() listsecretkeys = lambda: self._gpg.list_keys(secret=True) -- cgit v1.2.3