Skip to content

Commit 34c7f4b

Browse files
committed
Merge branch 'main' into ww/refactor-trust
2 parents 172fc0e + 09da22b commit 34c7f4b

File tree

15 files changed

+62
-39
lines changed

15 files changed

+62
-39
lines changed

.github/workflows/scorecards-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
persist-credentials: false
3030

3131
- name: "Run analysis"
32-
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
32+
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
3333
with:
3434
results_file: results.sarif
3535
results_format: sarif

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ All versions prior to 0.9.0 are untracked.
5858
* **BREAKING API CHANGE**: `Verifier.verify(...)` now takes a `sigstore.models.Bundle`,
5959
instead of a `VerificationMaterials` ([#937](https://github.com/sigstore/sigstore-python/pull/937))
6060

61+
* **BREAKING CLI CHANGE**: `sigstore sign` now emits `{input}.sigstore.json`
62+
by default instead of `{input}.sigstore`, per the client specification
63+
([#1007](https://github.com/sigstore/sigstore-python/pull/1007))
64+
6165
* sigstore-python now requires inclusion proofs in all signing and verification
6266
flows, regardless of bundle version of input types. Inputs that do not
6367
have an inclusion proof (such as detached materials) cause an online lookup
@@ -78,6 +82,11 @@ All versions prior to 0.9.0 are untracked.
7882
have been re-homed under `sigstore.models`
7983
([#990](https://github.com/sigstore/sigstore-python/pull/990))
8084

85+
* API: `oidc.IdentityToken.expected_certificate_subject` has been renamed
86+
to `oidc.IdentityToken.federated_issuer` to better describe what it actually
87+
contains. No functional changes have been made to it
88+
([#1016](https://github.com/sigstore/sigstore-python/pull/1016))
89+
8190
## [2.1.5]
8291

8392
## Fixed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ OpenID Connect options:
162162
False)
163163

164164
Output options:
165-
--no-default-files Don't emit the default output files ({input}.sigstore)
166-
(default: False)
165+
--no-default-files Don't emit the default output files
166+
({input}.sigstore.json) (default: False)
167167
--signature FILE, --output-signature FILE
168168
Write a single signature to the given file; does not
169169
work with multiple input files (default: None)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ dependencies = [
3535
"requests",
3636
"rich ~= 13.0",
3737
"rfc8785 ~= 0.1.2",
38-
"sigstore-protobuf-specs ~= 0.3.1",
38+
"sigstore-protobuf-specs ~= 0.3.2",
3939
# NOTE(ww): Under active development, so strictly pinned.
4040
"sigstore-rekor-types == 0.0.13",
41-
"tuf ~= 4.0",
41+
"tuf ~= 5.0",
4242
"platformdirs ~= 4.2",
4343
]
4444
requires-python = ">=3.8"

sigstore/_cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def _parser() -> argparse.ArgumentParser:
243243
"--no-default-files",
244244
action="store_true",
245245
default=_boolify_env("SIGSTORE_NO_DEFAULT_FILES"),
246-
help="Don't emit the default output files ({input}.sigstore)",
246+
help="Don't emit the default output files ({input}.sigstore.json)",
247247
)
248248
output_options.add_argument(
249249
"--signature",
@@ -487,7 +487,7 @@ def _sign(args: argparse.Namespace) -> None:
487487
output_dir.mkdir(parents=True, exist_ok=True)
488488

489489
if not bundle and not args.no_default_files:
490-
bundle = output_dir / f"{file.name}.sigstore"
490+
bundle = output_dir / f"{file.name}.sigstore.json"
491491

492492
if not args.overwrite:
493493
extants = []

sigstore/_internal/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@
1818
Everything in these APIs is considered internal and unstable, and is not
1919
subject to any stability guarantees.
2020
"""
21+
22+
from requests import __version__ as requests_version
23+
24+
from sigstore import __version__ as sigstore_version
25+
26+
USER_AGENT = f"sigstore-python/{sigstore_version} (python-requests/{requests_version})"

sigstore/_internal/fulcio/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
)
4545
from pydantic import BaseModel, ConfigDict, Field, field_validator
4646

47+
from sigstore._internal import USER_AGENT
4748
from sigstore._internal.sct import (
4849
UnexpectedSctCountException,
4950
_get_precertificate_signed_certificate_timestamps,
@@ -338,6 +339,11 @@ def __init__(self, url: str = DEFAULT_FULCIO_URL) -> None:
338339
_logger.debug(f"Fulcio client using URL: {url}")
339340
self.url = url
340341
self.session = requests.Session()
342+
self.session.headers.update(
343+
{
344+
"User-Agent": USER_AGENT,
345+
}
346+
)
341347

342348
def __del__(self) -> None:
343349
"""

sigstore/_internal/rekor/client.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import rekor_types
2929
import requests
3030

31+
from sigstore._internal import USER_AGENT
3132
from sigstore.models import LogEntry
3233

3334
_logger = logging.getLogger(__name__)
@@ -228,7 +229,11 @@ def __init__(self, url: str) -> None:
228229
self.url = urljoin(url, "api/v1/")
229230
self.session = requests.Session()
230231
self.session.headers.update(
231-
{"Content-Type": "application/json", "Accept": "application/json"}
232+
{
233+
"Content-Type": "application/json",
234+
"Accept": "application/json",
235+
"User-Agent": USER_AGENT,
236+
}
232237
)
233238

234239
def __del__(self) -> None:

sigstore/_internal/trust.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ def __init__(self, public_key: _PublicKey) -> None:
108108
Construct a key from the given Sigstore PublicKey message.
109109
"""
110110

111+
# NOTE: `raw_bytes` is marked as `optional` in the `PublicKey` message,
112+
# for unclear reasons.
113+
if not public_key.raw_bytes:
114+
raise VerificationError("public key is empty")
115+
111116
hash_algorithm: hashes.HashAlgorithm
112117
if public_key.key_details in self._RSA_SHA_256_DETAILS:
113118
hash_algorithm = hashes.SHA256()

sigstore/_internal/tuf.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525

2626
import platformdirs
2727
from tuf.api import exceptions as TUFExceptions
28-
from tuf.ngclient import RequestsFetcher, Updater
28+
from tuf.ngclient import Updater, UpdaterConfig
2929

30+
from sigstore import __version__
3031
from sigstore._utils import read_embedded
3132
from sigstore.errors import RootError, TUFError
3233

@@ -36,18 +37,6 @@
3637
STAGING_TUF_URL = "https://tuf-repo-cdn.sigstage.dev"
3738

3839

39-
@lru_cache()
40-
def _get_fetcher() -> RequestsFetcher:
41-
# NOTE: We poke into the underlying fetcher here to set a more reasonable timeout.
42-
# The default timeout is 4 seconds, which can cause spurious timeout errors on
43-
# CI systems like GitHub Actions (where traffic may be delayed/deprioritized due
44-
# to network load).
45-
fetcher = RequestsFetcher()
46-
fetcher.socket_timeout = 30
47-
48-
return fetcher
49-
50-
5140
def _get_dirs(url: str) -> tuple[Path, Path]:
5241
"""
5342
Given a TUF repository URL, return suitable local metadata and cache directories.
@@ -133,7 +122,7 @@ def __init__(self, url: str, offline: bool = False) -> None:
133122
metadata_base_url=self._repo_url,
134123
target_base_url=parse.urljoin(f"{self._repo_url}/", "targets/"),
135124
target_dir=str(self._targets_dir),
136-
fetcher=_get_fetcher(),
125+
config=UpdaterConfig(app_user_agent=f"sigstore-python/{__version__}"),
137126
)
138127
try:
139128
self._updater.refresh()

sigstore/dsse.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,7 @@ def to_json(self) -> str:
187187
"""
188188
Return a JSON string with this DSSE envelope's contents.
189189
"""
190-
# TODO: Unclear why mypy thinks this is returning `Any`.
191-
return self._inner.to_json() # type: ignore[no-any-return]
190+
return self._inner.to_json()
192191

193192

194193
def _pae(type_: str, body: bytes) -> bytes:
@@ -217,7 +216,7 @@ def _sign(key: ec.EllipticCurvePrivateKey, stmt: Statement) -> Envelope:
217216
_Envelope(
218217
payload=stmt._contents,
219218
payload_type=Envelope._TYPE,
220-
signatures=[Signature(sig=signature, keyid=None)],
219+
signatures=[Signature(sig=signature)],
221220
)
222221
)
223222

@@ -244,6 +243,4 @@ def _verify(key: ec.EllipticCurvePublicKey, evp: Envelope) -> bytes:
244243
except InvalidSignature:
245244
raise VerificationError("DSSE: invalid signature")
246245

247-
# TODO: Remove ignore when protobuf-specs contains a py.typed marker.
248-
# See: <https://github.com/sigstore/protobuf-specs/pull/287>
249-
return evp._inner.payload # type: ignore[no-any-return]
246+
return evp._inner.payload

sigstore/models.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def _to_dict_rekor(self) -> dict[str, Any]:
243243
log_index=self.log_index,
244244
log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)),
245245
integrated_time=self.integrated_time,
246-
inclusion_promise=inclusion_promise,
246+
inclusion_promise=inclusion_promise, # type: ignore[arg-type]
247247
inclusion_proof=inclusion_proof,
248248
canonicalized_body=base64.b64decode(self.body),
249249
)
@@ -494,8 +494,7 @@ def to_json(self) -> str:
494494
"""
495495
Return a JSON encoding of this bundle.
496496
"""
497-
# TODO: Unclear why mypy doesn't like this.
498-
return self._inner.to_json() # type: ignore[no-any-return]
497+
return self._inner.to_json()
499498

500499
@classmethod
501500
def from_parts(cls, cert: Certificate, sig: bytes, log_entry: LogEntry) -> Bundle:

sigstore/oidc.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import requests
3232
from pydantic import BaseModel, StrictStr
3333

34+
from sigstore._internal import USER_AGENT
3435
from sigstore.errors import Error, NetworkError
3536

3637
DEFAULT_OAUTH_ISSUER_URL = "https://oauth2.sigstore.dev/auth"
@@ -204,9 +205,9 @@ def issuer(self) -> str:
204205
return self._iss
205206

206207
@property
207-
def expected_certificate_subject(self) -> str:
208+
def federated_issuer(self) -> str:
208209
"""
209-
Returns a URL identifying the **expected** subject for any Sigstore
210+
Returns a URL identifying the **federated** issuer for any Sigstore
210211
certificate issued against this identity token.
211212
212213
The behavior of this field is slightly subtle: for non-federated
@@ -217,7 +218,7 @@ def expected_certificate_subject(self) -> str:
217218
implementation-defined claim.
218219
219220
This attribute exists so that clients who wish to inspect the expected
220-
subject of their certificates can do so without relying on
221+
underlying issuer of their certificates can do so without relying on
221222
implementation-specific behavior.
222223
"""
223224
if self._federated_issuer is not None:
@@ -255,12 +256,15 @@ def __init__(self, base_url: str) -> None:
255256
which is then used to bootstrap the issuer's state (such
256257
as authorization and token endpoints).
257258
"""
259+
self.session = requests.Session()
260+
self.session.headers.update({"User-Agent": USER_AGENT})
261+
258262
oidc_config_url = urllib.parse.urljoin(
259263
f"{base_url}/", ".well-known/openid-configuration"
260264
)
261265

262266
try:
263-
resp: requests.Response = requests.get(oidc_config_url, timeout=30)
267+
resp: requests.Response = self.session.get(oidc_config_url, timeout=30)
264268
except (requests.ConnectionError, requests.Timeout) as exc:
265269
raise NetworkError from exc
266270

@@ -352,7 +356,7 @@ def identity_token( # nosec: B107
352356
)
353357
logging.debug(f"PAYLOAD: data={data}")
354358
try:
355-
resp: requests.Response = requests.post(
359+
resp = self.session.post(
356360
self.oidc_config.token_endpoint,
357361
data=data,
358362
auth=auth,

test/unit/conftest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434
from tuf.api.exceptions import DownloadHTTPError
3535
from tuf.ngclient import FetcherInterface
36+
from tuf.ngclient.updater import requests_fetcher
3637

3738
from sigstore._internal import tuf
3839
from sigstore._internal.rekor import _hashedrekord_from_parts
@@ -225,7 +226,7 @@ def verify(self, cert):
225226

226227
@pytest.fixture
227228
def mock_staging_tuf(monkeypatch, tuf_dirs):
228-
"""Mock that prevents tuf module from making requests: it returns staging
229+
"""Mock that prevents python-tuf from making requests: it returns staging
229230
assets from a local directory instead
230231
231232
Return a tuple of dicts with the requested files and counts"""
@@ -244,7 +245,9 @@ def _fetch(self, url: str) -> Iterator[bytes]:
244245
failure[filename] += 1
245246
raise DownloadHTTPError("File not found", 404)
246247

247-
monkeypatch.setattr(tuf, "_get_fetcher", lambda: MockFetcher())
248+
monkeypatch.setattr(
249+
requests_fetcher, "RequestsFetcher", lambda app_user_agent: MockFetcher()
250+
)
248251

249252
return success, failure
250253

test/unit/test_oidc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,4 @@ def test_ok(self, dummy_jwt, iss, identity_claim, identity_value, fed_iss):
267267
assert identity.in_validity_period()
268268
assert identity.identity == identity_value
269269
assert identity.issuer == iss
270-
assert identity.expected_certificate_subject == iss if not fed_iss else fed_iss
270+
assert identity.federated_issuer == iss if not fed_iss else fed_iss

0 commit comments

Comments
 (0)