Skip to content

Commit 5ef5b23

Browse files
authored
_cli: add sigstore verify identity (#379)
* _cli: add `sigstore verify identity` This adds `sigstore verify identity`, which is aliased (through some `argparse` hackery) back to `sigstore verify`. This allows us to remain compatible with the existing CLI, while also giving us the flexibility we need to extend verification (e.g. `sigstore verify github`). Signed-off-by: William Woodruff <[email protected]> * _cli: help text for `sigstore verify identity` Signed-off-by: William Woodruff <[email protected]> * _cli, Makefile, README: fix `--help` generation Signed-off-by: William Woodruff <[email protected]> * README: typo Signed-off-by: William Woodruff <[email protected]> * ci: run check-readme against min Python version Signed-off-by: William Woodruff <[email protected]> * Makefile: fix `check-readme` target Signed-off-by: William Woodruff <[email protected]> * README: render `--help` with min Python version Signed-off-by: William Woodruff <[email protected]> * _cli: fix `sigstore verify` behavior Signed-off-by: William Woodruff <[email protected]> * _cli: hackety hack Signed-off-by: William Woodruff <[email protected]> * _cli: hackety hack Signed-off-by: William Woodruff <[email protected]> * README: update docs, examples Signed-off-by: William Woodruff <[email protected]> * _cli: add warning to bare `sigstore verify` Signed-off-by: William Woodruff <[email protected]> Signed-off-by: William Woodruff <[email protected]> Signed-off-by: William Woodruff <[email protected]>
1 parent eea4422 commit 5ef5b23

File tree

4 files changed

+104
-52
lines changed

4 files changed

+104
-52
lines changed

.github/workflows/ci.yml

+10-1
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,31 @@ jobs:
5757
runs-on: ubuntu-latest
5858
steps:
5959
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
60+
61+
# NOTE: We intentionally lint against our minimum supported Python.
6062
- uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912
6163
with:
6264
python-version: "3.7"
65+
6366
- name: deps
6467
run: make dev SIGSTORE_EXTRA=lint
68+
6569
- name: lint
6670
run: make lint
6771

6872
check-readme:
6973
runs-on: ubuntu-latest
7074
steps:
7175
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
76+
77+
# NOTE: We intentional check `--help` rendering against our minimum Python,
78+
# since it changes slightly between Python versions.
7279
- uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912
7380
with:
74-
python-version: "3.x"
81+
python-version: "3.7"
82+
7583
- name: deps
7684
run: make dev
85+
7786
- name: check-readme
7887
run: make check-readme

Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ check-readme:
117117
$(MAKE) -s run ARGS="sign --help" \
118118
)
119119

120-
# sigstore verify --help
120+
# sigstore verify identity --help
121121
@diff \
122122
<( \
123-
awk '/@begin-sigstore-verify-help@/{f=1;next} /@end-sigstore-verify-help@/{f=0} f' \
123+
awk '/@begin-sigstore-verify-identity-help@/{f=1;next} /@end-sigstore-verify-identity-help@/{f=0} f' \
124124
< README.md | sed '1d;$$d' \
125125
) \
126126
<( \
127-
$(MAKE) -s run ARGS="verify --help" \
127+
$(MAKE) -s run ARGS="verify identity --help" \
128128
)
129129

130130

README.md

+36-43
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,16 @@ a tool for signing and verifying Python package distributions
7878
positional arguments:
7979
{sign,verify,get-identity-token}
8080

81-
options:
81+
optional arguments:
8282
-h, --help show this help message and exit
8383
-V, --version show program's version number and exit
8484
-v, --verbose run with additional debug logging; supply multiple
8585
times to increase verbosity (default: 0)
8686
```
8787
<!-- @end-sigstore-help@ -->
8888
89-
Signing:
89+
90+
### Signing
9091
9192
<!-- @begin-sigstore-sign-help@ -->
9293
```
@@ -102,7 +103,7 @@ usage: sigstore sign [-h] [--identity-token TOKEN] [--oidc-client-id ID]
102103
positional arguments:
103104
FILE The file to sign
104105

105-
options:
106+
optional arguments:
106107
-h, --help show this help message and exit
107108

108109
OpenID Connect options:
@@ -150,22 +151,30 @@ Sigstore instance options:
150151
```
151152
<!-- @end-sigstore-sign-help@ -->
152153
153-
Verifying:
154+
### Verifying
155+
156+
#### Identities
154157
155-
<!-- @begin-sigstore-verify-help@ -->
158+
This is the most common verification done with `sigstore`, and therefore
159+
the one you probably want: you can use it to verify that a signature was
160+
produced by a particular identity (like `[email protected]`), as attested
161+
to by a particular OIDC provider (like `https://github.com/login/oauth`).
162+
163+
<!-- @begin-sigstore-verify-identity-help@ -->
156164
```
157-
usage: sigstore verify [-h] [--certificate FILE] [--signature FILE]
158-
[--rekor-bundle FILE] [--certificate-chain FILE]
159-
[--cert-email EMAIL] --cert-identity IDENTITY
160-
--cert-oidc-issuer URL [--require-rekor-offline]
161-
[--staging] [--rekor-url URL]
162-
[--rekor-root-pubkey FILE]
163-
FILE [FILE ...]
165+
usage: sigstore verify identity [-h] [--certificate FILE] [--signature FILE]
166+
[--rekor-bundle FILE]
167+
[--certificate-chain FILE]
168+
[--cert-email EMAIL] --cert-identity IDENTITY
169+
--cert-oidc-issuer URL
170+
[--require-rekor-offline] [--staging]
171+
[--rekor-url URL] [--rekor-root-pubkey FILE]
172+
FILE [FILE ...]
164173

165174
positional arguments:
166175
FILE The file to verify
167176

168-
options:
177+
optional arguments:
169178
-h, --help show this help message and exit
170179

171180
Verification inputs:
@@ -203,7 +212,11 @@ Sigstore instance options:
203212
A PEM-encoded root public key for Rekor itself
204213
(conflicts with --staging) (default: None)
205214
```
206-
<!-- @end-sigstore-verify-help@ -->
215+
<!-- @end-sigstore-verify-identity-help@ -->
216+
217+
For backwards compatibility, `sigstore verify [args ...]` is equivalent to
218+
`sigstore verify identity [args ...]`, but the latter form is **strongly**
219+
preferred.
207220
208221
## Example uses
209222
@@ -270,51 +283,31 @@ same directory as the file being verified:
270283

271284
```console
272285
# looks for foo.txt.sig and foo.txt.crt
273-
$ python -m sigstore verify foo.txt
286+
$ python -m sigstore verify identity foo.txt \
287+
--cert-identity '[email protected]' \
288+
--cert-oidc-issuer 'https://github.com/login/oauth'
274289
```
275290

276291
Multiple files can be verified at once:
277292

278293
```console
279294
# looks for {foo,bar}.txt.{sig,crt}
280-
$ python -m sigstore verify foo.txt bar.txt
295+
$ python -m sigstore verify identity foo.txt bar.txt \
296+
--cert-identity '[email protected]' \
297+
--cert-oidc-issuer 'https://github.com/login/oauth'
281298
```
282299

283300
If your signature and certificate are at different paths, you can specify them
284301
explicitly (but only for one file at a time):
285302

286303
```console
287-
$ python -m sigstore verify \
304+
$ python -m sigstore verify identity foo.txt \
288305
--certificate some/other/path/foo.crt \
289306
--signature some/other/path/foo.sig \
290-
foo.txt
291-
```
292-
293-
### Extended verification against OpenID Connect claims
294-
295-
By default, `sigstore verify` only checks the validity of the certificate,
296-
the correctness of the signature, and the consistency of both with the
297-
certificate transparency log.
298-
299-
To assert further details about the signature (such as *who* or *what* signed for the artifact),
300-
you can test against the OpenID Connect claims embedded within it.
301-
302-
For example, to accept the signature and certificate only if they correspond to a particular
303-
email identity:
304-
305-
```console
306-
$ python -m sigstore verify --cert-email [email protected] foo.txt
307-
```
308-
309-
Or to accept only if the OpenID Connect issuer is the expected one:
310-
311-
```console
312-
$ python -m sigstore verify --cert-oidc-issuer https://github.com/login/oauth foo.txt
307+
--cert-identity '[email protected]' \
308+
--cert-oidc-issuer 'https://github.com/login/oauth'
313309
```
314310

