Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Speedup indefinite freeze tests #1194

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 59 additions & 79 deletions tests/test_indefinite_freeze_attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from __future__ import division
from __future__ import unicode_literals

import datetime
import os
import time
import tempfile
Expand All @@ -53,6 +54,11 @@
import unittest
import sys

if sys.version_info >= (3, 3):
import unittest.mock as mock
else:
import mock

import tuf.formats
import tuf.log
import tuf.client.updater as updater
Expand Down Expand Up @@ -265,8 +271,7 @@ def test_with_tuf(self):
# Load the repository
repository = repo_tool.load_repository(self.repository_directory)

# Load the timestamp and snapshot keys, since we will be signing a new
# timestamp and a new snapshot file.
# Load the snapshot and timestamp keys
key_file = os.path.join(self.keystore_directory, 'timestamp_key')
timestamp_private = repo_tool.import_ed25519_privatekey_from_file(key_file,
'password')
Expand All @@ -276,17 +281,11 @@ def test_with_tuf(self):
'password')
repository.snapshot.load_signing_key(snapshot_private)

# Expire snapshot in 10s. This should be far enough into the future that we
# haven't reached it before the first refresh validates timestamp expiry.
# We want a successful refresh before expiry, then a second refresh after
# expiry (which we then expect to raise an exception due to expired
# metadata).
expiry_time = time.time() + 10
datetime_object = tuf.formats.unix_timestamp_to_datetime(int(expiry_time))

repository.snapshot.expiration = datetime_object

# Now write to the repository.
# sign snapshot with expiry in near future (earlier than e.g. timestamp)
expiry = int(time.time() + 60*60)
repository.snapshot.expiration = tuf.formats.unix_timestamp_to_datetime(
expiry)
repository.mark_dirty(['snapshot', 'timestamp'])
repository.writeall()

# And move the staged metadata to the "live" metadata.
Expand All @@ -297,30 +296,24 @@ def test_with_tuf(self):
# Refresh metadata on the client. For this refresh, all data is not expired.
logger.info('Test: Refreshing #1 - Initial metadata refresh occurring.')
self.repository_updater.refresh()
logger.info('Test: Refreshed #1 - Initial metadata refresh completed '
'successfully. Now sleeping until snapshot metadata expires.')

# Sleep until expiry_time ('repository.snapshot.expiration')
time.sleep(max(0, expiry_time - time.time() + 1))

logger.info('Test: Refreshing #2 - Now trying to refresh again after local'
' snapshot expiry.')

try:
self.repository_updater.refresh() # We expect this to fail!
logger.info('Test: Refreshing #2 - refresh after local snapshot expiry.')

except tuf.exceptions.ExpiredMetadataError:
logger.info('Test: Refresh #2 - failed as expected. Expired local'
' snapshot case generated a tuf.exceptions.ExpiredMetadataError'
' exception as expected. Test pass.')
# mock current time to one second after snapshot expiry
mock_time = mock.Mock()
mock_time.return_value = expiry + 1
with mock.patch('time.time', mock_time):
try:
self.repository_updater.refresh() # We expect this to fail!

# I think that I only expect tuf.ExpiredMetadata error here. A
# NoWorkingMirrorError indicates something else in this case - unavailable
# repo, for example.
else:
except tuf.exceptions.ExpiredMetadataError:
logger.info('Test: Refresh #2 - failed as expected. Expired local'
' snapshot case generated a tuf.exceptions.ExpiredMetadataError'
' exception as expected. Test pass.')

self.fail('TUF failed to detect expired stale snapshot metadata. Freeze'
' attack successful.')
else:
self.fail('TUF failed to detect expired stale snapshot metadata. Freeze'
' attack successful.')



Expand Down Expand Up @@ -355,7 +348,7 @@ def test_with_tuf(self):
# We cannot set the timestamp expiration with
# 'repository.timestamp.expiration = ...' with already-expired timestamp
# metadata because of consistency checks that occur during that assignment.
expiry_time = time.time() + 1
expiry_time = time.time() + 60*60
datetime_object = tuf.formats.unix_timestamp_to_datetime(int(expiry_time))
repository.timestamp.expiration = datetime_object
repository.writeall()
Expand All @@ -365,29 +358,21 @@ def test_with_tuf(self):
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
os.path.join(self.repository_directory, 'metadata'))

# Wait just long enough for the timestamp metadata (which is now both on
# the repository and on the client) to expire.
time.sleep(max(0, expiry_time - time.time() + 1))
# mock current time to one second after timestamp expiry
mock_time = mock.Mock()
mock_time.return_value = expiry_time + 1
with mock.patch('time.time', mock_time):
try:
self.repository_updater.refresh() # We expect NoWorkingMirrorError.

# Try to refresh top-level metadata on the client. Since we're already past
# 'repository.timestamp.expiration', the TUF client is expected to detect
# that timestamp metadata is outdated and refuse to continue the update
# process.
try:
self.repository_updater.refresh() # We expect NoWorkingMirrorError.
except tuf.exceptions.NoWorkingMirrorError as e:
# Make sure the contained error is ExpiredMetadataError
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))

