Skip to content

Commit f638e6d

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 f638e6d

File tree

2 files changed

+44
-20
lines changed

2 files changed

+44
-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: 34 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,21 @@ 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(
316+
VerificationError, match="not enough sources of verified time"
317+
):
308318
verifier.verify_artifact(
309319
asset("tsa/bundle.txt").read_bytes(),
310320
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -319,13 +329,19 @@ def test_verifier_outside_validity_range(
319329
def test_verifier_rfc3161_error(
320330
self, verifier, asset, null_policy, caplog, monkeypatch
321331
):
332+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
333+
# TSA timestamp are required
334+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
335+
322336
def verify_function(*args):
323337
raise rfc3161_client.VerificationError()
324338

325339
monkeypatch.setattr(rfc3161_client.verify._Verifier, "verify", verify_function)
326340

327341
with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
328-
with pytest.raises(VerificationError, match="not enough timestamps"):
342+
with pytest.raises(
343+
VerificationError, match="not enough sources of verified time"
344+
):
329345
verifier.verify_artifact(
330346
asset("tsa/bundle.txt").read_bytes(),
331347
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
@@ -345,15 +361,21 @@ def test_verifier_no_authorities(self, asset, null_policy):
345361
null_policy,
346362
)
347363

348-
def test_late_timestamp(self, caplog, verifier, asset, null_policy):
364+
def test_late_timestamp(self, caplog, verifier, asset, null_policy, monkeypatch):
349365
"""
350366
Ensures that verifying the signing certificate fails because the timestamp
351367
is outside the certificate's validity window. The sample bundle
352368
"tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)`
353369
into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor
354370
but before the timestamp is requested.
355371
"""
356-
with pytest.raises(VerificationError, match="not enough timestamps"):
372+
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
373+
# TSA timestamp are required
374+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
375+
376+
with pytest.raises(
377+
VerificationError, match="not enough sources of verified time"
378+
):
357379
verifier.verify_artifact(
358380
asset("tsa/bundle.txt").read_bytes(),
359381
Bundle.from_json(
@@ -370,8 +392,12 @@ def test_late_timestamp(self, caplog, verifier, asset, null_policy):
370392
def test_verifier_not_enough_timestamp(
371393
self, verifier, asset, null_policy, monkeypatch
372394
):
373-
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
374-
with pytest.raises(VerificationError, match="not enough timestamps"):
395+
# asset is a rekor v1 bundle: set threshold to 3 so integrated time and one
396+
# TSA timestamp are not enough
397+
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 3)
398+
with pytest.raises(
399+
VerificationError, match="not enough sources of verified time"
400+
):
375401
verifier.verify_artifact(
376402
asset("tsa/bundle.txt").read_bytes(),
377403
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),

0 commit comments

Comments
 (0)