def get_signature_status(signable, role=None, repository_name='default', threshold=None, keyids=None): """ <Purpose> Return a dictionary representing the status of the signatures listed in 'signable'. Signatures in the returned dictionary are identified by the signature keyid and can have a status of either: * bad -- Invalid signature * good -- Valid signature from key that is available in 'tuf.keydb', and is authorized for the passed role as per 'roledb' (authorization may be overwritten by passed 'keyids'). * unknown -- Signature from key that is not available in 'tuf.keydb', or if 'role' is None. * unknown signing schemes -- Signature from key with unknown signing scheme. * untrusted -- Valid signature from key that is available in 'tuf.keydb', but is not trusted for the passed role as per 'roledb' or the passed 'keyids'. NOTE: The result may contain duplicate keyids or keyids that reference the same key, if 'signable' lists multiple signatures from the same key. <Arguments> signable: A dictionary containing a list of signatures and a 'signed' identifier. signable = {'signed': 'signer', 'signatures': [{'keyid': keyid, 'sig': sig}]} Conformant to tuf.formats.SIGNABLE_SCHEMA. role: TUF role string (e.g. 'root', 'targets', 'snapshot' or timestamp). threshold: Rather than reference the role's threshold as set in roledb, use the given 'threshold' to calculate the signature status of 'signable'. 'threshold' is an integer value that sets the role's threshold value, or the minimum number of signatures needed for metadata to be considered fully signed. keyids: Similar to the 'threshold' argument, use the supplied list of 'keyids' to calculate the signature status, instead of referencing the keyids in roledb for 'role'. <Exceptions> securesystemslib.exceptions.FormatError, if 'signable' does not have the correct format. tuf.exceptions.UnknownRoleError, if 'role' is not recognized. <Side Effects> None. <Returns> A dictionary representing the status of the signatures in 'signable'. Conformant to tuf.formats.SIGNATURESTATUS_SCHEMA. """ # Do the arguments have the correct format? This check will ensure that # 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. formats.SIGNABLE_SCHEMA.check_match(signable) sslib_formats.NAME_SCHEMA.check_match(repository_name) if role is not None: formats.ROLENAME_SCHEMA.check_match(role) if threshold is not None: formats.THRESHOLD_SCHEMA.check_match(threshold) if keyids is not None: sslib_formats.KEYIDS_SCHEMA.check_match(keyids) # The signature status dictionary returned. signature_status = {} good_sigs = [] bad_sigs = [] unknown_sigs = [] untrusted_sigs = [] unknown_signing_schemes = [] # Extract the relevant fields from 'signable' that will allow us to identify # the different classes of keys (i.e., good_sigs, bad_sigs, etc.). signed = sslib_formats.encode_canonical(signable['signed']).encode('utf-8') signatures = signable['signatures'] # Iterate the signatures and enumerate the signature_status fields. # (i.e., good_sigs, bad_sigs, etc.). for signature in signatures: keyid = signature['keyid'] # Does the signature use an unrecognized key? try: key = keydb.get_key(keyid, repository_name) except exceptions.UnknownKeyError: unknown_sigs.append(keyid) continue # Does the signature use an unknown/unsupported signing scheme? try: valid_sig = sslib_keys.verify_signature(key, signature, signed) except sslib_exceptions.UnsupportedAlgorithmError: unknown_signing_schemes.append(keyid) continue # We are now dealing with either a trusted or untrusted key... if valid_sig: if role is not None: # Is this an unauthorized key? (a keyid associated with 'role') # Note that if the role is not known, tuf.exceptions.UnknownRoleError # is raised here. if keyids is None: keyids = roledb.get_role_keyids(role, repository_name) if keyid not in keyids: untrusted_sigs.append(keyid) continue # This is an unset role, thus an unknown signature. else: unknown_sigs.append(keyid) continue # Identify good/authorized key. good_sigs.append(keyid) else: # This is a bad signature for a trusted key. bad_sigs.append(keyid) # Retrieve the threshold value for 'role'. Raise # tuf.exceptions.UnknownRoleError if we were given an invalid role. if role is not None: if threshold is None: # Note that if the role is not known, tuf.exceptions.UnknownRoleError is # raised here. threshold = roledb.get_role_threshold( role, repository_name=repository_name) else: logger.debug('Not using roledb.py\'s threshold for ' + repr(role)) else: threshold = 0 # Build the signature_status dict. signature_status['threshold'] = threshold signature_status['good_sigs'] = good_sigs signature_status['bad_sigs'] = bad_sigs signature_status['unknown_sigs'] = unknown_sigs signature_status['untrusted_sigs'] = untrusted_sigs signature_status['unknown_signing_schemes'] = unknown_signing_schemes return signature_status
def _save_project_configuration(metadata_directory, targets_directory, public_keys, prefix, threshold, layout_type, project_name): """ <Purpose> Persist the project's information to a file. The saved project information can later be loaded with Project.load_project(). <Arguments> metadata_directory: Where the project's metadata is located. targets_directory: The location of the target files for this project. public_keys: A list containing the public keys for the project role. prefix: The project's prefix (if any.) threshold: The threshold value for the project role. layout_type: The layout type being used by the project, "flat" stands for separated targets and metadata directories, "repo-like" emulates the layout used by the repository tools project_name: The name given to the project, this sets the metadata filename so it matches the one stored in upstream. <Exceptions> securesystemslib.exceptions.FormatError are also expected if any of the arguments are malformed. OSError may rise if the metadata_directory/project.cfg file exists and is non-writeable <Side Effects> A 'project.cfg' configuration file is created or overwritten. <Returns> None. """ # Schema check for the arguments. sslib_formats.PATH_SCHEMA.check_match(metadata_directory) sslib_formats.PATH_SCHEMA.check_match(prefix) sslib_formats.PATH_SCHEMA.check_match(targets_directory) formats.RELPATH_SCHEMA.check_match(project_name) cfg_file_directory = metadata_directory # Check whether the layout type is 'flat' or 'repo-like'. # If it is, the .cfg file should be saved in the previous directory. if layout_type == 'repo-like': cfg_file_directory = os.path.dirname(metadata_directory) junk, targets_directory = os.path.split(targets_directory) junk, metadata_directory = os.path.split(metadata_directory) # Can the file be opened? project_filename = os.path.join(cfg_file_directory, PROJECT_FILENAME) # Build the fields of the configuration file. project_config = {} project_config['prefix'] = prefix project_config['public_keys'] = {} project_config['metadata_location'] = metadata_directory project_config['targets_location'] = targets_directory project_config['threshold'] = threshold project_config['layout_type'] = layout_type project_config['project_name'] = project_name # Build a dictionary containing the actual keys. for key in public_keys: key_info = keydb.get_key(key) key_metadata = format_keyval_to_metadata(key_info['keytype'], key_info['scheme'], key_info['keyval']) project_config['public_keys'][key] = key_metadata # Save the actual file. with open(project_filename, 'wt', encoding='utf8') as fp: json.dump(project_config, fp)
def verify(signable, role, repository_name='default', threshold=None, keyids=None): """ <Purpose> Verify that 'signable' has a valid threshold of authorized signatures identified by unique keyids. The threshold and whether a keyid is authorized is determined by querying the 'threshold' and 'keyids' info for the passed 'role' in 'roledb'. Both values can be overwritten by passing the 'threshold' or 'keyids' arguments. NOTE: - Signatures with identical authorized keyids only count towards the threshold once. - Signatures with the same key only count toward the threshold once. <Arguments> signable: A dictionary containing a list of signatures and a 'signed' identifier that conforms to SIGNABLE_SCHEMA, e.g.: signable = {'signed':, 'signatures': [{'keyid':, 'method':, 'sig':}]} role: TUF role string (e.g. 'root', 'targets', 'snapshot' or timestamp). threshold: Rather than reference the role's threshold as set in roledb, use the given 'threshold' to calculate the signature status of 'signable'. 'threshold' is an integer value that sets the role's threshold value, or the minimum number of signatures needed for metadata to be considered fully signed. keyids: Similar to the 'threshold' argument, use the supplied list of 'keyids' to calculate the signature status, instead of referencing the keyids in roledb for 'role'. <Exceptions> tuf.exceptions.UnknownRoleError, if 'role' is not recognized. securesystemslib.exceptions.FormatError, if 'signable' is not formatted correctly. securesystemslib.exceptions.Error, if an invalid threshold is encountered. <Side Effects> tuf.sig.get_signature_status() called. Any exceptions thrown by get_signature_status() will be caught here and re-raised. <Returns> Boolean. True if the number of good unique (by keyid) signatures >= the role's threshold, False otherwise. """ formats.SIGNABLE_SCHEMA.check_match(signable) formats.ROLENAME_SCHEMA.check_match(role) sslib_formats.NAME_SCHEMA.check_match(repository_name) # Retrieve the signature status. tuf.sig.get_signature_status() raises: # tuf.exceptions.UnknownRoleError # securesystemslib.exceptions.FormatError. 'threshold' and 'keyids' are also # validated. status = get_signature_status(signable, role, repository_name, threshold, keyids) # Retrieve the role's threshold and the authorized keys of 'status' threshold = status['threshold'] good_sigs = status['good_sigs'] # Does 'status' have the required threshold of signatures? # First check for invalid threshold values before returning result. # Note: get_signature_status() is expected to verify that 'threshold' is # not None or <= 0. if threshold is None or threshold <= 0: #pragma: no cover raise sslib_exceptions.Error("Invalid threshold: " + repr(threshold)) unique_keys = set() for keyid in good_sigs: key = keydb.get_key(keyid, repository_name) unique_keys.add(key['keyval']['public']) return len(unique_keys) >= threshold