Esempio n. 1
0
def _load_metadata(file_path):
    """
  <Purpose>
    Loads Metablock (link or layout metadata) file from disk

  <Arguments>
    file_path:
            path to link or layout metadata file

  <Exceptions>
    SystemExit(2) if any exception occurs

  <Returns>
    in-toto Metablock object (contains Link or Layout object)

  """
    try:
        return Metablock.load(file_path)

    except Exception as e:
        log.error("The following error occurred while loading the file '{}': "
                  "{}".format(file_path, e))
        sys.exit(2)
Esempio n. 2
0
    def __verify_in_toto_metadata(self, target_relpath,
                                  in_toto_metadata_relpaths, pubkey_relpaths):
        # Make a temporary directory.
        tempdir = tempfile.mkdtemp()
        prev_cwd = os.getcwd()

        try:
            # Copy files over into temp dir.
            rel_paths = [target_relpath
                         ] + in_toto_metadata_relpaths + pubkey_relpaths
            for rel_path in rel_paths:
                # Don't confuse Python with any leading path separator.
                rel_path = rel_path.lstrip('/')
                abs_path = os.path.join(self.__targets_dir, rel_path)
                shutil.copy(abs_path, tempdir)

            # Switch to the temp dir.
            os.chdir(tempdir)

            # Load the root layout and public keys.
            layout = Metablock.load('root.layout')
            pubkeys = glob.glob('*.pub')
            layout_key_dict = import_public_keys_from_files_as_dict(pubkeys)
            # Verify and inspect.
            params = substitute(target_relpath)
            verifylib.in_toto_verify(layout,
                                     layout_key_dict,
                                     substitution_parameters=params)
            logger.info('in-toto verified {}'.format(target_relpath))
        except:
            logger.exception(
                'in-toto failed to verify {}'.format(target_relpath))
            raise
        finally:
            os.chdir(prev_cwd)
            # Delete temp dir.
            shutil.rmtree(tempdir)
Esempio n. 3
0
    def setUpClass(self):
        """Copy test gpg rsa keyring, gpg demo metadata files and demo final
    product to tmp test dir. """

        self.working_dir = os.getcwd()
        self.test_dir = os.path.realpath(tempfile.mkdtemp())

        # Copy gpg keyring
        gpg_keyring_path = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "rsa")

        self.gnupg_home = os.path.join(self.test_dir, "rsa")
        shutil.copytree(gpg_keyring_path, self.gnupg_home)

        self.owner_gpg_keyid = "8465a1e2e0fb2b40adb2478e18fb3f537e0c8a17"

        # Copy gpg demo metadata files
        demo_files = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                  "demo_files_gpg")

        # find where the scripts directory is located.
        scripts_directory = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), "scripts")

        for file in os.listdir(demo_files):
            shutil.copy(os.path.join(demo_files, file), self.test_dir)

        # Change into test dir
        os.chdir(self.test_dir)
        shutil.copytree(scripts_directory, 'scripts')

        # Sign layout template with gpg key
        layout_template = Metablock.load("demo.layout.template")

        self.layout_path = "gpg_signed.layout"
        layout_template.sign_gpg(self.owner_gpg_keyid, self.gnupg_home)
        layout_template.dump(self.layout_path)
Esempio n. 4
0
def main():
    """Parse arguments and call in_toto_verify. """
    parser = create_parser()
    args = parser.parse_args()

    LOG.setLevelVerboseOrQuiet(args.verbose, args.quiet)

    # For verifying at least one of --layout-keys or --gpg must be specified
    # Note: Passing both at the same time is possible.
    if (args.layout_keys is None) and (args.gpg is None):
        parser.print_help()
        parser.error("wrong arguments: specify at least one of"
                     " '--layout-keys path [path ...]' or '--gpg id [id ...]'")

    try:
        LOG.info("Loading layout...")
        layout = Metablock.load(args.layout)

        layout_key_dict = {}
        if args.layout_keys is not None:
            LOG.info("Loading layout key(s)...")
            layout_key_dict.update(
                interface.import_publickeys_from_file(args.layout_keys,
                                                      args.key_types))

        if args.gpg is not None:
            LOG.info("Loading layout gpg key(s)...")
            layout_key_dict.update(
                gpg_interface.export_pubkeys(args.gpg, homedir=args.gpg_home))

        verifylib.in_toto_verify(layout, layout_key_dict, args.link_dir)

    except Exception as e:
        LOG.error("(in-toto-verify) {0}: {1}".format(type(e).__name__, e))
        sys.exit(1)

    sys.exit(0)
