Skip to content

Commit 1d1e238

Browse files
authored
_cli: Support local files in verify pypi subcommand (#98)
Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com>
1 parent 732f7ee commit 1d1e238

File tree

6 files changed

+168
-75
lines changed

6 files changed

+168
-75
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
specified with a `pypi:` prefix followed by the filename, e.g:
2424
`pypi:sampleproject-1.0.0.tar.gz`. The old way (passing
2525
the direct URL) is still supported.
26+
- The CLI subcommand `verify pypi` now supports passing the local paths
27+
to the artifact and its provenance file, allowing the user to verify
28+
files already downloaded from PyPI. The artifact path is passed as
29+
usual, whereas the provenance file path is passed using the
30+
`--provenance-file` option.
2631

2732
## [0.0.21]
2833

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,23 @@ pypi-attestations verify attestation \
142142
143143
### Verifying a PyPI package
144144
> [!NOTE]
145-
> The package to verify can be passed either as a `pypi:` prefixed filename (e.g:
146-
> 'pypi:sampleproject-1.0.0-py3-none-any.whl'), or as a direct URL
147-
> to the artifact hosted by PyPI.
145+
> The package to verify can be passed either as a path to a local file, a
146+
> `pypi:` prefixed filename (e.g: 'pypi:sampleproject-1.0.0-py3-none-any.whl'),
147+
> or as a direct URL to the artifact hosted by PyPI.
148+
148149
```bash
150+
# Download the artifact (and its provenance) from PyPI and verify it
149151
pypi-attestations verify pypi --repository https://github.com/sigstore/sigstore-python \
150152
pypi:sigstore-3.6.1-py3-none-any.whl
151153
152-
# or alternatively:
154+
# or alternatively, using the direct URL:
153155
pypi-attestations verify pypi --repository https://github.com/sigstore/sigstore-python \
154156
https://files.pythonhosted.org/packages/70/f5/324edb6a802438e97e289992a41f81bb7a58a1cda2e49439e7e48896649e/sigstore-3.6.1-py3-none-any.whl
157+
158+
# Verify the artifact and its provenance using local files
159+
pypi-attestations verify pypi --repository https://github.com/sigstore/sigstore-python \
160+
--provenance-file ~/Downloads/sigstore-3.6.1-py3-none-any.whl.provenance \
161+
~/Downloads/sigstore-3.6.1-py3-none-any.whl
155162
```
156163
157164
This command downloads the artifact and its provenance from PyPI. The artifact

src/pypi_attestations/_cli.py

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ def _parser() -> argparse.ArgumentParser:
157157
help="Use the staging environment",
158158
)
159159

160+
verify_pypi_command.add_argument(
161+
"--provenance-file",
162+
type=Path,
163+
help="Provide the provenance file instead of downloading it from PyPI",
164+
)
165+
160166
inspect_command = subcommands.add_parser(
161167
name="inspect",
162168
help="Inspect one or more inputs",
@@ -233,9 +239,33 @@ def _download_file(url: str, dest: Path) -> None:
233239
_die(f"Error downloading file: {e}")
234240

235241

236-
def _get_direct_url_from_arg(arg: str) -> URIReference:
242+
def _get_distribution_from_arg(arg: str) -> Distribution:
237243
"""Parse the artifact argument for the `verify pypi` subcommand.
238244
245+
The argument can be:
246+
- A pypi: prefixed filename (e.g. pypi:sampleproject-1.0.0.tar.gz)
247+
- A direct URL to a PyPI-hosted artifact
248+
- A path to a local file
249+
"""
250+
if arg.startswith("pypi:") or arg.startswith("https://"):
251+
pypi_url = _get_direct_url_from_arg(arg)
252+
dist_filename = pypi_url.path.split("/")[-1]
253+
with TemporaryDirectory() as temp_dir:
254+
dist_path = Path(temp_dir) / dist_filename
255+
_download_file(url=pypi_url.unsplit(), dest=dist_path)
256+
dist = Distribution.from_file(dist_path)
257+
else:
258+
dist_path = Path(arg)
259+
if not dist_path.exists():
260+
_die(f"File does not exist: {dist_path}")
261+
dist = Distribution.from_file(dist_path)
262+
263+
return dist
264+
265+
266+
def _get_direct_url_from_arg(arg: str) -> URIReference:
267+
"""Get the URL from the artifact argument for the `verify pypi` subcommand.
268+
239269
The argument can be:
240270
- A pypi: prefixed filename (e.g. pypi:sampleproject-1.0.0.tar.gz)
241271
- A direct URL to a PyPI-hosted artifact
@@ -288,17 +318,14 @@ def _get_direct_url_from_arg(arg: str) -> URIReference:
288318
return pypi_url
289319

290320

291-
def _get_provenance_from_pypi(filename: str) -> Provenance:
321+
def _get_provenance_from_pypi(dist: Distribution) -> Provenance:
292322
"""Use PyPI's integrity API to get a distribution's provenance."""
293-
try:
294-
if filename.endswith(".tar.gz") or filename.endswith(".zip"):
295-
name, version = parse_sdist_filename(filename)
296-
elif filename.endswith(".whl"):
297-
name, version, _, _ = parse_wheel_filename(filename)
298-
else:
299-
_die("URL should point to a wheel (*.whl) or a source distribution (*.zip or *.tar.gz)")
300-
except (InvalidSdistFilename, InvalidWheelFilename) as e:
301-
_die(f"Invalid distribution filename: {e}")
323+
filename = dist.name
324+
# Filename is already validated when creating the Distribution object
325+
if filename.endswith(".tar.gz") or filename.endswith(".zip"):
326+
name, version = parse_sdist_filename(filename)
327+
else:
328+
name, version, _, _ = parse_wheel_filename(filename)
302329

303330
provenance_url = f"https://pypi.org/integrity/{name}/{version}/{filename}/provenance"
304331
response = requests.get(provenance_url)
@@ -480,31 +507,34 @@ def _verify_attestation(args: argparse.Namespace) -> None:
480507
def _verify_pypi(args: argparse.Namespace) -> None:
481508
"""Verify a distribution hosted on PyPI.
482509
483-
The distribution is downloaded and verified. The verification is against
484-
the provenance file hosted on PyPI (if any), and against the repository URL
485-
passed by the user as a CLI argument.
510+
The distribution is downloaded (if needed) and verified. The verification is against
511+
the provenance file (passed using the `--provenance-file` option, or downloaded
512+
from PyPI if not provided), and against the repository URL passed by the user
513+
as a CLI argument.
486514
"""
487-
pypi_url = _get_direct_url_from_arg(args.distribution_file)
515+
dist = _get_distribution_from_arg(args.distribution_file)
488516

489-
with TemporaryDirectory() as temp_dir:
490-
dist_filename = pypi_url.path.split("/")[-1]
491-
dist_path = Path(temp_dir) / dist_filename
492-
_download_file(url=pypi_url.unsplit(), dest=dist_path)
493-
provenance = _get_provenance_from_pypi(dist_filename)
494-
dist = Distribution.from_file(dist_path)
517+
if args.provenance_file is None:
518+
provenance = _get_provenance_from_pypi(dist)
519+
else:
520+
if not args.provenance_file.exists():
521+
_die(f"Provenance file does not exist: {args.provenance_file}")
495522
try:
496-
for attestation_bundle in provenance.attestation_bundles:
497-
publisher = attestation_bundle.publisher
498-
_check_repository_identity(
499-
expected_repository_url=args.repository, publisher=publisher
500-
)
501-
policy = publisher._as_policy() # noqa: SLF001.
502-
for attestation in attestation_bundle.attestations:
503-
attestation.verify(policy, dist, staging=args.staging)
504-
except VerificationError as verification_error:
505-
_die(f"Verification failed for {dist_filename}: {verification_error}")
506-
507-
_logger.info(f"OK: {dist_filename}")
523+
provenance = Provenance.model_validate_json(args.provenance_file.read_bytes())
524+
except ValidationError as validation_error:
525+
_die(f"Invalid provenance: {validation_error}")
526+
527+
try:
528+
for attestation_bundle in provenance.attestation_bundles:
529+
publisher = attestation_bundle.publisher
530+
_check_repository_identity(expected_repository_url=args.repository, publisher=publisher)
531+
policy = publisher._as_policy() # noqa: SLF001.
532+
for attestation in attestation_bundle.attestations:
533+
attestation.verify(policy, dist, staging=args.staging)
534+
except VerificationError as verification_error:
535+
_die(f"Verification failed for {dist.name}: {verification_error}")
536+
537+
_logger.info(f"OK: {dist.name}")
508538

509539

510540
def main() -> None:

test/assets/sigstore-3.6.1.tar.gz

78.5 KB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"attestation_bundles":[{"attestations":[{"envelope":{"signature":"MEUCIQDMIOMtnfp8Sh5OmmuWUjteQueY9w0weYye1542/61bCgIgay9OlFBHW7ykJP7/Cnitk59eNDAPcvK9+unlCaceKwo=","statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoic2lnc3RvcmUtMy42LjEudGFyLmd6IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImVlNjBmZGM5MjM2ZmQ2NzA5MjcxYWQ1M2I0NDAyNzQ2MTM2MGMzZmRlMTU1ZDJhZjE1NDgyZTRjNDUxZmY4NjUifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9kb2NzLnB5cGkub3JnL2F0dGVzdGF0aW9ucy9wdWJsaXNoL3YxIiwicHJlZGljYXRlIjpudWxsfQ=="},"verification_material":{"certificate":"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","transparency_entries":[{"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMjM4OWMxOGMxMmY2YmYxOTQyNmM5YTZmNzEwNDUzZjMwNTAyNzNhNTE1MzY0MDVkYTJmOTE2NjUxYzJiZjdmNyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjdiZDYxODAwNWExNDIxMjViYjk5YzBjYmIwMjgzYjc2ODg1MjI2MmFkOGRiZjYzN2ViZTM4Zjc2MWVmYzRjZWEifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lRRE1JT010bmZwOFNoNU9tbXVXVWp0ZVF1ZVk5dzB3ZVl5ZTE1NDIvNjFiQ2dJZ2F5OU9sRkJIVzd5a0pQNy9Dbml0azU5ZU5EQVBjdks5K3VubENhY2VLd289IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VjeVZFTkRRbXdyWjBGM1NVSkJaMGxWWldsbGRVdFFUU3QzZEVOa2JFdEZkVTgyYmxJNGN6aExjR3RqZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmVFMXFSVFZOVkdOM1QxUlZlbGRvWTA1TmFsRjRUV3BGTlUxVVkzaFBWRlY2VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVkJTMWR6UkhGNVkyVktjMmwyTVRodlRIcHZaVTFGWm1aMVpXaEZTbEpFU0dSWldFd0tkV2xvVVM5bWNGVTNPVXR6U1VwdWVIaHZXbnBNY3pnMVVEaFZhM0JvTm5kSlVtVnVSRkozY1VJdlpVcExNazg1UzB0UFEwSllOSGRuWjFZMlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVTVNSE5uQ2pKVVNDODVRMlJ1VG5sUlkyOUlRMHBuZVZCaVpqSkJkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMkZCV1VSV1VqQlNRVkZJTDBKR05IZFlTVnBoWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBURE5PY0ZvelRqQmlNMHBzVEROT2NBcGFNMDR3WWpOS2JFeFlRalZrUjJoMlltazRkVm95YkRCaFNGWnBURE5rZG1OdGRHMWlSemt6WTNrNWVWcFhlR3haV0U1c1RHNXNkR0pGUW5sYVYxcDZDa3d6VW1oYU0wMTJaR3BOZFU1cE5IaE5SR3RIUTJselIwRlJVVUpuTnpoM1FWRkZSVXN5YURCa1NFSjZUMms0ZG1SSE9YSmFWelIxV1ZkT01HRlhPWFVLWTNrMWJtRllVbTlrVjBveFl6SldlVmt5T1hWa1IxWjFaRU0xYW1JeU1IZEdVVmxMUzNkWlFrSkJSMFIyZWtGQ1FXZFJTR050Vm5OYVYwWjZXbFJCTWdwQ1oyOXlRbWRGUlVGWlR5OU5RVVZFUWtObk5FOVVXbXBhYlZWNFRYcEZkMDVVVVRWT1YxVXlXa2ROTWxwcWFHMVpWMWw1VFRKVmVFMUVRVE5hUjBWNkNrNVhWbXRhVjFacFRVSlZSME5wYzBkQlVWRkNaemM0ZDBGUlVVVkNNVXBzWWtkV2FHTXlWWGRLWjFsTFMzZFpRa0pCUjBSMmVrRkNRbEZSV1dNeWJHNEtZek5TZG1OdFZYWmpNbXh1WXpOU2RtTnRWWFJqU0d3d1lVYzVkVTFDTkVkRGFYTkhRVkZSUW1jM09IZEJVVmxGUlVoS2JGcHVUWFprUjBadVkzazVNZ3BOZVRReVRHcEZkMDkzV1V0TGQxbENRa0ZIUkhaNlFVSkRRVkYwUkVOMGIyUklVbmRqZW05MlRETlNkbUV5Vm5WTWJVWnFaRWRzZG1KdVRYVmFNbXd3Q21GSVZtbGtXRTVzWTIxT2RtSnVVbXhpYmxGMVdUSTVkRTFIYjBkRGFYTkhRVkZSUW1jM09IZEJVV3RGV0VGNFlXRklVakJqU0UwMlRIazVibUZZVW04S1pGZEpkVmt5T1hSTU0wNXdXak5PTUdJelNteE1NMDV3V2pOT01HSXpTbXhNV0VJMVpFZG9kbUpwT0hWYU1td3dZVWhXYVV3elpIWmpiWFJ0WWtjNU13cGplVGw1V2xkNGJGbFlUbXhNYm14MFlrVkNlVnBYV25wTU0xSm9Xak5OZG1ScVRYVk9hVFI0VFVSblIwTnBjMGRCVVZGQ1p6YzRkMEZSYjBWTFozZHZDazlFYXpKWk1scHNUVlJOZUUxRVZUQlBWRlpzVG0xU2FrNXRXVFJhYlVadFRXcE9iRTFVUVhkT01sSm9UWHBXYkZwSFZteFpha0ZrUW1kdmNrSm5SVVVLUVZsUEwwMUJSVXhDUVRoTlJGZGtjR1JIYURGWmFURnZZak5PTUZwWFVYZFBkMWxMUzNkWlFrSkJSMFIyZWtGQ1JFRlJkRVJEZEc5a1NGSjNZM3B2ZGdwTU1tUndaRWRvTVZscE5XcGlNakIyWXpKc2JtTXpVblpqYlZWMll6SnNibU16VW5aamJWVjBZMGhzTUdGSE9YVk5SR2RIUTJselIwRlJVVUpuTnpoM0NrRlJNRVZMWjNkdlQwUnJNbGt5V214TlZFMTRUVVJWTUU5VVZteE9iVkpxVG0xWk5GcHRSbTFOYWs1c1RWUkJkMDR5VW1oTmVsWnNXa2RXYkZscVFXY0tRbWR2Y2tKblJVVkJXVTh2VFVGRlQwSkNTVTFGU0Vwc1dtNU5kbVJIUm01amVUa3lUWGswTWt4cVJYZEhVVmxMUzNkWlFrSkJSMFIyZWtGQ1JIZFJUQXBFUVdzd1RrUmpNazlVUlhkUFJGbDNTM2RaUzB0M1dVSkNRVWRFZG5wQlFrVkJVV1JFUW5SdlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyQ21NeWJHNWpNMUoyWTIxVmQwZEJXVXRMZDFsQ1FrRkhSSFo2UVVKRlVWRkxSRUZuTTAxVVFUVk9hazB4VFhwQ2NVSm5iM0pDWjBWRlFWbFBMMDFCUlZNS1FrWjNUVmR0YURCa1NFSjZUMms0ZGxveWJEQmhTRlpwVEcxT2RtSlRPWHBoVjJSNlpFYzVlVnBUT1hwaFYyUjZaRWM1ZVZwVE1YZGxXRkp2WWpJMGRncE1iV1J3WkVkb01WbHBPVE5pTTBweVdtMTRkbVF6VFhaamJWWnpXbGRHZWxwVE5UVmlWM2hCWTIxV2JXTjVPVEJaVjJSNlRETlpla3hxV1hWTlZFRTBDa0puYjNKQ1owVkZRVmxQTDAxQlJWUkNRMjlOUzBSbk5VNXRUbTFhVkVWNlRWUkJNVTVFYXpGYVZGcHJXWHBhYlU5SFdtaGFha2w2V2xSRmQwMUVaR3NLV1ZSTk1WcFhVbXhhVjBsM1JuZFpTMHQzV1VKQ1FVZEVkbnBCUWtaQlVVcEVRV1I1V2xkNGJGbFlUbXhOUmpoSFEybHpSMEZSVVVKbk56aDNRVkpWUlFwVlVYaFFZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0ZvelRqQmlNMHBzVEZoQ05XUkhhSFppYVRsb0Nsa3pVbkJpTWpWNlRETktNV0p1VFhaTlZFa3dUVlJqTUUxcVFUVk5SRVYyV1ZoU01GcFhNWGRrU0UxMlRWUkJWMEpuYjNKQ1owVkZRVmxQTDAxQlJWY0tRa0ZuVFVKdVFqRlpiWGh3V1hwRFFtbG5XVXRMZDFsQ1FrRklWMlZSU1VWQloxSTRRa2h2UVdWQlFqSkJUakE1VFVkeVIzaDRSWGxaZUd0bFNFcHNiZ3BPZDB0cFUydzJORE5xZVhRdk5HVkxZMjlCZGt0bE5rOUJRVUZDYXprdmJIUlNkMEZCUVZGRVFVVmpkMUpSU1doQlR6bGhUMGRpVDBaUGVtcGlXVTR6Q2xwUmIzcFFhRTFLTDNSRlVWSkJPVUZ6VERsaGFtNU9WVzFFYURoQmFVSnFabUZPVkRaNFVHODJRWEY0YkZWWWJ6ZHVkMDluVDBsTmJXUkdOVFJ0WnpVS1ZqbEtTbnBHTTBzNFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtGemVUbDFPRW96TUdwM1NHSkNiRE5DTXpGa0syOTNNVlJ1WlhWdlJ3cEVlSE5KYUdNelF6RXpaVWxVV1RnNFdVVmlPVWQxUnl0YVRFVk1ObEJrYzNwNk5FTk5VVU00UVRWQ1JtTnZURzVZYm13MWRFRkdWRXBITW5ndllYTnNDa1JNWTJsbmJEWjNObGRaUTJ0TlZHNVVaVWg2Y0hWMFNrbGlVbEJ1ZGtWcVFtcDJhME4xYnowS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9XX19","inclusionPromise":{"signedEntryTimestamp":"MEUCIQDrulw1km4at8ZmBCBTiB1EDhtGdmx8V5hck+FzPYg7jQIgLJ4O8E4Bd9cqn7G3HotsJ+I3NiVG5W+pZv7nQDjX5bY="},"inclusionProof":{"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n34812628\n+ce/n7ywQMI84KqPfAKGKMwdivhMAlf7XwPKYBidxYM=\n\n— rekor.sigstore.dev wNI9ajBFAiAhElSyD1E0nPtdd92eidXkaRcgYtILfOA1cXk1sDbLXAIhAItjpa2bFkL5aovzA1eUjkzZ6TX/PVL4+BNl5A+BHmby\n"},"hashes":["FhoMTHbH8IjfwSy6gvgd+d/IfANzjryvXh4eZSn6AS8=","1QyyxJKYbJBZlc15TbaxmNbEfA1AHmJr70+0qyePuXg=","EN1muAli1O0UWgSvkX6qMmJK1nLTyal0aWpuaHKQ2Y4=","Nl//RhaVeQQQoM17dAQHuAIj6Dkl/vM2NFCmc2mC8/4=","DPDWnECwPD/Wh144RnYVA7yenXvJzFtdLTFt0zs1a1g=","vqBR26dbTo8QhMdNFlg3s+NZOui+7VrzrGbP0fFVOzk=","mQnBdLQrv9x7kuZzlQT93vlvWUy7sfcsVWRId4hBEtg=","naGqS2+y9kMdzxW4CDHPJAJs/s1LMscH0gAbpFEhnkc=","pUm2APk0bAEfOBQX/2qQnXBGU08yCTl7wSgiwbyA1CI=","qw2H3MqjNE1OcI8EE5kjLoaRrucguamat/hjT+fJFS0=","TtWisxkCD12d93zYhBEcavGz5i/0U8SBkxnc2qfCBvw=","vemyaMj0Na1LMjbB/9Dmkq8T+jAb3o+yCESgAayUABU="],"logIndex":"34812627","rootHash":"+ce/n7ywQMI84KqPfAKGKMwdivhMAlf7XwPKYBidxYM=","treeSize":"34812628"},"integratedTime":"1734628193","kindVersion":{"kind":"dsse","version":"0.0.1"},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"logIndex":"156716889"}]},"version":1}],"publisher":{"environment":null,"kind":"GitHub","repository":"sigstore/sigstore-python","workflow":"release.yml"}}],"version":1}

0 commit comments

Comments
 (0)