Skip to content

Commit d425770

Browse files
authored
Refactor client trust/trust root management (#1010)
* sigstore: refactor trust state management Signed-off-by: William Woodruff <william@trailofbits.com> * test: rename Signed-off-by: William Woodruff <william@trailofbits.com> * docstring Signed-off-by: William Woodruff <william@trailofbits.com> * sigstore, test: propagate rename Signed-off-by: William Woodruff <william@trailofbits.com> * _internal: hackety hack Signed-off-by: William Woodruff <william@trailofbits.com> * sigstore: refactor purpose handling Signed-off-by: William Woodruff <william@trailofbits.com> * trust: docstring Signed-off-by: William Woodruff <william@trailofbits.com> * fixup trust tests Signed-off-by: William Woodruff <william@trailofbits.com> * test_sign: fixup Signed-off-by: William Woodruff <william@trailofbits.com> * hook up client trust config Signed-off-by: William Woodruff <william@trailofbits.com> * README: update `--help` Signed-off-by: William Woodruff <william@trailofbits.com> * README: document BYO PKI Signed-off-by: William Woodruff <william@trailofbits.com> * sigstore: enforce client trust config media type Signed-off-by: William Woodruff <william@trailofbits.com> * fix type Signed-off-by: William Woodruff <william@trailofbits.com> * enforce media types, unit tests Signed-off-by: William Woodruff <william@trailofbits.com> * test: more trust tests Signed-off-by: William Woodruff <william@trailofbits.com> * CHANGELOG: record changes Signed-off-by: William Woodruff <william@trailofbits.com> * README: fix `--help` Signed-off-by: William Woodruff <william@trailofbits.com> --------- Signed-off-by: William Woodruff <william@trailofbits.com>
1 parent dbab104 commit d425770

File tree

15 files changed

+812
-221
lines changed

15 files changed

+812
-221
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ All versions prior to 0.9.0 are untracked.
2828
for representing in-toto statements and DSSE envelopes
2929
([#930](https://github.com/sigstore/sigstore-python/pull/930))
3030

31+
* CLI: The `--trust-config` flag has been added as a global option,
32+
enabling consistent "BYO PKI" uses of `sigstore` with a single flag
33+
([#1010](https://github.com/sigstore/sigstore-python/pull/1010))
34+
3135
* CLI: The `sigstore verify` subcommands can now verify bundles containing
3236
DSSE entries, such as those produced by
3337
[GitHub Artifact Attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
@@ -49,6 +53,11 @@ All versions prior to 0.9.0 are untracked.
4953
The public verification and policy APIs now raise
5054
`sigstore.errors.VerificationError` on failure.
5155

56+
* **BREAKING CLI CHANGE**: The `--rekor-url` and `--fulcio-url`
57+
flags have been entirely removed. To configure a custom PKI, use
58+
`--trust-config`
59+
([#1010](https://github.com/sigstore/sigstore-python/pull/1010))
60+
5261
### Changed
5362

5463
* **BREAKING API CHANGE**: `Verifier.verify(...)` now takes a `bytes | Hashed`

README.md

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ else!
2424
* [Verifying](#verifying)
2525
* [Generic identities](#generic-identities)
2626
* [Signatures from GitHub Actions](#signatures-from-github-actions)
27+
* [Advanced usage](#advanced-usage)
2728
* [Example uses](#example-uses)
2829
* [Signing with ambient credentials](#signing-with-ambient-credentials)
2930
* [Signing with an email identity](#signing-with-an-email-identity)
@@ -96,29 +97,26 @@ Top-level:
9697

9798
<!-- @begin-sigstore-help@ -->
9899
```
99-
usage: sigstore [-h] [-v] [-V] [--staging] [--rekor-url URL] COMMAND ...
100+
usage: sigstore [-h] [-v] [-V] [--staging | --trust-config FILE] COMMAND ...
100101

101102
a tool for signing and verifying Python package distributions
102103

103104
positional arguments:
104-
COMMAND the operation to perform
105-
sign sign one or more inputs
106-
verify verify one or more inputs
105+
COMMAND the operation to perform
106+
sign sign one or more inputs
107+
verify verify one or more inputs
107108
get-identity-token
108-
retrieve and return a Sigstore-compatible OpenID Connect
109-
token
109+
retrieve and return a Sigstore-compatible OpenID
110+
Connect token
110111

111112
optional arguments:
112-
-h, --help show this help message and exit
113-
-v, --verbose run with additional debug logging; supply multiple times
114-
to increase verbosity (default: 0)
115-
-V, --version show program's version number and exit
116-
117-
Sigstore instance options:
118-
--staging Use sigstore's staging instances, instead of the default
119-
production instances (default: False)
120-
--rekor-url URL The Rekor instance to use (conflicts with --staging)
121-
(default: https://rekor.sigstore.dev)
113+
-h, --help show this help message and exit
114+
-v, --verbose run with additional debug logging; supply multiple
115+
times to increase verbosity (default: 0)
116+
-V, --version show program's version number and exit
117+
--staging Use sigstore's staging instances, instead of the
118+
default production instances (default: False)
119+
--trust-config FILE The client trust configuration to use (default: None)
122120
```
123121
<!-- @end-sigstore-help@ -->
124122
@@ -132,8 +130,7 @@ usage: sigstore sign [-h] [-v] [--identity-token TOKEN] [--oidc-client-id ID]
132130
[--oidc-disable-ambient-providers] [--oidc-issuer URL]
133131
[--oauth-force-oob] [--no-default-files]
134132
[--signature FILE] [--certificate FILE] [--bundle FILE]
135-
[--output-directory DIR] [--overwrite] [--staging]
136-
[--rekor-url URL] [--fulcio-url URL]
133+
[--output-directory DIR] [--overwrite]
137134
FILE [FILE ...]
138135

139136
positional arguments:
@@ -178,18 +175,6 @@ Output options:
178175
(default: None)
179176
--overwrite Overwrite preexisting signature and certificate
180177
outputs, if present (default: False)
181-
182-
Sigstore instance options:
183-
--staging Use sigstore's staging instances, instead of the
184-
default production instances. This option will be
185-
deprecated in favor of the global `--staging` option
186-
in a future release. (default: False)
187-
--rekor-url URL The Rekor instance to use (conflicts with --staging).
188-
This option will be deprecated in favor of the global
189-
`--rekor-url` option in a future release. (default:
190-
None)
191-
--fulcio-url URL The Fulcio instance to use (conflicts with --staging)
192-
(default: https://fulcio.sigstore.dev)
193178
```
194179
<!-- @end-sigstore-sign-help@ -->
195180
@@ -207,7 +192,7 @@ to by a particular OIDC provider (like `https://github.com/login/oauth`).
207192
usage: sigstore verify identity [-h] [-v] [--certificate FILE]
208193
[--signature FILE] [--bundle FILE] [--offline]
209194
--cert-identity IDENTITY --cert-oidc-issuer
210-
URL [--staging] [--rekor-url URL]
195+
URL
211196
FILE [FILE ...]
212197

213198
optional arguments:
@@ -234,16 +219,6 @@ Verification options:
234219
--cert-oidc-issuer URL
235220
The OIDC issuer URL to check for in the certificate's
236221
OIDC issuer extension (default: None)
237-
238-
Sigstore instance options:
239-
--staging Use sigstore's staging instances, instead of the
240-
default production instances. This option will be
241-
deprecated in favor of the global `--staging` option
242-
in a future release. (default: False)
243-
--rekor-url URL The Rekor instance to use (conflicts with --staging).
244-
This option will be deprecated in favor of the global
245-
`--rekor-url` option in a future release. (default:
246-
None)
247222
```
248223
<!-- @end-sigstore-verify-identity-help@ -->
249224
@@ -260,7 +235,7 @@ usage: sigstore verify github [-h] [-v] [--certificate FILE]
260235
[--signature FILE] [--bundle FILE] [--offline]
261236
[--cert-identity IDENTITY] [--trigger EVENT]
262237
[--sha SHA] [--name NAME] [--repository REPO]
263-
[--ref REF] [--staging] [--rekor-url URL]
238+
[--ref REF]
264239
FILE [FILE ...]
265240

266241
optional arguments:
@@ -294,19 +269,32 @@ Verification options:
294269
under (default: None)
295270
--ref REF The `git` ref that the workflow was invoked with
296271
(default: None)
297-
298-
Sigstore instance options:
299-
--staging Use sigstore's staging instances, instead of the
300-
default production instances. This option will be
301-
deprecated in favor of the global `--staging` option
302-
in a future release. (default: False)
303-
--rekor-url URL The Rekor instance to use (conflicts with --staging).
304-
This option will be deprecated in favor of the global
305-
`--rekor-url` option in a future release. (default:
306-
None)
307272
```
308273
<!-- @end-sigstore-verify-github-help@ -->
309274
275+
## Advanced usage
276+
277+
### Configuring a custom root of trust ("BYO PKI")
278+
279+
Apart from the default and "staging" Sigstore instances, `sigstore` also
280+
supports "BYO PKI" setups, where a user maintains their own Sigstore
281+
instance services.
282+
283+
These are supported via the `--trust-config` flag, which accepts a
284+
JSON-formatted file conforming to the `ClientTrustConfig` message
285+
in the [Sigstore protobuf specs](https://github.com/sigstore/protobuf-specs).
286+
This file configures the entire Sigstore instance state, *including* the URIs
287+
used to access the CA and artifact transparency services as well as the
288+
cryptographic root of trust itself.
289+
290+
To use a custom client config, prepend `--trust-config` to any `sigstore`
291+
command:
292+
293+
```console
294+
$ sigstore --trust-config custom.trustconfig.json sign foo.txt
295+
$ sigstore --trust-config custom.trustconfig.json verify identity foo.txt ...
296+
```
297+
310298
## Example uses
311299

312300
`sigstore` supports a wide variety of workflows and usages. Some common ones are

sigstore/_cli.py

Lines changed: 20 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,15 @@
2727
from rich.logging import RichHandler
2828

2929
from sigstore import __version__, dsse
30-
from sigstore._internal.fulcio.client import (
31-
DEFAULT_FULCIO_URL,
32-
ExpiredCertificate,
33-
FulcioClient,
34-
)
30+
from sigstore._internal.fulcio.client import ExpiredCertificate
3531
from sigstore._internal.rekor import _hashedrekord_from_parts
36-
from sigstore._internal.rekor.client import (
37-
DEFAULT_REKOR_URL,
38-
RekorClient,
39-
)
40-
from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot
32+
from sigstore._internal.trust import ClientTrustConfig
4133
from sigstore._utils import sha256_digest
4234
from sigstore.errors import Error, VerificationError
4335
from sigstore.hashes import Hashed
4436
from sigstore.models import Bundle
4537
from sigstore.oidc import (
4638
DEFAULT_OAUTH_ISSUER_URL,
47-
STAGING_OAUTH_ISSUER_URL,
4839
ExpiredIdentity,
4940
IdentityToken,
5041
Issuer,
@@ -95,35 +86,6 @@ def _boolify_env(envvar: str) -> bool:
9586
raise ValueError(f"can't coerce '{val}' to a boolean")
9687

9788

98-
def _add_shared_instance_options(group: argparse._ArgumentGroup) -> None:
99-
"""
100-
Common Sigstore instance options, shared between all `sigstore` subcommands.
101-
"""
102-
group.add_argument(
103-
"--staging",
104-
dest="__deprecated_staging",
105-
action="store_true",
106-
default=False,
107-
help=(
108-
"Use sigstore's staging instances, instead of the default production instances. "
109-
"This option will be deprecated in favor of the global `--staging` option "
110-
"in a future release."
111-
),
112-
)
113-
group.add_argument(
114-
"--rekor-url",
115-
dest="__deprecated_rekor_url",
116-
metavar="URL",
117-
type=str,
118-
default=None,
119-
help=(
120-
"The Rekor instance to use (conflicts with --staging). "
121-
"This option will be deprecated in favor of the global `--rekor-url` option "
122-
"in a future release."
123-
),
124-
)
125-
126-
12789
def _add_shared_verify_input_options(group: argparse._ArgumentGroup) -> None:
12890
"""
12991
Common input options, shared between all `sigstore verify` subcommands.
@@ -230,21 +192,19 @@ def _parser() -> argparse.ArgumentParser:
230192
"-V", "--version", action="version", version=f"sigstore {__version__}"
231193
)
232194

233-
global_instance_options = parser.add_argument_group("Sigstore instance options")
195+
global_instance_options = parser.add_mutually_exclusive_group()
234196
global_instance_options.add_argument(
235197
"--staging",
236198
action="store_true",
237199
default=_boolify_env("SIGSTORE_STAGING"),
238200
help="Use sigstore's staging instances, instead of the default production instances",
239201
)
240202
global_instance_options.add_argument(
241-
"--rekor-url",
242-
metavar="URL",
243-
type=str,
244-
default=os.getenv("SIGSTORE_REKOR_URL", DEFAULT_REKOR_URL),
245-
help="The Rekor instance to use (conflicts with --staging)",
203+
"--trust-config",
204+
metavar="FILE",
205+
type=Path,
206+
help="The client trust configuration to use",
246207
)
247-
248208
subcommands = parser.add_subparsers(
249209
required=True,
250210
dest="subcommand",
@@ -324,16 +284,6 @@ def _parser() -> argparse.ArgumentParser:
324284
help="Overwrite preexisting signature and certificate outputs, if present",
325285
)
326286

327-
instance_options = sign.add_argument_group("Sigstore instance options")
328-
_add_shared_instance_options(instance_options)
329-
instance_options.add_argument(
330-
"--fulcio-url",
331-
metavar="URL",
332-
type=str,
333-
default=os.getenv("SIGSTORE_FULCIO_URL", DEFAULT_FULCIO_URL),
334-
help="The Fulcio instance to use (conflicts with --staging)",
335-
)
336-
337287
sign.add_argument(
338288
"files",
339289
metavar="FILE",
@@ -385,9 +335,6 @@ def _parser() -> argparse.ArgumentParser:
385335
required=True,
386336
)
387337

388-
instance_options = verify_identity.add_argument_group("Sigstore instance options")
389-
_add_shared_instance_options(instance_options)
390-
391338
# `sigstore verify github`
392339
verify_github = verify_subcommand.add_parser(
393340
"github",
@@ -449,9 +396,6 @@ def _parser() -> argparse.ArgumentParser:
449396
help="The `git` ref that the workflow was invoked with",
450397
)
451398

452-
instance_options = verify_github.add_argument_group("Sigstore instance options")
453-
_add_shared_instance_options(instance_options)
454-
455399
# `sigstore get-identity-token`
456400
get_identity_token = subcommands.add_parser(
457401
"get-identity-token",
@@ -476,22 +420,6 @@ def main() -> None:
476420

477421
_logger.debug(f"parsed arguments {args}")
478422

479-
# A few instance flags (like `--staging` and `--rekor-url`) are supported at both the
480-
# top-level `sigstore` level and the subcommand level (e.g. `sigstore verify --staging`),
481-
# but the former is preferred.
482-
if getattr(args, "__deprecated_staging", False):
483-
_logger.warning(
484-
"`--staging` should be used as a global option, rather than a subcommand option. "
485-
"Passing `--staging` as a subcommand option will be deprecated in a future release."
486-
)
487-
args.staging = args.__deprecated_staging
488-
if getattr(args, "__deprecated_rekor_url", None):
489-
_logger.warning(
490-
"`--rekor-url` should be used as a global option, rather than a subcommand option. "
491-
"Passing `--rekor-url` as a subcommand option will be deprecated in a future release."
492-
)
493-
args.rekor_url = args.__deprecated_rekor_url
494-
495423
# Stuff the parser back into our namespace, so that we can use it for
496424
# error handling later.
497425
args._parser = parser
@@ -594,18 +522,14 @@ def _sign(args: argparse.Namespace) -> None:
594522
if args.staging:
595523
_logger.debug("sign: staging instances requested")
596524
signing_ctx = SigningContext.staging()
597-
args.oidc_issuer = STAGING_OAUTH_ISSUER_URL
598-
elif args.fulcio_url == DEFAULT_FULCIO_URL and args.rekor_url == DEFAULT_REKOR_URL:
599-
signing_ctx = SigningContext.production()
525+
elif args.trust_config:
526+
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
527+
signing_ctx = SigningContext._from_trust_config(trust_config)
600528
else:
601-
# Assume "production" trust root if no keys are given as arguments
602-
trusted_root = TrustedRoot.production(purpose=KeyringPurpose.SIGN)
603-
604-
signing_ctx = SigningContext(
605-
fulcio=FulcioClient(args.fulcio_url),
606-
rekor=RekorClient(args.rekor_url),
607-
trusted_root=trusted_root,
608-
)
529+
# If the user didn't request the staging instance or pass in an
530+
# explicit client trust config, we're using the public good (i.e.
531+
# production) instance.
532+
signing_ctx = SigningContext.production()
609533

610534
# The order of precedence for identities is as follows:
611535
#
@@ -745,8 +669,8 @@ def _collect_verification_state(
745669
missing.append(str(cert))
746670
input_map[file] = {"cert": cert, "sig": sig}
747671
else:
748-
# If a user hasn't explicitly supplied `--signature`, `--certificate` or
749-
# `--rekor-bundle`, we expect a bundle either supplied via `--bundle` or with the
672+
# If a user hasn't explicitly supplied `--signature` or `--certificate`,
673+
# we expect a bundle either supplied via `--bundle` or with the
750674
# default `{input}.sigstore(.json)?` name.
751675
if not bundle.is_file():
752676
missing.append(str(bundle))
@@ -761,16 +685,11 @@ def _collect_verification_state(
761685
if args.staging:
762686
_logger.debug("verify: staging instances requested")
763687
verifier = Verifier.staging()
764-
elif args.rekor_url == DEFAULT_REKOR_URL:
765-
verifier = Verifier.production()
688+
elif args.trust_config:
689+
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
690+
verifier = Verifier._from_trust_config(trust_config)
766691
else:
767-
trusted_root = TrustedRoot.production(purpose=KeyringPurpose.VERIFY)
768-
verifier = Verifier(
769-
rekor=RekorClient(
770-
url=args.rekor_url,
771-
),
772-
trusted_root=trusted_root,
773-
)
692+
verifier = Verifier.production()
774693

775694
all_materials = []
776695
for file, inputs in input_map.items():

sigstore/_internal/rekor/checkpoint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
from pydantic import BaseModel, Field, StrictStr
2929

30-
from sigstore._internal.trustroot import RekorKeyring
30+
from sigstore._internal.trust import RekorKeyring
3131
from sigstore._utils import KeyID
3232
from sigstore.errors import VerificationError
3333

0 commit comments

Comments
 (0)