def get_target_keyids(metadata_directory): """ <Purpose> Retrieve the role keyids for all the target roles located in 'metadata_directory'. The target's '.txt' metadata file is inspected and the keyids extracted. The 'targets.txt' role, including delegated roles (e.g., 'targets/role1.txt'), are all read. <Arguments> metadata_directory: The directory containing the 'targets.txt' metadata file and the metadata for optional delegated roles. The delegated role 'role1' whose parent is 'targets', would be located in the '{metadata_directory}/targets/role1' directory. <Exceptions> tuf.FormatError, if any of the arguments are improperly formatted. tuf.RepositoryError, if there was an error reading a target file. <Side Effects> Reads all of the target metadata found in 'metadata_directory' and stores the information extracted. <Returns> A dictionary containing the role information extracted from the metadata. Ex: {'targets':[keyid1, ...], 'targets/role1':[keyid], ...} """ # Does 'metadata_directory' have the correct format? # Raise 'tuf.FormatError, if there is a mismatch. tuf.formats.PATH_SCHEMA.check_match(metadata_directory) metadata_directory = check_directory(metadata_directory) # The dict holding the keyids for all the target roles. # This dict will be returned to the caller. role_keyids = {} # Read the 'targets.txt' file. This file must exist. targets_filepath = os.path.join(metadata_directory, 'targets.txt') if not os.path.exists(targets_filepath): raise tuf.RepositoryError('"targets.txt" not found') # Read the contents of 'targets.txt' and save the signable. targets_signable = tuf.util.load_json_file(targets_filepath) # Ensure the signable is properly formatted. try: tuf.formats.check_signable_object_format(targets_signable) except tuf.FormatError, e: raise tuf.RepositoryError('"targets.txt" is improperly formatted')
def find_delegated_role(roles, delegated_role): """ <Purpose> Find the index, if any, of a role with a given name in a list of roles. <Arguments> roles: The list of roles, each of which must have a name. delegated_role: The name of the role to be found in the list of roles. <Exceptions> tuf.RepositoryError, if the list of roles has invalid data. <Side Effects> No known side effects. <Returns> None, if the role with the given name does not exist, or its unique index in the list of roles. """ # Check argument types. tuf.formats.ROLELIST_SCHEMA.check_match(roles) tuf.formats.ROLENAME_SCHEMA.check_match(delegated_role) # The index of a role, if any, with the same name. role_index = None for index in xrange(len(roles)): role = roles[index] name = role.get('name') # This role has no name. if name is None: no_name_message = 'Role with no name!' raise tuf.RepositoryError(no_name_message) # Does this role have the same name? else: # This role has the same name, and... if name == delegated_role: # ...it is the only known role with the same name. if role_index is None: role_index = index # ...there are at least two roles with the same name! else: duplicate_role_message = 'Duplicate role (' + str( delegated_role) + ')!' raise tuf.RepositoryError(duplicate_role_message) # This role has a different name. else: continue return role_index
def update_client(repository_mirror): """ <Purpose> Perform an update of the metadata and target files located at 'repository_mirror'. Target files are saved to the 'targets' directory in the current working directory. The current directory must already include a 'metadata' directory, which in turn must contain the 'current' and 'previous' directories. At a minimum, these two directories require the 'root.txt' metadata file. <Arguments> repository_mirror: The URL to the repository mirror hosting the metadata and target files. E.g., 'http://localhost:8001' <Exceptions> tuf.RepositoryError, if 'repository_mirror' is improperly formatted. <Side Effects> Connects to a repository mirror and updates the metadata files and any target files. Obsolete targets are also removed locally. <Returns> None. """ # Does 'repository_mirror' have the correct format? try: tuf.formats.URL_SCHEMA.check_match(repository_mirror) except tuf.FormatError, e: message = 'The repository mirror supplied is invalid.' raise tuf.RepositoryError(message)
def build_repository(project_directory): """ <Purpose> Build a basic TUF repository. All of the required files needed by a repository mirror are created, such as the metadata files of the top-level roles, cryptographic keys, and the directories containing all of the target files. <Arguments> project_directory: The directory containing the target files to be copied over to the targets directory of the repository. <Exceptions> tuf.RepositoryError, if there was an error building the repository. <Side Effects> The repository files created are written to disk to the current working directory. <Returns> None. """ # Do the arguments have the correct format? # Raise 'tuf.RepositoryError' if there is a mismatch. try: tuf.formats.PATH_SCHEMA.check_match(project_directory) except tuf.FormatError, e: message = str(e) raise tuf.RepositoryError(message)
def update_client(repository_mirror): """ <Purpose> Perform an update of the metadata and target files located at 'repository_mirror'. Target files are saved to the 'targets' directory in the current working directory. The current directory must already include a 'metadata' directory, which in turn must contain the 'current' and 'previous' directories. At a minimum, these two directories require the 'root.json' metadata file. <Arguments> repository_mirror: The URL to the repository mirror hosting the metadata and target files. E.g., 'http://localhost:8001' <Exceptions> tuf.RepositoryError, if 'repository_mirror' is improperly formatted. <Side Effects> Connects to a repository mirror and updates the metadata files and any target files. Obsolete targets are also removed locally. <Returns> None. """ # Does 'repository_mirror' have the correct format? try: tuf.formats.URL_SCHEMA.check_match(repository_mirror) except tuf.FormatError as e: message = 'The repository mirror supplied is invalid.' raise tuf.RepositoryError(message) # Set the local repository directory containing all of the metadata files. tuf.conf.repository_directory = '.' # Set the repository mirrors. This dictionary is needed by the Updater # class of updater.py. repository_mirrors = { 'mirror': { 'url_prefix': repository_mirror, 'metadata_path': 'metadata', 'targets_path': 'targets', 'confined_target_dirs': [''] } } # Create the repository object using the repository name 'repository' # and the repository mirrors defined above. updater = tuf.client.updater.Updater('repository', repository_mirrors) # The local destination directory to save the target files. destination_directory = './targets' # Refresh the repository's top-level roles, store the target information for # all the targets tracked, and determine which of these targets have been # updated. updater.refresh() """
def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations): """ <Purpose> Ensure that the list of targets specified by 'rolename' are allowed; this is determined by inspecting the 'delegations' field of the parent role of 'rolename'. If a target specified by 'rolename' is not found in the delegations field of 'metadata_object_of_parent', raise an exception. The top-level role 'targets' is allowed to list any target file, so this function does not raise an exception if 'rolename' is 'targets'. Targets allowed are either exlicitly listed under the 'paths' field, or match one of the patterns (i.e., Unix shell-style wildcards) listed there. A parent role may delegate trust to all files under a particular directory, including files in subdirectories by using wildcards (e.g., '/packages/source/Django/*', '/packages/django*.tar.gzip). Targets listed in hashed bins are also validated (i.e., its calculated path hash prefix must be delegated by the parent role). TODO: Should the TUF spec restrict the repository to one particular algorithm when calcutating path hash prefixes (currently restricted to SHA256)? Should we allow the repository to specify in the role dictionary the algorithm used for these generated hashed paths? <Arguments> rolename: The name of the role whose targets must be verified. This is a role name and should not end in '.json'. Examples: 'root', 'targets', 'unclaimed'. list_of_targets: The targets of 'rolename', as listed in targets field of the 'rolename' metadata. 'list_of_targets' are target paths relative to the targets directory of the repository. The delegations of the parent role are checked to verify that the targets of 'list_of_targets' are valid. parent_delegations: The parent delegations of 'rolename'. The metadata object stores the allowed paths and path hash prefixes of child delegations in its 'delegations' attribute. <Exceptions> tuf.FormatError: If any of the arguments are improperly formatted. tuf.ForbiddenTargetError: If the targets of 'metadata_role' are not allowed according to the parent's metadata file. The 'paths' and 'path_hash_prefixes' attributes are verified. tuf.RepositoryError: If the parent of 'rolename' has not made a delegation to 'rolename'. <Side Effects> None. <Returns> None. """ # Do the arguments have the correct format? # Ensure the arguments have the appropriate number of objects and object # types, and that all dict keys are properly named. # Raise 'tuf.FormatError' if any are improperly formatted. tuf.formats.ROLENAME_SCHEMA.check_match(rolename) tuf.formats.RELPATHS_SCHEMA.check_match(list_of_targets) tuf.formats.DELEGATIONS_SCHEMA.check_match(parent_delegations) # Return if 'rolename' is 'targets'. 'targets' is not a delegated role. Any # target file listed in 'targets' is allowed. if rolename == 'targets': return # The allowed targets of delegated roles are stored in the parent's metadata # file. Iterate 'list_of_targets' and confirm they are trusted, or their root # parent directory exists in the role delegated paths, or path hash prefixes, # of the parent role. First, locate 'rolename' in the 'roles' attribute of # 'parent_delegations'. roles = parent_delegations['roles'] role_index = find_delegated_role(roles, rolename) # Ensure the delegated role exists prior to extracting trusted paths from # the parent's 'paths', or trusted path hash prefixes from the parent's # 'path_hash_prefixes'. if role_index is not None: role = roles[role_index] allowed_child_paths = role.get('paths') allowed_child_path_hash_prefixes = role.get('path_hash_prefixes') actual_child_targets = list_of_targets if allowed_child_path_hash_prefixes is not None: consistent = paths_are_consistent_with_hash_prefixes # 'actual_child_targets' (i.e., 'list_of_targets') should have length # greater than zero due to the tuf.format check above. if not consistent(actual_child_targets, allowed_child_path_hash_prefixes): message = repr(rolename) + ' specifies a target that does not' + \ ' have a path hash prefix listed in its parent role.' raise tuf.ForbiddenTargetError(message) elif allowed_child_paths is not None: # Check that each delegated target is either explicitly listed or a parent # directory is found under role['paths'], otherwise raise an exception. # If the parent role explicitly lists target file paths in 'paths', # this loop will run in O(n^2), the worst-case. The repository # maintainer will likely delegate entire directories, and opt for # explicit file paths if the targets in a directory are delegated to # different roles/developers. for child_target in actual_child_targets: for allowed_child_path in allowed_child_paths: if fnmatch.fnmatch(child_target, allowed_child_path): break else: raise tuf.ForbiddenTargetError('Role '+repr(rolename)+' specifies'+\ ' target '+repr(child_target)+','+\ ' which is not an allowed path'+\ ' according to the delegations set'+\ ' by its parent role.') else: # 'role' should have been validated when it was downloaded. # The 'paths' or 'path_hash_prefixes' attributes should not be missing, # so raise an error in case this clause is reached. raise tuf.FormatError(repr(role) + ' did not contain one of ' +\ 'the required fields ("paths" or ' +\ '"path_hash_prefixes").') # Raise an exception if the parent has not delegated to the specified # 'rolename' child role. else: raise tuf.RepositoryError('The parent role has not delegated to '+\ repr(rolename) + '.')
def find_delegated_role(roles, delegated_role): """ <Purpose> Find the index, if any, of a role with a given name in a list of roles. <Arguments> roles: The list of roles, each of which must have a 'name' attribute. delegated_role: The name of the role to be found in the list of roles. <Exceptions> tuf.RepositoryError, if the list of roles has invalid data. <Side Effects> No known side effects. <Returns> The unique index, an interger, in the list of roles. if 'delegated_role' does not exist, 'None' is returned. """ # Do the arguments have the correct format? # Ensure the arguments have the appropriate number of objects and object # types, and that all dict keys are properly named. # Raise 'tuf.FormatError' if any are improperly formatted. tuf.formats.ROLELIST_SCHEMA.check_match(roles) tuf.formats.ROLENAME_SCHEMA.check_match(delegated_role) # The index of a role, if any, with the same name. role_index = None for index in six.moves.xrange(len(roles)): role = roles[index] name = role.get('name') # This role has no name. if name is None: no_name_message = 'Role with no name.' raise tuf.RepositoryError(no_name_message) # Does this role have the same name? else: # This role has the same name, and... if name == delegated_role: # ...it is the only known role with the same name. if role_index is None: role_index = index # ...there are at least two roles with the same name. else: duplicate_role_message = 'Duplicate role (' + str( delegated_role) + ').' raise tuf.RepositoryError(duplicate_role_message) # This role has a different name. else: logger.debug('Skipping delegated role: ' + repr(delegated_role)) return role_index
# Do the arguments have the correct format? # Raise 'tuf.RepositoryError' if there is a mismatch. try: tuf.formats.PATH_SCHEMA.check_match(project_directory) except tuf.FormatError, e: message = str(e) raise tuf.RepositoryError(message) # Verify the 'project_directory' argument. project_directory = os.path.abspath(project_directory) try: tuf.repo.signerlib.check_directory(project_directory) except (tuf.FormatError, tuf.Error), e: message = str(e) raise tuf.RepositoryError(message) # Handle the expiration time. The expiration date determines when # the top-level roles expire. prompt_message = \ '\nWhen would you like your "root.txt" metadata to expire? (mm/dd/yyyy): ' timeout = 360 #None # for attempt in range(MAX_INPUT_ATTEMPTS): # # Get the difference between the user's entered expiration date and today's # # date. Convert and store the difference to total days till expiration. # try: # input_date = _prompt(prompt_message) # expiration_date = datetime.datetime.strptime(input_date, '%m/%d/%Y') # time_difference = expiration_date - datetime.datetime.now() # timeout = time_difference.days # if timeout < 1: