From 5243c7eb4c58a4419da2103af8c1e4004f01053b Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 24 Oct 2017 11:53:10 +0200 Subject: [feat] add fetch key by fingerprint to keymanager - Resolves: #9023 --- src/leap/bitmask/cli/keys.py | 21 ++++++++++++++ src/leap/bitmask/core/dispatcher.py | 10 +++++++ src/leap/bitmask/core/mail_services.py | 9 ++++++ src/leap/bitmask/keymanager/__init__.py | 37 +++++++++++++++++++++++++ tests/integration/keymanager/test_keymanager.py | 37 ++++++++++++++++++++++++- ui/app/lib/bitmask.js | 13 +++++++++ 6 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/cli/keys.py b/src/leap/bitmask/cli/keys.py index 82de3868..ab73bfff 100644 --- a/src/leap/bitmask/cli/keys.py +++ b/src/leap/bitmask/cli/keys.py @@ -41,6 +41,7 @@ SUBCOMMANDS: list List all known keys export Export a given key + fetch Fetch key by fingerprint insert Insert a key to the key storage delete Delete a key from the key storage '''.format(name=command.appname) @@ -97,6 +98,26 @@ SUBCOMMANDS: return self._send(self._print_key) + def fetch(self, raw_args): + parser = argparse.ArgumentParser( + description='Bitmask fetch key by fingerprint', + prog='%s %s %s' % tuple(sys.argv[:3])) + parser.add_argument('-u', '--userid', default='', + help='Select the userid of the keyring') + parser.add_argument('address', nargs=1, + help='email address to pin to the key') + parser.add_argument('fingerprint', nargs=1, + help='fingerprint to fetch of the key') + subargs = parser.parse_args(raw_args) + + userid = subargs.userid + if not userid: + userid = self.cfg.get('bonafide', 'active', default='') + self.data += ['fetch', userid, subargs.address[0], + subargs.fingerprint[0]] + + return self._send(self._print_key) + def insert(self, raw_args): parser = argparse.ArgumentParser( description='Bitmask import key', diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index 91168d87..f2b3e8ca 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -362,6 +362,16 @@ class KeysCmd(SubCommand): return service.do_export(uid, address, private, fetch_remote) + @register_method('dict') + def do_FETCH(self, service, *parts, **kw): + if len(parts) < 5: + raise ValueError("An email address is needed") + uid = parts[2] + address = parts[3] + fingerprint = parts[4] + + return service.do_fetch(uid, address, fingerprint) + @register_method('dict') def do_INSERT(self, service, *parts, **kw): if len(parts) < 6: diff --git a/src/leap/bitmask/core/mail_services.py b/src/leap/bitmask/core/mail_services.py index 31c27364..23105c7a 100644 --- a/src/leap/bitmask/core/mail_services.py +++ b/src/leap/bitmask/core/mail_services.py @@ -422,6 +422,15 @@ class KeymanagerService(HookableService): d.addCallback(lambda key: dict(key)) return d + def do_fetch(self, userid, address, fingerprint): + km = self._container.get_instance(userid) + if km is None: + return defer.fail(ValueError("User " + userid + " has no active " + "keymanager")) + d = km.fetch_key_fingerprint(address, fingerprint) + d.addCallback(lambda key: dict(key)) + return d + def do_insert(self, userid, address, rawkey, validation='Fingerprint'): km = self._container.get_instance(userid) if km is None: diff --git a/src/leap/bitmask/keymanager/__init__.py b/src/leap/bitmask/keymanager/__init__.py index 45b7e582..e3ff3b87 100644 --- a/src/leap/bitmask/keymanager/__init__.py +++ b/src/leap/bitmask/keymanager/__init__.py @@ -261,6 +261,43 @@ class KeyManager(object): d.addCallbacks(key_found, key_not_found) return d + @defer.inlineCallbacks + def fetch_key_fingerprint(self, address, fingerprint): + """ + Fetch a key from the key servers by fingerprint. + + It will replace any key assigned to the address in the keyring and have + validation level Fingerprint. + + :param address: The address bound to the key. + :type address: str + :param fingerprint: The fingerprint of the key to fetch. + :type fingerprint: str + + :return: A Deferred which fires with an EncryptionKey fetched, + or which fails with KeyNotFound if no key was found in the + keyserver for this fingerprint. + :rtype: Deferred + """ + key_data = yield self._nicknym.fetch_key_with_fingerprint(fingerprint) + key, _ = self._openpgp.parse_key(key_data, address) + key.validation = ValidationLevels.Fingerprint + + if key.fingerprint != fingerprint: + raise keymanager_errors.KeyNotFound("Got wrong fingerprint") + + try: + old_key = yield self._openpgp.get_key(address) + if old_key.fingerprint == key.fingerprint: + key.last_audited_at = old_key.last_audited_at + key.encr_used = old_key.encr_used + key.sign_used = old_key.sign_used + except keymanager_errors.KeyNotFound: + pass + + yield self._openpgp.put_key(key) + defer.returnValue(key) + def get_all_keys(self, private=False): """ Return all keys stored in local database. diff --git a/tests/integration/keymanager/test_keymanager.py b/tests/integration/keymanager/test_keymanager.py index 981fc6ba..1e836a5d 100644 --- a/tests/integration/keymanager/test_keymanager.py +++ b/tests/integration/keymanager/test_keymanager.py @@ -47,6 +47,8 @@ from common import ( PUBLIC_KEY_2, PRIVATE_KEY, PRIVATE_KEY_2, + NEW_PUB_KEY, + OLD_AND_NEW_KEY_ADDRESS ) @@ -211,7 +213,7 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): pubkey = yield km.get_key(ADDRESS, fetch_remote=False) # setup expected args data = urllib.urlencode({ - km.PUBKEY_KEY: pubkey.key_data, + km._nicknym.PUBKEY_KEY: pubkey.key_data, }) headers = {'Authorization': [str('Token token=%s' % token)]} headers['Content-Type'] = ['application/x-www-form-urlencoded'] @@ -608,6 +610,39 @@ class KeyManagerCryptoTestCase(KeyManagerWithSoledadTestCase): fetch_remote=False)) return self.assertFailure(d, errors.KeyNotFound) + @defer.inlineCallbacks + def test_fetch_key_fingerprint(self): + km = self._key_manager(user=ADDRESS_2) + km._nicknym.fetch_key_with_fingerprint = mock.Mock( + return_value=defer.succeed(PUBLIC_KEY)) + yield km.fetch_key_fingerprint(ADDRESS, KEY_FINGERPRINT) + key = yield km.get_key(ADDRESS, fetch_remote=False) + self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + + def test_fetch_key_fingerprint_wrong_fp(self): + km = self._key_manager(user=ADDRESS_2) + km._nicknym.fetch_key_with_fingerprint = mock.Mock( + return_value=defer.succeed(NEW_PUB_KEY)) + d = km.fetch_key_fingerprint(OLD_AND_NEW_KEY_ADDRESS, KEY_FINGERPRINT) + return self.assertFailure(d, errors.KeyNotFound) + + @defer.inlineCallbacks + def test_fetch_key_fingerprint_keep_usage(self): + km = self._key_manager(user=ADDRESS_2) + key, _ = km._openpgp.parse_key(PUBLIC_KEY, ADDRESS) + key.sign_used = True + yield km.put_key(key) + + km._nicknym.fetch_key_with_fingerprint = mock.Mock( + return_value=defer.succeed(PUBLIC_KEY)) + yield km.fetch_key_fingerprint(ADDRESS, KEY_FINGERPRINT) + + key = yield km.get_key(ADDRESS, fetch_remote=False) + self.assertEqual(key.fingerprint, KEY_FINGERPRINT) + self.assertTrue(key.sign_used) + self.assertFalse(key.encr_used) + + if __name__ == "__main__": import unittest unittest.main() diff --git a/ui/app/lib/bitmask.js b/ui/app/lib/bitmask.js index 37d09979..31a1e938 100644 --- a/ui/app/lib/bitmask.js +++ b/ui/app/lib/bitmask.js @@ -427,6 +427,19 @@ var bitmask = function(){ return call(['keys', 'export', uid, address, privstr]); }, + /** + * Fetch key by fingerprint + *, + * @param {string} uid The uid of the keyring. + * @param {string} address The email address of the key. + * @param {string} fingerprint The key fingerprnit. + * + * @return {Promise} The key + */ + fetch: function(uid, address, fingerprint) { + return call(['keys', 'fetch', address, fingerprint]); + }, + /** * Insert key * -- cgit v1.2.3