Skip to content

Commit 60b5906

Browse files
committed
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]>
1 parent afa4611 commit 60b5906

File tree

2 files changed

+36
-20
lines changed

2 files changed

+36
-20
lines changed

sigstore/verify/verifier.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,6 @@ def _establish_time(self, bundle: Bundle) -> list[TimestampVerificationResult]:
215215
raise VerificationError(msg)
216216

217217
timestamp_from_tsa = self._verify_timestamp_authority(bundle)
218-
if len(timestamp_from_tsa) < VERIFY_TIMESTAMP_THRESHOLD:
219-
msg = (
220-
f"not enough timestamps validated to meet the validation "
221-
f"threshold ({len(timestamp_from_tsa)}/{VERIFY_TIMESTAMP_THRESHOLD})"
222-
)
223-
raise VerificationError(msg)
224-
225218
verified_timestamps.extend(timestamp_from_tsa)
226219

227220
# If a timestamp from the Transparency Service is available, the Verifier MUST
@@ -232,6 +225,12 @@ def _establish_time(self, bundle: Bundle) -> list[TimestampVerificationResult]:
232225
if (
233226
timestamp := bundle.log_entry.integrated_time
234227
) and bundle.log_entry.inclusion_promise:
228+
kv = bundle.log_entry._kind_version
229+
if not (kv.kind in ["dsse", "hashedrekord"] and kv.version == "0.0.1"):
230+
raise VerificationError(
231+
"Integrated time only supported for dsse/hashedrekord 0.0.1 types"
232+
)
233+
235234
verified_timestamps.append(
236235
TimestampVerificationResult(
237236
source=TimestampSource.TRANSPARENCY_SERVICE,
@@ -320,13 +319,12 @@ def _verify_common_signing_cert(
320319
store.add_cert(parent_cert_ossl)
321320

322321
# (0): Establishing a Time for the Signature
323-
# First, establish a time for the signature. This timestamp is required to
322+
# First, establish a time for the signature. This is required to
324323
# validate the certificate chain, so this step comes first.
325-
# While this step is optional and only performed if timestamp data has been
326-
# provided within the bundle, providing a signed timestamp without a TSA to
327-
# verify it result in a VerificationError.
324+
# These include TSA timestamps and (in the case of rekor v1 entries)
325+
# rekor log integrated time.
328326
verified_timestamps = self._establish_time(bundle)
329-
if not verified_timestamps:
327+
if len(verified_timestamps) < VERIFY_TIMESTAMP_THRESHOLD:
330328
raise VerificationError("not enough sources of verified time")
331329

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

test/unit/verify/test_verifier.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,11 @@ def verifier(self, asset) -> Verifier:
219219
verifier._trusted_root._inner.timestamp_authorities = [authority._inner]
220220
return verifier
221221

222-
def test_verifier_verify_timestamp(self, verifier, asset, null_policy):
222+
def test_verifier_verify_timestamp(self, verifier, asset, null_policy, monkeypatch):
223+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
224+
# TSA timestamp are required
225+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
226+
223227
verifier.verify_artifact(
224228
asset("tsa/bundle.txt").read_bytes(),
225229
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -296,15 +300,19 @@ def test_verifier_duplicate_timestamp(self, verifier, asset, null_policy):
296300
)
297301

298302
def test_verifier_outside_validity_range(
299-
self, caplog, verifier, asset, null_policy
303+
self, caplog, verifier, asset, null_policy, monkeypatch
300304
):
305+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
306+
# TSA timestamp are required
307+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
308+
301309
# Set a date before the timestamp range
302310
verifier._trusted_root.get_timestamp_authorities()[
303311
0
304312
]._inner.valid_for.end = datetime(2024, 10, 31, tzinfo=timezone.utc)
305313

306314
with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
307-
with pytest.raises(VerificationError, match="not enough timestamps"):
315+
with pytest.raises(VerificationError, match="not enough sources of verified time"):
308316
verifier.verify_artifact(
309317
asset("tsa/bundle.txt").read_bytes(),
310318
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -319,13 +327,17 @@ def test_verifier_outside_validity_range(
319327
def test_verifier_rfc3161_error(
320328
self, verifier, asset, null_policy, caplog, monkeypatch
321329
):
330+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
331+
# TSA timestamp are required
332+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
333+
322334
def verify_function(*args):
323335
raise rfc3161_client.VerificationError()
324336

325337
monkeypatch.setattr(rfc3161_client.verify._Verifier, "verify", verify_function)
326338

327339
with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
328-
with pytest.raises(VerificationError, match="not enough timestamps"):
340+
with pytest.raises(VerificationError, match="not enough sources of verified time"):
329341
verifier.verify_artifact(
330342
asset("tsa/bundle.txt").read_bytes(),
331343
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -345,15 +357,19 @@ def test_verifier_no_authorities(self, asset, null_policy):
345357
null_policy,
346358
)
347359

348-
def test_late_timestamp(self, caplog, verifier, asset, null_policy):
360+
def test_late_timestamp(self, caplog, verifier, asset, null_policy, monkeypatch):
349361
"""
350362
Ensures that verifying the signing certificate fails because the timestamp
351363
is outside the certificate's validity window. The sample bundle
352364
"tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)`
353365
into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor
354366
but before the timestamp is requested.
355367
"""
356-
with pytest.raises(VerificationError, match="not enough timestamps"):
368+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
369+
# TSA timestamp are required
370+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
371+
372+
with pytest.raises(VerificationError, match="not enough sources of verified time"):
357373
verifier.verify_artifact(
358374
asset("tsa/bundle.txt").read_bytes(),
359375
Bundle.from_json(
@@ -370,8 +386,10 @@ def test_late_timestamp(self, caplog, verifier, asset, null_policy):
370386
def test_verifier_not_enough_timestamp(
371387
self, verifier, asset, null_policy, monkeypatch
372388
):
373-
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
374-
with pytest.raises(VerificationError, match="not enough timestamps"):
389+
# asset is a rekor v1 bundle: set threshold to 3 so integrated time and one
390+
# TSA timestamp are not enough
391+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 3)
392+
with pytest.raises(VerificationError, match="not enough sources of verified time"):
375393
verifier.verify_artifact(
376394
asset("tsa/bundle.txt").read_bytes(),
377395
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),

0 commit comments

Comments
 (0)