Skip to content

Commit dd8a7eb

Browse files
Review test_indefinite_freeze_attack.py and updater.py. Minor edits made
1 parent 7cd20fe commit dd8a7eb

File tree

2 files changed

+59
-52
lines changed

2 files changed

+59
-52
lines changed

tests/test_indefinite_freeze_attack.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,17 @@ def test_without_tuf(self):
191191

192192
# Test 2 Begin:
193193
#
194-
# 'timestamp.json' specifies the latest version of the repository files.
195-
# A client should only accept the same version of this file up to a certain
196-
# point, or else it cannot detect that new files are available for download.
197-
# Modify the repository's timestamp.json' so that it expires soon, copy it
198-
# over the to client, and attempt to re-fetch the same expired version.
194+
# 'timestamp.json' specifies the latest version of the repository files. A
195+
# client should only accept the same version of this file up to a certain
196+
# point, or else it cannot detect that new files are available for
197+
# download. Modify the repository's timestamp.json' so that it expires
198+
# soon, copy it over to the client, and attempt to re-fetch the same
199+
# expired version.
199200
#
200201
# A non-TUF client (without a way to detect when metadata has expired) is
201202
# expected to download the same version, and thus the same outdated files.
202-
# Verify that the same file size and hash of 'timestamp.json' is downloaded.
203+
# Verify that the downloaded 'timestamp.json' contains the same file size
204+
# and hash as the one available locally.
203205

204206
timestamp_path = os.path.join(self.repository_directory, 'metadata',
205207
'timestamp.json')
@@ -285,17 +287,18 @@ def test_with_tuf(self):
285287
repository.snapshot.load_signing_key(snapshot_private)
286288

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

295297
repository.snapshot.expiration = datetime_object
296298

297-
# Now write to the repository
299+
# Now write to the repository.
298300
repository.write()
301+
299302
# And move the staged metadata to the "live" metadata.
300303
shutil.rmtree(os.path.join(self.repository_directory, 'metadata'))
301304
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
@@ -304,27 +307,28 @@ def test_with_tuf(self):
304307
# Refresh metadata on the client. For this refresh, all data is not expired.
305308
logger.info('Test: Refreshing #1 - Initial metadata refresh occurring.')
306309
self.repository_updater.refresh()
307-
logger.info("Test: Refreshed #1 - Initial metadata refresh completed "
308-
"successfully. Now sleeping until snapshot metadata expires.")
310+
logger.info('Test: Refreshed #1 - Initial metadata refresh completed '
311+
'successfully. Now sleeping until snapshot metadata expires.')
309312

310313
# Sleep until expiry_time ('repository.snapshot.expiration')
311-
time.sleep( max(0, expiry_time - time.time()) )
314+
time.sleep(max(0, expiry_time - time.time()))
312315

313-
logger.info("Test: Refreshing #2 - Now trying to refresh again after local "
314-
"snapshot expiry.")
316+
logger.info('Test: Refreshing #2 - Now trying to refresh again after local'
317+
' snapshot expiry.')
315318
try:
316319
self.repository_updater.refresh() # We expect this to fail!
317320

318-
except tuf.ExpiredMetadataError as e:
319-
logger.info("Test: Refresh #2 - failed as expected. Expired local "
320-
"snapshot case generated a tuf.ExpiredMetadataError exception"
321-
" as expected. Test pass.")
321+
except tuf.ExpiredMetadataError:
322+
logger.info('Test: Refresh #2 - failed as expected. Expired local'
323+
' snapshot case generated a tuf.ExpiredMetadataError'
324+
' exception as expected. Test pass.')
325+
322326
# I think that I only expect tuf.ExpiredMetadata error here. A
323327
# NoWorkingMirrorError indicates something else in this case - unavailable
324328
# repo, for example.
325329
else:
326-
self.fail("TUF failed to detect expired stale snapshot metadata. Freeze "
327-
"attack successful.")
330+
self.fail('TUF failed to detect expired stale snapshot metadata. Freeze'
331+
' attack successful.')
328332

