Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add models for TimestampVerificationData #1186

Merged
merged 13 commits into from
Nov 6, 2024
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 stricly pinned.
DarkaMaul marked this conversation as resolved.
Show resolved Hide resolved
"rfc3161-client == 0.0.2",
# NOTE(ww): Both under active development, so strictly pinned.
"sigstore-protobuf-specs == 0.3.2",
"sigstore-rekor-types == 0.0.13",
Expand Down
73 changes: 73 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,65 @@ def _verify(self, keyring: RekorKeyring) -> None:
)


class TimestampVerificationData:
"""
Represents a TimestampVerificationData structure.
DarkaMaul marked this conversation as resolved.
Show resolved Hide resolved
"""

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.
"""
signed_ts: list[TimeStampResponse] = []
for rfc3161_signed_ts in self._inner.rfc3161_timestamps:
try:
signed_ts.append(
decode_timestamp_response(rfc3161_signed_ts.signed_timestamp)
)
except ValueError:
raise VerificationError("Invalid Timestamp Response")

self._signed_ts = signed_ts
DarkaMaul marked this conversation as resolved.
Show resolved Hide resolved

@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:
"""
Only used in tests.
"""
DarkaMaul marked this conversation as resolved.
Show resolved Hide resolved
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 +569,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
Loading