Skip to content

Commit

Permalink
Implement Sigstore signing and verification of models (#276)
Browse files Browse the repository at this point in the history
* Add a `from_str` method to `manifest.Shard`.

The `ShardedFileManifestItem` objects (items in `ShardLevelManifest`)
are recorded as a single string in the in-toto payloads used for
signing. The canonicalization to string is done by appending the file
(relative) path, the start offset and the end offset, separated by `:`.

When validating a signature and rebuilding a manifest, we need to parse
a string back to a shard. Rather than replicating this in all places, we
create a member function for the conversion.

Also adds the relevant tests.

Signed-off-by: Mihai Maruseac <mihaimaruseac@google.com>

* Ensure all in-toto statements have names for subjects

Although names in in-toto are optional, for sigstore-python they are
mandatory. So, we set the name to "." when we don't have other option.

Updated goldens to reflect the change.

Signed-off-by: Mihai Maruseac <mihaimaruseac@google.com>

* Add `SigstoreSignature` for storing Sigstore signatures

Signed-off-by: Mihai Maruseac <mihaimaruseac@google.com>

* Sign models with Sigstore, generate Sigstore bundles

Supports both signing models serialized to digests (a la
`serialize_v0`/`serialize_v1`) and models serialized to manifests.

Suppoorts both signing digests directly and signing in-toto manifest.

There is a need to convert from in-toto's in-toto types to the ones
expected by sigstore-python, but this additional step will be removed in
the future.

Signed-off-by: Mihai Maruseac <mihaimaruseac@google.com>

* Verify sigstore bundles

Signed-off-by: Mihai Maruseac <mihaimaruseac@google.com>

---------

Signed-off-by: Mihai Maruseac <mihaimaruseac@google.com>
  • Loading branch information
mihaimaruseac authored Aug 5, 2024
1 parent f89c933 commit e8db349
Show file tree
Hide file tree
Showing 25 changed files with 665 additions and 6 deletions.
26 changes: 26 additions & 0 deletions model_signing/manifest/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,32 @@ def __str__(self) -> str:
"""
return f"{str(self.path)}:{self.start}:{self.end}"

@classmethod
def from_str(cls, s: str) -> Self:
"""Builds a file shard from the string representation.
It is guaranteed that for a shard `shard` and a (valid) string `s` the
following two round-trip properties hold:
```
str(Shard.from_str(s)) == s
Shard.from_str(str(shard)) == shard
```
Raises:
ValueError: if the string argument does not represent a valid shard
serialization (is not in the format `path:start:end`).
"""
parts = s.split(":")
if len(parts) != 3:
raise ValueError(f"Expected 3 components separated by `:`, got {s}")

path = pathlib.PurePosixPath(parts[0])
start = int(parts[1])
end = int(parts[2])

return cls(path, start, end)


@dataclasses.dataclass
class ShardedFileManifestItem(ManifestItem):
Expand Down
37 changes: 37 additions & 0 deletions model_signing/manifest/manifest_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,43 @@ def test_manifest_has_the_correct_resource_descriptors(self):
assert descriptors[1].digest.digest_value == b"hash2"


class TestShard:

def test_round_trip_from_shard(self):
shard = manifest.Shard(pathlib.PurePosixPath("file"), 0, 42)
shard_str = str(shard)
assert manifest.Shard.from_str(shard_str) == shard

def test_round_trip_from_string(self):
shard_str = "file:0:42"
shard = manifest.Shard.from_str(shard_str)
assert str(shard) == shard_str

def test_invalid_shard_str_too_few_components(self):
shard_str = "file"

with pytest.raises(ValueError, match="Expected 3 components"):
manifest.Shard.from_str(shard_str)

def test_invalid_shard_str_too_many_components(self):
shard_str = "file:0:1:2"

with pytest.raises(ValueError, match="Expected 3 components"):
manifest.Shard.from_str(shard_str)

def test_invalid_shard_bad_type_for_start_offset(self):
shard_str = "file:zero:4"

with pytest.raises(ValueError, match="invalid literal for int"):
manifest.Shard.from_str(shard_str)

def test_invalid_shard_bad_type_for_endart_offset(self):
shard_str = "file:0:four"

with pytest.raises(ValueError, match="invalid literal for int"):
manifest.Shard.from_str(shard_str)


class TestShardLevelManifest:

def test_insert_order_does_not_matter(self):
Expand Down
Loading

0 comments on commit e8db349

Please sign in to comment.