Skip to content

Commit 48c6bb0

Browse files
Add binary digests as signing payloads. (#262)
* Add binary digests as signing payloads. This converts `DigestManifest`s to signing payloads in the style used by the existing `serialize_v0`/`serialize_v1` implementations: just the digest as a binary `BytesIO` object. For Sigstore signing, this payload can be signed via `sign_artifact` to produce just a Sigstore bundle with the signature. Signed-off-by: Mihai Maruseac <[email protected]> * Fix copy-paste typo Signed-off-by: Mihai Maruseac <[email protected]> --------- Signed-off-by: Mihai Maruseac <[email protected]>
1 parent 0769785 commit 48c6bb0

9 files changed

+141
-0
lines changed

model_signing/signing/as_bytes.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright 2024 The Sigstore Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Signing payload that is just a bytes view on the digest.
16+
17+
In general, this should only be used if we want to sign a model where we have
18+
the hash computed from somewhere else and want to avoid the in-toto types.
19+
"""
20+
21+
from typing import Self
22+
from typing_extensions import override
23+
24+
from model_signing.manifest import manifest as manifest_module
25+
from model_signing.signing import signing
26+
27+
28+
class BytesPayload(signing.SigningPayload):
29+
"""A payload that is a view into the bytes of a digest."""
30+
31+
def __init__(self, digest: bytes):
32+
"""Builds an instance of this payload.
33+
34+
Don't call this directly in production. Use `from_manifest()` instead.
35+
36+
Args:
37+
digest: The digest bytes as extracted from the manifest.
38+
"""
39+
self.digest = digest
40+
41+
@classmethod
42+
@override
43+
def from_manifest(cls, manifest: manifest_module.Manifest) -> Self:
44+
"""Converts a manifest to the signing payload used for signing.
45+
46+
The manifest must be a `DigestManifest` instance.
47+
48+
Args:
49+
manifest: the manifest to convert to signing payload.
50+
51+
Returns:
52+
An instance of `BytesPayload`.
53+
54+
Raises:
55+
TypeError: If the manifest is not `DigestManifest`.
56+
"""
57+
if not isinstance(manifest, manifest_module.DigestManifest):
58+
raise TypeError("Only DigestManifest is supported")
59+
60+
# guaranteed to have exactly one item
61+
subject = list(manifest.resource_descriptors())[0]
62+
return cls(subject.digest.digest_value)
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright 2024 The Sigstore Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for binary signing payloads.
16+
17+
NOTE: This test uses a goldens setup to compare expected results with data from
18+
files. If the golden tests are failing, regenerate the golden files with
19+
20+
pytest model_signing/ --update_goldens
21+
"""
22+
23+
import pytest
24+
25+
from model_signing import test_support
26+
from model_signing.hashing import file
27+
from model_signing.hashing import memory
28+
from model_signing.serialization import serialize_by_file
29+
from model_signing.signing import as_bytes
30+
31+
32+
class TestBytesPayload:
33+
34+
@pytest.mark.parametrize("model_fixture_name", test_support.all_test_models)
35+
def test_known_models(self, request, model_fixture_name):
36+
# Set up variables (arrange)
37+
testdata_path = request.path.parent / "testdata"
38+
test_path = testdata_path / "as_bytes"
39+
test_class_path = test_path / "TestBytesPayload"
40+
golden_path = test_class_path / model_fixture_name
41+
should_update = request.config.getoption("update_goldens")
42+
model = request.getfixturevalue(model_fixture_name)
43+
44+
# Compute payload (act)
45+
file_hasher = file.SimpleFileHasher(
46+
test_support.UNUSED_PATH, memory.SHA256()
47+
)
48+
serializer = serialize_by_file.DigestSerializer(
49+
file_hasher, memory.SHA256, allow_symlinks=True
50+
)
51+
manifest = serializer.serialize(model)
52+
payload = as_bytes.BytesPayload.from_manifest(manifest)
53+
54+
# Compare with golden, or write to golden (approximately "assert")
55+
if should_update:
56+
with open(golden_path, "w", encoding="utf-8") as f:
57+
f.write(f"{payload.digest.hex()}\n")
58+
else:
59+
with open(golden_path, "r", encoding="utf-8") as f:
60+
expected_bytes = bytes.fromhex(f.read().strip())
61+
62+
assert payload.digest == expected_bytes
63+
64+
def test_only_runs_on_expected_manifest_types(self, sample_model_folder):
65+
serializer = serialize_by_file.ManifestSerializer(
66+
lambda f: file.SimpleFileHasher(f, memory.SHA256()),
67+
allow_symlinks=True,
68+
)
69+
manifest = serializer.serialize(sample_model_folder)
70+
71+
with pytest.raises(TypeError, match="Only DigestManifest is supported"):
72+
as_bytes.BytesPayload.from_manifest(manifest)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
36eed9389ebbbe15ac15d33c81dabb60ccb7c945ff641d78f59db9aa9dc47ac9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
68efd863f20e083173846a5e98ad11387a1979efe20ded426a7930bab8358a9c
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3aab065c7181a173b5dd9e9d32a9f79923440b413be1e1ffcdba26a7365f719b
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
310af4fc4c52bf63cd1687c67076ed3e56bc5480a1b151539e6c550506ae0301
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8372365be7578241d18db47ec83b735bb450a10a1b4298d9b7b0d8bf543b7271

0 commit comments

Comments
 (0)