329333

330334

@@ -334,13 +338,14 @@ def test_with_tuf(self):
334338
# 'timestamp.json' specifies the latest version of the repository files.
335339
# A client should only accept the same version of this file up to a certain
336340
# point, or else it cannot detect that new files are available for download.
337-
# Modify the repository's timestamp.json' so that it is about to expire,
341+
# Modify the repository's 'timestamp.json' so that it is about to expire,
338342
# copy it over the to client, wait a moment until it expires, and attempt to
339343
# re-fetch the same expired version.
340344

341-
# The same scenario as in test_without_tuf() is followed here, except with a
342-
# TUF client. The TUF client performs a refresh of top-level metadata, which
343-
# includes 'timestamp.json'.
345+
# The same scenario as in test_without_tuf() is followed here, except with
346+
# a TUF client. The TUF client performs a refresh of top-level metadata,
347+
# which includes 'timestamp.json', and should detect a freeze attack if
348+
# the repository serves an outdated 'timestamp.json'.
344349

345350
# Modify the timestamp file on the remote repository. 'timestamp.json'
346351
# must be properly updated and signed with 'repository_tool.py', otherwise
@@ -368,11 +373,11 @@ def test_with_tuf(self):
368373
shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'),
369374
os.path.join(self.repository_directory, 'metadata'))
370375

371-
# Wait just long enough for the timestamp metadata (which is now both on the
372-
# repository and on the client) to expire.
373-
time.sleep( max(0, expiry_time - time.time()) )
376+
# Wait just long enough for the timestamp metadata (which is now both on
377+
# the repository and on the client) to expire.
378+
time.sleep(max(0, expiry_time - time.time()))
374379

375-
# Try to refresh metadata on the client. Since we're already past
380+
# Try to refresh top-level metadata on the client. Since we're already past
376381
# 'repository.timestamp.expiration', the TUF client is expected to detect
377382
# that timestamp metadata is outdated and refuse to continue the update
378383
# process.
@@ -389,8 +394,8 @@ def test_with_tuf(self):
389394
self.assertTrue(isinstance(mirror_error, tuf.ExpiredMetadataError))
390395

391396
else:
392-
self.fail("TUF failed to detect expired stale timestamp metadata. Freeze "
393-
"attack successful.")
397+
self.fail('TUF failed to detect expired, stale timestamp metadata.'
398+
' Freeze attack successful.')
394399

395400

396401
if __name__ == '__main__':

tuf/client/updater.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -565,20 +565,22 @@ def refresh(self, unsafely_update_root_if_necessary=True):
565565
<Purpose>
566566
Update the latest copies of the metadata for the top-level roles. The
567567
update request process follows a specific order to ensure the metadata
568-
files are securely updated: timestamp -> snapshot -> root -> targets.
568+
files are securely updated:
569+
timestamp -> snapshot -> root (if necessary) -> targets.
569570
570571
Delegated metadata is not refreshed by this method. After this method is
571-
called, the use of target methods (e.g., all_targets(), targets_of_role(),
572-
or target()) will update delegated metadata.
573-
Calling refresh() ensures that top-level metadata is up-to-date, so that
574-
the target methods can refer to the latest available content. Thus,
575-
refresh() should always be called by the client before any requests of
576-
target file information.
572+
called, the use of target methods (e.g., all_targets(),
573+
targets_of_role(), or target()) will update delegated metadata, when
574+
required. Calling refresh() ensures that top-level metadata is
575+
up-to-date, so that the target methods can refer to the latest available
576+
content. Thus, refresh() should always be called by the client before any
577+
requests of target file information.
577578
578-
The expiration time for downloaded metadata is also verified.
579+
The expiration time for downloaded metadata is also verified, including
580+
local metadata that the repository claims is up to date.
579581
580582
If the refresh fails for any reason, then unless
581-
unsafely_update_root_if_necessary is set, refresh will be retried once
583+
'unsafely_update_root_if_necessary' is set, refresh will be retried once
582584
after first attempting to update the root metadata file. Only after this
583585
check will the exceptions listed here potentially be raised.
584586
@@ -656,7 +658,7 @@ def refresh(self, unsafely_update_root_if_necessary=True):
656658

