Skip to content

trestle.core.commands.sign_manifest

trestle.core.commands.sign_manifest ¤

Trestle Sign Manifest Command.

Attributes¤

logger = logging.getLogger(__name__) module-attribute ¤

Classes¤

SignManifestCmd ¤

Bases: CommandBase


              flowchart TD
              trestle.core.commands.sign_manifest.SignManifestCmd[SignManifestCmd]
              trestle.core.commands.command_docs.CommandBase[CommandBase]

                              trestle.core.commands.command_docs.CommandBase --> trestle.core.commands.sign_manifest.SignManifestCmd
                


              click trestle.core.commands.sign_manifest.SignManifestCmd href "" "trestle.core.commands.sign_manifest.SignManifestCmd"
              click trestle.core.commands.command_docs.CommandBase href "" "trestle.core.commands.command_docs.CommandBase"
            

Sign a JSON package manifest as a detached DSSE envelope.

The manifest lists local JSON artifacts that belong to a package. This command canonicalizes each listed JSON artifact with RFC 8785, records each selected digest in an in-toto Statement, signs the Statement using the provided PEM private key, and writes the detached package envelope.

Source code in trestle/core/commands/sign_manifest.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
class SignManifestCmd(CommandBase):
    """Sign a JSON package manifest as a detached DSSE envelope.

    The manifest lists local JSON artifacts that belong to a package. This
    command canonicalizes each listed JSON artifact with RFC 8785, records each
    selected digest in an in-toto Statement, signs the Statement using the
    provided PEM private key, and writes the detached package envelope.
    """

    name = 'sign-manifest'

    def _init_arguments(self) -> None:
        self.add_argument(
            '--manifest', help='Path to the JSON package manifest to sign.', required=True, type=pathlib.Path
        )
        self.add_argument(
            '--private-key',
            dest='key',
            help='Path to the PEM private key for signing.',
            required=True,
            type=pathlib.Path,
        )
        self.add_argument(
            '--key-password-env',
            help='Environment variable containing the password for an encrypted PEM private key.',
            default=None,
        )
        self.add_argument('-o', '--output', help='Output DSSE package envelope file.', required=True, type=pathlib.Path)
        self.add_argument('--overwrite', help='Replace an existing DSSE package envelope.', action='store_true')
        self.add_argument(
            '--digest-algorithm',
            choices=[algorithm.value for algorithm in Algorithm],
            default=DEFAULT_DIGEST_ALGORITHM.value,
            help='Artifact digest algorithm (default: SHA-256).',
        )

    @beta_feature('json-manifest-signing')
    def _run(self, args: argparse.Namespace) -> int:
        """Sign a JSON package manifest."""
        try:
            log.set_log_level_from_args(args)
            self.sign_manifest(
                args.manifest,
                args.key,
                args.output,
                args.key_password_env,
                args.overwrite,
                digest_algorithm=Algorithm(args.digest_algorithm),
            )
            return CmdReturnCodes.SUCCESS.value
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while signing package manifest')

    @classmethod
    def sign_manifest(
        cls,
        manifest_path: pathlib.Path,
        key_path: pathlib.Path,
        output_path: pathlib.Path,
        key_password_env: Optional[str] = None,
        overwrite: bool = False,
        digest_algorithm: Algorithm = DEFAULT_DIGEST_ALGORITHM,
    ) -> None:
        """Write a detached DSSE envelope for a JSON package manifest."""
        manifest_path = manifest_path.resolve()
        key_path = key_path.resolve()
        resolved_output_path = output_path.resolve()
        if manifest_path == resolved_output_path:
            raise TrestleError('DSSE output path must be different from package manifest file.')
        if key_path == resolved_output_path:
            raise TrestleError('DSSE output path must be different from private key file.')

        key_password = None
        if key_password_env is not None:
            if key_password_env not in os.environ:
                raise TrestleError(f'Key password environment variable is not set: {key_password_env}')
            key_password = os.environ[key_password_env]

        signer = load_pem_private_key_signer(key_path, key_password)
        manifest = load_signing_manifest(manifest_path)
        envelope = create_manifest_envelope(manifest, signer, digest_algorithm)
        write_dsse_envelope(envelope, output_path, overwrite)
Attributes¤
name = 'sign-manifest' class-attribute instance-attribute ¤
Methods:¤
sign_manifest(manifest_path, key_path, output_path, key_password_env=None, overwrite=False, digest_algorithm=DEFAULT_DIGEST_ALGORITHM) classmethod ¤

Write a detached DSSE envelope for a JSON package manifest.

Source code in trestle/core/commands/sign_manifest.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@classmethod
def sign_manifest(
    cls,
    manifest_path: pathlib.Path,
    key_path: pathlib.Path,
    output_path: pathlib.Path,
    key_password_env: Optional[str] = None,
    overwrite: bool = False,
    digest_algorithm: Algorithm = DEFAULT_DIGEST_ALGORITHM,
) -> None:
    """Write a detached DSSE envelope for a JSON package manifest."""
    manifest_path = manifest_path.resolve()
    key_path = key_path.resolve()
    resolved_output_path = output_path.resolve()
    if manifest_path == resolved_output_path:
        raise TrestleError('DSSE output path must be different from package manifest file.')
    if key_path == resolved_output_path:
        raise TrestleError('DSSE output path must be different from private key file.')

    key_password = None
    if key_password_env is not None:
        if key_password_env not in os.environ:
            raise TrestleError(f'Key password environment variable is not set: {key_password_env}')
        key_password = os.environ[key_password_env]

    signer = load_pem_private_key_signer(key_path, key_password)
    manifest = load_signing_manifest(manifest_path)
    envelope = create_manifest_envelope(manifest, signer, digest_algorithm)
    write_dsse_envelope(envelope, output_path, overwrite)

Functions:¤

handler: python