From 66f56271d2abff5fcd96e8cddcead5ea095f5c2a Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Thu, 10 Mar 2016 15:01:12 -0500 Subject: [PATCH 01/18] This is a manual squash commit of detect_expiry_322, to avoid merge conflicts. Comment lines from the individual commits include: 1. Fix #322 by detecting expiry of stale files. initial attempt 2. temp commit of files from Soma 3. removing freeze_attack_stale_expiry and leaving the test added to indefinite freeze attack 4. fixing indefinite freeze attack test: now incorporates old reject-freshly-downloaded-but-expired-timestamp test as well as reject-stale-already-present-but-expired-snapshot test 5. small refinements to indefinite freeze attack test 6. Pulled the recursion out of the except block in refresh() to avoid unprintable nested exceptions. 7. Added comments to the last commit (retry_once) 8. Merge pull request #1 from awwad/detect_expiry_322_temp (removing cruft in another branch) --- tests/test_indefinite_freeze_attack.py | 120 ++++++++++++++++++++++++- tuf/client/updater.py | 62 +++++++++++-- 2 files changed, 171 insertions(+), 11 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 1522568633..91573b7b1b 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -15,6 +15,14 @@ than verifying text output), use pre-generated repository files, and discontinue use of the old repository tools. -vladimir.v.diaz + March 9, 2016. + Additional test added relating to issue: + https://github.com/theupdateframework/tuf/issues/322 + If a metadata file is not downloaded (no indication of a new version + available), its expiration must still be detected. This add'l test + complains if such does not occur, and accompanies code to detect it. + -sebastien.awwad + See LICENSE for licensing information. @@ -171,6 +179,7 @@ def tearDown(self): def test_without_tuf(self): + # Test 1: # Scenario: # 'timestamp.json' specifies the latest version of the repository files. # A client should only accept the same version of this file up to a certain @@ -215,9 +224,108 @@ def test_without_tuf(self): # Verify 'download_fileinfo' is equal to the current local file. self.assertEqual(download_fileinfo, fileinfo) + # Test 2: + # See description of scenario in Test 2 in the test_with_tuf method. + # Without TUF, Test 2 is tantamount to Test 1, and so it is not tested + # again here. def test_with_tuf(self): + # Two tests are conducted here. + # Test 1: If an expired timestamp is downloaded, is it recognized as such? + # Test 2: If we find that the timestamp acquired from a mirror indicates + # that there is no new snapshot file, and our current snapshot + # file is expired, is it recognized as such? + # + # Test 2 addresses this issue: + # https://github.com/theupdateframework/tuf/issues/322 + # + # If time has passed and our snapshot (or any targets role) is expired, and + # the mirror whose timestamp we fetched doesn't indicate the existence of a + # new snapshot version, we still need to check that it's expired and notify + # the software update system / application / user. This test creates that + # scenario. The correct behavior is to raise an exception. + # + # Background: Expiration checks were previously conducted + # (ensure_not_expired) when the metadata file was downloaded. If no new + # metadata file was downloaded, no expiry check would occurs. (Exception: + # root was checked for expiration at the beginning of each refresh() cycle, + # and timestamp was always checked because it was always fetched.) Snapshot + # and targets were never checked if the user does not have evidence that + # they have changed. + + timestamp_path = os.path.join(self.repository_directory, 'metadata', + 'timestamp.json') + + # Modify the timestamp file on the remote repository. 'timestamp.json' + # must be properly updated and signed with 'repository_tool.py', otherwise + # the client will reject it as invalid metadata. The resulting + # 'timestamp.json' should be valid metadata, but expired (as intended). + repository = repo_tool.load_repository(self.repository_directory) + + key_file = os.path.join(self.keystore_directory, 'timestamp_key') + timestamp_private = repo_tool.import_rsa_privatekey_from_file(key_file, + 'password') + + repository.timestamp.load_signing_key(timestamp_private) + + # Load snapshot keys. + key_file = os.path.join(self.keystore_directory, 'snapshot_key') + snapshot_private = repo_tool.import_rsa_privatekey_from_file(key_file, + 'password') + repository.snapshot.load_signing_key(snapshot_private) + + # Load root keys. + key_file = os.path.join(self.keystore_directory, 'root_key') + root_private = repo_tool.import_rsa_privatekey_from_file(key_file, + 'password') + repository.root.load_signing_key(root_private) + + time.sleep(0.1) + + # Expire snapshot in 7s + datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + + 7)) + repository.snapshot.expiration = datetime_object + + # Now write to the repository + repository.write() + # And move the staged metadata to the "live" metadata. + time.sleep(0.1) + shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) + shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), + os.path.join(self.repository_directory, 'metadata')) + + # Refresh metadata on the client. For this refresh, all data is not expired. + time.sleep(0.1) + logger.info('Test: Refreshing #1 - Initial metadata refresh occurring.') + self.repository_updater.refresh() + logger.info("Test: Refreshed #1 - Initial metadata refresh occurred. Now " + "sleeping 6s.") + + # Sleep for at least 7 seconds to ensure 'repository.snapshot.expiration' + # is reached. + time.sleep(7) + logger.info("Test: Refreshing #2 - Now trying to refresh again after " + " snapshot expiry.") + try: + self.repository_updater.refresh() # We expect this to fail! + + except tuf.ExpiredMetadataError as e: + logger.info("Test: Refresh #2 - failed as expected. Stale-expired " + "snapshot case generated a tuf.ExpiredMetadataError exception" + ". Test pass.") + #I think that I only expect tuf.ExpiredMetadata error here. A + # NoWorkingMirrorError indicates something else in this case - unavailable + # repo, for example. + else: + self.fail("TUF failed to detect expired stale snapshot metadata. Freeze.") + + + + + # Part 1: + # The same scenario outlined in test_without_tuf() is followed here, except # with a TUF client. The TUF client performs a refresh of top-level # metadata, which also includes 'timestamp.json'. @@ -237,8 +345,8 @@ def test_with_tuf(self): repository.timestamp.load_signing_key(timestamp_private) - # expire in 1 second. - datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 1)) + # expire in 4 seconds. + datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 4)) repository.timestamp.expiration = datetime_object repository.write() @@ -248,15 +356,19 @@ def test_with_tuf(self): os.path.join(self.repository_directory, 'metadata')) # Verify that the TUF client detects outdated metadata and refuses to - # continue the update process. Sleep for at least 2 seconds to ensure + # continue the update process. Sleep for at least 4 seconds to ensure # 'repository.timestamp.expiration' is reached. - time.sleep(2) + time.sleep(4) try: self.repository_updater.refresh() except tuf.NoWorkingMirrorError as e: for mirror_url, mirror_error in six.iteritems(e.mirror_errors): self.assertTrue(isinstance(mirror_error, tuf.ExpiredMetadataError)) + + else: + self.fail("TUF failed to detect expired stale timestamp metadata. Freeze " + "attack successful.") if __name__ == '__main__': diff --git a/tuf/client/updater.py b/tuf/client/updater.py index c7ebcf472f..99e2de9aea 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -643,6 +643,13 @@ def refresh(self, unsafely_update_root_if_necessary=True): logger.info('An expired Root metadata was loaded and must be updated.') raise + # If an exception is raised during the metadata update attempts, we will + # attempt to update root metadata once by recursing with a special argument + # to avoid further recursion. We use this bool and pull the recursion out + # of the except block so as to avoid unprintable nested NoWorkingMirrorError + # exceptions. + retry_once = False + # Use default but sane information for timestamp metadata, and do not # require strict checks on its required length. try: @@ -652,19 +659,53 @@ def refresh(self, unsafely_update_root_if_necessary=True): self._update_metadata_if_changed('root') self._update_metadata_if_changed('targets') + # There are two distinct error scenarios that can rise from the + # _update_metadata_if_changed calls in the try block above: + # + # - tuf.NoWorkingMirrorError: + # + # If a change to a metadata file IS detected in an + # _update_metadata_if_changed call, but we are unable to download a + # valid (not expired, properly signed, valid) version of that metadata + # file, and unexpired version of that metadata file, a + # tuf.NoWorkingMirrorError rises to this point. + # + # - tuf.ExpiredMetadataError: + # + # If, on the other hand, a change to a metadata file IS NOT detected in + # a given _update_metadata_if_changed call, but we observe that the + # version of the metadata file we have on hand is now expired, a + # tuf.ExpiredMetadataError exception rises to this point. + # except tuf.NoWorkingMirrorError as e: if unsafely_update_root_if_necessary: - message = 'Valid top-level metadata cannot be downloaded. Unsafely '+\ - 'update the Root metadata.' - logger.info(message) - - self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) - self.refresh(unsafely_update_root_if_necessary=False) - + logger.info('Valid top-level metadata cannot be downloaded. Unsafely ' + 'update the Root metadata.') + retry_once = True + else: raise + except tuf.ExpiredMetadataError as e: + if unsafely_update_root_if_necessary: + logger.info('No changes were detected from the mirrors for a given role' + ', and that metadata that is available on disk has been found to be ' + 'expired. Trying to update root in case of foul play.') + retry_once = True + + # The caller explicitly requested not to unsafely fetch an expired Root. + else: + logger.info('No changes were detected from the mirrors for a given role' + ', and that metadata that is available on disk has been found to be ' + 'expired. Your metadata is out of date.') + raise + # Update failed and we aren't already in a retry. Try once more after + # updating root. + if retry_once: + self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) + self.refresh(unsafely_update_root_if_necessary=False) + @@ -1578,6 +1619,13 @@ def _update_metadata_if_changed(self, metadata_role, expected_versioninfo): logger.info(repr(uncompressed_metadata_filename) + ' up-to-date.') + # Since we have not downloaded a new version of this metadata, we + # should check to see if our version is stale and notify the user + # if so. This raises tuf.ExpiredMetadataError if the metadata we + # have is expired. Resolves issue #322. + self._ensure_not_expired(self.metadata['current'][metadata_role], + metadata_role) + return logger.debug('Metadata ' + repr(uncompressed_metadata_filename) + ' has changed.') From 0fa451892962574f28b6f065da10385438927603 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 11 Mar 2016 17:01:35 -0500 Subject: [PATCH 02/18] comment improvement in indefinite freeze attack test --- tests/test_indefinite_freeze_attack.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 91573b7b1b..42d24b5bd6 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -18,9 +18,10 @@ March 9, 2016. Additional test added relating to issue: https://github.com/theupdateframework/tuf/issues/322 - If a metadata file is not downloaded (no indication of a new version - available), its expiration must still be detected. This add'l test - complains if such does not occur, and accompanies code to detect it. + If a metadata file is not updated (no indication of a new version + available), the expiration of the pre-existing, locally trusted metadata + must still be detected. This additional test complains if such does not + occur, and accompanies code in tuf.client.updater:refresh() to detect it. -sebastien.awwad From 8adab689ffb78cdff92d980347a2da93f2250222 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 12:40:06 -0400 Subject: [PATCH 03/18] Expanding and clarifying docstring for tuf.client.updater::refresh() --- tuf/client/updater.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tuf/client/updater.py b/tuf/client/updater.py index 99e2de9aea..2275374971 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -563,15 +563,23 @@ def _import_delegations(self, parent_role): def refresh(self, unsafely_update_root_if_necessary=True): """ - Update the latest copies of the metadata for the top-level roles. - The update request process follows a specific order to ensure the - metadata files are securely updated. - - The client would call refresh() prior to requesting target file - information. Calling refresh() ensures target methods, like - all_targets() and target(), refer to the latest available content. - The latest copies, according to the currently trusted top-level metadata, - of delegated metadata are downloaded and updated by the target methods. + Update the latest copies of the metadata for the top-level roles. The + update request process follows a specific order to ensure the metadata + files are securely updated: timestamp -> snapshot -> root -> targets. + + Delegated metadata is not refreshed by this method. After this method is + called, the use of target methods (e.g., all_targets(), targets_of_role(), + or target()) will update delegated metadata. + Calling refresh() ensures that top-level metadata is up-to-date, so that + the target methods can refer to the latest available content. Thus, + refresh() should always be called by the client before any requests of + target file information. + + The expiration time for downloaded metadata is also verified. + + If the refresh fails for any reason, it will be retried once after first + attempting to update the root metadata file. Only then will the exceptions + listed here potentially be raised. unsafely_update_root_if_necessary: @@ -584,7 +592,9 @@ def refresh(self, unsafely_update_root_if_necessary=True): If the metadata for any of the top-level roles cannot be updated. tuf.ExpiredMetadataError: - If any metadata has expired. + 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). Updates the metadata files of the top-level roles with the latest From 6a4982fa8a360e1c9ad6accb6758107715863a18 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 12:59:46 -0400 Subject: [PATCH 04/18] Improving comments in test_indefinite_freeze_attack --- tests/test_indefinite_freeze_attack.py | 57 +++++++++++++++++--------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 42d24b5bd6..7bcf90b6f6 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -180,13 +180,26 @@ def tearDown(self): def test_without_tuf(self): - # Test 1: - # Scenario: + # Two tests are conducted here. + # Test 1: If an expired timestamp is downloaded, is it recognized as such? + # Test 2: If we find that the timestamp acquired from a mirror indicates + # that there is no new snapshot file, and our current snapshot + # file is expired, is it recognized as such? + + # Test 2 Begin: + # + # See description of scenario in Test 2 in the test_with_tuf method. + # Without TUF, Test 2 is tantamount to Test 1, and so we skip this test. + + + # Test 1 Begin: + # # 'timestamp.json' specifies the latest version of the repository files. # A client should only accept the same version of this file up to a certain # point, or else it cannot detect that new files are available for download. # Modify the repository's timestamp.json' so that it expires soon, copy it # over the to client, and attempt to re-fetch the same expired version. + # # A non-TUF client (without a way to detect when metadata has expired) is # expected to download the same version, and thus the same outdated files. # Verify that the same file size and hash of 'timestamp.json' is downloaded. @@ -225,11 +238,6 @@ def test_without_tuf(self): # Verify 'download_fileinfo' is equal to the current local file. self.assertEqual(download_fileinfo, fileinfo) - # Test 2: - # See description of scenario in Test 2 in the test_with_tuf method. - # Without TUF, Test 2 is tantamount to Test 1, and so it is not tested - # again here. - def test_with_tuf(self): # Two tests are conducted here. @@ -237,9 +245,11 @@ def test_with_tuf(self): # Test 2: If we find that the timestamp acquired from a mirror indicates # that there is no new snapshot file, and our current snapshot # file is expired, is it recognized as such? + + + # Test 2 Begin: # - # Test 2 addresses this issue: - # https://github.com/theupdateframework/tuf/issues/322 + # Addresses this issue: https://github.com/theupdateframework/tuf/issues/322 # # If time has passed and our snapshot (or any targets role) is expired, and # the mirror whose timestamp we fetched doesn't indicate the existence of a @@ -247,13 +257,14 @@ def test_with_tuf(self): # the software update system / application / user. This test creates that # scenario. The correct behavior is to raise an exception. # - # Background: Expiration checks were previously conducted - # (ensure_not_expired) when the metadata file was downloaded. If no new - # metadata file was downloaded, no expiry check would occurs. (Exception: - # root was checked for expiration at the beginning of each refresh() cycle, - # and timestamp was always checked because it was always fetched.) Snapshot - # and targets were never checked if the user does not have evidence that - # they have changed. + # Background: Expiration checks (updater._ensure_not_expired) were + # previously conducted when the metadata file was downloaded. If no new + # metadata file was downloaded, no expiry check would occur. In particular, + # while root was checked for expiration at the beginning of each + # updater.refresh() cycle, and timestamp was always checked because it was + # always fetched, snapshot and targets were never checked if the user did + # not receive evidence that they had changed. + # That bug was fixed and this test tests that fix going forward. timestamp_path = os.path.join(self.repository_directory, 'metadata', 'timestamp.json') @@ -325,11 +336,17 @@ def test_with_tuf(self): - # Part 1: + # Test 1 Begin: + # + # 'timestamp.json' specifies the latest version of the repository files. + # A client should only accept the same version of this file up to a certain + # point, or else it cannot detect that new files are available for download. + # Modify the repository's timestamp.json' so that it expires soon, copy it + # over the to client, and attempt to re-fetch the same expired version. - # The same scenario outlined in test_without_tuf() is followed here, except - # with a TUF client. The TUF client performs a refresh of top-level - # metadata, which also includes 'timestamp.json'. + # The same scenario as in test_without_tuf() is followed here, except with a + # TUF client. The TUF client performs a refresh of top-level metadata, which + # includes 'timestamp.json'. timestamp_path = os.path.join(self.repository_directory, 'metadata', 'timestamp.json') From 08e005330a90d290b4069311408340a5b7cd9387 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 15:51:58 -0400 Subject: [PATCH 05/18] Minor log message improvements. --- tests/test_indefinite_freeze_attack.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 7bcf90b6f6..a7924c2d5e 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -312,26 +312,27 @@ def test_with_tuf(self): time.sleep(0.1) logger.info('Test: Refreshing #1 - Initial metadata refresh occurring.') self.repository_updater.refresh() - logger.info("Test: Refreshed #1 - Initial metadata refresh occurred. Now " - "sleeping 6s.") + logger.info("Test: Refreshed #1 - Initial metadata refresh completed " + "successfully. Now sleeping 7s so snapshot metadata expires.") # Sleep for at least 7 seconds to ensure 'repository.snapshot.expiration' # is reached. time.sleep(7) - logger.info("Test: Refreshing #2 - Now trying to refresh again after " - " snapshot expiry.") + logger.info("Test: Refreshing #2 - Now trying to refresh again after local " + "snapshot expiry.") try: self.repository_updater.refresh() # We expect this to fail! except tuf.ExpiredMetadataError as e: - logger.info("Test: Refresh #2 - failed as expected. Stale-expired " + logger.info("Test: Refresh #2 - failed as expected. Expired local " "snapshot case generated a tuf.ExpiredMetadataError exception" - ". Test pass.") - #I think that I only expect tuf.ExpiredMetadata error here. A + " as expected. Test pass.") + # I think that I only expect tuf.ExpiredMetadata error here. A # NoWorkingMirrorError indicates something else in this case - unavailable # repo, for example. else: - self.fail("TUF failed to detect expired stale snapshot metadata. Freeze.") + self.fail("TUF failed to detect expired stale snapshot metadata. Freeze " + "attack successful.") From 825022320705fe8345c4fd52fe6e8d1025073a18 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 16:30:12 -0400 Subject: [PATCH 06/18] removed unnecessary temp variable reload line; switched names of Test 1 and Test 2 --- tests/test_indefinite_freeze_attack.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index a7924c2d5e..853efdffe6 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -181,18 +181,18 @@ def tearDown(self): def test_without_tuf(self): # Two tests are conducted here. - # Test 1: If an expired timestamp is downloaded, is it recognized as such? - # Test 2: If we find that the timestamp acquired from a mirror indicates + # Test 1: If we find that the timestamp acquired from a mirror indicates # that there is no new snapshot file, and our current snapshot # file is expired, is it recognized as such? + # Test 2: If an expired timestamp is downloaded, is it recognized as such? - # Test 2 Begin: + # Test 1 Begin: # # See description of scenario in Test 2 in the test_with_tuf method. # Without TUF, Test 2 is tantamount to Test 1, and so we skip this test. - # Test 1 Begin: + # Test 2 Begin: # # 'timestamp.json' specifies the latest version of the repository files. # A client should only accept the same version of this file up to a certain @@ -241,13 +241,13 @@ def test_without_tuf(self): def test_with_tuf(self): # Two tests are conducted here. - # Test 1: If an expired timestamp is downloaded, is it recognized as such? - # Test 2: If we find that the timestamp acquired from a mirror indicates + # Test 1: If we find that the timestamp acquired from a mirror indicates # that there is no new snapshot file, and our current snapshot # file is expired, is it recognized as such? + # Test 2: If an expired timestamp is downloaded, is it recognized as such? - # Test 2 Begin: + # Test 1 Begin: # # Addresses this issue: https://github.com/theupdateframework/tuf/issues/322 # @@ -337,7 +337,7 @@ def test_with_tuf(self): - # Test 1 Begin: + # Test 2 Begin: # # 'timestamp.json' specifies the latest version of the repository files. # A client should only accept the same version of this file up to a certain @@ -349,16 +349,13 @@ def test_with_tuf(self): # TUF client. The TUF client performs a refresh of top-level metadata, which # includes 'timestamp.json'. - timestamp_path = os.path.join(self.repository_directory, 'metadata', - 'timestamp.json') - # Modify the timestamp file on the remote repository. 'timestamp.json' # must be properly updated and signed with 'repository_tool.py', otherwise # the client will reject it as invalid metadata. The resulting # 'timestamp.json' should be valid metadata, but expired (as intended). repository = repo_tool.load_repository(self.repository_directory) - key_file = os.path.join(self.keystore_directory, 'timestamp_key') + key_file = os.path.join(self.keystore_directory, 'timestamp_key') timestamp_private = repo_tool.import_rsa_privatekey_from_file(key_file, 'password') From 0bf30b7ec3363a1d0a954ae936e641c75d9498e9 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 16:51:54 -0400 Subject: [PATCH 07/18] Adding .DS_Store to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5dbf7e8095..d16c81a975 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ build/* .coverage .tox/* tests/htmlcov/* +.DS_Store \ No newline at end of file From fee25a44cddf6c580756e014e2713c03f56d563e Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 17:39:04 -0400 Subject: [PATCH 08/18] sharing bug with Vlad --- tests/test_indefinite_freeze_attack.py | 16 ++++++++-------- tuf/client/updater.py | 17 +++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 853efdffe6..b6b1cd03f2 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -295,9 +295,9 @@ def test_with_tuf(self): time.sleep(0.1) - # Expire snapshot in 7s + # Expire snapshot in 3s datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + - 7)) + 3)) repository.snapshot.expiration = datetime_object # Now write to the repository @@ -313,11 +313,11 @@ def test_with_tuf(self): 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 7s so snapshot metadata expires.") + "successfully. Now sleeping 3s so snapshot metadata expires.") # Sleep for at least 7 seconds to ensure 'repository.snapshot.expiration' # is reached. - time.sleep(7) + time.sleep(3) logger.info("Test: Refreshing #2 - Now trying to refresh again after local " "snapshot expiry.") try: @@ -361,8 +361,8 @@ def test_with_tuf(self): repository.timestamp.load_signing_key(timestamp_private) - # expire in 4 seconds. - datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 4)) + # expire in 3 seconds. + datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 3)) repository.timestamp.expiration = datetime_object repository.write() @@ -372,9 +372,9 @@ def test_with_tuf(self): os.path.join(self.repository_directory, 'metadata')) # Verify that the TUF client detects outdated metadata and refuses to - # continue the update process. Sleep for at least 4 seconds to ensure + # continue the update process. Sleep for at least 3 seconds to ensure # 'repository.timestamp.expiration' is reached. - time.sleep(4) + time.sleep(3) try: self.repository_updater.refresh() diff --git a/tuf/client/updater.py b/tuf/client/updater.py index 2275374971..e1a87b1083 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -658,7 +658,7 @@ def refresh(self, unsafely_update_root_if_necessary=True): # to avoid further recursion. We use this bool and pull the recursion out # of the except block so as to avoid unprintable nested NoWorkingMirrorError # exceptions. - retry_once = False +# retry_once = False # Use default but sane information for timestamp metadata, and do not # require strict checks on its required length. @@ -691,7 +691,9 @@ def refresh(self, unsafely_update_root_if_necessary=True): if unsafely_update_root_if_necessary: logger.info('Valid top-level metadata cannot be downloaded. Unsafely ' 'update the Root metadata.') - retry_once = True +# retry_once = True + self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) + self.refresh(unsafely_update_root_if_necessary=False) else: raise @@ -701,7 +703,10 @@ def refresh(self, unsafely_update_root_if_necessary=True): logger.info('No changes were detected from the mirrors for a given role' ', and that metadata that is available on disk has been found to be ' 'expired. Trying to update root in case of foul play.') - retry_once = True +# retry_once = True + self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) + self.refresh(unsafely_update_root_if_necessary=False) + # The caller explicitly requested not to unsafely fetch an expired Root. else: @@ -712,9 +717,9 @@ def refresh(self, unsafely_update_root_if_necessary=True): # Update failed and we aren't already in a retry. Try once more after # updating root. - if retry_once: - self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) - self.refresh(unsafely_update_root_if_necessary=False) +# if retry_once: +# self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) +# self.refresh(unsafely_update_root_if_necessary=False) From 93aec83fd5884398deea25e609913708054681dc Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 17:43:22 -0400 Subject: [PATCH 09/18] fixing bug with logger.exception called outside of an except block --- 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 e1a87b1083..3c68bf4289 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -1206,7 +1206,7 @@ def _get_metadata_file(self, metadata_role, remote_filename, return file_object else: - logger.exception('Failed to update {0} from all mirrors: {1}'.format( + logger.error('Failed to update {0} from all mirrors: {1}'.format( remote_filename, file_mirror_errors)) raise tuf.NoWorkingMirrorError(file_mirror_errors) From ab4cbdde528a4eff071610290f6320e7d074d422 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Mon, 14 Mar 2016 18:08:24 -0400 Subject: [PATCH 10/18] guaranteed test failure: unprintable on 3, printable on 2 --- tests/test_indefinite_freeze_attack.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index b6b1cd03f2..b4251ba807 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -295,9 +295,9 @@ def test_with_tuf(self): time.sleep(0.1) - # Expire snapshot in 3s + # Expire snapshot in 0s datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + - 3)) + 0)) repository.snapshot.expiration = datetime_object # Now write to the repository @@ -313,11 +313,11 @@ def test_with_tuf(self): 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 3s so snapshot metadata expires.") + "successfully. Now sleeping 6s so snapshot metadata expires.") - # Sleep for at least 7 seconds to ensure 'repository.snapshot.expiration' + # Sleep for at least 2 seconds to ensure 'repository.snapshot.expiration' # is reached. - time.sleep(3) + time.sleep(2) logger.info("Test: Refreshing #2 - Now trying to refresh again after local " "snapshot expiry.") try: @@ -361,8 +361,8 @@ def test_with_tuf(self): repository.timestamp.load_signing_key(timestamp_private) - # expire in 3 seconds. - datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 3)) + # expire in 2 seconds. + datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 2)) repository.timestamp.expiration = datetime_object repository.write() @@ -374,7 +374,7 @@ def test_with_tuf(self): # Verify that the TUF client detects outdated metadata and refuses to # continue the update process. Sleep for at least 3 seconds to ensure # 'repository.timestamp.expiration' is reached. - time.sleep(3) + time.sleep(2) try: self.repository_updater.refresh() From 85a750260391ce04d20d0b4b0830519fda185bcd Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 11:50:11 -0400 Subject: [PATCH 11/18] - Tweaked the sleep durations to be more minimal, based on time that had passed. (updater.refresh() can take a while.) - Put code back into working state after some failure mode debugging of a separate bug by Vlad and me. - Expanded some comments, added explanations, and made a few others more readable. - Removed an unnecessary path recalculation and an unnecessary sleep. --- tests/test_indefinite_freeze_attack.py | 76 ++++++++++++++++---------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index b4251ba807..33da0b15b4 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -181,6 +181,7 @@ def tearDown(self): def test_without_tuf(self): # Two tests are conducted here. + # # Test 1: If we find that the timestamp acquired from a mirror indicates # that there is no new snapshot file, and our current snapshot # file is expired, is it recognized as such? @@ -189,7 +190,8 @@ def test_without_tuf(self): # Test 1 Begin: # # See description of scenario in Test 2 in the test_with_tuf method. - # Without TUF, Test 2 is tantamount to Test 1, and so we skip this test. + # Without TUF, Test 2 and Test 1 are functionally equivalent, and so we skip + # Test 1. # Test 2 Begin: @@ -208,7 +210,8 @@ def test_without_tuf(self): 'timestamp.json') timestamp_metadata = tuf.util.load_json_file(timestamp_path) - expires = tuf.formats.unix_timestamp_to_datetime(int(time.time() - 10)) + expiry_time = time.time() - 10 + expires = tuf.formats.unix_timestamp_to_datetime(int(expiry_time)) expires = expires.isoformat() + 'Z' timestamp_metadata['signed']['expires'] = expires tuf.formats.check_signable_object_format(timestamp_metadata) @@ -241,6 +244,7 @@ def test_without_tuf(self): def test_with_tuf(self): # Two tests are conducted here. + # # Test 1: If we find that the timestamp acquired from a mirror indicates # that there is no new snapshot file, and our current snapshot # file is expired, is it recognized as such? @@ -251,7 +255,7 @@ def test_with_tuf(self): # # Addresses this issue: https://github.com/theupdateframework/tuf/issues/322 # - # If time has passed and our snapshot (or any targets role) is expired, and + # If time has passed and our snapshot or targets role is expired, and # the mirror whose timestamp we fetched doesn't indicate the existence of a # new snapshot version, we still need to check that it's expired and notify # the software update system / application / user. This test creates that @@ -263,16 +267,13 @@ def test_with_tuf(self): # while root was checked for expiration at the beginning of each # updater.refresh() cycle, and timestamp was always checked because it was # always fetched, snapshot and targets were never checked if the user did - # not receive evidence that they had changed. + # not receive evidence that they had changed. This bug allowed a class of + # freeze attacks. # That bug was fixed and this test tests that fix going forward. - timestamp_path = os.path.join(self.repository_directory, 'metadata', - 'timestamp.json') - # Modify the timestamp file on the remote repository. 'timestamp.json' # must be properly updated and signed with 'repository_tool.py', otherwise - # the client will reject it as invalid metadata. The resulting - # 'timestamp.json' should be valid metadata, but expired (as intended). + # the client will reject it as invalid metadata. repository = repo_tool.load_repository(self.repository_directory) key_file = os.path.join(self.keystore_directory, 'timestamp_key') @@ -293,31 +294,34 @@ def test_with_tuf(self): 'password') repository.root.load_signing_key(root_private) - time.sleep(0.1) + # Expire snapshot in 8s. 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() + 8 + datetime_object = tuf.formats.unix_timestamp_to_datetime(int(expiry_time)) - # Expire snapshot in 0s - datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + - 0)) repository.snapshot.expiration = datetime_object # Now write to the repository repository.write() # And move the staged metadata to the "live" metadata. - time.sleep(0.1) + time.sleep(0.1) # superstitious move to allow write to finish? shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), os.path.join(self.repository_directory, 'metadata')) # Refresh metadata on the client. For this refresh, all data is not expired. - time.sleep(0.1) + time.sleep(0.1) # superstitious move to allow copy to finish? 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 6s so snapshot metadata expires.") + "successfully. Now sleeping until snapshot metadata expires.") + + # Sleep until expiry_time ('repository.snapshot.expiration') + time.sleep( max(0, expiry_time - time.time()) ) - # Sleep for at least 2 seconds to ensure 'repository.snapshot.expiration' - # is reached. - time.sleep(2) logger.info("Test: Refreshing #2 - Now trying to refresh again after local " "snapshot expiry.") try: @@ -342,8 +346,9 @@ def test_with_tuf(self): # 'timestamp.json' specifies the latest version of the repository files. # A client should only accept the same version of this file up to a certain # point, or else it cannot detect that new files are available for download. - # Modify the repository's timestamp.json' so that it expires soon, copy it - # over the to client, and attempt to re-fetch the same expired version. + # Modify the repository's timestamp.json' so that it is about to expire, + # copy it over the to client, wait a moment until it expires, and attempt to + # re-fetch the same expired version. # The same scenario as in test_without_tuf() is followed here, except with a # TUF client. The TUF client performs a refresh of top-level metadata, which @@ -361,8 +366,12 @@ def test_with_tuf(self): repository.timestamp.load_signing_key(timestamp_private) - # expire in 2 seconds. - datetime_object = tuf.formats.unix_timestamp_to_datetime(int(time.time() + 2)) + # Set timestamp metadata to expire soon. + # 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 + datetime_object = tuf.formats.unix_timestamp_to_datetime(int(expiry_time)) repository.timestamp.expiration = datetime_object repository.write() @@ -370,15 +379,24 @@ def test_with_tuf(self): shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), os.path.join(self.repository_directory, 'metadata')) - - # Verify that the TUF client detects outdated metadata and refuses to - # continue the update process. Sleep for at least 3 seconds to ensure - # 'repository.timestamp.expiration' is reached. - time.sleep(2) + + # 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()) ) + + # Try to refresh 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() + self.repository_updater.refresh() # We expect NoWorkingMirrorError. except tuf.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.ExpiredMetadataError)) From 893911614ad3b67915346c0844f91bb84dbdefd9 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 11:54:18 -0400 Subject: [PATCH 12/18] fixing minor comment wording --- tests/test_indefinite_freeze_attack.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 33da0b15b4..56da64686a 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -180,19 +180,14 @@ def tearDown(self): def test_without_tuf(self): - # Two tests are conducted here. + # Without TUF, Test 1 and Test 2 are functionally equivalent, so we skip + # Test 1 and only perform Test 2. # # Test 1: If we find that the timestamp acquired from a mirror indicates # that there is no new snapshot file, and our current snapshot # file is expired, is it recognized as such? # Test 2: If an expired timestamp is downloaded, is it recognized as such? - # Test 1 Begin: - # - # See description of scenario in Test 2 in the test_with_tuf method. - # Without TUF, Test 2 and Test 1 are functionally equivalent, and so we skip - # Test 1. - # Test 2 Begin: # From 167af8dd653db020fa1b39b6cdcebc8981cecd60 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 12:25:33 -0400 Subject: [PATCH 13/18] comment wording: refresh docstring explanation --- tuf/client/updater.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/tuf/client/updater.py b/tuf/client/updater.py index 3c68bf4289..652f661d15 100755 --- a/tuf/client/updater.py +++ b/tuf/client/updater.py @@ -577,9 +577,10 @@ def refresh(self, unsafely_update_root_if_necessary=True): The expiration time for downloaded metadata is also verified. - If the refresh fails for any reason, it will be retried once after first - attempting to update the root metadata file. Only then will the exceptions - listed here potentially be raised. + If the refresh fails for any reason, then unless + unsafely_update_root_if_necessary is set, refresh will be retried once + after first attempting to update the root metadata file. Only after this + check will the exceptions listed here potentially be raised. unsafely_update_root_if_necessary: @@ -655,10 +656,7 @@ def refresh(self, unsafely_update_root_if_necessary=True): # If an exception is raised during the metadata update attempts, we will # attempt to update root metadata once by recursing with a special argument - # to avoid further recursion. We use this bool and pull the recursion out - # of the except block so as to avoid unprintable nested NoWorkingMirrorError - # exceptions. -# retry_once = False + # to avoid further recursion. # Use default but sane information for timestamp metadata, and do not # require strict checks on its required length. @@ -691,7 +689,6 @@ def refresh(self, unsafely_update_root_if_necessary=True): if unsafely_update_root_if_necessary: logger.info('Valid top-level metadata cannot be downloaded. Unsafely ' 'update the Root metadata.') -# retry_once = True self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) self.refresh(unsafely_update_root_if_necessary=False) @@ -703,7 +700,6 @@ def refresh(self, unsafely_update_root_if_necessary=True): logger.info('No changes were detected from the mirrors for a given role' ', and that metadata that is available on disk has been found to be ' 'expired. Trying to update root in case of foul play.') -# retry_once = True self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) self.refresh(unsafely_update_root_if_necessary=False) @@ -715,13 +711,6 @@ def refresh(self, unsafely_update_root_if_necessary=True): 'expired. Your metadata is out of date.') raise - # Update failed and we aren't already in a retry. Try once more after - # updating root. -# if retry_once: -# self._update_metadata('root', DEFAULT_ROOT_UPPERLENGTH) -# self.refresh(unsafely_update_root_if_necessary=False) - - def _check_hashes(self, file_object, trusted_hashes): From 91379863017ad4c08300b9ad0e9df1f20aa0886a Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 14:14:26 -0400 Subject: [PATCH 14/18] Removing superstitious 0.1s sleeps. --- tests/test_indefinite_freeze_attack.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 56da64686a..9a8d436798 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -302,13 +302,11 @@ def test_with_tuf(self): # Now write to the repository repository.write() # And move the staged metadata to the "live" metadata. - time.sleep(0.1) # superstitious move to allow write to finish? shutil.rmtree(os.path.join(self.repository_directory, 'metadata')) shutil.copytree(os.path.join(self.repository_directory, 'metadata.staged'), os.path.join(self.repository_directory, 'metadata')) # Refresh metadata on the client. For this refresh, all data is not expired. - time.sleep(0.1) # superstitious move to allow copy to finish? logger.info('Test: Refreshing #1 - Initial metadata refresh occurring.') self.repository_updater.refresh() logger.info("Test: Refreshed #1 - Initial metadata refresh completed " From 4d8732bee844d90a55fbd1a1ddaad292627ef3c4 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 14:23:58 -0400 Subject: [PATCH 15/18] removing unnecessary loading of root keys in test --- tests/test_indefinite_freeze_attack.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index 9a8d436798..e46c6a8592 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -283,12 +283,6 @@ def test_with_tuf(self): 'password') repository.snapshot.load_signing_key(snapshot_private) - # Load root keys. - key_file = os.path.join(self.keystore_directory, 'root_key') - root_private = repo_tool.import_rsa_privatekey_from_file(key_file, - 'password') - repository.root.load_signing_key(root_private) - # Expire snapshot in 8s. 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 From 1ee1f92a4d7664342dc9f9cbc2026c42f05a32c2 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 14:27:40 -0400 Subject: [PATCH 16/18] added clarifying comments to repo and key loading --- tests/test_indefinite_freeze_attack.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_indefinite_freeze_attack.py b/tests/test_indefinite_freeze_attack.py index e46c6a8592..ce70d2b3bf 100755 --- a/tests/test_indefinite_freeze_attack.py +++ b/tests/test_indefinite_freeze_attack.py @@ -269,15 +269,16 @@ def test_with_tuf(self): # Modify the timestamp file on the remote repository. 'timestamp.json' # must be properly updated and signed with 'repository_tool.py', otherwise # the client will reject it as invalid metadata. + + # 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. key_file = os.path.join(self.keystore_directory, 'timestamp_key') timestamp_private = repo_tool.import_rsa_privatekey_from_file(key_file, 'password') - repository.timestamp.load_signing_key(timestamp_private) - - # Load snapshot keys. key_file = os.path.join(self.keystore_directory, 'snapshot_key') snapshot_private = repo_tool.import_rsa_privatekey_from_file(key_file, 'password') From c4ef697ec8e04def7c5ccab6d4ca4372835af932 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 16:39:47 -0400 Subject: [PATCH 17/18] Fix for newly discovered python 3 issue causing unprintable exceptions --- tuf/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tuf/__init__.py b/tuf/__init__.py index ba4328ea33..f8e52b5b7b 100755 --- a/tuf/__init__.py +++ b/tuf/__init__.py @@ -332,13 +332,13 @@ def __init__(self, mirror_errors): def __str__(self): all_errors = 'No working mirror was found:' - for mirror_url, mirror_error in self.mirror_errors.iteritems(): + for mirror_url, mirror_error in six.iteritems(self.mirror_errors): try: # http://docs.python.org/2/library/urlparse.html#urlparse.urlparse mirror_url_tokens = six.moves.urllib.parse.urlparse(mirror_url) except: - logging.exception('Failed to parse mirror URL: ' + repr(mirror_url)) + #logging.exception('Failed to parse mirror URL: ' + repr(mirror_url)) mirror_netloc = mirror_url else: From 7cd20fe0358988de21824dc0e7e3f9603e54fb3e Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 16 Mar 2016 16:45:58 -0400 Subject: [PATCH 18/18] Fixing another bug in tuf/__init__() and making the intended logging there happen. --- tuf/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tuf/__init__.py b/tuf/__init__.py index f8e52b5b7b..0899478e6c 100755 --- a/tuf/__init__.py +++ b/tuf/__init__.py @@ -30,6 +30,9 @@ import six +import logging +logger = logging.getLogger('tuf.__init__') + # Import 'tuf.formats' if a module tries to import the # entire tuf package (i.e., from tuf import *). __all__ = ['formats'] @@ -338,7 +341,7 @@ def __str__(self): mirror_url_tokens = six.moves.urllib.parse.urlparse(mirror_url) except: - #logging.exception('Failed to parse mirror URL: ' + repr(mirror_url)) + logger.exception('Failed to parse mirror URL: ' + repr(mirror_url)) mirror_netloc = mirror_url else: