Skip to content

Clean up verified time handling #1489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions sigstore/verify/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,6 @@ def _establish_time(self, bundle: Bundle) -> list[TimestampVerificationResult]:
raise VerificationError(msg)

timestamp_from_tsa = self._verify_timestamp_authority(bundle)
if len(timestamp_from_tsa) < VERIFY_TIMESTAMP_THRESHOLD:
msg = (
f"not enough timestamps validated to meet the validation "
f"threshold ({len(timestamp_from_tsa)}/{VERIFY_TIMESTAMP_THRESHOLD})"
)
raise VerificationError(msg)

verified_timestamps.extend(timestamp_from_tsa)

# If a timestamp from the Transparency Service is available, the Verifier MUST
Expand All @@ -232,6 +225,12 @@ def _establish_time(self, bundle: Bundle) -> list[TimestampVerificationResult]:
if (
timestamp := bundle.log_entry.integrated_time
) and bundle.log_entry.inclusion_promise:
kv = bundle.log_entry._kind_version
if not (kv.kind in ["dsse", "hashedrekord"] and kv.version == "0.0.1"):
Comment on lines +228 to +229
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check will not work when backported to 3.x (as log_entry does not have kindversion there) but the check can be removed there -- only 0.0.1 versions are supported anyway

raise VerificationError(
"Integrated time only supported for dsse/hashedrekord 0.0.1 types"
)

verified_timestamps.append(
TimestampVerificationResult(
source=TimestampSource.TRANSPARENCY_SERVICE,
Expand Down Expand Up @@ -320,13 +319,12 @@ def _verify_common_signing_cert(
store.add_cert(parent_cert_ossl)

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

# (1): verify that the signing certificate is signed by the root
Expand Down
42 changes: 34 additions & 8 deletions test/unit/verify/test_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ def verifier(self, asset) -> Verifier:
verifier._trusted_root._inner.timestamp_authorities = [authority._inner]
return verifier

def test_verifier_verify_timestamp(self, verifier, asset, null_policy):
def test_verifier_verify_timestamp(self, verifier, asset, null_policy, monkeypatch):
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
# TSA timestamp are required
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)

verifier.verify_artifact(
asset("tsa/bundle.txt").read_bytes(),
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
Expand Down Expand Up @@ -296,15 +300,21 @@ def test_verifier_duplicate_timestamp(self, verifier, asset, null_policy):
)

def test_verifier_outside_validity_range(
self, caplog, verifier, asset, null_policy
self, caplog, verifier, asset, null_policy, monkeypatch
):
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
# TSA timestamp are required
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)

# Set a date before the timestamp range
verifier._trusted_root.get_timestamp_authorities()[
0
]._inner.valid_for.end = datetime(2024, 10, 31, tzinfo=timezone.utc)

with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
with pytest.raises(VerificationError, match="not enough timestamps"):
with pytest.raises(
VerificationError, match="not enough sources of verified time"
):
verifier.verify_artifact(
asset("tsa/bundle.txt").read_bytes(),
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
Expand All @@ -319,13 +329,19 @@ def test_verifier_outside_validity_range(
def test_verifier_rfc3161_error(
self, verifier, asset, null_policy, caplog, monkeypatch
):
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
# TSA timestamp are required
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)

def verify_function(*args):
raise rfc3161_client.VerificationError()

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

with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"):
with pytest.raises(VerificationError, match="not enough timestamps"):
with pytest.raises(
VerificationError, match="not enough sources of verified time"
):
verifier.verify_artifact(
asset("tsa/bundle.txt").read_bytes(),
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
Expand All @@ -345,15 +361,21 @@ def test_verifier_no_authorities(self, asset, null_policy):
null_policy,
)

def test_late_timestamp(self, caplog, verifier, asset, null_policy):
def test_late_timestamp(self, caplog, verifier, asset, null_policy, monkeypatch):
"""
Ensures that verifying the signing certificate fails because the timestamp
is outside the certificate's validity window. The sample bundle
"tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)`
into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor
but before the timestamp is requested.
"""
with pytest.raises(VerificationError, match="not enough timestamps"):
# asset is a rekor v1 bundle: set threshold to 2 so both integrated time and the
# TSA timestamp are required
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)

with pytest.raises(
VerificationError, match="not enough sources of verified time"
):
verifier.verify_artifact(
asset("tsa/bundle.txt").read_bytes(),
Bundle.from_json(
Expand All @@ -370,8 +392,12 @@ def test_late_timestamp(self, caplog, verifier, asset, null_policy):
def test_verifier_not_enough_timestamp(
self, verifier, asset, null_policy, monkeypatch
):
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 2)
with pytest.raises(VerificationError, match="not enough timestamps"):
# asset is a rekor v1 bundle: set threshold to 3 so integrated time and one
# TSA timestamp are not enough
monkeypatch.setattr("sigstore.verify.verifier.VERIFY_TIMESTAMP_THRESHOLD", 3)
with pytest.raises(
VerificationError, match="not enough sources of verified time"
):
verifier.verify_artifact(
asset("tsa/bundle.txt").read_bytes(),
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
Expand Down
Loading