10000 Add models for TimestampVerificationData (#1186) · sigstore/sigstore-python@0a069f7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0a069f7

Browse files
DarkaMaulfacutuescawoodruffw
authored
Add models for TimestampVerificationData (#1186)
Co-authored-by: Facundo Tuesca <facu@tuesca.com> Co-authored-by: William Woodruff <william@yossarian.net>
1 parent cac62e8 commit 0a069f7

File tree

3 files changed

+131
-1
lines changed

3 files changed

+131
-1
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ dependencies = [
3636
"requests",
3737
"rich ~= 13.0",
3838
"rfc8785 ~= 0.1.2",
39+
# NOTE(dm): Under very active development, so strictly pinned.
40+
"rfc3161-client == 0.0.3",
3941
# NOTE(ww): Both under active development, so strictly pinned.
4042
"sigstore-protobuf-specs == 0.3.2",
4143
"sigstore-rekor-types == 0.0.13",

sigstore/models.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,17 @@
4343
)
4444
from pydantic.dataclasses import dataclass
4545
from rekor_types import Dsse, Hashedrekord, ProposedEntry
46+
from rfc3161_client import TimeStampResponse, decode_timestamp_response
4647
from sigstore_protobuf_specs.dev.sigstore.bundle import v1 as bundle_v1
4748
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
4849
Bundle as _Bundle,
4950
)
51+
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
52+
TimestampVerificationData as _TimestampVerificationData,
53+
)
54+
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import (
55+
VerificationMaterial as _VerificationMaterial,
56+
)
5057
from sigstore_protobuf_specs.dev.sigstore.common import v1 as common_v1
5158
from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1
5259
from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import (
@@ -328,6 +335,64 @@ def _verify(self, keyring: RekorKeyring) -> None:
328335
)
329336

330337

338+
class TimestampVerificationData:
339+
"""
340+
Represents a TimestampVerificationData structure.
341+
342+
@private
343+
"""
344+
345+
def __init__(self, inner: _TimestampVerificationData) -> None:
346+
"""Init method."""
347+
self._inner = inner
348+
self._verify()
349+
350+
def _verify(self) -> None:
351+
"""
352+
Verifies the TimestampVerificationData.
353+
354+
It verifies that TimeStamp Responses embedded in the bundle are correctly
355+
formed.
356+
"""
357+
try:
358+
self._signed_ts = [
359+
decode_timestamp_response(ts.signed_timestamp)
360+
for ts in self._inner.rfc3161_timestamps
361+
]
362+
except ValueError:
363+
raise VerificationError("Invalid Timestamp Response")
364+
365+
@property
366+
def rfc3161_timestamps(self) -> list[TimeStampResponse]:
367+
"""Returns a list of signed timestamp."""
368+
return self._signed_ts
369+
370+
@classmethod
371+
def from_json(cls, raw: str | bytes) -> TimestampVerificationData:
372+
"""
373+
Deserialize the given timestamp verification data.
374+
"""
375+
inner = _TimestampVerificationData().from_json(raw)
376+
return cls(inner)
8000
377+
378+
379+
class VerificationMaterial:
380+
"""
381+
Represents a VerificationMaterial structure.
382+
"""
383+
384+
def __init__(self, inner: _VerificationMaterial) -> None:
385+
"""Init method."""
386+
self._inner = inner
387+
388+
@property
389+
def timestamp_verification_data(self) -> TimestampVerificationData:
390+
"""
391+
Returns the Timestamp Verification Data.
392+
"""
393+
return TimestampVerificationData(self._inner.timestamp_verification_data)
394+
395+
331396
class InvalidBundle(Error):
332397
"""
333398
Raised when the associated `Bundle` is invalid in some way.
@@ -503,6 +568,13 @@ def _dsse_envelope(self) -> dsse.Envelope | None:
503568
return dsse.Envelope(self._inner.dsse_envelope)
504569
return None
505570

571+
@property
572+
def verification_material(self) -> VerificationMaterial:
573+
"""
574+
Returns the bundle's verification material.
575+
"""
576+
return VerificationMaterial(self._inner.verification_material)
577+
506578
@classmethod
507579
def from_json(cls, raw: bytes | str) -> Bundle:
508580
"""

test/unit/test_models.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@
1818
import pytest
1919
from pydantic import ValidationError
2020

