diff --git a/mypy.ini b/mypy.ini index c14e6ab5..3595e5f5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,6 @@ [mypy] warn_unused_configs = True files = - securesystemslib/util.py, securesystemslib/signer/*.py, securesystemslib/storage.py, securesystemslib/gpg/constants.py diff --git a/securesystemslib/unittest_toolbox.py b/securesystemslib/unittest_toolbox.py deleted file mode 100755 index 7ad4e9e4..00000000 --- a/securesystemslib/unittest_toolbox.py +++ /dev/null @@ -1,136 +0,0 @@ -""" - - unittest_toolbox.py - - - Konstantin Andrianov. - - - March 26, 2012. - - - See LICENSE for licensing information. - - - Provides an array of various methods for unit testing. Use it instead of - actual unittest module. This module builds on unittest module. - Specifically, Modified_TestCase is a derived class from unittest.TestCase. -""" - -import os -import random -import shutil -import string -import tempfile -import unittest - - -class Modified_TestCase(unittest.TestCase): # pylint: disable=invalid-name - """ - - Provide additional test-setup methods to make testing - of module's methods-under-test as independent as possible. - - If you want to modify setUp()/tearDown() do: - class Your_Test_Class(modified_TestCase): - def setUp(): - your setup modification - your setup modification - ... - modified_TestCase.setUp(self) - - - make_temp_directory(self, directory=None): - Creates and returns an absolute path of a temporary directory. - - make_temp_file(self, suffix='.txt', directory=None): - Creates and returns an absolute path of an empty temp file. - - make_temp_data_file(self, suffix='', directory=None, data = junk_data): - Returns an absolute path of a temp file containing some data. - - random_path(self, length = 7): - Generate a 'random' path consisting of n-length strings of random chars. - - - Static Methods: - -------------- - Following methods are static because they technically don't operate - on any instances of the class, what they do is: they modify class variables - (dictionaries) that are shared among all instances of the class. So - it is possible to call them without instantiating the class. - - random_string(length=7): - Generate a 'length' long string of random characters. - """ - - def setUp(self): - self._cleanup = [] - - def tearDown(self): - for cleanup_function in self._cleanup: - # Perform clean up by executing clean-up functions. - try: - # OSError will occur if the directory was already removed. - cleanup_function() - - except OSError: - pass - - def make_temp_directory(self, directory=None): - """Creates and returns an absolute path of a directory.""" - prefix = self.__class__.__name__ + "_" - temp_directory = tempfile.mkdtemp(prefix=prefix, dir=directory) - - def _destroy_temp_directory(): - shutil.rmtree(temp_directory) - - self._cleanup.append(_destroy_temp_directory) - return temp_directory - - def make_temp_file(self, suffix=".txt", directory=None): - """Creates and returns an absolute path of an empty file.""" - - prefix = "tmp_file_" + self.__class__.__name__ + "_" - temp_file = tempfile.mkstemp( - suffix=suffix, prefix=prefix, dir=directory - ) - - def _destroy_temp_file(): - os.unlink(temp_file[1]) - - self._cleanup.append(_destroy_temp_file) - return temp_file[1] - - def make_temp_data_file(self, suffix="", directory=None, data="junk data"): - """Returns an absolute path of a temp file containing data.""" - - temp_file_path = self.make_temp_file(suffix=suffix, directory=directory) - temp_file = ( - open( # pylint: disable=unspecified-encoding,consider-using-with - temp_file_path, "wt" - ) - ) - temp_file.write(data) - temp_file.close() - - return temp_file_path - - def random_path(self, length=7): - """Generate a 'random' path consisting of random n-length strings.""" - - rand_path = "/" + self.random_string(length) - for _ in range(2): - rand_path = os.path.join(rand_path, self.random_string(length)) - - return rand_path - - @staticmethod - def random_string(length=15): - """Generate a random string of specified length.""" - - rand_str = "" - for _ in range(length): - rand_str += random.choice("abcdefABCDEF" + string.digits) # nosec - - return rand_str diff --git a/securesystemslib/util.py b/securesystemslib/util.py deleted file mode 100644 index 5efe3489..00000000 --- a/securesystemslib/util.py +++ /dev/null @@ -1,455 +0,0 @@ -""" - - util.py - - - Konstantin Andrianov - - - March 24, 2012. Derived from original util.py written by Geremy Condra. - - - See LICENSE for licensing information. - - - Provides utility services. This module supplies utility functions such as: - get_file_details() that computes the length and hash of a file, import_json - that tries to import a working json module, load_json_* functions, etc. -""" - -import json -import logging -import os -from typing import IO, Any, Dict, List, Optional, Sequence, Tuple, Union - -from securesystemslib import exceptions, formats -from securesystemslib.hash import digest_fileobject -from securesystemslib.storage import FilesystemBackend, StorageBackendInterface - -logger = logging.getLogger(__name__) - - -def get_file_details( # pylint: disable=dangerous-default-value - filepath: str, - hash_algorithms: List[str] = ["sha256"], - storage_backend: Optional[StorageBackendInterface] = None, -) -> Tuple[int, Dict[str, str]]: - """ - - To get file's length and hash information. The hash is computed using the - sha256 algorithm. This function is used in the signerlib.py and updater.py - modules. - - - filepath: - Absolute file path of a file. - - hash_algorithms: - A list of hash algorithms with which the file's hash should be computed. - Defaults to ['sha256'] - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - - securesystemslib.exceptions.FormatError: If hash of the file does not match - HASHDICT_SCHEMA. - - securesystemslib.exceptions.StorageError: The file at "filepath" cannot be - opened or found. - - - A tuple (length, hashes) describing 'filepath'. - """ - - # Making sure that the format of 'filepath' is a path string. - # 'securesystemslib.exceptions.FormatError' is raised on incorrect format. - formats.PATH_SCHEMA.check_match(filepath) - formats.HASHALGORITHMS_SCHEMA.check_match(hash_algorithms) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - file_length = get_file_length(filepath, storage_backend) - file_hashes = get_file_hashes(filepath, hash_algorithms, storage_backend) - - return file_length, file_hashes - - -def get_file_hashes( # pylint: disable=dangerous-default-value - filepath: str, - hash_algorithms: List[str] = ["sha256"], - storage_backend: Optional[StorageBackendInterface] = None, -) -> Dict[str, str]: - """ - - Compute hash(es) of the file at filepath using each of the specified - hash algorithms. If no algorithms are specified, then the hash is - computed using the SHA-256 algorithm. - - - filepath: - Absolute file path of a file. - - hash_algorithms: - A list of hash algorithms with which the file's hash should be computed. - Defaults to ['sha256'] - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - - securesystemslib.exceptions.FormatError: If hash of the file does not match - HASHDICT_SCHEMA. - - securesystemslib.exceptions.StorageError: The file at "filepath" cannot be - opened or found. - - - A dictionary conforming to securesystemslib.formats.HASHDICT_SCHEMA - containing information about the hashes of the file at "filepath". - """ - - # Making sure that the format of 'filepath' is a path string. - # 'securesystemslib.exceptions.FormatError' is raised on incorrect format. - formats.PATH_SCHEMA.check_match(filepath) - formats.HASHALGORITHMS_SCHEMA.check_match(hash_algorithms) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - file_hashes = {} - - with storage_backend.get(filepath) as fileobj: - # Obtaining hash of the file. - for algorithm in hash_algorithms: - digest_object = digest_fileobject(fileobj, algorithm) - file_hashes.update({algorithm: digest_object.hexdigest()}) - - # Performing a format check to ensure 'file_hash' corresponds HASHDICT_SCHEMA. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. - formats.HASHDICT_SCHEMA.check_match(file_hashes) - - return file_hashes - - -def get_file_length( - filepath: str, storage_backend: Optional[StorageBackendInterface] = None -) -> int: - """ - - To get file's length information. - - - filepath: - Absolute file path of a file. - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - - securesystemslib.exceptions.StorageError: The file at "filepath" cannot be - opened or found. - - - The length, in bytes, of the file at 'filepath'. - """ - - # Making sure that the format of 'filepath' is a path string. - # 'securesystemslib.exceptions.FormatError' is raised on incorrect format. - formats.PATH_SCHEMA.check_match(filepath) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - return storage_backend.getsize(filepath) - - -def persist_temp_file( - temp_file: IO, - persist_path: str, - storage_backend: Optional[StorageBackendInterface] = None, - should_close: bool = True, - restrict: bool = False, -) -> None: - """ - - Copies 'temp_file' (a file like object) to a newly created non-temp file at - 'persist_path'. - - - temp_file: - File object to persist, typically a file object returned by one of the - interfaces in the tempfile module of the standard library. - - persist_path: - File path to create the persistent file in. - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - should_close: - A boolean indicating whether the file should be closed after it has been - persisted. Default is True, the file is closed. - - restrict: - A boolean indicating whether the file should have restricted privileges. - What evactly counts as restricted privileges is an implementation detail - of the backing StorageBackendInterface implementation. - - - securesystemslib.exceptions.StorageError: If file cannot be written. - - - None. - """ - - if storage_backend is None: - storage_backend = FilesystemBackend() - - storage_backend.put(temp_file, persist_path, restrict=restrict) - - if should_close: - temp_file.close() - - -def ensure_parent_dir( - filename: str, storage_backend: Optional[StorageBackendInterface] = None -) -> None: - """ - - To ensure existence of the parent directory of 'filename'. If the parent - directory of 'name' does not exist, create it. - - Example: If 'filename' is '/a/b/c/d.txt', and only the directory '/a/b/' - exists, then directory '/a/b/c/d/' will be created. - - - filename: - A path string. - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - - securesystemslib.exceptions.FormatError: If 'filename' is improperly - formatted. - securesystemslib.exceptions.StorageError: When folder cannot be created. - - - A directory is created whenever the parent directory of 'filename' does not - exist. - - - None. - """ - - # Ensure 'filename' corresponds to 'PATH_SCHEMA'. - # Raise 'securesystemslib.exceptions.FormatError' on a mismatch. - formats.PATH_SCHEMA.check_match(filename) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - # Split 'filename' into head and tail, check if head exists. - directory = os.path.split(filename)[0] - - # Check for cases where filename is without directory like 'file.txt' - # and as a result directory is an empty string - if directory: - storage_backend.create_folder(directory) - - -def file_in_confined_directories( - filepath: str, confined_directories: Sequence[str] -) -> bool: - """ - - Check if the directory containing 'filepath' is in the list/tuple of - 'confined_directories'. - - - filepath: - A string representing the path of a file. The following example path - strings are viewed as files and not directories: 'a/b/c', 'a/b/c.txt'. - - confined_directories: - A sequence (such as list, or tuple) of directory strings. - - - securesystemslib.exceptions.FormatError: On incorrect format of the input. - - - Boolean. True, if path is either the empty string - or in 'confined_paths'; False, otherwise. - """ - - # Do the arguments have the correct format? - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. - formats.PATH_SCHEMA.check_match(filepath) - formats.NAMES_SCHEMA.check_match(confined_directories) - - for confined_directory in confined_directories: - # The empty string (arbitrarily chosen) signifies the client is confined - # to all directories and subdirectories. No need to check 'filepath'. - if confined_directory == "": - return True - - # Normalized paths needed, to account for up-level references, etc. - # callers have the option of setting the list of directories in - # 'confined_directories'. - filepath = os.path.normpath(filepath) - confined_directory = os.path.normpath(confined_directory) - - # A caller may restrict himself to specific directories on the - # remote repository. The list of paths in 'confined_path', not including - # each path's subdirectories, are the only directories the client will - # download targets from. - if os.path.dirname(filepath) == confined_directory: - return True - - return False - - -def load_json_string(data: Union[str, bytes]) -> Any: - """ - - Deserialize 'data' (JSON string) to a Python object. - - - data: - A JSON string. - - - securesystemslib.exceptions.Error, if 'data' cannot be deserialized to a - Python object. - - - None. - - - Deserialized object. For example, a dictionary. - """ - - deserialized_object = None - - try: - deserialized_object = json.loads(data) - - except TypeError: - message = "Invalid JSON string: " + repr(data) - raise exceptions.Error(message) # pylint: disable=raise-missing-from - - except ValueError: - message = "Cannot deserialize to a Python object: " + repr(data) - raise exceptions.Error(message) # pylint: disable=raise-missing-from - - return deserialized_object - - -def load_json_file( - filepath: str, storage_backend: Optional[StorageBackendInterface] = None -) -> Any: - """ - - Deserialize a JSON object from a file containing the object. - - - filepath: - Absolute path of JSON file. - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. When no object is - passed a FilesystemBackend will be instantiated and used. - - - securesystemslib.exceptions.FormatError: If 'filepath' is improperly - formatted. - - securesystemslib.exceptions.Error: If 'filepath' cannot be deserialized to - a Python object. - - securesystemslib.exceptions.StorageError: If file cannot be loaded. - - IOError in case of runtime IO exceptions. - - - None. - - - Deserialized object. For example, a dictionary. - """ - - # Making sure that the format of 'filepath' is a path string. - # securesystemslib.exceptions.FormatError is raised on incorrect format. - formats.PATH_SCHEMA.check_match(filepath) - - if storage_backend is None: - storage_backend = FilesystemBackend() - - deserialized_object = None - with storage_backend.get(filepath) as file_obj: - raw_data = file_obj.read().decode("utf-8") - - try: - deserialized_object = json.loads(raw_data) - - except (ValueError, TypeError): - raise exceptions.Error( # pylint: disable=raise-missing-from - "Cannot deserialize to a" " Python object: " + filepath - ) - - return deserialized_object - - -def digests_are_equal(digest1: str, digest2: str) -> bool: - """ - - While protecting against timing attacks, compare the hexadecimal arguments - and determine if they are equal. - - - digest1: - The first hexadecimal string value to compare. - - digest2: - The second hexadecimal string value to compare. - - - securesystemslib.exceptions.FormatError: If the arguments are improperly - formatted. - - - None. - - - Return True if 'digest1' is equal to 'digest2', False otherwise. - """ - - # Ensure the arguments have the appropriate number of objects and object - # types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. - formats.HEX_SCHEMA.check_match(digest1) - formats.HEX_SCHEMA.check_match(digest2) - - if len(digest1) != len(digest2): - return False - - are_equal = True - - for val1, val2 in zip(digest1, digest2): - if val1 != val2: - are_equal = False - - return are_equal diff --git a/tests/test_util.py b/tests/test_util.py deleted file mode 100644 index e108b348..00000000 --- a/tests/test_util.py +++ /dev/null @@ -1,416 +0,0 @@ -""" - - test_util.py - - - Konstantin Andrianov. - - - February 1, 2013. - - - See LICENSE for licensing information. - - - Unit test for 'util.py' -""" - -import logging -import os -import stat -import tempfile -import timeit -import unittest - -import securesystemslib.hash -import securesystemslib.util -from securesystemslib import exceptions, unittest_toolbox - -logger = logging.getLogger(__name__) - - -class TestUtil( - unittest_toolbox.Modified_TestCase -): # pylint: disable=missing-class-docstring - def setUp(self): - unittest_toolbox.Modified_TestCase.setUp(self) - self.temp_fileobj = tempfile.TemporaryFile() - - def tearDown(self): - unittest_toolbox.Modified_TestCase.tearDown(self) - self.temp_fileobj.close() - - def test_B1_get_file_details(self): - # Goal: Verify proper output given certain expected/unexpected input. - - # Making a temporary file. - filepath = self.make_temp_data_file() - - # Computing the hash and length of the tempfile. - digest_object = securesystemslib.hash.digest_filename( - filepath, algorithm="sha256" - ) - file_hash = {"sha256": digest_object.hexdigest()} - file_length = os.path.getsize(filepath) - - # Test: Expected input. - self.assertEqual( - securesystemslib.util.get_file_details(filepath), - (file_length, file_hash), - ) - - # Test: Incorrect input. - bogus_inputs = [ - self.random_string(), - 1234, - [self.random_string()], - {"a": "b"}, - None, - ] - - for bogus_input in bogus_inputs: - if isinstance(bogus_input, str): - self.assertRaises( - securesystemslib.exceptions.StorageError, - securesystemslib.util.get_file_details, - bogus_input, - ) - else: - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.get_file_details, - bogus_input, - ) - - def test_B2_get_file_hashes(self): - # Goal: Verify proper output given certain expected/unexpected input. - - # Making a temporary file. - filepath = self.make_temp_data_file() - - # Computing the hash of the tempfile. - digest_object = securesystemslib.hash.digest_filename( - filepath, algorithm="sha256" - ) - file_hash = {"sha256": digest_object.hexdigest()} - - # Test: Expected input. - self.assertEqual( - securesystemslib.util.get_file_hashes(filepath), file_hash - ) - - # Test: Incorrect input. - bogus_inputs = [ - self.random_string(), - 1234, - [self.random_string()], - {"a": "b"}, - None, - ] - - for bogus_input in bogus_inputs: - if isinstance(bogus_input, str): - self.assertRaises( - securesystemslib.exceptions.StorageError, - securesystemslib.util.get_file_hashes, - bogus_input, - ) - else: - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.get_file_hashes, - bogus_input, - ) - - def test_B3_get_file_length(self): - # Goal: Verify proper output given certain expected/unexpected input. - - # Making a temporary file. - filepath = self.make_temp_data_file() - - # Computing the length of the tempfile. - file_length = os.path.getsize(filepath) - - # Test: Expected input. - self.assertEqual( - securesystemslib.util.get_file_length(filepath), file_length - ) - - # Test: Incorrect input. - bogus_inputs = [ - self.random_string(), - 1234, - [self.random_string()], - {"a": "b"}, - None, - ] - - for bogus_input in bogus_inputs: - if isinstance(bogus_input, str): - self.assertRaises( - securesystemslib.exceptions.StorageError, - securesystemslib.util.get_file_length, - bogus_input, - ) - else: - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.get_file_length, - bogus_input, - ) - - def test_B4_ensure_parent_dir(self): - existing_parent_dir = self.make_temp_directory() - non_existing_parent_dir = os.path.join(existing_parent_dir, "a", "b") - - for parent_dir in [ - existing_parent_dir, - non_existing_parent_dir, - 12, - [3], - ]: - if isinstance(parent_dir, str): - securesystemslib.util.ensure_parent_dir( - os.path.join(parent_dir, "a.txt") - ) - self.assertTrue(os.path.isdir(parent_dir)) - - else: - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.ensure_parent_dir, - parent_dir, - ) - - # When we call ensure_parent_dir with filepath arg like "a.txt", - # then the directory of that filepath will be an empty string. - # We want to make sure that securesyslib.storage.create_folder() - # won't be called with an empty string and thus raise an exception. - # If an exception is thrown the test will fail. - securesystemslib.util.ensure_parent_dir("a.txt") - - def test_B5_file_in_confined_directories(self): - # Goal: Provide invalid input for 'filepath' and 'confined_directories'. - # Include inputs like: '[1, 2, "a"]' and such... - # Reference to 'file_in_confined_directories()' to improve readability. - in_confined_directory = ( - securesystemslib.util.file_in_confined_directories - ) - list_of_confined_directories = ["a", 12, {"a": "a"}, [1]] - list_of_filepaths = [12, ["a"], {"a": "a"}, "a"] - for bogus_confined_directory in list_of_confined_directories: - for filepath in list_of_filepaths: - self.assertRaises( - securesystemslib.exceptions.FormatError, - in_confined_directory, - filepath, - bogus_confined_directory, - ) - - # Test: Inputs that evaluate to False. - confined_directories = ["a/b/", "a/b/c/d/"] - self.assertFalse( - in_confined_directory("a/b/c/1.txt", confined_directories) - ) - - confined_directories = ["a/b/c/d/e/"] - self.assertFalse(in_confined_directory("a", confined_directories)) - self.assertFalse(in_confined_directory("a/b", confined_directories)) - self.assertFalse(in_confined_directory("a/b/c", confined_directories)) - self.assertFalse(in_confined_directory("a/b/c/d", confined_directories)) - # Below, 'e' is a file in the 'a/b/c/d/' directory. - self.assertFalse( - in_confined_directory("a/b/c/d/e", confined_directories) - ) - - # Test: Inputs that evaluate to True. - self.assertTrue(in_confined_directory("a/b/c.txt", [""])) - self.assertTrue(in_confined_directory("a/b/c.txt", ["a/b/"])) - self.assertTrue(in_confined_directory("a/b/c.txt", ["x", ""])) - self.assertTrue(in_confined_directory("a/b/c/..", ["a/"])) - - def test_B7_load_json_string(self): - # Test normal case. - data = ["a", {"b": ["c", None, 30.3, 29]}] - json_string = securesystemslib.util.json.dumps(data) - self.assertEqual( - data, securesystemslib.util.load_json_string(json_string) - ) - - # Test invalid arguments. - self.assertRaises( - securesystemslib.exceptions.Error, - securesystemslib.util.load_json_string, - 8, - ) - invalid_json_string = json_string + "." - self.assertRaises( - securesystemslib.exceptions.Error, - securesystemslib.util.load_json_string, - invalid_json_string, - ) - - def test_B8_load_json_file(self): - data = ["a", {"b": ["c", None, 30.3, 29]}] - filepath = self.make_temp_file() - fileobj = ( - open( # pylint: disable=unspecified-encoding,consider-using-with - filepath, "wt" - ) - ) - securesystemslib.util.json.dump(data, fileobj) - fileobj.close() - self.assertEqual(data, securesystemslib.util.load_json_file(filepath)) - - # Improperly formatted arguments. - for bogus_arg in [1, [b"a"], {"a": b"b"}]: - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.load_json_file, - bogus_arg, - ) - - # Non-existent path. - self.assertRaises( - securesystemslib.exceptions.StorageError, - securesystemslib.util.load_json_file, - "non-existent.json", - ) - - # Invalid JSON content. - filepath_bad_data = self.make_temp_file() - fileobj = ( - open( # pylint: disable=unspecified-encoding,consider-using-with - filepath_bad_data, "wt" - ) - ) - fileobj.write("junk data") - fileobj.close() - - self.assertRaises( - securesystemslib.exceptions.Error, - securesystemslib.util.load_json_file, - filepath_bad_data, - ) - - def test_B9_persist_temp_file(self): - # Destination directory to save the temporary file in. - dest_temp_dir = self.make_temp_directory() - - # Test the default of persisting the file and closing the tmpfile - dest_path = os.path.join(dest_temp_dir, self.random_string()) - tmpfile = tempfile.TemporaryFile() - tmpfile.write(self.random_string().encode("utf-8")) - - # Write a file with restricted permissions - securesystemslib.util.persist_temp_file( - tmpfile, dest_path, restrict=True - ) - self.assertTrue(dest_path) - - # Need to set also the stat.S_IFREG bit to match the st_mode output - # stat.S_IFREG - Regular file - expected_mode = stat.S_IFREG | stat.S_IRUSR | stat.S_IWUSR - if os.name == "nt": - # Windows only supports setting the read-only attribute. - expected_mode = ( - stat.S_IFREG - | stat.S_IWUSR - | stat.S_IRUSR - | stat.S_IWGRP - | stat.S_IRGRP - | stat.S_IWOTH - | stat.S_IROTH - ) - self.assertEqual(os.stat(dest_path).st_mode, expected_mode) - self.assertTrue(tmpfile.closed) - - # Test persisting a file without automatically closing the tmpfile - dest_path2 = os.path.join(dest_temp_dir, self.random_string()) - tmpfile = tempfile.TemporaryFile() - tmpfile.write(self.random_string().encode("utf-8")) - securesystemslib.util.persist_temp_file( - tmpfile, dest_path2, should_close=False - ) - self.assertFalse(tmpfile.closed) - - # Test persisting a file with an empty filename - with self.assertRaises(exceptions.StorageError): - securesystemslib.util.persist_temp_file(tmpfile, "") - - tmpfile.close() - - def test_C5_unittest_toolbox_make_temp_directory(self): - # Verify that the tearDown function does not fail when - # unittest_toolbox.make_temp_directory deletes the generated temp directory - # here. - temp_directory = self.make_temp_directory() - os.rmdir(temp_directory) - - def test_c5_unittest_toolbox_random_path(self): - # Verify that a random path can be generated with unittest_toolbox. - random_path = self.random_path(length=10) - self.assertTrue( - securesystemslib.formats.PATH_SCHEMA.matches(random_path) - ) - self.assertTrue( # pylint: disable=redundant-unittest-assert - 10, len(random_path) - ) - - def test_digests_are_equal(self): - digest = ( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ) - - # Normal case: test for digests that are equal. - self.assertTrue(securesystemslib.util.digests_are_equal(digest, digest)) - - # Normal case: test for digests that are unequal. - self.assertFalse( - securesystemslib.util.digests_are_equal(digest, "0a8df1") - ) - - # Test for invalid arguments. - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.digests_are_equal, - 7, - digest, - ) - self.assertRaises( - securesystemslib.exceptions.FormatError, - securesystemslib.util.digests_are_equal, - digest, - 7, - ) - - # Test that digests_are_equal() takes the same amount of time to compare - # equal and unequal arguments. - runtime = timeit.timeit( - 'digests_are_equal("ab8df", "ab8df")', - setup="from securesystemslib.util import digests_are_equal", - number=100000, - ) - - runtime2 = timeit.timeit( - 'digests_are_equal("ab8df", "1b8df")', - setup="from securesystemslib.util import digests_are_equal", - number=100000, - ) - - runtime3 = timeit.timeit('"ab8df" == "ab8df"', number=100000) - - runtime4 = timeit.timeit('"ab8df" == "1b8df"', number=1000000) - - # The ratio for the 'digest_are_equal' runtimes should be at or near 1. - ratio_digests_are_equal = abs(runtime2 / runtime) - - # The ratio for the variable-time runtimes should be (>1) & at or near 10? - ratio_variable_compare = abs(runtime4 / runtime3) - - self.assertTrue(ratio_digests_are_equal < ratio_variable_compare) - - -# Run unit test. -if __name__ == "__main__": - unittest.main()