Verified Commit fb7eef01 by meskio

[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
parent f6c71494
Pipeline #7070 failed with stages
in 96 minutes 34 seconds
......@@ -7,6 +7,7 @@ Changelog
Features
~~~~~~~~
- `#8217 <https://0xacab.org/leap/bitmask-dev/issues/8217>`_: renew OpenPGP keys before they expire.
- Set a windows title, so that Bitmask windows can be programmatically manipulated.
Misc
......
......@@ -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
---------------------------------------
......
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -191,6 +191,37 @@ class KeyManagerKeyManagementTestCase(KeyManagerWithSoledadTestCase):
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()
yield km._openpgp.put_raw_key(self.get_private_binary_key(), ADDRESS)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment