Skip to content

Commit 313da43

Browse files
Merge branch 'main' into kindversion
2 parents 0172eed + ad9a001 commit 313da43

File tree

13 files changed

+375
-73
lines changed

13 files changed

+375
-73
lines changed

CHANGELOG.md

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

26+
### Changed
27+
28+
* `--trust-config` now requires a file with SigningConfig v0.2, and is able to fully
29+
configure the used Sigstore instance [#1358]/(https://github.com/sigstore/sigstore-python/pull/1358)
30+
2631
## [3.6.2]
2732

2833
### Fixed

install/requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,9 @@ multidict==6.4.3 \
372372
--hash=sha256:fbd8d737867912b6c5f99f56782b8cb81f978a97b4437a1c476de90a3e41c9a1 \
373373
--hash=sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad
374374
# via grpclib
375-
platformdirs==4.3.7 \
376-
--hash=sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94 \
377-
--hash=sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351
375+
platformdirs==4.3.8 \
376+
--hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \
377+
--hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4
378378
# via sigstore
379379
pyasn1==0.6.1 \
380380
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dependencies = [
3838
"rfc8785 ~= 0.1.2",
3939
"rfc3161-client >= 0.1.2,< 1.1.0",
4040
# NOTE(ww): Both under active development, so strictly pinned.
41-
"sigstore-protobuf-specs == 0.3.2",
41+
"sigstore-protobuf-specs == 0.4.1",
4242
"sigstore-rekor-types == 0.0.18",
4343
"tuf ~= 6.0",
4444
"platformdirs ~= 4.2",

sigstore/_cli.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -713,9 +713,7 @@ def _sign_common(
713713
else:
714714
sig_output = sys.stdout
715715

716-
signature = base64.b64encode(
717-
result._inner.message_signature.signature
718-
).decode()
716+
signature = base64.b64encode(result.signature).decode()
719717
print(signature, file=sig_output)
720718
if outputs.signature is not None:
721719
print(f"Signature written to {outputs.signature}")
@@ -1202,7 +1200,7 @@ def _fix_bundle(args: argparse.Namespace) -> None:
12021200
# for custom Rekor instances.
12031201
rekor = RekorClient.staging() if args.staging else RekorClient.production()
12041202

1205-
raw_bundle = RawBundle().from_json(args.bundle.read_text())
1203+
raw_bundle = RawBundle.from_dict(json.loads(args.bundle.read_bytes()))
12061204

12071205
if len(raw_bundle.verification_material.tlog_entries) != 1:
12081206
_fatal("unfixable bundle: must have exactly one log entry")

sigstore/_internal/trust.py

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@
4545
ClientTrustConfig as _ClientTrustConfig,
4646
)
4747
from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import (
48+
Service,
49+
ServiceSelector,
4850
TransparencyLogInstance,
4951
)
52+
from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import (
53+
SigningConfig as _SigningConfig,
54+
)
5055
from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import (
5156
TrustedRoot as _TrustedRoot,
5257
)
@@ -93,14 +98,14 @@ class Key:
9398
key: PublicKey
9499
key_id: KeyID
95100

96-
_RSA_SHA_256_DETAILS: ClassVar[set[_PublicKeyDetails]] = {
101+
_RSA_SHA_256_DETAILS: ClassVar = {
97102
_PublicKeyDetails.PKCS1_RSA_PKCS1V5,
98103
_PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256,
99104
_PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256,
100105
_PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256,
101106
}
102107

103-
_EC_DETAILS_TO_HASH: ClassVar[dict[_PublicKeyDetails, hashes.HashAlgorithm]] = {
108+
_EC_DETAILS_TO_HASH: ClassVar = {
104109
_PublicKeyDetails.PKIX_ECDSA_P256_SHA_256: hashes.SHA256(),
105110
_PublicKeyDetails.PKIX_ECDSA_P384_SHA_384: hashes.SHA384(),
106111
_PublicKeyDetails.PKIX_ECDSA_P521_SHA_512: hashes.SHA512(),
@@ -278,6 +283,114 @@ def certificates(self, *, allow_expired: bool) -> list[Certificate]:
278283
return self._certificates
279284

280285

286+
class SigningConfig:
287+
"""
288+
Signing configuration for a Sigstore instance.
289+
"""
290+
291+
class SigningConfigType(str, Enum):
292+
"""
293+
Known Sigstore signing config media types.
294+
"""
295+
296+
SIGNING_CONFIG_0_2 = "application/vnd.dev.sigstore.signingconfig.v0.2+json"
297+
298+
def __str__(self) -> str:
299+
"""Returns the variant's string value."""
300+
return self.value
301+
302+
def __init__(self, inner: _SigningConfig):
303+
"""
304+
Construct a new `SigningConfig`.
305+
306+
@api private
307+
"""
308+
self._inner = inner
309+
self._verify()
310+
311+
def _verify(self) -> None:
312+
"""
313+
Performs various feats of heroism to ensure that the signing config
314+
is well-formed.
315+
"""
316+
317+
# must have a recognized media type.
318+
try:
319+
SigningConfig.SigningConfigType(self._inner.media_type)
320+
except ValueError:
321+
raise Error(f"unsupported signing config format: {self._inner.media_type}")
322+
323+
# currently not supporting other select modes
324+
# TODO: Support other modes ensuring tsa_urls() and tlog_urls() work
325+
if self._inner.rekor_tlog_config.selector != ServiceSelector.ANY:
326+
raise Error(
327+
f"unsupported tlog selector {self._inner.rekor_tlog_config.selector}"
328+
)
329+
if self._inner.tsa_config.selector != ServiceSelector.ANY:
330+
raise Error(f"unsupported TSA selector {self._inner.tsa_config.selector}")
331+
332+
@classmethod
333+
def from_file(
334+
cls,
335+
path: str,
336+
) -> SigningConfig:
337+
"""Create a new signing config from file"""
338+
inner = _SigningConfig().from_json(Path(path).read_bytes())
339+
return cls(inner)
340+
341+
@staticmethod
342+
def _get_valid_service_url(services: list[Service]) -> str | None:
343+
for service in services:
344+
if service.major_api_version != 1:
345+
continue
346+
347+
if not _is_timerange_valid(service.valid_for, allow_expired=False):
348+
continue
349+
return service.url
350+
return None
351+
352+
def get_tlog_urls(self) -> list[str]:
353+
"""
354+
Returns the rekor transparency logs that client should sign with.
355+
Currently only returns a single one but could in future return several
356+
"""
357+
358+
url = self._get_valid_service_url(self._inner.rekor_tlog_urls)
359+
if not url:
360+
raise Error("No valid Rekor transparency log found in signing config")
361+
return [url]
362+
363+
def get_fulcio_url(self) -> str:
364+
"""
365+
Returns url for the fulcio instance that client should use to get a
366+
signing certificate from
367+
"""
368+
url = self._get_valid_service_url(self._inner.ca_urls)
369+
if not url:
370+
raise Error("No valid Fulcio CA found in signing config")
371+
return url
372+
373+
def get_oidc_url(self) -> str:
374+
"""
375+
Returns url for the OIDC provider that client should use to interactively
376+
authenticate.
377+
"""
378+
url = self._get_valid_service_url(self._inner.oidc_urls)
379+
if not url:
380+
raise Error("No valid OIDC provider found in signing config")
381+
return url
382+
383+
def get_tsa_urls(self) -> list[str]:
384+
"""
385+
Returns timestamp authority API end points. Currently returns a single one
386+
but may return more in future.
387+
"""
388+
url = self._get_valid_service_url(self._inner.tsa_urls)
389+
if not url:
390+
raise Error("No valid Timestamp Authority found in signing config")
391+
return [url]
392+
393+
281394
class TrustedRoot:
282395
"""
283396
The cryptographic root(s) of trust for a Sigstore instance.
@@ -473,3 +586,10 @@ def trusted_root(self) -> TrustedRoot:
473586
Return the interior root of trust, as a `TrustedRoot`.
474587
"""
475588
return TrustedRoot(self._inner.trusted_root)
589+
590+
@property
591+
def signing_config(self) -> SigningConfig:
592+
"""
593+
Return the interior root of trust, as a `SigningConfig`.
594+
"""
595+
return SigningConfig(self._inner.signing_config)

sigstore/hashes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ def __str__(self) -> str:
6060
"""
6161
Returns a str representation of this `Hashed`.
6262
"""
63-
return f"{self.algorithm.name}:{self.digest.hex()}"
63+
return f"{HashAlgorithm(self.algorithm)}:{self.digest.hex()}"

sigstore/models.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from __future__ import annotations
2020

2121
import base64
22+
import json
2223
import logging
2324
import typing
2425
from enum import Enum
@@ -470,18 +471,18 @@ def _verify(self) -> None:
470471
Bundle.BundleType.BUNDLE_0_3_ALT,
471472
):
472473
# For "v3" bundles, the signing certificate is the only one present.
474+
if not self._inner.verification_material.certificate:
475+
raise InvalidBundle("expected certificate in bundle")
476+
473477
leaf_cert = load_der_x509_certificate(
474478
self._inner.verification_material.certificate.raw_bytes
475479
)
476480
else:
477481
# In older bundles, there is an entire pool (misleadingly called
478482
# a chain) of certificates, the first of which is the signing
479483
# certificate.
480-
certs = (
481-
self._inner.verification_material.x509_certificate_chain.certificates
482-
)
483-
484-
if len(certs) == 0:
484+
chain = self._inner.verification_material.x509_certificate_chain
485+
if not chain or not chain.certificates:
485486
raise InvalidBundle("expected non-empty certificate chain in bundle")
486487

487488
# Per client policy in protobuf-specs: the first entry in the chain
@@ -493,7 +494,7 @@ def _verify(self) -> None:
493494
# and intermediate CAs, so we issue warnings and not hard errors
494495
# in those cases.
495496
leaf_cert, *chain_certs = (
496-
load_der_x509_certificate(cert.raw_bytes) for cert in certs
497+
load_der_x509_certificate(cert.raw_bytes) for cert in chain.certificates
497498
)
498499
if not cert_is_leaf(leaf_cert):
499500
raise InvalidBundle(
@@ -580,8 +581,8 @@ def _dsse_envelope(self) -> dsse.Envelope | None:
580581
581582
@private
582583
"""
583-
if self._inner.dsse_envelope:
584-
return dsse.Envelope(self._inner.dsse_envelope)
584+
if self._inner.is_set("dsse_envelope"):
585+
return dsse.Envelope(self._inner.dsse_envelope) # type: ignore[arg-type]
585586
return None
586587

587588
@property
@@ -593,7 +594,7 @@ def signature(self) -> bytes:
593594
return (
594595
self._dsse_envelope.signature
595596
if self._dsse_envelope
596-
else self._inner.message_signature.signature
597+
else self._inner.message_signature.signature # type: ignore[union-attr]
597598
)
598599

599600
@property
@@ -608,7 +609,7 @@ def from_json(cls, raw: bytes | str) -> Bundle:
608609
"""
609610
Deserialize the given Sigstore bundle.
610611
"""
611-
inner = _Bundle().from_json(raw)
612+
inner = _Bundle.from_dict(json.loads(raw))
612613
return cls(inner)
613614

614615
def to_json(self) -> str:
@@ -627,7 +628,10 @@ def _to_parts(
627628
"""
628629

629630
content: common_v1.MessageSignature | dsse.Envelope
630-
content = self._dsse_envelope or self._inner.message_signature
631+
if self._dsse_envelope:
632+
content = self._dsse_envelope
633+
else:
634+
content = self._inner.message_signature # type: ignore[assignment]
631635

632636
return (self.signing_certificate, content, self.log_entry)
633637

@@ -654,30 +658,32 @@ def _from_parts(
654658
@private
655659
"""
656660

657-
inner = _Bundle(
658-
media_type=Bundle.BundleType.BUNDLE_0_3.value,
659-
verification_material=bundle_v1.VerificationMaterial(
660-
certificate=common_v1.X509Certificate(cert.public_bytes(Encoding.DER)),
661-
),
661+
timestamp_verifcation_data = bundle_v1.TimestampVerificationData(
662+
rfc3161_timestamps=[]
662663
)
664+
if signed_timestamp is not None:
665+
timestamp_verifcation_data.rfc3161_timestamps.extend(
666+
[
667+
Rfc3161SignedTimestamp(signed_timestamp=response.as_bytes())
668+
for response in signed_timestamp
669+
]
670+
)
663671

664672
# Fill in the appropriate variants.
665673
if isinstance(content, common_v1.MessageSignature):
666-
inner.message_signature = content
674+
# mypy will be mystified if types are specified here
675+
content_dict: dict[str, Any] = {"message_signature": content}
667676
else:
668-
inner.dsse_envelope = content._inner
677+
content_dict = {"dsse_envelope": content._inner}
669678

670-
tlog_entry = log_entry._to_rekor()
671-
inner.verification_material.tlog_entries = [tlog_entry]
672-
673-
if signed_timestamp is not None:
674-
inner.verification_material.timestamp_verification_data = (
675-
bundle_v1.TimestampVerificationData(
676-
rfc3161_timestamps=[
677-
Rfc3161SignedTimestamp(signed_timestamp=response.as_bytes())
678-
for response in signed_timestamp
679-
]
680-
)
681-
)
679+
inner = _Bundle(
680+
media_type=Bundle.BundleType.BUNDLE_0_3.value,
681+
verification_material=bundle_v1.VerificationMaterial(
682+
certificate=common_v1.X509Certificate(cert.public_bytes(Encoding.DER)),
683+
tlog_entries=[log_entry._to_rekor()],
684+
timestamp_verification_data=timestamp_verifcation_data,
685+
),
686+
**content_dict,
687+
)
682688

683689
return cls(inner)

sigstore/sign.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,13 +352,13 @@ def _from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext:
352352
353353
@api private
354354
"""
355+
signing_config = trust_config.signing_config
355356
return cls(
356-
fulcio=FulcioClient(trust_config._inner.signing_config.ca_url),
357-
rekor=RekorClient(trust_config._inner.signing_config.tlog_urls[0]),
357+
fulcio=FulcioClient(signing_config.get_fulcio_url()),
358+
rekor=RekorClient(signing_config.get_tlog_urls()[0]),
358359
trusted_root=trust_config.trusted_root,
359360
tsa_clients=[
360-
TimestampAuthorityClient(tsa_url)
361-
for tsa_url in trust_config._inner.signing_config.tsa_urls
361+
TimestampAuthorityClient(url) for url in signing_config.get_tsa_urls()
362362
],
363363
)
364364

0 commit comments

Comments
 (0)