657659
# If an exception is raised during the metadata update attempts, we will
658660
# attempt to update root metadata once by recursing with a special argument
659-
# to avoid further recursion.
661+
# (unsafely_update_root_if_necessary) to avoid further recursion.
660662

661663
# Use default but sane information for timestamp metadata, and do not
662664
# require strict checks on its required length.
@@ -675,35 +677,33 @@ def refresh(self, unsafely_update_root_if_necessary=True):
675677
# If a change to a metadata file IS detected in an
676678
# _update_metadata_if_changed call, but we are unable to download a
677679
# valid (not expired, properly signed, valid) version of that metadata
678-
# file, and unexpired version of that metadata file, a
679-
# tuf.NoWorkingMirrorError rises to this point.
680+
# file, a tuf.NoWorkingMirrorError rises to this point.
680681
#
681682
# - tuf.ExpiredMetadataError:
682683
#
683-
# If, on the other hand, a change to a metadata file IS NOT detected in
684-
# a given _update_metadata_if_changed call, but we observe that the
684+
# If, on the other hand, a change to a metadata file IS NOT detected
685+
# in a given _update_metadata_if_changed call, but we observe that the
685686
# version of the metadata file we have on hand is now expired, a
686687
# tuf.ExpiredMetadataError exception rises to this point.
687688
#
688-
except tuf.NoWorkingMirrorError as e:
689+
except tuf.NoWorkingMirrorError:
689690
if unsafely_update_root_if_necessary:
690-
logger.info('Valid top-level metadata cannot be downloaded. Unsafely '
691-
'update the Root metadata.')
691+
logger.info('Valid top-level metadata cannot be downloaded. Unsafely'
692+
' update the Root metadata.')
692693
self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH)
693694
self.refresh(unsafely_update_root_if_necessary=False)
694695

695696
else:
696697
raise
697698

698-
except tuf.ExpiredMetadataError as e:
699+
except tuf.ExpiredMetadataError:
699700
if unsafely_update_root_if_necessary:
700701
logger.info('No changes were detected from the mirrors for a given role'
701-
', and that metadata that is available on disk has been found to be '
702-
'expired. Trying to update root in case of foul play.')
702+
', and that metadata that is available on disk has been found to be'
703+
' expired. Trying to update root in case of foul play.')
703704
self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH)
704705
self.refresh(unsafely_update_root_if_necessary=False)
705706

706-
707707
# The caller explicitly requested not to unsafely fetch an expired Root.
708708
else:
709709
logger.info('No changes were detected from the mirrors for a given role'
@@ -713,6 +713,8 @@ def refresh(self, unsafely_update_root_if_necessary=True):
713713

714714

715715

716+
717+
716718
def _check_hashes(self, file_object, trusted_hashes):
717719
"""
718720
<Purpose>
@@ -1624,7 +1626,7 @@ def _update_metadata_if_changed(self, metadata_role,
16241626
logger.info(repr(uncompressed_metadata_filename) + ' up-to-date.')
16251627

16261628
# Since we have not downloaded a new version of this metadata, we
1627-
# should check to see if our version is stale and notify the user
1629+
# should check to see if our local version is stale and notify the user
16281630
# if so. This raises tuf.ExpiredMetadataError if the metadata we
16291631
# have is expired. Resolves issue #322.
16301632
self._ensure_not_expired(self.metadata['current'][metadata_role],

0 commit comments

Comments
 (0)