Skip to content

Commit 41d95ad

Browse files
committed
Merge remote-tracking branch 'origin/main' into add-rekor-version-option
2 parents dc21376 + afa4611 commit 41d95ad

File tree

9 files changed

+60
-39
lines changed

9 files changed

+60
-39
lines changed

.github/workflows/scorecards-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ jobs:
5252

5353
# Upload the results to GitHub's code scanning dashboard.
5454
- name: "Upload to code-scanning"
55-
uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3
55+
uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
5656
with:
5757
sarif_file: results.sarif

install/requirements.txt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -539,9 +539,9 @@ rfc8785==0.1.4 \
539539
--hash=sha256:520d690b448ecf0703691c76e1a34a24ddcd4fc5bc41d589cb7c58ec651bcd48 \
540540
--hash=sha256:e545841329fe0eee4f6a3b44e7034343100c12b4ec566dc06ca9735681deb4da
541541
# via sigstore
542-
rich==14.0.0 \
543-
--hash=sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0 \
544-
--hash=sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725
542+
rich==14.1.0 \
543+
--hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \
544+
--hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8
545545
# via sigstore
546546
securesystemslib==1.3.0 \
547547
--hash=sha256:5b53e5989289d97fa42ed7fde1b4bad80985f15dba8c774c043b395a90c908e5 \
@@ -550,7 +550,7 @@ securesystemslib==1.3.0 \
550550
sigstore==3.6.4 \
551551
--hash=sha256:76f247a86738c9e076a243e0068ac68625848868890ed38491acc159752a46ac \
552552
--hash=sha256:d5678a7f4b78b084eb2c1a9eab31af81e6daf1f949abc3b7539a96900220d0d6
553-
# via -r requirements.in
553+
# via -r install/requirements.in
554554
sigstore-protobuf-specs==0.3.2 \
555555
--hash=sha256:50c99fa6747a3a9c5c562a43602cf76df0b199af28f0e9d4319b6775630425ea \
556556
--hash=sha256:cae041b40502600b8a633f43c257695d0222a94efa1e5110a7ec7ada78c39d99
@@ -575,7 +575,6 @@ typing-extensions==4.14.0 \
575575
# pydantic
576576
# pydantic-core
577577
# pyopenssl
578-
# rich
579578
# typing-inspection
580579
typing-inspection==0.4.1 \
581580
--hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ lint = [
6262
"mypy ~= 1.1",
6363
# NOTE(ww): ruff is under active development, so we pin conservatively here
6464
# and let Dependabot periodically perform this update.
65-
"ruff < 0.12.5",
65+
"ruff < 0.12.6",
6666
"types-requests",
6767
"types-pyOpenSSL",
6868
]

sigstore/verify/verifier.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,18 @@ def _verify_signed_timestamp(
128128
for certificate_authority in cert_authorities:
129129
certificates = certificate_authority.certificates(allow_expired=True)
130130

131-
builder = VerifierBuilder()
132-
for certificate in certificates:
133-
builder.add_root_certificate(certificate)
131+
# We expect at least a signing cert and a root cert but there may be intermediates
132+
if len(certificates) < 2:
133+
_logger.debug("Unable to verify Timestamp: cert chain is incomplete")
134+
continue
135+
136+
builder = (
137+
VerifierBuilder()
138+
.tsa_certificate(certificates[0])
139+
.add_root_certificate(certificates[-1])
140+
)
141+
for certificate in certificates[1:-1]:
142+
builder = builder.add_intermediate_certificate(certificate)
134143

135144
verifier = builder.build()
136145
try:

test/assets/tsa/issue1482-message

72 Bytes
Binary file not shown.
728 Bytes
Binary file not shown.

test/integration/cli/test_sign.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,16 @@ def get_cli_params(
5656

5757
@pytest.mark.staging
5858
@pytest.mark.ambient_oidc
59-
def test_sign_success_default_output_bundle(capsys, sigstore, asset_integration):
59+
def test_sign_success_default_output_bundle(
60+
capsys, sigstore, asset_integration, tmp_path
61+
):
6062
artifact = asset_integration("a.txt")
61-
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")
63+
expected_output_bundle = tmp_path / "a.txt.sigstore.json"
6264

63-
assert not expected_output_bundle.exists()
6465
sigstore(
6566
*get_cli_params(
6667
artifact_paths=[artifact],
68+
output_directory=tmp_path,
6769
)
6870
)
6971

@@ -78,8 +80,6 @@ def test_sign_success_default_output_bundle(capsys, sigstore, asset_integration)
7880
input_=input_file.read(), bundle=bundle, policy=UnsafeNoOp()
7981
)
8082

81-
expected_output_bundle.unlink()
82-
8383
captures = capsys.readouterr()
8484
assert captures.out.endswith(
8585
f"Sigstore bundle written to {expected_output_bundle}\n"
@@ -88,8 +88,8 @@ def test_sign_success_default_output_bundle(capsys, sigstore, asset_integration)
8888

8989
@pytest.mark.staging
9090
@pytest.mark.ambient_oidc
91-
def test_sign_success_multiple_artifacts(capsys, sigstore, asset_integration):
92-
artifacts = [
91+
def test_sign_success_multiple_artifacts(capsys, sigstore, asset_integration, tmp_path):
92+
artifacts: list[Path] = [
9393
asset_integration("a.txt"),
9494
asset_integration("b.txt"),
9595
asset_integration("c.txt"),
@@ -98,13 +98,14 @@ def test_sign_success_multiple_artifacts(capsys, sigstore, asset_integration):
9898
sigstore(
9999
*get_cli_params(
100100
artifact_paths=artifacts,
101+
output_directory=tmp_path,
101102
)
102103
)
103104

104105
captures = capsys.readouterr()
105106

106107
for artifact in artifacts:
107-
expected_output_bundle = Path(f"{artifact}.sigstore.json")
108+
expected_output_bundle = tmp_path / f"{artifact.name}.sigstore.json"
108109

109110
assert f"Sigstore bundle written to {expected_output_bundle}\n" in captures.out
110111

@@ -115,7 +116,6 @@ def test_sign_success_multiple_artifacts(capsys, sigstore, asset_integration):
115116
open(artifact, "rb") as input_file,
116117
):
117118
bundle = Bundle.from_json(bundle_file.read())
118-
expected_output_bundle.unlink()
119119
verifier.verify_artifact(
120120
input_=input_file.read(), bundle=bundle, policy=UnsafeNoOp()
121121
)
@@ -124,14 +124,14 @@ def test_sign_success_multiple_artifacts(capsys, sigstore, asset_integration):
124124
@pytest.mark.staging
125125
@pytest.mark.ambient_oidc
126126
def test_sign_success_multiple_artifacts_rekor_v2(
127-
capsys, sigstore, asset_integration, asset
127+
capsys, sigstore, asset_integration, asset, tmp_path
128128
):
129129
"""This is a copy of test_sign_success_multiple_artifacts that exists to ensure the
130130
multi-threaded signing works with rekor v2 as well: this test can be removed when v2
131131
is the default
132132
"""
133133

134-
artifacts = [
134+
artifacts: list[Path] = [
135135
asset_integration("a.txt"),
136136
asset_integration("b.txt"),
137137
asset_integration("c.txt"),
@@ -141,13 +141,14 @@ def test_sign_success_multiple_artifacts_rekor_v2(
141141
*get_cli_params(
142142
artifact_paths=artifacts,
143143
trust_config_path=asset("trust_config/staging-but-sign-with-rekor-v2.json"),
144+
output_directory=tmp_path,
144145
)
145146
)
146147

147148
captures = capsys.readouterr()
148149

149150
for artifact in artifacts:
150-
expected_output_bundle = Path(f"{artifact}.sigstore.json")
151+
expected_output_bundle = tmp_path / f"{artifact.name}.sigstore.json"
151152

152153
assert f"Sigstore bundle written to {expected_output_bundle}\n" in captures.out
153154

@@ -158,7 +159,6 @@ def test_sign_success_multiple_artifacts_rekor_v2(
158159
open(artifact, "rb") as input_file,
159160
):
160161
bundle = Bundle.from_json(bundle_file.read())
161-
expected_output_bundle.unlink()
162162
verifier.verify_artifact(
163163
input_=input_file.read(), bundle=bundle, policy=UnsafeNoOp()
164164
)
@@ -240,14 +240,14 @@ def test_sign_success_no_default_files(capsys, sigstore, asset_integration, tmp_
240240

241241
@pytest.mark.staging
242242
@pytest.mark.ambient_oidc
243-
def test_sign_overwrite_existing_bundle(capsys, sigstore, asset_integration):
243+
def test_sign_overwrite_existing_bundle(capsys, sigstore, asset_integration, tmp_path):
244244
artifact = asset_integration("a.txt")
245-
expected_output_bundle = artifact.with_name("a.txt.sigstore.json")
245+
expected_output_bundle = tmp_path / "a.txt.sigstore.json"
246246

247-
assert not expected_output_bundle.exists()
248247
sigstore(
249248
*get_cli_params(
250249
artifact_paths=[artifact],
250+
output_directory=tmp_path,
251251
)
252252
)
253253

@@ -256,6 +256,7 @@ def test_sign_overwrite_existing_bundle(capsys, sigstore, asset_integration):
256256
sigstore(
257257
*get_cli_params(
258258
artifact_paths=[artifact],
259+
output_directory=tmp_path,
259260
overwrite=True,
260261
)
261262
)
@@ -265,6 +266,7 @@ def test_sign_overwrite_existing_bundle(capsys, sigstore, asset_integration):
265266
sigstore(
266267
*get_cli_params(
267268
artifact_paths=[artifact],
269+
output_directory=tmp_path,
268270
overwrite=False,
269271
)
270272
)
@@ -275,8 +277,6 @@ def test_sign_overwrite_existing_bundle(capsys, sigstore, asset_integration):
275277
f"Refusing to overwrite outputs without --overwrite: {expected_output_bundle}\n"
276278
)
277279

278-
expected_output_bundle.unlink()
279-
280280

281281
def test_sign_fails_with_default_files_and_bundle_options(
282282
capsys, sigstore, asset_integration

test/unit/test_sign.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
from sigstore.verify.policy import UnsafeNoOp
3030

3131

32-
@pytest.mark.parametrize("env", ["staging", "production"])
32+
# only check the log contents for production: staging is already on
33+
# rekor v2 and we don't currently support log lookups on rekor v2.
34+
# This test can likely be removed once prod also uses rekor v2
35+
@pytest.mark.parametrize("env", ["production"])
3336
@pytest.mark.ambient_oidc
34-
def test_sign_rekor_entry_consistent(sign_ctx_and_ident_for_env):
37+
def test_sign_rekor_entry_consistent(request, sign_ctx_and_ident_for_env):
3538
ctx_cls, identity = sign_ctx_and_ident_for_env
3639

3740
# NOTE: The actual signer instance is produced lazily, so that parameter
@@ -108,25 +111,20 @@ def test_sct_verify_keyring_error(sign_ctx_and_ident_for_env, monkeypatch):
108111

109112
@pytest.mark.parametrize("env", ["staging", "production"])
110113
@pytest.mark.ambient_oidc
111-
def test_identity_proof_claim_lookup(sign_ctx_and_ident_for_env, monkeypatch):
114+
def test_identity_proof_fallback_claim(sign_ctx_and_ident_for_env, monkeypatch):
112115
ctx_cls, identity = sign_ctx_and_ident_for_env
113116

114117
ctx: SigningContext = ctx_cls()
115118
assert identity is not None
116119

117-
# clear out the known issuers, forcing the `Identity`'s `proof_claim` to be looked up.
120+
# clear out known issuers, forcing the `Identity`'s `sub` claim to be used
121+
# as fall back
118122
monkeypatch.setattr(sigstore.oidc, "_KNOWN_OIDC_ISSUERS", {})
119123

120124
payload = secrets.token_bytes(32)
121125

122126
with ctx.signer(identity) as signer:
123-
expected_entry = signer.sign_artifact(payload).log_entry
124-
actual_entry = ctx._rekor.log.entries.get(log_index=expected_entry.log_index)
125-
126-
assert expected_entry.body == actual_entry.body
127-
assert expected_entry.integrated_time == actual_entry.integrated_time
128-
assert expected_entry.log_id == actual_entry.log_id
129-
assert expected_entry.log_index == actual_entry.log_index
127+
signer.sign_artifact(payload)
130128

131129

132130
@pytest.mark.staging

test/unit/verify/test_verifier.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,18 @@ def test_verifier_not_enough_timestamp(
377377
Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()),
378378
null_policy,
379379
)
380+
381+
def test_verify_signed_timestamp_regression(self, asset):
382+
"""
383+
Ensure we correctly verify a timestamp with no embedded certs.
384+
385+
This is a regression test for # 1482
386+
"""
387+
verifier = Verifier.staging(offline=True)
388+
ts = rfc3161_client.decode_timestamp_response(
389+
asset("tsa/issue1482-timestamp-with-no-cert").read_bytes()
390+
)
391+
res = verifier._verify_signed_timestamp(
392+
ts, asset("tsa/issue1482-message").read_bytes()
393+
)
394+
assert res is not None

0 commit comments

Comments
 (0)