Esempio n. 5
0
 def test_create_unfininished_metadata_verify_signature(self):
     """Test record start creates metadata with expected signature. """
     in_toto_record_start(self.step_name, [self.test_material], self.key)
     link = Metablock.load(self.link_name_unfinished)
     link.verify_signature(self.key)
     os.remove(self.link_name_unfinished)
Esempio n. 6
0
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)
Esempio n. 7
0
    def setUpClass(self):
        """Creates and changes into temporary directory.
    Copies demo files to temp dir...
      - owner/functionary key pairs
      - *.link metadata files
      - layout template (not signed, no expiration date)
      - final product

    ...and dumps various layouts for different test scenarios
    """
        # Backup original cwd
        self.working_dir = os.getcwd()

        # Find demo files
        demo_files = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                  "demo_files")

        # Create and change into temporary directory
        self.test_dir = os.path.realpath(tempfile.mkdtemp())
        os.chdir(self.test_dir)

        # Copy demo files to temp dir
        for file in os.listdir(demo_files):
            shutil.copy(os.path.join(demo_files, file), self.test_dir)

        # Load layout template
        layout_template = Metablock.load("demo.layout.template")

        # Store various layout paths to be used in tests
        self.layout_single_signed_path = "single-signed.layout"
        self.layout_double_signed_path = "double-signed.layout"
        self.layout_bad_sig = "bad-sig.layout"
        self.layout_expired_path = "expired.layout"
        self.layout_failing_step_rule_path = "failing-step-rule.layout"
        self.layout_failing_inspection_rule_path = "failing-inspection-rule.layout"
        self.layout_failing_inspection_retval = "failing-inspection-retval.layout"

        # Import layout signing keys
        alice = import_rsa_key_from_file("alice")
        bob = import_rsa_key_from_file("bob")
        self.alice_path = "alice.pub"
        self.bob_path = "bob.pub"

        # dump single signed layout
        layout = copy.deepcopy(layout_template)
        layout.sign(alice)
        layout.dump(self.layout_single_signed_path)

        # dump double signed layout
        layout = copy.deepcopy(layout_template)
        layout.sign(alice)
        layout.sign(bob)
        layout.dump(self.layout_double_signed_path)

        # dump layout with bad signature
        layout = copy.deepcopy(layout_template)
        layout.sign(alice)
        layout.signed.readme = "this breaks the signature"
        layout.dump(self.layout_bad_sig)

        # dump expired layout
        layout = copy.deepcopy(layout_template)
        layout.signed.expires = (
            datetime.today() +
            relativedelta(months=-1)).strftime("%Y-%m-%dT%H:%M:%SZ")
        layout.sign(alice)
        layout.dump(self.layout_expired_path)

        # dump layout with failing step rule
        layout = copy.deepcopy(layout_template)
        layout.signed.steps[0].expected_products.insert(0, ["MODIFY", "*"])
        layout.sign(alice)
        layout.dump(self.layout_failing_step_rule_path)

        # dump layout with failing inspection rule
        layout = copy.deepcopy(layout_template)
        layout.signed.inspect[0].expected_materials.insert(0, ["MODIFY", "*"])
        layout.sign(alice)
        layout.dump(self.layout_failing_inspection_rule_path)

        # dump layout with failing inspection retval
        layout = copy.deepcopy(layout_template)
        layout.signed.inspect[0].run = ["expr", "1", "/", "0"]
        layout.sign(alice)
        layout.dump(self.layout_failing_inspection_retval)
Esempio n. 8
0
 def test_verify_passing_double_signed_layout(self):
     """Test pass verification of double-signed layout. """
     layout = Metablock.load(self.layout_double_signed_path)
     layout_key_dict = import_rsa_public_keys_from_files_as_dict(
         [self.alice_path, self.bob_path])
     in_toto_verify(layout, layout_key_dict)
Esempio n. 9
0
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)
Esempio n. 10
0
 def test_create_unfinished_metadata_with_expected_material(self):
     """Test record start creates metadata with expected material. """
     in_toto_record_start(self.step_name, self.key, [self.test_material])
     link = Metablock.load(self.link_name_unfinished)
     self.assertEquals(link.signed.materials.keys(), [self.test_material])
     os.remove(self.link_name_unfinished)
Esempio n. 11
0
def load_links_for_layout(layout, link_dir_path):
  """
  <Purpose>
    Try to load all existing metadata files for each Step of the Layout
    from the current directory.

    For each step the metadata might consist of multiple (thresholds) Link
    or Layout (sub-layouts) files.

  <Arguments>
    layout:
          Layout object

    link_dir_path:
          A path to directory where links are loaded from


  <Side Effects>
    Calls function to read files from disk

  <Exceptions>
    in_toto.exceptions.LinkNotFoundError,
            if fewer than `threshold` link files can be found for any given
            step of the supply chain (preliminary threshold check)

  <Returns>
    A dictionary carrying all the found metadata corresponding to the
    passed layout, e.g.:

    {
      <step name> : {
        <functionary key id> : <Metablock containing a Link or Layout object>,
        ...
      }, ...
    }


  """
  steps_metadata = {}

  # Iterate over all the steps in the layout
  for step in layout.steps:
    links_per_step = {}

    # We try to load a link for every authorized functionary, but don't fail
    # if the file does not exist (authorized != required)
    # FIXME: Should we really pass on IOError, or just skip inexistent links?
    for authorized_keyid in step.pubkeys:
      # Iterate over the authorized key and if present over subkeys
      for keyid in [authorized_keyid] + list(layout.keys.get(authorized_keyid,
          {}).get("subkeys", {}).keys()):

        filename = in_toto.models.link.FILENAME_FORMAT.format(
            step_name=step.name, keyid=keyid)
        filepath = os.path.join(link_dir_path, filename)

        try:
          metadata = Metablock.load(filepath)
          links_per_step[keyid] = metadata

        except IOError:
          pass

    # This is only a preliminary threshold check, based on (authorized)
    # filenames, to fail early. A more thorough signature-based threshold
    # check is indispensable.
    if len(links_per_step) < step.threshold:
      raise in_toto.exceptions.LinkNotFoundError("Step '{0}' requires '{1}'"
          " link metadata file(s), found '{2}'."
          .format(step.name, step.threshold, len(links_per_step)))

    steps_metadata[step.name] = links_per_step

  return steps_metadata
# we have a TUF-vetted package now, but we can dive a little bit further back
# now :)
client.download_target(package_info, 'client')
x_in_toto = package_info['fileinfo']['custom']['x-in-toto']
for _target in x_in_toto:
    in_toto_md_info = client.get_one_valid_targetinfo(_target)
    client.download_target(in_toto_md_info, 'client')

# we should be ready for in-toto verification

print("Setting up test hardness for in-toto verification")
alice = import_ed25519_publickey_from_file('./keys/alice.pub')
layout_keys = {x['keyid']: x for x in [alice]}
shutil.copyfile("client/packages/demo-project.tar.gz",
                "in_toto_md/demo-project.tar.gz")

for _file in glob.glob("client/layouts/*"):
    shutil.copyfile(_file, os.path.join("in_toto_md", os.path.basename(_file)))

os.chdir("in_toto_md")
layout = Metablock.load('root.layout')

# if things are horrible, this will throw a nasty exception. You just wait...
print("Running in-toto verification")
verifylib.in_toto_verify(layout, layout_keys)

# Since it didn't, we can probably copy our target to the main directory
print("Successfully verified with TUF and in-toto!")
shutil.copyfile('demo-project.tar.gz', '../demo-project.tar.gz')
Esempio n. 13
0
def main():
    """Parse arguments and call in_toto_verify. """

    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="""
Verifies that a software supply chain was carried out according to the passed
in-toto supply chain layout.

The verification includes the following checks:
  * the layout was signed with the the passed key(s),
  * the layout has not expired,
  * signed link metadata files exist for each step of the layout in the CWD,
  * link files are provided by the required number of authorized functionaries,
  * the materials and products for each step, as reported by the corresponding
    link files, adhere to the artifact rules specified by the step.

Additionally, inspection commands defined in the layout are executed
sequentially, followed by applying the inspection's artifact rules.

The command returns a nonzero value if verification fails and zero otherwise.
""")

    parser.usage = "%(prog)s <named arguments> [optional arguments]"

    parser.epilog = """
examples:
  Verify supply chain in 'root.layout', signed with private part of
  'key_file.pub'.

      {prog} --layout root.layout --layout-keys key_file.pub


  Verify supply chain like above but load links corresponding to steps of
  root.layout from 'link_dir'.

      {prog} --layout root.layout --layout-keys key_file.pub \\
          --link-dir link_dir


  Verify supply chain in 'root.layout', signed with GPG key '...7E0C8A17',
  whose public part can be found in the GPG keyring at '~/.gnupg'.

      {prog} --layout root.layout \\
      --gpg 8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17 --gpg-home ~/.gnupg


""".format(prog=parser.prog)

    named_args = parser.add_argument_group("required named arguments")

    named_args.add_argument(
        "-l",
        "--layout",
        type=str,
        required=True,
        metavar="<path>",
        help=("Path to root layout specifying the software supply chain to be"
              " verified."))

    named_args.add_argument(
        "-k",
        "--layout-keys",
        type=str,
        metavar="<path>",
        nargs="+",
        help=
        ("Path(s) to PEM formatted public key(s), used to verify the passed root"
         " layout's signature(s)."
         " Passing at least one key using '--layout-keys' and/or '--gpg' is"
         " required. For each passed key the layout must carry a valid"
         " signature."))

    named_args.add_argument(
        "-g",
        "--gpg",
        nargs="+",
        metavar="<id>",
        help=
        ("GPG keyid, identifying a public key in the GPG keychain, used to verify"
         " the passed root layout's signature(s)."
         " Passing at least one key using '--layout-keys' and/or '--gpg' is"
         " required. For each passed key the layout must carry a valid"
         " signature."))

    parser.add_argument(
        "--link-dir",
        dest="link_dir",
        type=str,
        metavar="<path>",
        default=".",
        help=(
            "Path to directory where link metadata files for steps defined in"
            " the root layout should be loaded from. If not passed links are"
            " loaded from the current working directory."))

    parser.add_argument(
        "--gpg-home",
        dest="gpg_home",
        type=str,
        metavar="<path>",
        help=
        ("Path to GPG keyring to load GPG key identified"
         " by '--gpg' option.  If '--gpg-home' is not passed, the default GPG"
         " keyring is used."))

    verbosity_args = parser.add_mutually_exclusive_group(required=False)
    verbosity_args.add_argument("-v",
                                "--verbose",
                                dest="verbose",
                                help="Verbose execution.",
                                action="store_true")

    verbosity_args.add_argument("-q",
                                "--quiet",
                                dest="quiet",
                                help="Suppress all output.",
                                action="store_true")

    args = parser.parse_args()

    log.setLevelVerboseOrQuiet(args.verbose, args.quiet)

    # For verifying at least one of --layout-keys or --gpg must be specified
    # Note: Passing both at the same time is possible.
    if (args.layout_keys == None) and (args.gpg == None):
        parser.print_help()
        parser.error("wrong arguments: specify at least one of"
                     " `--layout-keys path [path ...]` or `--gpg id [id ...]`")

    try:
        log.info("Loading layout...")
        layout = Metablock.load(args.layout)

        layout_key_dict = {}
        if args.layout_keys != None:
            log.info("Loading layout key(s)...")
            layout_key_dict.update(
                in_toto.util.import_rsa_public_keys_from_files_as_dict(
                    args.layout_keys))

        if args.gpg != None:
            log.info("Loading layout gpg key(s)...")
            layout_key_dict.update(
                in_toto.util.import_gpg_public_keys_from_keyring_as_dict(
                    args.gpg, gpg_home=args.gpg_home))

        verifylib.in_toto_verify(layout, layout_key_dict, args.link_dir)

    except Exception as e:
        log.error("(in-toto-verify) {0}: {1}".format(type(e).__name__, e))
        sys.exit(1)

    sys.exit(0)