Skip to content

Commit 7184eab

Browse files
authored
Clean up verified time handling (#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 5ea398f commit 7184eab

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
@@ -62,9 +62,9 @@
6262
# From https://github.com/sigstore/sigstore-go/blob/e92142f0734064ebf6001f188b7330a1212245fe/pkg/verify/tsa.go#L29
6363
MAX_ALLOWED_TIMESTAMP: int = 32
6464

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

6969

7070
class Verifier:
@@ -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 verified times 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) < VERIFIED_TIME_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.VERIFIED_TIME_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.VERIFIED_TIME_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.VERIFIED_TIME_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.VERIFIED_TIME_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.VERIFIED_TIME_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)