Skip to content

trestle.core.signing_manifest

trestle.core.signing_manifest ¤

Helpers for signed JSON package manifests.

Attributes¤

ARTIFACT_FIELDS = {'name', 'uri', 'mediaType'} module-attribute ¤

MANIFEST_FIELDS = {'primaryArtifact', 'artifacts'} module-attribute ¤

MANIFEST_VERSION = 'v1' module-attribute ¤

PACKAGE_PREDICATE_TYPE = 'https://oscal-compass.github.io/compliance-trestle/predicates/oscal-package/v1' module-attribute ¤

PACKAGE_TOOL = 'compliance-trestle' module-attribute ¤

Classes¤

ManifestArtifact dataclass ¤

An artifact listed in a package manifest.

Source code in trestle/core/signing_manifest.py
51
52
53
54
55
56
57
58
59
60
61
62
@dataclass(frozen=True)
class ManifestArtifact:
    """An artifact listed in a package manifest."""

    name: str
    uri: str
    media_type: str
    path: pathlib.Path

    def to_predicate(self) -> Dict[str, str]:
        """Return the stable predicate representation for the artifact."""
        return {'name': self.name, 'uri': self.uri, 'mediaType': self.media_type}
Attributes¤
media_type instance-attribute ¤
name instance-attribute ¤
path instance-attribute ¤
uri instance-attribute ¤
Methods:¤
__init__(name, uri, media_type, path) ¤
to_predicate() ¤

Return the stable predicate representation for the artifact.

Source code in trestle/core/signing_manifest.py
60
61
62
def to_predicate(self) -> Dict[str, str]:
    """Return the stable predicate representation for the artifact."""
    return {'name': self.name, 'uri': self.uri, 'mediaType': self.media_type}

SigningManifest dataclass ¤

A validated JSON signing manifest.

Source code in trestle/core/signing_manifest.py
65
66
67
68
69
70
71
72
73
74
75
@dataclass(frozen=True)
class SigningManifest:
    """A validated JSON signing manifest."""

    path: pathlib.Path
    primary_artifact: str
    artifacts: List[ManifestArtifact]

    def predicate_artifacts(self) -> List[Dict[str, str]]:
        """Return the stable predicate artifact list."""
        return [artifact.to_predicate() for artifact in self.artifacts]
Attributes¤
artifacts instance-attribute ¤
path instance-attribute ¤
primary_artifact instance-attribute ¤
Methods:¤
__init__(path, primary_artifact, artifacts) ¤
predicate_artifacts() ¤

Return the stable predicate artifact list.

Source code in trestle/core/signing_manifest.py
73
74
75
def predicate_artifacts(self) -> List[Dict[str, str]]:
    """Return the stable predicate artifact list."""
    return [artifact.to_predicate() for artifact in self.artifacts]

Functions:¤

build_manifest_statement(manifest, digest_algorithm=DEFAULT_DIGEST_ALGORITHM) ¤

Build the in-toto Statement payload for a signing manifest.

Source code in trestle/core/signing_manifest.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def build_manifest_statement(
    manifest: SigningManifest, digest_algorithm: Algorithm = DEFAULT_DIGEST_ALGORITHM
) -> Dict[str, Any]:
    """Build the in-toto Statement payload for a signing manifest."""
    return {
        '_type': IN_TOTO_STATEMENT_TYPE,
        'subject': _manifest_subjects(manifest, digest_algorithm),
        'predicateType': PACKAGE_PREDICATE_TYPE,
        'predicate': {
            'tool': PACKAGE_TOOL,
            'manifestVersion': MANIFEST_VERSION,
            'primaryArtifact': manifest.primary_artifact,
            'artifacts': manifest.predicate_artifacts(),
        },
    }

create_manifest_envelope(manifest, signer, digest_algorithm=DEFAULT_DIGEST_ALGORITHM) ¤

Create a DSSE envelope for a validated signing manifest.

Source code in trestle/core/signing_manifest.py
122
123
124
125
126
def create_manifest_envelope(
    manifest: SigningManifest, signer: Signer, digest_algorithm: Algorithm = DEFAULT_DIGEST_ALGORITHM
) -> Dict[str, Any]:
    """Create a DSSE envelope for a validated signing manifest."""
    return sign_in_toto_statement(build_manifest_statement(manifest, digest_algorithm), signer)

load_signing_manifest(manifest_path) ¤

Load and validate a JSON signing manifest.

Source code in trestle/core/signing_manifest.py
 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
118
119
def load_signing_manifest(manifest_path: pathlib.Path) -> SigningManifest:
    """Load and validate a JSON signing manifest."""
    manifest_path = manifest_path.resolve()
    if manifest_path.suffix.lower() != '.json':
        raise TrestleError(f'Signing manifest must be a JSON file: {manifest_path}')
    if not manifest_path.exists() or not manifest_path.is_file():
        raise TrestleError(f'Signing manifest path does not exist or is not a file: {manifest_path}')

    try:
        manifest_data, _ = load_canonical_json_file(manifest_path)
    except TrestleError as error:
        raise TrestleError(f'Unable to load signing manifest JSON: {manifest_path}: {error}') from error

    if not isinstance(manifest_data, dict):
        raise TrestleError('Signing manifest must be a JSON object.')

    _reject_unknown_fields(manifest_data, MANIFEST_FIELDS, 'Signing manifest')
    primary_artifact = _required_string(manifest_data, 'primaryArtifact', 'Signing manifest')
    artifacts_data = manifest_data.get('artifacts')
    if not isinstance(artifacts_data, list) or not artifacts_data:
        raise TrestleError('Signing manifest artifacts must be a non-empty list.')

    artifacts: List[ManifestArtifact] = []
    artifact_names = set()
    for index, artifact_data in enumerate(artifacts_data):
        if not isinstance(artifact_data, dict):
            raise TrestleError(f'Signing manifest artifact at index {index} must be a mapping.')
        _reject_unknown_fields(artifact_data, ARTIFACT_FIELDS, f'Signing manifest artifact at index {index}')
        name = _required_string(artifact_data, 'name', f'Signing manifest artifact at index {index}')
        uri = _required_string(artifact_data, 'uri', f'Signing manifest artifact {name}')
        media_type = _required_string(artifact_data, 'mediaType', f'Signing manifest artifact {name}')
        if name in artifact_names:
            raise TrestleError(f'Signing manifest contains a duplicate artifact name: {name}')
        artifact_names.add(name)
        artifact_path = _artifact_path(manifest_path.parent, uri)
        _validate_artifact_path(artifact_path)
        artifacts.append(ManifestArtifact(name, uri, media_type, artifact_path))

    if primary_artifact not in artifact_names:
        raise TrestleError(f'Signing manifest primaryArtifact is not listed in artifacts: {primary_artifact}')

    return SigningManifest(manifest_path, primary_artifact, artifacts)

verify_manifest_envelope(manifest, envelope, public_key, digest_algorithm=None) ¤

Verify a package, detecting its signed digest algorithm unless one is required.

Source code in trestle/core/signing_manifest.py
146
147
148
149
150
151
152
153
def verify_manifest_envelope(
    manifest: SigningManifest, envelope: Dict[str, Any], public_key: Key, digest_algorithm: Optional[Algorithm] = None
) -> Dict[str, Any]:
    """Verify a package, detecting its signed digest algorithm unless one is required."""
    payload = verify_dsse_payload(envelope, public_key)
    statement = load_in_toto_statement(payload)
    _validate_manifest_statement(statement, manifest, digest_algorithm)
    return statement

handler: python