21-
from sigstore.models import Bundle, InvalidBundle, LogEntry, LogInclusionProof
21+
from sigstore.errors import VerificationError
22+
from sigstore.models import (
23+
Bundle,
24+
InvalidBundle,
25+
LogEntry,
26+
LogInclusionProof,
27+
TimestampVerificationData,
28+
VerificationMaterial,
29+
)
2230

2331

2432
class TestLogEntry:
@@ -88,6 +96,54 @@ def test_checkpoint_missing(self):
8896
)
8997

9098

99+
class TestTimestampVerificationData:
100+
"""
101+
Tests for the `TimestampVerificationData` wrapper model.
102+
"""
103+
104+
def test_valid_timestamp(self, asset):
105+
timestamp = {
< F438 code>106+
"rfc3161Timestamps": [
107+
{
108+
"signedTimestamp": "MIIEgTADAgEAMIIEeAYJKoZIhvcNAQcCoIIEaTCCBGUCAQMxDTALBglghkgBZQMEAgEwgc8GCyqGSIb3DQEJEAEEoIG/BIG8MIG5AgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgyGobd7rprYIL0JTus5EpEb7jrrecS+cMbb42ftjtm+UCFBV/kwOOwt0tdtYXK1FGhXf7W4oFGA8yMDI0MTAyMjA3MzEwNVowAwIBAQIUTo190a2ixXglxLh7KJcwj6B4kf+gNKQyMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmegggHRMIIBzTCCAXKgAwIBAgIUIYzlmDAtGrQ5jmcZpeAN0Wyj8Q8wCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNDEwMjIwNzIyNTNaFw0zMzEwMjIwNzI1NTNaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQBhKWvDUj1+VFrWudnWIRzAug99WAydJuyF9pxneWppyXbjio3RSoNBvhg+91eeue7GpRQx5ZoxdeiHJD5p7Z0o2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFD7JreyIuE9lHC9k+cFePRXIPdNaMB8GA1UdIwQYMBaAFJMEP2b7r8olhCtvCokuFyTMC0nOMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0kAMEYCIQC69iKNrM4N2/OHksX7zEJM7ImGR+Puq7ALM8l3+riChgIhAKbEWTmifAE6VaQwnL0NNTJskSgk6r8BzvbJtJEZpk6fMYIBqDCCAaQCAQEwSDAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlAhQhjOWYMC0atDmOZxml4A3RbKPxDzALBglghkgBZQMEAgGggfMwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNDEwMjIwNzMxMDVaMC8GCSqGSIb3DQEJBDEiBCBr9fx6gIRsipdGxMDIw1tpvHUv3y10SHUzEM+HHP15+DCBhQYLKoZIhvcNAQkQAi8xdjB0MHIwcAQg2PR1japGgjWt7Cd0jQJrSYlYTblz/UeoJw0LkbqIsSIwTDA0pDIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZQIUIYzlmDAtGrQ5jmcZpeAN0Wyj8Q8wCgYIKoZIzj0EAwIERjBEAiBDfeCcnA1qIlHfMK/u3FZ1HtS9840NnXXaRdMD4R7MywIgZfoBiAMV3SFqO71+eo2kD9oBkW49Pb9eoQs00nOlvn8="
109+
}
110+
]
111+
}
112+
113+
timestamp_verification = TimestampVerificationData.from_json(
114+
json.dumps(timestamp)
115+
)
116+
117+
assert timestamp_verification.rfc3161_timestamps
118+
119+
def test_no_timestamp(self, asset):
120+
timestamp = {"rfc3161Timestamps": []}
121+
timestamp_verification = TimestampVerificationData.from_json(
122+
json.dumps(timestamp)
123+
)
124+
125+
assert not timestamp_verification.rfc3161_timestamps
126+
127+
def test_invalid_timestamp(self, asset):
128+
timestamp = {"rfc3161Timestamps": [{"signedTimestamp": "invalid-entry"}]}
129+
with pytest.raises(VerificationError, match="Invalid Timestamp"):
130+
TimestampVerificationData.from_json(json.dumps(timestamp))
131+
132+
133+
class TestVerificationMaterial:
134+
"""
135+
Tests for the `VerificationMaterial` wrapper model.
136+
"""
137+
138+
def test_valid_verification_material(self, asset):
139+
bundle = Bundle.from_json(asset("bundle.txt.sigstore").read_bytes())
140+
141+
verification_material = VerificationMaterial(
142+
bundle._inner.verification_material
143+
)
144+
assert verification_material
145+
146+
91147
class TestBundle:
92148
"""
93149
Tests for the `Bundle` wrapper model.

0 commit comments

Comments
 (0)
0