except tuf.exceptions.NoWorkingMirrorError as e:
# NoWorkingMirrorError indicates that we did not find valid, unexpired
# metadata at any mirror. That exception class preserves the errors from
# each mirror. We now assert that for each mirror, the particular error
# detected was that metadata was expired (the timestamp we manually
# expired).
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))

else:
self.fail('TUF failed to detect expired, stale timestamp metadata.'
' Freeze attack successful.')
else:
self.fail('TUF failed to detect expired, stale timestamp metadata.'
' Freeze attack successful.')



Expand Down Expand Up @@ -416,8 +401,8 @@ def test_with_tuf(self):
# Set ts to expire in 1 month.
ts_expiry_time = time.time() + 2630000

# Set snapshot to expire in 1 second.
snapshot_expiry_time = time.time() + 1
# Set snapshot to expire in 1 hour.
snapshot_expiry_time = time.time() + 60*60

ts_datetime_object = tuf.formats.unix_timestamp_to_datetime(
int(ts_expiry_time))
Expand All @@ -432,28 +417,23 @@ def test_with_tuf(self):
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
os.path.join(self.repository_directory, 'metadata'))

# Wait just long enough for the Snapshot metadata (which is now on the
# repository) to expire.
time.sleep(max(0, snapshot_expiry_time - time.time() + 1))


try:
# We expect the following refresh() to raise a NoWorkingMirrorError.
self.repository_updater.refresh()

except tuf.exceptions.NoWorkingMirrorError as e:
# NoWorkingMirrorError indicates that we did not find valid, unexpired
# metadata at any mirror. That exception class preserves the errors from
# each mirror. We now assert that for each mirror, the particular error
# detected was that metadata was expired (the Snapshot we manually
# expired).
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))
self.assertTrue(mirror_url.endswith('snapshot.json'))

else:
self.fail('TUF failed to detect expired, stale Snapshot metadata.'
' Freeze attack successful.')
# mock current time to one second after snapshot expiry
mock_time = mock.Mock()
mock_time.return_value = snapshot_expiry_time + 1
with mock.patch('time.time', mock_time):
try:
# We expect the following refresh() to raise a NoWorkingMirrorError.
self.repository_updater.refresh()

except tuf.exceptions.NoWorkingMirrorError as e:
# Make sure the contained error is ExpiredMetadataError
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))
self.assertTrue(mirror_url.endswith('snapshot.json'))

else:
self.fail('TUF failed to detect expired, stale Snapshot metadata.'
' Freeze attack successful.')

# The client should have rejected the malicious Snapshot metadata, and
# distrusted the local snapshot file that is no longer valid.
Expand Down
18 changes: 14 additions & 4 deletions tuf/client/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,9 +1021,8 @@ def refresh(self, unsafely_update_root_if_necessary=True):
If the metadata for any of the top-level roles cannot be updated.

tuf.exceptions.ExpiredMetadataError:
If any of the top-level metadata is expired (whether a new version was
downloaded expired or no new version was found and the existing
version is now expired).
If any of the top-level metadata is expired and no new version was
found.

<Side Effects>
Updates the metadata files of the top-level roles with the latest
Expand Down Expand Up @@ -1900,6 +1899,9 @@ def _update_metadata_if_changed(self, metadata_role,
is 'timestamp'. See refresh().

<Exceptions>
tuf.exceptions.ExpiredMetadataError:
If local metadata is expired and newer metadata is not available.

tuf.exceptions.NoWorkingMirrorError:
If 'metadata_role' could not be downloaded after determining that it
had changed.
Expand Down Expand Up @@ -2393,7 +2395,6 @@ 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:
message = 'Metadata '+repr(metadata_rolename)+' expired on ' + \
expires_datetime.ctime() + ' (UTC).'
Expand Down Expand Up @@ -2495,6 +2496,9 @@ def _refresh_targets_metadata(self, rolename='targets',
repository (via snapshot.json) should be refreshed.

<Exceptions>
tuf.exceptions.ExpiredMetadataError:
If local metadata is expired and newer metadata is not available.

tuf.exceptions.RepositoryError:
If the metadata file for the 'targets' role is missing from the
'snapshot' metadata.
Expand Down Expand Up @@ -2715,6 +2719,9 @@ def get_one_valid_targetinfo(self, target_filepath):
the 'targets' (or equivalent) directory on a given mirror.

<Exceptions>
tuf.exceptions.ExpiredMetadataError:
If local metadata is expired and newer metadata is not available.

securesystemslib.exceptions.FormatError:
If 'target_filepath' is improperly formatted.

Expand Down Expand Up @@ -2770,6 +2777,9 @@ def _preorder_depth_first_walk(self, target_filepath):
the 'targets' (or equivalent) directory on a given mirror.

<Exceptions>
tuf.exceptions.ExpiredMetadataError:
If local metadata is expired and newer metadata is not available.

securesystemslib.exceptions.FormatError:
If 'target_filepath' is improperly formatted.

Expand Down