Skip to content

Commit 300b502

Browse files
authored
fix: require an inclusion promise when log integration time is used (#1247)
1 parent 9ee7ac2 commit 300b502

File tree

8 files changed

+150
-4
lines changed

8 files changed

+150
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ All versions prior to 0.9.0 are untracked.
3636
verifying legacy bundles was never shown
3737
([#1198](https://github.com/sigstore/sigstore-python/pull/1198))
3838

39+
* Strengthened the requirement that an inclusion promise is present
40+
*if* no other source of signed time is present
41+
([#1247](https://github.com/sigstore/sigstore-python/pull/1247))
42+
3943
## [3.5.3]
4044

4145
### Fixed

sigstore/models.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,11 @@ def _verify(self) -> None:
525525
# * For 0.2+, an inclusion proof is required; the client MUST
526526
# verify the inclusion proof. The inclusion prof MUST contain
527527
# a checkpoint.
528-
# The inclusion promise is NOT required; if present, the client
529-
# SHOULD verify it.
528+
#
529+
# The inclusion promise is NOT required if another source of signed
530+
# time (such as a signed timestamp) is present. If no other source
531+
# of signed time is present, then the inclusion promise MUST be
532+
# present.
530533
#
531534
# Before all of this, we require that the inclusion proof be present
532535
# (when constructing the LogEntry).
@@ -543,6 +546,14 @@ def _verify(self) -> None:
543546
if not log_entry.inclusion_proof.checkpoint:
544547
raise InvalidBundle("expected checkpoint in inclusion proof")
545548

549+
if (
550+
not log_entry.inclusion_promise
551+
and not self._inner.verification_material.timestamp_verification_data.rfc3161_timestamps
552+
):
553+
raise InvalidBundle(
554+
"bundle must contain an inclusion promise or signed timestamp(s)"
555+
)
556+
546557
self._log_entry = log_entry
547558

548559
@property

sigstore/verify/verifier.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,12 @@ def _establish_time(self, bundle: Bundle) -> List[TimestampVerificationResult]:
227227

228228
# If a timestamp from the Transparency Service is available, the Verifier MUST
229229
# perform path validation using the timestamp from the Transparency Service.
230-
if timestamp := bundle.log_entry.integrated_time:
230+
# NOTE: We only include this timestamp if it's accompanied by an inclusion
231+
# promise that cryptographically binds it. We verify the inclusion promise
232+
# itself later, as part of log entry verification.
233+
if (
234+
timestamp := bundle.log_entry.integrated_time
235+
) and bundle.log_entry.inclusion_promise:
231236
verified_timestamps.append(
232237
TimestampVerificationResult(
233238
source=TimestampSource.TRANSPARENCY_SERVICE,

test/assets/bundle_v3_github.whl.sigstore

Lines changed: 62 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DO NOT MODIFY ME!
2+
3+
this is the input for bundle_v3_no_signed_time, which ensures clients reject
4+
bundles that don't have a source of signed time.
5+
6+
DO NOT MODIFY ME!
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
3+
"verificationMaterial": {
4+
"certificate": {
5+
"rawBytes": "MIIC1DCCAlugAwIBAgIUXgKINnY7rbT5gHmj9yeiZXGg3rkwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQxMjEwMjE0MTI1WhcNMjQxMjEwMjE1MTI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4ul4I08UFGizCla6qRUGFiwEPNsFRnvBPDvQ4ViJ+Q83HOlYWWxCAjoJpGd9FWtyxTPKDsG0n4t6Mr+jSwz22KOCAXowggF2MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUZ7cNLqQlnKAXnf6jmb9cv70dppgwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wIwYDVR0RAQH/BBkwF4EVd2lsbGlhbUB5b3NzYXJpYW4ubmV0MCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABk7KFEeIAAAQDAEgwRgIhAOeS6rR2aksHhN9Rxbx+ANuAlXhP4vTPKMLBHd6JAm4lAiEAx+/kzKJ2SxSCAYm582jKeAa1LCVmUaO85FO2WTV7MYEwCgYIKoZIzj0EAwMDZwAwZAIwDXrVAPgutWZWPfE3QWy/4gG/PbMbYUfqNsEpQEeMm8GeraZN3zffzw16FFhWsMbXAjApxDNgKvmztHOKStyvmOXPiJCixzx/gLFbhVn7Q+qY6vjC83B0XgPsyQ2T0i8Ldzg="
6+
},
7+
"tlogEntries": [
8+
{
9+
"logIndex": "154562758",
10+
"logId": {
11+
"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
12+
},
13+
"kindVersion": {
14+
"kind": "hashedrekord",
15+
"version": "0.0.1"
16+
},
17+
"integratedTime": "1733866885",
18+
"inclusionProof": {
19+
"logIndex": "32658496",
20+
"rootHash": "IbC2+n9aYhFlm5nFwkp+j7/Hc9XuYWxyE5OlXIoIijY=",
21+
"treeSize": "32658497",
22+
"hashes": [
23+
"CVvwGSdkZ5FUDnltf3Me3nXyco4G9mwTsYbIxz0RS+U=",
24+
"DJrEpKAKhEPhZ5aKvlaRImFebTv5tc17rsfOkhSS6fY=",
25+
"tsYfO+hUsl4KKY+qsPx/k4NzOzE5zWRsc4Ufgn4oh/U=",
26+
"ZjSpDQt5kIQfJd6B/BDNWLRhYOGwnlxE6pT4JJaiD5s=",
27+
"OMoiMVnwD3sG6Cc6HCg+ySmqBAH1nn0mA5+tjFxiyeg=",
28+
"gSWKL2k1ZGZm45C8hSdNwWan8qOrszl5X7Ws56h+FVM=",
29+
"R7hO1X+KgSw8Oojd8i2+G3BzBYztkRBE6LpYSXPg33U=",
30+
"oOecFfN3YqDOkbijS/ej1WF5Da/Gt/AZNhbwE9uoOE8=",
31+
"4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=",
32+
"gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw="
33+
],
34+
"checkpoint": {
35+
"envelope": "rekor.sigstore.dev - 1193050959916656506\n32658497\nIbC2+n9aYhFlm5nFwkp+j7/Hc9XuYWxyE5OlXIoIijY=\n\n\u2014 rekor.sigstore.dev wNI9ajBGAiEAgjFaCZlVvHUnDgxLf+4XjN6ahWNkkKh9QFTOqHBpyw4CIQDmy4JQs+2BKtvheo/HQogyhh5EYGYZeBDdRvyyX1fg+w==\n"
36+
}
37+
},
38+
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjYTZkZTk5YTExZDNkMzgwNTZkODM4YzdkYzlhMjNhMTFhMGM4MWJjYWNlMGQxMWVhYTMwMWEyZmZiNDgyYzQyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUMzc2pYZVZoTHRqbE13dG0yRE5CYVdVaFBWOVJ1U1dsWW1EcHQzRzFQVW5RSWhBUElxRHUwTVkza1FtelE2QmswS2VSTW5mQ3Y0VVdEVU5jclRnN0cyYjdzTCIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhSRU5EUVd4MVowRjNTVUpCWjBsVldHZExTVTV1V1RkeVlsUTFaMGh0YWpsNVpXbGFXRWRuTTNKcmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJlRTFxUlhkTmFrVXdUVlJKTVZkb1kwNU5hbEY0VFdwRmQwMXFSVEZOVkVreFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVUwZFd3MFNUQTRWVVpIYVhwRGJHRTJjVkpWUjBacGQwVlFUbk5HVW01MlFsQkVkbEVLTkZacFNpdFJPRE5JVDJ4WlYxZDRRMEZxYjBwd1IyUTVSbGQwZVhoVVVFdEVjMGN3YmpSME5rMXlLMnBUZDNveU1rdFBRMEZZYjNkblowWXlUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZhTjJOT0NreHhVV3h1UzBGWWJtWTJhbTFpT1dOMk56QmtjSEJuZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBsM1dVUldVakJTUVZGSUwwSkNhM2RHTkVWV1pESnNjMkpIYkdoaVZVSTFZak5PZWxsWVNuQlpWelIxWW0xV01FMURkMGREYVhOSFFWRlJRZ3BuTnpoM1FWRkZSVWh0YURCa1NFSjZUMms0ZGxveWJEQmhTRlpwVEcxT2RtSlRPWE5pTW1Sd1ltazVkbGxZVmpCaFJFRjFRbWR2Y2tKblJVVkJXVTh2Q2sxQlJVbENRMEZOU0cxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YzJJeVpIQmlhVGwyV1ZoV01HRkVRMEpwZDFsTFMzZFpRa0pCU0ZjS1pWRkpSVUZuVWpsQ1NITkJaVkZDTTBGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFncHJOMHRHUldWSlFVRkJVVVJCUldkM1VtZEphRUZQWlZNMmNsSXlZV3R6U0doT09WSjRZbmdyUVU1MVFXeFlhRkEwZGxSUVMwMU1Ra2hrTmtwQmJUUnNDa0ZwUlVGNEt5OXJla3RLTWxONFUwTkJXVzAxT0RKcVMyVkJZVEZNUTFadFZXRlBPRFZHVHpKWFZGWTNUVmxGZDBObldVbExiMXBKZW1vd1JVRjNUVVFLV25kQmQxcEJTWGRFV0hKV1FWQm5kWFJYV2xkUVprVXpVVmQ1THpSblJ5OVFZazFpV1ZWbWNVNXpSWEJSUldWTmJUaEhaWEpoV2s0emVtWm1lbmN4TmdwR1JtaFhjMDFpV0VGcVFYQjRSRTVuUzNadGVuUklUMHRUZEhsMmJVOVlVR2xLUTJsNGVuZ3ZaMHhHWW1oV2JqZFJLM0ZaTm5acVF6Z3pRakJZWjFCekNubFJNbFF3YVRoTVpIcG5QUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0="
39+
}
40+
],
41+
"timestampVerificationData": {}
42+
},
43+
"messageSignature": {
44+
"messageDigest": {
45+
"algorithm": "SHA2_256",
46+
"digest": "ym3pmhHT04BW2DjH3JojoRoMgbys4NEeqjAaL/tILEI="
47+
},
48+
"signature": "MEYCIQC3sjXeVhLtjlMwtm2DNBaWUhPV9RuSWlYmDpt3G1PUnQIhAPIqDu0MY3kQmzQ6Bk0KeRMnfCv4UWDUNcrTg7G2b7sL"
49+
}
50+
}

test/unit/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ def signing_bundle(asset):
117117
def _signing_bundle(name: str) -> tuple[Path, Bundle]:
118118
file = asset(name)
119119
bundle_path = asset(f"{name}.sigstore")
120+
if not bundle_path.is_file():
121+
bundle_path = asset(f"{name}.sigstore.json")
120122
bundle = Bundle.from_json(bundle_path.read_bytes())
121123

122124
return (file, bundle)

test/unit/test_models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ def test_bundle_roundtrip(self, signing_bundle):
180180
bundle.to_json()
181181
)
182182

183+
def test_bundle_missing_signed_time(self, signing_bundle):
184+
with pytest.raises(
185+
InvalidBundle,
186+
match=r"bundle must contain an inclusion promise or signed timestamp\(s\)",
187+
):
188+
signing_bundle("bundle_v3_no_signed_time.txt")
189+
183190

184191
class TestKnownBundleTypes:
185192
def test_str(self):

0 commit comments

Comments
 (0)