Skip to content

Commit 6cb9d45

Browse files
author
lukpueh
authored
Merge pull request #1194 from jku/speedup-indefinite-freeze-tests
Speedup indefinite freeze tests
2 parents cab9738 + 05cf090 commit 6cb9d45

File tree

2 files changed

+73
-83
lines changed

2 files changed

+73
-83
lines changed

tests/test_indefinite_freeze_attack.py

Lines changed: 59 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from __future__ import division
4545
from __future__ import unicode_literals
4646

47+
import datetime
4748
import os
4849
import time
4950
import tempfile
@@ -53,6 +54,11 @@
5354
import unittest
5455
import sys
5556

57+
if sys.version_info >= (3, 3):
58+
import unittest.mock as mock
59+
else:
60+
import mock
61+
5662
import tuf.formats
5763
import tuf.log
5864
import tuf.client.updater as updater
@@ -265,8 +271,7 @@ def test_with_tuf(self):
265271
# Load the repository
266272
repository = repo_tool.load_repository(self.repository_directory)
267273

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

279-
# Expire snapshot in 10s. This should be far enough into the future that we
280-
# haven't reached it before the first refresh validates timestamp expiry.
281-
# We want a successful refresh before expiry, then a second refresh after
282-
# expiry (which we then expect to raise an exception due to expired
283-
# metadata).
284-
expiry_time = time.time() + 10
285-
datetime_object = tuf.formats.unix_timestamp_to_datetime(int(expiry_time))
286-
287-
repository.snapshot.expiration = datetime_object
288-
289-
# Now write to the repository.
284+
# sign snapshot with expiry in near future (earlier than e.g. timestamp)
285+
expiry = int(time.time() + 60*60)
286+
repository.snapshot.expiration = tuf.formats.unix_timestamp_to_datetime(
287+
expiry)
288+
repository.mark_dirty(['snapshot', 'timestamp'])
290289
repository.writeall()
291290

292291
# And move the staged metadata to the "live" metadata.
@@ -297,30 +296,24 @@ def test_with_tuf(self):
297296
# Refresh metadata on the client. For this refresh, all data is not expired.
298297
logger.info('Test: Refreshing #1 - Initial metadata refresh occurring.')
299298
self.repository_updater.refresh()
300-
logger.info('Test: Refreshed #1 - Initial metadata refresh completed '
301-
'successfully. Now sleeping until snapshot metadata expires.')
302-
303-
# Sleep until expiry_time ('repository.snapshot.expiration')
304-
time.sleep(max(0, expiry_time - time.time() + 1))
305-
306-
logger.info('Test: Refreshing #2 - Now trying to refresh again after local'
307-
' snapshot expiry.')
308299

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

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

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

322-
self.fail('TUF failed to detect expired stale snapshot metadata. Freeze'
323-
' attack successful.')
314+
else:
315+
self.fail('TUF failed to detect expired stale snapshot metadata. Freeze'
316+
' attack successful.')
324317

325318

326319

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

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

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

379-
except tuf.exceptions.NoWorkingMirrorError as e:
380-
# NoWorkingMirrorError indicates that we did not find valid, unexpired
381-
# metadata at any mirror. That exception class preserves the errors from
382-
# each mirror. We now assert that for each mirror, the particular error
383-
# detected was that metadata was expired (the timestamp we manually
384-
# expired).
385-
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
386-
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))
387-
388-
else:
389-
self.fail('TUF failed to detect expired, stale timestamp metadata.'
390-
' Freeze attack successful.')
373+
else:
374+
self.fail('TUF failed to detect expired, stale timestamp metadata.'
375+
' Freeze attack successful.')
391376

392377

393378

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

419-
# Set snapshot to expire in 1 second.
420-
snapshot_expiry_time = time.time() + 1
404+
# Set snapshot to expire in 1 hour.
405+
snapshot_expiry_time = time.time() + 60*60
421406

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

