Skip to content

Commit

Permalink
Add models for TimestampVerificationData (#1186)
Browse files Browse the repository at this point in the history
Co-authored-by: Facundo Tuesca <[email protected]>
Co-authored-by: William Woodruff <[email protected]>
  • Loading branch information
3 people authored Nov 6, 2024
1 parent cac62e8 commit 0a069f7
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 1 deletion.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ dependencies = [
"requests",
"rich ~= 13.0",
"rfc8785 ~= 0.1.2",
# NOTE(dm): Under very active development, so strictly pinned.
"rfc3161-client == 0.0.3",
# NOTE(ww): Both under active development, so strictly pinned.
"sigstore-protobuf-specs == 0.3.2",
"sigstore-rekor-types == 0.0.13",
Expand Down
72 changes: 72 additions & 0 deletions sigstore/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@
)
from pydantic.dataclasses import dataclass
from rekor_types import Dsse, Hashedrekord, ProposedEntry
from rfc3161_client import TimeStampResponse, decode_timestamp_response
from sigstore_protobuf_specs.dev.sigstore.bundle import v1 as bundle_v1
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
Bundle as _Bundle,
)
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
TimestampVerificationData as _TimestampVerificationData,
)
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
VerificationMaterial as _VerificationMaterial,
)
from sigstore_protobuf_specs.dev.sigstore.common import v1 as common_v1
from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1
from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import (
Expand Down Expand Up @@ -328,6 +335,64 @@ def _verify(self, keyring: RekorKeyring) -> None:
)


class TimestampVerificationData:
"""
Represents a TimestampVerificationData structure.
@private
"""

def __init__(self, inner: _TimestampVerificationData) -> None:
"""Init method."""
self._inner = inner
self._verify()

def _verify(self) -> None:
"""
Verifies the TimestampVerificationData.
It verifies that TimeStamp Responses embedded in the bundle are correctly
formed.
"""
try:
self._signed_ts = [
decode_timestamp_response(ts.signed_timestamp)
for ts in self._inner.rfc3161_timestamps
]
except ValueError:
raise VerificationError("Invalid Timestamp Response")

@property
def rfc3161_timestamps(self) -> list[TimeStampResponse]:
"""Returns a list of signed timestamp."""
return self._signed_ts

@classmethod
def from_json(cls, raw: str | bytes) -> TimestampVerificationData:
"""
Deserialize the given timestamp verification data.
"""
inner = _TimestampVerificationData().from_json(raw)
return cls(inner)


class VerificationMaterial:
"""
Represents a VerificationMaterial structure.
"""

def __init__(self, inner: _VerificationMaterial) -> None:
"""Init method."""
self._inner = inner

@property
def timestamp_verification_data(self) -> TimestampVerificationData:
"""
Returns the Timestamp Verification Data.
"""
return TimestampVerificationData(self._inner.timestamp_verification_data)


class InvalidBundle(Error):
"""
Raised when the associated `Bundle` is invalid in some way.
Expand Down Expand Up @@ -503,6 +568,13 @@ def _dsse_envelope(self) -> dsse.Envelope | None:
return dsse.Envelope(self._inner.dsse_envelope)
return None

@property
def verification_material(self) -> VerificationMaterial:
"""
Returns the bundle's verification material.
"""
return VerificationMaterial(self._inner.verification_material)

@classmethod
def from_json(cls, raw: bytes | str) -> Bundle:
"""
Expand Down
58 changes: 57 additions & 1 deletion test/unit/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@
import pytest
from pydantic import ValidationError

from sigstore.models import Bundle, InvalidBundle, LogEntry, LogInclusionProof
from sigstore.errors import VerificationError
from sigstore.models import (
Bundle,
InvalidBundle,
LogEntry,
LogInclusionProof,
TimestampVerificationData,
VerificationMaterial,
)


class TestLogEntry:
Expand Down Expand Up @@ -88,6 +96,54 @@ def test_checkpoint_missing(self):
)


class TestTimestampVerificationData:
"""
Tests for the `TimestampVerificationData` wrapper model.
"""

def test_valid_timestamp(self, asset):
timestamp = {
"rfc3161Timestamps": [
{
"signedTimestamp": "MIIEgTADAgEAMIIEeAYJKoZIhvcNAQcCoIIEaTCCBGUCAQMxDTALBglghkgBZQMEAgEwgc8GCyqGSIb3DQEJEAEEoIG/BIG8MIG5AgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgyGobd7rprYIL0JTus5EpEb7jrrecS+cMbb42ftjtm+UCFBV/kwOOwt0tdtYXK1FGhXf7W4oFGA8yMDI0MTAyMjA3MzEwNVowAwIBAQIUTo190a2ixXglxLh7KJcwj6B4kf+gNKQyMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmegggHRMIIBzTCCAXKgAwIBAgIUIYzlmDAtGrQ5jmcZpeAN0Wyj8Q8wCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNDEwMjIwNzIyNTNaFw0zMzEwMjIwNzI1NTNaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQBhKWvDUj1+VFrWudnWIRzAug99WAydJuyF9pxneWppyXbjio3RSoNBvhg+91eeue7GpRQx5ZoxdeiHJD5p7Z0o2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFD7JreyIuE9lHC9k+cFePRXIPdNaMB8GA1UdIwQYMBaAFJMEP2b7r8olhCtvCokuFyTMC0nOMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0kAMEYCIQC69iKNrM4N2/OHksX7zEJM7ImGR+Puq7ALM8l3+riChgIhAKbEWTmifAE6VaQwnL0NNTJskSgk6r8BzvbJtJEZpk6fMYIBqDCCAaQCAQEwSDAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlAhQhjOWYMC0atDmOZxml4A3RbKPxDzALBglghkgBZQMEAgGggfMwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNDEwMjIwNzMxMDVaMC8GCSqGSIb3DQEJBDEiBCBr9fx6gIRsipdGxMDIw1tpvHUv3y10SHUzEM+HHP15+DCBhQYLKoZIhvcNAQkQAi8xdjB0MHIwcAQg2PR1japGgjWt7Cd0jQJrSYlYTblz/UeoJw0LkbqIsSIwTDA0pDIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZQIUIYzlmDAtGrQ5jmcZpeAN0Wyj8Q8wCgYIKoZIzj0EAwIERjBEAiBDfeCcnA1qIlHfMK/u3FZ1HtS9840NnXXaRdMD4R7MywIgZfoBiAMV3SFqO71+eo2kD9oBkW49Pb9eoQs00nOlvn8="
}
]
}

timestamp_verification = TimestampVerificationData.from_json(
json.dumps(timestamp)
)

assert timestamp_verification.rfc3161_timestamps

def test_no_timestamp(self, asset):
timestamp = {"rfc3161Timestamps": []}
timestamp_verification = TimestampVerificationData.from_json(
json.dumps(timestamp)
)

assert not timestamp_verification.rfc3161_timestamps

def test_invalid_timestamp(self, asset):
timestamp = {"rfc3161Timestamps": [{"signedTimestamp": "invalid-entry"}]}
with pytest.raises(VerificationError, match="Invalid Timestamp"):
TimestampVerificationData.from_json(json.dumps(timestamp))


class TestVerificationMaterial:
"""
Tests for the `VerificationMaterial` wrapper model.
"""

def test_valid_verification_material(self, asset):
bundle = Bundle.from_json(asset("bundle.txt.sigstore").read_bytes())

verification_material = VerificationMaterial(
bundle._inner.verification_material
)
assert verification_material


class TestBundle:
"""
Tests for the `Bundle` wrapper model.
Expand Down

0 comments on commit 0a069f7

Please sign in to comment.