Skip to content

Commit 600c5fe

Browse files
committed
Clean up verified time handling (sigstore#1489)
* Clean verified time handling Try to handle TSA timestamps and rekor v1 integrated time in a sensible manner: * no special cases for when TSA timestamps are present * require one verified time by default * Only allow integrated time to be a verified time if entry is from rekor v1 * VERIFY_TIMESTAMP_THRESHOLD now refers to "number of verified times", not just TSA timestamps * Tests use a rekor v1 bundle but expect it to be invalid if the timestamp is invalid -- but the integrated time is enough. Fix this by monkeypatching VERIFY_TIMESTAMP_THRESHOLD Signed-off-by: Jussi Kukkonen <[email protected]> * verify: Rename VERIFY_TIMESTAMP_THRESHOLD VERIFIED_TIME_THRESHOLD makes more sense since integrated time is also in this threshold. Strictly speaking this is an API change but since the meaning has (slightly) changed already that makes sense. Signed-off-by: Jussi Kukkonen <[email protected]> --------- Signed-off-by: Jussi Kukkonen <[email protected]>
1 parent dd952eb commit 600c5fe

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

sigstore/verify/verifier.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@
5959
# From https://github.com/sigstore/sigstore-go/blob/e92142f0734064ebf6001f188b7330a1212245fe/pkg/verify/tsa.go#L29
6060
MAX_ALLOWED_TIMESTAMP: int = 32
6161

62-
# When verifying a timestamp, this threshold represents the minimum number of required
63-
# timestamps to consider a signature valid.
64-
VERIFY_TIMESTAMP_THRESHOLD: int = 1
62+
# When verifying an entry, this threshold represents the minimum number of required
63+
# verified times to consider a signature valid.
64+
VERIFIED_TIME_THRESHOLD: int = 1
6565

6666

6767
class Verifier:
@@ -226,13 +226,6 @@ def _establish_time(self, bundle: Bundle) -> list[TimestampVerificationResult]:
226226
raise VerificationError(msg)
227227

228228
timestamp_from_tsa = self._verify_timestamp_authority(bundle)
229-
if len(timestamp_from_tsa) < VERIFY_TIMESTAMP_THRESHOLD:
230-
msg = (
231-
f"not enough timestamps validated to meet the validation "
232-
f"threshold ({len(timestamp_from_tsa)}/{VERIFY_TIMESTAMP_THRESHOLD})"
233-
)
234-
raise VerificationError(msg)
235-
236229
verified_timestamps.extend(timestamp_from_tsa)
237230

238231
# If a timestamp from the Transparency Service is available, the Verifier MUST
@@ -243,6 +236,12 @@ def _establish_time(self, bundle: Bundle) -> list[TimestampVerificationResult]:
243236
if (
244237
timestamp := bundle.log_entry.integrated_time
245238
) and bundle.log_entry.inclusion_promise:
239+
kv = bundle.log_entry._kind_version
240+
if not (kv.kind in ["dsse", "hashedrekord"] and kv.version == "0.0.1"):
241+
raise VerificationError(
242+
"Integrated time only supported for dsse/hashedrekord 0.0.1 types"
243+
)
244+
246245
verified_timestamps.append(
247246
TimestampVerificationResult(
248247
source=TimestampSource.TRANSPARENCY_SERVICE,
@@ -331,13 +330,12 @@ def _verify_common_signing_cert(
331330
store.add_cert(parent_cert_ossl)
332331

333332
# (0): Establishing a Time for the Signature
334-
# First, establish a time for the signature. This timestamp is required to
333+
# First, establish verified times for the signature. This is required to
335334
# validate the certificate chain, so this step comes first.
336-
# While this step is optional and only performed if timestamp data has been
337-
# provided within the bundle, providing a signed timestamp without a TSA to
338-
# verify it result in a VerificationError.
335+
# These include TSA timestamps and (in the case of rekor v1 entries)
336+
# rekor log integrated time.
339337
verified_timestamps = self._establish_time(bundle)
340-
if not verified_timestamps:
338+
if len(verified_timestamps) < VERIFIED_TIME_THRESHOLD:
341339
raise VerificationError("not enough sources of verified time")
342340

343341
# (1): verify that the signing certificate is signed by the root

test/unit/verify/test_verifier.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,11 @@ def verifier(self, asset) -> Verifier:
205205
verifier._trusted_root._inner.timestamp_authorities = [authority._inner]
206206
return verifier
207207

208-
def test_verifier_verify_timestamp(self, verifier, asset, null_policy):
208+
def test_verifier_verify_timestamp(self, verifier, asset, null_policy, monkeypatch):
209+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
210+
# TSA timestamp are required
211+
monkeypatch.setattr("sigstore.verify.verifier.VERIFIED_TIME_THRESHOLD", 2)
212+
209213
verifier.verify_artifact(
210214
asset("tsa/bundle.txt").read_bytes(),
211215
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -260,15 +264,21 @@ def test_verifier_no_validity(self, caplog, verifier, asset, null_policy):
260264
)
261265

262266
def test_verifier_outside_validity_range(
263-
self, caplog, verifier, asset, null_policy
267+
self, caplog, verifier, asset, null_policy, monkeypatch
264268
):
269+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
270+
# TSA timestamp are required
271+
monkeypatch.setattr("sigstore.verify.verifier.VERIFIED_TIME_THRESHOLD", 2)
272+
265273
# Set a date before the timestamp range
266274
verifier._trusted_root.get_timestamp_authorities()[
267275
0
268276
]._inner.valid_for.end = datetime(2024, 10, 31, tzinfo=timezone.utc)
269277

270278
with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
271-
with pytest.raises(VerificationError, match="not enough timestamps"):
279+
with pytest.raises(
280+
VerificationError, match="not enough sources of verified time"
281+
):
272282
verifier.verify_artifact(
273283
asset("tsa/bundle.txt").read_bytes(),
274284
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -283,13 +293,19 @@ def test_verifier_outside_validity_range(
283293
def test_verifier_rfc3161_error(
284294
self, verifier, asset, null_policy, caplog, monkeypatch
285295
):
296+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
297+
# TSA timestamp are required
298+
monkeypatch.setattr("sigstore.verify.verifier.VERIFIED_TIME_THRESHOLD", 2)
299+
286300
def verify_function(*args):
287301
raise rfc3161_client.VerificationError()
288302

289303
monkeypatch.setattr(rfc3161_client.verify._Verifier, "verify", verify_function)
290304

291305
with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
292-
with pytest.raises(VerificationError, match="not enough timestamps"):
306+
with pytest.raises(
307+
VerificationError, match="not enough sources of verified time"
308+
):
293309
verifier.verify_artifact(
294310
asset("tsa/bundle.txt").read_bytes(),
295311
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -309,15 +325,21 @@ def test_verifier_no_authorities(self, asset, null_policy):
309325
null_policy,
310326
)
311327

312-
def test_late_timestamp(self, caplog, verifier, asset, null_policy):
328+
def test_late_timestamp(self, caplog, verifier, asset, null_policy, monkeypatch):
313329
"""
314330
Ensures that verifying the signing certificate fails because the timestamp
315331
is outside the certificate's validity window. The sample bundle
316332
"tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)`
317333
into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor
318334
but before the timestamp is requested.
319335
"""
320-
with pytest.raises(VerificationError, match="not enough timestamps"):
336+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
337+
# TSA timestamp are required
338+
monkeypatch.setattr("sigstore.verify.verifier.VERIFIED_TIME_THRESHOLD", 2)
339+
340+
with pytest.raises(
341+
VerificationError, match="not enough sources of verified time"
342+
):
321343
verifier.verify_artifact(
322344
asset("tsa/bundle.txt").read_bytes(),
323345
Bundle.from_json(
@@ -334,8 +356,12 @@ def test_late_timestamp(self, caplog, verifier, asset, null_policy):
334356
def test_verifier_not_enough_timestamp(
335357
self, verifier, asset, null_policy, monkeypatch
336358
):
337-
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
338-
with pytest.raises(VerificationError, match="not enough timestamps"):
359+
# asset is a rekor v1 bundle: set threshold to 3 so integrated time and one
360+
# TSA timestamp are not enough
361+
monkeypatch.setattr("sigstore.verify.verifier.VERIFIED_TIME_THRESHOLD", 3)
362+
with pytest.raises(
363+
VerificationError, match="not enough sources of verified time"
364+
):
339365
verifier.verify_artifact(
340366
asset("tsa/bundle.txt").read_bytes(),
341367
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),

0 commit comments

Comments
 (0)