Skip to content

Commit a193d6c

Browse files
Merge branch 'main' into optional-inclusion-promise
2 parents 9a168a5 + 30a74ed commit a193d6c

File tree

5 files changed

+73
-13
lines changed

5 files changed

+73
-13
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ All versions prior to 0.9.0 are untracked.
2727
* API: Make Rekor APIs compatible with Rekor v2 by removing trailing slashes
2828
from endpoints ([#1366](https://github.com/sigstore/sigstore-python/pull/1366))
2929

30+
* Verify: verify that all established times (timestamps or the log integration time)
31+
are within the signing certificate validity period. At least one established time is
32+
still required.
33+
[#1381](https://github.com/sigstore/sigstore-python/pull/1381)
34+
3035
* CI: Timestamp Authority tests use latest release, not latest tag, of
3136
[sigstore/timestamp-authority](https://github.com/sigstore/timestamp-authority)
3237
[#1377](https://github.com/sigstore/sigstore-python/pull/1377)

sigstore/verify/verifier.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -372,17 +372,17 @@ def _verify_common_signing_cert(
372372
except VerificationError as exc:
373373
raise VerificationError(f"invalid log entry: {exc}")
374374

375-
# (6): verify that log entry was integrated circa the signing certificate's
376-
# validity period.
377-
integrated_time = datetime.fromtimestamp(entry.integrated_time, tz=timezone.utc)
378-
if not (
379-
bundle.signing_certificate.not_valid_before_utc
380-
<= integrated_time
381-
<= bundle.signing_certificate.not_valid_after_utc
382-
):
383-
raise VerificationError(
384-
"invalid signing cert: expired at time of Rekor entry"
385-
)
375+
# (6): verify our established times (timestamps or the log integration time) are
376+
# within signing certificate validity period.
377+
for vts in verified_timestamps:
378+
if not (
379+
bundle.signing_certificate.not_valid_before_utc
380+
<= vts.time
381+
<= bundle.signing_certificate.not_valid_after_utc
382+
):
383+
raise VerificationError(
384+
f"invalid signing cert: expired at time of signing, time via {vts}"
385+
)
386386

387387
def verify_dsse(
388388
self, bundle: Bundle, policy: VerificationPolicy
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDBTCCAougAwIBAgIUIs3M2DgogCj3KotUVZg8Mok6IhMwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNTEyMTg0ODI2WhcNMjUwNTEyMTg1ODI2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEn2elp6N4BmBpOaQbpbiYY5EBXJq5+f0tPnffeJTbLVzPgUbpX4T5ZS7KDuQFQSPrljgIZAO3+ZmFSFFnwVrNv6OCAaowggGmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+j0g8S3mHrEo3eautm7T4RnwWwUwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlsXTr40AAAQDAEgwRgIhAN790LnuqDZwfqzyilH4qtk7zVvVZUQXB0Q0YfX9tNWXAiEAzH649BUx15UYsZUGihsBfNUQXov87UYzfYE2Zw2L174wCgYIKoZIzj0EAwMDaAAwZQIwCJ8+cVdfOc5SPoQnjY6rrIxIlYqLgtW65YrX8GzbRW4NpP37m6nxi6cjqtgwGFMeAjEAp4JgaETMFRgSBSSZLB7uhqr1fY97LPcHmAebKFpqFQERELMUmmqk5uHB2wgtvzB2"}, "tlogEntries": [{"logIndex": "42066373", "logId": {"keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1747075707", "inclusionPromise": {"signedEntryTimestamp": "MEUCIDxpagNcBytw52lZI3CwbTA6lfydnHlIGogI1Jfu13PKAiEAri9BAnNJDCZV3gEj9MuLEPw6jVbAiKfmrUmoaAIqSsc="}, "inclusionProof": {"logIndex": "10383961", "rootHash": "K6ZWztp1qbjIuzexDwMUOhf/+S+wqz4iQEDFTcEnNGQ=", "treeSize": "10383962", "hashes": ["HjQ6YJcBoxxKm4Uxs0zJCqC/LM/phnZMGiOiDLXo5wg=", "HSzuxscITh6g9k7vt64/9Z8zPwGwcQJv7NfnX92ULng=", "EEVPMqL5AIgHaYl2NbjmSTvn31oGEjhpTPbpgowrPM4=", "WbnH9wLRq4lD3Ju3FWOBZ+PEfvXT2c0Ugqy78gFgR0M=", "Kv0MBtfoWuGMfuJhPQiwSV7qUt+ALTQMx9BWYUrusb0=", "vld+lIPewmCjCp7W2cwUZD59sPgCK0rC0T2GpveXsmM=", "//dvSvxZzO+sVgkN0WfDdWVO4VGUsVGNT6bSmn5b/Qg=", "AzkFO8X9eKMNJxy+AuVjKe/2ObuNc4pGFzYucDuH87I=", "BVBT6KGWJPrAI4T7Zzt529+ZxU+G5UR0UMqPDcKUYzk=", "1CmXahercpSNPyH2ATDpK8S80Gim/GrKkm/8V5Ozue0=", "ZyAV6AeFLhv6n2Ya599XWHwy3HCr/y0+RF0P6Smg8IU=", "GoHRwlhYuJIYJdmRnHX5HWLr2ngxzHnAIIqBewovBi0=", "OdoqbUqBYHhj2W1RLM8APkQOnM2K9gzGm1KPFmwIIeQ="], "checkpoint": {"envelope": "rekor.sigstage.dev - 8202293616175992157\n10383962\nK6ZWztp1qbjIuzexDwMUOhf/+S+wqz4iQEDFTcEnNGQ=\n\n\u2014 rekor.sigstage.dev 0y8wozBFAiEA+12tjmkJ2CeZlW4baTsLtnVfdSeWNyW8ZFykmBcAn4QCIB6OZTD/bVgAsuq5FgSQZzwn0RPYl7+S1IFRYAoHIP5G\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4MDJkZDYwZmY4ODMzMzgwMmYyNTg1ZTczMDQzYmQyMWMzNDEyODVlMTk5MmZlNWIzMTc1NWUxY2FkZWFlMzBlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRmhsZU1oMjlRRW5QbVc3Mjhkd1h1ZkdGTVo0NG8zNkNseGVxRWVWaUxSdEFpQjIzUkRHenArbjF3aDVjVTF0cC9CampIc3RBQjdsWmY5S0tKbnpwM3ViV0E9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUkNWRU5EUVc5MVowRjNTVUpCWjBsVlNYTXpUVEpFWjI5blEyb3pTMjkwVlZaYVp6aE5iMnMyU1doTmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFZkMDVVUlhsTlZHY3dUMFJKTWxkb1kwNU5hbFYzVGxSRmVVMVVaekZQUkVreVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ1TW1Wc2NEWk9ORUp0UW5CUFlWRmljR0pwV1ZrMVJVSllTbkUxSzJZd2RGQnVabVlLWlVwVVlreFdlbEJuVldKd1dEUlVOVnBUTjB0RWRWRkdVVk5RY214cVowbGFRVTh6SzFwdFJsTkdSbTUzVm5KT2RqWlBRMEZoYjNkblowZHRUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlVyYWpCbkNqaFRNMjFJY2tWdk0yVmhkWFJ0TjFRMFVtNTNWM2RWZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFkUldVUldVakJTUVZGSUwwSkZPSGRVV1VaTVlWYzFlbHBYVGpGamJWVjBXVEo0ZG1SWFVqQmlNMEYwWXpKb2FHTnRWbXRNV0ZaNldsaEtRUXBaTW5oMlpGZFNNR0l6UVhSalNFcDJXa014TVdONU1XeFpXRTR3VEcxc2FHSlROVzVqTWxaNVpHMXNhbHBYUm1wWk1qa3hZbTVSZFZreU9YUk5RMnRIQ2tOcGMwZEJVVkZDWnpjNGQwRlJSVVZITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVGeVFtZHZja0puUlVVS1FWbFBMMDFCUlVsQ1FqQk5SekpvTUdSSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd4TWJVNTJZbFJEUW1sM1dVdExkMWxDUWtGSVZ3cGxVVWxGUVdkU09VSkljMEZsVVVJelFVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5ETnFlWFF2TkdWTFkyOUJka3RsTms5QlFVRkNDbXh6V0ZSeU5EQkJRVUZSUkVGRlozZFNaMGxvUVU0M09UQk1iblZ4UkZwM1puRjZlV2xzU0RSeGRHczNlbFoyVmxwVlVWaENNRkV3V1daWU9YUk9WMWdLUVdsRlFYcElOalE1UWxWNE1UVlZXWE5hVlVkcGFITkNaazVWVVZodmRqZzNWVmw2WmxsRk1scDNNa3d4TnpSM1EyZFpTVXR2V2tsNmFqQkZRWGROUkFwaFFVRjNXbEZKZDBOS09DdGpWbVJtVDJNMVUxQnZVVzVxV1RaeWNrbDRTV3haY1V4bmRGYzJOVmx5V0RoSGVtSlNWelJPY0ZBek4yMDJibmhwTm1OcUNuRjBaM2RIUmsxbFFXcEZRWEEwU21kaFJWUk5SbEpuVTBKVFUxcE1RamQxYUhGeU1XWlpPVGRNVUdOSWJVRmxZa3RHY0hGR1VVVlNSVXhOVlcxdGNXc0tOWFZJUWpKM1ozUjJla0l5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0="}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE6TADAgEAMIIE4AYJKoZIhvcNAQcCoIIE0TCCBM0CAQMxDTALBglghkgBZQMEAgEwgcIGCyqGSIb3DQEJEAEEoIGyBIGvMIGsAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgLenXcJBzdjua5V/WDyNWpTIysBnS9xKUPS0plFLqG0gCFQD3x9GVccz7Cvui6lxEdDQtb7L3uBgPMjAyNTA1MTIxOTAwMjdaMAMCAQECCHSCL66M6EByoDKkMDAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYaCCAhMwggIPMIIBlqADAgECAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkMB4XDTI1MDMyODA5MTQwNloXDTM1MDMyNjA4MTQwNlowLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATHW/kXcekP16Ae6SekEWVHPtAFEMm7hp5XO33MktFjSW+bHWUXtYEzZz0A3xkY9CyYOoeUk3ZH/v5HEuS+UvORzX0g7Hfy3uYYYRwHtqBQN0IX8rLdFMtIrRej/QCAdB2jajBoMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUqPxk9ijeLuY7c09UjFLE4ZzdU6UwHwYDVR0jBBgwFoAUOyBGWV61Mk1HMM5uY+5zdEfyBH0wFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDZwAwZAIwRK9VLoYa0Xff4nX1N/AQ1YleNG/iLT8dAXAtRKRfpN9XuDScbxWeo0cku8SkC06NAjBQPe7LBNeitA/UOBtXT2sX1h6f4ISqz+ISmJ4lY+y3bzRJI5nk1r53I9WT3/xIWToxggHbMIIB1wIBATBRMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAsGCWCGSAFlAwQCAaCB/DAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI1MDUxMjE5MDAyN1owLwYJKoZIhvcNAQkEMSIEIB+SgwjYmkSbLhZNvWnGj/KrNAOr+sqpO38OpoIYSOSZMIGOBgsqhkiG9w0BCRACLzF/MH0wezB5BCAG9P/gR/6zWZm3M7DXoyNQHPwY5MAzZqhF13U250snRDBVMD2kOzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAgRnMGUCMFMwn1mx1D3q+vKwf57UDA96286zoTJ+ITJG5IQVypKLqnKSEX8Gm7GIRDXR06PJPgIxANj1zJ+cVXxoYuH4H8yobeqVeztGLZNd+YqbkyuvTkcX46CTCH0e6imE+Z4yTCRiYw=="}]}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4="}, "signature": "MEQCIFhleMh29QEnPmW728dwXufGFMZ44o36ClxeqEeViLRtAiB23RDGzp+n1wh5cU1tp/BjjHstAB7lZf9KKJnzp3ubWA=="}}

test/unit/test_models.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030

3131

3232
class TestLogEntry:
33-
def test_missing_inclusion_proof(self):
33+
@pytest.mark.parametrize("integrated_time", [0, 1746819403])
34+
def test_missing_inclusion_proof(self, integrated_time: int):
3435
with pytest.raises(ValueError, match=r"inclusion_proof"):
3536
LogEntry(
3637
uuid="fake",
3738
body=b64encode(b"fake"),
38-
integrated_time=0,
39+
integrated_time=integrated_time,
3940
log_id="1234",
4041
log_index=1,
4142
inclusion_proof=None,

test/unit/verify/test_verifier.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515

1616
import hashlib
17+
import json
1718
import logging
1819
from datetime import datetime, timezone
1920

@@ -222,6 +223,36 @@ def test_verifier_no_validity_end(self, verifier, asset, null_policy):
222223
null_policy,
223224
)
224225

226+
@pytest.mark.parametrize(
227+
"fields_to_delete",
228+
(
229+
[],
230+
["inclusionPromise"],
231+
# integratedTime is required to verify the inclusionPromise.
232+
pytest.param(["integratedTime"], marks=pytest.mark.xfail),
233+
["inclusionPromise", "integratedTime"],
234+
),
235+
)
236+
def test_vierifier_verify_no_inclusion_promise_and_integrated_time(
237+
self, verifier, asset, null_policy, fields_to_delete
238+
):
239+
"""
240+
Ensure that we can still verify a Bundle with a rfc3161 timestamp if the SET can't be verified or isn't present.
241+
There is one exception: When inclusionPromise is present, but integratedTime is not, then we expect a failure
242+
because the integratedTime is required to verify the inclusionPromise.
243+
"""
244+
bundle_dict = json.loads(asset("tsa/bundle.txt.sigstore").read_bytes())
245+
(entry_dict,) = bundle_dict["verificationMaterial"]["tlogEntries"]
246+
for field in fields_to_delete:
247+
del entry_dict[field]
248+
# Bundle.from_json() also validates the bundle's layout.
249+
bundle = Bundle.from_json(json.dumps(bundle_dict))
250+
verifier.verify_artifact(
251+
asset("tsa/bundle.txt").read_bytes(),
252+
bundle,
253+
null_policy,
254+
)
255+
225256
def test_verifier_without_timestamp(
226257
self, verifier, asset, null_policy, monkeypatch
227258
):
@@ -301,6 +332,28 @@ def test_verifier_no_authorities(self, asset, null_policy):
301332
null_policy,
302333
)
303334

335+
def test_late_timestamp(self, caplog, verifier, asset, null_policy):
336+
"""
337+
Ensures that verifying the signing certificate fails because the timestamp
338+
is outside the certificate's validity window. The sample bundle
339+
"tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)`
340+
into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor
341+
but before the timestamp is requested.
342+
"""
343+
with pytest.raises(VerificationError, match="not enough timestamps"):
344+
verifier.verify_artifact(
345+
asset("tsa/bundle.txt").read_bytes(),
346+
Bundle.from_json(
347+
asset("tsa/bundle.txt.late_timestamp.sigstore").read_bytes()
348+
),
349+
null_policy,
350+
)
351+
352+
assert (
353+
caplog.records[0].message
354+
== "Error while verifying certificates: Unable to verify certificate"
355+
)
356+
304357
def test_verifier_not_enough_timestamp(
305358
self, verifier, asset, null_policy, monkeypatch
306359
):

0 commit comments

Comments
 (0)