From e5c567d79557eecbd708f4fabe8d04c92cf66ea1 Mon Sep 17 00:00:00 2001 From: NavaL Date: Fri, 18 Nov 2016 20:22:14 +0100 Subject: [feat] first phase of key regeneration - if current key pair is expired, it'll be extended for a day first - new key pair will be signed by the old key --- src/leap/bitmask/keymanager/__init__.py | 19 +++++++++++ src/leap/bitmask/keymanager/keys.py | 3 ++ src/leap/bitmask/keymanager/openpgp.py | 42 +++++++++++++++++++++++-- tests/integration/keymanager/test_keymanager.py | 23 +++++++------- tox.ini | 2 +- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/leap/bitmask/keymanager/__init__.py b/src/leap/bitmask/keymanager/__init__.py index 03ade634..483c7e51 100644 --- a/src/leap/bitmask/keymanager/__init__.py +++ b/src/leap/bitmask/keymanager/__init__.py @@ -330,6 +330,25 @@ class KeyManager(object): d.addCallback(signal_finished) return d + def regenerate_key(self): + """ + Regenerate a key bound to the user's address. + + :return: A Deferred which fires with the generated EncryptionKey. + :rtype: Deferred + """ + + def signal_finished(key): + emit_async( + catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) + return key + self.log.info('Regenerating key for %s.' % self._address) + emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) + + d = self._openpgp.regenerate_key(self._address) + d.addCallback(signal_finished) + return d + # # Setters/getters # diff --git a/src/leap/bitmask/keymanager/keys.py b/src/leap/bitmask/keymanager/keys.py index 54609c05..d37c6d21 100644 --- a/src/leap/bitmask/keymanager/keys.py +++ b/src/leap/bitmask/keymanager/keys.py @@ -283,6 +283,9 @@ class OpenPGPKey(object): value = str(value) return key, value + def has_expired(self): + return self.expiry_date < datetime.now() + def __iter__(self): return self diff --git a/src/leap/bitmask/keymanager/openpgp.py b/src/leap/bitmask/keymanager/openpgp.py index 99e5bc72..d0acca54 100644 --- a/src/leap/bitmask/keymanager/openpgp.py +++ b/src/leap/bitmask/keymanager/openpgp.py @@ -22,10 +22,8 @@ Infrastructure for using OpenPGP keys in Key Manager. import os import re import tempfile -import traceback import io - from datetime import datetime from multiprocessing import cpu_count from twisted.internet import defer @@ -162,6 +160,41 @@ class OpenPGPScheme(object): # # Keys management # + @defer.inlineCallbacks + def regenerate_key(self, address): + """ + Deactivate Current keypair, + Generate a new OpenPGP keypair bound to C{address}, + and sign the new key with the old key. + + :param address: The address bound to the key. + :type address: str + + :return: A Deferred which fires with the new key bound to address. + :rtype: Deferred + """ + leap_assert(is_address(address), 'Not an user address: %s' % address) + current_sec_key = yield self.get_key(address, private=True) + with TempGPGWrapper([current_sec_key], self._gpgbinary) as gpg: + if current_sec_key.has_expired(): + temporary_extension_period = '1' # extend for 1 extra day + gpg.extend_key(current_sec_key.fingerprint, + validity=temporary_extension_period) + yield self.unactivate_key(address) + new_key = yield self.gen_key(address) + gpg.import_keys(new_key.key_data) + key_signing = yield from_thread(gpg.sign_key, new_key.fingerprint) + if key_signing.status == 'ok': + fetched_keys = gpg.list_keys(secret=False) + fetched_key = filter(lambda k: k['fingerprint'] == + new_key.fingerprint, fetched_keys)[0] + key_data = gpg.export_keys(new_key.fingerprint, secret=False) + renewed_key = self._build_key_from_gpg( + fetched_key, + key_data, + new_key.address) + yield self.put_key(renewed_key) + defer.returnValue(new_key) def gen_key(self, address): """ @@ -382,6 +415,7 @@ class OpenPGPScheme(object): :return: A Deferred which fires when the key is in the storage. :rtype: Deferred """ + def merge_and_put((keydoc, activedoc)): if not keydoc: return put_new_key(activedoc) @@ -440,6 +474,7 @@ class OpenPGPScheme(object): (keydoc, activedoc) or None if it does not exist. :rtype: Deferred """ + def get_key_from_active_doc(activedoc): if not activedoc: return (None, None) @@ -654,7 +689,7 @@ class OpenPGPScheme(object): yield self.put_key(renewed_key) defer.returnValue(renewed_key) except Exception as e: - logger.warn('Failed to Extend Key: %s expiration date.' % str(e)) + log.warn('Failed to Extend Key: %s expiration date.' % str(e)) raise errors.KeyExpiryExtensionError(str(e)) @defer.inlineCallbacks @@ -844,6 +879,7 @@ class OpenPGPScheme(object): the deletions are completed :rtype: Deferred """ + def log_key_doc(doc): self.log.error("\t%s: %s" % (doc.content[KEY_UIDS_KEY], doc.content[KEY_FINGERPRINT_KEY])) diff --git a/tests/integration/keymanager/test_keymanager.py b/tests/integration/keymanager/test_keymanager.py index 466414a1..4e6d62c8 100644 --- a/tests/integration/keymanager/test_keymanager.py +++ b/tests/integration/keymanager/test_keymanager.py @@ -21,7 +21,7 @@ import json import urllib import tempfile import pkg_resources -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from twisted.internet import defer from twisted.trial import unittest @@ -554,26 +554,27 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): km.send_key.assert_called_once_with() @defer.inlineCallbacks - def test_keymanager_extend_key_expiry_date_for_key_pair(self): + def test_key_regenerate_gets_new_expiry_date_and_signed_by_old_key(self): km = self._key_manager(user=ADDRESS_EXPIRING) yield km._openpgp.put_raw_key(PRIVATE_EXPIRING_KEY, ADDRESS_EXPIRING) - key = yield km.get_key(ADDRESS_EXPIRING) + old_key = yield km.get_key(ADDRESS_EXPIRING) - yield km.extend_key(validity='1w') + new_key = yield km.regenerate_key() - new_expiry_date = datetime.strptime( - KEY_EXPIRING_CREATION_DATE, '%Y-%m-%d') - new_expiry_date += timedelta(weeks=1) + today = datetime.now() + new_expiry_date = date(today.year + 1, today.month, today.day) renewed_public_key = yield km.get_key(ADDRESS_EXPIRING) renewed_private_key = yield km.get_key(ADDRESS_EXPIRING, private=True) - self.assertEqual(new_expiry_date.date(), + self.assertEqual(new_expiry_date, renewed_public_key.expiry_date.date()) - self.assertEqual(new_expiry_date.date(), + self.assertEqual(new_expiry_date, renewed_private_key.expiry_date.date()) - self.assertEqual(key.fingerprint, renewed_public_key.fingerprint) - self.assertEqual(key.fingerprint, renewed_private_key.fingerprint) + self.assertNotEqual(old_key.fingerprint, + renewed_public_key.fingerprint) + self.assertEqual(new_key.fingerprint, renewed_public_key.fingerprint) + self.assertIn(old_key.fingerprint[-16:], renewed_public_key.signatures) @defer.inlineCallbacks def test_key_extension_with_invalid_period_throws_exception(self): diff --git a/tox.ini b/tox.ini index fbcc8548..6d181452 100644 --- a/tox.ini +++ b/tox.ini @@ -50,7 +50,7 @@ commands = py.test -k 'not bench' --pep8 -x {posargs} deps = -r{toxinidir}/pkg/requirements-testing.pip - -egit+https://github.com/pixelated/python-gnupg.git@feat_extend_key#egg=gnupg + -egit+https://github.com/pixelated/python-gnupg.git@key_extension_and_sign#egg=gnupg -e../leap_commondev -e../soledad setenv = -- cgit v1.2.3