Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
specified with a `pypi:` prefix followed by the filename, e.g:
`pypi:sampleproject-1.0.0.tar.gz`. The old way (passing
the direct URL) is still supported.
- The CLI subcommand `verify pypi` now supports passing the local paths
to the artifact and its provenance file, allowing the user to verify
files already downloaded from PyPI. The artifact path is passed as
usual, whereas the provenance file path is passed using the
`--provenance-file` option.

## [0.0.21]

Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,23 @@ pypi-attestations verify attestation \

### Verifying a PyPI package
> [!NOTE]
> The package to verify can be passed either as a `pypi:` prefixed filename (e.g:
> 'pypi:sampleproject-1.0.0-py3-none-any.whl'), or as a direct URL
> to the artifact hosted by PyPI.
> The package to verify can be passed either as a path to a local file, a
> `pypi:` prefixed filename (e.g: 'pypi:sampleproject-1.0.0-py3-none-any.whl'),
> or as a direct URL to the artifact hosted by PyPI.

```bash
# Download the artifact (and its provenance) from PyPI and verify it
pypi-attestations verify pypi --repository https://github.com/sigstore/sigstore-python \
pypi:sigstore-3.6.1-py3-none-any.whl

# or alternatively:
# or alternatively, using the direct URL:
pypi-attestations verify pypi --repository https://github.com/sigstore/sigstore-python \
https://files.pythonhosted.org/packages/70/f5/324edb6a802438e97e289992a41f81bb7a58a1cda2e49439e7e48896649e/sigstore-3.6.1-py3-none-any.whl

# Verify the artifact and its provenance using local files
pypi-attestations verify pypi --repository https://github.com/sigstore/sigstore-python \
--provenance-file ~/Downloads/sigstore-3.6.1-py3-none-any.whl.provenance \
~/Downloads/sigstore-3.6.1-py3-none-any.whl
```

This command downloads the artifact and its provenance from PyPI. The artifact
Expand Down
96 changes: 63 additions & 33 deletions src/pypi_attestations/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ def _parser() -> argparse.ArgumentParser:
help="Use the staging environment",
)

verify_pypi_command.add_argument(
"--provenance-file",
type=Path,
help="Provide the provenance file instead of downloading it from PyPI",
)

inspect_command = subcommands.add_parser(
name="inspect",
help="Inspect one or more inputs",
Expand Down Expand Up @@ -233,9 +239,33 @@ def _download_file(url: str, dest: Path) -> None:
_die(f"Error downloading file: {e}")


def _get_direct_url_from_arg(arg: str) -> URIReference:
def _get_distribution_from_arg(arg: str) -> Distribution:
"""Parse the artifact argument for the `verify pypi` subcommand.

The argument can be:
- A pypi: prefixed filename (e.g. pypi:sampleproject-1.0.0.tar.gz)
- A direct URL to a PyPI-hosted artifact
- A path to a local file
"""
if arg.startswith("pypi:") or arg.startswith("https://"):
pypi_url = _get_direct_url_from_arg(arg)
dist_filename = pypi_url.path.split("/")[-1]
with TemporaryDirectory() as temp_dir:
dist_path = Path(temp_dir) / dist_filename
_download_file(url=pypi_url.unsplit(), dest=dist_path)
dist = Distribution.from_file(dist_path)
else:
dist_path = Path(arg)
if not dist_path.exists():
_die(f"File does not exist: {dist_path}")
dist = Distribution.from_file(dist_path)

return dist


def _get_direct_url_from_arg(arg: str) -> URIReference:
"""Get the URL from the artifact argument for the `verify pypi` subcommand.

The argument can be:
- A pypi: prefixed filename (e.g. pypi:sampleproject-1.0.0.tar.gz)
- A direct URL to a PyPI-hosted artifact
Expand Down Expand Up @@ -288,17 +318,14 @@ def _get_direct_url_from_arg(arg: str) -> URIReference:
return pypi_url


def _get_provenance_from_pypi(filename: str) -> Provenance:
def _get_provenance_from_pypi(dist: Distribution) -> Provenance:
"""Use PyPI's integrity API to get a distribution's provenance."""
try:
if filename.endswith(".tar.gz") or filename.endswith(".zip"):
name, version = parse_sdist_filename(filename)
elif filename.endswith(".whl"):
name, version, _, _ = parse_wheel_filename(filename)
else:
_die("URL should point to a wheel (*.whl) or a source distribution (*.zip or *.tar.gz)")
except (InvalidSdistFilename, InvalidWheelFilename) as e:
_die(f"Invalid distribution filename: {e}")
filename = dist.name
# Filename is already validated when creating the Distribution object
if filename.endswith(".tar.gz") or filename.endswith(".zip"):
name, version = parse_sdist_filename(filename)
else:
name, version, _, _ = parse_wheel_filename(filename)

provenance_url = f"https://pypi.org/integrity/{name}/{version}/{filename}/provenance"
response = requests.get(provenance_url)
Expand Down Expand Up @@ -480,31 +507,34 @@ def _verify_attestation(args: argparse.Namespace) -> None:
def _verify_pypi(args: argparse.Namespace) -> None:
"""Verify a distribution hosted on PyPI.

The distribution is downloaded and verified. The verification is against
the provenance file hosted on PyPI (if any), and against the repository URL
passed by the user as a CLI argument.
The distribution is downloaded (if needed) and verified. The verification is against
the provenance file (passed using the `--provenance-file` option, or downloaded
from PyPI if not provided), and against the repository URL passed by the user
as a CLI argument.
"""
pypi_url = _get_direct_url_from_arg(args.distribution_file)
dist = _get_distribution_from_arg(args.distribution_file)

with TemporaryDirectory() as temp_dir:
dist_filename = pypi_url.path.split("/")[-1]
dist_path = Path(temp_dir) / dist_filename
_download_file(url=pypi_url.unsplit(), dest=dist_path)
provenance = _get_provenance_from_pypi(dist_filename)
dist = Distribution.from_file(dist_path)
if args.provenance_file is None:
provenance = _get_provenance_from_pypi(dist)
else:
if not args.provenance_file.exists():
_die(f"Provenance file does not exist: {args.provenance_file}")
try:
for attestation_bundle in provenance.attestation_bundles:
publisher = attestation_bundle.publisher
_check_repository_identity(
expected_repository_url=args.repository, publisher=publisher
)
policy = publisher._as_policy() # noqa: SLF001.
for attestation in attestation_bundle.attestations:
attestation.verify(policy, dist, staging=args.staging)
except VerificationError as verification_error:
_die(f"Verification failed for {dist_filename}: {verification_error}")

_logger.info(f"OK: {dist_filename}")
provenance = Provenance.model_validate_json(args.provenance_file.read_bytes())
except ValidationError as validation_error:
_die(f"Invalid provenance: {validation_error}")

try:
for attestation_bundle in provenance.attestation_bundles:
publisher = attestation_bundle.publisher
_check_repository_identity(expected_repository_url=args.repository, publisher=publisher)
policy = publisher._as_policy() # noqa: SLF001.
for attestation in attestation_bundle.attestations:
attestation.verify(policy, dist, staging=args.staging)
except VerificationError as verification_error:
_die(f"Verification failed for {dist.name}: {verification_error}")

_logger.info(f"OK: {dist.name}")


def main() -> None:
Expand Down
Binary file added test/assets/sigstore-3.6.1.tar.gz
Binary file not shown.
1 change: 1 addition & 0 deletions test/assets/sigstore-3.6.1.tar.gz.provenance
Original file line number Diff line number Diff line change
@@ -0,0 +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}
Loading