315-
These options can be combined, and further extended validation options (e.g., for
316-
signing results from GitHub Actions) are under development.
317-
318311
## Licensing
319312

320313
`sigstore` is licensed under the Apache 2.0 License.

sigstore/_cli.py

+55-5
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,42 @@ def _boolify_env(envvar: str) -> bool:
8282
raise ValueError(f"can't coerce '{val}' to a boolean")
8383

8484

85+
def _set_default_verify_subparser(parser: argparse.ArgumentParser, name: str) -> None:
86+
"""
87+
An argparse patch for configuring a default subparser for `sigstore verify`.
88+
89+
Adapted from <https://stackoverflow.com/a/26379693>
90+
"""
91+
subparser_found = False
92+
for arg in sys.argv[1:]:
93+
if arg in ["-h", "--help"]: # global help if no subparser
94+
break
95+
else:
96+
for x in parser._subparsers._actions: # type: ignore[union-attr]
97+
if not isinstance(x, argparse._SubParsersAction):
98+
continue
99+
for sp_name in x._name_parser_map.keys():
100+
if sp_name in sys.argv[1:]:
101+
subparser_found = True
102+
if not subparser_found:
103+
try:
104+
# If `sigstore verify identity` wasn't passed explicitly, we need
105+
# to insert the `identity` subcommand into the correct position
106+
# within `sys.argv`. To do that, we get the index of the `verify`
107+
# subcommand, and insert it directly after it.
108+
verify_idx = sys.argv.index("verify")
109+
sys.argv.insert(verify_idx + 1, name)
110+
logger.warning(
111+
"`sigstore verify` without a subcommand will be treated as "
112+
"`sigstore verify identity`, but this behavior will be deprecated "
113+
"in a future release"
114+
)
115+
except ValueError:
116+
# This happens when we invoke `sigstore sign`, since there's no
117+
# `verify` subcommand to insert under. We do nothing in this case.
118+
pass
119+
120+
85121
def _add_shared_instance_options(group: argparse._ArgumentGroup) -> None:
86122
group.add_argument(
87123
"--staging",
@@ -243,10 +279,18 @@ def _parser() -> argparse.ArgumentParser:
243279

244280
# `sigstore verify`
245281
verify = subcommands.add_parser(
246-
"verify", formatter_class=argparse.ArgumentDefaultsHelpFormatter
282+
"verify",
283+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
247284
)
285+
verify_subcommand = verify.add_subparsers(dest="verify_subcommand")
248286

249-
input_options = verify.add_argument_group("Verification inputs")
287+
# `sigstore verify identity`
288+
verify_identity = verify_subcommand.add_parser(
289+
"identity",
290+
help="verify against a known identity and identity provider",
291+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
292+
)
293+
input_options = verify_identity.add_argument_group("Verification inputs")
250294
input_options.add_argument(
251295
"--certificate",
252296
"--cert",
@@ -270,7 +314,9 @@ def _parser() -> argparse.ArgumentParser:
270314
help="The offline Rekor bundle to verify with; not used with multiple inputs",
271315
)
272316

273-
verification_options = verify.add_argument_group("Extended verification options")
317+
verification_options = verify_identity.add_argument_group(
318+
"Extended verification options"
319+
)
274320
verification_options.add_argument(
275321
"--certificate-chain",
276322
metavar="FILE",
@@ -309,17 +355,21 @@ def _parser() -> argparse.ArgumentParser:
309355
help="Require offline Rekor verification with a bundle; implied by --rekor-bundle",
310356
)
311357

312-
instance_options = verify.add_argument_group("Sigstore instance options")
358+
instance_options = verify_identity.add_argument_group("Sigstore instance options")
313359
_add_shared_instance_options(instance_options)
314360

315-
verify.add_argument(
361+
verify_identity.add_argument(
316362
"files",
317363
metavar="FILE",
318364
type=Path,
319365
nargs="+",
320366
help="The file to verify",
321367
)
322368

369+
# `sigstore verify` defaults to `sigstore verify identity`, for backwards
370+
# compatibility.
371+
_set_default_verify_subparser(verify, "identity")
372+
323373
# `sigstore get-identity-token`
324374
get_identity_token = subcommands.add_parser("get-identity-token")
325375
_add_shared_oidc_options(get_identity_token)

0 commit comments

Comments
 (0)