def test_glob_to_many_unfinished_files(self): """Test record stop with to many unfinished files when globbing (gpg). """ name = "test-to-many-glob" fn1 = UNFINISHED_FILENAME_FORMAT.format(step_name=name, keyid="a12345678") fn2 = UNFINISHED_FILENAME_FORMAT.format(step_name=name, keyid="b12345678") open(fn1, "w").close() open(fn2, "w").close() args = ["--step-name", name, "--gpg", self.gpg_key_768C43, "--gpg-home", self.gnupg_home] self.assert_cli_sys_exit(["stop"] + args, 1)
def test_wrong_signature_in_unfinished_metadata(self): """Test record stop exits on wrong signature, no link recorded. """ in_toto_record_start(self.step_name, [], self.key) link_name = UNFINISHED_FILENAME_FORMAT.format(step_name=self.step_name, keyid=self.key["keyid"]) changed_link_name = UNFINISHED_FILENAME_FORMAT.format( step_name=self.step_name, keyid=self.key2["keyid"]) os.rename(link_name, changed_link_name) with self.assertRaises(SignatureVerificationError): in_toto_record_stop(self.step_name, [], self.key2) with self.assertRaises(IOError): open(self.link_name, "r") os.rename(changed_link_name, link_name) os.remove(self.link_name_unfinished)
def in_toto_record_stop(step_name, key, product_list): """ <Purpose> Finishes creating link metadata for a multi-part in-toto step. Loads signing key and unfinished link metadata file from disk, verifies that the file was signed with the key, records products, updates unfinished Link object (products and signature), removes unfinished link file from and stores new link file to disk. <Arguments> step_name: A unique name to relate link metadata with a step defined in the layout. key: Private key to sign link metadata. Format is securesystemslib.formats.KEY_SCHEMA product_list: List of file or directory paths that should be recorded as products. <Exceptions> None. <Side Effects> Writes newly created link metadata file to disk using the filename scheme from link.FILENAME_FORMAT Removes unfinished link file link.UNFINISHED_FILENAME_FORMAT from disk <Returns> None. """ fn = FILENAME_FORMAT.format(step_name=step_name, keyid=key["keyid"]) unfinished_fn = UNFINISHED_FILENAME_FORMAT.format(step_name=step_name, keyid=key["keyid"]) log.info("Stop recording '{}'...".format(step_name)) # Expects an a file with name UNFINISHED_FILENAME_FORMAT in the current dir log.info("Loading preliminary link metadata '{}'...".format(unfinished_fn)) link_metadata = Metablock.load(unfinished_fn) # The file must have been signed by the same key log.info("Verifying preliminary link signature...") keydict = {key["keyid"]: key} link_metadata.verify_signatures(keydict) if product_list: log.info("Recording products '{}'...".format(", ".join(product_list))) link_metadata.signed.products = record_artifacts_as_dict(product_list) log.info("Updating signature with key '{:.8}...'...".format(key["keyid"])) link_metadata.signatures = [] link_metadata.sign(key) log.info("Storing link metadata to '{}'...".format(fn)) link_metadata.dump(fn) log.info("Removing unfinished link metadata '{}'...".format(unfinished_fn)) os.remove(unfinished_fn)
def in_toto_record_start(step_name, key, material_list): """ <Purpose> Starts creating link metadata for a multi-part in-toto step. I.e. records passed materials, creates link meta data object from it, signs it with passed key and stores it to disk with UNFINISHED_FILENAME_FORMAT. <Arguments> step_name: A unique name to relate link metadata with a step defined in the layout. key: Private key to sign link metadata. Format is securesystemslib.formats.KEY_SCHEMA material_list: List of file or directory paths that should be recorded as materials. <Exceptions> None. <Side Effects> Writes newly created link metadata file to disk using the filename scheme from link.UNFINISHED_FILENAME_FORMAT <Returns> None. """ unfinished_fn = UNFINISHED_FILENAME_FORMAT.format(step_name=step_name, keyid=key["keyid"]) log.info("Start recording '{}'...".format(step_name)) if material_list: log.info("Recording materials '{}'...".format( ", ".join(material_list))) materials_dict = record_artifacts_as_dict(material_list) log.info("Creating preliminary link metadata...") link = in_toto.models.link.Link(name=step_name, materials=materials_dict, products={}, command=[], byproducts={}, environment={"workdir": os.getcwd()}) link_metadata = Metablock(signed=link) log.info("Signing link metadata with key '{:.8}...'...".format( key["keyid"])) link_metadata.sign(key) log.info( "Storing preliminary link metadata to '{}'...".format(unfinished_fn)) link_metadata.dump(unfinished_fn)
def setUpClass(self): """Create and change into temporary directory, generate key pair and dummy material, read key pair. """ self.set_up_test_dir() self.key_path = "test_key" generate_and_write_unencrypted_rsa_keypair(self.key_path) self.key = import_rsa_privatekey_from_file(self.key_path) self.step_name = "test_step" self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format(step_name=self.step_name, keyid=self.key["keyid"]) self.test_material = "test_material" Path(self.test_material).touch()
def setUpClass(self): """Create and change into temporary directory, generate key pair and dummy material, read key pair. """ super(TestInTotoRecordStart, self).setUpClass() self.key_path = "test_key" generate_and_write_rsa_keypair(self.key_path) self.key = prompt_import_rsa_key_from_file(self.key_path) self.step_name = "test_step" self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format( step_name=self.step_name, keyid=self.key["keyid"]) self.test_material = "test_material" open(self.test_material, "w").close()
def setUpClass(self): """Create and change into temporary directory, generate key pair and dummy material, read key pair. """ self.working_dir = os.getcwd() self.test_dir = tempfile.mkdtemp() os.chdir(self.test_dir) self.key_path = "test_key" generate_and_write_rsa_keypair(self.key_path) self.key = prompt_import_rsa_key_from_file(self.key_path) self.step_name = "test_step" self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format( step_name=self.step_name, keyid=self.key["keyid"]) self.test_material = "test_material" open(self.test_material, "w").close()
def setUpClass(self): """Create and change into temporary directory, generate two key pairs and dummy product. """ self.set_up_test_dir() self.key_path = "test-key" self.key_path2 = "test-key2" generate_and_write_unencrypted_rsa_keypair(self.key_path) generate_and_write_unencrypted_rsa_keypair(self.key_path2) self.key = import_rsa_privatekey_from_file(self.key_path) self.key2 = import_rsa_privatekey_from_file(self.key_path2) self.step_name = "test-step" self.link_name = "{}.{:.8}.link".format(self.step_name, self.key["keyid"]) self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format( step_name=self.step_name, keyid=self.key["keyid"]) self.test_product = "test_product" Path(self.test_product).touch()
def setUpClass(self): """Create and change into temporary directory, generate two key pairs and dummy product. """ super(TestInTotoRecordStop, self).setUpClass() self.key_path = "test-key" self.key_path2 = "test-key2" generate_and_write_rsa_keypair(self.key_path) generate_and_write_rsa_keypair(self.key_path2) self.key = prompt_import_rsa_key_from_file(self.key_path) self.key2 = prompt_import_rsa_key_from_file(self.key_path2) self.step_name = "test-step" self.link_name = "{}.{:.8}.link".format(self.step_name, self.key["keyid"]) self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format( step_name=self.step_name, keyid=self.key["keyid"]) self.test_product = "test_product" open(self.test_product, "w").close()
def in_toto_record_stop(step_name, product_list, signing_key=None, gpg_keyid=None, gpg_use_default=False, gpg_home=None, exclude_patterns=None, base_path=None, normalize_line_endings=False, lstrip_paths=None, metadata_directory=None): """Finalizes preliminary link metadata generated with in_toto_record_start. Loads preliminary link metadata file, verifies its signature, and records paths and hashes as products, thus finalizing the link metadata. The metadata is signed with the passed signing_key, a gpg key identified by its ID, or the default gpg key. If multiple key arguments are passed, only one key is used in above order of precedence. At least one key argument must be passed and it must be the same as the one used to sign the preliminary link metadata file. The resulting link file is written to ``STEP-NAME.KEYID-PREFIX.link``. Use this function together with in_toto_record_start as an alternative to in_toto_run, in order to provide evidence for supply chain steps that cannot be carried out by a single command. Arguments: step_name: A unique name to associate link metadata with a step. product_list: A list of artifact paths to be recorded as products. Directories are traversed recursively. signing_key (optional): A key used to sign the resulting link metadata. The format is securesystemslib.formats.KEY_SCHEMA. gpg_keyid (optional): A keyid used to identify a local gpg key used to sign the resulting link metadata. gpg_use_default (optional): A boolean indicating if the default gpg key should be used to sign the resulting link metadata. gpg_home (optional): A path to the gpg home directory. If not set the default gpg home directory is used. exclude_patterns (optional): A list of filename patterns to exclude certain files from being recorded as artifacts. base_path (optional): A path relative to which artifacts are recorded. Default is the current working directory. normalize_line_endings (optional): A boolean indicating if line endings of artifacts should be normalized before hashing for cross-platform support. lstrip_paths (optional): A list of path prefixes used to left-strip artifact paths before storing them in the resulting link metadata. metadata_directory (optional): A directory path to write the resulting link metadata file to. Default destination is the current working directory. Raises: securesystemslib.exceptions.FormatError: Passed arguments are malformed. ValueError: None of signing_key, gpg_keyid or gpg_use_default=True is passed. LinkNotFoundError: No preliminary link metadata file found. securesystemslib.exceptions.StorageError: Cannot hash artifacts. PrefixError: Left-stripping artifact paths results in non-unique dict keys. securesystemslib.process.subprocess.TimeoutExpired: Link command times out. IOError, FileNotFoundError, NotADirectoryError, PermissionError: Cannot write link metadata. securesystemslib.exceptions.CryptoError, \ securesystemslib.exceptions.UnsupportedAlgorithmError: Signing errors. ValueError, OSError, securesystemslib.gpg.exceptions.CommandError, \ securesystemslib.gpg.exceptions.KeyNotFoundError: gpg signing errors. Side Effects: Reads preliminary link metadata file from disk. Reads artifact files from disk. Calls system gpg in a subprocess, if a gpg key argument is passed. Writes resulting link metadata file to disk. Removes preliminary link metadata file from disk. """ LOG.info("Stop recording '{}'...".format(step_name)) # Check that we have something to sign and if the formats are right if not signing_key and not gpg_keyid and not gpg_use_default: raise ValueError("Pass either a signing key, a gpg keyid or set" " gpg_use_default to True") if signing_key: _check_match_signing_key(signing_key) if gpg_keyid: securesystemslib.formats.KEYID_SCHEMA.check_match(gpg_keyid) if exclude_patterns: securesystemslib.formats.NAMES_SCHEMA.check_match(exclude_patterns) if base_path: securesystemslib.formats.PATH_SCHEMA.check_match(base_path) if metadata_directory: securesystemslib.formats.PATH_SCHEMA.check_match(metadata_directory) # Load preliminary link file # If we have a signing key we can use the keyid to construct the name if signing_key: unfinished_fn = UNFINISHED_FILENAME_FORMAT.format( step_name=step_name, keyid=signing_key["keyid"]) # FIXME: Currently there is no way to know the default GPG key's keyid and # so we glob for preliminary link files else: unfinished_fn_glob = UNFINISHED_FILENAME_FORMAT_GLOB.format( step_name=step_name, pattern="*") unfinished_fn_list = glob.glob(unfinished_fn_glob) if not len(unfinished_fn_list): raise in_toto.exceptions.LinkNotFoundError( "Could not find a preliminary" " link for step '{}' in the current working directory.".format( step_name)) if len(unfinished_fn_list) > 1: raise in_toto.exceptions.LinkNotFoundError( "Found more than one" " preliminary links for step '{}' in the current working directory:" " {}. We need exactly one to stop recording.".format( step_name, ", ".join(unfinished_fn_list))) unfinished_fn = unfinished_fn_list[0] LOG.info("Loading preliminary link metadata '{}'...".format(unfinished_fn)) link_metadata = Metablock.load(unfinished_fn) # The file must have been signed by the same key # If we have a signing_key we use it for verification as well if signing_key: LOG.info( "Verifying preliminary link signature using passed signing key...") keyid = signing_key["keyid"] verification_key = signing_key elif gpg_keyid: LOG.info( "Verifying preliminary link signature using passed gpg key...") gpg_pubkey = securesystemslib.gpg.functions.export_pubkey( gpg_keyid, gpg_home) keyid = gpg_pubkey["keyid"] verification_key = gpg_pubkey else: # must be gpg_use_default # FIXME: Currently there is no way to know the default GPG key's keyid # before signing. As a workaround we extract the keyid of the preliminary # Link file's signature and try to export a pubkey from the gpg # home directory. We do this even if a gpg_keyid was specified, because gpg # accepts many different ids (mail, name, parts of an id, ...) but we # need a specific format. LOG.info( "Verifying preliminary link signature using default gpg key...") keyid = link_metadata.signatures[0]["keyid"] gpg_pubkey = securesystemslib.gpg.functions.export_pubkey( keyid, gpg_home) verification_key = gpg_pubkey link_metadata.verify_signature(verification_key) # Record products if a product path list was passed if product_list: LOG.info("Recording products '{}'...".format(", ".join(product_list))) link_metadata.signed.products = record_artifacts_as_dict( product_list, exclude_patterns=exclude_patterns, base_path=base_path, follow_symlink_dirs=True, normalize_line_endings=normalize_line_endings, lstrip_paths=lstrip_paths) link_metadata.signatures = [] if signing_key: LOG.info("Updating signature with key '{:.8}...'...".format(keyid)) link_metadata.sign(signing_key) else: # gpg_keyid or gpg_use_default # In both cases we use the keyid we got from verifying the preliminary # link signature above. LOG.info("Updating signature with gpg key '{:.8}...'...".format(keyid)) link_metadata.sign_gpg(keyid, gpg_home) fn = FILENAME_FORMAT.format(step_name=step_name, keyid=keyid) if metadata_directory is not None: fn = os.path.join(metadata_directory, fn) LOG.info("Storing link metadata to '{}'...".format(fn)) link_metadata.dump(fn) LOG.info("Removing unfinished link metadata '{}'...".format(unfinished_fn)) os.remove(unfinished_fn)
def in_toto_record_start(step_name, material_list, signing_key=None, gpg_keyid=None, gpg_use_default=False, gpg_home=None, exclude_patterns=None, base_path=None, record_environment=False, normalize_line_endings=False, lstrip_paths=None): """Generates preliminary link metadata. Records paths and hashes of materials in a preliminary link metadata file. The metadata is signed with the passed signing_key, a gpg key identified by its ID, or the default gpg key. If multiple key arguments are passed, only one key is used in above order of precedence. At least one key argument must be passed. The resulting link file is written to ``.STEP-NAME.KEYID-PREFIX.link-unfinished``. Use this function together with in_toto_record_stop as an alternative to in_toto_run, in order to provide evidence for supply chain steps that cannot be carried out by a single command. Arguments: step_name: A unique name to associate link metadata with a step. material_list: A list of artifact paths to be recorded as materials. Directories are traversed recursively. signing_key (optional): A key used to sign the resulting link metadata. The format is securesystemslib.formats.KEY_SCHEMA. gpg_keyid (optional): A keyid used to identify a local gpg key used to sign the resulting link metadata. gpg_use_default (optional): A boolean indicating if the default gpg key should be used to sign the resulting link metadata. gpg_home (optional): A path to the gpg home directory. If not set the default gpg home directory is used. exclude_patterns (optional): A list of filename patterns to exclude certain files from being recorded as artifacts. See Config docs for details. base_path (optional): A path relative to which artifacts are recorded. Default is the current working directory. record_environment (optional): A boolean indicating if information about the environment should be added in the resulting link metadata. normalize_line_endings (optional): A boolean indicating if line endings of artifacts should be normalized before hashing for cross-platform support. lstrip_paths (optional): A list of path prefixes used to left-strip artifact paths before storing them in the resulting link metadata. Raises: securesystemslib.exceptions.FormatError: Passed arguments are malformed. ValueError: None of signing_key, gpg_keyid or gpg_use_default=True is passed. securesystemslib.exceptions.StorageError: Cannot hash artifacts. PrefixError: Left-stripping artifact paths results in non-unique dict keys. securesystemslib.process.subprocess.TimeoutExpired: Link command times out. IOError, PermissionError: Cannot write link metadata. securesystemslib.exceptions.CryptoError, \ securesystemslib.exceptions.UnsupportedAlgorithmError: Signing errors. ValueError, OSError, securesystemslib.gpg.exceptions.CommandError, \ securesystemslib.gpg.exceptions.KeyNotFoundError: gpg signing errors. Side Effects: Reads artifact files from disk. Calls system gpg in a subprocess, if a gpg key argument is passed. Writes preliminary link metadata file to disk. """ LOG.info("Start recording '{}'...".format(step_name)) # Fail if there is no signing key arg at all if not signing_key and not gpg_keyid and not gpg_use_default: raise ValueError("Pass either a signing key, a gpg keyid or set" " gpg_use_default to True!") # Check key formats to fail early if signing_key: _check_match_signing_key(signing_key) if gpg_keyid: securesystemslib.formats.KEYID_SCHEMA.check_match(gpg_keyid) if exclude_patterns: securesystemslib.formats.NAMES_SCHEMA.check_match(exclude_patterns) if base_path: securesystemslib.formats.PATH_SCHEMA.check_match(base_path) if material_list: LOG.info("Recording materials '{}'...".format( ", ".join(material_list))) materials_dict = record_artifacts_as_dict( material_list, exclude_patterns=exclude_patterns, base_path=base_path, follow_symlink_dirs=True, normalize_line_endings=normalize_line_endings, lstrip_paths=lstrip_paths) LOG.info("Creating preliminary link metadata...") environment = {} if record_environment: environment['workdir'] = os.getcwd().replace('\\', '/') link = in_toto.models.link.Link(name=step_name, materials=materials_dict, products={}, command=[], byproducts={}, environment=environment) link_metadata = Metablock(signed=link) if signing_key: LOG.info("Signing link metadata using passed key...") signature = link_metadata.sign(signing_key) elif gpg_keyid: LOG.info("Signing link metadata using passed GPG keyid...") signature = link_metadata.sign_gpg(gpg_keyid, gpg_home=gpg_home) else: # (gpg_use_default) LOG.info("Signing link metadata using default GPG key ...") signature = link_metadata.sign_gpg(gpg_keyid=None, gpg_home=gpg_home) # We need the signature's keyid to write the link to keyid infix'ed filename signing_keyid = signature["keyid"] unfinished_fn = UNFINISHED_FILENAME_FORMAT.format(step_name=step_name, keyid=signing_keyid) LOG.info( "Storing preliminary link metadata to '{}'...".format(unfinished_fn)) link_metadata.dump(unfinished_fn)
def in_toto_record_stop(step_name, product_list, signing_key=None, gpg_keyid=None, gpg_use_default=False, gpg_home=None, exclude_patterns=None, base_path=None, normalize_line_endings=False): """ <Purpose> Finishes creating link metadata for a multi-part in-toto step. Loads unfinished link metadata file from disk, verifies that the file was signed with either the passed signing key, a gpg key identified by the passed gpg_keyid or the default gpg key. Then records products, updates unfinished Link object (products and signature), removes unfinished link file from and stores new link file to disk. One of signing_key, gpg_keyid or gpg_use_default has to be passed and it needs to be the same that was used with preceding in_toto_record_start. <Arguments> step_name: A unique name to relate link metadata with a step defined in the layout. product_list: List of file or directory paths that should be recorded as products. signing_key: (optional) If not None, link metadata is signed with this key. Format is securesystemslib.formats.KEY_SCHEMA gpg_keyid: (optional) If not None, link metadata is signed with a gpg key identified by the passed keyid. gpg_use_default: (optional) If True, link metadata is signed with default gpg key. gpg_home: (optional) Path to GPG keyring (if not set the default keyring is used). exclude_patterns: (optional) Artifacts matched by the pattern are excluded from the products sections in the resulting link. base_path: (optional) If passed, record products relative to base_path. Default is current working directory. NOTE: The base_path part of the recorded products is not included in the resulting preliminary link's product section. normalize_line_endings: (optional) If True, replaces windows and mac line endings with unix line endings before hashing products, for cross-platform support. <Exceptions> ValueError if none of signing_key, gpg_keyid or gpg_use_default=True is passed. securesystemslib.FormatError if a signing_key is passed and does not match securesystemslib.formats.KEY_SCHEMA or a gpg_keyid is passed and does not match securesystemslib.formats.KEYID_SCHEMA, or exclude_patterns are passed and don't match securesystemslib.formats.NAMES_SCHEMA, or base_path is passed and does not match securesystemslib.formats.PATH_SCHEMA or is not a directory. LinkNotFoundError if gpg is used for signing and the corresponding preliminary link file can not be found in the current working directory <Side Effects> Writes newly created link metadata file to disk using the filename scheme from link.FILENAME_FORMAT Removes unfinished link file link.UNFINISHED_FILENAME_FORMAT from disk <Returns> None. """ log.info("Stop recording '{}'...".format(step_name)) # Check that we have something to sign and if the formats are right if not signing_key and not gpg_keyid and not gpg_use_default: raise ValueError("Pass either a signing key, a gpg keyid or set" " gpg_use_default to True") if signing_key: _check_match_signing_key(signing_key) if gpg_keyid: securesystemslib.formats.KEYID_SCHEMA.check_match(gpg_keyid) if exclude_patterns: securesystemslib.formats.NAMES_SCHEMA.check_match(exclude_patterns) if base_path: securesystemslib.formats.PATH_SCHEMA.check_match(base_path) # Load preliminary link file # If we have a signing key we can use the keyid to construct the name if signing_key: unfinished_fn = UNFINISHED_FILENAME_FORMAT.format( step_name=step_name, keyid=signing_key["keyid"]) # FIXME: Currently there is no way to know the default GPG key's keyid and # so we glob for preliminary link files else: unfinished_fn_glob = UNFINISHED_FILENAME_FORMAT_GLOB.format( step_name=step_name, pattern="*") unfinished_fn_list = glob.glob(unfinished_fn_glob) if not len(unfinished_fn_list): raise in_toto.exceptions.LinkNotFoundError( "Could not find a preliminary" " link for step '{}' in the current working directory.".format( step_name)) if len(unfinished_fn_list) > 1: raise in_toto.exceptions.LinkNotFoundError( "Found more than one" " preliminary links for step '{}' in the current working directory:" " {}. We need exactly one to stop recording.".format( step_name, ", ".join(unfinished_fn_list))) unfinished_fn = unfinished_fn_list[0] log.info("Loading preliminary link metadata '{}'...".format(unfinished_fn)) link_metadata = Metablock.load(unfinished_fn) # The file must have been signed by the same key # If we have a signing_key we use it for verification as well if signing_key: log.info( "Verifying preliminary link signature using passed signing key...") keyid = signing_key["keyid"] verification_key = signing_key elif gpg_keyid: log.info( "Verifying preliminary link signature using passed gpg key...") gpg_pubkey = in_toto.gpg.functions.gpg_export_pubkey( gpg_keyid, gpg_home) keyid = gpg_pubkey["keyid"] verification_key = gpg_pubkey else: # must be gpg_use_default # FIXME: Currently there is no way to know the default GPG key's keyid # before signing. As a workaround we extract the keyid of the preliminary # Link file's signature and try to export a pubkey from the gpg # keyring. We do this even if a gpg_keyid was specified, because gpg # accepts many different ids (mail, name, parts of an id, ...) but we # need a specific format. log.info( "Verifying preliminary link signature using default gpg key...") keyid = link_metadata.signatures[0]["keyid"] gpg_pubkey = in_toto.gpg.functions.gpg_export_pubkey(keyid, gpg_home) verification_key = gpg_pubkey link_metadata.verify_signature(verification_key) # Record products if a product path list was passed if product_list: log.info("Recording products '{}'...".format(", ".join(product_list))) link_metadata.signed.products = record_artifacts_as_dict( product_list, exclude_patterns=exclude_patterns, base_path=base_path, follow_symlink_dirs=True, normalize_line_endings=normalize_line_endings) link_metadata.signatures = [] if signing_key: log.info("Updating signature with key '{:.8}...'...".format(keyid)) link_metadata.sign(signing_key) else: # gpg_keyid or gpg_use_default # In both cases we use the keyid we got from verifying the preliminary # link signature above. log.info("Updating signature with gpg key '{:.8}...'...".format(keyid)) link_metadata.sign_gpg(keyid, gpg_home) fn = FILENAME_FORMAT.format(step_name=step_name, keyid=keyid) log.info("Storing link metadata to '{}'...".format(fn)) link_metadata.dump(fn) log.info("Removing unfinished link metadata '{}'...".format(unfinished_fn)) os.remove(unfinished_fn)
def in_toto_record_start(step_name, material_list, signing_key=None, gpg_keyid=None, gpg_use_default=False, gpg_home=None, exclude_patterns=None, base_path=None, record_environment=False, normalize_line_endings=False): """ <Purpose> Starts creating link metadata for a multi-part in-toto step. I.e. records passed materials, creates link meta data object from it, signs it with passed signing_key, gpg key identified by the passed gpg_keyid or the default gpg key and stores it to disk under UNFINISHED_FILENAME_FORMAT. One of signing_key, gpg_keyid or gpg_use_default has to be passed. <Arguments> step_name: A unique name to relate link metadata with a step defined in the layout. material_list: List of file or directory paths that should be recorded as materials. signing_key: (optional) If not None, link metadata is signed with this key. Format is securesystemslib.formats.KEY_SCHEMA gpg_keyid: (optional) If not None, link metadata is signed with a gpg key identified by the passed keyid. gpg_use_default: (optional) If True, link metadata is signed with default gpg key. gpg_home: (optional) Path to GPG keyring (if not set the default keyring is used). exclude_patterns: (optional) Artifacts matched by the pattern are excluded from the materials section in the resulting preliminary link. base_path: (optional) If passed, record materials relative to base_path. Default is current working directory. NOTE: The base_path part of the recorded materials is not included in the resulting preliminary link's material section. record_environment: (optional) if values such as workdir should be recorded on the environment dictionary (false by default) normalize_line_endings: (optional) If True, replaces windows and mac line endings with unix line endings before hashing materials, for cross-platform support. <Exceptions> ValueError if none of signing_key, gpg_keyid or gpg_use_default=True is passed. securesystemslib.FormatError if a signing_key is passed and does not match securesystemslib.formats.KEY_SCHEMA or a gpg_keyid is passed and does not match securesystemslib.formats.KEYID_SCHEMA or exclude_patterns are passed and don't match securesystemslib.formats.NAMES_SCHEMA, or base_path is passed and does not match securesystemslib.formats.PATH_SCHEMA or is not a directory. <Side Effects> Writes newly created link metadata file to disk using the filename scheme from link.UNFINISHED_FILENAME_FORMAT <Returns> None. """ log.info("Start recording '{}'...".format(step_name)) # Fail if there is no signing key arg at all if not signing_key and not gpg_keyid and not gpg_use_default: raise ValueError("Pass either a signing key, a gpg keyid or set" " gpg_use_default to True!") # Check key formats to fail early if signing_key: _check_match_signing_key(signing_key) if gpg_keyid: securesystemslib.formats.KEYID_SCHEMA.check_match(gpg_keyid) if exclude_patterns: securesystemslib.formats.NAMES_SCHEMA.check_match(exclude_patterns) if base_path: securesystemslib.formats.PATH_SCHEMA.check_match(base_path) if material_list: log.info("Recording materials '{}'...".format( ", ".join(material_list))) materials_dict = record_artifacts_as_dict( material_list, exclude_patterns=exclude_patterns, base_path=base_path, follow_symlink_dirs=True, normalize_line_endings=normalize_line_endings) log.info("Creating preliminary link metadata...") environment = {} if record_environment: environment['workdir'] = os.getcwd().replace('\\', '/') link = in_toto.models.link.Link(name=step_name, materials=materials_dict, products={}, command=[], byproducts={}, environment=environment) link_metadata = Metablock(signed=link) if signing_key: log.info("Signing link metadata using passed key...") signature = link_metadata.sign(signing_key) elif gpg_keyid: log.info("Signing link metadata using passed GPG keyid...") signature = link_metadata.sign_gpg(gpg_keyid, gpg_home=gpg_home) else: # (gpg_use_default) log.info("Signing link metadata using default GPG key ...") signature = link_metadata.sign_gpg(gpg_keyid=None, gpg_home=gpg_home) # We need the signature's keyid to write the link to keyid infix'ed filename signing_keyid = signature["keyid"] unfinished_fn = UNFINISHED_FILENAME_FORMAT.format(step_name=step_name, keyid=signing_keyid) log.info( "Storing preliminary link metadata to '{}'...".format(unfinished_fn)) link_metadata.dump(unfinished_fn)