diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e1f8601..1ba3931 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,3 +38,25 @@ jobs: - name: test run: make test INSTALL_EXTRA=test + + test-offline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + persist-credentials: false + + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 + with: + python-version: 3.13 + cache: "pip" + cache-dependency-path: pyproject.toml + allow-prereleases: true + + - name: install firejail + run: sudo apt-get install -y firejail + + - name: run tests offline + run: | + make dev INSTALL_EXTRA=test + firejail --noprofile --net=none --env=TEST_OFFLINE=1 make test-nocoverage \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d17cd65..2fcdf23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- The `Attestation.verify(...)` API has been changed to accept an `offline` + parameter that, when True, disables TUF refreshes. +- The CLI `verify` commands now also accept an `--offline` flag that disables + TUF refreshes. Additionally, when used with the `verify pypi` subcommand, the + `--offline` flag enforces that the distribution and provenance file arguments + must be local file paths. + ## [0.0.22] ### Changed diff --git a/Makefile b/Makefile index a927b27..e128ce8 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,11 @@ test tests: $(VENV)/pyvenv.cfg pytest --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) && \ python -m coverage report -m $(COV_ARGS) +.PHONY: test-nocoverage +test-nocoverage: $(VENV)/pyvenv.cfg + . $(VENV_BIN)/activate && \ + pytest $(T) $(TEST_ARGS) + .PHONY: doc doc: $(VENV)/pyvenv.cfg . $(VENV_BIN)/activate && \ diff --git a/README.md b/README.md index 6be1216..c4f16db 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ print(attestation.model_dump_json()) # Verify an attestation against a Python artifact attestation_path = Path("test_package-0.0.1-py3-none-any.whl.attestation") attestation = Attestation.model_validate_json(attestation_path.read_bytes()) -verifier = Verifier.production() identity = policy.Identity(identity="example@gmail.com", issuer="https://accounts.google.com") attestation.verify(identity=identity, dist=dist) ``` diff --git a/pyproject.toml b/pyproject.toml index 6bb970f..e2d330d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools", "setuptools-scm"] build-backend = "setuptools.build_meta" [project] diff --git a/src/pypi_attestations/_cli.py b/src/pypi_attestations/_cli.py index 0d002e5..0db5b7f 100644 --- a/src/pypi_attestations/_cli.py +++ b/src/pypi_attestations/_cli.py @@ -125,6 +125,13 @@ def _parser() -> argparse.ArgumentParser: help="Use the staging environment", ) + verify_attestation_command.add_argument( + "--offline", + action="store_true", + default=False, + help="Disable TUF refresh", + ) + verify_attestation_command.add_argument( "files", metavar="FILE", @@ -157,6 +164,13 @@ def _parser() -> argparse.ArgumentParser: help="Use the staging environment", ) + verify_pypi_command.add_argument( + "--offline", + action="store_true", + default=False, + help="Force use of local files and disable TUF refresh", + ) + verify_pypi_command.add_argument( "--provenance-file", type=Path, @@ -239,7 +253,7 @@ def _download_file(url: str, dest: Path) -> None: _die(f"Error downloading file: {e}") -def _get_distribution_from_arg(arg: str) -> Distribution: +def _get_distribution_from_arg(arg: str, offline: bool) -> Distribution: """Parse the artifact argument for the `verify pypi` subcommand. The argument can be: @@ -248,6 +262,8 @@ def _get_distribution_from_arg(arg: str) -> Distribution: - A path to a local file """ if arg.startswith("pypi:") or arg.startswith("https://"): + if offline: + _die("The '--offline' option can only be used with local files") pypi_url = _get_direct_url_from_arg(arg) dist_filename = pypi_url.path.split("/")[-1] with TemporaryDirectory() as temp_dir: @@ -497,7 +513,7 @@ def _verify_attestation(args: argparse.Namespace) -> None: _die(f"Invalid Python package distribution: {e}") try: - attestation.verify(pol, dist, staging=args.staging) + attestation.verify(pol, dist, staging=args.staging, offline=args.offline) except VerificationError as verification_error: _die(f"Verification failed for {file_path}: {verification_error}") @@ -512,9 +528,11 @@ def _verify_pypi(args: argparse.Namespace) -> None: from PyPI if not provided), and against the repository URL passed by the user as a CLI argument. """ - dist = _get_distribution_from_arg(args.distribution_file) + dist = _get_distribution_from_arg(args.distribution_file, offline=args.offline) if args.provenance_file is None: + if args.offline: + _die("The '--offline' option can only be used with local files") provenance = _get_provenance_from_pypi(dist) else: if not args.provenance_file.exists(): @@ -530,7 +548,7 @@ def _verify_pypi(args: argparse.Namespace) -> None: _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) + attestation.verify(policy, dist, staging=args.staging, offline=args.offline) except VerificationError as verification_error: _die(f"Verification failed for {dist.name}: {verification_error}") diff --git a/src/pypi_attestations/_impl.py b/src/pypi_attestations/_impl.py index a52eb14..2933ee5 100644 --- a/src/pypi_attestations/_impl.py +++ b/src/pypi_attestations/_impl.py @@ -230,6 +230,7 @@ def verify( dist: Distribution, *, staging: bool = False, + offline: bool = False, ) -> tuple[str, Optional[dict[str, Any]]]: """Verify against an existing Python distribution. @@ -241,6 +242,9 @@ def verify( `staging` parameter can be toggled to enable the staging verifier instead. + If `offline` is `True`, the verifier will not attempt to refresh the + TUF repository. + On failure, raises an appropriate subclass of `AttestationError`. """ # NOTE: Can't do `isinstance` with `Publisher` since it's @@ -253,9 +257,9 @@ def verify( policy = identity if staging: - verifier = Verifier.staging() + verifier = Verifier.staging(offline=offline) else: - verifier = Verifier.production() + verifier = Verifier.production(offline=offline) bundle = self.to_bundle() try: diff --git a/test/assets/pypi_attestations-0.0.16.tar.gz.attestation b/test/assets/pypi_attestations-0.0.16.tar.gz.attestation deleted file mode 100644 index 6125413..0000000 --- a/test/assets/pypi_attestations-0.0.16.tar.gz.attestation +++ /dev/null @@ -1,48 +0,0 @@ -{ - "envelope": { - "signature": "MEQCIEK1TlO0FG/yzoXHFu/ML77ATbXSHGgeMOFyT05x9bH3AiA8hubwYj3wV9yznsZkwJjHcjIHGSPgyJ8UZ+QP/o3img==", - "statement": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicHlwaV9hdHRlc3RhdGlvbnMtMC4wLjE2LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJjYmQyYjk0NmZlMTYwNzkzNjA2ZGVlNDUxNmVmNThhYzU5NTk0NTZlNjk2MzBhN2YwM2U3ZGU3M2FhN2YyNzM3In19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vZG9jcy5weXBpLm9yZy9hdHRlc3RhdGlvbnMvcHVibGlzaC92MSIsInByZWRpY2F0ZSI6bnVsbH0=" - }, - "verification_material": { - "certificate": "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", - "transparency_entries": [ - { - "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiN2Y0YjdmYzIyMDEwNDQ4ODlkZWZlZDRlNDgzZmQ0NWQ4YWM5MWYwMjcyZTBkOWZjODc3MzAyODY3YzAyYTJmOSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImQ0ZjYxNWJjZDU1YWQzMDUzNDkxNmE1OWQ4N2EyZDBkZDZjNTE2MWE5ODdiMzg5MzY2YWY2ZWE0YjA2YmU2YzUifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lFSzFUbE8wRkcveXpvWEhGdS9NTDc3QVRiWFNIR2dlTU9GeVQwNXg5YkgzQWlBOGh1YndZajN3Vjl5em5zWmt3SmpIY2pJSEdTUGd5SjhVWitRUC9vM2ltZz09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VjdmFrTkRRbTlQWjBGM1NVSkJaMGxWVWpoMWQwMUVLMnRYYVdaNVUxVmlTR1p6U0RFelJFVmlkazg0ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmVFMVVRVE5OYWtrd1RYcEpNVmRvWTA1TmFsRjRUVlJCTTAxcVNURk5la2t4VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVjFkRzFxTlVaSGJVTm9hWFZsTVhCaFRIVTRhRTFuVURkQlkzQmxVbmROYUVsc2IwMEtjak15VkdVMlNISlRlREpzT0RCSGVHVmpMMlJDZVVwVE16TlRjR3BCV0ZRMVZVbDZhbmQxWjNOME5rTlhOa2hLVjB0UFEwSmhTWGRuWjFkbFRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVlpRbGxIQ213eWFsTTRWVXB4VTFaeFIwTTFWMEl3ZW5RMmNtRXdkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMkpuV1VSV1VqQlNRVkZJTDBKSFVYZFpiMXBuWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBURE5TZVZsWGJITmlNbHBwWVZoU2VncE1NMEkxWTBkcmRGbFlVakJhV0U0d1dWaFNjR0l5TlhwTWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhR015VlhWbFZ6RnpDbEZJU214YWJrMTJaRWRHYm1ONU9USk5RelIzVEdwRk1rMUVhMGREYVhOSFFWRlJRbWMzT0hkQlVVVkZTekpvTUdSSVFucFBhVGgyWkVjNWNscFhOSFVLV1ZkT01HRlhPWFZqZVRWdVlWaFNiMlJYU2pGak1sWjVXVEk1ZFdSSFZuVmtRelZxWWpJd2QwWlJXVXRMZDFsQ1FrRkhSSFo2UVVKQloxRklZMjFXY3dwYVYwWjZXbFJCTWtKbmIzSkNaMFZGUVZsUEwwMUJSVVJDUTJjeFQwZE5ORTU2U214T2FtUnFUVVJPYWs5WFRYZE5la1pwV1ZSamVGbHFSVEpPVkZKdENscHFWVEJOYlZwdFRXcHJkMWt5VVROTlFsVkhRMmx6UjBGUlVVSm5OemgzUVZGUlJVSXpTbXhpUjFab1l6SlZkMHQzV1V0TGQxbENRa0ZIUkhaNlFVSUtRbEZSWkdSSVNtaGhWM2gyV20xS2NHUklUWFpqU0d4M1lWTXhhR1JJVW14ak0xSm9aRWRzZG1KdVRYZElkMWxMUzNkWlFrSkJSMFIyZWtGQ1FtZFJVZ3BqYlZadFkzazVNRmxYWkhwTU0xbDNUR3BCZFUxVVdYZFBkMWxMUzNkWlFrSkJSMFIyZWtGQ1EwRlJkRVJEZEc5a1NGSjNZM3B2ZGt3elVuWmhNbFoxQ2t4dFJtcGtSMngyWW01TmRWb3liREJoU0ZacFpGaE9iR050VG5aaWJsSnNZbTVSZFZreU9YUk5TRUZIUTJselIwRlJVVUpuTnpoM1FWRnJSVmxuZUdjS1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVEROU2VWbFhiSE5pTWxwcFlWaFNla3d6UWpWalIydDBXVmhTTUZwWVRqQlpXRkp3WWpJMWVncE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFprUjBadVkzazVNazFETkhkTWFrVXlDazFFWjBkRGFYTkhRVkZSUW1jM09IZEJVVzlGUzJkM2IwNVVhR3BQUkdONVdsUlpNMWw2UVhwWmVteHFUVVJOZUZsdFJUTk5WMGw0VG1wVk1GcHRXVEVLVGtSS2JWcHFTVFZOUjA1clRucEJaRUpuYjNKQ1owVkZRVmxQTDAxQlJVeENRVGhOUkZka2NHUkhhREZaYVRGdllqTk9NRnBYVVhkUlFWbExTM2RaUWdwQ1FVZEVkbnBCUWtSQlVYbEVSRUp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmRtTkliSGRoVXpGb0NtUklVbXhqTTFKb1pFZHNkbUp1VFhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUkZGUmNVUkRaekZQUjAwMFRucEtiRTVxWkdwTlJFNXFUMWROZDAxNlJta0tXVlJqZUZscVJUSk9WRkp0V21wVk1FMXRXbTFOYW10M1dUSlJNMDFEUlVkRGFYTkhRVkZSUW1jM09IZEJVVFJGUlhkM1VtTnRWbTFqZVRrd1dWZGtlZ3BNTTFsM1RHcEJkVTFVV1hkSFVWbExTM2RaUWtKQlIwUjJla0ZDUkhkUlRFUkJhek5PZWtsNVRrUmpNRTFxVFhkTVoxbExTM2RaUWtKQlIwUjJla0ZDQ2tWQlVXZEVRalZ2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmQwWjNXVXRMZDFsQ1FrRkhSSFo2UVVJS1JWRlJTa1JCWTNsTmVrVXdUa1JKZWsxSVFVZERhWE5IUVZGUlFtYzNPSGRCVWtsRldXZDRaMkZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1NMUo1V1Zkc2MySXlXbWxoV0ZKNlRETkNOV05IYTNSWldGSXdXbGhPTUZsWVVuQmlNalY2VEhrMWJtRllVbTlrVjBsMlpESTVlV0V5V25OaU0yUjZDa3d6U214aVIxWm9ZekpWZFdWWE1YTlJTRXBzV201TmRtUkhSbTVqZVRreVRVTTBkMHhxUlRKTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjhLVGxSb2FrOUVZM2xhVkZreldYcEJlbGw2YkdwTlJFMTRXVzFGTTAxWFNYaE9hbFV3V20xWk1VNUVTbTFhYWtrMVRVZE9hMDU2UVZoQ1oyOXlRbWRGUlFwQldVOHZUVUZGVlVKQmEwMUNNMHBzWWtkV2FHTXlWWGRhUVZsTFMzZFpRa0pCUjBSMmVrRkNSbEZTVjBSR1VtOWtTRkozWTNwdmRrd3laSEJrUjJneENsbHBOV3BpTWpCMlpFaEthR0ZYZUhaYWJVcHdaRWhOZG1OSWJIZGhVekZvWkVoU2JHTXpVbWhrUjJ4MlltNU5kbGxYVGpCaFZ6bDFZM2s1ZVdSWE5Yb0tUSHBGZUU1NlRYbE9WRmswVFhwbk1Fd3lSakJrUjFaMFkwaFNla3g2UlhkR1oxbExTM2RaUWtKQlIwUjJla0ZDUm1kUlNVUkJXbmRrVjBwellWZE5kd3BuV1d0SFEybHpSMEZSVVVJeGJtdERRa0ZKUldWM1VqVkJTR05CWkZGRVpGQlVRbkY0YzJOU1RXMU5Xa2hvZVZwYWVtTkRiMnR3WlhWT05EaHlaaXRJQ21sdVMwRk1lVzUxYW1kQlFVRmFUVWw1TDI5d1FVRkJSVUYzUWtkTlJWRkRTVVpYUlRNdmQyOTRjM1pEZEN0VFdtMW1NbEpESzFGdk1YZFlabVZLUTJVS0wwaHlOVTVJZW14M1pEQklRV2xDYVdVeFdGSmpkMU5xTkhWbVIwRXlRMEV2ZVRkQ2JuRXhkMVJXYm5NeVVHNVdNRmx2VTNscFVqUnNha0ZMUW1kbmNRcG9hMnBQVUZGUlJFRjNUbkJCUkVKdFFXcEZRV2hJYXpnMlNFVnRkalJyTTJWNk1XcHdOVlIzWm13d2VtcFVVRXRHYWpSaU1HSlpTRzFOVUVaNlNWUkNDbEo2V2t4WGNFZERMMUp4YXpKc2FsVXlObnB1UVdwRlFUaFVWMVpUYldaWWNHMUpXVVUyTlRJck0ybEVNMUpOZHl0NE0zbHBUMlIxYm01SGFVNWlXR1lLVm5Ob04wdHVXbUl2WjNCd09ETnBUakJETkRWRU4yTXhDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifV19fQ==", - "inclusionPromise": { - "signedEntryTimestamp": "MEYCIQCd4F6sF6WMgFMMK4iOV2hpd/IEJ/ScLRKagSPw6Pf8CgIhAJdfBmna8lHVMxrxxJfI8YmHG8VsGm0lb9EcJLJ+BL+n" - }, - "inclusionProof": { - "checkpoint": { - "envelope": "rekor.sigstore.dev - 1193050959916656506\n25533066\nqvs8JkP8+3Utq03DxtSYzwsDWKXEAoriQSm5nauVCqY=\n\n— rekor.sigstore.dev wNI9ajBFAiEApqCgo2XcekZNseJlFzWEX0GK8tkBHHhCYmAPU9aSEkECIEHC7q9+y8OA1uKSWZvIWaM2HN+N+TTbIMCRn7ES3o95\n" - }, - "hashes": [ - "N8S5teoiks1Q2l1X06qKPa5RBAnKLBmz+MbDm0HBQLQ=", - "oR60wUolAs2vAjVLrNo9glJpArDAREyv2P2bj3aJt5w=", - "4ZD3GEL8Ur5l8CIMyjk8ZxkKxzEu9Lc3LwV8DQfSbsY=", - "WF8fC14uf8N7BWexcGPtcn2ukIqswoQlCOl4ffrjUuY=", - "bWVWNxTbzTRrZ8w26ffyWKjSyKuFMvkBwb9IlaeYyfU=", - "0xmVoTBKl7Win61Lt53r5MffAMQUIxTZve8Fta/wZtg=", - "benQsKBgqC82O3PMKh73dzqGPFgjXPZfBvOVBC9f7VE=", - "DvpH9wBCyQKtTWiggquWWE+DEos61Wkar5IFAE5f24M=", - "vjvWRq9utVBPAcT20yr9yyI/ov6jd+e3RpU+SDkDepY=", - "4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=", - "gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw=" - ], - "logIndex": "25533065", - "rootHash": "qvs8JkP8+3Utq03DxtSYzwsDWKXEAoriQSm5nauVCqY=", - "treeSize": "25533066" - }, - "integratedTime": "1731019406", - "kindVersion": { - "kind": "dsse", - "version": "0.0.1" - }, - "logId": { - "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" - }, - "logIndex": "147437327" - } - ] - }, - "version": 1 -} diff --git a/test/assets/pypi_attestations-0.0.19.tar.gz.provenance b/test/assets/pypi_attestations-0.0.19.tar.gz.provenance new file mode 100644 index 0000000..95569bb --- /dev/null +++ b/test/assets/pypi_attestations-0.0.19.tar.gz.provenance @@ -0,0 +1 @@ +{"attestation_bundles":[{"attestations":[{"envelope":{"signature":"MEYCIQC+Yjc/GP2HATHRmlyN85BGBJdDgOWm/GVMDuoU6ESX/gIhAIyakdnRMW84PJVQJxpPp3zxwA9IdP64teBnkeFl71OH","statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicHlwaV9hdHRlc3RhdGlvbnMtMC4wLjE5LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiI5YmIxYWRkMDRiMWI0ZTE4MmJlNmIwYjgwOTMxNTkzZjdhMjkxZWI0OWQ2OWI0ZmQ3MjhhNWQ0Y2JjZGM0YmQzIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vZG9jcy5weXBpLm9yZy9hdHRlc3RhdGlvbnMvcHVibGlzaC92MSIsInByZWRpY2F0ZSI6bnVsbH0="},"verification_material":{"certificate":"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","transparency_entries":[{"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWFjNTY1OWQ4NDZhMTYxMzdhNDFiMzQxM2E5MTE4ZjFiODYxNDFhNmJiMWIxNTRhYWZhNWZhNGY2NTJjYWM0NCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImY5NzkwZThhODdkMDE4OThkMDBkMmNlMWNhNmNlNDJiZmM2Y2U1OWZhYWEzMWJhYTBhYjY0MDY0YTY5NjgwMDAifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRQytZamMvR1AySEFUSFJtbHlOODVCR0JKZERnT1dtL0dWTUR1b1U2RVNYL2dJaEFJeWFrZG5STVc4NFBKVlFKeHBQcDN6eHdBOUlkUDY0dGVCbmtlRmw3MU9IIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VjdmFrTkRRbTlUWjBGM1NVSkJaMGxWUWprclYySndlRkZTSzI5V2RXZFdiRkl5VVhBd01XOTNablpGZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmVFMXFRVEJOYWsxNFRrUkJlRmRvWTA1TmFsRjRUV3BCTUUxcVRYbE9SRUY0VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVm5ZU3RNV1ZneWRsUkVOMk16WmxreGRDOXdjR05XVjI1WmMzVTBaRXB0VEZwblIyd0tTRlJvTTJka1VERXdVRWRUZUhWUGNXWmlUVzVYTkZCbmEwcE9iR0oxYWtjMlJEVlliazU1VmtKcGFXaFljQzlUUldGUFEwSmhUWGRuWjFkbVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVktWVmczQ21scWJYSkZTalJhWjA1NFYxWnZTbE5wTWtwWFMwOWpkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMkpuV1VSV1VqQlNRVkZJTDBKSFVYZFpiMXBuWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBURE5TZVZsWGJITmlNbHBwWVZoU2VncE1NMEkxWTBkcmRGbFlVakJhV0U0d1dWaFNjR0l5TlhwTWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhR015VlhWbFZ6RnpDbEZJU214YWJrMTJaRWRHYm1ONU9USk5RelIzVEdwRk5VMUVhMGREYVhOSFFWRlJRbWMzT0hkQlVVVkZTekpvTUdSSVFucFBhVGgyWkVjNWNscFhOSFVLV1ZkT01HRlhPWFZqZVRWdVlWaFNiMlJYU2pGak1sWjVXVEk1ZFdSSFZuVmtRelZxWWpJd2QwWlJXVXRMZDFsQ1FrRkhSSFo2UVVKQloxRklZMjFXY3dwYVYwWjZXbFJCTWtKbmIzSkNaMFZGUVZsUEwwMUJSVVJDUTJkM1QwUm5kMDF0Vm0xYVZFWnRUMGRWTVZwdFZtcE9SMFpyVDBSUmVWcEVXbWxQUjA1c0NrOVVZekpPVkZsM1QxUktiRnBVWTNsTlFsVkhRMmx6UjBGUlVVSm5OemgzUVZGUlJVSXpTbXhpUjFab1l6SlZkMHQzV1V0TGQxbENRa0ZIUkhaNlFVSUtRbEZSWkdSSVNtaGhWM2gyV20xS2NHUklUWFpqU0d4M1lWTXhhR1JJVW14ak0xSm9aRWRzZG1KdVRYZElkMWxMUzNkWlFrSkJSMFIyZWtGQ1FtZFJVZ3BqYlZadFkzazVNRmxYWkhwTU0xbDNUR3BCZFUxVWEzZFBkMWxMUzNkWlFrSkJSMFIyZWtGQ1EwRlJkRVJEZEc5a1NGSjNZM3B2ZGt3elVuWmhNbFoxQ2t4dFJtcGtSMngyWW01TmRWb3liREJoU0ZacFpGaE9iR050VG5aaWJsSnNZbTVSZFZreU9YUk5TRUZIUTJselIwRlJVVUpuTnpoM1FWRnJSVmxuZUdjS1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVEROU2VWbFhiSE5pTWxwcFlWaFNla3d6UWpWalIydDBXVmhTTUZwWVRqQlpXRkp3WWpJMWVncE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFprUjBadVkzazVNazFETkhkTWFrVTFDazFFWjBkRGFYTkhRVkZSUW1jM09IZEJVVzlGUzJkM2IwMUVaelJOUkVwc1dtMVZlRnBxYUd4T1YxcHNXWHBTYUZwRVp6Qk5iVkV5V1dwb2FscFVhek1LVG1wVk1rMUVhM2xhVjFVelRXcEJaRUpuYjNKQ1owVkZRVmxQTDAxQlJVeENRVGhOUkZka2NHUkhhREZaYVRGdllqTk9NRnBYVVhkUlFWbExTM2RaUWdwQ1FVZEVkbnBCUWtSQlVYbEVSRUp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmRtTkliSGRoVXpGb0NtUklVbXhqTTFKb1pFZHNkbUp1VFhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUkZGUmNVUkRaM2RQUkdkM1RXMVdiVnBVUm0xUFIxVXhXbTFXYWs1SFJtc0tUMFJSZVZwRVdtbFBSMDVzVDFSak1rNVVXWGRQVkVwc1dsUmplVTFEUlVkRGFYTkhRVkZSUW1jM09IZEJVVFJGUlhkM1VtTnRWbTFqZVRrd1dWZGtlZ3BNTTFsM1RHcEJkVTFVYTNkSFVWbExTM2RaUWtKQlIwUjJla0ZDUkhkUlRFUkJhek5PZWtsNVRrUmpNRTFxVFhkTVoxbExTM2RaUWtKQlIwUjJla0ZDQ2tWQlVXZEVRalZ2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmQwWjNXVXRMZDFsQ1FrRkhSSFo2UVVJS1JWRlJTa1JCWTNsTmVrVXdUa1JKZWsxSVFVZERhWE5IUVZGUlFtYzNPSGRCVWtsRldXZDRaMkZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1NMUo1V1Zkc2MySXlXbWxoV0ZKNlRETkNOV05IYTNSWldGSXdXbGhPTUZsWVVuQmlNalY2VEhrMWJtRllVbTlrVjBsMlpESTVlV0V5V25OaU0yUjZDa3d6U214aVIxWm9ZekpWZFdWWE1YTlJTRXBzV201TmRtUkhSbTVqZVRreVRVTTBkMHhxUlRWTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjhLVFVSbk5FMUVTbXhhYlZWNFdtcG9iRTVYV214WmVsSm9Xa1JuTUUxdFVUSlphbWhxV2xSck0wNXFWVEpOUkd0NVdsZFZNMDFxUVZoQ1oyOXlRbWRGUlFwQldVOHZUVUZGVlVKQmEwMUNNMHBzWWtkV2FHTXlWWGRhUVZsTFMzZFpRa0pCUjBSMmVrRkNSbEZTVjBSR1VtOWtTRkozWTNwdmRrd3laSEJrUjJneENsbHBOV3BpTWpCMlpFaEthR0ZYZUhaYWJVcHdaRWhOZG1OSWJIZGhVekZvWkVoU2JHTXpVbWhrUjJ4MlltNU5kbGxYVGpCaFZ6bDFZM2s1ZVdSWE5Yb0tUSHBGZVUxVVdUVlBWR2MxVG5wbk0wd3lSakJrUjFaMFkwaFNla3g2UlhkR1oxbExTM2RaUWtKQlIwUjJla0ZDUm1kUlNVUkJXbmRrVjBwellWZE5kd3BuV1c5SFEybHpSMEZSVVVJeGJtdERRa0ZKUldaQlVqWkJTR2RCWkdkRVpGQlVRbkY0YzJOU1RXMU5Xa2hvZVZwYWVtTkRiMnR3WlhWT05EaHlaaXRJQ21sdVMwRk1lVzUxYW1kQlFVRmFUMVE0TjBWbFFVRkJSVUYzUWtoTlJWVkRTVkZFVkRWWVEzQTFXbXN5V1V4blNGTjNTMmczVkRFemJrSmpjR0ZGYTNFS1UwRTVSa1ZPWkd4ak56bFNWMmRKWjB0b1FrY3JWM2g1YWk5S01WazROekZrVDNWMVIwWnNLeXRNYUZCeVdsQmhNMU5XYms5VFoxZHVNVEIzUTJkWlNRcExiMXBKZW1vd1JVRjNUVVJoUVVGM1dsRkplRUZLUzJWVVoyb3JaMmhqTTNCbFJEQXhRVkprT1dGSlZuTkZjRVJIWkdKUFlYcExRVEoxWVZGT2RsTk1DazkxWkhWNVNtdE1iRTVWVGt0T09GRmhUbmR3YzFGSmQxUndXalkwZG5ScWQwTXlVMXBCY1dJeU1VSnVOSEZXTjNSTVFrcHRTVmQ0TVd4VlpUQlJiRllLZVdsNE1URTRNRlJyUTFvd1FsWmpabTlFTkhoSlEwdEpDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifV19fQ==","inclusionPromise":{"signedEntryTimestamp":"MEQCIBCltBioLJc+JvwwmgQ1BYgIOE5RTG+4e7X3zQRGa6ZHAiA46xAH8obqC29dw2T8gwyllNzqr2tfG8iMWLnFKuIORQ=="},"inclusionProof":{"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n31550402\ny0mvJokg0K9X78/zSkzLfaoYE1EUnA0l4CTQQXV1g4o=\n\n— rekor.sigstore.dev wNI9ajBFAiBegciq2ELY5ENvqNVWFm9cEfG5dKBwBegOEaoDVWLdVwIhAJAOwu2VWIQojdyGzsV/t4MRCOkduH9SVKL8yoK9qMc2\n"},"hashes":["Ys0Xl1UkpNskWt6zZj4IF0V4C0hQnG2rcYT7yITCgxs=","SZr1mNOrcyCB9fdcJOMiVFgh1vv3F1JOn4Iro9CbeI0=","PcaivMMXf/sp7holRDnainZ7CqkhfGbumRzqHBLORyo=","QmGjL5m3QfWxTeS1DSKdcZi1Cb/kXHez6zUQJFM+6No=","RzzP82vxkFlwbK42drRrLKdQO7cU4TutI9RH4GZWqmc=","T180VQg42xooybP3Nj9QuoOWlDWbbaQ4n4S2wWmwsSc=","hABDIJpRLjWOq3nQy0+WmZvudV2qvqHNysU41bEne1Q=","pDqPuFyZgsegAtT2zcnZSet9YAZExTulHeEunQ5/LRg=","Q+viUQ+8PPr3Ck1WOMcjETEmAMGVbNDvGlSy6G/aFAI=","R7hO1X+KgSw8Oojd8i2+G3BzBYztkRBE6LpYSXPg33U=","oOecFfN3YqDOkbijS/ej1WF5Da/Gt/AZNhbwE9uoOE8=","4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=","gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw="],"logIndex":"31550401","rootHash":"y0mvJokg0K9X78/zSkzLfaoYE1EUnA0l4CTQQXV1g4o=","treeSize":"31550402"},"integratedTime":"1733354041","kindVersion":{"kind":"dsse","version":"0.0.1"},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"logIndex":"153454663"}]},"version":1}],"publisher":{"environment":null,"kind":"GitHub","repository":"trailofbits/pypi-attestations","workflow":"release.yml"}}],"version":1} diff --git a/test/assets/sigstore-3.6.1.tar.gz b/test/assets/sigstore-3.6.1.tar.gz deleted file mode 100644 index fb36a61..0000000 Binary files a/test/assets/sigstore-3.6.1.tar.gz and /dev/null differ diff --git a/test/assets/sigstore-3.6.1.tar.gz.provenance b/test/assets/sigstore-3.6.1.tar.gz.provenance deleted file mode 100644 index 673b7f2..0000000 --- a/test/assets/sigstore-3.6.1.tar.gz.provenance +++ /dev/null @@ -1 +0,0 @@ -{"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} diff --git a/test/test_cli.py b/test/test_cli.py index e025786..55d17f7 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -23,9 +23,11 @@ ) from pypi_attestations._impl import Attestation, AttestationError, Distribution -ONLINE_TESTS = "CI" in os.environ or "TEST_INTERACTIVE" in os.environ -online = pytest.mark.skipif(not ONLINE_TESTS, reason="online tests not enabled") +ONLINE_TESTS = ( + "CI" in os.environ or "TEST_INTERACTIVE" in os.environ +) and "TEST_OFFLINE" not in os.environ +online = pytest.mark.skipif(not ONLINE_TESTS, reason="online tests not enabled") _HERE = Path(__file__).parent _ASSETS = _HERE / "assets" @@ -35,12 +37,12 @@ publish_attestation_path = _ASSETS / "pypi_attestations-0.0.19.tar.gz.publish.attestation" slsa_attestation_path = _ASSETS / "pypi_attestations-0.0.19.tar.gz.slsa.attestation" -pypi_wheel_url = "https://files.pythonhosted.org/packages/70/f5/324edb6a802438e97e289992a41f81bb7a58a1cda2e49439e7e48896649e/sigstore-3.6.1-py3-none-any.whl" -pypi_sdist_url = "https://files.pythonhosted.org/packages/db/89/b982115aabe1068fd581d83d2a0b26b78e1e7ce6184e75003d173e15c0b3/sigstore-3.6.1.tar.gz" +pypi_wheel_url = "https://files.pythonhosted.org/packages/fb/f2/3e026065773b84c5b2345e2548a08b10105d324b9b95c72643f57a25fcbb/pypi_attestations-0.0.19-py3-none-any.whl" +pypi_sdist_url = "https://files.pythonhosted.org/packages/c5/4d/a114bdd186903426bd9c1e9c3700761ec5eaac260fa3dfdef14bf84b751b/pypi_attestations-0.0.19.tar.gz" pypi_wheel_filename = pypi_wheel_url.split("/")[-1] pypi_sdist_filename = pypi_sdist_url.split("/")[-1] -pypi_wheel_abbrev = f"sigstore/{pypi_wheel_filename}" -pypi_sdist_abbrev = f"sigstore/{pypi_sdist_filename}" +pypi_wheel_abbrev = f"pypi-attestations/{pypi_wheel_filename}" +pypi_sdist_abbrev = f"pypi-attestations/{pypi_sdist_filename}" pypi_sdist_path = _ASSETS / pypi_sdist_filename pypi_sdist_provenance_path = _ASSETS / f"{pypi_sdist_filename}.provenance" @@ -216,6 +218,7 @@ def test_verify_attestation_command(caplog: pytest.LogCaptureFixture) -> None: [ "verify", "attestation", + "--offline", "--identity", publish_attestation_identity, artifact_path.as_posix(), @@ -233,6 +236,7 @@ def test_verify_attestation_command(caplog: pytest.LogCaptureFixture) -> None: "verify", "attestation", "--staging", + "--offline", "--identity", publish_attestation_identity, artifact_path.as_posix(), @@ -256,6 +260,7 @@ def test_verify_attestation_invalid_attestation(caplog: pytest.LogCaptureFixture [ "verify", "attestation", + "--offline", "--identity", publish_attestation_identity, fake_package_name.as_posix(), @@ -271,6 +276,7 @@ def test_verify_attestation_missing_artifact(caplog: pytest.LogCaptureFixture) - [ "verify", "attestation", + "--offline", "--identity", publish_attestation_identity, "not_a_file.txt", @@ -288,6 +294,7 @@ def test_verify_attestation_missing_attestation(caplog: pytest.LogCaptureFixture [ "verify", "attestation", + "--offline", "--identity", publish_attestation_identity, f.name, @@ -310,6 +317,7 @@ def test_verify_attestation_invalid_artifact( [ "verify", "attestation", + "--offline", "--identity", publish_attestation_identity, copied_artifact.as_posix(), @@ -381,7 +389,7 @@ def test_verify_pypi_command( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", url_argument, ] ) @@ -393,8 +401,9 @@ def test_verify_pypi_command_with_local_files(caplog: pytest.LogCaptureFixture) [ "verify", "pypi", + "--offline", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "--provenance-file", pypi_sdist_provenance_path.as_posix(), pypi_sdist_path.as_posix(), @@ -403,6 +412,40 @@ def test_verify_pypi_command_with_local_files(caplog: pytest.LogCaptureFixture) assert f"OK: {pypi_sdist_filename}" in caplog.text +def test_verify_pypi_command_offline_without_local_dist(caplog: pytest.LogCaptureFixture) -> None: + with pytest.raises(SystemExit): + run_main_with_command( + [ + "verify", + "pypi", + "--offline", + "--repository", + "https://github.com/trailofbits/pypi-attestations", + "--provenance-file", + pypi_sdist_provenance_path.as_posix(), + pypi_sdist_url, + ] + ) + assert "The '--offline' option can only be used with local files" in caplog.text + + +def test_verify_pypi_command_offline_without_local_provenance( + caplog: pytest.LogCaptureFixture, +) -> None: + with pytest.raises(SystemExit): + run_main_with_command( + [ + "verify", + "pypi", + "--offline", + "--repository", + "https://github.com/trailofbits/pypi-attestations", + pypi_sdist_path.as_posix(), + ] + ) + assert "The '--offline' option can only be used with local files" in caplog.text + + @online def test_verify_pypi_command_env_fail(caplog: pytest.LogCaptureFixture) -> None: with pytest.raises(SystemExit): @@ -413,7 +456,7 @@ def test_verify_pypi_command_env_fail(caplog: pytest.LogCaptureFixture) -> None: "pypi", "--staging", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", pypi_wheel_url, ] ) @@ -435,7 +478,7 @@ def test_verify_pypi_command_failure_download( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", pypi_wheel_url + "invalid", ] ) @@ -454,7 +497,7 @@ def test_verify_pypi_command_failure_download( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", pypi_wheel_url, ] ) @@ -471,7 +514,7 @@ def test_verify_pypi_invalid_url( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "https://example.com/mypkg-1.2.0.tar.gz", ] ) @@ -489,7 +532,7 @@ def test_verify_pypi_invalid_sdist_filename_pypi( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", f"pypi:{pypi_wheel_filename}.invalid_ext", ] ) @@ -506,7 +549,7 @@ def test_verify_pypi_invalid_sdist_filename_pypi( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "pypi:invalid-sdist-name.tar.gz", # Invalid sdist filename format ] ) @@ -529,7 +572,7 @@ def _download_file(url: str, dest: Path) -> None: "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", pypi_wheel_url, ] ) @@ -558,7 +601,7 @@ def test_verify_pypi_error_getting_provenance( monkeypatch.setattr( pypi_attestations._cli, "_get_distribution_from_arg", - lambda arg: Distribution(name=pypi_wheel_filename, digest="a"), + lambda arg, offline: Distribution(name=pypi_wheel_filename, digest="a"), ) response = requests.Response() response.status_code = status_code @@ -569,7 +612,7 @@ def test_verify_pypi_error_getting_provenance( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", pypi_wheel_url, ] ) @@ -588,7 +631,7 @@ def test_verify_pypi_error_finding_package_info( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "pypi:somefile-1.0.0.tar.gz", ] ) @@ -607,7 +650,7 @@ def test_verify_pypi_error_finding_artifact_url( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "pypi:somefile-1.0.0.tar.gz", ] ) @@ -622,7 +665,7 @@ def test_verify_pypi_error_validating_provenance( monkeypatch.setattr( pypi_attestations._cli, "_get_distribution_from_arg", - lambda arg: Distribution(name=pypi_wheel_filename, digest="a"), + lambda arg, offline: Distribution(name=pypi_wheel_filename, digest="a"), ) response = stub(status_code=200, raise_for_status=lambda: None, text="not json") response.status_code = 200 @@ -633,7 +676,7 @@ def test_verify_pypi_error_validating_provenance( "verify", "pypi", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", pypi_wheel_url, ] ) @@ -647,14 +690,14 @@ def test_verify_pypi_error_validating_provenance( "repository,expected_error", [ ( - "https://gitlab.com/sigstore/sigstore-python", + "https://gitlab.com/trailofbits/pypi-attestations", "Verification failed: provenance was signed by a github.com repository, but expected " "a gitlab.com repository", ), ( "https://github.com/other/repo", - 'Verification failed: provenance was signed by repository "sigstore/sigstore-python", ' - 'expected "other/repo"', + "Verification failed: provenance was signed by repository " + '"trailofbits/pypi-attestations", expected "other/repo"', ), ], ) @@ -683,7 +726,7 @@ def test_verify_pypi_command_publisher_doesnt_match_user_repository( "repository,expected_error", [ # Only github.com or gitlab.com allowed - ("https://example.com/sigstore/sigstore-python", "Unsupported/invalid URL"), + ("https://example.com/trailofbits/pypi-attestations", "Unsupported/invalid URL"), # Only HTTPS allowed ("http://github.com/other/repo", "Unsupported/invalid URL"), ], @@ -714,8 +757,9 @@ def test_verify_pypi_command_local_nonexistent_artifact(caplog: pytest.LogCaptur [ "verify", "pypi", + "--offline", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "--provenance-file", pypi_sdist_provenance_path.as_posix(), "nonexistent-artifact.whl", @@ -730,8 +774,9 @@ def test_verify_pypi_command_local_nonexistent_provenance(caplog: pytest.LogCapt [ "verify", "pypi", + "--offline", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "--provenance-file", "nonexistent-provenance.json", pypi_sdist_path.as_posix(), @@ -746,7 +791,7 @@ def test_verify_pypi_command_local_invalid_provenance( monkeypatch.setattr( pypi_attestations._cli, "_get_distribution_from_arg", - lambda arg: Distribution(name=pypi_sdist_filename, digest="a"), + lambda arg, offline: Distribution(name=pypi_sdist_filename, digest="a"), ) with tempfile.NamedTemporaryFile(suffix=".provenance") as f: @@ -757,8 +802,9 @@ def test_verify_pypi_command_local_invalid_provenance( [ "verify", "pypi", + "--offline", "--repository", - "https://github.com/sigstore/sigstore-python", + "https://github.com/trailofbits/pypi-attestations", "--provenance-file", f.name, pypi_sdist_path.as_posix(), diff --git a/test/test_impl.py b/test/test_impl.py index ba8fa3d..d501ac1 100644 --- a/test/test_impl.py +++ b/test/test_impl.py @@ -18,7 +18,9 @@ import pypi_attestations._impl as impl -ONLINE_TESTS = "CI" in os.environ or "TEST_INTERACTIVE" in os.environ +ONLINE_TESTS = ( + "CI" in os.environ or "TEST_INTERACTIVE" in os.environ +) and "TEST_OFFLINE" not in os.environ online = pytest.mark.skipif(not ONLINE_TESTS, reason="online tests not enabled") @@ -29,7 +31,7 @@ dist = impl.Distribution.from_file(dist_path) dist_bundle_path = _ASSETS / "rfc8785-0.1.2-py3-none-any.whl.sigstore" dist_attestation_path = _ASSETS / "rfc8785-0.1.2-py3-none-any.whl.attestation" -pypi_attestations_attestation = _ASSETS / "pypi_attestations-0.0.16.tar.gz.attestation" +pypi_attestations_attestation = _ASSETS / "pypi_attestations-0.0.19.tar.gz.publish.attestation" # produced by actions/attest@v1 gh_signed_dist_path = _ASSETS / "pypi_attestation_models-0.0.4a2.tar.gz" @@ -133,7 +135,7 @@ def test_verify_github_attested(self) -> None: bundle = Bundle.from_json(gh_signed_dist_bundle_path.read_bytes()) attestation = impl.Attestation.from_bundle(bundle) - predicate_type, predicate = attestation.verify(pol, gh_signed_dist) + predicate_type, predicate = attestation.verify(pol, gh_signed_dist, offline=True) assert predicate_type == "https://docs.pypi.org/attestations/publish/v1" assert predicate == {} @@ -146,7 +148,7 @@ def test_verify_from_github_publisher(self) -> None: bundle = Bundle.from_json(gh_signed_dist_bundle_path.read_bytes()) attestation = impl.Attestation.from_bundle(bundle) - predicate_type, predicate = attestation.verify(publisher, gh_signed_dist) + predicate_type, predicate = attestation.verify(publisher, gh_signed_dist, offline=True) assert predicate_type == "https://docs.pypi.org/attestations/publish/v1" assert predicate == {} @@ -157,7 +159,7 @@ def test_verify_from_gitlab_publisher(self) -> None: ) attestation = impl.Attestation.model_validate_json(gl_attestation_path.read_bytes()) - predicate_type, predicate = attestation.verify(publisher, gl_signed_dist) + predicate_type, predicate = attestation.verify(publisher, gl_signed_dist, offline=True) assert predicate_type == "https://docs.pypi.org/attestations/publish/v1" assert predicate is None @@ -171,7 +173,7 @@ def test_verify_from_github_publisher_wrong(self) -> None: attestation = impl.Attestation.from_bundle(bundle) with pytest.raises(impl.VerificationError, match=r"Build Config URI .+ does not match"): - attestation.verify(publisher, gh_signed_dist) + attestation.verify(publisher, gh_signed_dist, offline=True) def test_verify_from_gitlab_publisher_wrong(self) -> None: publisher = impl.GitLabPublisher( @@ -181,7 +183,7 @@ def test_verify_from_gitlab_publisher_wrong(self) -> None: attestation = impl.Attestation.model_validate_json(gl_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match=r"Build Config URI .+ does not match"): - attestation.verify(publisher, gl_signed_dist) + attestation.verify(publisher, gl_signed_dist, offline=True) def test_verify(self) -> None: # Our checked-in asset has this identity. @@ -190,7 +192,7 @@ def test_verify(self) -> None: ) attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) - predicate_type, predicate = attestation.verify(pol, dist, staging=True) + predicate_type, predicate = attestation.verify(pol, dist, staging=True, offline=True) assert attestation.statement["_type"] == "https://in-toto.io/Statement/v1" assert ( @@ -202,7 +204,7 @@ def test_verify(self) -> None: # convert the attestation to a bundle and verify it that way too bundle = attestation.to_bundle() - Verifier.staging().verify_dsse(bundle, policy.UnsafeNoOp()) + Verifier.staging(offline=True).verify_dsse(bundle, policy.UnsafeNoOp()) def test_verify_digest_mismatch(self, tmp_path: Path) -> None: # Our checked-in asset has this identity. @@ -221,7 +223,7 @@ def test_verify_digest_mismatch(self, tmp_path: Path) -> None: with pytest.raises( impl.VerificationError, match="subject does not match distribution digest" ): - attestation.verify(pol, modified_dist, staging=True) + attestation.verify(pol, modified_dist, staging=True, offline=True) def test_verify_filename_mismatch(self, tmp_path: Path) -> None: # Our checked-in asset has this identity. @@ -240,7 +242,7 @@ def test_verify_filename_mismatch(self, tmp_path: Path) -> None: with pytest.raises( impl.VerificationError, match="subject does not match distribution name" ): - attestation.verify(pol, different_name_dist, staging=True) + attestation.verify(pol, different_name_dist, staging=True, offline=True) def test_verify_policy_mismatch(self) -> None: # Wrong identity. @@ -249,11 +251,11 @@ def test_verify_policy_mismatch(self) -> None: attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match=r"Certificate's SANs do not match"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_verify_wrong_envelope(self, monkeypatch: pytest.MonkeyPatch) -> None: staging = pretend.call_recorder( - lambda: pretend.stub( + lambda offline: pretend.stub( verify_dsse=pretend.call_recorder(lambda bundle, policy: ("fake-type", None)) ) ) @@ -263,11 +265,11 @@ def test_verify_wrong_envelope(self, monkeypatch: pytest.MonkeyPatch) -> None: attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match="expected JSON envelope, got fake-type"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_verify_bad_payload(self, monkeypatch: pytest.MonkeyPatch) -> None: staging = pretend.call_recorder( - lambda: pretend.stub( + lambda offline: pretend.stub( verify_dsse=pretend.call_recorder( lambda bundle, policy: ("application/vnd.in-toto+json", b"invalid json") ) @@ -279,7 +281,7 @@ def test_verify_bad_payload(self, monkeypatch: pytest.MonkeyPatch) -> None: attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match="invalid statement"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_verify_too_many_subjects(self, monkeypatch: pytest.MonkeyPatch) -> None: statement = ( @@ -296,7 +298,7 @@ def test_verify_too_many_subjects(self, monkeypatch: pytest.MonkeyPatch) -> None ) staging = pretend.call_recorder( - lambda: pretend.stub( + lambda offline: pretend.stub( verify_dsse=pretend.call_recorder( lambda bundle, policy: ( "application/vnd.in-toto+json", @@ -311,7 +313,7 @@ def test_verify_too_many_subjects(self, monkeypatch: pytest.MonkeyPatch) -> None attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match="too many subjects in statement"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_verify_subject_missing_name(self, monkeypatch: pytest.MonkeyPatch) -> None: statement = ( @@ -327,7 +329,7 @@ def test_verify_subject_missing_name(self, monkeypatch: pytest.MonkeyPatch) -> N ) staging = pretend.call_recorder( - lambda: pretend.stub( + lambda offline: pretend.stub( verify_dsse=pretend.call_recorder( lambda bundle, policy: ( "application/vnd.in-toto+json", @@ -342,7 +344,7 @@ def test_verify_subject_missing_name(self, monkeypatch: pytest.MonkeyPatch) -> N attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match="invalid subject: missing name"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_verify_subject_invalid_name(self, monkeypatch: pytest.MonkeyPatch) -> None: statement = ( @@ -361,7 +363,7 @@ def test_verify_subject_invalid_name(self, monkeypatch: pytest.MonkeyPatch) -> N ) staging = pretend.call_recorder( - lambda: pretend.stub( + lambda offline: pretend.stub( verify_dsse=pretend.call_recorder( lambda bundle, policy: ( "application/vnd.in-toto+json", @@ -376,7 +378,7 @@ def test_verify_subject_invalid_name(self, monkeypatch: pytest.MonkeyPatch) -> N attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match="invalid subject: Invalid wheel filename"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_verify_unknown_attestation_type(self, monkeypatch: pytest.MonkeyPatch) -> None: statement = ( @@ -401,7 +403,7 @@ def test_verify_unknown_attestation_type(self, monkeypatch: pytest.MonkeyPatch) ) staging = pretend.call_recorder( - lambda: pretend.stub( + lambda offline: pretend.stub( verify_dsse=pretend.call_recorder( lambda bundle, policy: ( "application/vnd.in-toto+json", @@ -416,7 +418,7 @@ def test_verify_unknown_attestation_type(self, monkeypatch: pytest.MonkeyPatch) attestation = impl.Attestation.model_validate_json(dist_attestation_path.read_bytes()) with pytest.raises(impl.VerificationError, match="unknown attestation type: foo"): - attestation.verify(pol, dist, staging=True) + attestation.verify(pol, dist, staging=True, offline=True) def test_certificate_claims(self) -> None: attestation = impl.Attestation.model_validate_json( @@ -427,25 +429,25 @@ def test_certificate_claims(self) -> None: ("1.3.6.1.4.1.57264.1.8", "https://token.actions.githubusercontent.com"), ( "1.3.6.1.4.1.57264.1.9", - "https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.16", + "https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.19", ), - ("1.3.6.1.4.1.57264.1.10", "58c872e67c03c9c031ba71b1654ff542ff290cd7"), + ("1.3.6.1.4.1.57264.1.10", "08802efe1f8e5fec4ad842d6b8ce97656092ee72"), ("1.3.6.1.4.1.57264.1.11", "github-hosted"), ("1.3.6.1.4.1.57264.1.12", "https://github.com/trailofbits/pypi-attestations"), - ("1.3.6.1.4.1.57264.1.13", "58c872e67c03c9c031ba71b1654ff542ff290cd7"), - ("1.3.6.1.4.1.57264.1.14", "refs/tags/v0.0.16"), + ("1.3.6.1.4.1.57264.1.13", "08802efe1f8e5fec4ad842d6b8ce97656092ee72"), + ("1.3.6.1.4.1.57264.1.14", "refs/tags/v0.0.19"), ("1.3.6.1.4.1.57264.1.15", "772247423"), ("1.3.6.1.4.1.57264.1.16", "https://github.com/trailofbits"), ("1.3.6.1.4.1.57264.1.17", "2314423"), ( "1.3.6.1.4.1.57264.1.18", - "https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.16", + "https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.19", ), - ("1.3.6.1.4.1.57264.1.19", "58c872e67c03c9c031ba71b1654ff542ff290cd7"), + ("1.3.6.1.4.1.57264.1.19", "08802efe1f8e5fec4ad842d6b8ce97656092ee72"), ("1.3.6.1.4.1.57264.1.20", "release"), ( "1.3.6.1.4.1.57264.1.21", - "https://github.com/trailofbits/pypi-attestations/actions/runs/11732568384/attempts/1", + "https://github.com/trailofbits/pypi-attestations/actions/runs/12169989787/attempts/1", ), ("1.3.6.1.4.1.57264.1.22", "public"), }