From 4bcd703462ce747ab010525f6ff0a4ada35ed946 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Tue, 8 Dec 2020 13:32:48 +0000 Subject: [PATCH 1/2] client: update expiration check to match spec The specification, as of 1.0.16, describes an update expiration check as: > The expiration timestamp in the trusted $ROLE metadata file MUST be higher than the fixed update expiration time. Having done some research into how other security providers are comparing expiration equivalents (i.e. OpenSSL x509 certificate checking code, and GnuPG expiration checks), and how other TUF implementations are performing the same check (rust-tuf, go-tuf), we came to a consensus that the correct way to implement expiration comparisons is: expiration <= now Where: expiration: is the metadata's expiration datetime now: is the current system time, or the fixed notion of time in the detailed client workflow (introduced in 1.0.16 of the spec) Fixes #1231 Signed-off-by: Joshua Lock --- tuf/client/updater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tuf/client/updater.py b/tuf/client/updater.py index d9ffb1f0..9ada0974 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -2266,7 +2266,7 @@ def _ensure_not_expired(self, metadata_object, metadata_rolename): expires_timestamp = tuf.formats.datetime_to_unix_timestamp(expires_datetime) current_time = int(time.time()) - if expires_timestamp < current_time: + if expires_timestamp <= current_time: message = 'Metadata '+repr(metadata_rolename)+' expired on ' + \ expires_datetime.ctime() + ' (UTC).' raise tuf.exceptions.ExpiredMetadataError(message) From fccd0786344079ed97f0d0a57b2cfc07e7b0a556 Mon Sep 17 00:00:00 2001 From: Joshua Lock Date: Wed, 9 Dec 2020 21:58:00 +0000 Subject: [PATCH 2/2] Update tests for client expiration check Add a test to ensure that metadata expires at the expiration time, not after it. This tests the change to the updater introduced in 4bcd703 Signed-off-by: Joshua Lock --- tests/test_updater.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/test_updater.py b/tests/test_updater.py index bf66cc5c..f8d6379d 100755 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -60,6 +60,11 @@ import unittest import json +if sys.version_info >= (3, 3): + import unittest.mock as mock +else: + import mock + import tuf import tuf.exceptions import tuf.log @@ -670,14 +675,31 @@ def test_2__ensure_not_expired(self): root_metadata = self.repository_updater.metadata['current']['root'] self.repository_updater._ensure_not_expired(root_metadata, 'root') - # 'tuf.exceptions.ExpiredMetadataError' should be raised in this next test condition, - # because the expiration_date has expired by 10 seconds. + # Metadata with an expiration time in the future should, of course, not + # count as expired + expires = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 10)) + expires = expires.isoformat() + 'Z' + root_metadata['expires'] = expires + self.assertTrue(tuf.formats.ROOT_SCHEMA.matches(root_metadata)) + self.repository_updater._ensure_not_expired(root_metadata, 'root') + + # Metadata that expires at the exact current time is considered expired + expire_time = int(time.time()) + expires = \ + tuf.formats.unix_timestamp_to_datetime(expire_time).isoformat()+'Z' + root_metadata['expires'] = expires + mock_time = mock.Mock() + mock_time.return_value = expire_time + self.assertTrue(tuf.formats.ROOT_SCHEMA.matches(root_metadata)) + with mock.patch('time.time', mock_time): + self.assertRaises(tuf.exceptions.ExpiredMetadataError, + self.repository_updater._ensure_not_expired, + root_metadata, 'root') + + # Metadata that expires in the past is considered expired expires = tuf.formats.unix_timestamp_to_datetime(int(time.time() - 10)) expires = expires.isoformat() + 'Z' root_metadata['expires'] = expires - - # Ensure the 'expires' value of the root file is valid by checking the - # the formats of the 'root.json' object. self.assertTrue(tuf.formats.ROOT_SCHEMA.matches(root_metadata)) self.assertRaises(tuf.exceptions.ExpiredMetadataError, self.repository_updater._ensure_not_expired,