Skip to content

Commit 5909d95

Browse files
authored
feat: adding trust_config parameter for private sigstore instances (#460)
* feat: adding trust_config param for private sigstore instances Signed-off-by: SequeI <[email protected]> * fix: reword trust config example Signed-off-by: SequeI <[email protected]> --------- Signed-off-by: SequeI <[email protected]>
1 parent 8d54db8 commit 5909d95

File tree

7 files changed

+130
-16
lines changed

7 files changed

+130
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ All versions prior to 1.0.0 are untracked.
2828
- cli: Added support for `--ignore_unsigned_files` option
2929
- Implemented a new, minimal container image. This variant excludes optional dependencies (like OTel and PKCS#11) to reduce footprint, focusing solely on core signing and verification mechanisms.
3030
- The library now requires at least v4.0.0 of `sigstore` due to breaking changes in that library
31+
- Added support for signing and verifying using private Sigstore instances (`--trust_config`)
3132

3233
## [1.0.1] - 2024-04-18
3334

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,32 @@ This will open an OIDC flow to obtain a short lived token for the certificate.
117117
The identity used during signing and the provider must be reused during
118118
verification.
119119

120+
## Using Private Sigstore Instances
121+
122+
To use a private Sigstore setup (e.g. custom Rekor/Fulcio), use the `--trust_config` flag:
123+
124+
```bash
125+
[...]$ model_signing sign bert-base-uncased --trust_config client_trust_config.json
126+
```
127+
128+
For verification:
129+
130+
```bash
131+
[...]$ model_signing verify bert-base-uncased \
132+
--signature model.sig \
133+
--trust_config client_trust_config.json
134+
--identity "$identity"
135+
--identity_provider "$oidc_provider"
136+
```
137+
138+
The `client_trust_config.json` file should include:
139+
140+
- A signed target trust root
141+
- A `signingConfig` section with your private Rekor, Fulcio, and CT log endpoints
142+
- Public keys for verification (if applicable)
143+
144+
You can find an example `client_trust_config.json` that references the public Sigstore production services in the Sigstore Python repository [here](https://github.com/sigstore/sigstore-python/blob/main/test/assets/trust_config/config.v1.json).
145+
120146
As another example, here is how we can sign with private keys. First, we
121147
generate the key pair:
122148

src/model_signing/_cli.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ def set_attribute(self, key, value):
6868
help="Location of the signature file to verify.",
6969
)
7070

71+
# Decorator for the commonly used option for the custom trust configuration.
72+
_trust_config_option = click.option(
73+
"--trust_config",
74+
type=pathlib.Path,
75+
metavar="TRUST_CONFIG_PATH",
76+
help="The client trust configuration to use",
77+
)
78+
7179
# Decorator for the commonly used option to ignore certain paths
7280
_ignore_paths_option = click.option(
7381
"--ignore-paths",
@@ -278,6 +286,7 @@ def _sign() -> None:
278286
@_allow_symlinks_option
279287
@_write_signature_option
280288
@_sigstore_staging_option
289+
@_trust_config_option
281290
@click.option(
282291
"--use_ambient_credentials",
283292
type=bool,
@@ -326,6 +335,7 @@ def _sign_sigstore(
326335
identity_token: Optional[str] = None,
327336
client_id: Optional[str] = None,
328337
client_secret: Optional[str] = None,
338+
trust_config: Optional[pathlib.Path] = None,
329339
) -> None:
330340
"""Sign using Sigstore (DEFAULT signing method).
331341
@@ -342,6 +352,19 @@ def _sign_sigstore(
342352
Sigstore allows users to use a staging instance for test-only signatures.
343353
Passing the `--use_staging` flag would use that instance instead of the
344354
production one.
355+
356+
Additionally, you can specify a custom trust configuration JSON file using
357+
the `--trust_config` flag. This allows you to fully customize the PKI
358+
(Private Key Infrastructure) used in the signing process. By providing a
359+
`--trust_config`, you can define your own transparency logs, certificate
360+
authorities, and other trust settings, enabling full control over the
361+
trust model, including which PKI to use for signature verification.
362+
363+
If `--trust_config` is not provided, the default Sigstore instance is
364+
used, which is pre-configured with Sigstore’s own trusted transparency
365+
logs and certificate authorities. This provides a ready-to-use default
366+
trust model for most use cases but may not be suitable for custom or
367+
highly regulated environments.
345368
"""
346369
with tracer.start_as_current_span("Sign") as span:
347370
span.set_attribute("sigstore.sign_method", "sigstore")
@@ -362,6 +385,7 @@ def _sign_sigstore(
362385
force_oob=oauth_force_oob,
363386
client_id=client_id,
364387
client_secret=client_secret,
388+
trust_config=trust_config,
365389
).set_hashing_config(
366390
model_signing.hashing.Config()
367391
.set_ignored_paths(
@@ -599,6 +623,12 @@ def _verify() -> None:
599623
signature is assumed to be generated via Sigstore (as if invoking `sigstore`
600624
subcommand).
601625
626+
To enable verification with custom PKI configurations, use the
627+
`--trust_config` option. This allows you to specify your own set of trusted
628+
public keys, transparency logs, and certificate authorities for verifying
629+
the signature. If not provided, the default Sigstore instance and its
630+
associated public keys, logs, and authorities are used.
631+
602632
Use each subcommand's `--help` option for details on each mode.
603633
"""
604634

@@ -610,6 +640,7 @@ def _verify() -> None:
610640
@_ignore_git_paths_option
611641
@_allow_symlinks_option
612642
@_sigstore_staging_option
643+
@_trust_config_option
613644
@click.option(
614645
"--identity",
615646
type=str,
@@ -635,6 +666,7 @@ def _verify_sigstore(
635666
identity_provider: str,
636667
use_staging: bool,
637668
ignore_unsigned_files: bool,
669+
trust_config: Optional[pathlib.Path] = None,
638670
) -> None:
639671
"""Verify using Sigstore (DEFAULT verification method).
640672
@@ -661,6 +693,7 @@ def _verify_sigstore(
661693
identity=identity,
662694
oidc_issuer=identity_provider,
663695
use_staging=use_staging,
696+
trust_config=trust_config,
664697
).set_hashing_config(
665698
model_signing.hashing.Config()
666699
.set_ignored_paths(

src/model_signing/_signing/sign_sigstore.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def __init__(
7373
force_oob: bool = False,
7474
client_id: Optional[str] = None,
7575
client_secret: Optional[str] = None,
76+
trust_config: Optional[pathlib.Path] = None,
7677
):
7778
"""Initializes Sigstore signers.
7879
@@ -107,9 +108,18 @@ def __init__(
107108
identity to the OIDC provider. If not provided, it is assumed
108109
that the client is public or the provider does not require a
109110
secret.
111+
trust_config: A path to a custom trust configuration. When
112+
provided, the signature verification process will rely on the
113+
supplied PKI and trust configurations, instead of the default
114+
Sigstore setup. If not specified, the default Sigstore
115+
configuration is used.
110116
"""
111117
if use_staging:
112118
trust_config = sigstore_models.ClientTrustConfig.staging()
119+
elif trust_config:
120+
trust_config = sigstore_models.ClientTrustConfig.from_json(
121+
trust_config.read_text()
122+
)
113123
else:
114124
trust_config = sigstore_models.ClientTrustConfig.production()
115125

@@ -135,11 +145,13 @@ def _get_identity_token(self) -> sigstore_oidc.IdentityToken:
135145
3) Interactive OAuth flow
136146
"""
137147
if self._identity_token:
138-
return sigstore_oidc.IdentityToken(self._identity_token)
148+
return sigstore_oidc.IdentityToken(
149+
self._identity_token, self._client_id
150+
)
139151
if self._use_ambient_credentials:
140-
token = sigstore_oidc.detect_credential()
152+
token = sigstore_oidc.detect_credential(self._client_id)
141153
if token:
142-
return sigstore_oidc.IdentityToken(token)
154+
return sigstore_oidc.IdentityToken(token, self._client_id)
143155

144156
return self._issuer.identity_token(
145157
force_oob=self._force_oob,
@@ -168,7 +180,12 @@ class Verifier(signing.Verifier):
168180
"""Signature verification using Sigstore."""
169181

170182
def __init__(
171-
self, *, identity: str, oidc_issuer: str, use_staging: bool = False
183+
self,
184+
*,
185+
identity: str,
186+
oidc_issuer: str,
187+
use_staging: bool = False,
188+
trust_config: Optional[pathlib.Path] = None,
172189
):
173190
"""Initializes Sigstore verifiers.
174191
@@ -182,11 +199,24 @@ def __init__(
182199
certificate used for the signature.
183200
use_staging: Use staging configurations, instead of production. This
184201
is supposed to be set to True only when testing. Default is False.
202+
trust_config: A path to a custom trust configuration. When provided,
203+
the signature verification process will rely on the supplied
204+
PKI and trust configurations, instead of the default Sigstore
205+
setup. If not specified, the default Sigstore configuration
206+
is used.
185207
"""
186-
if use_staging:
187-
self._verifier = sigstore_verifier.Verifier.staging()
208+
if trust_config:
209+
trust_config = sigstore_models.ClientTrustConfig.from_json(
210+
trust_config.read_text()
211+
)
212+
elif use_staging:
213+
trust_config = sigstore_models.ClientTrustConfig.staging()
188214
else:
189-
self._verifier = sigstore_verifier.Verifier.production()
215+
trust_config = sigstore_models.ClientTrustConfig.production()
216+
217+
self._verifier = sigstore_verifier.Verifier(
218+
trusted_root=trust_config.trusted_root
219+
)
190220

191221
self._policy = sigstore_verifier.policy.Identity(
192222
identity=identity, issuer=oidc_issuer

src/model_signing/signing.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,13 @@ def sign(model_path: hashing.PathLike, signature_path: hashing.PathLike):
7878
class Config:
7979
"""Configuration to use when signing models.
8080
81-
Currently we support signing with Sigstore (public instance and staging
82-
instance), signing with private keys and signing with signing certificates.
83-
Other signing modes can be added in the future.
81+
Currently, we support signing with Sigstore (both the public
82+
instance and staging instance), signing with private keys,
83+
signing with signing certificates, and signing with custom
84+
PKI configurations using the `--trust_config` option.
85+
This allows users to bring their own trust configuration
86+
to sign and verify models. Other signing modes may be
87+
added in the future.
8488
"""
8589

8690
def __init__(self):
@@ -127,6 +131,7 @@ def use_sigstore_signer(
127131
identity_token: Optional[str] = None,
128132
client_id: Optional[str] = None,
129133
client_secret: Optional[str] = None,
134+
trust_config: Optional[pathlib.Path] = None,
130135
) -> Self:
131136
"""Configures the signing to be performed with Sigstore.
132137
@@ -161,6 +166,11 @@ def use_sigstore_signer(
161166
identity to the OIDC provider. If not provided, it is assumed
162167
that the client is public or the provider does not require a
163168
secret.
169+
trust_config: A path to a custom trust configuration. When provided,
170+
the signature verification process will rely on the supplied
171+
PKI and trust configurations, instead of the default Sigstore
172+
setup. If not specified, the default Sigstore configuration
173+
is used.
164174
165175
Return:
166176
The new signing configuration.
@@ -173,6 +183,7 @@ def use_sigstore_signer(
173183
force_oob=force_oob,
174184
client_id=client_id,
175185
client_secret=client_secret,
186+
trust_config=trust_config,
176187
)
177188
return self
178189

src/model_signing/verifying.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from collections.abc import Iterable
4141
import pathlib
4242
import sys
43+
from typing import Optional
4344

4445
from model_signing import hashing
4546
from model_signing import manifest
@@ -210,7 +211,12 @@ def _guess_hashing_config(self, source_manifest: manifest.Manifest) -> None:
210211
raise ValueError("Cannot guess the hashing configuration")
211212

212213
def use_sigstore_verifier(
213-
self, *, identity: str, oidc_issuer: str, use_staging: bool = False
214+
self,
215+
*,
216+
identity: str,
217+
oidc_issuer: str,
218+
use_staging: bool = False,
219+
trust_config: Optional[pathlib.Path] = None,
214220
) -> Self:
215221
"""Configures the verification of signatures produced by Sigstore.
216222
@@ -224,13 +230,21 @@ def use_sigstore_verifier(
224230
certificate used for the signature.
225231
use_staging: Use staging configurations, instead of production. This
226232
is supposed to be set to True only when testing. Default is False.
233+
trust_config: A path to a custom trust configuration. When provided,
234+
the signature verification process will rely on the supplied
235+
PKI and trust configurations, instead of the default Sigstore
236+
setup. If not specified, the default Sigstore configuration
237+
is used.
227238
228239
Return:
229240
The new verification configuration.
230241
"""
231242
self._uses_sigstore = True
232243
self._verifier = sigstore.Verifier(
233-
identity=identity, oidc_issuer=oidc_issuer, use_staging=use_staging
244+
identity=identity,
245+
oidc_issuer=oidc_issuer,
246+
use_staging=use_staging,
247+
trust_config=trust_config,
234248
)
235249
return self
236250

tests/_signing/sigstore_test.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def mocked_oidc_provider():
7171

7272
# return whatever raw_token was passed in
7373
mocked_identity_token = mocked_objects["IdentityToken"]
74-
mocked_identity_token.side_effect = lambda x: x
74+
mocked_identity_token.side_effect = lambda token, client_id: token
7575

7676
mocked_issuer = mocked_objects["Issuer"]
7777
mocked_issuer.return_value.identity_token.return_value = "fake_token"
@@ -149,8 +149,7 @@ def mocked_sigstore_verifier():
149149
sigstore.sigstore_verifier, "Verifier", autospec=True
150150
) as mocked_verifier:
151151
mocked_verifier.verify_dsse = _mocked_verify_dsse
152-
mocked_verifier.staging = lambda: mocked_verifier
153-
mocked_verifier.production = lambda: mocked_verifier
152+
mocked_verifier.return_value = mocked_verifier
154153
yield mocked_verifier
155154

156155

@@ -163,7 +162,7 @@ def _verify_dsse(bundle, policy):
163162
sigstore.sigstore_verifier, "Verifier", autospec=True
164163
) as mocked_verifier:
165164
mocked_verifier.verify_dsse = _verify_dsse
166-
mocked_verifier.staging = lambda: mocked_verifier
165+
mocked_verifier.return_value = mocked_verifier
167166
yield mocked_verifier
168167

169168

0 commit comments

Comments
 (0)