From 40c08c351e35bd03ca2c7c20acebf3dd37f53311 Mon Sep 17 00:00:00 2001 From: Marina Moore Date: Fri, 1 Oct 2021 12:53:52 -0400 Subject: [PATCH 1/3] Initial snapshot abstraction design This design was inspired by the StorageBackendInterface in securesystemslib. Signed-off-by: Marina Moore --- tuf/snapshot.py | 122 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tuf/snapshot.py diff --git a/tuf/snapshot.py b/tuf/snapshot.py new file mode 100644 index 0000000000..18e99da8ea --- /dev/null +++ b/tuf/snapshot.py @@ -0,0 +1,122 @@ +class SnapshotInterface(): + """ + + Defines an interface for abstract snapshot metadata operations + to be implemented for a variety of snapshot creation methods, + including the classic manifest snapshot metadata and snapshot + merkle trees + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def add_to_snapshot(self, rolename : str): + """ + + Indicate to the snapshot interface that 'rolename' + should be included in the next snapshot generation. + + + rolename: + The name of the role to be added + + + TODO + + + None + """ + raise NotImplementedError # pragma: no cover + + + + + @abc.abstractmethod + def remove_from_snapshot(self, rolename : str): + """ + + Indicate to the snapshot interface that 'rolename' + should be removed from the next snapshot generation. + + + rolename: + The name of the role to be removed + + + TODO + + + None + """ + raise NotImplementedError # pragma: no cover + + + + + @abc.abstractmethod + def generate_snapshot_metadata(metadata_directory, version, expiration_date, + storage_backend, consistent_snapshot=False, + repository_name='default', use_length=False, use_hashes=False): + """ + + Create the snapshot metadata + + + metadata_directory: + The directory containing the 'root.json' and 'targets.json' metadata + files. + + version: + The metadata version number. Clients use the version number to + determine if the downloaded version is newer than the one currently + trusted. + + expiration_date: + The expiration date of the metadata file. + Conformant to 'securesystemslib.formats.ISO8601_DATETIME_SCHEMA'. + + storage_backend: + An object which implements + securesystemslib.storage.StorageBackendInterface. + + consistent_snapshot: + Boolean. If True, a file digest is expected to be prepended to the + filename of any target file located in the targets directory. Each digest + is stripped from the target filename and listed in the snapshot metadata. + + repository_name: + The name of the repository. If not supplied, 'rolename' is added to the + 'default' repository. + + use_length: + Whether to include the optional length attribute for targets + metadata files in the snapshot metadata. + Default is False to save bandwidth but without losing security + from rollback attacks. + Read more at section 5.6 from the Mercury paper: + https://www.usenix.org/conference/atc17/technical-sessions/presentation/kuppusamy + + use_hashes: + Whether to include the optional hashes attribute for targets + metadata files in the snapshot metadata. + Default is False to save bandwidth but without losing security + from rollback attacks. + Read more at section 5.6 from the Mercury paper: + https://www.usenix.org/conference/atc17/technical-sessions/presentation/kuppusamy + + + securesystemslib.exceptions.FormatError, if the arguments are improperly + formatted. + + securesystemslib.exceptions.Error, if an error occurred trying to generate + the snapshot metadata object. + + + The 'root.json' and 'targets.json' files are read. + + + TODO + """ + raise NotImplementedError # pragma: no cover + + From 7386bbbde4951d96106a0d7a3eef57f37f3006a8 Mon Sep 17 00:00:00 2001 From: Marina Moore Date: Mon, 4 Oct 2021 15:50:00 -0400 Subject: [PATCH 2/3] add implementation of ManifestSnapshot This commit moves the implementation of generate_snapshot_metadata to the snapshot backend. In the future, this implementation may be simplified by keeping track of the fileinfodict in the ManifestSnapshot class instead of re-computing these on each run. Signed-off-by: Marina Moore --- tests/test_repository_lib.py | 31 ++++--- tuf/repository_lib.py | 169 +++-------------------------------- tuf/snapshot.py | 140 ++++++++++++++++++++++++++++- 3 files changed, 168 insertions(+), 172 deletions(-) diff --git a/tests/test_repository_lib.py b/tests/test_repository_lib.py index 96dcb0e0e0..d88852cbc3 100755 --- a/tests/test_repository_lib.py +++ b/tests/test_repository_lib.py @@ -38,6 +38,7 @@ import tuf.roledb import tuf.keydb import tuf.settings +import tuf.snapshot import tuf.repository_lib as repo_lib import tuf.repository_tool as repo_tool @@ -499,8 +500,10 @@ def test_generate_snapshot_metadata(self): metadata_directory, version, expiration_date, storage_backend = \ self._setup_generate_snapshot_metadata_test() + snapshot_backend = tuf.snapshot.ManifestSnapshot() + snapshot_metadata = \ - repo_lib.generate_snapshot_metadata(metadata_directory, version, + snapshot_backend.generate_snapshot_metadata(metadata_directory, version, expiration_date, storage_backend, consistent_snapshot=False) @@ -508,16 +511,16 @@ def test_generate_snapshot_metadata(self): # Test improperly formatted arguments. - self.assertRaises(securesystemslib.exceptions.FormatError, repo_lib.generate_snapshot_metadata, + self.assertRaises(securesystemslib.exceptions.FormatError, snapshot_backend.generate_snapshot_metadata, 3, version, expiration_date, consistent_snapshot=False, storage_backend=storage_backend) - self.assertRaises(securesystemslib.exceptions.FormatError, repo_lib.generate_snapshot_metadata, + self.assertRaises(securesystemslib.exceptions.FormatError, snapshot_backend.generate_snapshot_metadata, metadata_directory, '3', expiration_date, storage_backend, consistent_snapshot=False) - self.assertRaises(securesystemslib.exceptions.FormatError, repo_lib.generate_snapshot_metadata, + self.assertRaises(securesystemslib.exceptions.FormatError, snapshot_backend.generate_snapshot_metadata, metadata_directory, version, '3', storage_backend, consistent_snapshot=False) - self.assertRaises(securesystemslib.exceptions.FormatError, repo_lib.generate_snapshot_metadata, + self.assertRaises(securesystemslib.exceptions.FormatError, snapshot_backend.generate_snapshot_metadata, metadata_directory, version, expiration_date, 3, storage_backend) @@ -527,8 +530,10 @@ def test_generate_snapshot_metadata_with_length(self): metadata_directory, version, expiration_date, storage_backend = \ self._setup_generate_snapshot_metadata_test() + snapshot_backend = tuf.snapshot.ManifestSnapshot() + snapshot_metadata = \ - repo_lib.generate_snapshot_metadata(metadata_directory, version, + snapshot_backend.generate_snapshot_metadata(metadata_directory, version, expiration_date, storage_backend, consistent_snapshot=False, @@ -541,7 +546,7 @@ def test_generate_snapshot_metadata_with_length(self): # In the metadata_directory, there are files with format: # 1.root.json. The prefix number should be removed. stripped_filename, version = \ - repo_lib._strip_version_number(metadata_filename, + repo_lib.strip_version_number(metadata_filename, consistent_snapshot=True) # In the repository, the file "role_file.xml" have been added to make @@ -558,8 +563,10 @@ def test_generate_snapshot_metadata_with_hashes(self): metadata_directory, version, expiration_date, storage_backend = \ self._setup_generate_snapshot_metadata_test() + snapshot_backend = tuf.snapshot.ManifestSnapshot() + snapshot_metadata = \ - repo_lib.generate_snapshot_metadata(metadata_directory, version, + snapshot_backend.generate_snapshot_metadata(metadata_directory, version, expiration_date, storage_backend, consistent_snapshot=False, @@ -572,7 +579,7 @@ def test_generate_snapshot_metadata_with_hashes(self): # In the metadata_directory, there are files with format: # 1.root.json. The prefix number should be removed. stripped_filename, version = \ - repo_lib._strip_version_number(metadata_filename, + repo_lib.strip_version_number(metadata_filename, consistent_snapshot=True) # In the repository, the file "role_file.xml" have been added to make @@ -589,8 +596,10 @@ def test_generate_snapshot_metadata_with_hashes_and_length(self): metadata_directory, version, expiration_date, storage_backend = \ self._setup_generate_snapshot_metadata_test() + snapshot_backend = tuf.snapshot.ManifestSnapshot() + snapshot_metadata = \ - repo_lib.generate_snapshot_metadata(metadata_directory, version, + snapshot_backend.generate_snapshot_metadata(metadata_directory, version, expiration_date, storage_backend, consistent_snapshot=False, @@ -604,7 +613,7 @@ def test_generate_snapshot_metadata_with_hashes_and_length(self): # In the metadata_directory, there are files with format: # 1.root.json. The prefix number should be removed. stripped_filename, version = \ - repo_lib._strip_version_number(metadata_filename, + repo_lib.strip_version_number(metadata_filename, consistent_snapshot=True) # In the repository, the file "role_file.xml" have been added to make diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index 642447d8b3..5154ce15e5 100644 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -47,6 +47,7 @@ from tuf import roledb from tuf import settings from tuf import sig +from tuf import snapshot # See 'log.py' to learn how logging is handled in TUF. @@ -90,7 +91,7 @@ def _generate_and_write_metadata(rolename, metadata_filename, increment_version_number=True, repository_name='default', use_existing_fileinfo=False, use_timestamp_length=True, use_timestamp_hashes=True, use_snapshot_length=False, - use_snapshot_hashes=False): + use_snapshot_hashes=False, snapshot_backend=None): """ Non-public function that can generate and write the metadata for the specified 'rolename'. It also increments the version number of 'rolename' if @@ -117,7 +118,9 @@ def _generate_and_write_metadata(rolename, metadata_filename, elif rolename == 'snapshot': - metadata = generate_snapshot_metadata(metadata_directory, + if snapshot_backend == None: + snapshot_backend = snapshot.ManifestSnapshot() + metadata = snapshot_backend.generate_snapshot_metadata(metadata_directory, roleinfo['version'], roleinfo['expires'], storage_backend, consistent_snapshot, repository_name, use_length=use_snapshot_length, use_hashes=use_snapshot_hashes) @@ -393,7 +396,7 @@ def _delete_obsolete_metadata(metadata_directory, snapshot_metadata, # have a prepended version number even though the repository is now # a non-consistent one. if metadata_role not in snapshot_metadata['meta']: - metadata_role, junk = _strip_version_number(metadata_role, + metadata_role, junk = strip_version_number(metadata_role, consistent_snapshot) else: @@ -442,7 +445,7 @@ def _get_written_metadata(metadata_signable): -def _strip_version_number(metadata_filename, consistent_snapshot): +def strip_version_number(metadata_filename, consistent_snapshot): """ Strip from 'metadata_filename' any version number (in the expected '{dirname}/.rolename.' format) that @@ -851,7 +854,7 @@ def get_delegated_roles_metadata_filenames(metadata_directory, # Example: '10.django.json' --> 'django.json' consistent = \ metadata_role.endswith('root.json') or consistent_snapshot == True - metadata_name, junk = _strip_version_number(metadata_role, + metadata_name, junk = strip_version_number(metadata_role, consistent) if metadata_name.endswith(METADATA_EXTENSION): @@ -1519,7 +1522,7 @@ def _generate_targets_fileinfo(target_files, targets_directory, -def _get_hashes_and_length_if_needed(use_length, use_hashes, full_file_path, +def get_hashes_and_length_if_needed(use_length, use_hashes, full_file_path, storage_backend): """ Calculate length and hashes only if they are required, @@ -1541,158 +1544,6 @@ def _get_hashes_and_length_if_needed(use_length, use_hashes, full_file_path, -def generate_snapshot_metadata(metadata_directory, version, expiration_date, - storage_backend, consistent_snapshot=False, - repository_name='default', use_length=False, use_hashes=False): - """ - - Create the snapshot metadata. The minimum metadata must exist (i.e., - 'root.json' and 'targets.json'). This function searches - 'metadata_directory' and the resulting snapshot file will list all the - delegated roles found there. - - - metadata_directory: - The directory containing the 'root.json' and 'targets.json' metadata - files. - - version: - The metadata version number. Clients use the version number to - determine if the downloaded version is newer than the one currently - trusted. - - expiration_date: - The expiration date of the metadata file. - Conformant to 'securesystemslib.formats.ISO8601_DATETIME_SCHEMA'. - - storage_backend: - An object which implements - securesystemslib.storage.StorageBackendInterface. - - consistent_snapshot: - Boolean. If True, a file digest is expected to be prepended to the - filename of any target file located in the targets directory. Each digest - is stripped from the target filename and listed in the snapshot metadata. - - repository_name: - The name of the repository. If not supplied, 'rolename' is added to the - 'default' repository. - - use_length: - Whether to include the optional length attribute for targets - metadata files in the snapshot metadata. - Default is False to save bandwidth but without losing security - from rollback attacks. - Read more at section 5.6 from the Mercury paper: - https://www.usenix.org/conference/atc17/technical-sessions/presentation/kuppusamy - - use_hashes: - Whether to include the optional hashes attribute for targets - metadata files in the snapshot metadata. - Default is False to save bandwidth but without losing security - from rollback attacks. - Read more at section 5.6 from the Mercury paper: - https://www.usenix.org/conference/atc17/technical-sessions/presentation/kuppusamy - - - securesystemslib.exceptions.FormatError, if the arguments are improperly - formatted. - - securesystemslib.exceptions.Error, if an error occurred trying to generate - the snapshot metadata object. - - - The 'root.json' and 'targets.json' files are read. - - - The snapshot metadata object, conformant to 'tuf.formats.SNAPSHOT_SCHEMA'. - """ - - # Do the arguments have the correct format? - # This check ensures arguments have the appropriate number of objects and - # object types, and that all dict keys are properly named. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - sslib_formats.PATH_SCHEMA.check_match(metadata_directory) - formats.METADATAVERSION_SCHEMA.check_match(version) - sslib_formats.ISO8601_DATETIME_SCHEMA.check_match(expiration_date) - sslib_formats.BOOLEAN_SCHEMA.check_match(consistent_snapshot) - sslib_formats.NAME_SCHEMA.check_match(repository_name) - sslib_formats.BOOLEAN_SCHEMA.check_match(use_length) - sslib_formats.BOOLEAN_SCHEMA.check_match(use_hashes) - - # Snapshot's 'fileinfodict' shall contain the version number of Root, - # Targets, and all delegated roles of the repository. - fileinfodict = {} - - length, hashes = _get_hashes_and_length_if_needed(use_length, use_hashes, - os.path.join(metadata_directory, TARGETS_FILENAME), storage_backend) - - targets_role = TARGETS_FILENAME[:-len(METADATA_EXTENSION)] - - targets_file_version = get_metadata_versioninfo(targets_role, - repository_name) - - # Make file info dictionary with make_metadata_fileinfo because - # in the tuf spec length and hashes are optional for all - # METAFILES in snapshot.json including the top-level targets file. - fileinfodict[TARGETS_FILENAME] = formats.make_metadata_fileinfo( - targets_file_version['version'], length, hashes) - - # Search the metadata directory and generate the versioninfo of all the role - # files found there. This information is stored in the 'meta' field of - # 'snapshot.json'. - - metadata_files = sorted(storage_backend.list_folder(metadata_directory), - reverse=True) - for metadata_filename in metadata_files: - # Strip the version number if 'consistent_snapshot' is True. - # Example: '10.django.json' --> 'django.json' - metadata_name, junk = _strip_version_number(metadata_filename, - consistent_snapshot) - - # All delegated roles are added to the snapshot file. - if metadata_filename.endswith(METADATA_EXTENSION): - rolename = metadata_filename[:-len(METADATA_EXTENSION)] - - # Obsolete role files may still be found. Ensure only roles loaded - # in the roledb are included in the Snapshot metadata. Since the - # snapshot and timestamp roles are not listed in snapshot.json, do not - # list these roles found in the metadata directory. - if roledb.role_exists(rolename, repository_name) and \ - rolename not in roledb.TOP_LEVEL_ROLES: - - length, hashes = _get_hashes_and_length_if_needed(use_length, use_hashes, - os.path.join(metadata_directory, metadata_filename), storage_backend) - - file_version = get_metadata_versioninfo(rolename, - repository_name) - - fileinfodict[metadata_name] = formats.make_metadata_fileinfo( - file_version['version'], length, hashes) - - else: - logger.debug('Metadata file has an unsupported file' - ' extension: ' + metadata_filename) - - # Generate the Snapshot metadata object. - # Use generalized build_dict_conforming_to_schema func to produce a dict that - # contains all the appropriate information for snapshot metadata, - # checking that the result conforms to the appropriate schema. - # TODO: Later, probably after the rewrite for TUF Issue #660, generalize - # further, upward, by replacing generate_targets_metadata, - # generate_root_metadata, etc. with one function that generates - # metadata, possibly rolling that upwards into the calling function. - # There are very few things that really need to be done differently. - return formats.build_dict_conforming_to_schema( - formats.SNAPSHOT_SCHEMA, - version=version, - expires=expiration_date, - meta=fileinfodict) - - - - - def generate_timestamp_metadata(snapshot_file_path, version, expiration_date, storage_backend, repository_name, use_length=True, use_hashes=True): @@ -1758,7 +1609,7 @@ def generate_timestamp_metadata(snapshot_file_path, version, expiration_date, snapshot_fileinfo = {} - length, hashes = _get_hashes_and_length_if_needed(use_length, use_hashes, + length, hashes = get_hashes_and_length_if_needed(use_length, use_hashes, snapshot_file_path, storage_backend) snapshot_filename = os.path.basename(snapshot_file_path) diff --git a/tuf/snapshot.py b/tuf/snapshot.py index 18e99da8ea..84f276fbeb 100644 --- a/tuf/snapshot.py +++ b/tuf/snapshot.py @@ -1,3 +1,25 @@ +import abc +import os +import logging + +from securesystemslib import formats as sslib_formats + +from tuf import formats +from tuf import roledb +from tuf import repository_lib as repolib + +# See 'log.py' to learn how logging is handled in TUF. +logger = logging.getLogger(__name__) + +# The extension of TUF metadata. +METADATA_EXTENSION = '.json' + +# The metadata filenames of the top-level roles. +ROOT_FILENAME = 'root' + METADATA_EXTENSION +TARGETS_FILENAME = 'targets' + METADATA_EXTENSION +SNAPSHOT_FILENAME = 'snapshot' + METADATA_EXTENSION +TIMESTAMP_FILENAME = 'timestamp' + METADATA_EXTENSION + class SnapshotInterface(): """ @@ -54,7 +76,7 @@ def remove_from_snapshot(self, rolename : str): @abc.abstractmethod - def generate_snapshot_metadata(metadata_directory, version, expiration_date, + def generate_snapshot_metadata(self, metadata_directory, version, expiration_date, storage_backend, consistent_snapshot=False, repository_name='default', use_length=False, use_hashes=False): """ @@ -112,7 +134,7 @@ def generate_snapshot_metadata(metadata_directory, version, expiration_date, the snapshot metadata object. - The 'root.json' and 'targets.json' files are read. + The 'root.json' and 'targets.json' files are read. TODO @@ -120,3 +142,117 @@ def generate_snapshot_metadata(metadata_directory, version, expiration_date, raise NotImplementedError # pragma: no cover + + +class ManifestSnapshot(SnapshotInterface): + """ + + A concrete implementation of SnapshotInterface that creates + snapshot metadata using the traditional method described inthe + TUF specification + """ + + + # As ManifestSnapshot is effectively a stateless wrapper around various + # standard library operations, we only ever need a single instance of it. + # That single instance is safe to be (re-)used by all callers. Therefore + # implement the singleton pattern to avoid uneccesarily creating multiple + # objects. + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = object.__new__(cls, *args, **kwargs) + return cls._instance + + + def add_to_snapshot(self, rolename : str): + return + + def remove_from_snapshot(self, rolename : str): + return + + def generate_snapshot_metadata(self, metadata_directory, version, expiration_date, + storage_backend, consistent_snapshot=False, + repository_name='default', use_length=False, use_hashes=False): + + # Do the arguments have the correct format? + # This check ensures arguments have the appropriate number of objects and + # object types, and that all dict keys are properly named. + # Raise 'securesystemslib.exceptions.FormatError' if the check fails. + sslib_formats.PATH_SCHEMA.check_match(metadata_directory) + formats.METADATAVERSION_SCHEMA.check_match(version) + sslib_formats.ISO8601_DATETIME_SCHEMA.check_match(expiration_date) + sslib_formats.BOOLEAN_SCHEMA.check_match(consistent_snapshot) + sslib_formats.NAME_SCHEMA.check_match(repository_name) + sslib_formats.BOOLEAN_SCHEMA.check_match(use_length) + sslib_formats.BOOLEAN_SCHEMA.check_match(use_hashes) + + # Snapshot's 'fileinfodict' shall contain the version number of Root, + # Targets, and all delegated roles of the repository. + fileinfodict = {} + + length, hashes = repolib.get_hashes_and_length_if_needed(use_length, use_hashes, + os.path.join(metadata_directory, TARGETS_FILENAME), storage_backend) + + targets_role = TARGETS_FILENAME[:-len(METADATA_EXTENSION)] + + targets_file_version = repolib.get_metadata_versioninfo(targets_role, + repository_name) + + # Make file info dictionary with make_metadata_fileinfo because + # in the tuf spec length and hashes are optional for all + # METAFILES in snapshot.json including the top-level targets file. + fileinfodict[TARGETS_FILENAME] = formats.make_metadata_fileinfo( + targets_file_version['version'], length, hashes) + + # Search the metadata directory and generate the versioninfo of all the role + # files found there. This information is stored in the 'meta' field of + # 'snapshot.json'. + + metadata_files = sorted(storage_backend.list_folder(metadata_directory), + reverse=True) + for metadata_filename in metadata_files: + # Strip the version number if 'consistent_snapshot' is True. + # Example: '10.django.json' --> 'django.json' + metadata_name, junk = repolib.strip_version_number(metadata_filename, + consistent_snapshot) + + # All delegated roles are added to the snapshot file. + if metadata_filename.endswith(METADATA_EXTENSION): + rolename = metadata_filename[:-len(METADATA_EXTENSION)] + + # Obsolete role files may still be found. Ensure only roles loaded + # in the roledb are included in the Snapshot metadata. Since the + # snapshot and timestamp roles are not listed in snapshot.json, do not + # list these roles found in the metadata directory. + if roledb.role_exists(rolename, repository_name) and \ + rolename not in roledb.TOP_LEVEL_ROLES: + + length, hashes = repolib.get_hashes_and_length_if_needed(use_length, use_hashes, + os.path.join(metadata_directory, metadata_filename), storage_backend) + + file_version = repolib.get_metadata_versioninfo(rolename, + repository_name) + + fileinfodict[metadata_name] = formats.make_metadata_fileinfo( + file_version['version'], length, hashes) + + else: + logger.debug('Metadata file has an unsupported file' + ' extension: ' + metadata_filename) + + # Generate the Snapshot metadata object. + # Use generalized build_dict_conforming_to_schema func to produce a dict that + # contains all the appropriate information for snapshot metadata, + # checking that the result conforms to the appropriate schema. + # TODO: Later, probably after the rewrite for TUF Issue #660, generalize + # further, upward, by replacing generate_targets_metadata, + # generate_root_metadata, etc. with one function that generates + # metadata, possibly rolling that upwards into the calling function. + # There are very few things that really need to be done differently. + return formats.build_dict_conforming_to_schema( + formats.SNAPSHOT_SCHEMA, + version=version, + expires=expiration_date, + meta=fileinfodict) From b7722b0c1451098c69c14cd6d7c83fe3d9a28d89 Mon Sep 17 00:00:00 2001 From: Marina Moore Date: Mon, 4 Oct 2021 16:02:32 -0400 Subject: [PATCH 3/3] Add uses of add_to_snapshot and remove_from snapshot to repository_tool Adds roles to the snapshot backend when they are written to disk so that the backend may maintain current snapshot information, and removes delegated targets from the snapshot backend when they are deleted TODO: add tests, more uses of add_to_snapshot Signed-off-by: Marina Moore --- tuf/repository_tool.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tuf/repository_tool.py b/tuf/repository_tool.py index af78b2ba32..d67654d08a 100755 --- a/tuf/repository_tool.py +++ b/tuf/repository_tool.py @@ -45,6 +45,7 @@ from tuf import log from tuf import repository_lib as repo_lib from tuf import roledb +from tuf import snapshot # Copy API @@ -214,7 +215,8 @@ class Repository(object): def __init__(self, repository_directory, metadata_directory, targets_directory, storage_backend, repository_name='default', use_timestamp_length=True, use_timestamp_hashes=True, - use_snapshot_length=False, use_snapshot_hashes=False): + use_snapshot_length=False, use_snapshot_hashes=False, + snapshot_backend=None): # Do the arguments have the correct format? # Ensure the arguments have the appropriate number of objects and object @@ -239,6 +241,11 @@ def __init__(self, repository_directory, metadata_directory, self._use_snapshot_length = use_snapshot_length self._use_snapshot_hashes = use_snapshot_hashes + if snapshot_backend == None: + self._snapshot_backend = snapshot.ManifestSnapshot() + else: + self.snapshot_backend = snapshot_backend + try: roledb.create_roledb(repository_name) keydb.create_keydb(repository_name) @@ -254,6 +261,10 @@ def __init__(self, repository_directory, metadata_directory, self.targets = Targets(self._targets_directory, 'targets', repository_name=self._repository_name) + self._snapshot_backend.add_to_snapshot("root") + self._snapshot_backend.add_to_snapshot("timestamp") + self._snapshot_backend.add_to_snapshot("targets") + def writeall(self, consistent_snapshot=False, use_existing_fileinfo=False): @@ -321,6 +332,8 @@ def writeall(self, consistent_snapshot=False, use_existing_fileinfo=False): dirty_rolenames = roledb.get_dirty_roles(self._repository_name) for dirty_rolename in dirty_rolenames: + self._snapshot_backend.add_to_snapshot(dirty_rolename) + # Ignore top-level roles, they will be generated later in this method. if dirty_rolename in roledb.TOP_LEVEL_ROLES: @@ -436,6 +449,8 @@ def write(self, rolename, consistent_snapshot=False, increment_version_number=Tr rolename_filename = os.path.join(self._metadata_directory, rolename + METADATA_EXTENSION) + self._snapshot_backend.add_to_snapshot(rolename) + filenames = {'root': os.path.join(self._metadata_directory, repo_lib.ROOT_FILENAME), 'targets': os.path.join(self._metadata_directory, repo_lib.TARGETS_FILENAME), 'snapshot': os.path.join(self._metadata_directory, repo_lib.SNAPSHOT_FILENAME), @@ -676,6 +691,7 @@ class provides methods that are needed by all top-level roles, such as def __init__(self): self._rolename = None self._repository_name = None + self._snapshot_backend = None def add_verification_key(self, key, expires=None): @@ -1675,7 +1691,8 @@ class Targets(Metadata): """ def __init__(self, targets_directory, rolename='targets', roleinfo=None, - parent_targets_object=None, repository_name='default'): + parent_targets_object=None, repository_name='default', + snapshot_backend=None): # Do the arguments have the correct format? # Ensure the arguments have the appropriate number of objects and object @@ -1696,6 +1713,11 @@ def __init__(self, targets_directory, rolename='targets', roleinfo=None, self._parent_targets_object = self self._repository_name = repository_name + if snapshot_backend == None: + self._snapshot_backend = snapshot.ManifestSnapshot() + else: + self._snapshot_backend = snapshot_backend + # Keep a reference to the top-level 'targets' object. Any delegated roles # that may be created, can be added to and accessed via the top-level # 'targets' object. @@ -1844,6 +1866,7 @@ def remove_delegated_role(self, rolename): return else: + self._snapshot_backend.remove_from_snapshot(rolename) del self._delegated_roles[rolename]