From fb7eef011cc672f1884bcfcd4c859a549d8f3e49 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Tue, 31 Oct 2017 11:02:00 +0100 Subject: [feat] extend the expiration of private keys if needed Check on every fetch of the private key if the expiration is less than two months before it expire. And extend the expiration if needed. - Resolves: #8217 --- docs/changelog.rst | 1 + docs/keymanager/index.rst | 10 ++++++++ src/leap/bitmask/keymanager/__init__.py | 9 +++++++ src/leap/bitmask/keymanager/keys.py | 2 ++ tests/integration/keymanager/test_keymanager.py | 33 ++++++++++++++++++++++++- 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 20c6b7d6..4b2558a2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,7 @@ Changelog Features ~~~~~~~~ +- `#8217 `_: renew OpenPGP keys before they expire. - Set a windows title, so that Bitmask windows can be programmatically manipulated. Misc diff --git a/docs/keymanager/index.rst b/docs/keymanager/index.rst index 5bc66b6f..033292d5 100644 --- a/docs/keymanager/index.rst +++ b/docs/keymanager/index.rst @@ -47,6 +47,16 @@ Currently Bitmask can discover new public keys from different sources: Other methods are planned to be added in the future, like discovery from signatures in emails, headers (autocrypt spec) or other kind of key servers. +Key expiration dates +-------------------- + +KeyManager creates the OpenPGP key with the default expiration of gnupg, that currently is 2 years after the key creation. We want keys with expiration date, to be able to roll new ones if the key material get lost. + +We will reduce the default expiration lenght in the future. That will require the rest of OpenPGP ecosystem to have good refresh mechanisms for keys, situation that is improving in the last years. + +KeyManager extends one year the expiration date automatically two months before the key gets expired. + + Implementation: using Soledad Documents --------------------------------------- diff --git a/src/leap/bitmask/keymanager/__init__.py b/src/leap/bitmask/keymanager/__init__.py index c1095877..dc6b88d3 100644 --- a/src/leap/bitmask/keymanager/__init__.py +++ b/src/leap/bitmask/keymanager/__init__.py @@ -254,6 +254,13 @@ class KeyManager(object): self.log.debug('Getting key for %s' % (address,)) emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) + @defer.inlineCallbacks + def maybe_extend_expiration(key): + if key.needs_renewal(): + key = yield self._openpgp.expire(key, expiration_time='1y') + yield self.send_key() + defer.returnValue(key) + def key_found(key): emit_async(catalog.KEYMANAGER_KEY_FOUND, address) return key @@ -288,6 +295,8 @@ class KeyManager(object): # return key if it exists in local database d = self._openpgp.get_key(address, private=private) + if private: + d.addCallback(maybe_extend_expiration) d.addCallbacks(ensure_valid, key_not_found) return d diff --git a/src/leap/bitmask/keymanager/keys.py b/src/leap/bitmask/keymanager/keys.py index 0f68c06b..efc6f925 100644 --- a/src/leap/bitmask/keymanager/keys.py +++ b/src/leap/bitmask/keymanager/keys.py @@ -338,6 +338,8 @@ class OpenPGPKey(object): :return: True if the current date is within the threshold :rtype: Boolean """ + if self.expiry_date is None: + return False days_till_expiry = (self.expiry_date - datetime.now()) return days_till_expiry.days < pre_expiration_threshold diff --git a/tests/integration/keymanager/test_keymanager.py b/tests/integration/keymanager/test_keymanager.py index 8ed70bdf..544a1a18 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, date +from datetime import datetime, date, timedelta from twisted.internet import defer from twisted.trial import unittest @@ -190,6 +190,37 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase): key.fingerprint.lower(), DIFFERENT_KEY_FPR.lower()) self.assertFalse(key.private) + @defer.inlineCallbacks + def test_get_expired_private_key_extends_expiration(self): + token = "mytoken" + km = self._key_manager(user=ADDRESS_EXPIRING, token=token) + km._nicknym.put_key = mock.Mock(return_value=defer.succeed('')) + + yield km.put_raw_key(PRIVATE_EXPIRING_KEY, ADDRESS_EXPIRING) + key = yield km.get_key(ADDRESS_EXPIRING, private=True) + + def assert_expiration_date(key): + expected = datetime.now() + timedelta(days=365) + self.assertTrue(expected - key.expiry_date < timedelta(days=1)) + + # check that the right key is returned with the expiration extended + self.assertTrue(key.private) + assert_expiration_date(key) + self.assertTrue(km._nicknym.put_key.called) + key_sent_data = km._nicknym.put_key.call_args[0][1] + key_sent_pub, key_sent_priv = km._openpgp.parse_key(key_sent_data) + self.assertTrue(key_sent_priv is None) + assert_expiration_date(key_sent_pub) + + # check that the key in the keyring has the right expiration and + # a second get key doesn't try to extend the expiration again + km._nicknym.put_key = mock.Mock(return_value=defer.succeed('')) + pubkey = yield km.get_key(ADDRESS_EXPIRING) + privkey = yield km.get_key(ADDRESS_EXPIRING, private=True) + self.assertFalse(km._nicknym.put_key.called) + assert_expiration_date(privkey) + assert_expiration_date(pubkey) + @defer.inlineCallbacks def test_get_public_key_with_binary_private_key(self): km = self._key_manager() -- cgit v1.2.3