435-
# Wait just long enough for the Snapshot metadata (which is now on the
436-
# repository) to expire.
437-
time.sleep(max(0, snapshot_expiry_time - time.time() + 1))
438-
439-
440-
try:
441-
# We expect the following refresh() to raise a NoWorkingMirrorError.
442-
self.repository_updater.refresh()
443-
444-
except tuf.exceptions.NoWorkingMirrorError as e:
445-
# NoWorkingMirrorError indicates that we did not find valid, unexpired
446-
# metadata at any mirror. That exception class preserves the errors from
447-
# each mirror. We now assert that for each mirror, the particular error
448-
# detected was that metadata was expired (the Snapshot we manually
449-
# expired).
450-
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
451-
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))
452-
self.assertTrue(mirror_url.endswith('snapshot.json'))
453-
454-
else:
455-
self.fail('TUF failed to detect expired, stale Snapshot metadata.'
456-
' Freeze attack successful.')
420+
# mock current time to one second after snapshot expiry
421+
mock_time = mock.Mock()
422+
mock_time.return_value = snapshot_expiry_time + 1
423+
with mock.patch('time.time', mock_time):
424+
try:
425+
# We expect the following refresh() to raise a NoWorkingMirrorError.
426+
self.repository_updater.refresh()
427+
428+
except tuf.exceptions.NoWorkingMirrorError as e:
429+
# Make sure the contained error is ExpiredMetadataError
430+
for mirror_url, mirror_error in six.iteritems(e.mirror_errors):
431+
self.assertTrue(isinstance(mirror_error, tuf.exceptions.ExpiredMetadataError))
432+
self.assertTrue(mirror_url.endswith('snapshot.json'))
433+
434+
else:
435+
self.fail('TUF failed to detect expired, stale Snapshot metadata.'
436+
' Freeze attack successful.')
457437

458438
# The client should have rejected the malicious Snapshot metadata, and
459439
# distrusted the local snapshot file that is no longer valid.

tuf/client/updater.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,9 +1021,8 @@ def refresh(self, unsafely_update_root_if_necessary=True):
10211021
If the metadata for any of the top-level roles cannot be updated.
10221022
10231023
tuf.exceptions.ExpiredMetadataError:
1024-
If any of the top-level metadata is expired (whether a new version was
1025-
downloaded expired or no new version was found and the existing
1026-
version is now expired).
1024+
If any of the top-level metadata is expired and no new version was
1025+
found.
10271026
10281027
<Side Effects>
10291028
Updates the metadata files of the top-level roles with the latest
@@ -1900,6 +1899,9 @@ def _update_metadata_if_changed(self, metadata_role,
19001899
is 'timestamp'. See refresh().
19011900
19021901
<Exceptions>
1902+
tuf.exceptions.ExpiredMetadataError:
1903+
If local metadata is expired and newer metadata is not available.
1904+
19031905
tuf.exceptions.NoWorkingMirrorError:
19041906
If 'metadata_role' could not be downloaded after determining that it
19051907
had changed.
@@ -2393,7 +2395,6 @@ def _ensure_not_expired(self, metadata_object, metadata_rolename):
23932395
expires_timestamp = tuf.formats.datetime_to_unix_timestamp(expires_datetime)
23942396

23952397
current_time = int(time.time())
2396-
23972398
if expires_timestamp < current_time:
23982399
message = 'Metadata '+repr(metadata_rolename)+' expired on ' + \
23992400
expires_datetime.ctime() + ' (UTC).'
@@ -2495,6 +2496,9 @@ def _refresh_targets_metadata(self, rolename='targets',
24952496
repository (via snapshot.json) should be refreshed.
24962497
24972498
<Exceptions>
2499+
tuf.exceptions.ExpiredMetadataError:
2500+
If local metadata is expired and newer metadata is not available.
2501+
24982502
tuf.exceptions.RepositoryError:
24992503
If the metadata file for the 'targets' role is missing from the
25002504
'snapshot' metadata.
@@ -2715,6 +2719,9 @@ def get_one_valid_targetinfo(self, target_filepath):
27152719
the 'targets' (or equivalent) directory on a given mirror.
27162720
27172721
<Exceptions>
2722+
tuf.exceptions.ExpiredMetadataError:
2723+
If local metadata is expired and newer metadata is not available.
2724+
27182725
securesystemslib.exceptions.FormatError:
27192726
If 'target_filepath' is improperly formatted.
27202727
@@ -2770,6 +2777,9 @@ def _preorder_depth_first_walk(self, target_filepath):
27702777
the 'targets' (or equivalent) directory on a given mirror.
27712778
27722779
<Exceptions>
2780+
tuf.exceptions.ExpiredMetadataError:
2781+
If local metadata is expired and newer metadata is not available.
2782+
27732783
securesystemslib.exceptions.FormatError:
27742784
If 'target_filepath' is improperly formatted.
27752785

0 commit comments

Comments
 (0)